mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-27 01:07:10 +00:00
chore: add simple validation for static dialer-proxy config (#2551)
Currently, it can only validate whether a cycle exists in proxies, and cannot determine if it is caused by groups.
This commit is contained in:
@@ -955,6 +955,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
)
|
||||
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||
}
|
||||
|
||||
// validate dialer-proxy references
|
||||
if err := validateDialerProxies(proxies); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
// Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order.
|
||||
@@ -143,6 +144,64 @@ func proxyGroupsDagSort(groupsConfig []map[string]any) error {
|
||||
return fmt.Errorf("loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements)
|
||||
}
|
||||
|
||||
// validateDialerProxies checks if all dialer-proxy references are valid
|
||||
func validateDialerProxies(proxies map[string]C.Proxy) error {
|
||||
graph := make(map[string]string) // proxy name -> dialer-proxy name
|
||||
|
||||
// collect all proxies with dialer-proxy configured
|
||||
for name, proxy := range proxies {
|
||||
dialerProxy := proxy.ProxyInfo().DialerProxy
|
||||
if dialerProxy != "" {
|
||||
// validate each dialer-proxy reference
|
||||
_, exist := proxies[dialerProxy]
|
||||
if !exist {
|
||||
return fmt.Errorf("proxy [%s] dialer-proxy [%s] not found", name, dialerProxy)
|
||||
}
|
||||
|
||||
// build dependency graph
|
||||
graph[name] = dialerProxy
|
||||
}
|
||||
}
|
||||
|
||||
// perform depth-first search to detect cycles for each proxy
|
||||
for name := range graph {
|
||||
visited := make(map[string]bool, len(graph))
|
||||
path := make([]string, 0, len(graph))
|
||||
if validateDialerProxiesHasCycle(name, graph, visited, path) {
|
||||
return fmt.Errorf("proxy [%s] has circular dialer-proxy dependency", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateDialerProxiesHasCycle performs DFS to detect if there's a cycle starting from current proxy
|
||||
func validateDialerProxiesHasCycle(current string, graph map[string]string, visited map[string]bool, path []string) bool {
|
||||
// check if current is already in path (cycle detected)
|
||||
for _, p := range path {
|
||||
if p == current {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// already visited and no cycle
|
||||
if visited[current] {
|
||||
return false
|
||||
}
|
||||
|
||||
visited[current] = true
|
||||
path = append(path, current)
|
||||
|
||||
// check dialer-proxy of current proxy
|
||||
if dialerProxy, exists := graph[current]; exists {
|
||||
if validateDialerProxiesHasCycle(dialerProxy, graph, visited, path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func verifyIP6() bool {
|
||||
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_SYSTEM_IPV6_CHECK")); skip {
|
||||
return true
|
||||
|
||||
79
config/utils_test.go
Normal file
79
config/utils_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateDialerProxies(t *testing.T) {
|
||||
testCases := []struct {
|
||||
testName string
|
||||
proxy []map[string]any
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
testName: "ValidReference",
|
||||
proxy: []map[string]any{ // create proxy with valid dialer-proxy reference
|
||||
{"name": "base-proxy", "type": "socks5", "server": "127.0.0.1", "port": 1080},
|
||||
{"name": "proxy-with-dialer", "type": "socks5", "server": "127.0.0.1", "port": 1081, "dialer-proxy": "base-proxy"},
|
||||
},
|
||||
errContains: "",
|
||||
},
|
||||
{
|
||||
testName: "NotFoundReference",
|
||||
proxy: []map[string]any{ // create proxy with non-existent dialer-proxy reference
|
||||
{"name": "proxy-with-dialer", "type": "socks5", "server": "127.0.0.1", "port": 1081, "dialer-proxy": "non-existent-proxy"},
|
||||
},
|
||||
errContains: "not found",
|
||||
},
|
||||
{
|
||||
testName: "CircularDependency",
|
||||
proxy: []map[string]any{
|
||||
// create proxy A that references B
|
||||
{"name": "proxy-a", "type": "socks5", "server": "127.0.0.1", "port": 1080, "dialer-proxy": "proxy-c"},
|
||||
// create proxy B that references C
|
||||
{"name": "proxy-b", "type": "socks5", "server": "127.0.0.1", "port": 1081, "dialer-proxy": "proxy-a"},
|
||||
// create proxy C that references A (creates cycle)
|
||||
{"name": "proxy-c", "type": "socks5", "server": "127.0.0.1", "port": 1082, "dialer-proxy": "proxy-a"},
|
||||
},
|
||||
errContains: "circular",
|
||||
},
|
||||
{
|
||||
testName: "ComplexChain",
|
||||
proxy: []map[string]any{ // create a valid chain: proxy-d -> proxy-c -> proxy-b -> proxy-a
|
||||
{"name": "proxy-a", "type": "socks5", "server": "127.0.0.1", "port": 1080},
|
||||
{"name": "proxy-b", "type": "socks5", "server": "127.0.0.1", "port": 1081, "dialer-proxy": "proxy-a"},
|
||||
{"name": "proxy-c", "type": "socks5", "server": "127.0.0.1", "port": 1082, "dialer-proxy": "proxy-b"},
|
||||
{"name": "proxy-d", "type": "socks5", "server": "127.0.0.1", "port": 1083, "dialer-proxy": "proxy-c"},
|
||||
},
|
||||
errContains: "",
|
||||
},
|
||||
{
|
||||
testName: "EmptyDialerProxy",
|
||||
proxy: []map[string]any{ // create proxy without dialer-proxy
|
||||
{"name": "simple-proxy", "type": "socks5", "server": "127.0.0.1", "port": 1080},
|
||||
},
|
||||
errContains: "",
|
||||
},
|
||||
{
|
||||
testName: "SelfReference",
|
||||
proxy: []map[string]any{ // create proxy that references itself
|
||||
{"name": "self-proxy", "type": "socks5", "server": "127.0.0.1", "port": 1080, "dialer-proxy": "self-proxy"},
|
||||
},
|
||||
errContains: "circular",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.testName, func(t *testing.T) {
|
||||
config := RawConfig{Proxy: testCase.proxy}
|
||||
_, _, err := parseProxies(&config)
|
||||
if testCase.errContains == "" {
|
||||
assert.NoError(t, err, testCase.testName)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, testCase.errContains, testCase.testName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user