mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Fix legacy DNS rule_set accept_empty matching
This commit is contained in:
@@ -66,6 +66,9 @@ type RuleSet interface {
|
||||
|
||||
type RuleSetUpdateCallback func(it RuleSet)
|
||||
|
||||
// Rule-set metadata only exposes headless-rule capabilities that outer routers
|
||||
// need before evaluating nested matches. Headless rules do not support
|
||||
// ip_version, so there is intentionally no ContainsIPVersionRule flag here.
|
||||
type RuleSetMetadata struct {
|
||||
ContainsProcessRule bool
|
||||
ContainsWIFIRule bool
|
||||
|
||||
@@ -1058,6 +1058,9 @@ func dnsRuleModeRequirementsInDefaultRule(router adapter.Router, rule option.Def
|
||||
return false, false, E.New("rule-set not found: ", tag)
|
||||
}
|
||||
metadata := ruleSet.Metadata()
|
||||
// Rule sets are built from headless rules, so query_type is the only
|
||||
// per-query DNS predicate they can contribute here. ip_version is not a
|
||||
// headless-rule item and is therefore intentionally absent from metadata.
|
||||
forceNew = forceNew || metadata.ContainsDNSQueryTypeRule
|
||||
if !rule.RuleSetIPCIDRMatchSource && metadata.ContainsIPCIDRRule {
|
||||
needsLegacy = true
|
||||
|
||||
@@ -661,6 +661,88 @@ func TestLookupLegacyModeFallsBackAfterRejectedAddressLimitResponse(t *testing.T
|
||||
require.Equal(t, []string{"private", "default"}, lookups)
|
||||
}
|
||||
|
||||
func TestLookupLegacyModeRuleSetAcceptEmptyDoesNotTreatMismatchAsEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
ruleSet, err := rulepkg.NewRuleSet(ctx, log.NewNOPFactory().NewLogger("router"), option.RuleSet{
|
||||
Type: C.RuleSetTypeInline,
|
||||
Tag: "legacy-ipcidr-set",
|
||||
InlineOptions: option.PlainRuleSet{
|
||||
Rules: []option.HeadlessRule{{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultHeadlessRule{
|
||||
IPCIDR: badoption.Listable[string]{"10.0.0.0/8"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
ctx = service.ContextWith[adapter.Router](ctx, &fakeRouter{
|
||||
ruleSets: map[string]adapter.RuleSet{
|
||||
"legacy-ipcidr-set": ruleSet,
|
||||
},
|
||||
})
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
privateTransport := &fakeDNSTransport{tag: "private", transportType: C.DNSTypeUDP}
|
||||
var lookups []string
|
||||
router := newTestRouterWithContext(t, ctx, []option.DNSRule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
RuleSet: badoption.Listable[string]{"legacy-ipcidr-set"},
|
||||
RuleSetIPCIDRAcceptEmpty: true,
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{Server: "private"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{Server: "default"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &fakeDNSTransportManager{
|
||||
defaultTransport: defaultTransport,
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"default": defaultTransport,
|
||||
"private": privateTransport,
|
||||
},
|
||||
}, &fakeDNSClient{
|
||||
lookup: func(transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) {
|
||||
require.Equal(t, "example.com", domain)
|
||||
require.Equal(t, C.DomainStrategyIPv4Only, options.LookupStrategy)
|
||||
lookups = append(lookups, transport.Tag())
|
||||
switch transport.Tag() {
|
||||
case "private":
|
||||
response := FixedResponse(0, fixedQuestion(domain, mDNS.TypeA), []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60)
|
||||
return MessageToAddresses(response), response, nil
|
||||
case "default":
|
||||
response := FixedResponse(0, fixedQuestion(domain, mDNS.TypeA), []netip.Addr{netip.MustParseAddr("9.9.9.9")}, 60)
|
||||
return MessageToAddresses(response), response, nil
|
||||
}
|
||||
return nil, nil, errors.New("unexpected transport")
|
||||
},
|
||||
})
|
||||
|
||||
require.True(t, router.legacyAddressFilterMode)
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
|
||||
LookupStrategy: C.DomainStrategyIPv4Only,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("9.9.9.9")}, addresses)
|
||||
require.Equal(t, []string{"private", "default"}, lookups)
|
||||
}
|
||||
|
||||
func TestDNSResponseAddressesMatchesMessageToAddressesForHTTPSHints(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -77,12 +77,18 @@ func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {
|
||||
return r.ipSet.Contains(metadata.Source.Addr)
|
||||
}
|
||||
if metadata.DestinationAddressMatchFromResponse {
|
||||
for _, address := range metadata.DNSResponseAddressesForMatch() {
|
||||
addresses := metadata.DNSResponseAddressesForMatch()
|
||||
if len(addresses) == 0 {
|
||||
// Legacy rule_set_ip_cidr_accept_empty only applies when the DNS response
|
||||
// does not expose any address answers for matching.
|
||||
return metadata.IPCIDRAcceptEmpty
|
||||
}
|
||||
for _, address := range addresses {
|
||||
if r.ipSet.Contains(address) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return metadata.IPCIDRAcceptEmpty
|
||||
return false
|
||||
}
|
||||
if metadata.Destination.IsIP() {
|
||||
return r.ipSet.Contains(metadata.Destination.Addr)
|
||||
|
||||
@@ -612,6 +612,8 @@ func TestDNSRuleSetSemantics(t *testing.T) {
|
||||
ipCidrAcceptEmpty: true,
|
||||
})
|
||||
})
|
||||
require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1"))))
|
||||
require.False(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8"))))
|
||||
require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest()))
|
||||
require.False(t, metadata.IPCIDRMatchSource)
|
||||
require.False(t, metadata.IPCIDRAcceptEmpty)
|
||||
|
||||
Reference in New Issue
Block a user