Files
sing-box/adapter/inbound.go
世界 6c5f351dcf dns: preserve legacy address-filter pre-match semantics
Legacy DNS address-filter mode still accepts destination-side IP
predicates with a deprecation warning, but the recent evaluate/
match_response refactor started evaluating those predicates during
pre-response Match(). That broke rules whose transport selection must
be deferred until MatchAddressLimit() can inspect the upstream reply.

Restore the old defer behavior by reintroducing an internal
IgnoreDestinationIPCIDRMatch flag on InboundContext and using it only
for legacy pre-response DNS matching. Default and logical DNS rules now
carry the legacy mode bit, set the ignore flag on metadata copies while
performing pre-response Match(), and explicitly clear it again for
match_response and MatchAddressLimit() so response-phase matching still
checks the returned addresses.

Add regression coverage for direct legacy destination-IP rules,
rule_set-backed CIDR rules, logical wrappers, and the legacy Lookup
router path, including fallback after a rejected response. This keeps
legacy configs working without changing new-mode evaluate semantics.

Tests: go test ./route/rule ./dns
Tests: make
2026-03-27 12:41:19 +08:00

186 lines
4.7 KiB
Go

package adapter
import (
"context"
"net"
"net/netip"
"strings"
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
"github.com/miekg/dns"
)
type Inbound interface {
Lifecycle
Type() string
Tag() string
}
type TCPInjectableInbound interface {
Inbound
ConnectionHandlerEx
}
type UDPInjectableInbound interface {
Inbound
PacketConnectionHandlerEx
}
type InboundRegistry interface {
option.InboundOptionsRegistry
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
}
type InboundManager interface {
Lifecycle
Inbounds() []Inbound
Get(tag string) (Inbound, bool)
Remove(tag string) error
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error
}
type InboundContext struct {
Inbound string
InboundType string
IPVersion uint8
Network string
Source M.Socksaddr
Destination M.Socksaddr
User string
Outbound string
// sniffer
Protocol string
Domain string
Client string
SniffContext any
SnifferNames []string
SniffError error
// cache
// Deprecated: implement in rule action
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
RouteOriginalDestination M.Socksaddr
UDPDisableDomainUnmapping bool
UDPConnect bool
UDPTimeout time.Duration
TLSFragment bool
TLSFragmentFallbackDelay time.Duration
TLSRecordFragment bool
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
DestinationAddresses []netip.Addr
DNSResponse *dns.Msg
DestinationAddressMatchFromResponse bool
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *ConnectionOwner
SourceMACAddress net.HardwareAddr
SourceHostname string
QueryType uint16
FakeIP bool
// rule cache
IPCIDRMatchSource bool
IPCIDRAcceptEmpty bool
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
DidMatch bool
IgnoreDestinationIPCIDRMatch bool
}
func (c *InboundContext) ResetRuleCache() {
c.IPCIDRMatchSource = false
c.IPCIDRAcceptEmpty = false
c.ResetRuleMatchCache()
}
func (c *InboundContext) ResetRuleMatchCache() {
c.SourceAddressMatch = false
c.SourcePortMatch = false
c.DestinationAddressMatch = false
c.DestinationPortMatch = false
c.DidMatch = false
}
func (c *InboundContext) DestinationAddressesForMatch() []netip.Addr {
if c.DestinationAddressMatchFromResponse {
return DNSResponseAddresses(c.DNSResponse)
}
return c.DestinationAddresses
}
func (c *InboundContext) DNSResponseAddressesForMatch() []netip.Addr {
return DNSResponseAddresses(c.DNSResponse)
}
func DNSResponseAddresses(response *dns.Msg) []netip.Addr {
if response == nil || response.Rcode != dns.RcodeSuccess {
return nil
}
addresses := make([]netip.Addr, 0, len(response.Answer))
for _, rawRecord := range response.Answer {
switch record := rawRecord.(type) {
case *dns.A:
addresses = append(addresses, M.AddrFromIP(record.A))
case *dns.AAAA:
addresses = append(addresses, M.AddrFromIP(record.AAAA))
case *dns.HTTPS:
for _, value := range record.SVCB.Value {
if value.Key() == dns.SVCB_IPV4HINT || value.Key() == dns.SVCB_IPV6HINT {
addresses = append(addresses, common.Map(strings.Split(value.String(), ","), M.ParseAddr)...)
}
}
}
}
return addresses
}
type inboundContextKey struct{}
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
return context.WithValue(ctx, (*inboundContextKey)(nil), inboundContext)
}
func ContextFrom(ctx context.Context) *InboundContext {
metadata := ctx.Value((*inboundContextKey)(nil))
if metadata == nil {
return nil
}
return metadata.(*InboundContext)
}
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
var newMetadata InboundContext
if metadata := ContextFrom(ctx); metadata != nil {
newMetadata = *metadata
}
return WithContext(ctx, &newMetadata), &newMetadata
}
func OverrideContext(ctx context.Context) context.Context {
if metadata := ContextFrom(ctx); metadata != nil {
newMetadata := *metadata
return WithContext(ctx, &newMetadata)
}
return ctx
}