Forward endpoint local flows to tun

This commit is contained in:
世界 committed 2026-11-09 15:17:20 +00:00
1 parent 5702a4966e
commit fbf514205c
19 files changed
+160 -82

No files matched your search

+92
View File
@@ -0,0 +1,92 @@
package adapter
import (
"context"
"net/netip"
"sync/atomic"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/gtcpip/header"
"github.com/sagernet/sing/common/byteformats"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func JudgeHostFlow(ctx context.Context, networkManager NetworkManager, logger log.ContextLogger, network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
port := networkManager.HostPort()
if port == nil {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
inet4Address, inet6Address := port.PortAddresses()
hostAddress := inet4Address
if destination.Addr().Is6() {
hostAddress = inet6Address
}
networkName := flowNetworkName(network)
if !hostAddress.IsValid() || networkName == "" {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
hostDestination := netip.AddrPortFrom(hostAddress, destination.Port())
return tun.FlowVerdict{
Action: tun.ActionFlow,
Port: port,
Destination: hostDestination,
NewTracker: func() tun.FlowTracker {
flowContext := log.ContextWithNewID(ctx)
sourceAddress := M.SocksaddrFromNetIP(source)
destinationAddress := M.SocksaddrFromNetIP(hostDestination)
if networkName == N.NetworkICMP {
sourceAddress.Port = 0
destinationAddress.Port = 0
}
logger.InfoContext(flowContext, "forward ", networkName, " connection from ", sourceAddress, " to ", destinationAddress, " via tun")
return NewFlowLogger(flowContext, logger)
},
}
}
func flowNetworkName(network uint8) string {
switch network {
case uint8(header.TCPProtocolNumber):
return N.NetworkTCP
case uint8(header.UDPProtocolNumber):
return N.NetworkUDP
case uint8(header.ICMPv4ProtocolNumber), uint8(header.ICMPv6ProtocolNumber):
return N.NetworkICMP
default:
return ""
}
}
var _ tun.FlowTracker = (*FlowLogger)(nil)
type FlowLogger struct {
ctx context.Context
logger log.ContextLogger
upload atomic.Int64
download atomic.Int64
}
func NewFlowLogger(ctx context.Context, logger log.ContextLogger) *FlowLogger {
return &FlowLogger{ctx: ctx, logger: logger}
}
func (l *FlowLogger) AttachFlow(handle tun.FlowHandle) {
}
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())))
}
+2
View File
@@ -30,6 +30,8 @@ type NetworkManager interface {
AutoRedirectOutputMarkFunc() control.Func
RegisterBridgeInterface(interfaceName string)
BridgeInterfaces() []string
RegisterHostPort(port tun.RoutedPort)
HostPort() tun.RoutedPort
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
+2 -10
View File
@@ -7,7 +7,6 @@ import (
"time"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/gtcpip/header"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
@@ -50,15 +49,8 @@ type PreMatchResult struct {
}
func JudgeFlow(router Router, metadata InboundContext, network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
var networkName string
switch network {
case uint8(header.TCPProtocolNumber):
networkName = N.NetworkTCP
case uint8(header.UDPProtocolNumber):
networkName = N.NetworkUDP
case uint8(header.ICMPv4ProtocolNumber), uint8(header.ICMPv6ProtocolNumber):
networkName = N.NetworkICMP
default:
networkName := flowNetworkName(network)
if networkName == "" {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
metadata.Network = networkName
+1 -1
View File
@@ -58,7 +58,7 @@ require (
github.com/sagernet/sing-usbip v0.0.0-20260817040617-28bd42667eca
github.com/sagernet/sing-vmess v0.2.9-0.20261108133243-b01463cef941
github.com/sagernet/smux v1.5.50-sing-box-mod.1
github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.5.0.20261022222424-1688b52f8204
github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.5.0.20261028024127-670967df7dbf
github.com/sagernet/wireguard-go v0.0.8-0.20261022215849-fdf0ad046586
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.10.2
+2 -2
View File
@@ -346,8 +346,8 @@ github.com/sagernet/sing-vmess v0.2.9-0.20261108133243-b01463cef941 h1:QiLfQ/yZw
github.com/sagernet/sing-vmess v0.2.9-0.20261108133243-b01463cef941/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=
github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.5.0.20261022222424-1688b52f8204 h1:k25hl6yegTD3I3UEp95ZF09KDBiFsutUawV7vjRjQsg=
github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.5.0.20261022222424-1688b52f8204/go.mod h1:Z8Eql5l0q6mQ+etunojfaZxNJVhEkpbKGS/nnx9ZQpE=
github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.5.0.20261028024127-670967df7dbf h1:59b83P3sRDWJcTifDZpeVn4gZFropKsNdYWuPfhqI1w=
github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.5.0.20261028024127-670967df7dbf/go.mod h1:Z8Eql5l0q6mQ+etunojfaZxNJVhEkpbKGS/nnx9ZQpE=
github.com/sagernet/wireguard-go v0.0.8-0.20261022215849-fdf0ad046586 h1:lssrGZJRiO6s2ShbBjchpFsNml9cj/7G+aeEL57ZvVc=
github.com/sagernet/wireguard-go v0.0.8-0.20261022215849-fdf0ad046586/go.mod h1:er10sELpmzLXq7S7Pbc1Zsbyapcr+/gxNAHKTo6fzVA=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
+3 -1
View File
@@ -125,6 +125,8 @@ func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.Co
Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeMASQUEClient, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
router: router,
logger: logger,
system: options.System,
network: service.FromContext[adapter.NetworkManager](ctx),
},
ctx: ctx,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
@@ -273,7 +275,7 @@ func (c *ClientEndpoint) DetachReturn(returnPath tun.Return) error {
}
func (c *ClientEndpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
return c.judgeFlow(c, c.state.Load().localAddresses, network, source, destination, firstPacket)
return c.judgeFlow(c.ctx, c, c.state.Load().localAddresses, network, source, destination, firstPacket)
}
func (c *ClientEndpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) {
+6 -1
View File
@@ -30,6 +30,8 @@ type endpointBase struct {
endpoint.Adapter
router adapter.Router
logger log.ContextLogger
system bool
network adapter.NetworkManager
}
func newDeviceOptions(ctx context.Context, logger log.ContextLogger, handler tun.Handler, options option.MASQUEEndpointOptions, udpTimeout time.Duration, address []netip.Prefix) *device.Options {
@@ -101,10 +103,13 @@ func (e *endpointBase) newDNSPacket(ctx context.Context, endpoint adapter.Endpoi
e.router.HijackDNSPacket(ctx, payload, writer, metadata)
}
func (e *endpointBase) judgeFlow(endpoint adapter.Endpoint, localAddresses []netip.Prefix, network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
func (e *endpointBase) judgeFlow(ctx context.Context, endpoint adapter.Endpoint, localAddresses []netip.Prefix, network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
if isLocalAddress(localAddresses, destination.Addr()) {
if e.system {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
return adapter.JudgeHostFlow(ctx, e.network, e.logger, network, source, destination)
}
return adapter.JudgeFlow(e.router, adapter.InboundContext{Inbound: endpoint.Tag(), InboundType: endpoint.Type()}, network, source, destination, firstPacket)
}
+3 -1
View File
@@ -80,6 +80,8 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co
Adapter: endpoint.NewAdapter(C.TypeMASQUEServer, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
router: router,
logger: logger,
system: options.System,
network: service.FromContext[adapter.NetworkManager](ctx),
},
ctx: ctx,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
@@ -249,7 +251,7 @@ func (s *ServerEndpoint) DetachReturn(returnPath tun.Return) error {
}
func (s *ServerEndpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
return s.judgeFlow(s, s.localAddresses, network, source, destination, firstPacket)
return s.judgeFlow(s.ctx, s, s.localAddresses, network, source, destination, firstPacket)
}
func (s *ServerEndpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) {
+3 -1
View File
@@ -111,6 +111,8 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeOpenConnect, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
router: router,
logger: logger,
system: options.System,
network: service.FromContext[adapter.NetworkManager](ctx),
},
loopContext: loopContext,
cancelLoop: cancelLoop,
@@ -569,7 +571,7 @@ func (e *Endpoint) DetachReturn(returnPath tun.Return) error {
}
func (e *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
return judgeOpenConnectFlow(e.router, e.Tag(), e.Type(), e.state.Load().localAddresses, network, source, destination, firstPacket)
return e.judgeFlow(e.loopContext, e, e.state.Load().localAddresses, network, source, destination, firstPacket)
}
func (e *Endpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) {
+7 -2
View File
@@ -30,6 +30,8 @@ type endpointBase struct {
endpoint.Adapter
router adapter.Router
logger log.ContextLogger
system bool
network adapter.NetworkManager
}
func (e *endpointBase) newConnection(ctx context.Context, endpoint adapter.Endpoint, localAddresses []netip.Prefix, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
@@ -91,13 +93,16 @@ func loopbackAddressFor(address netip.Addr) netip.Addr {
return netip.IPv6Loopback()
}
func judgeOpenConnectFlow(router adapter.Router, tag string, endpointType string, localAddresses []netip.Prefix, network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
func (e *endpointBase) judgeFlow(ctx context.Context, endpoint adapter.Endpoint, localAddresses []netip.Prefix, network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
for _, localPrefix := range localAddresses {
if destination.Addr() == localPrefix.Addr() {
if e.system {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
return adapter.JudgeHostFlow(ctx, e.network, e.logger, network, source, destination)
}
return adapter.JudgeFlow(router, adapter.InboundContext{Inbound: tag, InboundType: endpointType}, network, source, destination, firstPacket)
}
return adapter.JudgeFlow(e.router, adapter.InboundContext{Inbound: endpoint.Tag(), InboundType: endpoint.Type()}, network, source, destination, firstPacket)
}
func materialSource(name string, inlineValues []string, path string) (openconnect.Material, error) {
+3 -1
View File
@@ -82,6 +82,8 @@ func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.Co
Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeOpenVPNClient, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
router: router,
logger: logger,
system: options.System,
network: service.FromContext[adapter.NetworkManager](ctx),
},
ctx: ctx,
loopContext: loopContext,
@@ -752,7 +754,7 @@ func (c *ClientEndpoint) DetachReturn(returnPath tun.Return) error {
}
func (c *ClientEndpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
return judgeOpenVPNFlow(c.router, c.Tag(), c.Type(), c.state.Load().localAddresses, network, source, destination, firstPacket)
return c.judgeFlow(c.ctx, c, c.state.Load().localAddresses, network, source, destination, firstPacket)
}
func (c *ClientEndpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) {
+7 -2
View File
@@ -36,6 +36,8 @@ type endpointBase struct {
endpoint.Adapter
router adapter.Router
logger log.ContextLogger
system bool
network adapter.NetworkManager
}
func (e *endpointBase) newConnection(ctx context.Context, endpoint adapter.Endpoint, localAddresses []netip.Prefix, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
@@ -97,13 +99,16 @@ func loopbackAddressFor(address netip.Addr) netip.Addr {
return netip.IPv6Loopback()
}
func judgeOpenVPNFlow(router adapter.Router, tag string, endpointType string, localAddresses []netip.Prefix, network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
func (e *endpointBase) judgeFlow(ctx context.Context, endpoint adapter.Endpoint, localAddresses []netip.Prefix, network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
for _, localPrefix := range localAddresses {
if destination.Addr() == localPrefix.Addr() {
if e.system {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
return adapter.JudgeHostFlow(ctx, e.network, e.logger, network, source, destination)
}
return adapter.JudgeFlow(router, adapter.InboundContext{Inbound: tag, InboundType: endpointType}, network, source, destination, firstPacket)
}
return adapter.JudgeFlow(e.router, adapter.InboundContext{Inbound: endpoint.Tag(), InboundType: endpoint.Type()}, network, source, destination, firstPacket)
}
func keyDirectionValue(direction string) (int, error) {
+3 -1
View File
@@ -80,6 +80,8 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co
Adapter: endpoint.NewAdapter(C.TypeOpenVPNServer, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
router: router,
logger: logger,
system: options.System,
network: service.FromContext[adapter.NetworkManager](ctx),
},
ctx: ctx,
cancelLoop: cancelLoop,
@@ -668,7 +670,7 @@ func (s *ServerEndpoint) DetachReturn(returnPath tun.Return) error {
}
func (s *ServerEndpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
return judgeOpenVPNFlow(s.router, s.Tag(), s.Type(), s.localAddresses, network, source, destination, firstPacket)
return s.judgeFlow(s.ctx, s, s.localAddresses, network, source, destination, firstPacket)
}
func (s *ServerEndpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter) {
+3
View File
@@ -46,8 +46,11 @@ func (t *Endpoint) UpstreamPort() any {
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 {
if t.systemInterface {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
return adapter.JudgeHostFlow(t.ctx, t.network, t.logger, network, source, destination)
}
if t.started.Load() {
tsFilter := t.wgEngine.GetFilter()
if tsFilter != nil {
+3
View File
@@ -527,6 +527,9 @@ func (t *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error {
if err != nil {
return E.Cause(err, "starting TUN interface")
}
if routedPort, isRoutedPort := t.tunStack.(tun.RoutedPort); isRoutedPort {
t.networkManager.RegisterHostPort(routedPort)
}
if t.autoRedirect != nil {
scope.Add(t.autoRedirect.Close)
monitor.Start("initialize auto-redirect")
+7
View File
@@ -46,6 +46,8 @@ type Endpoint struct {
dnsRouter adapter.DNSRouter
logger logger.ContextLogger
localAddresses []netip.Prefix
system bool
network adapter.NetworkManager
endpoint *wireguard.Endpoint
onDemand bool
bindAccess sync.Mutex
@@ -60,6 +62,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
logger: logger,
localAddresses: options.Address,
system: options.System,
onDemand: options.OnDemand,
}
if options.Detour != "" && options.ListenPort != 0 {
@@ -83,6 +86,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
udpTimeout = C.UDPTimeout
}
networkManager := service.FromContext[adapter.NetworkManager](ctx)
ep.network = networkManager
wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{
Context: ctx,
Logger: logger,
@@ -222,8 +226,11 @@ func (w *Endpoint) DetachReturn(returnPath tun.Return) error {
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()) {
if w.system {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
return adapter.JudgeHostFlow(w.ctx, w.network, w.logger, network, source, destination)
}
}
return adapter.JudgeFlow(w.router, adapter.InboundContext{Inbound: w.Tag(), InboundType: w.Type()}, network, source, destination, firstPacket)
}
-58
View File
@@ -1,76 +1,18 @@
package route
import (
"context"
"sync"
"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"
"github.com/sagernet/sing/common/byteformats"
N "github.com/sagernet/sing/common/network"
)
var (
_ tun.FlowTracker = (*flowLogger)(nil)
_ tun.FlowTracker = (*flowInterrupter)(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 struct {
access sync.Mutex
trackers []tun.FlowTracker
+12
View File
@@ -42,6 +42,7 @@ type NetworkManager struct {
autoDetectInterface bool
defaultOptions adapter.NetworkOptions
autoRedirectOutputMark uint32
hostPort common.TypedValue[tun.RoutedPort]
bridgeInterfaceAccess sync.Mutex
bridgeInterfaces []string
networkMonitor tun.NetworkUpdateMonitor
@@ -394,6 +395,17 @@ func (r *NetworkManager) AutoRedirectOutputMark() uint32 {
return r.autoRedirectOutputMark
}
func (r *NetworkManager) RegisterHostPort(port tun.RoutedPort) {
if r.hostPort.Load() != nil {
return
}
r.hostPort.Store(port)
}
func (r *NetworkManager) HostPort() tun.RoutedPort {
return r.hostPort.Load()
}
func (r *NetworkManager) RegisterBridgeInterface(interfaceName string) {
r.bridgeInterfaceAccess.Lock()
defer r.bridgeInterfaceAccess.Unlock()
+1 -1
View File
@@ -540,7 +540,7 @@ func (r *Router) preMatchFlow(ctx context.Context, metadata *adapter.InboundCont
result.NewTracker = func() tun.FlowTracker {
r.logger.InfoContext(ctx, "pre-match: forward ", metadataCopy.Network, " connection from ", metadataCopy.Source, " to ", metadataCopy.Destination, " via outbound/", outbound.Type(), "[", outbound.Tag(), "]")
flowTrackers := make([]tun.FlowTracker, 0, len(r.trackers)+2)
flowTrackers = append(flowTrackers, newFlowLogger(ctx, r.logger, metadataCopy, outbound))
flowTrackers = append(flowTrackers, adapter.NewFlowLogger(ctx, r.logger))
flowInterrupter := newFlowInterrupter(chain)
if flowInterrupter != nil {
flowTrackers = append(flowTrackers, flowInterrupter)