Add L3 forwarding support

This commit is contained in:
世界
2026-07-06 14:26:32 +08:00
parent e9b86275fa
commit eaec0cf88b
24 changed files with 763 additions and 646 deletions
+39 -27
View File
@@ -5,6 +5,7 @@ package cloudflare
import (
"context"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -13,7 +14,6 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-cloudflared"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/bufio"
@@ -137,32 +137,44 @@ type icmpRouterHandler struct {
tag string
}
func (h *icmpRouterHandler) RouteICMPConnection(ctx context.Context, session tun.DirectRouteSession, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
var ipVersion uint8
if session.Destination.Is4() {
ipVersion = 4
} else {
ipVersion = 6
}
destination := M.SocksaddrFrom(session.Destination, 0)
routeDestination, err := h.router.PreMatch(adapter.InboundContext{
Inbound: h.tag,
InboundType: C.TypeCloudflared,
IPVersion: ipVersion,
Network: N.NetworkICMP,
Source: M.SocksaddrFrom(session.Source, 0),
Destination: destination,
OriginDestination: destination,
}, routeContext, timeout, false)
if err != nil {
switch {
case rule.IsBypassed(err):
err = nil
case rule.IsRejected(err):
h.logger.Trace("reject ICMP connection from ", session.Source, " to ", session.Destination)
default:
h.logger.Warn(E.Cause(err, "link ICMP connection from ", session.Source, " to ", session.Destination))
func (h *icmpRouterHandler) RouteICMPFlow(source netip.Addr, destination netip.Addr) (tun.Port, error) {
result := h.router.PreMatch(adapter.InboundContext{
Inbound: h.tag,
InboundType: C.TypeCloudflared,
Network: N.NetworkICMP,
Source: M.SocksaddrFrom(source, 0),
Destination: M.SocksaddrFrom(destination, 0),
})
switch result.Action {
case adapter.PreMatchFlow:
flowOutbound, isFlowOutbound := result.Outbound.(adapter.FlowOutbound)
if !isFlowOutbound {
return nil, E.New("outbound is not a flow outbound")
}
if result.Destination.IsValid() && result.Destination.Addr() != destination {
h.logger.Trace("drop ICMP flow from ", source, " to ", destination, ": destination override is not supported from cloudflared")
return nil, E.New("destination override is not supported")
}
inet4Address, inet6Address := flowOutbound.PortAddresses()
var portAddress netip.Addr
if destination.Is4() {
portAddress = inet4Address
} else {
portAddress = inet6Address
}
if !portAddress.IsValid() || !portAddress.IsUnspecified() {
h.logger.Trace("drop ICMP flow from ", source, " to ", destination, ": forwarding ICMP to outbound/", result.Outbound.Type(), "[", result.Outbound.Tag(), "] is not supported from cloudflared")
return nil, E.New("unsupported flow outbound")
}
h.logger.Debug("link ICMP flow from ", source, " to ", destination, " via outbound/", result.Outbound.Type(), "[", result.Outbound.Tag(), "]")
return flowOutbound, nil
case adapter.PreMatchReject:
h.logger.Trace("reject ICMP flow from ", source, " to ", destination)
return nil, E.New("rejected")
case adapter.PreMatchDrop:
return nil, E.New("dropped")
default:
h.logger.Trace("drop ICMP flow from ", source, " to ", destination, ": no direct route")
return nil, E.New("no direct route")
}
return routeDestination, err
}
+36 -8
View File
@@ -16,6 +16,7 @@ import (
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
@@ -31,7 +32,7 @@ var (
_ N.ParallelDialer = (*Outbound)(nil)
_ dialer.ParallelNetworkDialer = (*Outbound)(nil)
_ dialer.DirectDialer = (*Outbound)(nil)
_ adapter.DirectRouteOutbound = (*Outbound)(nil)
_ adapter.FlowOutbound = (*Outbound)(nil)
)
type Outbound struct {
@@ -44,6 +45,7 @@ type Outbound struct {
fallbackDelay time.Duration
isEmpty bool
myAddresses common.TypedValue[[]netip.Prefix]
icmpPort *ping.Port
}
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) {
@@ -75,6 +77,11 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if options.ProxyProtocol != 0 {
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
}
if defaultDialer, isDefaultDialer := common.Cast[*dialer.DefaultDialer](outbound.dialer); isDefaultDialer {
outbound.icmpPort = ping.NewPort(ctx, logger, func(destination netip.Addr) control.Func {
return defaultDialer.DialerForICMPDestination(destination).Control
}, 0)
}
return outbound, nil
}
@@ -148,14 +155,35 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return conn, nil
}
func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
ctx := log.ContextWithNewID(h.ctx)
destination, err := ping.ConnectDestination(ctx, h.logger, common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control, metadata.Destination.Addr, routeContext, timeout)
if err != nil {
return nil, err
func (h *Outbound) SupportsFlow(network string) bool {
return network == N.NetworkICMP && h.icmpPort != nil
}
func (h *Outbound) PortAddresses() (netip.Addr, netip.Addr) {
return h.icmpPort.PortAddresses()
}
func (h *Outbound) PortMTU() uint32 {
return h.icmpPort.PortMTU()
}
func (h *Outbound) AttachReturn(returnPath tun.Return) error {
return h.icmpPort.AttachReturn(returnPath)
}
func (h *Outbound) DetachReturn(returnPath tun.Return) error {
return h.icmpPort.DetachReturn(returnPath)
}
func (h *Outbound) WritePackets(packets [][]byte) error {
return h.icmpPort.WritePackets(packets)
}
func (h *Outbound) Close() error {
if h.icmpPort != nil {
return h.icmpPort.Close()
}
h.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return destination, nil
return nil
}
func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) {
-10
View File
@@ -3,7 +3,6 @@ package group
import (
"context"
"net"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
@@ -12,7 +11,6 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
@@ -182,14 +180,6 @@ func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, m
}
}
func (s *Selector) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
selected := s.selected.Load()
if !common.Contains(selected.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag())
}
return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
}
func RealTag(detour adapter.Outbound) string {
if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
return group.Now()
-16
View File
@@ -14,7 +14,6 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/batch"
E "github.com/sagernet/sing/common/exceptions"
@@ -169,21 +168,6 @@ func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, me
s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)
}
func (s *URLTest) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
s.group.Touch()
selected := s.group.selectedOutboundTCP
if selected == nil {
selected, _ = s.group.Select(N.NetworkTCP)
}
if selected == nil {
return nil, E.New("missing supported outbound")
}
if !common.Contains(selected.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag())
}
return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
}
type URLTestGroup struct {
ctx context.Context
outbound adapter.OutboundManager
+14 -104
View File
@@ -15,6 +15,7 @@ import (
"reflect"
"runtime"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
@@ -34,7 +35,6 @@ import (
"github.com/sagernet/sing-box/protocol/tailscale/tailssh"
R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/control"
@@ -56,7 +56,6 @@ import (
tsTUN "github.com/sagernet/tailscale/net/tstun"
"github.com/sagernet/tailscale/tailcfg"
"github.com/sagernet/tailscale/tsnet"
"github.com/sagernet/tailscale/types/ipproto"
"github.com/sagernet/tailscale/types/nettype"
"github.com/sagernet/tailscale/version"
"github.com/sagernet/tailscale/wgengine"
@@ -70,8 +69,8 @@ import (
var (
_ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
_ adapter.DirectRouteOutbound = (*Endpoint)(nil)
_ dialer.PacketDialerWithDestination = (*Endpoint)(nil)
_ tun.Port = (*Endpoint)(nil)
)
func init() {
@@ -95,6 +94,9 @@ type Endpoint struct {
stack *stack.Stack
icmpForwarder *tun.ICMPForwarder
filter *atomic.Pointer[filter.Filter]
returnAccess sync.Mutex
returnPath tun.Return
wgEngine wgengine.ExportedUserspaceEngine
onReconfigHook wgengine.ReconfigListener
sshReconfigHook wgengine.ReconfigListener
@@ -287,6 +289,7 @@ func (t *Endpoint) start() error {
if mtu == 0 {
mtu = uint32(tsTUN.DefaultTUNMTU())
}
t.systemInterfaceMTU = mtu
tunName := t.systemInterfaceName
if tunName == "" {
tunName = tun.CalculateInterfaceName("tailscale")
@@ -361,7 +364,9 @@ func (t *Endpoint) postStart() error {
}, true
})
}
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
wgEngine := t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine)
wgEngine.SetOnReconfigListener(t.onReconfig)
t.wgEngine = wgEngine
ipStack := t.server.ExportNetstack().ExportIPStack()
gErr := ipStack.SetSpoofing(tun.DefaultNIC, true)
@@ -372,7 +377,7 @@ func (t *Endpoint) postStart() error {
if gErr != nil {
return gonet.TranslateNetstackError(gErr)
}
icmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t.logger, t, t.icmpTimeout)
icmpForwarder := tun.NewICMPForwarder(ipStack, t, t.logger)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
t.stack = ipStack
@@ -622,6 +627,10 @@ func (t *Endpoint) Logout(ctx context.Context) error {
func (t *Endpoint) Close() error {
var err error
t.started.Store(false)
if t.icmpForwarder != nil {
t.icmpForwarder.Close()
t.icmpForwarder = nil
}
common.Close(common.PtrOrNil(t.sshServerInstance))
t.sshServerInstance = nil
if t.serverStarted {
@@ -776,62 +785,6 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return packetConn, nil
}
func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
if !t.started.Load() {
return nil, E.New("Tailscale is not ready yet")
}
tsFilter := t.filter.Load()
if tsFilter != nil {
var ipProto ipproto.Proto
switch N.NetworkName(network) {
case N.NetworkTCP:
ipProto = ipproto.TCP
case N.NetworkUDP:
ipProto = ipproto.UDP
case N.NetworkICMP:
if !destination.IsIPv6() {
ipProto = ipproto.ICMPv4
} else {
ipProto = ipproto.ICMPv6
}
}
response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto)
switch response {
case filter.Drop:
return nil, syscall.ECONNREFUSED
case filter.DropSilently:
return nil, tun.ErrDrop
}
}
var ipVersion uint8
if !destination.IsIPv6() {
ipVersion = 4
} else {
ipVersion = 6
}
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
Inbound: t.Tag(),
InboundType: t.Type(),
IPVersion: ipVersion,
Network: network,
Source: source,
Destination: destination,
}, routeContext, timeout, false)
if err != nil {
switch {
case R.IsBypassed(err):
err = nil
case R.IsRejected(err):
t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
default:
if network == N.NetworkICMP {
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
}
return routeDestination, err
}
func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
var metadata adapter.InboundContext
metadata.Inbound = t.Tag()
@@ -872,40 +825,6 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}
func (t *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
if !t.started.Load() {
return nil, E.New("Tailscale is not ready yet")
}
ctx := log.ContextWithNewID(t.ctx)
var destination tun.DirectRouteDestination
var err error
if t.systemDialer != nil {
destination, err = ping.ConnectDestination(
ctx, t.logger,
t.systemDialer.DialerForICMPDestination(metadata.Destination.Addr).Control,
metadata.Destination.Addr, routeContext, timeout,
)
} else {
inet4Address, inet6Address := t.server.TailscaleIPs()
if metadata.Destination.Addr.Is4() && !inet4Address.IsValid() || metadata.Destination.Addr.Is6() && !inet6Address.IsValid() {
return nil, E.New("Tailscale is not ready yet")
}
destination, err = ping.ConnectGVisor(
ctx, t.logger,
metadata.Source.Addr, metadata.Destination.Addr,
routeContext,
t.stack,
inet4Address, inet6Address,
timeout,
)
}
if err != nil {
return nil, err
}
t.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return destination, nil
}
func (t *Endpoint) PreferredDomain(domain string) bool {
routeDomains := t.routeDomains.Load()
if routeDomains == nil {
@@ -933,15 +852,6 @@ func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCf
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
return
}
var inet4Address, inet6Address netip.Addr
for _, address := range cfg.Addresses {
if address.Addr().Is4() {
inet4Address = address.Addr()
} else if address.Addr().Is6() {
inet6Address = address.Addr()
}
}
t.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
t.cfg = cfg
t.dnsCfg = dnsCfg
+133
View File
@@ -0,0 +1,133 @@
//go:build with_gvisor
package tailscale
import (
"net/netip"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/gtcpip/header"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
tsTUN "github.com/sagernet/tailscale/net/tstun"
"github.com/sagernet/tailscale/types/ipproto"
"github.com/sagernet/tailscale/wgengine/filter"
)
func (t *Endpoint) SupportsFlow(network string) bool {
switch network {
case N.NetworkTCP, N.NetworkUDP, N.NetworkICMP:
return true
default:
return false
}
}
func (t *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
if !t.started.Load() {
return netip.Addr{}, netip.Addr{}
}
return t.server.TailscaleIPs()
}
func (t *Endpoint) PortMTU() uint32 {
if t.systemInterface {
return t.systemInterfaceMTU
}
return uint32(tsTUN.DefaultTUNMTU())
}
func (t *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
inet4Address, inet6Address := t.PortAddresses()
if destination.Addr() == inet4Address || destination.Addr() == inet6Address {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
if t.filter != nil {
tsFilter := t.filter.Load()
if tsFilter != nil {
var (
ipProto ipproto.Proto
destinationPort uint16
)
switch network {
case uint8(header.TCPProtocolNumber):
ipProto = ipproto.TCP
destinationPort = destination.Port()
case uint8(header.UDPProtocolNumber):
ipProto = ipproto.UDP
destinationPort = destination.Port()
case uint8(header.ICMPv4ProtocolNumber):
ipProto = ipproto.ICMPv4
case uint8(header.ICMPv6ProtocolNumber):
ipProto = ipproto.ICMPv6
}
switch tsFilter.Check(source.Addr(), destination.Addr(), destinationPort, ipProto) {
case filter.Drop:
return tun.FlowVerdict{Action: tun.ActionReject}
case filter.DropSilently:
return tun.FlowVerdict{Action: tun.ActionDrop}
}
}
}
return adapter.JudgeFlow(t.router, t.Tag(), t.Type(), network, source, destination)
}
func (t *Endpoint) AttachReturn(returnPath tun.Return) error {
t.returnAccess.Lock()
defer t.returnAccess.Unlock()
if t.returnPath == returnPath {
return nil
}
if t.returnPath != nil {
return E.New("return path already attached")
}
err := t.wgEngine.SetReturnPath(returnPath)
if err != nil {
return err
}
t.returnPath = returnPath
return nil
}
func (t *Endpoint) DetachReturn(returnPath tun.Return) error {
t.returnAccess.Lock()
defer t.returnAccess.Unlock()
if t.returnPath == returnPath {
t.returnPath = nil
}
return nil
}
func (t *Endpoint) WritePackets(packets [][]byte) error {
if !t.started.Load() {
return E.New("Tailscale is not ready yet")
}
unmatched, err := t.wgEngine.InputPackets(packets)
if err != nil || len(unmatched) == 0 {
return err
}
t.returnAccess.Lock()
returnPath := t.returnPath
t.returnAccess.Unlock()
if returnPath == nil {
return nil
}
headroom := returnPath.ReturnHeadroom()
inet4Address, inet6Address := t.PortAddresses()
var replies [][]byte
for _, packet := range unmatched {
source := inet4Address
if header.IPVersion(packet) == header.IPv6Version {
source = inet6Address
}
reply, replyOk := tun.BuildUnreachable(packet, source, headroom)
if replyOk {
replies = append(replies, reply)
}
}
if len(replies) > 0 {
returnPath.ReturnPackets(replies)
}
return nil
}
+25 -59
View File
@@ -16,7 +16,6 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
@@ -99,7 +98,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
tunMTU := options.MTU
enableGSO := C.IsLinux && options.Stack == "gvisor" && platformInterface == nil && tunMTU > 0 && tunMTU < 49152
if tunMTU == 0 {
if platformInterface != nil && platformInterface.UnderNetworkExtension() {
// In Network Extension, when MTU exceeds 4064 (4096-UTUN_IF_HEADROOM_SIZE), the performance of tun will drop significantly, which may be a system bug.
@@ -111,6 +109,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
tunMTU = 65535
}
}
var enableGSO bool
if C.IsLinux && platformInterface == nil {
enableGSO = (options.Stack == "gvisor" && tunMTU < 49152)
}
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
@@ -178,7 +180,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
excludeMACAddress = append(excludeMACAddress, mac)
}
networkManager := service.FromContext[adapter.NetworkManager](ctx)
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && tunMTU <= 9000))
inbound := &Inbound{
tag: tag,
ctx: ctx,
@@ -320,6 +322,22 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
t.dnsHijackAddress = append(inet4DNSAddress, inet6DNSAddress...)
}
case adapter.StartStateStart:
if t.platformInterface == nil &&
((C.IsLinux && !t.tunOptions.GSO) || (C.IsDarwin && !t.tunOptions.EXP_MultiPendingPackets)) {
endpointManager := service.FromContext[adapter.EndpointManager](t.ctx)
if endpointManager != nil {
for _, managedEndpoint := range endpointManager.Endpoints() {
if _, isFlowOutbound := managedEndpoint.(adapter.FlowOutbound); isFlowOutbound {
if C.IsLinux {
t.tunOptions.GSO = true
} else {
t.tunOptions.EXP_MultiPendingPackets = true
}
break
}
}
}
}
if C.IsAndroid && t.platformInterface == nil {
t.tunOptions.BuildAndroidRules(t.networkManager.PackageManager())
}
@@ -460,34 +478,8 @@ func (t *Inbound) Close() error {
)
}
func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
var ipVersion uint8
if !destination.IsIPv6() {
ipVersion = 4
} else {
ipVersion = 6
}
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
Inbound: t.tag,
InboundType: C.TypeTun,
IPVersion: ipVersion,
Network: network,
Source: source,
Destination: destination,
}, routeContext, timeout, false)
if err != nil {
switch {
case rule.IsBypassed(err):
err = nil
case rule.IsRejected(err):
t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
default:
if network == N.NetworkICMP {
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
}
return routeDestination, err
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) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
@@ -534,34 +526,8 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
type autoRedirectHandler Inbound
func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
var ipVersion uint8
if !destination.IsIPv6() {
ipVersion = 4
} else {
ipVersion = 6
}
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
Inbound: t.tag,
InboundType: C.TypeTun,
IPVersion: ipVersion,
Network: network,
Source: source,
Destination: destination,
}, routeContext, timeout, true)
if err != nil {
switch {
case rule.IsBypassed(err):
t.logger.Trace("bypass ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
case rule.IsRejected(err):
t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
default:
if network == N.NetworkICMP {
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
}
return routeDestination, err
func (t *autoRedirectHandler) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
return (*Inbound)(t).JudgeFlow(network, source, destination)
}
func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
+36 -36
View File
@@ -13,7 +13,6 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-box/transport/wireguard"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
@@ -137,37 +136,45 @@ func (w *Endpoint) Close() error {
return w.endpoint.Close()
}
func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
if !w.started.Load() {
return nil, E.New("WireGuard is not ready yet")
func (w *Endpoint) SupportsFlow(network string) bool {
switch network {
case N.NetworkTCP, N.NetworkUDP, N.NetworkICMP:
return true
default:
return false
}
var ipVersion uint8
if !destination.IsIPv6() {
ipVersion = 4
} else {
ipVersion = 6
}
routeDestination, err := w.router.PreMatch(adapter.InboundContext{
Inbound: w.Tag(),
InboundType: w.Type(),
IPVersion: ipVersion,
Network: network,
Source: source,
Destination: destination,
}, routeContext, timeout, false)
if err != nil {
switch {
case rule.IsBypassed(err):
err = nil
case rule.IsRejected(err):
w.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
default:
if network == N.NetworkICMP {
w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
func (w *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
return w.endpoint.PortAddresses()
}
func (w *Endpoint) PortMTU() uint32 {
return w.endpoint.PortMTU()
}
func (w *Endpoint) AttachReturn(returnPath tun.Return) error {
return w.endpoint.AttachReturn(returnPath)
}
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 {
for _, localPrefix := range w.localAddresses {
if localPrefix.Contains(destination.Addr()) {
return tun.FlowVerdict{Action: tun.ActionAccept}
}
}
return routeDestination, err
return adapter.JudgeFlow(w.router, w.Tag(), w.Type(), network, source, destination)
}
func (w *Endpoint) WritePackets(packets [][]byte) error {
if !w.started.Load() {
return E.New("WireGuard is not ready yet")
}
return w.endpoint.WritePackets(packets)
}
func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
@@ -279,10 +286,3 @@ func (w *Endpoint) PreferredAddress(address netip.Addr) bool {
}
return w.endpoint.Lookup(address) != nil
}
func (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
if !w.started.Load() {
return nil, E.New("WireGuard is not ready yet")
}
return w.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout)
}