From 7243392af2ae758165b0acfa462ce476c408e9a9 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Wed, 8 May 2024 00:22:36 +0800 Subject: [PATCH] Fix DNS servers with same tag wrongly merged --- infra/conf/merge/merge_test.go | 104 ++++++++++++++++++++++++++++++++- infra/conf/merge/rules.go | 14 +++-- infra/conf/merge/tag.go | 25 +++++--- 3 files changed, 129 insertions(+), 14 deletions(-) diff --git a/infra/conf/merge/merge_test.go b/infra/conf/merge/merge_test.go index 6f393a9e3..331d03b71 100644 --- a/infra/conf/merge/merge_test.go +++ b/infra/conf/merge/merge_test.go @@ -78,7 +78,7 @@ func TestMergeTag(t *testing.T) { "outboundTag": "out-2" }] } - } + } ` expected := ` { @@ -194,6 +194,108 @@ func TestMergeTagDeep(t *testing.T) { assertResult(t, m, expected) } +func TestNoMergeDnsServers(t *testing.T) { + json1 := ` + { + "dns": { + "queryStrategy": "UseIPv4", + "fallbackStrategy": "disabled-if-any-match", + "domainMatcher": "mph", + "servers": [ + { + "address": "aaa.bbb.ccc.ddd", + "port": 53, + "domains": [ + "geosite:cn" + ], + "tag": "dns-domestic" + }, + { + "address": "114.114.114.114", + "port": 53, + "domains": [ + "geosite:cn" + ], + "tag": "dns-domestic" + }, + { + "address": "https://1.1.1.1/dns-query", + "tag": "dns-international" + } + ] + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "domainMatcher": "mph", + "rules": [ + { + "type": "field", + "inboundTag": "dns-domestic", + "outboundTag": "direct" + }, + { + "type": "field", + "inboundTag": "dns-international", + "outboundTag": "proxy" + } + ] + } + } +` + expected := ` + { + "dns": { + "queryStrategy": "UseIPv4", + "fallbackStrategy": "disabled-if-any-match", + "domainMatcher": "mph", + "servers": [ + { + "address": "aaa.bbb.ccc.ddd", + "port": 53, + "domains": [ + "geosite:cn" + ], + "tag": "dns-domestic" + }, + { + "address": "114.114.114.114", + "port": 53, + "domains": [ + "geosite:cn" + ], + "tag": "dns-domestic" + }, + { + "address": "https://1.1.1.1/dns-query", + "tag": "dns-international" + } + ] + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "domainMatcher": "mph", + "rules": [ + { + "type": "field", + "inboundTag": "dns-domestic", + "outboundTag": "direct" + }, + { + "type": "field", + "inboundTag": "dns-international", + "outboundTag": "proxy" + } + ] + } + } +` + m, err := merge.JSONs([][]byte{[]byte(json1)}) + if err != nil { + t.Error(err) + } + assertResult(t, m, expected) +} + func assertResult(t *testing.T, value []byte, expected string) { v := make(map[string]interface{}) err := serial.DecodeJSON(bytes.NewReader(value), &v) diff --git a/infra/conf/merge/rules.go b/infra/conf/merge/rules.go index 5ba3f909a..319bde9df 100644 --- a/infra/conf/merge/rules.go +++ b/infra/conf/merge/rules.go @@ -4,6 +4,8 @@ package merge +import "fmt" + const ( priorityKey string = "_priority" tagKey string = "_tag" @@ -11,7 +13,7 @@ const ( // ApplyRules applies merge rules according to _tag, _priority fields, and remove them func ApplyRules(m map[string]interface{}) error { - err := sortMergeSlices(m) + err := sortMergeSlices(m, "") if err != nil { return err } @@ -20,22 +22,22 @@ func ApplyRules(m map[string]interface{}) error { } // sortMergeSlices enumerates all slices in a map, to sort by priority and merge by tag -func sortMergeSlices(target map[string]interface{}) error { +func sortMergeSlices(target map[string]interface{}, path string) error { for key, value := range target { if slice, ok := value.([]interface{}); ok { sortByPriority(slice) - s, err := mergeSameTag(slice) + s, err := mergeSameTag(slice, fmt.Sprintf("%s.%s", path, key)) if err != nil { return err } target[key] = s - for _, item := range s { + for i, item := range s { if m, ok := item.(map[string]interface{}); ok { - sortMergeSlices(m) + sortMergeSlices(m, fmt.Sprintf("%s.%s.%d", path, key, i)) } } } else if field, ok := value.(map[string]interface{}); ok { - sortMergeSlices(field) + sortMergeSlices(field, fmt.Sprintf("%s.%s", path, key)) } } return nil diff --git a/infra/conf/merge/tag.go b/infra/conf/merge/tag.go index 6c2632e70..3850d5512 100644 --- a/infra/conf/merge/tag.go +++ b/infra/conf/merge/tag.go @@ -4,10 +4,16 @@ package merge -func getTag(v map[string]interface{}) string { - if field, ok := v["tag"]; ok { - if t, ok := field.(string); ok { - return t +import ( + "strings" +) + +func getTag(v map[string]interface{}, tagKeyOnly bool) string { + if !tagKeyOnly { + if field, ok := v["tag"]; ok { + if t, ok := field.(string); ok { + return t + } } } if field, ok := v[tagKey]; ok { @@ -18,16 +24,21 @@ func getTag(v map[string]interface{}) string { return "" } -func mergeSameTag(s []interface{}) ([]interface{}, error) { +func mergeSameTag(s []interface{}, path string) ([]interface{}, error) { // from: [a,"",b,"",a,"",b,""] // to: [a,"",b,"",merged,"",merged,""] merged := &struct{}{} + tagKeyOnly := false + // See https://github.com/v2fly/v2ray-core/issues/2981 + if strings.HasPrefix(path, ".dns.servers") { + tagKeyOnly = true + } for i, item1 := range s { map1, ok := item1.(map[string]interface{}) if !ok { continue } - tag1 := getTag(map1) + tag1 := getTag(map1, tagKeyOnly) if tag1 == "" { continue } @@ -36,7 +47,7 @@ func mergeSameTag(s []interface{}) ([]interface{}, error) { if !ok { continue } - tag2 := getTag(map2) + tag2 := getTag(map2, tagKeyOnly) if tag1 == tag2 { s[j] = merged err := mergeMaps(map1, map2)