From 65967a51dc665da228995b057438dcd1cb7fc05d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 1 Apr 2026 21:28:34 +0800 Subject: [PATCH] dns: reject evaluate fakeip servers --- dns/router.go | 36 +++++++++++ dns/router_test.go | 151 +++++++++++++++++++++++++-------------------- 2 files changed, 121 insertions(+), 66 deletions(-) diff --git a/dns/router.go b/dns/router.go index fd86319ab..dd637a6cc 100644 --- a/dns/router.go +++ b/dns/router.go @@ -272,6 +272,10 @@ func (r *Router) buildRules(startRules bool) ([]adapter.DNSRule, bool, dnsRuleMo return nil, false, dnsRuleModeFlags{}, err } } + err = validateEvaluateFakeIPRules(r.rawRules, r.transport) + if err != nil { + return nil, false, dnsRuleModeFlags{}, err + } newRules := make([]adapter.DNSRule, 0, len(r.rawRules)) for i, ruleOptions := range r.rawRules { var dnsRule adapter.DNSRule @@ -1078,6 +1082,27 @@ func validateLegacyDNSModeDisabledRules(rules []option.DNSRule) error { return nil } +func validateEvaluateFakeIPRules(rules []option.DNSRule, transportManager adapter.DNSTransportManager) error { + if transportManager == nil { + return nil + } + for i, rule := range rules { + if dnsRuleActionType(rule) != C.RuleActionTypeEvaluate { + continue + } + server := dnsRuleActionServer(rule) + if server == "" { + continue + } + transport, loaded := transportManager.Transport(server) + if !loaded || transport.Type() != C.DNSTypeFakeIP { + continue + } + return E.New("dns rule[", i, "]: evaluate action cannot use fakeip server: ", server) + } + return nil +} + func validateLegacyDNSModeDisabledRuleTree(rule option.DNSRule) (bool, error) { switch rule.Type { case "", C.RuleTypeDefault: @@ -1146,3 +1171,14 @@ func dnsRuleActionType(rule option.DNSRule) string { return "" } } + +func dnsRuleActionServer(rule option.DNSRule) string { + switch rule.Type { + case "", C.RuleTypeDefault: + return rule.DefaultOptions.RouteOptions.Server + case C.RuleTypeLogical: + return rule.LogicalOptions.RouteOptions.Server + default: + return "" + } +} diff --git a/dns/router_test.go b/dns/router_test.go index e436db7f4..8d1a0770b 100644 --- a/dns/router_test.go +++ b/dns/router_test.go @@ -2022,84 +2022,39 @@ func TestLookupLegacyDNSModeDisabledSkipsFakeIPRule(t *testing.T) { require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses) } -func TestLookupLegacyDNSModeDisabledEvaluateSkipFakeIPPreservesResponse(t *testing.T) { +func TestExchangeLegacyDNSModeDisabledAllowsRouteFakeIPRule(t *testing.T) { t.Parallel() - defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP} - router := newTestRouter(t, []option.DNSRule{ - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultDNSRule{ - RawDefaultDNSRule: option.RawDefaultDNSRule{ - Domain: badoption.Listable[string]{"example.com"}, - }, - DNSRuleAction: option.DNSRuleAction{ - Action: C.RuleActionTypeEvaluate, - RouteOptions: option.DNSRouteActionOptions{Server: "upstream"}, - }, + fakeTransport := &fakeDNSTransport{tag: "fake", transportType: C.DNSTypeFakeIP} + router := newTestRouter(t, []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.DNSRouteActionOptions{Server: "fake"}, }, }, - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultDNSRule{ - RawDefaultDNSRule: option.RawDefaultDNSRule{ - Domain: badoption.Listable[string]{"example.com"}, - }, - DNSRuleAction: option.DNSRuleAction{ - Action: C.RuleActionTypeEvaluate, - RouteOptions: option.DNSRouteActionOptions{Server: "fake"}, - }, - }, - }, - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultDNSRule{ - RawDefaultDNSRule: option.RawDefaultDNSRule{ - MatchResponse: true, - ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")}, - }, - DNSRuleAction: option.DNSRuleAction{ - Action: C.RuleActionTypeRoute, - RouteOptions: option.DNSRouteActionOptions{Server: "selected"}, - }, - }, - }, - }, &fakeDNSTransportManager{ - defaultTransport: defaultTransport, + }}, &fakeDNSTransportManager{ + defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, transports: map[string]adapter.DNSTransport{ - "default": defaultTransport, - "upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP}, - "fake": &fakeDNSTransport{tag: "fake", transportType: C.DNSTypeFakeIP}, - "selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP}, + "default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}, + "fake": fakeTransport, }, }, &fakeDNSClient{ exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) { - switch transport.Tag() { - case "upstream": - if message.Question[0].Qtype == mDNS.TypeA { - return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil - } - return FixedResponse(0, message.Question[0], nil, 60), nil - case "selected": - if message.Question[0].Qtype == mDNS.TypeA { - return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil - } - return FixedResponse(0, message.Question[0], nil, 60), nil - case "default": - if message.Question[0].Qtype == mDNS.TypeA { - return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("4.4.4.4")}, 60), nil - } - return FixedResponse(0, message.Question[0], nil, 60), nil - default: - return nil, E.New("unexpected transport") - } + require.Same(t, fakeTransport, transport) + return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("198.18.0.1")}, 60), nil }, }) - router.currentRules.Load().legacyDNSMode = false - addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{}) + response, err := router.Exchange(context.Background(), &mDNS.Msg{ + Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, + }, adapter.DNSQueryOptions{}) require.NoError(t, err) - require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses) + require.Equal(t, []netip.Addr{netip.MustParseAddr("198.18.0.1")}, MessageToAddresses(response)) } func TestInitializeRejectsDNSRuleStrategyWhenLegacyDNSModeIsDisabledByEvaluate(t *testing.T) { @@ -2133,6 +2088,70 @@ func TestInitializeRejectsDNSRuleStrategyWhenLegacyDNSModeIsDisabledByEvaluate(t require.ErrorContains(t, err, "deprecated") } +func TestInitializeRejectsEvaluateFakeIPServerInDefaultRule(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{transports: map[string]adapter.DNSTransport{"fake": &fakeDNSTransport{tag: "fake", transportType: C.DNSTypeFakeIP}}}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + router.currentRules.Store(newRulesSnapshot(make([]adapter.DNSRule, 0, 1), false)) + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "fake"}, + }, + }, + }}) + require.ErrorContains(t, err, "evaluate action cannot use fakeip server") + require.ErrorContains(t, err, "fake") +} + +func TestInitializeRejectsEvaluateFakeIPServerInLogicalRule(t *testing.T) { + t.Parallel() + + router := &Router{ + ctx: context.Background(), + logger: log.NewNOPFactory().NewLogger("dns"), + transport: &fakeDNSTransportManager{transports: map[string]adapter.DNSTransport{"fake": &fakeDNSTransport{tag: "fake", transportType: C.DNSTypeFakeIP}}}, + client: &fakeDNSClient{}, + rawRules: make([]option.DNSRule, 0, 1), + defaultDomainStrategy: C.DomainStrategyAsIS, + } + router.currentRules.Store(newRulesSnapshot(make([]adapter.DNSRule, 0, 1), false)) + err := router.Initialize([]option.DNSRule{{ + Type: C.RuleTypeLogical, + LogicalOptions: option.LogicalDNSRule{ + RawLogicalDNSRule: option.RawLogicalDNSRule{ + Mode: C.LogicalTypeOr, + Rules: []option.DNSRule{{ + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultDNSRule{ + RawDefaultDNSRule: option.RawDefaultDNSRule{ + Domain: badoption.Listable[string]{"example.com"}, + }, + }, + }}, + }, + DNSRuleAction: option.DNSRuleAction{ + Action: C.RuleActionTypeEvaluate, + RouteOptions: option.DNSRouteActionOptions{Server: "fake"}, + }, + }, + }}) + require.ErrorContains(t, err, "evaluate action cannot use fakeip server") + require.ErrorContains(t, err, "fake") +} + func TestInitializeRejectsDNSRuleStrategyWhenLegacyDNSModeIsDisabledByMatchResponse(t *testing.T) { t.Parallel()