Compare commits

...

4 Commits

Author SHA1 Message Date
世界
a1a0d8356c dns: revert legacy pre-match to simple flag-based approach
Remove the 754-line boolean satisfiability formula system
(rule_dns_legacy.go) and restore the original IgnoreDestinationIPCIDRMatch
flag for legacy address filter pre-matching. Adding complexity to
optimize a legacy compatibility path is not worthwhile.
2026-04-02 23:50:39 +08:00
世界
646ed69c0b dns: unify match_response gate error for all Response Match Fields
ip_cidr and ip_is_private are Response Match Fields in new mode,
same as response_rcode/answer/ns/extra. Use a single consistent
error message when any of them appear without match_response.
2026-04-02 22:44:41 +08:00
世界
74688751b0 dns: simplify evaluate action transport resolution
Remove resolveDNSRoute indirection from evaluate action since
evaluate+fakeip is already rejected at build time, making the
dnsRouteStatusSkipped branch dead code. Inline transport lookup
directly instead.

Also remove the context-cancellation early return that was not
part of the design spec, and fix test naming to avoid newMode.
2026-04-02 22:16:39 +08:00
世界
dfa460372f dns: ignore split lookup errors on partial success 2026-04-02 20:18:36 +08:00
7 changed files with 39 additions and 1097 deletions

View File

