mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
Compare commits
4 Commits
250bddfc8e
...
a1a0d8356c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1a0d8356c | ||
|
|
646ed69c0b | ||
|
|
74688751b0 | ||
|
|
dfa460372f |
@@ -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() {
|
||||
|
||||
106
dns/router.go
106
dns/router.go
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user