From fbf514205cb44c62d84285f65c0036ba24fd5d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 28 Oct 2026 10:42:34 +0800 Subject: [PATCH] Forward endpoint local flows to tun --- adapter/host.go | 92 ++++++++++++++++++++++++++++++++ adapter/network.go | 2 + adapter/router.go | 12 +---- go.mod | 2 +- go.sum | 4 +- protocol/masque/client.go | 4 +- protocol/masque/endpoint.go | 13 +++-- protocol/masque/server.go | 4 +- protocol/openconnect/client.go | 4 +- protocol/openconnect/endpoint.go | 15 ++++-- protocol/openvpn/client.go | 4 +- protocol/openvpn/endpoint.go | 15 ++++-- protocol/openvpn/server.go | 4 +- protocol/tailscale/port.go | 5 +- protocol/tun/inbound.go | 3 ++ protocol/wireguard/endpoint.go | 9 +++- route/flow_tracker.go | 58 -------------------- route/network.go | 12 +++++ route/route.go | 2 +- 19 files changed, 171 insertions(+), 93 deletions(-) create mode 100644 adapter/host.go diff --git a/adapter/host.go b/adapter/host.go new file mode 100644 index 000000000..3f8f437a7 --- /dev/null +++ b/adapter/host.go @@ -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()))) +} diff --git a/adapter/network.go b/adapter/network.go index b1460b4a3..4af5b954e 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -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 diff --git a/adapter/router.go b/adapter/router.go index ae9c1dc42..b5f5703d7 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -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 diff --git a/go.mod b/go.mod index 84e5fc594..09e29c48f 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 3cb5e4a8b..359111fac 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/protocol/masque/client.go b/protocol/masque/client.go index f87fc3b86..9d8082d0d 100644 --- a/protocol/masque/client.go +++ b/protocol/masque/client.go @@ -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) { diff --git a/protocol/masque/endpoint.go b/protocol/masque/endpoint.go index 27fac8661..839e78316 100644 --- a/protocol/masque/endpoint.go +++ b/protocol/masque/endpoint.go @@ -28,8 +28,10 @@ func RegisterEndpoint(registry *endpoint.Registry) { type endpointBase struct { endpoint.Adapter - router adapter.Router - logger log.ContextLogger + 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,9 +103,12 @@ 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()) { - return tun.FlowVerdict{Action: tun.ActionAccept} + 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) } diff --git a/protocol/masque/server.go b/protocol/masque/server.go index bd37395a7..b761b437f 100644 --- a/protocol/masque/server.go +++ b/protocol/masque/server.go @@ -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) { diff --git a/protocol/openconnect/client.go b/protocol/openconnect/client.go index 85c893035..d8ff5f25b 100644 --- a/protocol/openconnect/client.go +++ b/protocol/openconnect/client.go @@ -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) { diff --git a/protocol/openconnect/endpoint.go b/protocol/openconnect/endpoint.go index 2b726218f..0f7927671 100644 --- a/protocol/openconnect/endpoint.go +++ b/protocol/openconnect/endpoint.go @@ -28,8 +28,10 @@ func RegisterEndpoint(registry *endpoint.Registry) { type endpointBase struct { endpoint.Adapter - router adapter.Router - logger log.ContextLogger + 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() { - return tun.FlowVerdict{Action: tun.ActionAccept} + 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) { diff --git a/protocol/openvpn/client.go b/protocol/openvpn/client.go index b07ad149f..79c87f5ea 100644 --- a/protocol/openvpn/client.go +++ b/protocol/openvpn/client.go @@ -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) { diff --git a/protocol/openvpn/endpoint.go b/protocol/openvpn/endpoint.go index 6d238daf5..227b06303 100644 --- a/protocol/openvpn/endpoint.go +++ b/protocol/openvpn/endpoint.go @@ -34,8 +34,10 @@ func RegisterEndpoint(registry *endpoint.Registry) { type endpointBase struct { endpoint.Adapter - router adapter.Router - logger log.ContextLogger + 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() { - return tun.FlowVerdict{Action: tun.ActionAccept} + 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) { diff --git a/protocol/openvpn/server.go b/protocol/openvpn/server.go index 1026579f5..028219ad4 100644 --- a/protocol/openvpn/server.go +++ b/protocol/openvpn/server.go @@ -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) { diff --git a/protocol/tailscale/port.go b/protocol/tailscale/port.go index fb382504e..0a160384c 100644 --- a/protocol/tailscale/port.go +++ b/protocol/tailscale/port.go @@ -46,7 +46,10 @@ 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 { - return tun.FlowVerdict{Action: tun.ActionAccept} + 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() diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 69f74a494..53ea19cd1 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -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") diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 70db411e0..e2cece656 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -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,7 +226,10 @@ 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()) { - return tun.FlowVerdict{Action: tun.ActionAccept} + 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) diff --git a/route/flow_tracker.go b/route/flow_tracker.go index 7a269870d..05252017b 100644 --- a/route/flow_tracker.go +++ b/route/flow_tracker.go @@ -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 diff --git a/route/network.go b/route/network.go index 99f4c7703..b6e6f048c 100644 --- a/route/network.go +++ b/route/network.go @@ -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() diff --git a/route/route.go b/route/route.go index 341f20ebc..56d2e798f 100644 --- a/route/route.go +++ b/route/route.go @@ -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)