@@ -97,11 +97,12 @@ type InboundContext struct {
IPCIDRMatchSource bool
IPCIDRAcceptEmpty bool
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
DidMatch bool
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
DidMatch bool
IgnoreDestinationIPCIDRMatch bool
}
func (c *InboundContext) ResetRuleCache() {

View File

@@ -380,31 +380,7 @@ type exchangeWithRulesResult struct {
const dnsRespondMissingResponseMessage = "respond action requires an evaluated response from a preceding evaluate action"
type lookupSplitHardError struct {
cause error
}
func (e *lookupSplitHardError) Error() string {
return e.cause.Error()
}
func (e *lookupSplitHardError) Unwrap() error {
return e.cause
}
func newLookupSplitHardError(err error) error {
if err == nil {
return nil
}
return &lookupSplitHardError{cause: err}
}
func isLookupSplitHardError(err error) bool {
var target *lookupSplitHardError
return errors.As(err, &target)
}
func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool, hardFailMissingTransport bool) exchangeWithRulesResult {
func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool) exchangeWithRulesResult {
metadata := adapter.ContextFrom(ctx)
if metadata == nil {
panic("no context")
@@ -425,29 +401,20 @@ func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule,
r.applyDNSRouteOptions(&effectiveOptions, *action)
case *R.RuleActionEvaluate:
queryOptions := effectiveOptions
transport, status := r.resolveDNSRoute(action.Server, action.RuleActionDNSRouteOptions, allowFakeIP, &queryOptions)
switch status {
case dnsRouteStatusMissing:
err := E.New("transport not found: ", action.Server)
if hardFailMissingTransport {
return exchangeWithRulesResult{err: newLookupSplitHardError(err)}
}
r.logger.ErrorContext(ctx, err)
transport, loaded := r.transport.Transport(action.Server)
if !loaded {
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
evaluatedResponse = nil
evaluatedTransport = nil
continue
case dnsRouteStatusSkipped:
continue
}
r.applyDNSRouteOptions(&queryOptions, action.RuleActionDNSRouteOptions)
exchangeOptions := queryOptions
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
exchangeOptions.Strategy = r.defaultDomainStrategy
}
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
if err != nil {
if E.IsClosedOrCanceled(err) {
return exchangeWithRulesResult{err: err}
}
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
evaluatedResponse = nil
evaluatedTransport = nil
@@ -458,7 +425,7 @@ func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule,
case *R.RuleActionRespond:
if evaluatedResponse == nil {
return exchangeWithRulesResult{
err: newLookupSplitHardError(E.New(dnsRespondMissingResponseMessage)),
err: E.New(dnsRespondMissingResponseMessage),
}
}
return exchangeWithRulesResult{
@@ -470,11 +437,7 @@ func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule,
transport, status := r.resolveDNSRoute(action.Server, action.RuleActionDNSRouteOptions, allowFakeIP, &queryOptions)
switch status {
case dnsRouteStatusMissing:
err := E.New("transport not found: ", action.Server)
if hardFailMissingTransport {
return exchangeWithRulesResult{err: newLookupSplitHardError(err)}
}
r.logger.ErrorContext(ctx, err)
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
continue
case dnsRouteStatusSkipped:
continue
@@ -579,58 +542,22 @@ func (r *Router) lookupWithRules(ctx context.Context, rules []adapter.DNSRule, d
return r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions)
}
var (
response4 []netip.Addr
response6 []netip.Addr
ordinaryErr4 error
ordinaryErr6 error
hardErr4 error
hardErr6 error
response4 []netip.Addr
response6 []netip.Addr
)
var group task.Group
group.Append("exchange4", func(ctx context.Context) error {
result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeA, lookupOptions)
response4 = result
if err == nil {
return nil
}
if E.IsClosedOrCanceled(err) {
return err
}
if isLookupSplitHardError(err) {
hardErr4 = err
return nil
}
ordinaryErr4 = err
return nil
return err
})
group.Append("exchange6", func(ctx context.Context) error {
result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions)
response6 = result
if err == nil {
return nil
}
if E.IsClosedOrCanceled(err) {
return err
}
if isLookupSplitHardError(err) {
hardErr6 = err
return nil
}
ordinaryErr6 = err
return nil
return err
})
err := group.Run(ctx)
if err != nil {
return nil, err
}
err = E.Errors(hardErr4, hardErr6)
if len(response4) == 0 && len(response6) == 0 {
if err != nil {
return nil, err
}
return nil, E.Errors(ordinaryErr4, ordinaryErr6)
}
if err != nil {
return nil, err
}
return sortAddresses(response4, response6, strategy), nil
@@ -647,7 +574,7 @@ func (r *Router) lookupWithRulesType(ctx context.Context, rules []adapter.DNSRul
Qclass: mDNS.ClassINET,
}},
}
exchangeResult := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), rules, request, options, false, true)
exchangeResult := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), rules, request, options, false)
if exchangeResult.rejectAction != nil {
return nil, exchangeResult.rejectAction.Error(ctx)
}
@@ -706,7 +633,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
}
response, err = r.client.Exchange(ctx, transport, message, options, nil)
} else if !legacyDNSMode {
exchangeResult := r.exchangeWithRules(ctx, rules, message, options, true, false)
exchangeResult := r.exchangeWithRules(ctx, rules, message, options, true)
response, transport, err = exchangeResult.response, exchangeResult.transport, exchangeResult.err
} else {
var (
@@ -1132,11 +1059,8 @@ func validateLegacyDNSModeDisabledRuleTree(rule option.DNSRule) (bool, error) {
func validateLegacyDNSModeDisabledDefaultRule(rule option.DefaultDNSRule) (bool, error) {
hasResponseRecords := hasResponseMatchFields(rule)
if hasResponseRecords && !rule.MatchResponse {
return false, E.New("Response Match Fields (response_rcode, response_answer, response_ns, response_extra) require match_response to be enabled")
}
if (len(rule.IPCIDR) > 0 || rule.IPIsPrivate) && !rule.MatchResponse {
return false, E.New(deprecated.OptionLegacyDNSAddressFilter.MessageWithLink())
if (hasResponseRecords || len(rule.IPCIDR) > 0 || rule.IPIsPrivate) && !rule.MatchResponse {
return false, E.New("Response Match Fields (ip_cidr, ip_is_private, response_rcode, response_answer, response_ns, response_extra) require match_response to be enabled")
}
// Intentionally do not reject rule_set here. A referenced rule set may mix
// destination-IP predicates with pre-response predicates such as domain items.

View File

@@ -414,8 +414,8 @@ func TestInitializeRejectsDirectLegacyRuleWhenRuleSetForcesNew(t *testing.T) {
},
},
})
require.ErrorContains(t, err, "Address Filter Fields")
require.ErrorContains(t, err, "deprecated")
require.ErrorContains(t, err, "Response Match Fields")
require.ErrorContains(t, err, "require match_response")
}
func TestLookupLegacyDNSModeDefersRuleSetDestinationIPMatch(t *testing.T) {
@@ -573,7 +573,7 @@ func TestValidateRuleSetMetadataUpdateRejectsRuleSetThatWouldDisableLegacyDNSMod
err := router.ValidateRuleSetMetadataUpdate("dynamic-set", adapter.RuleSetMetadata{
ContainsDNSQueryTypeRule: true,
})
require.ErrorContains(t, err, "Address Filter Fields")
require.ErrorContains(t, err, "require match_response")
}
func TestValidateRuleSetMetadataUpdateRejectsRuleSetOnlyLegacyModeSwitchToNew(t *testing.T) {
@@ -672,7 +672,7 @@ func TestValidateRuleSetMetadataUpdateBeforeStartUsesStartupValidation(t *testin
err = router.ValidateRuleSetMetadataUpdate("dynamic-set", adapter.RuleSetMetadata{
ContainsDNSQueryTypeRule: true,
})
require.ErrorContains(t, err, "Address Filter Fields")
require.ErrorContains(t, err, "require match_response")
}
func TestValidateRuleSetMetadataUpdateRejectsRuleSetThatWouldRequireLegacyDNSMode(t *testing.T) {
@@ -715,7 +715,7 @@ func TestValidateRuleSetMetadataUpdateRejectsRuleSetThatWouldRequireLegacyDNSMod
require.ErrorContains(t, err, "Address Filter Fields")
}
func TestValidateRuleSetMetadataUpdateAllowsRuleSetThatKeepsNewMode(t *testing.T) {
func TestValidateRuleSetMetadataUpdateAllowsRuleSetThatKeepsNonLegacyDNSMode(t *testing.T) {
t.Parallel()
fakeSet := &fakeRuleSet{}
@@ -1855,101 +1855,6 @@ func TestLookupLegacyDNSModeDisabledAllowsPartialSuccessForExchangeFailure(t *te
require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses)
}
func TestLookupLegacyDNSModeDisabledRespondWithoutEvaluatedResponseIsHardError(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"},
},
},
},
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
Domain: badoption.Listable[string]{"example.com"},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeRespond,
},
},
},
}, &fakeDNSTransportManager{
defaultTransport: defaultTransport,
transports: map[string]adapter.DNSTransport{
"default": defaultTransport,
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
},
}, &fakeDNSClient{
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
require.Equal(t, "upstream", transport.Tag())
switch message.Question[0].Qtype {
case mDNS.TypeA:
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
case mDNS.TypeAAAA:
return nil, E.New("upstream exchange failed")
default:
return nil, E.New("unexpected qtype")
}
},
})
router.legacyDNSMode = false
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
require.Nil(t, addresses)
require.ErrorContains(t, err, dnsRespondMissingResponseMessage)
}
func TestLookupLegacyDNSModeDisabledTransportNotFoundIsHardError(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"},
QueryType: badoption.Listable[option.DNSQueryType]{option.DNSQueryType(mDNS.TypeAAAA)},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeRoute,
RouteOptions: option.DNSRouteActionOptions{Server: "missing"},
},
},
}}, &fakeDNSTransportManager{
defaultTransport: defaultTransport,
transports: map[string]adapter.DNSTransport{
"default": defaultTransport,
},
}, &fakeDNSClient{
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
require.Equal(t, "default", transport.Tag())
switch message.Question[0].Qtype {
case mDNS.TypeA:
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
case mDNS.TypeAAAA:
return FixedResponse(0, message.Question[0], nil, 60), nil
default:
return nil, E.New("unexpected qtype")
}
},
})
router.legacyDNSMode = false
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
require.Nil(t, addresses)
require.ErrorContains(t, err, "transport not found: missing")
}
func TestLookupLegacyDNSModeDisabledAllowsPartialSuccessForRcodeError(t *testing.T) {
t.Parallel()

View File

@@ -56,11 +56,11 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
}
func (r *abstractDefaultRule) destinationIPCIDRMatchesSource(metadata *adapter.InboundContext) bool {
return metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
return !metadata.IgnoreDestinationIPCIDRMatch && metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
}
func (r *abstractDefaultRule) destinationIPCIDRMatchesDestination(metadata *adapter.InboundContext) bool {
return !metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
return !metadata.IgnoreDestinationIPCIDRMatch && !metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
}
func (r *abstractDefaultRule) requiresSourceAddressMatch(metadata *adapter.InboundContext) bool {
@@ -156,6 +156,9 @@ func (r *abstractDefaultRule) matchStatesWithBase(metadata *adapter.InboundConte
return r.invertedFailure(inheritedBase)
}
if r.invert {
if metadata.IgnoreDestinationIPCIDRMatch && stateSet == emptyRuleMatchState() && !metadata.DidMatch && len(r.destinationIPCIDRItems) > 0 {
return emptyRuleMatchState().withBase(inheritedBase)
}
return 0
}
return stateSet

View File

@@ -372,26 +372,17 @@ func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
func (r *DefaultDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool {
if r.matchResponse {
return !r.legacyMatchStatesForMatch(metadata).isEmpty()
return false
}
return !r.abstractDefaultRule.legacyMatchStates(metadata).isEmpty()
metadata.IgnoreDestinationIPCIDRMatch = true
defer func() { metadata.IgnoreDestinationIPCIDRMatch = false }()
return !r.abstractDefaultRule.matchStates(metadata).isEmpty()
}
func (r *DefaultDNSRule) matchStatesForMatch(metadata *adapter.InboundContext) ruleMatchStateSet {
return r.matchStatesForMatchWithMissingResponse(metadata, true)
}
func (r *DefaultDNSRule) legacyMatchStatesForMatch(metadata *adapter.InboundContext) ruleMatchStateSet {
return r.matchStatesForMatchWithMissingResponse(metadata, false)
}
func (r *DefaultDNSRule) matchStatesForMatchWithMissingResponse(metadata *adapter.InboundContext, ordinaryFailure bool) ruleMatchStateSet {
if r.matchResponse {
if metadata.DNSResponse == nil {
if ordinaryFailure {
return r.abstractDefaultRule.invertedFailure(0)
}
return 0
return r.abstractDefaultRule.invertedFailure(0)
}
matchMetadata := *metadata
matchMetadata.DestinationAddressMatchFromResponse = true
@@ -518,7 +509,9 @@ func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
}
func (r *LogicalDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool {
return !r.abstractLogicalRule.legacyMatchStates(metadata).isEmpty()
metadata.IgnoreDestinationIPCIDRMatch = true
defer func() { metadata.IgnoreDestinationIPCIDRMatch = false }()
return !r.abstractLogicalRule.matchStates(metadata).isEmpty()
}
func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext, response *dns.Msg) bool {

View File

@@ -1,754 +0,0 @@
package rule
import (
"net/netip"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"go4.org/netipx"
)
type legacyResponseLiteralKind uint8
const (
legacyLiteralRequireEmpty legacyResponseLiteralKind = iota
legacyLiteralRequireNonEmpty
legacyLiteralRequireSet
legacyLiteralForbidSet
)
type legacyResponseLiteral struct {
kind legacyResponseLiteralKind
ipSet *netipx.IPSet
}
type legacyResponseFormulaKind uint8
const (
legacyFormulaFalse legacyResponseFormulaKind = iota
legacyFormulaTrue
legacyFormulaLiteral
legacyFormulaAnd
legacyFormulaOr
)
type legacyResponseFormula struct {
kind legacyResponseFormulaKind
literal legacyResponseLiteral
children []legacyResponseFormula
}
type legacyResponseConstraint struct {
requireEmpty bool
requireNonEmpty bool
requiredSets []*netipx.IPSet
forbiddenSet *netipx.IPSet
}
const (
legacyRuleMatchDeferredDestinationAddress ruleMatchState = 1 << 4
legacyRuleMatchStateCount = 32
)
type legacyRuleMatchStateSet [legacyRuleMatchStateCount]legacyResponseFormula
var (
legacyAllIPSet = func() *netipx.IPSet {
var builder netipx.IPSetBuilder
builder.Complement()
return common.Must1(builder.IPSet())
}()
legacyNonPublicIPSet = func() *netipx.IPSet {
var builder netipx.IPSetBuilder
for _, prefix := range []string{
"0.0.0.0/32",
"10.0.0.0/8",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.168.0.0/16",
"224.0.0.0/4",
"::/128",
"::1/128",
"fc00::/7",
"fe80::/10",
"ff00::/8",
} {
builder.AddPrefix(netip.MustParsePrefix(prefix))
}
return common.Must1(builder.IPSet())
}()
)
func legacyFalseFormula() legacyResponseFormula {
return legacyResponseFormula{}
}
func legacyTrueFormula() legacyResponseFormula {
return legacyResponseFormula{kind: legacyFormulaTrue}
}
func legacyLiteralFormula(literal legacyResponseLiteral) legacyResponseFormula {
return legacyResponseFormula{
kind: legacyFormulaLiteral,
literal: literal,
}
}
func (f legacyResponseFormula) isFalse() bool {
return f.kind == legacyFormulaFalse
}
func (f legacyResponseFormula) isTrue() bool {
return f.kind == legacyFormulaTrue
}
func (f legacyResponseFormula) or(other legacyResponseFormula) legacyResponseFormula {
return legacyOrFormulas(f, other)
}
func (f legacyResponseFormula) and(other legacyResponseFormula) legacyResponseFormula {
return legacyAndFormulas(f, other)
}
func (f legacyResponseFormula) not() legacyResponseFormula {
switch f.kind {
case legacyFormulaFalse:
return legacyTrueFormula()
case legacyFormulaTrue:
return legacyFalseFormula()
case legacyFormulaLiteral:
return legacyLiteralFormula(legacyNegateResponseLiteral(f.literal))
case legacyFormulaAnd:
negated := make([]legacyResponseFormula, 0, len(f.children))
for _, child := range f.children {
negated = append(negated, child.not())
}
return legacyOrFormulas(negated...)
case legacyFormulaOr:
negated := make([]legacyResponseFormula, 0, len(f.children))
for _, child := range f.children {
negated = append(negated, child.not())
}
return legacyAndFormulas(negated...)
default:
panic("unknown legacy response formula kind")
}
}
func legacyNegateResponseLiteral(literal legacyResponseLiteral) legacyResponseLiteral {
switch literal.kind {
case legacyLiteralRequireEmpty:
return legacyResponseLiteral{kind: legacyLiteralRequireNonEmpty}
case legacyLiteralRequireNonEmpty:
return legacyResponseLiteral{kind: legacyLiteralRequireEmpty}
case legacyLiteralRequireSet:
return legacyResponseLiteral{kind: legacyLiteralForbidSet, ipSet: literal.ipSet}
case legacyLiteralForbidSet:
return legacyResponseLiteral{kind: legacyLiteralRequireSet, ipSet: literal.ipSet}
default:
panic("unknown legacy response literal kind")
}
}
func legacyOrFormulas(formulas ...legacyResponseFormula) legacyResponseFormula {
children := make([]legacyResponseFormula, 0, len(formulas))
for _, formula := range formulas {
if formula.isFalse() {
continue
}
if formula.isTrue() {
return legacyTrueFormula()
}
if formula.kind == legacyFormulaOr {
children = append(children, formula.children...)
continue
}
children = append(children, formula)
}
switch len(children) {
case 0:
return legacyFalseFormula()
case 1:
return children[0]
default:
return legacyResponseFormula{
kind: legacyFormulaOr,
children: children,
}
}
}
func legacyAndFormulas(formulas ...legacyResponseFormula) legacyResponseFormula {
children := make([]legacyResponseFormula, 0, len(formulas))
for _, formula := range formulas {
if formula.isFalse() {
return legacyFalseFormula()
}
if formula.isTrue() {
continue
}
if formula.kind == legacyFormulaAnd {
children = append(children, formula.children...)
continue
}
children = append(children, formula)
}
switch len(children) {
case 0:
return legacyTrueFormula()
case 1:
return children[0]
}
result := legacyResponseFormula{
kind: legacyFormulaAnd,
children: children,
}
if !result.satisfiable() {
return legacyFalseFormula()
}
return result
}
func (f legacyResponseFormula) satisfiable() bool {
return legacyResponseFormulasSatisfiable(legacyResponseConstraint{}, []legacyResponseFormula{f})
}
func legacyResponseFormulasSatisfiable(constraint legacyResponseConstraint, formulas []legacyResponseFormula) bool {
stack := append(make([]legacyResponseFormula, 0, len(formulas)), formulas...)
var disjunctions []legacyResponseFormula
for len(stack) > 0 {
formula := stack[len(stack)-1]
stack = stack[:len(stack)-1]
switch formula.kind {
case legacyFormulaFalse:
return false
case legacyFormulaTrue:
continue
case legacyFormulaLiteral:
var ok bool
constraint, ok = constraint.withLiteral(formula.literal)
if !ok {
return false
}
case legacyFormulaAnd:
stack = append(stack, formula.children...)
case legacyFormulaOr:
if len(formula.children) == 0 {
return false
}
disjunctions = append(disjunctions, formula)
default:
panic("unknown legacy response formula kind")
}
}
if len(disjunctions) == 0 {
return true
}
bestIndex := 0
for i := 1; i < len(disjunctions); i++ {
if len(disjunctions[i].children) < len(disjunctions[bestIndex].children) {
bestIndex = i
}
}
selected := disjunctions[bestIndex]
remaining := make([]legacyResponseFormula, 0, len(disjunctions)-1)
remaining = append(remaining, disjunctions[:bestIndex]...)
remaining = append(remaining, disjunctions[bestIndex+1:]...)
for _, child := range selected.children {
nextFormulas := make([]legacyResponseFormula, 0, len(remaining)+1)
nextFormulas = append(nextFormulas, remaining...)
nextFormulas = append(nextFormulas, child)
if legacyResponseFormulasSatisfiable(constraint, nextFormulas) {
return true
}
}
return false
}
func (c legacyResponseConstraint) withLiteral(literal legacyResponseLiteral) (legacyResponseConstraint, bool) {
switch literal.kind {
case legacyLiteralRequireEmpty:
c.requireEmpty = true
case legacyLiteralRequireNonEmpty:
c.requireNonEmpty = true
case legacyLiteralRequireSet:
requiredSets := make([]*netipx.IPSet, len(c.requiredSets)+1)
copy(requiredSets, c.requiredSets)
requiredSets[len(c.requiredSets)] = literal.ipSet
c.requiredSets = requiredSets
case legacyLiteralForbidSet:
c.forbiddenSet = legacyUnionIPSets(c.forbiddenSet, literal.ipSet)
default:
panic("unknown legacy response literal kind")
}
return c, c.satisfiable()
}
func (c legacyResponseConstraint) satisfiable() bool {
if c.requireEmpty && (c.requireNonEmpty || len(c.requiredSets) > 0) {
return false
}
if c.requireEmpty {
return true
}
for _, required := range c.requiredSets {
if !legacyIPSetHasAllowedIP(required, c.forbiddenSet) {
return false
}
}
if c.requireNonEmpty && len(c.requiredSets) == 0 {
return legacyIPSetHasAllowedIP(legacyAllIPSet, c.forbiddenSet)
}
return true
}
func legacyUnionIPSets(left *netipx.IPSet, right *netipx.IPSet) *netipx.IPSet {
if left == nil {
return right
}
if right == nil {
return left
}
var builder netipx.IPSetBuilder
builder.AddSet(left)
builder.AddSet(right)
return common.Must1(builder.IPSet())
}
func legacyIPSetHasAllowedIP(required *netipx.IPSet, forbidden *netipx.IPSet) bool {
if required == nil {
required = legacyAllIPSet
}
if forbidden == nil {
return len(required.Ranges()) > 0
}
builder := netipx.IPSetBuilder{}
builder.AddSet(required)
builder.RemoveSet(forbidden)
remaining := common.Must1(builder.IPSet())
return len(remaining.Ranges()) > 0
}
func legacySingleRuleMatchState(state ruleMatchState) legacyRuleMatchStateSet {
return legacySingleRuleMatchStateWithFormula(state, legacyTrueFormula())
}
func legacySingleRuleMatchStateWithFormula(state ruleMatchState, formula legacyResponseFormula) legacyRuleMatchStateSet {
var stateSet legacyRuleMatchStateSet
if !formula.isFalse() {
stateSet[state] = formula
}
return stateSet
}
func (s legacyRuleMatchStateSet) isEmpty() bool {
for _, formula := range s {
if !formula.isFalse() {
return false
}
}
return true
}
func (s legacyRuleMatchStateSet) merge(other legacyRuleMatchStateSet) legacyRuleMatchStateSet {
var merged legacyRuleMatchStateSet
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
merged[state] = s[state].or(other[state])
}
return merged
}
func (s legacyRuleMatchStateSet) combine(other legacyRuleMatchStateSet) legacyRuleMatchStateSet {
if s.isEmpty() || other.isEmpty() {
return legacyRuleMatchStateSet{}
}
var combined legacyRuleMatchStateSet
for left := ruleMatchState(0); left < legacyRuleMatchStateCount; left++ {
if s[left].isFalse() {
continue
}
for right := ruleMatchState(0); right < legacyRuleMatchStateCount; right++ {
if other[right].isFalse() {
continue
}
combined[left|right] = combined[left|right].or(s[left].and(other[right]))
}
}
return combined
}
func (s legacyRuleMatchStateSet) withBase(base ruleMatchState) legacyRuleMatchStateSet {
if s.isEmpty() {
return legacyRuleMatchStateSet{}
}
var withBase legacyRuleMatchStateSet
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
if s[state].isFalse() {
continue
}
withBase[state|base] = withBase[state|base].or(s[state])
}
return withBase
}
func (s legacyRuleMatchStateSet) filter(allowed func(ruleMatchState) bool) legacyRuleMatchStateSet {
var filtered legacyRuleMatchStateSet
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
if s[state].isFalse() {
continue
}
if allowed(state) {
filtered[state] = s[state]
}
}
return filtered
}
func (s legacyRuleMatchStateSet) addBit(bit ruleMatchState) legacyRuleMatchStateSet {
var withBit legacyRuleMatchStateSet
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
if s[state].isFalse() {
continue
}
withBit[state|bit] = withBit[state|bit].or(s[state])
}
return withBit
}
func (s legacyRuleMatchStateSet) branchOnBit(bit ruleMatchState, condition legacyResponseFormula) legacyRuleMatchStateSet {
if condition.isFalse() {
return s
}
if condition.isTrue() {
return s.addBit(bit)
}
var branched legacyRuleMatchStateSet
conditionFalse := condition.not()
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
if s[state].isFalse() {
continue
}
if state.has(bit) {
branched[state] = branched[state].or(s[state])
continue
}
branched[state] = branched[state].or(s[state].and(conditionFalse))
branched[state|bit] = branched[state|bit].or(s[state].and(condition))
}
return branched
}
func (s legacyRuleMatchStateSet) andFormula(formula legacyResponseFormula) legacyRuleMatchStateSet {
if formula.isFalse() || s.isEmpty() {
return legacyRuleMatchStateSet{}
}
if formula.isTrue() {
return s
}
var result legacyRuleMatchStateSet
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
if s[state].isFalse() {
continue
}
result[state] = s[state].and(formula)
}
return result
}
func (s legacyRuleMatchStateSet) anyFormula() legacyResponseFormula {
var formula legacyResponseFormula
for _, stateFormula := range s {
formula = formula.or(stateFormula)
}
return formula
}
type legacyRuleStateMatcher interface {
legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet
}
type legacyRuleStateMatcherWithBase interface {
legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet
}
func legacyMatchHeadlessRuleStates(rule adapter.HeadlessRule, metadata *adapter.InboundContext) legacyRuleMatchStateSet {
return legacyMatchHeadlessRuleStatesWithBase(rule, metadata, 0)
}
func legacyMatchHeadlessRuleStatesWithBase(rule adapter.HeadlessRule, metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
if matcher, loaded := rule.(legacyRuleStateMatcherWithBase); loaded {
return matcher.legacyMatchStatesWithBase(metadata, base)
}
if matcher, loaded := rule.(legacyRuleStateMatcher); loaded {
return matcher.legacyMatchStates(metadata).withBase(base)
}
if rule.Match(metadata) {
return legacySingleRuleMatchState(base)
}
return legacyRuleMatchStateSet{}
}
func legacyMatchRuleItemStatesWithBase(item RuleItem, metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
if matcher, loaded := item.(legacyRuleStateMatcherWithBase); loaded {
return matcher.legacyMatchStatesWithBase(metadata, base)
}
if matcher, loaded := item.(legacyRuleStateMatcher); loaded {
return matcher.legacyMatchStates(metadata).withBase(base)
}
if item.Match(metadata) {
return legacySingleRuleMatchState(base)
}
return legacyRuleMatchStateSet{}
}
func (r *DefaultHeadlessRule) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
return r.abstractDefaultRule.legacyMatchStates(metadata)
}
func (r *LogicalHeadlessRule) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
return r.abstractLogicalRule.legacyMatchStates(metadata)
}
func (r *RuleSetItem) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
return r.legacyMatchStatesWithBase(metadata, 0)
}
func (r *RuleSetItem) legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
var stateSet legacyRuleMatchStateSet
for _, ruleSet := range r.setList {
nestedMetadata := *metadata
nestedMetadata.ResetRuleMatchCache()
nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource
nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
stateSet = stateSet.merge(legacyMatchHeadlessRuleStatesWithBase(ruleSet, &nestedMetadata, base))
}
return stateSet
}
func (s *LocalRuleSet) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
return s.legacyMatchStatesWithBase(metadata, 0)
}
func (s *LocalRuleSet) legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
var stateSet legacyRuleMatchStateSet
for _, rule := range s.rules {
nestedMetadata := *metadata
nestedMetadata.ResetRuleMatchCache()
stateSet = stateSet.merge(legacyMatchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
}
return stateSet
}
func (s *RemoteRuleSet) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
return s.legacyMatchStatesWithBase(metadata, 0)
}
func (s *RemoteRuleSet) legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
var stateSet legacyRuleMatchStateSet
for _, rule := range s.rules {
nestedMetadata := *metadata
nestedMetadata.ResetRuleMatchCache()
stateSet = stateSet.merge(legacyMatchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
}
return stateSet
}
func (r *abstractDefaultRule) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
return r.legacyMatchStatesWithBase(metadata, 0)
}
func (r *abstractDefaultRule) legacyMatchStatesWithBase(metadata *adapter.InboundContext, inheritedBase ruleMatchState) legacyRuleMatchStateSet {
if len(r.allItems) == 0 {
return legacySingleRuleMatchState(inheritedBase)
}
evaluationBase := inheritedBase
if r.invert {
evaluationBase = 0
}
stateSet := legacySingleRuleMatchState(evaluationBase)
if len(r.sourceAddressItems) > 0 {
metadata.DidMatch = true
if matchAnyItem(r.sourceAddressItems, metadata) {
stateSet = stateSet.addBit(ruleMatchSourceAddress)
}
}
if r.destinationIPCIDRMatchesSource(metadata) {
metadata.DidMatch = true
stateSet = stateSet.branchOnBit(ruleMatchSourceAddress, legacyDestinationIPFormula(r.destinationIPCIDRItems, metadata))
}
if len(r.sourcePortItems) > 0 {
metadata.DidMatch = true
if matchAnyItem(r.sourcePortItems, metadata) {
stateSet = stateSet.addBit(ruleMatchSourcePort)
}
}
if len(r.destinationAddressItems) > 0 {
metadata.DidMatch = true
if matchAnyItem(r.destinationAddressItems, metadata) {
stateSet = stateSet.addBit(ruleMatchDestinationAddress)
}
}
if r.legacyDestinationIPCIDRMatchesDestination(metadata) {
metadata.DidMatch = true
stateSet = stateSet.branchOnBit(legacyRuleMatchDeferredDestinationAddress, legacyDestinationIPFormula(r.destinationIPCIDRItems, metadata))
}
if len(r.destinationPortItems) > 0 {
metadata.DidMatch = true
if matchAnyItem(r.destinationPortItems, metadata) {
stateSet = stateSet.addBit(ruleMatchDestinationPort)
}
}
for _, item := range r.items {
metadata.DidMatch = true
if !item.Match(metadata) {
if r.invert {
return legacySingleRuleMatchState(inheritedBase)
}
return legacyRuleMatchStateSet{}
}
}
if r.ruleSetItem != nil {
metadata.DidMatch = true
var merged legacyRuleMatchStateSet
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
if stateSet[state].isFalse() {
continue
}
nestedStateSet := legacyMatchRuleItemStatesWithBase(r.ruleSetItem, metadata, state)
merged = merged.merge(nestedStateSet.andFormula(stateSet[state]))
}
stateSet = merged
}
stateSet = stateSet.filter(func(state ruleMatchState) bool {
if r.legacyRequiresSourceAddressMatch(metadata) && !state.has(ruleMatchSourceAddress) {
return false
}
if len(r.sourcePortItems) > 0 && !state.has(ruleMatchSourcePort) {
return false
}
if r.legacyRequiresDestinationAddressMatch(metadata) && !state.has(ruleMatchDestinationAddress) {
return false
}
if r.legacyRequiresDeferredDestinationAddressMatch(metadata) && !state.has(legacyRuleMatchDeferredDestinationAddress) {
return false
}
if len(r.destinationPortItems) > 0 && !state.has(ruleMatchDestinationPort) {
return false
}
return true
})
if r.invert {
return legacySingleRuleMatchStateWithFormula(inheritedBase, stateSet.anyFormula().not())
}
return stateSet
}
func (r *abstractDefaultRule) legacyRequiresSourceAddressMatch(metadata *adapter.InboundContext) bool {
return len(r.sourceAddressItems) > 0 || r.destinationIPCIDRMatchesSource(metadata)
}
func (r *abstractDefaultRule) legacyDestinationIPCIDRMatchesDestination(metadata *adapter.InboundContext) bool {
return !metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
}
func (r *abstractDefaultRule) legacyRequiresDestinationAddressMatch(metadata *adapter.InboundContext) bool {
return len(r.destinationAddressItems) > 0
}
func (r *abstractDefaultRule) legacyRequiresDeferredDestinationAddressMatch(metadata *adapter.InboundContext) bool {
return r.legacyDestinationIPCIDRMatchesDestination(metadata)
}
func (r *abstractLogicalRule) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
return r.legacyMatchStatesWithBase(metadata, 0)
}
func (r *abstractLogicalRule) legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
evaluationBase := base
if r.invert {
evaluationBase = 0
}
var stateSet legacyRuleMatchStateSet
if r.mode == C.LogicalTypeAnd {
stateSet = legacySingleRuleMatchState(evaluationBase)
for _, rule := range r.rules {
nestedMetadata := *metadata
nestedMetadata.ResetRuleCache()
stateSet = stateSet.combine(legacyMatchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase))
if stateSet.isEmpty() && !r.invert {
return legacyRuleMatchStateSet{}
}
}
} else {
for _, rule := range r.rules {
nestedMetadata := *metadata
nestedMetadata.ResetRuleCache()
stateSet = stateSet.merge(legacyMatchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase))
}
}
if r.invert {
return legacySingleRuleMatchStateWithFormula(base, stateSet.anyFormula().not())
}
return stateSet
}
func legacyDestinationIPFormula(items []RuleItem, metadata *adapter.InboundContext) legacyResponseFormula {
if legacyDestinationIPResolved(metadata) {
if matchAnyItem(items, metadata) {
return legacyTrueFormula()
}
return legacyFalseFormula()
}
var formula legacyResponseFormula
for _, rawItem := range items {
switch item := rawItem.(type) {
case *IPCIDRItem:
if item.isSource || metadata.IPCIDRMatchSource {
if item.Match(metadata) {
return legacyTrueFormula()
}
continue
}
formula = formula.or(legacyLiteralFormula(legacyResponseLiteral{
kind: legacyLiteralRequireSet,
ipSet: item.ipSet,
}))
if metadata.IPCIDRAcceptEmpty {
formula = formula.or(legacyLiteralFormula(legacyResponseLiteral{
kind: legacyLiteralRequireEmpty,
}))
}
case *IPIsPrivateItem:
if item.isSource {
if item.Match(metadata) {
return legacyTrueFormula()
}
continue
}
formula = formula.or(legacyLiteralFormula(legacyResponseLiteral{
kind: legacyLiteralRequireSet,
ipSet: legacyNonPublicIPSet,
}))
case *IPAcceptAnyItem:
formula = formula.or(legacyLiteralFormula(legacyResponseLiteral{
kind: legacyLiteralRequireNonEmpty,
}))
default:
if rawItem.Match(metadata) {
return legacyTrueFormula()
}
}
}
return formula
}
func legacyDestinationIPResolved(metadata *adapter.InboundContext) bool {
return metadata.IPCIDRMatchSource ||
metadata.DestinationAddressMatchFromResponse ||
metadata.DNSResponse != nil ||
metadata.Destination.IsIP() ||
len(metadata.DestinationAddresses) > 0
}

