2018-07-26 00:04:59 +08:00
|
|
|
package config
|
2018-06-10 22:50:03 +08:00
|
|
|
|
|
|
|
import (
|
2018-08-12 02:23:46 +08:00
|
|
|
"fmt"
|
2018-06-10 22:50:03 +08:00
|
|
|
"strings"
|
2018-10-18 23:24:04 +08:00
|
|
|
|
|
|
|
C "github.com/Dreamacro/clash/constant"
|
2018-06-10 22:50:03 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
func trimArr(arr []string) (r []string) {
|
|
|
|
for _, e := range arr {
|
|
|
|
r = append(r, strings.Trim(e, " "))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2018-08-12 02:23:46 +08:00
|
|
|
|
2018-10-18 23:24:04 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-08-26 22:43:38 +08:00
|
|
|
func or(pointers ...*int) *int {
|
|
|
|
for _, p := range pointers {
|
|
|
|
if p != nil {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pointers[len(pointers)-1]
|
|
|
|
}
|
2019-08-12 10:11:44 +08:00
|
|
|
|
|
|
|
// 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{}) error {
|
|
|
|
|
|
|
|
type Node struct {
|
|
|
|
indegree int
|
|
|
|
// topological order
|
|
|
|
topo int
|
|
|
|
// the origional data in `groupsConfig`
|
|
|
|
data map[string]interface{}
|
|
|
|
// `outdegree` and `from` are used in loop locating
|
|
|
|
outdegree int
|
|
|
|
from []string
|
|
|
|
}
|
|
|
|
|
|
|
|
graph := make(map[string]*Node)
|
|
|
|
|
|
|
|
// Step 1.1 build dependency graph
|
|
|
|
for idx, mapping := range groupsConfig {
|
|
|
|
// record original order in config file.
|
|
|
|
// this field can be used determinate the display order in FrontEnd.
|
|
|
|
mapping["configIdx"] = idx
|
|
|
|
groupName, existName := mapping["name"].(string)
|
|
|
|
if !existName {
|
|
|
|
return fmt.Errorf("ProxyGroup %d: missing name", idx)
|
|
|
|
}
|
|
|
|
if node, ok := graph[groupName]; ok {
|
|
|
|
if node.data != nil {
|
|
|
|
return fmt.Errorf("ProxyGroup %s: duplicate group name", groupName)
|
|
|
|
}
|
|
|
|
node.data = mapping
|
|
|
|
} else {
|
|
|
|
graph[groupName] = &Node{0, -1, mapping, 0, nil}
|
|
|
|
}
|
|
|
|
proxies, existProxies := mapping["proxies"]
|
|
|
|
if !existProxies {
|
|
|
|
return fmt.Errorf("ProxyGroup %s: the `proxies` field is requried", groupName)
|
|
|
|
}
|
|
|
|
for _, proxy := range proxies.([]interface{}) {
|
|
|
|
proxy := proxy.(string)
|
|
|
|
if node, ex := graph[proxy]; ex {
|
|
|
|
node.indegree++
|
|
|
|
} else {
|
|
|
|
graph[proxy] = &Node{1, -1, nil, 0, nil}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Step 1.2 Topological Sort
|
|
|
|
// topological index of **ProxyGroup**
|
|
|
|
index := 0
|
|
|
|
queue := make([]string, 0)
|
|
|
|
for name, node := range graph {
|
|
|
|
// in the begning, put nodes that have `node.indegree == 0` into queue.
|
|
|
|
if node.indegree == 0 {
|
|
|
|
queue = append(queue, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// every element in queue have indegree == 0
|
|
|
|
for ; len(queue) > 0; queue = queue[1:] {
|
|
|
|
name := queue[0]
|
|
|
|
node := graph[name]
|
|
|
|
if node.data != nil {
|
|
|
|
index++
|
|
|
|
groupsConfig[len(groupsConfig)-index] = node.data
|
|
|
|
for _, proxy := range node.data["proxies"].([]interface{}) {
|
|
|
|
child := graph[proxy.(string)]
|
|
|
|
child.indegree--
|
|
|
|
if child.indegree == 0 {
|
|
|
|
queue = append(queue, proxy.(string))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete(graph, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// no loop is detected, return sorted ProxyGroup
|
|
|
|
if len(graph) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, proxy := range node.data["proxies"].([]interface{}) {
|
|
|
|
node.outdegree++
|
|
|
|
child := graph[proxy.(string)]
|
|
|
|
if child.from == nil {
|
|
|
|
child.from = make([]string, 0, child.indegree)
|
|
|
|
}
|
|
|
|
child.from = append(child.from, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Step 2.2 remove nodes outside the loop. so that we have only the loops remain in `graph`
|
|
|
|
queue = make([]string, 0)
|
|
|
|
// initialize queue with node have outdegree == 0
|
|
|
|
for name, node := range graph {
|
|
|
|
if node.outdegree == 0 {
|
|
|
|
queue = append(queue, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// every element in queue have outdegree == 0
|
|
|
|
for ; len(queue) > 0; queue = queue[1:] {
|
|
|
|
name := queue[0]
|
|
|
|
node := graph[name]
|
|
|
|
for _, f := range node.from {
|
|
|
|
graph[f].outdegree--
|
|
|
|
if graph[f].outdegree == 0 {
|
|
|
|
queue = append(queue, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete(graph, name)
|
|
|
|
}
|
|
|
|
// Step 2.3 report the elements in loop
|
|
|
|
loopElements := make([]string, 0, len(graph))
|
|
|
|
for name := range graph {
|
|
|
|
loopElements = append(loopElements, name)
|
|
|
|
delete(graph, name)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("Loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements)
|
|
|
|
}
|