Feature: add experimental provider

This commit is contained in:
Dreamacro
2019-12-08 12:17:24 +08:00
parent 4d7096f451
commit c427bc89ef
33 changed files with 1247 additions and 620 deletions

View File

@ -7,8 +7,9 @@ import (
"os"
"strings"
adapters "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/outboundgroup"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/component/auth"
trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/component/fakeip"
@ -68,6 +69,7 @@ type Config struct {
Rules []C.Rule
Users []auth.AuthUser
Proxies map[string]C.Proxy
Providers map[string]provider.ProxyProvider
}
type rawDNS struct {
@ -99,12 +101,13 @@ type rawConfig struct {
ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"`
Hosts map[string]string `yaml:"hosts"`
DNS rawDNS `yaml:"dns"`
Experimental Experimental `yaml:"experimental"`
Proxy []map[string]interface{} `yaml:"Proxy"`
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
Rule []string `yaml:"Rule"`
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-provider"`
Hosts map[string]string `yaml:"hosts"`
DNS rawDNS `yaml:"dns"`
Experimental Experimental `yaml:"experimental"`
Proxy []map[string]interface{} `yaml:"Proxy"`
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
Rule []string `yaml:"Rule"`
}
// Parse config
@ -146,11 +149,12 @@ func Parse(buf []byte) (*Config, error) {
}
config.General = general
proxies, err := parseProxies(rawCfg)
proxies, providers, err := parseProxies(rawCfg)
if err != nil {
return nil, err
}
config.Proxies = proxies
config.Providers = providers
rules, err := parseRules(rawCfg, proxies)
if err != nil {
@ -171,7 +175,6 @@ func Parse(buf []byte) (*Config, error) {
config.Hosts = hosts
config.Users = parseAuthentication(rawCfg.Authentication)
return config, nil
}
@ -210,75 +213,38 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
return general, nil
}
func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
proxies := make(map[string]C.Proxy)
func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) {
proxies = make(map[string]C.Proxy)
providersMap = make(map[string]provider.ProxyProvider)
proxyList := []string{}
proxiesConfig := cfg.Proxy
groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
defer func() {
// Destroy already created provider when err != nil
if err != nil {
for _, provider := range providersMap {
provider.Destroy()
}
}
}()
proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
proxyList = append(proxyList, "DIRECT", "REJECT")
// parse proxy
for idx, mapping := range proxiesConfig {
proxyType, existType := mapping["type"].(string)
if !existType {
return nil, fmt.Errorf("Proxy %d missing type", idx)
}
var proxy C.ProxyAdapter
err := fmt.Errorf("cannot parse")
switch proxyType {
case "ss":
ssOption := &adapters.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption)
if err != nil {
break
}
proxy, err = adapters.NewShadowSocks(*ssOption)
case "socks5":
socksOption := &adapters.Socks5Option{}
err = decoder.Decode(mapping, socksOption)
if err != nil {
break
}
proxy = adapters.NewSocks5(*socksOption)
case "http":
httpOption := &adapters.HttpOption{}
err = decoder.Decode(mapping, httpOption)
if err != nil {
break
}
proxy = adapters.NewHttp(*httpOption)
case "vmess":
vmessOption := &adapters.VmessOption{}
err = decoder.Decode(mapping, vmessOption)
if err != nil {
break
}
proxy, err = adapters.NewVmess(*vmessOption)
case "snell":
snellOption := &adapters.SnellOption{}
err = decoder.Decode(mapping, snellOption)
if err != nil {
break
}
proxy, err = adapters.NewSnell(*snellOption)
default:
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
}
proxy, err := outbound.ParseProxy(mapping)
if err != nil {
return nil, fmt.Errorf("Proxy [%d]: %s", idx, err.Error())
return nil, nil, fmt.Errorf("Proxy %d: %w", idx, err)
}
if _, exist := proxies[proxy.Name()]; exist {
return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
return nil, nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
}
proxies[proxy.Name()] = adapters.NewProxy(proxy)
proxies[proxy.Name()] = proxy
proxyList = append(proxyList, proxy.Name())
}
@ -286,95 +252,62 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
for idx, mapping := range groupsConfig {
groupName, existName := mapping["name"].(string)
if !existName {
return nil, fmt.Errorf("ProxyGroup %d: missing name", idx)
return nil, nil, fmt.Errorf("ProxyGroup %d: missing name", idx)
}
proxyList = append(proxyList, groupName)
}
// check if any loop exists and sort the ProxyGroups
if err := proxyGroupsDagSort(groupsConfig, decoder); err != nil {
return nil, err
if err := proxyGroupsDagSort(groupsConfig); err != nil {
return nil, nil, err
}
// parse and initial providers
for name, mapping := range providersConfig {
if name == provider.ReservedName {
return nil, nil, fmt.Errorf("can not defined a provider called `%s`", provider.ReservedName)
}
pd, err := provider.ParseProxyProvider(name, mapping)
if err != nil {
return nil, nil, err
}
providersMap[name] = pd
}
for _, provider := range providersMap {
log.Infoln("Start initial provider %s", provider.Name())
if err := provider.Initial(); err != nil {
return nil, nil, err
}
}
// parse proxy group
for _, mapping := range groupsConfig {
groupType, existType := mapping["type"].(string)
groupName, _ := mapping["name"].(string)
if !existType {
return nil, fmt.Errorf("ProxyGroup %s: missing type", groupName)
}
if _, exist := proxies[groupName]; exist {
return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
}
var group C.ProxyAdapter
ps := []C.Proxy{}
err := fmt.Errorf("cannot parse")
switch groupType {
case "url-test":
urlTestOption := &adapters.URLTestOption{}
err = decoder.Decode(mapping, urlTestOption)
if err != nil {
break
}
ps, err = getProxies(proxies, urlTestOption.Proxies)
if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
}
group, err = adapters.NewURLTest(*urlTestOption, ps)
case "select":
selectorOption := &adapters.SelectorOption{}
err = decoder.Decode(mapping, selectorOption)
if err != nil {
break
}
ps, err = getProxies(proxies, selectorOption.Proxies)
if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
}
group, err = adapters.NewSelector(selectorOption.Name, ps)
case "fallback":
fallbackOption := &adapters.FallbackOption{}
err = decoder.Decode(mapping, fallbackOption)
if err != nil {
break
}
ps, err = getProxies(proxies, fallbackOption.Proxies)
if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
}
group, err = adapters.NewFallback(*fallbackOption, ps)
case "load-balance":
loadBalanceOption := &adapters.LoadBalanceOption{}
err = decoder.Decode(mapping, loadBalanceOption)
if err != nil {
break
}
ps, err = getProxies(proxies, loadBalanceOption.Proxies)
if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
}
group, err = adapters.NewLoadBalance(*loadBalanceOption, ps)
}
for idx, mapping := range groupsConfig {
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
if err != nil {
return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
return nil, nil, fmt.Errorf("ProxyGroup[%d]: %w", idx, err)
}
proxies[groupName] = adapters.NewProxy(group)
groupName := group.Name()
if _, exist := proxies[groupName]; exist {
return nil, nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
}
proxies[groupName] = outbound.NewProxy(group)
}
ps := []C.Proxy{}
for _, v := range proxyList {
ps = append(ps, proxies[v])
}
pd, _ := provider.NewCompatibleProvier(provider.ReservedName, ps, nil)
providersMap[provider.ReservedName] = pd
global, _ := adapters.NewSelector("GLOBAL", ps)
proxies["GLOBAL"] = adapters.NewProxy(global)
return proxies, nil
global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd})
proxies["GLOBAL"] = outbound.NewProxy(global)
return proxies, providersMap, nil
}
func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {

View File

@ -4,9 +4,8 @@ import (
"fmt"
"strings"
adapters "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/outboundgroup"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
)
func trimArr(arr []string) (r []string) {
@ -16,18 +15,6 @@ func trimArr(arr []string) (r []string) {
return
}
func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
var ps []C.Proxy
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
ps = append(ps, p)
}
return ps, nil
}
func or(pointers ...*int) *int {
for _, p := range pointers {
if p != nil {
@ -40,8 +27,7 @@ func or(pointers ...*int) *int {
// Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order.
// Meanwhile, record the original index in the config file.
// If loop is detected, return an error with location of loop.
func proxyGroupsDagSort(groupsConfig []map[string]interface{}, decoder *structure.Decoder) error {
func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error {
type graphNode struct {
indegree int
// topological order
@ -50,34 +36,36 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}, decoder *structur
data map[string]interface{}
// `outdegree` and `from` are used in loop locating
outdegree int
option *outboundgroup.GroupCommonOption
from []string
}
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
graph := make(map[string]*graphNode)
// Step 1.1 build dependency graph
for _, mapping := range groupsConfig {
option := &adapters.ProxyGroupOption{}
err := decoder.Decode(mapping, option)
groupName := option.Name
if err != nil {
return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
option := &outboundgroup.GroupCommonOption{}
if err := decoder.Decode(mapping, option); err != nil {
return fmt.Errorf("ProxyGroup %s: %s", option.Name, err.Error())
}
groupName := option.Name
if node, ok := graph[groupName]; ok {
if node.data != nil {
return fmt.Errorf("ProxyGroup %s: duplicate group name", groupName)
}
node.data = mapping
node.option = option
} else {
graph[groupName] = &graphNode{0, -1, mapping, 0, nil}
graph[groupName] = &graphNode{0, -1, mapping, 0, option, nil}
}
for _, proxy := range option.Proxies {
if node, ex := graph[proxy]; ex {
node.indegree++
} else {
graph[proxy] = &graphNode{1, -1, nil, 0, nil}
graph[proxy] = &graphNode{1, -1, nil, 0, nil, nil}
}
}
}
@ -95,14 +83,19 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}, decoder *structur
for ; len(queue) > 0; queue = queue[1:] {
name := queue[0]
node := graph[name]
if node.data != nil {
if node.option != nil {
index++
groupsConfig[len(groupsConfig)-index] = node.data
for _, proxy := range node.data["proxies"].([]interface{}) {
child := graph[proxy.(string)]
if len(node.option.Proxies) == 0 {
delete(graph, name)
continue
}
for _, proxy := range node.option.Proxies {
child := graph[proxy]
child.indegree--
if child.indegree == 0 {
queue = append(queue, proxy.(string))
queue = append(queue, proxy)
}
}
}
@ -117,12 +110,17 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}, decoder *structur
// if loop is detected, locate the loop and throw an error
// Step 2.1 rebuild the graph, fill `outdegree` and `from` filed
for name, node := range graph {
if node.data == nil {
if node.option == nil {
continue
}
for _, proxy := range node.data["proxies"].([]interface{}) {
if len(node.option.Proxies) == 0 {
continue
}
for _, proxy := range node.option.Proxies {
node.outdegree++
child := graph[proxy.(string)]
child := graph[proxy]
if child.from == nil {
child.from = make([]string, 0, child.indegree)
}