View File

@@ -733,18 +733,6 @@ func TestDNSMatchResponseMissingResponseUsesBooleanSemantics(t *testing.T) {
})
}
func TestDNSLegacyMatchResponseMissingResponseStillFailsClosed(t *testing.T) {
t.Parallel()
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
rule.invert = true
})
rule.matchResponse = true
metadata := testMetadata("lookup.example")
require.False(t, rule.LegacyPreMatch(&metadata))
}
func TestDNSAddressLimitIgnoresDestinationAddresses(t *testing.T) {
t.Parallel()
@@ -963,30 +951,6 @@ func TestDNSLegacyInvertAddressLimitPreLookupRegression(t *testing.T) {
func TestDNSLegacyInvertLogicalAddressLimitPreLookupRegression(t *testing.T) {
t.Parallel()
t.Run("wrapper invert keeps nested deferred rule matchable", func(t *testing.T) {
t.Parallel()
nestedRule := dnsRuleForTest(func(rule *abstractDefaultRule) {
addDestinationIPIsPrivateItem(rule)
})
logicalRule := &LogicalDNSRule{
abstractLogicalRule: abstractLogicalRule{
rules: []adapter.HeadlessRule{nestedRule},
mode: C.LogicalTypeAnd,
invert: true,
},
}
preLookupMetadata := testMetadata("lookup.example")
require.True(t, logicalRule.LegacyPreMatch(&preLookupMetadata))
matchedMetadata := testMetadata("lookup.example")
require.False(t, logicalRule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(netip.MustParseAddr("10.0.0.1"))))
unmatchedMetadata := testMetadata("lookup.example")
require.True(t, logicalRule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8"))))
})
t.Run("inverted deferred child does not suppress branch", func(t *testing.T) {
t.Parallel()
@@ -1028,94 +992,6 @@ func TestDNSLegacyInvertRuleSetAddressLimitPreLookupRegression(t *testing.T) {
require.True(t, rule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8"))))
}
func TestDNSLegacyInvertNegationStressRegression(t *testing.T) {
t.Parallel()
const branchCount = 20
unmatchedResponse := dnsResponseForTest(netip.MustParseAddr("203.0.113.250"))
t.Run("logical wrapper", func(t *testing.T) {
t.Parallel()
branches := make([]adapter.HeadlessRule, 0, branchCount)
var matchedAddrs []netip.Addr
for i := 0; i < branchCount; i++ {
firstCIDR, secondCIDR, branchAddrs := legacyNegationBranchCIDRs(i)
if matchedAddrs == nil {
matchedAddrs = branchAddrs
}
branches = append(branches, &LogicalDNSRule{
abstractLogicalRule: abstractLogicalRule{
mode: C.LogicalTypeAnd,
rules: []adapter.HeadlessRule{
dnsRuleForTest(func(rule *abstractDefaultRule) {
addDestinationIPCIDRItem(t, rule, []string{firstCIDR})
}),
dnsRuleForTest(func(rule *abstractDefaultRule) {
addDestinationIPCIDRItem(t, rule, []string{secondCIDR})
}),
},
},
})
}
rule := &LogicalDNSRule{
abstractLogicalRule: abstractLogicalRule{
rules: branches,
mode: C.LogicalTypeOr,
invert: true,
},
}
preLookupMetadata := testMetadata("lookup.example")
require.True(t, rule.LegacyPreMatch(&preLookupMetadata))
matchedMetadata := testMetadata("lookup.example")
require.False(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(matchedAddrs...)))
unmatchedMetadata := testMetadata("lookup.example")
require.True(t, rule.MatchAddressLimit(&unmatchedMetadata, unmatchedResponse))
})
t.Run("ruleset wrapper", func(t *testing.T) {
t.Parallel()
branches := make([]adapter.HeadlessRule, 0, branchCount)
var matchedAddrs []netip.Addr
for i := 0; i < branchCount; i++ {
firstCIDR, secondCIDR, branchAddrs := legacyNegationBranchCIDRs(i)
if matchedAddrs == nil {
matchedAddrs = branchAddrs
}
branches = append(branches, headlessLogicalRule(
C.LogicalTypeAnd,
false,
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
addDestinationIPCIDRItem(t, rule, []string{firstCIDR})
}),
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
addDestinationIPCIDRItem(t, rule, []string{secondCIDR})
}),
))
}
ruleSet := newLocalRuleSetForTest("dns-legacy-negation-stress", branches...)
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
rule.invert = true
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
})
preLookupMetadata := testMetadata("lookup.example")
require.True(t, rule.LegacyPreMatch(&preLookupMetadata))
matchedMetadata := testMetadata("lookup.example")
require.False(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(matchedAddrs...)))
unmatchedMetadata := testMetadata("lookup.example")
require.True(t, rule.MatchAddressLimit(&unmatchedMetadata, unmatchedResponse))
})
}
func TestDNSInvertAddressLimitPreLookupRegression(t *testing.T) {
t.Parallel()
testCases := []struct {
@@ -1296,12 +1172,6 @@ func dnsResponseForTest(addresses ...netip.Addr) *mDNS.Msg {
return response
}
func legacyNegationBranchCIDRs(index int) (string, string, []netip.Addr) {
first := netip.AddrFrom4([4]byte{198, 18, 0, byte(index*2 + 1)})
second := netip.AddrFrom4([4]byte{198, 18, 0, byte(index*2 + 2)})
return first.String() + "/32", second.String() + "/32", []netip.Addr{first, second}
}
func addRuleSetItem(rule *abstractDefaultRule, item *RuleSetItem) {
rule.ruleSetItem = item
rule.allItems = append(rule.allItems, item)