Improve bridge
This commit is contained in:
@@ -94,6 +94,7 @@ type InboundContext struct {
|
|||||||
SourceHostname string
|
SourceHostname string
|
||||||
QueryType uint16
|
QueryType uint16
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
PreMatch bool
|
||||||
|
|
||||||
// rule cache
|
// rule cache
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -22,14 +22,14 @@ type Outbound interface {
|
|||||||
|
|
||||||
type OutboundWithPreferredRoutes interface {
|
type OutboundWithPreferredRoutes interface {
|
||||||
Outbound
|
Outbound
|
||||||
PreferredDomain(domain string) bool
|
PreferredDomain(metadata *InboundContext, domain string) bool
|
||||||
PreferredAddress(address netip.Addr) bool
|
PreferredAddress(metadata *InboundContext, address netip.Addr) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlowOutbound interface {
|
type FlowOutbound interface {
|
||||||
Outbound
|
Outbound
|
||||||
tun.Port
|
tun.Port
|
||||||
SupportsFlow(network string) bool
|
PreMatchFlow(network string, destination netip.Addr) PreMatchAction
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundRegistry interface {
|
type OutboundRegistry interface {
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ icon: material/new-box
|
|||||||
or other L3 endpoints via the `route` action in
|
or other L3 endpoints via the `route` action in
|
||||||
[Pre-match](/configuration/shared/pre-match/); L4 connections will be rejected.
|
[Pre-match](/configuration/shared/pre-match/); L4 connections will be rejected.
|
||||||
|
|
||||||
|
Traffic to local addresses of the machine (loopback, or addresses assigned to its
|
||||||
|
network interfaces) will be rejected.
|
||||||
|
|
||||||
|
It is recommended to use [`preferred_by`](/configuration/route/rule/#preferred_by)
|
||||||
|
as a gate in the `route` rule: it only matches in
|
||||||
|
[pre-match](/configuration/shared/pre-match/) and excludes local addresses that
|
||||||
|
cannot be routed.
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ icon: material/new-box
|
|||||||
通过[预匹配](/zh/configuration/shared/pre-match/)中的 `route` 动作,将 L3 流量从 TUN
|
通过[预匹配](/zh/configuration/shared/pre-match/)中的 `route` 动作,将 L3 流量从 TUN
|
||||||
或其他 L3 endpoints 路由到它;L4 连接将被拒绝。
|
或其他 L3 endpoints 路由到它;L4 连接将被拒绝。
|
||||||
|
|
||||||
|
到本机本地地址(loopback 或分配给本机网络接口的地址)的流量将被拒绝。
|
||||||
|
|
||||||
|
建议使用 [`preferred_by`](/zh/configuration/route/rule/#preferred_by) 作为 `route`
|
||||||
|
规则的门禁:它仅在[预匹配](/zh/configuration/shared/pre-match/)中匹配,且排除了无法路由的本地地址。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -465,10 +465,11 @@ See [Wi-Fi State](/configuration/shared/wifi-state/) for details.
|
|||||||
|
|
||||||
Match specified outbounds' preferred routes.
|
Match specified outbounds' preferred routes.
|
||||||
|
|
||||||
| Type | Match |
|
| Type | Match |
|
||||||
|-------------|-----------------------------------------------|
|
|-------------|----------------------------------------------------|
|
||||||
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
||||||
| `wireguard` | Match peers's allowed IPs |
|
| `wireguard` | Match peers's allowed IPs |
|
||||||
|
| `bridge` | Match all addresses except local addresses of the machine, only in [pre-match](/configuration/shared/pre-match/) |
|
||||||
|
|
||||||
#### source_mac_address
|
#### source_mac_address
|
||||||
|
|
||||||
|
|||||||
@@ -467,6 +467,7 @@ icon: material/new-box
|
|||||||
|-------------|--------------------------------|
|
|-------------|--------------------------------|
|
||||||
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
||||||
| `wireguard` | 匹配对端的 allowed IPs |
|
| `wireguard` | 匹配对端的 allowed IPs |
|
||||||
|
| `bridge` | 匹配除本机本地地址外的所有地址,仅在[预匹配](/zh/configuration/shared/pre-match/)中 |
|
||||||
|
|
||||||
#### source_mac_address
|
#### source_mac_address
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,8 @@
|
|||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/protocol/bridge"
|
"github.com/sagernet/sing-box/protocol/bridge"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
@@ -35,8 +31,6 @@ func NewBridgeService(options *BridgeOptions) (BridgeSession, error) {
|
|||||||
}
|
}
|
||||||
serviceOptions.Inet6Port = inet6Port
|
serviceOptions.Inet6Port = inet6Port
|
||||||
}
|
}
|
||||||
logFactory := log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now()}, os.Stderr, "", nil, false)
|
|
||||||
serviceOptions.Logger = logFactory.Logger()
|
|
||||||
service, err := bridge.NewService(serviceOptions)
|
service, err := bridge.NewService(serviceOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -3,12 +3,8 @@
|
|||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/protocol/bridge"
|
"github.com/sagernet/sing-box/protocol/bridge"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
@@ -37,8 +33,6 @@ func NewBridgeService(options *BridgeOptions) (BridgeSession, error) {
|
|||||||
}
|
}
|
||||||
serviceOptions.Inet6Port = inet6Port
|
serviceOptions.Inet6Port = inet6Port
|
||||||
}
|
}
|
||||||
logFactory := log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now()}, os.Stderr, "", nil, false)
|
|
||||||
serviceOptions.Logger = logFactory.Logger()
|
|
||||||
service, err := bridge.NewService(serviceOptions)
|
service, err := bridge.NewService(serviceOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ require (
|
|||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1
|
github.com/sagernet/sing-shadowtls v0.2.1
|
||||||
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814
|
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814
|
||||||
github.com/sagernet/sing-tun v0.8.12-0.20260707160752-9162aa68ac1e
|
github.com/sagernet/sing-tun v0.8.12-0.20260708061915-3b7dd334b582
|
||||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb
|
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
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/smux v1.5.50-sing-box-mod.1
|
||||||
|
|||||||
@@ -274,8 +274,8 @@ github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkG
|
|||||||
github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814 h1:xfnkRpjVRVeJhVvDZA8PzTLlKGTb1o2kdI4uv1YymXo=
|
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814 h1:xfnkRpjVRVeJhVvDZA8PzTLlKGTb1o2kdI4uv1YymXo=
|
||||||
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY=
|
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY=
|
||||||
github.com/sagernet/sing-tun v0.8.12-0.20260707160752-9162aa68ac1e h1:cMMz0qQtCGT1CxXKo2mheM0hFlPaosYBWBqwZHdokYE=
|
github.com/sagernet/sing-tun v0.8.12-0.20260708061915-3b7dd334b582 h1:3p7Odviib/LlgsCjE50s2EcF5Oa6fuHbZFSqUf7g6Rc=
|
||||||
github.com/sagernet/sing-tun v0.8.12-0.20260707160752-9162aa68ac1e/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
|
github.com/sagernet/sing-tun v0.8.12-0.20260708061915-3b7dd334b582/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
|
||||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0=
|
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0=
|
||||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb/go.mod h1:D4CnJX3MNAAANhbQUxfIRgBdnvlTEaV7h6ojedcs+pw=
|
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb/go.mod h1:D4CnJX3MNAAANhbQUxfIRgBdnvlTEaV7h6ojedcs+pw=
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ package bridge
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -31,8 +33,10 @@ type backendDarwin struct {
|
|||||||
inet4Local netip.Addr
|
inet4Local netip.Addr
|
||||||
inet6Local netip.Addr
|
inet6Local netip.Addr
|
||||||
|
|
||||||
|
batchTUN tun.DarwinTUN
|
||||||
|
|
||||||
writeAccess sync.Mutex
|
writeAccess sync.Mutex
|
||||||
writeBuffer []byte
|
writeBatch []*buf.Buffer
|
||||||
|
|
||||||
pfDevice *pfDevice
|
pfDevice *pfDevice
|
||||||
pfToken uint64
|
pfToken uint64
|
||||||
@@ -44,7 +48,7 @@ type backendDarwin struct {
|
|||||||
|
|
||||||
func newBackend(ctx context.Context, logger logger.ContextLogger, networkManager adapter.NetworkManager, tag string, options option.BridgeOutboundOptions) (Backend, error) {
|
func newBackend(ctx context.Context, logger logger.ContextLogger, networkManager adapter.NetworkManager, tag string, options option.BridgeOutboundOptions) (Backend, error) {
|
||||||
instance := &backendDarwin{
|
instance := &backendDarwin{
|
||||||
writeBuffer: make([]byte, tun.PacketOffset+maxPacketLength),
|
writeBatch: make([]*buf.Buffer, 0, bridgeWriteBatchSize),
|
||||||
}
|
}
|
||||||
err := instance.init(ctx, logger, networkManager, tag, options)
|
err := instance.init(ctx, logger, networkManager, tag, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -78,11 +82,13 @@ func (b *backendDarwin) start() error {
|
|||||||
b.tunName = tun.CalculateInterfaceName(b.bridgeName)
|
b.tunName = tun.CalculateInterfaceName(b.bridgeName)
|
||||||
b.anchorName = "com.apple/sing-box-" + b.tunName
|
b.anchorName = "com.apple/sing-box-" + b.tunName
|
||||||
tunInterface, err := tun.New(tun.Options{
|
tunInterface, err := tun.New(tun.Options{
|
||||||
Name: b.tunName,
|
Name: b.tunName,
|
||||||
MTU: bridgeTunMTU,
|
MTU: bridgeTunMTU,
|
||||||
AutoRoute: false,
|
AutoRoute: false,
|
||||||
InterfaceMonitor: b.networkManager.InterfaceMonitor(),
|
InterfaceMonitor: b.networkManager.InterfaceMonitor(),
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
|
EXP_ExternalConfiguration: true,
|
||||||
|
EXP_MultiPendingPackets: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "create bridge tun")
|
return E.Cause(err, "create bridge tun")
|
||||||
@@ -106,11 +112,12 @@ func (b *backendDarwin) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "enable pf")
|
return E.Cause(err, "enable pf")
|
||||||
}
|
}
|
||||||
|
b.batchTUN = tunInterface.(tun.DarwinTUN)
|
||||||
b.closed = make(chan struct{})
|
b.closed = make(chan struct{})
|
||||||
b.readDone = make(chan struct{})
|
b.readDone = make(chan struct{})
|
||||||
b.registerMonitors(b.syncEgress)
|
b.registerMonitors(b.syncEgress)
|
||||||
b.syncEgress()
|
b.syncEgress()
|
||||||
go b.readLoop()
|
go b.batchReadLoop()
|
||||||
b.logger.Info("bridge started at ", b.tunName, " (masquerade, egress ", b.egressLabel(), ")")
|
b.logger.Info("bridge started at ", b.tunName, " (masquerade, egress ", b.egressLabel(), ")")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -137,6 +144,7 @@ func (b *backendDarwin) startPlatform() error {
|
|||||||
FileDescriptor: session.FileDescriptor(),
|
FileDescriptor: session.FileDescriptor(),
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
EXP_ExternalConfiguration: true,
|
EXP_ExternalConfiguration: true,
|
||||||
|
EXP_MultiPendingPackets: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "create bridge tun")
|
return E.Cause(err, "create bridge tun")
|
||||||
@@ -146,40 +154,16 @@ func (b *backendDarwin) startPlatform() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start bridge tun")
|
return E.Cause(err, "start bridge tun")
|
||||||
}
|
}
|
||||||
|
b.batchTUN = tunInterface.(tun.DarwinTUN)
|
||||||
b.closed = make(chan struct{})
|
b.closed = make(chan struct{})
|
||||||
b.readDone = make(chan struct{})
|
b.readDone = make(chan struct{})
|
||||||
b.registerMonitors(b.syncSessionEgress)
|
b.registerMonitors(b.syncSessionEgress)
|
||||||
b.syncSessionEgress()
|
b.syncSessionEgress()
|
||||||
go b.readLoop()
|
go b.batchReadLoop()
|
||||||
b.logger.Info("bridge started at ", b.tunName, " (platform, egress ", b.egressLabel(), ")")
|
b.logger.Info("bridge started at ", b.tunName, " (platform, egress ", b.egressLabel(), ")")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *backendDarwin) registerMonitors(syncFunc func()) {
|
|
||||||
var unregisterFuncs []func()
|
|
||||||
networkMonitor := b.networkManager.NetworkMonitor()
|
|
||||||
if networkMonitor != nil {
|
|
||||||
networkElement := networkMonitor.RegisterCallback(syncFunc)
|
|
||||||
unregisterFuncs = append(unregisterFuncs, func() { networkMonitor.UnregisterCallback(networkElement) })
|
|
||||||
} else if b.boundInterface != "" {
|
|
||||||
b.logger.Debug("network monitor unavailable, pinned egress will not track interface changes")
|
|
||||||
}
|
|
||||||
if b.boundInterface == "" {
|
|
||||||
interfaceMonitor := b.networkManager.InterfaceMonitor()
|
|
||||||
if interfaceMonitor != nil {
|
|
||||||
interfaceElement := interfaceMonitor.RegisterCallback(func(_ *control.Interface, _ int) { syncFunc() })
|
|
||||||
unregisterFuncs = append(unregisterFuncs, func() { interfaceMonitor.UnregisterCallback(interfaceElement) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(unregisterFuncs) > 0 {
|
|
||||||
b.unregister = func() {
|
|
||||||
for _, unregisterFunc := range unregisterFuncs {
|
|
||||||
unregisterFunc()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *backendDarwin) egressLabel() string {
|
func (b *backendDarwin) egressLabel() string {
|
||||||
if b.boundInterface != "" {
|
if b.boundInterface != "" {
|
||||||
return b.boundInterface
|
return b.boundInterface
|
||||||
@@ -231,18 +215,27 @@ func (b *backendDarwin) PortMTU() uint32 {
|
|||||||
func (b *backendDarwin) WritePackets(packets [][]byte) error {
|
func (b *backendDarwin) WritePackets(packets [][]byte) error {
|
||||||
b.writeAccess.Lock()
|
b.writeAccess.Lock()
|
||||||
defer b.writeAccess.Unlock()
|
defer b.writeAccess.Unlock()
|
||||||
for _, packet := range packets {
|
for len(packets) > 0 {
|
||||||
if len(packet) == 0 || len(packet) > maxPacketLength {
|
chunk := packets
|
||||||
|
if len(chunk) > bridgeWriteBatchSize {
|
||||||
|
chunk = chunk[:bridgeWriteBatchSize]
|
||||||
|
}
|
||||||
|
packets = packets[len(chunk):]
|
||||||
|
batch := b.writeBatch[:0]
|
||||||
|
for _, packet := range chunk {
|
||||||
|
if len(packet) == 0 || len(packet) > maxPacketLength {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ipVersion := header.IPVersion(packet)
|
||||||
|
if ipVersion != header.IPv4Version && ipVersion != header.IPv6Version {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
batch = append(batch, buf.As(packet))
|
||||||
|
}
|
||||||
|
if len(batch) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ipVersion := header.IPVersion(packet)
|
err := b.batchTUN.BatchWrite(batch)
|
||||||
if ipVersion != header.IPv4Version && ipVersion != header.IPv6Version {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buffer := b.writeBuffer[:tun.PacketOffset+len(packet)]
|
|
||||||
tun.PacketFillHeader(buffer, ipVersion)
|
|
||||||
copy(buffer[tun.PacketOffset:], packet)
|
|
||||||
_, err := b.tunInterface.Write(buffer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -250,6 +243,81 @@ func (b *backendDarwin) WritePackets(packets [][]byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *backendDarwin) batchReadLoop() {
|
||||||
|
defer close(b.readDone)
|
||||||
|
headroom := -1
|
||||||
|
var buffers [][]byte
|
||||||
|
var batch [][]byte
|
||||||
|
for {
|
||||||
|
packets, err := b.batchTUN.BatchRead()
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-b.closed:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if E.IsClosed(err) || errors.Is(err, syscall.EBADF) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.logger.Debug(E.Cause(err, "bridge tun read"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(packets) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.returnAccess.Lock()
|
||||||
|
returnPaths := b.returnPaths
|
||||||
|
b.returnAccess.Unlock()
|
||||||
|
if len(returnPaths) == 0 {
|
||||||
|
buf.ReleaseMulti(packets)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pathHeadroom := returnPaths[0].ReturnHeadroom()
|
||||||
|
if pathHeadroom != headroom {
|
||||||
|
headroom = pathHeadroom
|
||||||
|
buffers = buffers[:0]
|
||||||
|
}
|
||||||
|
for len(buffers) < len(packets) {
|
||||||
|
buffers = append(buffers, make([]byte, headroom+bridgeTunMTU))
|
||||||
|
}
|
||||||
|
batch = batch[:0]
|
||||||
|
for _, packet := range packets {
|
||||||
|
payload := packet.Bytes()
|
||||||
|
if len(payload) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fixReturnChecksum(payload)
|
||||||
|
buffer := buffers[len(batch)][:headroom+len(payload)]
|
||||||
|
copy(buffer[headroom:], payload)
|
||||||
|
batch = append(batch, buffer)
|
||||||
|
}
|
||||||
|
buf.ReleaseMulti(packets)
|
||||||
|
if len(batch) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unconsumed := batch
|
||||||
|
currentHeadroom := headroom
|
||||||
|
for _, returnPath := range returnPaths {
|
||||||
|
if len(unconsumed) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nextHeadroom := returnPath.ReturnHeadroom()
|
||||||
|
if nextHeadroom != currentHeadroom {
|
||||||
|
rebuffered := make([][]byte, 0, len(unconsumed))
|
||||||
|
for _, packet := range unconsumed {
|
||||||
|
payload := packet[currentHeadroom:]
|
||||||
|
buffer := make([]byte, nextHeadroom+len(payload))
|
||||||
|
copy(buffer[nextHeadroom:], payload)
|
||||||
|
rebuffered = append(rebuffered, buffer)
|
||||||
|
}
|
||||||
|
unconsumed = rebuffered
|
||||||
|
currentHeadroom = nextHeadroom
|
||||||
|
}
|
||||||
|
unconsumed = returnPath.ReturnPackets(unconsumed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *backendDarwin) syncEgress() {
|
func (b *backendDarwin) syncEgress() {
|
||||||
b.egressAccess.Lock()
|
b.egressAccess.Lock()
|
||||||
defer b.egressAccess.Unlock()
|
defer b.egressAccess.Unlock()
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
const (
|
const (
|
||||||
defaultBridgeRuleIndex = 100
|
defaultBridgeRuleIndex = 100
|
||||||
defaultBridgeTableIndexBase = 2200
|
defaultBridgeTableIndexBase = 2200
|
||||||
bridgeWriteBatchSize = 32
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type backendLinux struct {
|
type backendLinux struct {
|
||||||
@@ -83,12 +82,12 @@ func (b *backendLinux) start() error {
|
|||||||
b.tunName = tun.CalculateInterfaceName(b.bridgeName)
|
b.tunName = tun.CalculateInterfaceName(b.bridgeName)
|
||||||
b.nftTableName = "sing-box-" + b.tunName
|
b.nftTableName = "sing-box-" + b.tunName
|
||||||
tunInterface, err := tun.New(tun.Options{
|
tunInterface, err := tun.New(tun.Options{
|
||||||
Name: b.tunName,
|
Name: b.tunName,
|
||||||
MTU: bridgeTunMTU,
|
MTU: bridgeTunMTU,
|
||||||
GSO: true,
|
GSO: true,
|
||||||
AutoRoute: false,
|
InterfaceMonitor: b.networkManager.InterfaceMonitor(),
|
||||||
InterfaceMonitor: b.networkManager.InterfaceMonitor(),
|
Logger: b.logger,
|
||||||
Logger: b.logger,
|
EXP_ExternalConfiguration: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "create bridge tun")
|
return E.Cause(err, "create bridge tun")
|
||||||
@@ -184,6 +183,7 @@ func (b *backendLinux) startPlatform() error {
|
|||||||
tunInterface, err := tun.New(tun.Options{
|
tunInterface, err := tun.New(tun.Options{
|
||||||
Name: b.tunName,
|
Name: b.tunName,
|
||||||
MTU: bridgeTunMTU,
|
MTU: bridgeTunMTU,
|
||||||
|
GSO: true,
|
||||||
FileDescriptor: session.FileDescriptor(),
|
FileDescriptor: session.FileDescriptor(),
|
||||||
Logger: b.logger,
|
Logger: b.logger,
|
||||||
})
|
})
|
||||||
@@ -195,9 +195,22 @@ func (b *backendLinux) startPlatform() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start bridge tun")
|
return E.Cause(err, "start bridge tun")
|
||||||
}
|
}
|
||||||
|
linuxTUN := tunInterface.(tun.LinuxTUN)
|
||||||
|
if linuxTUN.BatchSize() > 1 {
|
||||||
|
b.batchTUN = linuxTUN
|
||||||
|
b.writeHeadroom = linuxTUN.FrontHeadroom()
|
||||||
|
b.writeBuffers = make([][]byte, bridgeWriteBatchSize)
|
||||||
|
for i := range b.writeBuffers {
|
||||||
|
b.writeBuffers[i] = make([]byte, b.writeHeadroom+maxPacketLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
b.closed = make(chan struct{})
|
b.closed = make(chan struct{})
|
||||||
b.readDone = make(chan struct{})
|
b.readDone = make(chan struct{})
|
||||||
go b.readLoop()
|
if b.batchTUN != nil {
|
||||||
|
go b.batchReadLoop()
|
||||||
|
} else {
|
||||||
|
go b.readLoop()
|
||||||
|
}
|
||||||
monitor := b.networkManager.InterfaceMonitor()
|
monitor := b.networkManager.InterfaceMonitor()
|
||||||
if monitor != nil {
|
if monitor != nil {
|
||||||
element := monitor.RegisterCallback(func(_ *control.Interface, _ int) { b.syncSessionEgress() })
|
element := monitor.RegisterCallback(func(_ *control.Interface, _ int) { b.syncSessionEgress() })
|
||||||
|
|||||||
+51
-10
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
@@ -22,9 +23,10 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.Outbound = (*Outbound)(nil)
|
_ adapter.Outbound = (*Outbound)(nil)
|
||||||
_ adapter.FlowOutbound = (*Outbound)(nil)
|
_ adapter.FlowOutbound = (*Outbound)(nil)
|
||||||
_ adapter.Lifecycle = (*Outbound)(nil)
|
_ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil)
|
||||||
|
_ adapter.Lifecycle = (*Outbound)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
@@ -34,7 +36,10 @@ type Backend interface {
|
|||||||
|
|
||||||
type Outbound struct {
|
type Outbound struct {
|
||||||
outbound.Adapter
|
outbound.Adapter
|
||||||
backend Backend
|
logger log.ContextLogger
|
||||||
|
networkManager adapter.NetworkManager
|
||||||
|
platformInterface adapter.PlatformInterface
|
||||||
|
backend Backend
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.BridgeOutboundOptions) (adapter.Outbound, error) {
|
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.BridgeOutboundOptions) (adapter.Outbound, error) {
|
||||||
@@ -44,8 +49,11 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Outbound{
|
return &Outbound{
|
||||||
Adapter: outbound.NewAdapter(C.TypeBridge, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
|
Adapter: outbound.NewAdapter(C.TypeBridge, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
|
||||||
backend: outboundBackend,
|
logger: logger,
|
||||||
|
networkManager: networkManager,
|
||||||
|
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
||||||
|
backend: outboundBackend,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,8 +65,41 @@ func (o *Outbound) Close() error {
|
|||||||
return o.backend.Close()
|
return o.backend.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Outbound) SupportsFlow(network string) bool {
|
func (o *Outbound) PreferredDomain(metadata *adapter.InboundContext, domain string) bool {
|
||||||
return true
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) PreferredAddress(metadata *adapter.InboundContext, address netip.Addr) bool {
|
||||||
|
return metadata.PreMatch && !o.isLocalDestination(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction {
|
||||||
|
if o.isLocalDestination(destination) {
|
||||||
|
o.logger.Warn("rejected connection to local destination ", destination, ": traffic to local addresses is not supported by bridge, exclude them in route rules")
|
||||||
|
return adapter.PreMatchReject
|
||||||
|
}
|
||||||
|
return adapter.PreMatchFlow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) isLocalDestination(destination netip.Addr) bool {
|
||||||
|
if !destination.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
destination = destination.Unmap()
|
||||||
|
if destination.IsLoopback() || destination.IsUnspecified() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if o.platformInterface != nil && slices.Contains(o.platformInterface.MyInterfaceAddress(), destination) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, netInterface := range o.networkManager.InterfaceFinder().Interfaces() {
|
||||||
|
for _, prefix := range netInterface.Addresses {
|
||||||
|
if prefix.Addr() == destination {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Outbound) PortAddresses() (netip.Addr, netip.Addr) {
|
func (o *Outbound) PortAddresses() (netip.Addr, netip.Addr) {
|
||||||
@@ -82,9 +123,9 @@ func (o *Outbound) WritePackets(packets [][]byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
return nil, E.New("Only L3 traffic is supported by bridge")
|
return nil, E.New("only L3 traffic is supported by bridge")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return nil, E.New("Only L3 traffic is supported by bridge")
|
return nil, E.New("only L3 traffic is supported by bridge")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bridgeTunMTU = 1500
|
bridgeTunMTU = 1500
|
||||||
maxPacketLength = 0xffff
|
maxPacketLength = 0xffff
|
||||||
bridgeMaxInstances = 254
|
bridgeMaxInstances = 254
|
||||||
|
bridgeWriteBatchSize = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ func buildBridgeAnchorRules(ruleLogger logger.ContextLogger, tunName string, egr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// pf rules are last-match: the pass rules below override the route-to pin
|
// pf rules are last-match: the pass rules below override the route-to pin
|
||||||
// for destinations in connected subnets and for addresses owned by the host
|
// for destinations in connected subnets, so the routing table delivers them
|
||||||
// itself, so they reach local delivery on their own interface.
|
// on their own interface.
|
||||||
for _, prefix := range localPrefixes {
|
for _, prefix := range localPrefixes {
|
||||||
port := inet4Port
|
port := inet4Port
|
||||||
if !prefix.Addr().Is4() {
|
if !prefix.Addr().Is4() {
|
||||||
@@ -72,16 +72,6 @@ func buildBridgeAnchorRules(ruleLogger logger.ContextLogger, tunName string, egr
|
|||||||
}
|
}
|
||||||
rules = append(rules, pfPassInRule(tunName, port, prefix))
|
rules = append(rules, pfPassInRule(tunName, port, prefix))
|
||||||
}
|
}
|
||||||
for _, address := range hostAddresses() {
|
|
||||||
port := inet4Port
|
|
||||||
if !address.Is4() {
|
|
||||||
port = inet6Port
|
|
||||||
}
|
|
||||||
if !port.IsValid() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rules = append(rules, pfPassInRule(tunName, port, netip.PrefixFrom(address, address.BitLen())))
|
|
||||||
}
|
|
||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,31 +143,6 @@ func collectLocalSegments(egress string, boundInterface string, inet4Active bool
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// hostAddresses stands in for pfctl's `self`, which expands to every address
|
|
||||||
// assigned to any interface at ruleset load time.
|
|
||||||
func hostAddresses() []netip.Addr {
|
|
||||||
interfaceAddrs, err := net.InterfaceAddrs()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var addresses []netip.Addr
|
|
||||||
for _, interfaceAddr := range interfaceAddrs {
|
|
||||||
ipNet, isIPNet := interfaceAddr.(*net.IPNet)
|
|
||||||
if !isIPNet {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, valid := netip.AddrFromSlice(ipNet.IP)
|
|
||||||
if !valid {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address = address.Unmap()
|
|
||||||
if !slices.Contains(addresses, address) {
|
|
||||||
addresses = append(addresses, address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addresses
|
|
||||||
}
|
|
||||||
|
|
||||||
func pfScrubRule(egress string, port netip.Addr, maxMSS uint16) pfAnchorRule {
|
func pfScrubRule(egress string, port netip.Addr, maxMSS uint16) pfAnchorRule {
|
||||||
rule := pfRule{
|
rule := pfRule{
|
||||||
Action: pfActionScrub,
|
Action: pfActionScrub,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package bridge
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/netlink"
|
"github.com/sagernet/netlink"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
@@ -65,10 +66,18 @@ func NewService(options ServiceOptions) (*Service, error) {
|
|||||||
func (s *Service) start(bridgeName string) error {
|
func (s *Service) start(bridgeName string) error {
|
||||||
s.tunName = tun.CalculateInterfaceName(bridgeName)
|
s.tunName = tun.CalculateInterfaceName(bridgeName)
|
||||||
s.nftTableName = "sing-box-" + s.tunName
|
s.nftTableName = "sing-box-" + s.tunName
|
||||||
tunFileDescriptor, err := openBridgeTun(s.tunName)
|
tunFileDescriptor, err := openTUN(s.tunName, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "create bridge tun")
|
return E.Cause(err, "create bridge tun")
|
||||||
}
|
}
|
||||||
|
err = setTCPOffload(tunFileDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn(E.Cause(err, "set TCP offload"))
|
||||||
|
}
|
||||||
|
err = setUDPOffload(tunFileDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn(E.Cause(err, "set UDP offload"))
|
||||||
|
}
|
||||||
s.tunFileDescriptor = tunFileDescriptor
|
s.tunFileDescriptor = tunFileDescriptor
|
||||||
tunLink, err := netlink.LinkByName(s.tunName)
|
tunLink, err := netlink.LinkByName(s.tunName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -223,29 +232,11 @@ func isDefaultDestination(destination *net.IPNet) bool {
|
|||||||
return ones == 0
|
return ones == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func openBridgeTun(name string) (int, error) {
|
//go:linkname openTUN github.com/sagernet/sing-tun.open
|
||||||
tunFileDescriptor, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
|
func openTUN(name string, vnetHdr bool) (int, error)
|
||||||
if err != nil {
|
|
||||||
tunFileDescriptor, err = unix.Open("/dev/tun", unix.O_RDWR, 0)
|
//go:linkname setTCPOffload github.com/sagernet/sing-tun.setTCPOffload
|
||||||
}
|
func setTCPOffload(fd int) error
|
||||||
if err != nil {
|
|
||||||
return -1, E.Cause(err, "open tun control device")
|
//go:linkname setUDPOffload github.com/sagernet/sing-tun.setUDPOffload
|
||||||
}
|
func setUDPOffload(fd int) error
|
||||||
ifreq, err := unix.NewIfreq(name)
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(tunFileDescriptor)
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
ifreq.SetUint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
|
||||||
err = unix.IoctlIfreq(tunFileDescriptor, unix.TUNSETIFF, ifreq)
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(tunFileDescriptor)
|
|
||||||
return -1, E.Cause(err, "TUNSETIFF")
|
|
||||||
}
|
|
||||||
err = unix.SetNonblock(tunFileDescriptor, true)
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(tunFileDescriptor)
|
|
||||||
return -1, E.Cause(err, "set nonblock")
|
|
||||||
}
|
|
||||||
return tunFileDescriptor, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -155,8 +155,11 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Outbound) SupportsFlow(network string) bool {
|
func (h *Outbound) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction {
|
||||||
return network == N.NetworkICMP && h.icmpPort != nil
|
if network == N.NetworkICMP && h.icmpPort != nil {
|
||||||
|
return adapter.PreMatchFlow
|
||||||
|
}
|
||||||
|
return adapter.PreMatchContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Outbound) PortAddresses() (netip.Addr, netip.Addr) {
|
func (h *Outbound) PortAddresses() (netip.Addr, netip.Addr) {
|
||||||
|
|||||||
@@ -825,7 +825,7 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Endpoint) PreferredDomain(domain string) bool {
|
func (t *Endpoint) PreferredDomain(metadata *adapter.InboundContext, domain string) bool {
|
||||||
routeDomains := t.routeDomains.Load()
|
routeDomains := t.routeDomains.Load()
|
||||||
if routeDomains == nil {
|
if routeDomains == nil {
|
||||||
return false
|
return false
|
||||||
@@ -833,7 +833,7 @@ func (t *Endpoint) PreferredDomain(domain string) bool {
|
|||||||
return routeDomains[strings.ToLower(domain)]
|
return routeDomains[strings.ToLower(domain)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Endpoint) PreferredAddress(address netip.Addr) bool {
|
func (t *Endpoint) PreferredAddress(metadata *adapter.InboundContext, address netip.Addr) bool {
|
||||||
routePrefixes := t.routePrefixes.Load()
|
routePrefixes := t.routePrefixes.Load()
|
||||||
if routePrefixes == nil {
|
if routePrefixes == nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -9,19 +9,13 @@ import (
|
|||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
tsTUN "github.com/sagernet/tailscale/net/tstun"
|
tsTUN "github.com/sagernet/tailscale/net/tstun"
|
||||||
"github.com/sagernet/tailscale/types/ipproto"
|
"github.com/sagernet/tailscale/types/ipproto"
|
||||||
"github.com/sagernet/tailscale/wgengine/filter"
|
"github.com/sagernet/tailscale/wgengine/filter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *Endpoint) SupportsFlow(network string) bool {
|
func (t *Endpoint) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction {
|
||||||
switch network {
|
return adapter.PreMatchFlow
|
||||||
case N.NetworkTCP, N.NetworkUDP, N.NetworkICMP:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
|
func (t *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
|
||||||
|
|||||||
+18
-9
@@ -325,17 +325,26 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
|||||||
case adapter.StartStateStart:
|
case adapter.StartStateStart:
|
||||||
if t.platformInterface == nil &&
|
if t.platformInterface == nil &&
|
||||||
((C.IsLinux && !t.tunOptions.GSO) || (C.IsDarwin && !t.tunOptions.EXP_MultiPendingPackets)) {
|
((C.IsLinux && !t.tunOptions.GSO) || (C.IsDarwin && !t.tunOptions.EXP_MultiPendingPackets)) {
|
||||||
|
outboundManager := service.FromContext[adapter.OutboundManager](t.ctx)
|
||||||
endpointManager := service.FromContext[adapter.EndpointManager](t.ctx)
|
endpointManager := service.FromContext[adapter.EndpointManager](t.ctx)
|
||||||
if endpointManager != nil {
|
for _, outbound := range outboundManager.Outbounds() {
|
||||||
for _, managedEndpoint := range endpointManager.Endpoints() {
|
if _, isFlowOutbound := outbound.(adapter.FlowOutbound); isFlowOutbound {
|
||||||
if _, isFlowOutbound := managedEndpoint.(adapter.FlowOutbound); isFlowOutbound {
|
if C.IsLinux {
|
||||||
if C.IsLinux {
|
t.tunOptions.GSO = true
|
||||||
t.tunOptions.GSO = true
|
} else {
|
||||||
} else {
|
t.tunOptions.EXP_MultiPendingPackets = true
|
||||||
t.tunOptions.EXP_MultiPendingPackets = true
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, endpoint := range endpointManager.Endpoints() {
|
||||||
|
if _, isFlowOutbound := endpoint.(adapter.FlowOutbound); isFlowOutbound {
|
||||||
|
if C.IsLinux {
|
||||||
|
t.tunOptions.GSO = true
|
||||||
|
} else {
|
||||||
|
t.tunOptions.EXP_MultiPendingPackets = true
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,13 +136,8 @@ func (w *Endpoint) Close() error {
|
|||||||
return w.endpoint.Close()
|
return w.endpoint.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Endpoint) SupportsFlow(network string) bool {
|
func (w *Endpoint) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction {
|
||||||
switch network {
|
return adapter.PreMatchFlow
|
||||||
case N.NetworkTCP, N.NetworkUDP, N.NetworkICMP:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
|
func (w *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
|
||||||
@@ -276,11 +271,11 @@ func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
return packetConn, nil
|
return packetConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Endpoint) PreferredDomain(domain string) bool {
|
func (w *Endpoint) PreferredDomain(metadata *adapter.InboundContext, domain string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Endpoint) PreferredAddress(address netip.Addr) bool {
|
func (w *Endpoint) PreferredAddress(metadata *adapter.InboundContext, address netip.Addr) bool {
|
||||||
if !w.started.Load() {
|
if !w.started.Load() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-9
@@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
"github.com/sagernet/sing-box/common/sniff"
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -308,6 +307,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
|
|
||||||
func (r *Router) PreMatch(metadata adapter.InboundContext, firstPacket []byte) adapter.PreMatchResult {
|
func (r *Router) PreMatch(metadata adapter.InboundContext, firstPacket []byte) adapter.PreMatchResult {
|
||||||
ctx := log.ContextWithNewID(r.ctx)
|
ctx := log.ContextWithNewID(r.ctx)
|
||||||
|
metadata.PreMatch = true
|
||||||
continueResult := adapter.PreMatchResult{Action: adapter.PreMatchContinue}
|
continueResult := adapter.PreMatchResult{Action: adapter.PreMatchContinue}
|
||||||
packetDestination := metadata.Destination
|
packetDestination := metadata.Destination
|
||||||
if metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) {
|
if metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) {
|
||||||
@@ -464,16 +464,13 @@ func (r *Router) preMatchFlow(ctx context.Context, metadata *adapter.InboundCont
|
|||||||
return continueResult
|
return continueResult
|
||||||
}
|
}
|
||||||
flowOutbound, isFlowOutbound := outbound.(adapter.FlowOutbound)
|
flowOutbound, isFlowOutbound := outbound.(adapter.FlowOutbound)
|
||||||
if !isFlowOutbound || !flowOutbound.SupportsFlow(metadata.Network) {
|
if !isFlowOutbound {
|
||||||
if outbound.Type() == C.TypeDirect {
|
|
||||||
directDialer, isDirectDialer := outbound.(dialer.DirectDialer)
|
|
||||||
if isDirectDialer && directDialer.IsEmpty() && !metadata.Destination.IsDomain() && metadata.Destination == packetDestination {
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return continueResult
|
return continueResult
|
||||||
}
|
}
|
||||||
|
flowAction := flowOutbound.PreMatchFlow(metadata.Network, metadata.Destination.Addr)
|
||||||
|
if flowAction != adapter.PreMatchFlow {
|
||||||
|
return adapter.PreMatchResult{Action: flowAction, Outbound: outbound}
|
||||||
|
}
|
||||||
result := adapter.PreMatchResult{Action: adapter.PreMatchFlow, Outbound: outbound}
|
result := adapter.PreMatchResult{Action: adapter.PreMatchFlow, Outbound: outbound}
|
||||||
if metadata.Network == N.NetworkUDP {
|
if metadata.Network == N.NetworkUDP {
|
||||||
if metadata.UDPTimeout > 0 {
|
if metadata.UDPTimeout > 0 {
|
||||||
@@ -507,6 +504,10 @@ func (r *Router) preMatchFlow(ctx context.Context, metadata *adapter.InboundCont
|
|||||||
}
|
}
|
||||||
return adapter.PreMatchResult{Action: adapter.PreMatchReject}
|
return adapter.PreMatchResult{Action: adapter.PreMatchReject}
|
||||||
}
|
}
|
||||||
|
flowAction = flowOutbound.PreMatchFlow(metadata.Network, newDestination)
|
||||||
|
if flowAction != adapter.PreMatchFlow {
|
||||||
|
return adapter.PreMatchResult{Action: flowAction, Outbound: outbound}
|
||||||
|
}
|
||||||
result.Destination = netip.AddrPortFrom(newDestination, metadata.Destination.Port)
|
result.Destination = netip.AddrPortFrom(newDestination, metadata.Destination.Port)
|
||||||
} else if metadata.Destination != packetDestination {
|
} else if metadata.Destination != packetDestination {
|
||||||
result.Destination = metadata.Destination.AddrPort()
|
result.Destination = metadata.Destination.AddrPort()
|
||||||
|
|||||||
@@ -50,14 +50,14 @@ func (r *PreferredByItem) Match(metadata *adapter.InboundContext) bool {
|
|||||||
}
|
}
|
||||||
if domainHost != "" {
|
if domainHost != "" {
|
||||||
for _, outbound := range r.outbounds {
|
for _, outbound := range r.outbounds {
|
||||||
if outbound.PreferredDomain(domainHost) {
|
if outbound.PreferredDomain(metadata, domainHost) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsIP() {
|
if metadata.Destination.IsIP() {
|
||||||
for _, outbound := range r.outbounds {
|
for _, outbound := range r.outbounds {
|
||||||
if outbound.PreferredAddress(metadata.Destination.Addr) {
|
if outbound.PreferredAddress(metadata, metadata.Destination.Addr) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ func (r *PreferredByItem) Match(metadata *adapter.InboundContext) bool {
|
|||||||
if len(metadata.DestinationAddresses) > 0 {
|
if len(metadata.DestinationAddresses) > 0 {
|
||||||
for _, address := range metadata.DestinationAddresses {
|
for _, address := range metadata.DestinationAddresses {
|
||||||
for _, outbound := range r.outbounds {
|
for _, outbound := range r.outbounds {
|
||||||
if outbound.PreferredAddress(address) {
|
if outbound.PreferredAddress(metadata, address) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user