From ab90334502768d5a6806ae05e02cbe4377068688 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Mon, 12 Oct 2026 04:33:31 +0800 Subject: [PATCH] infra/conf: reject conflicting routing and DNS options when script is configured --- infra/conf/dns.go | 11 ++++++++++ infra/conf/dns_test.go | 43 +++++++++++++++++++++++++++++++++++++-- infra/conf/router.go | 3 +++ infra/conf/router_test.go | 40 ++++++++++++++++++++++++++++++++++-- 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/infra/conf/dns.go b/infra/conf/dns.go index 4fb5d5b1e..71ac95115 100644 --- a/infra/conf/dns.go +++ b/infra/conf/dns.go @@ -272,6 +272,17 @@ func (m *HostsWrapper) Build() ([]*dns.Config_HostMapping, error) { // Build implements Buildable func (c *DNSConfig) Build() (*dns.Config, error) { + if c.Script != "" { + if c.DisableFallback || c.DisableFallbackIfMatch || c.EnableParallelQuery { + return nil, errors.New("DNS script cannot be used with disableFallback, disableFallbackIfMatch or enableParallelQuery") + } + for i, server := range c.Servers { + if len(server.Domains) > 0 || server.SkipFallback || server.FinalQuery { + return nil, errors.New("DNS script cannot be used with domains, skipFallback or finalQuery in server ", i) + } + } + } + config := &dns.Config{ Tag: c.Tag, DisableCache: c.DisableCache, diff --git a/infra/conf/dns_test.go b/infra/conf/dns_test.go index e0065ccd3..62d303e98 100644 --- a/infra/conf/dns_test.go +++ b/infra/conf/dns_test.go @@ -2,6 +2,7 @@ package conf_test import ( "encoding/json" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -28,7 +29,6 @@ func TestDNSConfigParsing(t *testing.T) { testCases := []TestCase{ { Input: `{ - "script": "base64:%", "servers": [{ "address": "8.8.8.8", "port": 5353, @@ -53,7 +53,6 @@ func TestDNSConfigParsing(t *testing.T) { }`, Parser: parserCreator(), Output: &dns.Config{ - Script: "base64:%", NameServer: []*dns.NameServer{ { Address: &net.Endpoint{ @@ -138,3 +137,43 @@ func TestDNSNameServerID(t *testing.T) { t.Fatalf("nameserver IDs = %v, want primary", built.NameServer) } } + +func TestDNSScriptConfig(t *testing.T) { + for _, test := range []struct { + name string + input string + wantError string + }{ + {name: "script only", input: `{"script":"base64:%"}`}, + {name: "compatible fields", input: `{"script":"base64:%","servers":["1.1.1.1",{"id":"primary","address":"8.8.8.8","expectedIPs":["8.8.8.8"],"queryStrategy":"UseIPv4","disableCache":true}],"hosts":{"example.com":"127.0.0.1"},"disableCache":true}`}, + {name: "disabled options", input: `{"script":"base64:%","disableFallback":false,"disableFallbackIfMatch":false,"enableParallelQuery":false,"servers":[{"address":"1.1.1.1","domains":[],"skipFallback":false,"finalQuery":false}]}`}, + {name: "domains", input: `{"script":"base64:%","servers":[{"address":"1.1.1.1","domains":["domain:example.com"]}]}`, wantError: "domains"}, + {name: "skip fallback", input: `{"script":"base64:%","servers":[{"address":"1.1.1.1","skipFallback":true}]}`, wantError: "skipFallback"}, + {name: "final query", input: `{"script":"base64:%","servers":[{"address":"1.1.1.1","finalQuery":true}]}`, wantError: "finalQuery"}, + {name: "later server", input: `{"script":"base64:%","servers":["1.1.1.1",{"address":"8.8.8.8","finalQuery":true}]}`, wantError: "server 1"}, + {name: "disable fallback", input: `{"script":"base64:%","disableFallback":true}`, wantError: "disableFallback"}, + {name: "disable fallback if match", input: `{"script":"base64:%","disableFallbackIfMatch":true}`, wantError: "disableFallbackIfMatch"}, + {name: "parallel query", input: `{"script":"base64:%","enableParallelQuery":true}`, wantError: "enableParallelQuery"}, + {name: "empty script", input: `{"script":"","disableFallback":true,"disableFallbackIfMatch":true,"enableParallelQuery":true,"servers":[{"address":"1.1.1.1","domains":["example.com"],"skipFallback":true,"finalQuery":true}]}`}, + } { + t.Run(test.name, func(t *testing.T) { + var config DNSConfig + if err := json.Unmarshal([]byte(test.input), &config); err != nil { + t.Fatal(err) + } + built, err := config.Build() + if test.wantError != "" { + if built != nil || err == nil || !strings.Contains(err.Error(), "DNS script cannot be used with ") || !strings.Contains(err.Error(), test.wantError) { + t.Fatalf("Build() = %v, %v; want nil config and script conflict error mentioning %s", built, err, test.wantError) + } + return + } + if err != nil { + t.Fatal(err) + } + if built.Script != config.Script || len(built.NameServer) != len(config.Servers) { + t.Fatalf("Build() did not preserve script or servers: %v", built) + } + }) + } +} diff --git a/infra/conf/router.go b/infra/conf/router.go index 825f7782e..85d50ad1b 100644 --- a/infra/conf/router.go +++ b/infra/conf/router.go @@ -94,6 +94,9 @@ func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy { func (c *RouterConfig) Build() (*router.Config, error) { config := new(router.Config) + if c.Script != "" && (c.DomainStrategy != nil || len(c.RuleList) > 0) { + return nil, errors.New("routing script cannot be used with domainStrategy or rules") + } config.Script = c.Script config.DomainStrategy = c.getDomainStrategy() diff --git a/infra/conf/router_test.go b/infra/conf/router_test.go index 4fa28bc44..2caf2e558 100644 --- a/infra/conf/router_test.go +++ b/infra/conf/router_test.go @@ -2,6 +2,7 @@ package conf_test import ( "encoding/json" + "strings" "testing" "time" _ "unsafe" @@ -29,7 +30,6 @@ func TestRouterConfig(t *testing.T) { runMultiTestCase(t, []TestCase{ { Input: `{ - "script": "missing.lua", "domainStrategy": "AsIs", "rules": [ { @@ -91,7 +91,6 @@ func TestRouterConfig(t *testing.T) { }`, Parser: createParser(), Output: &router.Config{ - Script: "missing.lua", DomainStrategy: router.Config_AsIs, BalancingRule: []*router.BalancingRule{ { @@ -238,3 +237,40 @@ func TestRouterConfig(t *testing.T) { }, }) } + +func TestRouterScriptConfig(t *testing.T) { + for _, test := range []struct { + name string + input string + wantError bool + }{ + {name: "script only", input: `{"script":"missing.lua"}`}, + {name: "empty rules", input: `{"script":"missing.lua","rules":[]}`}, + {name: "balancers", input: `{"script":"missing.lua","balancers":[{"tag":"b1","selector":["direct"]}]}`}, + {name: "default domain strategy", input: `{"script":"missing.lua","domainStrategy":"AsIs"}`, wantError: true}, + {name: "IP domain strategy", input: `{"script":"missing.lua","domainStrategy":"IPIfNonMatch"}`, wantError: true}, + {name: "empty domain strategy", input: `{"script":"missing.lua","domainStrategy":""}`, wantError: true}, + {name: "rules", input: `{"script":"missing.lua","rules":[{"outboundTag":"direct","domain":["example.com"]}]}`, wantError: true}, + {name: "empty script", input: `{"script":"","domainStrategy":"IPOnDemand","rules":[{"outboundTag":"direct","domain":["example.com"]}]}`}, + } { + t.Run(test.name, func(t *testing.T) { + var config RouterConfig + if err := json.Unmarshal([]byte(test.input), &config); err != nil { + t.Fatal(err) + } + built, err := config.Build() + if test.wantError { + if built != nil || err == nil || !strings.Contains(err.Error(), "routing script cannot be used with domainStrategy or rules") { + t.Fatalf("Build() = %v, %v; want nil config and script conflict error", built, err) + } + return + } + if err != nil { + t.Fatal(err) + } + if built.Script != config.Script || len(built.Rule) != len(config.RuleList) || len(built.BalancingRule) != len(config.Balancers) { + t.Fatalf("Build() did not preserve script, rules or balancers: %v", built) + } + }) + } +}