Imrpove flow tracking & sniff action

This commit is contained in:
世界 2026-07-06 23:40:48 +08:00
parent 9b5dee7dcf
commit f8f408feff
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
15 changed files with 368 additions and 53 deletions

View file

@ -17,7 +17,7 @@ import (
type Router interface {
Lifecycle
ConnectionRouter
PreMatch(metadata InboundContext) PreMatchResult
PreMatch(metadata InboundContext, firstPacket []byte) PreMatchResult
ConnectionRouterEx
RuleSet(tag string) (RuleSet, bool)
Rules() []Rule
@ -42,9 +42,10 @@ type PreMatchResult struct {
Action PreMatchAction
Outbound Outbound
Destination netip.AddrPort
NewTracker func() tun.FlowTracker
}
func JudgeFlow(router Router, inbound string, inboundType string, network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
func JudgeFlow(router Router, inbound string, inboundType string, network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
var networkName string
switch network {
case uint8(header.TCPProtocolNumber):
@ -67,14 +68,14 @@ func JudgeFlow(router Router, inbound string, inboundType string, network uint8,
metadata.Source.Port = 0
metadata.Destination.Port = 0
}
result := router.PreMatch(metadata)
result := router.PreMatch(metadata, firstPacket)
switch result.Action {
case PreMatchFlow:
port, isPort := result.Outbound.(tun.Port)
if !isPort {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
verdict := tun.FlowVerdict{Action: tun.ActionFlow, Port: port}
verdict := tun.FlowVerdict{Action: tun.ActionFlow, Port: port, NewTracker: result.NewTracker}
if result.Destination.IsValid() {
destinationPort := result.Destination.Port()
if networkName == N.NetworkICMP {
@ -97,6 +98,7 @@ func JudgeFlow(router Router, inbound string, inboundType string, network uint8,
type ConnectionTracker interface {
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
RoutedFlow(ctx context.Context, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) tun.FlowTracker
}
// Deprecated: Use ConnectionRouterEx instead.

View file

@ -7,6 +7,7 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
@ -68,6 +69,13 @@ func (m *Manager) RoutedPacketConnection(ctx context.Context, conn N.PacketConn,
return tracker
}
func (m *Manager) RoutedFlow(ctx context.Context, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) tun.FlowTracker {
return &flowTracker{
metadata: m.newTrackerMetadata(metadata, matchedRule, matchOutbound, new(atomic.Int64), new(atomic.Int64)),
manager: m,
}
}
func (m *Manager) newTrackerMetadata(metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound, upload *atomic.Int64, download *atomic.Int64) TrackerMetadata {
id, _ := uuid.NewV4()
var (
@ -135,6 +143,53 @@ func (t *connTracker) WriterReplaceable() bool {
return true
}
var (
_ Tracker = (*flowTracker)(nil)
_ tun.FlowTracker = (*flowTracker)(nil)
)
type flowTracker struct {
metadata TrackerMetadata
manager *Manager
handle tun.FlowHandle
}
func (t *flowTracker) Metadata() *TrackerMetadata {
return &t.metadata
}
func (t *flowTracker) AttachFlow(handle tun.FlowHandle) {
t.handle = handle
t.manager.join(t)
}
func (t *flowTracker) CountForward(n int) {
t.metadata.Upload.Add(int64(n))
t.manager.uploadTotal.Add(int64(n))
}
func (t *flowTracker) CountReverse(n int) {
t.metadata.Download.Add(int64(n))
t.manager.downloadTotal.Add(int64(n))
}
func (t *flowTracker) FlowEstablished() {
}
func (t *flowTracker) CloseFlow(reason tun.FlowCloseReason) {
t.manager.leave(t)
}
func (t *flowTracker) Close() error {
handle := t.handle
if handle != nil {
handle.CloseFlow()
} else {
t.manager.leave(t)
}
return nil
}
type packetConnTracker struct {
N.PacketConn
metadata TrackerMetadata

View file

@ -986,8 +986,13 @@ func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb
s.serviceAccess.RLock()
nowService := s.instance
s.serviceAccess.RUnlock()
if nowService != nil && nowService.connectionManager != nil {
nowService.connectionManager.CloseAll()
if nowService != nil {
if nowService.connectionManager != nil {
nowService.connectionManager.CloseAll()
}
if nowService.trafficManager != nil {
nowService.trafficManager.CloseAllConnections()
}
}
return &emptypb.Empty{}, nil
}

View file

@ -6,7 +6,8 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.14.0"
:material-alert: [route](#route)
:material-alert: [route](#route)
:material-plus: [sniff](#sniff)
!!! quote "Changes in sing-box 1.13.0"
@ -16,11 +17,11 @@ Pre-match is rule matching that runs before the connection is established.
### How it works
When an L3 inbound (TUN, WireGuard, or Tailscale) receives a connection request, the connection has not yet been established,
so no connection data can be read. In this phase, sing-box runs the routing rules in pre-match mode.
When an L3 inbound (TUN, WireGuard, or Tailscale) receives a connection request, the connection has not yet been established:
for TCP connections no connection data is available, while for UDP connections only the first packet is available.
In this phase, sing-box runs the routing rules in pre-match mode.
Since connection data is unavailable, only actions that do not require connection data can be executed.
When a rule matches an action that requires an established connection, pre-match stops at that rule.
When a rule matches an action that requires more connection data than available, pre-match stops at that rule.
### Supported actions
@ -53,6 +54,19 @@ otherwise connections will be rejected.
See [route](/configuration/route/rule_action/#route) for details.
#### sniff
!!! question "Since sing-box 1.14.0"
For UDP connections, the first packet is available in pre-match,
so protocol sniffing runs on it directly and rule matching continues with the sniffed metadata.
When sniffers require more data (like a fragmented QUIC Client Hello), pre-match stops at that rule.
For TCP connections, pre-match always stops at that rule.
See [sniff](/configuration/route/rule_action/#sniff) for details.
#### bypass
!!! question "Since sing-box 1.13.0"

View file

@ -6,7 +6,8 @@ icon: material/new-box
!!! quote "sing-box 1.14.0 中的更改"
:material-alert: [route](#route)
:material-alert: [route](#route)
:material-plus: [sniff](#sniff)
!!! quote "sing-box 1.13.0 中的更改"
@ -16,9 +17,9 @@ icon: material/new-box
### 工作原理
当 L3 入站TUN、WireGuard 或 Tailscale收到连接请求时连接尚未建立,因此无法读取连接数据。在此阶段sing-box 在预匹配模式下运行路由规则。
当 L3 入站TUN、WireGuard 或 Tailscale收到连接请求时连接尚未建立:对于 TCP 连接,无连接数据可用;对于 UDP 连接,仅首个数据包可用。在此阶段sing-box 在预匹配模式下运行路由规则。
由于连接数据不可用,只有不需要连接数据的动作才能执行。当规则匹配到需要已建立连接的动作时,预匹配将在该规则处停止。
当规则匹配到需要比当前可用数据更多连接数据的动作时,预匹配将在该规则处停止。
### 支持的动作
@ -47,6 +48,18 @@ FakeIP 目标需要在预匹配中先执行 `resolve` 动作,否则连接将
详情参阅 [route](/zh/configuration/route/rule_action/#route)。
#### sniff
!!! question "自 sing-box 1.14.0 起"
对于 UDP 连接,首个数据包在预匹配中可用,因此协议探测将直接在其上运行,随后规则匹配将携带探测结果继续。
当探测器需要更多数据时(如分片的 QUIC Client Hello预匹配将在该规则处停止。
对于 TCP 连接,预匹配总是在该规则处停止。
详情参阅 [sniff](/zh/configuration/route/rule_action/#sniff)。
#### bypass
!!! question "自 sing-box 1.13.0 起"

View file

@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
@ -118,6 +119,63 @@ func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.Packet
return bufio.NewInt64CounterPacketConn(conn, readCounter, nil, writeCounter, nil)
}
func (s *StatsService) RoutedFlow(ctx context.Context, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) tun.FlowTracker {
inbound := metadata.Inbound
user := metadata.User
outbound := matchOutbound.Tag()
var uplinkCounter []*atomic.Int64
var downlinkCounter []*atomic.Int64
countInbound := inbound != "" && s.inbounds[inbound]
countOutbound := outbound != "" && s.outbounds[outbound]
countUser := user != "" && s.users[user]
if !countInbound && !countOutbound && !countUser {
return nil
}
s.access.Lock()
if countInbound {
uplinkCounter = append(uplinkCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>uplink"))
downlinkCounter = append(downlinkCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>downlink"))
}
if countOutbound {
uplinkCounter = append(uplinkCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>uplink"))
downlinkCounter = append(downlinkCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>downlink"))
}
if countUser {
uplinkCounter = append(uplinkCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>uplink"))
downlinkCounter = append(downlinkCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
}
s.access.Unlock()
return &statsFlowTracker{uplinkCounter: uplinkCounter, downlinkCounter: downlinkCounter}
}
var _ tun.FlowTracker = (*statsFlowTracker)(nil)
type statsFlowTracker struct {
uplinkCounter []*atomic.Int64
downlinkCounter []*atomic.Int64
}
func (t *statsFlowTracker) AttachFlow(handle tun.FlowHandle) {
}
func (t *statsFlowTracker) CountForward(n int) {
for _, counter := range t.uplinkCounter {
counter.Add(int64(n))
}
}
func (t *statsFlowTracker) CountReverse(n int) {
for _, counter := range t.downlinkCounter {
counter.Add(int64(n))
}
}
func (t *statsFlowTracker) FlowEstablished() {
}
func (t *statsFlowTracker) CloseFlow(reason tun.FlowCloseReason) {
}
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
s.access.Lock()
counter, loaded := s.counters[request.Name]

2
go.mod
View file

@ -55,7 +55,7 @@ require (
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
github.com/sagernet/smux v1.5.50-sing-box-mod.1
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.9
github.com/sagernet/wireguard-go v0.0.5-0.20260706130655-57baac9504a8
github.com/sagernet/wireguard-go v0.0.5-0.20260706153856-2c27bbf4f97f
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1

2
go.sum
View file

@ -302,6 +302,8 @@ github.com/sagernet/wireguard-go v0.0.4 h1:w/vHtk7AzMG37+D/uQROBL72Gj/Gj+FHMY5kg
github.com/sagernet/wireguard-go v0.0.4/go.mod h1:hEqi4y5czEg6LYtX2Bpjg+lV0b/J1n+5rA885Z66Mx0=
github.com/sagernet/wireguard-go v0.0.5-0.20260706130655-57baac9504a8 h1:gfukXANr9v5kcrKGeoCV5c+IdessM9NRGzWE7Aot9sw=
github.com/sagernet/wireguard-go v0.0.5-0.20260706130655-57baac9504a8/go.mod h1:hEqi4y5czEg6LYtX2Bpjg+lV0b/J1n+5rA885Z66Mx0=
github.com/sagernet/wireguard-go v0.0.5-0.20260706153856-2c27bbf4f97f h1:TzN97RL07xWb3gZtmqFhsdkud4f6G/pohiaOLiqSBj4=
github.com/sagernet/wireguard-go v0.0.5-0.20260706153856-2c27bbf4f97f/go.mod h1:hEqi4y5czEg6LYtX2Bpjg+lV0b/J1n+5rA885Z66Mx0=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=

View file

@ -144,7 +144,7 @@ func (h *icmpRouterHandler) RouteICMPFlow(source netip.Addr, destination netip.A
Network: N.NetworkICMP,
Source: M.SocksaddrFrom(source, 0),
Destination: M.SocksaddrFrom(destination, 0),
})
}, nil)
switch result.Action {
case adapter.PreMatchFlow:
flowOutbound, isFlowOutbound := result.Outbound.(adapter.FlowOutbound)

View file

@ -38,7 +38,7 @@ func (t *Endpoint) PortMTU() uint32 {
return uint32(tsTUN.DefaultTUNMTU())
}
func (t *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
func (t *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
inet4Address, inet6Address := t.PortAddresses()
if destination.Addr() == inet4Address || destination.Addr() == inet6Address {
return tun.FlowVerdict{Action: tun.ActionAccept}
@ -70,7 +70,7 @@ func (t *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination n
}
}
}
return adapter.JudgeFlow(t.router, t.Tag(), t.Type(), network, source, destination)
return adapter.JudgeFlow(t.router, t.Tag(), t.Type(), network, source, destination, firstPacket)
}
func (t *Endpoint) AttachReturn(returnPath tun.Return) error {

View file

@ -6,6 +6,7 @@ import (
"net/netip"
"os"
"runtime"
"slices"
"strconv"
"strings"
"time"
@ -479,8 +480,11 @@ func (t *Inbound) Close() error {
)
}
func (t *Inbound) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
return adapter.JudgeFlow(t.router, t.tag, C.TypeTun, network, source, destination)
func (t *Inbound) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
if slices.Contains(t.dnsHijackAddress, destination.Addr()) {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
return adapter.JudgeFlow(t.router, t.tag, C.TypeTun, network, source, destination, firstPacket)
}
func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
@ -490,10 +494,8 @@ func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S
metadata.InboundType = C.TypeTun
metadata.Source = source
metadata.Destination = destination
for _, dnsHijackAddress := range t.dnsHijackAddress {
if destination.Addr == dnsHijackAddress {
metadata.Protocol = C.ProtocolDNS
}
if slices.Contains(t.dnsHijackAddress, destination.Addr) {
metadata.Protocol = C.ProtocolDNS
}
if metadata.Protocol == C.ProtocolDNS {
t.logger.InfoContext(ctx, "inbound DNS connection from ", metadata.Source)
@ -527,8 +529,8 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
type autoRedirectHandler Inbound
func (t *autoRedirectHandler) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
return (*Inbound)(t).JudgeFlow(network, source, destination)
func (t *autoRedirectHandler) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
return (*Inbound)(t).JudgeFlow(network, source, destination, firstPacket)
}
func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {

View file

@ -161,13 +161,13 @@ func (w *Endpoint) DetachReturn(returnPath tun.Return) error {
return w.endpoint.DetachReturn(returnPath)
}
func (w *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
func (w *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
for _, localPrefix := range w.localAddresses {
if localPrefix.Contains(destination.Addr()) {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
}
return adapter.JudgeFlow(w.router, w.Tag(), w.Type(), network, source, destination)
return adapter.JudgeFlow(w.router, w.Tag(), w.Type(), network, source, destination, firstPacket)
}
func (w *Endpoint) WritePackets(packets [][]byte) error {

101
route/flow_tracker.go Normal file
View file

@ -0,0 +1,101 @@
package route
import (
"context"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/byteformats"
N "github.com/sagernet/sing/common/network"
)
var (
_ tun.FlowTracker = (*flowLogger)(nil)
_ tun.FlowTracker = (multiFlowTracker)(nil)
)
type flowLogger struct {
ctx context.Context
logger log.ContextLogger
network string
source string
destination string
outbound adapter.Outbound
createdAt time.Time
upload atomic.Int64
download atomic.Int64
}
func newFlowLogger(ctx context.Context, logger log.ContextLogger, metadata adapter.InboundContext, outbound adapter.Outbound) *flowLogger {
var source, destination string
if metadata.Network == N.NetworkICMP {
source = metadata.Source.AddrString()
destination = metadata.Destination.AddrString()
} else {
source = metadata.Source.String()
destination = metadata.Destination.String()
}
return &flowLogger{
ctx: ctx,
logger: logger,
network: metadata.Network,
source: source,
destination: destination,
outbound: outbound,
}
}
func (l *flowLogger) AttachFlow(handle tun.FlowHandle) {
l.createdAt = time.Now()
}
func (l *flowLogger) CountForward(n int) {
l.upload.Add(int64(n))
}
func (l *flowLogger) CountReverse(n int) {
l.download.Add(int64(n))
}
func (l *flowLogger) FlowEstablished() {
}
func (l *flowLogger) CloseFlow(reason tun.FlowCloseReason) {
l.logger.DebugContext(l.ctx, "flow closed: ", reason,
", upload ", byteformats.FormatBytes(uint64(l.upload.Load())), ", download ", byteformats.FormatBytes(uint64(l.download.Load())))
}
type multiFlowTracker []tun.FlowTracker
func (t multiFlowTracker) AttachFlow(handle tun.FlowHandle) {
for _, tracker := range t {
tracker.AttachFlow(handle)
}
}
func (t multiFlowTracker) CountForward(n int) {
for _, tracker := range t {
tracker.CountForward(n)
}
}
func (t multiFlowTracker) CountReverse(n int) {
for _, tracker := range t {
tracker.CountReverse(n)
}
}
func (t multiFlowTracker) FlowEstablished() {
for _, tracker := range t {
tracker.FlowEstablished()
}
}
func (t multiFlowTracker) CloseFlow(reason tun.FlowCloseReason) {
for _, tracker := range t {
tracker.CloseFlow(reason)
}
}

View file

@ -12,8 +12,10 @@ import (
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-mux"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
@ -28,6 +30,16 @@ import (
"golang.org/x/exp/slices"
)
var defaultPacketSniffers = []sniff.PacketSniffer{
sniff.DomainNameQuery,
sniff.QUICClientHello,
sniff.STUNMessage,
sniff.UTP,
sniff.UDPTracker,
sniff.DTLSRecord,
sniff.NTP,
}
// Deprecated: use RouteConnectionEx instead.
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
done := make(chan any)
@ -294,7 +306,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
return nil
}
func (r *Router) PreMatch(metadata adapter.InboundContext) adapter.PreMatchResult {
func (r *Router) PreMatch(metadata adapter.InboundContext, firstPacket []byte) adapter.PreMatchResult {
ctx := log.ContextWithNewID(r.ctx)
continueResult := adapter.PreMatchResult{Action: adapter.PreMatchContinue}
packetDestination := metadata.Destination
if metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) {
@ -319,46 +332,89 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) adapter.PreMatchResul
if !currentRule.Match(&metadata) {
continue
}
ruleDescription := currentRule.String()
if ruleDescription != "" {
r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action())
} else {
r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] => ", currentRule.Action())
}
switch action := currentRule.Action().(type) {
case *R.RuleActionSniff:
if metadata.Network == N.NetworkICMP {
continue
}
return continueResult
if metadata.Network != N.NetworkUDP || len(firstPacket) == 0 {
return continueResult
}
if sniff.Skip(&metadata) || metadata.Protocol != "" {
continue
}
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
continue
}
if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil {
continue
}
packetSniffers := action.PacketSniffers
if len(packetSniffers) == 0 {
packetSniffers = defaultPacketSniffers
}
sniffErr := sniff.PeekPacket(ctx, &metadata, firstPacket, packetSniffers...)
metadata.SnifferNames = action.SnifferNames
metadata.SniffError = sniffErr
if sniffErr != nil {
if errors.Is(sniffErr, sniff.ErrNeedMoreData) {
return continueResult
}
continue
}
//goland:noinspection GoDeprecation
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
metadata.Destination = M.Socksaddr{
Fqdn: metadata.Domain,
Port: metadata.Destination.Port,
}
}
if metadata.Domain != "" && metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client)
} else if metadata.Domain != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
} else if metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client)
} else {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
}
case *R.RuleActionRouteOptions:
applyRouteOptionsOverride(&metadata, action)
case *R.RuleActionRoute:
applyRouteOptionsOverride(&metadata, &action.RuleActionRouteOptions)
r.logger.Debug("pre-match[", currentRuleIndex, "] ", currentRule, " => ", action)
return r.preMatchFlow(&metadata, packetDestination, action.Outbound)
return r.preMatchFlow(ctx, &metadata, packetDestination, currentRule, action.Outbound)
case *R.RuleActionBypass:
applyRouteOptionsOverride(&metadata, &action.RuleActionRouteOptions)
r.logger.Debug("pre-match[", currentRuleIndex, "] ", currentRule, " => ", action)
if action.Outbound == "" {
if metadata.Destination.IsDomain() || metadata.Destination != packetDestination {
return continueResult
}
return adapter.PreMatchResult{Action: adapter.PreMatchBypass}
}
return r.preMatchFlow(&metadata, packetDestination, action.Outbound)
return r.preMatchFlow(ctx, &metadata, packetDestination, currentRule, action.Outbound)
case *R.RuleActionReject:
r.logger.Debug("pre-match[", currentRuleIndex, "] ", currentRule, " => ", action)
rejectErr := action.Error(r.ctx)
if errors.Is(rejectErr, R.ErrDrop) {
return adapter.PreMatchResult{Action: adapter.PreMatchDrop}
}
return adapter.PreMatchResult{Action: adapter.PreMatchReject}
case *R.RuleActionResolve:
resolveErr := r.actionResolve(adapter.WithContext(r.ctx, &metadata), &metadata, action)
resolveErr := r.actionResolve(adapter.WithContext(ctx, &metadata), &metadata, action)
if resolveErr != nil {
r.logger.Debug("pre-match[", currentRuleIndex, "] ", currentRule, " => ", action, ": ", resolveErr)
r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", action, ": ", resolveErr)
return adapter.PreMatchResult{Action: adapter.PreMatchReject}
}
default:
return continueResult
}
}
return r.preMatchFlow(&metadata, packetDestination, "")
return r.preMatchFlow(ctx, &metadata, packetDestination, nil, "")
}
func applyRouteOptionsOverride(metadata *adapter.InboundContext, routeOptions *R.RuleActionRouteOptions) {
@ -378,7 +434,7 @@ func applyRouteOptionsOverride(metadata *adapter.InboundContext, routeOptions *R
}
}
func (r *Router) preMatchFlow(metadata *adapter.InboundContext, packetDestination M.Socksaddr, outboundTag string) adapter.PreMatchResult {
func (r *Router) preMatchFlow(ctx context.Context, metadata *adapter.InboundContext, packetDestination M.Socksaddr, matchedRule adapter.Rule, outboundTag string) adapter.PreMatchResult {
continueResult := adapter.PreMatchResult{Action: adapter.PreMatchContinue}
var outbound adapter.Outbound
if outboundTag == "" {
@ -409,7 +465,7 @@ func (r *Router) preMatchFlow(metadata *adapter.InboundContext, packetDestinatio
if outbound.Type() == C.TypeDirect {
directDialer, isDirectDialer := outbound.(dialer.DirectDialer)
if isDirectDialer && directDialer.IsEmpty() && !metadata.Destination.IsDomain() && metadata.Destination == packetDestination {
r.logger.Debug("pre-match bypass ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
r.logger.DebugContext(ctx, "pre-match bypass ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return adapter.PreMatchResult{Action: adapter.PreMatchBypass, Outbound: outbound}
}
}
@ -429,9 +485,9 @@ func (r *Router) preMatchFlow(metadata *adapter.InboundContext, packetDestinatio
}
if !newDestination.IsValid() {
if len(metadata.DestinationAddresses) == 0 {
r.logger.Warn("pre-match: reject ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to fake destination ", metadata.Destination.Fqdn, ": a resolve action is required before routing to outbound/", outbound.Type(), "[", outbound.Tag(), "]")
r.logger.WarnContext(ctx, "pre-match: reject ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to fake destination ", metadata.Destination.Fqdn, ": a resolve action is required before routing to outbound/", outbound.Type(), "[", outbound.Tag(), "]")
} else {
r.logger.Debug("pre-match: reject ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to fake destination ", metadata.Destination.Fqdn, ": no resolved address for this address family")
r.logger.DebugContext(ctx, "pre-match: reject ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to fake destination ", metadata.Destination.Fqdn, ": no resolved address for this address family")
}
return adapter.PreMatchResult{Action: adapter.PreMatchReject}
}
@ -439,7 +495,22 @@ func (r *Router) preMatchFlow(metadata *adapter.InboundContext, packetDestinatio
} else if metadata.Destination != packetDestination {
result.Destination = metadata.Destination.AddrPort()
}
r.logger.Debug("pre-match forward ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString(), " via outbound/", outbound.Type(), "[", outbound.Tag(), "]")
r.logger.InfoContext(ctx, "pre-match: forward ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString(), " via outbound/", outbound.Type(), "[", outbound.Tag(), "]")
metadataCopy := *metadata
result.NewTracker = func() tun.FlowTracker {
flowTrackers := make([]tun.FlowTracker, 0, len(r.trackers)+1)
flowTrackers = append(flowTrackers, newFlowLogger(ctx, r.logger, metadataCopy, outbound))
for _, tracker := range r.trackers {
flowTracker := tracker.RoutedFlow(ctx, metadataCopy, matchedRule, outbound)
if flowTracker != nil {
flowTrackers = append(flowTrackers, flowTracker)
}
}
if len(flowTrackers) == 1 {
return flowTrackers[0]
}
return multiFlowTracker(flowTrackers)
}
return result
}
@ -678,15 +749,7 @@ func (r *Router) actionSniff(
if len(action.PacketSniffers) > 0 {
packetSniffers = action.PacketSniffers
} else {
packetSniffers = []sniff.PacketSniffer{
sniff.DomainNameQuery,
sniff.QUICClientHello,
sniff.STUNMessage,
sniff.UTP,
sniff.UDPTracker,
sniff.DTLSRecord,
sniff.NTP,
}
packetSniffers = defaultPacketSniffers
}
var err error
for _, packetBuffer := range inputPacketBuffers {

View file

@ -20,7 +20,7 @@ type ruleSetItemTestRouter struct {
func (r *ruleSetItemTestRouter) Start(adapter.StartStage) error { return nil }
func (r *ruleSetItemTestRouter) Close() error { return nil }
func (r *ruleSetItemTestRouter) PreMatch(adapter.InboundContext) adapter.PreMatchResult {
func (r *ruleSetItemTestRouter) PreMatch(adapter.InboundContext, []byte) adapter.PreMatchResult {
return adapter.PreMatchResult{}
}