Compare commits

..

22 Commits

Author SHA1 Message Date
世界
8a37b52c57
documentation: Bump version 2025-08-26 11:18:41 +08:00
世界
610ed9e2ff
Add proxy support for ICMP echo request 2025-08-26 11:12:14 +08:00
世界
3e41a3553d
Fix resolve using resolved 2025-08-26 10:37:10 +08:00
世界
f9c5da2e57
Fix wireguard crash 2025-08-25 19:57:45 +08:00
世界
438b1b383b
documentation: Update behavior of local DNS server on darwin 2025-08-25 19:57:41 +08:00
世界
8c0e899e37
Stop using DHCP on iOS and tvOS
We do not have the `com.apple.developer.networking.multicast` entitlement and are unable to obtain it for non-technical reasons.
2025-08-25 19:57:40 +08:00
世界
addc385f83
Remove use of ldflags -checklinkname=0 on darwin 2025-08-25 19:57:38 +08:00
世界
8a7cd246bc
Fix local DNS server on darwin
We mistakenly believed that `libresolv`'s `search` function worked correctly in NetworkExtension, but it seems only `getaddrinfo` does.

This commit changes the behavior of the `local` DNS server in NetworkExtension to prefer DHCP, falling back to `getaddrinfo` if DHCP servers are unavailable.

It's worth noting that `prefer_go` does not disable DHCP since it respects Dial Fields, but `getaddrinfo` does the opposite. The new behavior only applies to NetworkExtension, not to all scenarios (primarily command-line binaries) as it did previously.

In addition, this commit also improves the DHCP DNS server to use the same robust query logic as `local`.
2025-08-25 19:57:35 +08:00
世界
bcd0cd5d94
Fix legacy DNS config 2025-08-25 19:57:32 +08:00
世界
caf39fa141
Fix rule-set format 2025-08-25 19:57:30 +08:00
世界
a9f9ce92bb
documentation: Remove outdated icons 2025-08-25 19:57:27 +08:00
世界
06e588d9b8
documentation: Improve local DNS server 2025-08-25 19:57:27 +08:00
世界
5442e05bba
Use libresolv in local DNS server on darwin 2025-08-25 19:57:24 +08:00
世界
49bf5fac99
Use resolved in local DNS server if available 2025-08-25 19:57:21 +08:00
xchacha20-poly1305
b5ba2ed1f0
Fix rule set version 2025-08-25 19:57:17 +08:00
世界
f6bee821e3
documentation: Add preferred_by route rule item 2025-08-25 19:57:15 +08:00
世界
a514f203a3
Add preferred_by route rule item 2025-08-25 19:57:13 +08:00
世界
345341fc05
documentation: Add interface address rule items 2025-08-25 19:57:10 +08:00
世界
97113801e4
Add interface address rule items 2025-08-25 19:57:08 +08:00
neletor
c994065f74
Add support for ech retry configs 2025-08-25 19:57:08 +08:00
Zephyruso
3328a20a9b
Add /dns/flush-clash meta api 2025-08-25 19:57:08 +08:00
世界
031f25c1c1
Deprecate common/atomic 2025-08-25 19:49:12 +08:00
34 changed files with 658 additions and 131 deletions

View File

@ -3,9 +3,11 @@ package adapter
import ( import (
"context" "context"
"net/netip" "net/netip"
"time"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@ -20,10 +22,16 @@ type Outbound interface {
} }
type OutboundWithPreferredRoutes interface { type OutboundWithPreferredRoutes interface {
Outbound
PreferredDomain(domain string) bool PreferredDomain(domain string) bool
PreferredAddress(address netip.Addr) bool PreferredAddress(address netip.Addr) bool
} }
type DirectRouteOutbound interface {
Outbound
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
}
type OutboundRegistry interface { type OutboundRegistry interface {
option.OutboundOptionsRegistry option.OutboundOptionsRegistry
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)

View File

@ -6,8 +6,10 @@ import (
"net" "net"
"net/http" "net/http"
"sync" "sync"
"time"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-tun"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
@ -19,7 +21,7 @@ import (
type Router interface { type Router interface {
Lifecycle Lifecycle
ConnectionRouter ConnectionRouter
PreMatch(metadata InboundContext) error PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
ConnectionRouterEx ConnectionRouterEx
RuleSet(tag string) (RuleSet, bool) RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool NeedWIFIState() bool

View File

@ -15,7 +15,6 @@ import (
"github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -43,7 +42,7 @@ type DefaultDialer struct {
networkType []C.InterfaceType networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration networkFallbackDelay time.Duration
networkLastFallback atomic.TypedValue[time.Time] networkLastFallback common.TypedValue[time.Time]
} }
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
@ -318,6 +317,14 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
} }
} }
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
if !destination.Is6() {
return dialerFromTCPDialer(d.dialer6)
} else {
return dialerFromTCPDialer(d.dialer4)
}
}
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if strategy == nil { if strategy == nil {
strategy = d.networkStrategy strategy = d.networkStrategy

View File

@ -8,10 +8,10 @@ import (
"net" "net"
"os" "os"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"

View File

@ -7,7 +7,6 @@ import (
"errors" "errors"
"net" "net"
mDNS "github.com/miekg/dns"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/dns"
@ -20,6 +19,8 @@ import (
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
mDNS "github.com/miekg/dns"
) )
func RegisterTransport(registry *dns.TransportRegistry) { func RegisterTransport(registry *dns.TransportRegistry) {

View File

@ -2,15 +2,19 @@ package local
import ( import (
"context" "context"
"errors"
"os" "os"
"sync" "sync"
"sync/atomic"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/service/resolved" "github.com/sagernet/sing-box/service/resolved"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/control"
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/common/x/list"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
@ -18,11 +22,18 @@ import (
) )
type DBusResolvedResolver struct { type DBusResolvedResolver struct {
logger logger.ContextLogger ctx context.Context
interfaceMonitor tun.DefaultInterfaceMonitor logger logger.ContextLogger
systemBus *dbus.Conn interfaceMonitor tun.DefaultInterfaceMonitor
resoledObject atomic.TypedValue[dbus.BusObject] interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
closeOnce sync.Once systemBus *dbus.Conn
resoledObject atomic.Pointer[ResolvedObject]
closeOnce sync.Once
}
type ResolvedObject struct {
dbus.BusObject
InterfaceIndex int32
} }
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) { func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
@ -35,6 +46,7 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso
return nil, err return nil, err
} }
return &DBusResolvedResolver{ return &DBusResolvedResolver{
ctx: ctx,
logger: logger, logger: logger,
interfaceMonitor: interfaceMonitor, interfaceMonitor: interfaceMonitor,
systemBus: systemBus, systemBus: systemBus,
@ -43,6 +55,7 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso
func (t *DBusResolvedResolver) Start() error { func (t *DBusResolvedResolver) Start() error {
t.updateStatus() t.updateStatus()
t.interfaceCallback = t.interfaceMonitor.RegisterCallback(t.updateDefaultInterface)
err := t.systemBus.BusObject().AddMatchSignal( err := t.systemBus.BusObject().AddMatchSignal(
"org.freedesktop.DBus", "org.freedesktop.DBus",
"NameOwnerChanged", "NameOwnerChanged",
@ -58,6 +71,9 @@ func (t *DBusResolvedResolver) Start() error {
func (t *DBusResolvedResolver) Close() error { func (t *DBusResolvedResolver) Close() error {
t.closeOnce.Do(func() { t.closeOnce.Do(func() {
if t.interfaceCallback != nil {
t.interfaceMonitor.UnregisterCallback(t.interfaceCallback)
}
if t.systemBus != nil { if t.systemBus != nil {
_ = t.systemBus.Close() _ = t.systemBus.Close()
} }
@ -70,22 +86,23 @@ func (t *DBusResolvedResolver) Object() any {
} }
func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
defaultInterface := t.interfaceMonitor.DefaultInterface()
if defaultInterface == nil {
return nil, E.New("missing default interface")
}
question := message.Question[0] question := message.Question[0]
call := object.(*dbus.Object).CallWithContext( resolvedObject := object.(*ResolvedObject)
call := resolvedObject.CallWithContext(
ctx, ctx,
"org.freedesktop.resolve1.Manager.ResolveRecord", "org.freedesktop.resolve1.Manager.ResolveRecord",
0, 0,
int32(defaultInterface.Index), resolvedObject.InterfaceIndex,
question.Name, question.Name,
question.Qclass, question.Qclass,
question.Qtype, question.Qtype,
uint64(0), uint64(0),
) )
if call.Err != nil { if call.Err != nil {
var dbusError dbus.Error
if errors.As(call.Err, &dbusError) && dbusError.Name == "org.freedesktop.resolve1.NoNameServers" {
t.updateStatus()
}
return nil, E.Cause(call.Err, " resolve record via resolved") return nil, E.Cause(call.Err, " resolve record via resolved")
} }
var ( var (
@ -137,14 +154,76 @@ func (t *DBusResolvedResolver) loopUpdateStatus() {
} }
func (t *DBusResolvedResolver) updateStatus() { func (t *DBusResolvedResolver) updateStatus() {
dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1") dbusObject, err := t.checkResolved(context.Background())
err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err oldValue := t.resoledObject.Swap(dbusObject)
if err != nil { if err != nil {
if t.resoledObject.Swap(nil) != nil { var dbusErr dbus.Error
if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwnerCould" {
t.logger.Debug(E.Cause(err, "systemd-resolved service unavailable"))
}
if oldValue != nil {
t.logger.Debug("systemd-resolved service is gone") t.logger.Debug("systemd-resolved service is gone")
} }
return return
} else if oldValue == nil {
t.logger.Debug("using systemd-resolved service as resolver")
} }
t.resoledObject.Store(dbusObject) }
t.logger.Debug("using systemd-resolved service as resolver")
func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObject, error) {
dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1")
err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err
if err != nil {
return nil, err
}
defaultInterface := t.interfaceMonitor.DefaultInterface()
if defaultInterface == nil {
return nil, E.New("missing default interface")
}
call := dbusObject.(*dbus.Object).CallWithContext(
ctx,
"org.freedesktop.resolve1.Manager.GetLink",
0,
int32(defaultInterface.Index),
)
if call.Err != nil {
return nil, err
}
var linkPath dbus.ObjectPath
err = call.Store(&linkPath)
if err != nil {
return nil, err
}
linkObject := t.systemBus.Object("org.freedesktop.resolve1", linkPath)
if linkObject == nil {
return nil, E.New("missing link object for default interface")
}
dnsProp, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS")
if err != nil {
return nil, err
}
var linkDNS []resolved.LinkDNS
err = dnsProp.Store(&linkDNS)
if err != nil {
return nil, err
}
if len(linkDNS) == 0 {
for _, inbound := range service.FromContext[adapter.InboundManager](t.ctx).Inbounds() {
if inbound.Type() == C.TypeTun {
return nil, E.New("No appropriate name servers or networks for name found")
}
}
return &ResolvedObject{
BusObject: dbusObject,
}, nil
} else {
return &ResolvedObject{
BusObject: dbusObject,
InterfaceIndex: int32(defaultInterface.Index),
}, nil
}
}
func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) {
t.updateStatus()
} }

View File

@ -2,15 +2,24 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.13.0-alpha.5 #### 1.13.0-alpha.6
* Add proxy support for ICMP echo request **1**
* Fixes and improvements * Fixes and improvements
**1**:
You can now match ICMP echo (ping) requests using the new `icmp` network in routing rules.
Such traffic originates from `TUN`, `WireGuard`, and `Tailscale` inbounds and can be routed to `Direct`, `WireGuard`, and `Tailscale` outbounds.
See [Route Rule](/configuration/route/rule/#network).
#### 1.12.3 #### 1.12.3
* Fixes and improvements * Fixes and improvements
#### 1.13.0-alpha.5 #### 1.13.0-alpha.4
* Fixes and improvements * Fixes and improvements

View File

@ -7,7 +7,8 @@ icon: material/new-box
:material-plus: [interface_address](#interface_address) :material-plus: [interface_address](#interface_address)
:material-plus: [network_interface_address](#network_interface_address) :material-plus: [network_interface_address](#network_interface_address)
:material-plus: [default_interface_address](#default_interface_address) :material-plus: [default_interface_address](#default_interface_address)
:material-plus: [preferred_by](#preferred_by) :material-plus: [preferred_by](#preferred_by)
:material-alert: [network](#network)
!!! quote "Changes in sing-box 1.11.0" !!! quote "Changes in sing-box 1.11.0"
@ -226,7 +227,15 @@ Sniffed client type, see [Protocol Sniff](/configuration/route/sniff/) for detai
#### network #### network
`tcp` or `udp`. !!! quote "Changes in sing-box 1.13.0"
Since sing-box 1.13.0, you can match ICMP echo (ping) requests via the new `icmp` network.
Such traffic originates from `TUN`, `WireGuard`, and `Tailscale` inbounds and can be routed to `Direct`, `WireGuard`, and `Tailscale` outbounds.
Match network type.
`tcp`, `udp` or `icmp`.
#### domain #### domain

View File

@ -7,7 +7,8 @@ icon: material/new-box
:material-plus: [interface_address](#interface_address) :material-plus: [interface_address](#interface_address)
:material-plus: [network_interface_address](#network_interface_address) :material-plus: [network_interface_address](#network_interface_address)
:material-plus: [default_interface_address](#default_interface_address) :material-plus: [default_interface_address](#default_interface_address)
:material-plus: [preferred_by](#preferred_by) :material-plus: [preferred_by](#preferred_by)
:material-alert: [network](#network)
!!! quote "sing-box 1.11.0 中的更改" !!! quote "sing-box 1.11.0 中的更改"
@ -223,7 +224,15 @@ icon: material/new-box
#### network #### network
`tcp``udp` !!! quote "sing-box 1.13.0 中的更改"
自 sing-box 1.13.0 起,您可以通过新的 `icmp` 网络匹配 ICMP 回显ping请求。
此类流量源自 `TUN``WireGuard``Tailscale` 入站,并可路由至 `Direct``WireGuard``Tailscale` 出站。
匹配网络类型。
`tcp``udp``icmp`
#### domain #### domain

View File

@ -3,12 +3,12 @@ package trafficontrol
import ( import (
"runtime" "runtime"
"sync" "sync"
"sync/atomic"
"time" "time"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/clashapi/compatible" "github.com/sagernet/sing-box/experimental/clashapi/compatible"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/common/x/list"

View File

@ -2,11 +2,11 @@ package trafficontrol
import ( import (
"net" "net"
"sync/atomic"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"

View File

@ -7,11 +7,11 @@ import (
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"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/common/atomic"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"

8
go.mod
View File

@ -25,18 +25,18 @@ require (
github.com/sagernet/cors v1.2.1 github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1 github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gomobile v0.1.8
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237
github.com/sagernet/quic-go v0.52.0-beta.1 github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f github.com/sagernet/sing v0.7.6-0.20250825141840-811aa328e57b
github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-mux v0.3.3
github.com/sagernet/sing-quic v0.5.0 github.com/sagernet/sing-quic v0.5.0
github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.7.0-beta.1 github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250826030950-79e2d3b56d01
github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/sing-vmess v0.2.7
github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/smux v1.5.34-mod.2
github.com/sagernet/tailscale v1.80.3-mod.5 github.com/sagernet/tailscale v1.80.3-mod.6
github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/wireguard-go v0.0.1-beta.7
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1

16
go.sum
View File

@ -158,8 +158,8 @@ github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQ
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo= github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo=
github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38= github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU=
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
@ -167,8 +167,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f h1:HIBo8l+tsS3wLwuI1E56uRTQw46QytXSUpZTP3vwG/U= github.com/sagernet/sing v0.7.6-0.20250825141840-811aa328e57b h1:RCfo1Q6VDAXfumNupRyqTomKzDODhASswkxVCqM8l2M=
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.7.6-0.20250825141840-811aa328e57b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak= github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
@ -179,14 +179,14 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY= github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250826030950-79e2d3b56d01 h1:eUVH7DY/1P/EwNSV5fwgkT3IlXY9AyxFThgi0liGFmI=
github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ= github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250826030950-79e2d3b56d01/go.mod h1:LokZYuEV3crByjQc/XRohLgfNvybtXdx5qe/I4W6S7k=
github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=
github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc= github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A= github.com/sagernet/tailscale v1.80.3-mod.6 h1:oJs0jpRNS/12+mPf3r9maxWl9dWy1RanugLNmsF74Gs=
github.com/sagernet/tailscale v1.80.3-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= github.com/sagernet/tailscale v1.80.3-mod.6/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=

View File

@ -13,6 +13,8 @@ import (
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"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -29,10 +31,12 @@ var (
_ N.ParallelDialer = (*Outbound)(nil) _ N.ParallelDialer = (*Outbound)(nil)
_ dialer.ParallelNetworkDialer = (*Outbound)(nil) _ dialer.ParallelNetworkDialer = (*Outbound)(nil)
_ dialer.DirectDialer = (*Outbound)(nil) _ dialer.DirectDialer = (*Outbound)(nil)
_ adapter.DirectRouteOutbound = (*Outbound)(nil)
) )
type Outbound struct { type Outbound struct {
outbound.Adapter outbound.Adapter
ctx context.Context
logger logger.ContextLogger logger logger.ContextLogger
dialer dialer.ParallelInterfaceDialer dialer dialer.ParallelInterfaceDialer
domainStrategy C.DomainStrategy domainStrategy C.DomainStrategy
@ -58,7 +62,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err return nil, err
} }
outbound := &Outbound{ outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
ctx: ctx,
logger: logger, logger: logger,
//nolint:staticcheck //nolint:staticcheck
domainStrategy: C.DomainStrategy(options.DomainStrategy), domainStrategy: C.DomainStrategy(options.DomainStrategy),
@ -146,6 +151,16 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return conn, nil 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
}
h.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return destination, nil
}
func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag() metadata.Outbound = h.Tag()

View File

@ -10,7 +10,7 @@ import (
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"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common"
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"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -37,7 +37,7 @@ type Selector struct {
tags []string tags []string
defaultTag string defaultTag string
outbounds map[string]adapter.Outbound outbounds map[string]adapter.Outbound
selected atomic.TypedValue[adapter.Outbound] selected common.TypedValue[adapter.Outbound]
interruptGroup *interrupt.Group interruptGroup *interrupt.Group
interruptExternalConnections bool interruptExternalConnections bool
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"net" "net"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -14,7 +15,6 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/batch" "github.com/sagernet/sing/common/batch"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -192,7 +192,7 @@ type URLTestGroup struct {
ticker *time.Ticker ticker *time.Ticker
close chan struct{} close chan struct{}
started bool started bool
lastActive atomic.TypedValue[time.Time] lastActive common.TypedValue[time.Time]
} }
func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) { func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) {

View File

@ -13,6 +13,7 @@ import (
"reflect" "reflect"
"runtime" "runtime"
"strings" "strings"
"sync/atomic"
"syscall" "syscall"
"time" "time"
@ -20,6 +21,7 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
"github.com/sagernet/gvisor/pkg/tcpip/header" "github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -29,9 +31,10 @@ import (
"github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -56,7 +59,10 @@ import (
"go4.org/netipx" "go4.org/netipx"
) )
var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) var (
_ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
_ adapter.DirectRouteOutbound = (*Endpoint)(nil)
)
func init() { func init() {
version.SetVersion("sing-box " + C.Version) version.SetVersion("sing-box " + C.Version)
@ -76,12 +82,13 @@ type Endpoint struct {
platformInterface platform.Interface platformInterface platform.Interface
server *tsnet.Server server *tsnet.Server
stack *stack.Stack stack *stack.Stack
icmpForwarder *tun.ICMPForwarder
filter *atomic.Pointer[filter.Filter] filter *atomic.Pointer[filter.Filter]
onReconfigHook wgengine.ReconfigListener onReconfigHook wgengine.ReconfigListener
cfg *wgcfg.Config cfg *wgcfg.Config
dnsCfg *tsDNS.Config dnsCfg *tsDNS.Config
routeDomains atomic.TypedValue[map[string]bool] routeDomains common.TypedValue[map[string]bool]
routePrefixes atomic.Pointer[netipx.IPSet] routePrefixes atomic.Pointer[netipx.IPSet]
acceptRoutes bool acceptRoutes bool
@ -175,7 +182,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
}, },
} }
return &Endpoint{ return &Endpoint{
Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
ctx: ctx, ctx: ctx,
router: router, router: router,
logger: logger, logger: logger,
@ -240,9 +247,12 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
return gonet.TranslateNetstackError(gErr) return gonet.TranslateNetstackError(gErr)
} }
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket) ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket)
udpForwarder := tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout) ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout).HandlePacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket) icmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t, t.udpTimeout)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
t.stack = ipStack t.stack = ipStack
t.icmpForwarder = icmpForwarder
localBackend := t.server.ExportLocalBackend() localBackend := t.server.ExportLocalBackend()
perfs := &ipn.MaskedPrefs{ perfs := &ipn.MaskedPrefs{
@ -263,7 +273,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
if err != nil { if err != nil {
return E.Cause(err, "update prefs") return E.Cause(err, "update prefs")
} }
t.filter = atomic.PointerForm(localBackend.ExportFilter()) t.filter = localBackend.ExportFilter()
go t.watchState() go t.watchState()
return nil return nil
} }
@ -415,7 +425,7 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return udpConn, nil return udpConn, nil
} }
func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
tsFilter := t.filter.Load() tsFilter := t.filter.Load()
if tsFilter != nil { if tsFilter != nil {
var ipProto ipproto.Proto var ipProto ipproto.Proto
@ -424,22 +434,41 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
ipProto = ipproto.TCP ipProto = ipproto.TCP
case N.NetworkUDP: case N.NetworkUDP:
ipProto = ipproto.UDP 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) response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto)
switch response { switch response {
case filter.Drop: case filter.Drop:
return syscall.ECONNRESET return nil, syscall.ECONNREFUSED
case filter.DropSilently: case filter.DropSilently:
return tun.ErrDrop return nil, tun.ErrDrop
} }
} }
return t.router.PreMatch(adapter.InboundContext{ var ipVersion uint8
if !destination.IsIPv6() {
ipVersion = 4
} else {
ipVersion = 6
}
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
Inbound: t.Tag(), Inbound: t.Tag(),
InboundType: t.Type(), InboundType: t.Type(),
IPVersion: ipVersion,
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
}) }, routeContext, timeout)
if err != nil {
if !rule.IsRejected(err) {
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) { func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
@ -482,6 +511,27 @@ 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) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
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")
}
ctx := log.ContextWithNewID(t.ctx)
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 { func (t *Endpoint) PreferredDomain(domain string) bool {
routeDomains := t.routeDomains.Load() routeDomains := t.routeDomains.Load()
if routeDomains == nil { if routeDomains == nil {
@ -509,6 +559,15 @@ 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)) { if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
return 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.cfg = cfg
t.dnsCfg = dnsCfg t.dnsCfg = dnsCfg

View File

@ -18,6 +18,7 @@ import (
"github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -454,15 +455,28 @@ func (t *Inbound) Close() error {
) )
} }
func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
return t.router.PreMatch(adapter.InboundContext{ var ipVersion uint8
if !destination.IsIPv6() {
ipVersion = 4
} else {
ipVersion = 6
}
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
Inbound: t.tag, Inbound: t.tag,
InboundType: C.TypeTun, InboundType: C.TypeTun,
IPVersion: ipVersion,
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
InboundOptions: t.inboundOptions, InboundOptions: t.inboundOptions,
}) }, routeContext, timeout)
if err != nil {
if !rule.IsRejected(err) {
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
return routeDestination, err
} }
func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {

View File

@ -12,7 +12,9 @@ import (
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"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-box/transport/wireguard" "github.com/sagernet/sing-box/transport/wireguard"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -40,7 +42,7 @@ type Endpoint struct {
func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) { func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) {
ep := &Endpoint{ ep := &Endpoint{
Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
ctx: ctx, ctx: ctx,
router: router, router: router,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx), dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
@ -124,14 +126,27 @@ func (w *Endpoint) Close() error {
return w.endpoint.Close() return w.endpoint.Close()
} }
func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
return w.router.PreMatch(adapter.InboundContext{ var ipVersion uint8
if !destination.IsIPv6() {
ipVersion = 4
} else {
ipVersion = 6
}
routeDestination, err := w.router.PreMatch(adapter.InboundContext{
Inbound: w.Tag(), Inbound: w.Tag(),
InboundType: w.Type(), InboundType: w.Type(),
IPVersion: ipVersion,
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
}) }, routeContext, timeout)
if err != nil {
if !rule.IsRejected(err) {
w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
}
}
return routeDestination, err
} }
func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
@ -220,3 +235,7 @@ func (w *Endpoint) PreferredDomain(domain string) bool {
func (w *Endpoint) PreferredAddress(address netip.Addr) bool { func (w *Endpoint) PreferredAddress(address netip.Addr) bool {
return w.endpoint.Lookup(address) != nil return w.endpoint.Lookup(address) != nil
} }
func (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
return w.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout)
}

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"net" "net"
"net/netip" "net/netip"
"time"
"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"
@ -13,6 +14,7 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/wireguard" "github.com/sagernet/sing-box/transport/wireguard"
tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
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"
@ -42,7 +44,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
deprecated.Report(ctx, deprecated.OptionWireGuardGSO) deprecated.Report(ctx, deprecated.OptionWireGuardGSO)
} }
outbound := &Outbound{ outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
ctx: ctx, ctx: ctx,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx), dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
logger: logger, logger: logger,
@ -168,3 +170,7 @@ func (o *Outbound) PreferredDomain(domain string) bool {
func (o *Outbound) PreferredAddress(address netip.Addr) bool { func (o *Outbound) PreferredAddress(address netip.Addr) bool {
return o.endpoint.Lookup(address) != nil return o.endpoint.Lookup(address) != nil
} }
func (o *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
return o.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout)
}

View File

@ -19,7 +19,6 @@ import (
"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/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
@ -37,7 +36,7 @@ var _ adapter.NetworkManager = (*NetworkManager)(nil)
type NetworkManager struct { type NetworkManager struct {
logger logger.ContextLogger logger logger.ContextLogger
interfaceFinder *control.DefaultInterfaceFinder interfaceFinder *control.DefaultInterfaceFinder
networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface] networkInterfaces common.TypedValue[[]adapter.NetworkInterface]
autoDetectInterface bool autoDetectInterface bool
defaultOptions adapter.NetworkOptions defaultOptions adapter.NetworkOptions

View File

@ -17,6 +17,7 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
R "github.com/sagernet/sing-box/route/rule" R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-mux" "github.com/sagernet/sing-mux"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-vmess" "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
@ -258,19 +259,37 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
return nil return nil
} }
func (r *Router) PreMatch(metadata adapter.InboundContext) error { func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil) selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil)
if err != nil { if err != nil {
return err return nil, err
} }
if selectedRule == nil { if selectedRule != nil {
return nil switch action := selectedRule.Action().(type) {
case *R.RuleActionReject:
return nil, action.Error(context.Background())
case *R.RuleActionRoute:
if routeContext == nil {
return nil, nil
}
outbound, loaded := r.outbound.Outbound(action.Outbound)
if !loaded {
return nil, E.New("outbound not found: ", action.Outbound)
}
if !common.Contains(outbound.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
}
return outbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
}
} }
rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject) if selectedRule != nil || metadata.Network != N.NetworkICMP {
if !isReject { return nil, nil
return nil
} }
return rejectAction.Error(context.Background()) defaultOutbound := r.outbound.Default()
if !common.Contains(defaultOutbound.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag())
}
return defaultOutbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
} }
func (r *Router) matchRule( func (r *Router) matchRule(
@ -463,7 +482,7 @@ match:
} else if len(newPacketBuffers) > 0 { } else if len(newPacketBuffers) > 0 {
packetBuffers = append(packetBuffers, newPacketBuffers...) packetBuffers = append(packetBuffers, newPacketBuffers...)
} }
} else { } else if metadata.Network != N.NetworkICMP {
selectedRule = currentRule selectedRule = currentRule
selectedRuleIndex = currentRuleIndex selectedRuleIndex = currentRuleIndex
break match break match
@ -477,8 +496,7 @@ match:
actionType := currentRule.Action().Type() actionType := currentRule.Action().Type()
if actionType == C.RuleActionTypeRoute || if actionType == C.RuleActionTypeRoute ||
actionType == C.RuleActionTypeReject || actionType == C.RuleActionTypeReject ||
actionType == C.RuleActionTypeHijackDNS || actionType == C.RuleActionTypeHijackDNS {
(actionType == C.RuleActionTypeSniff && preMatch) {
selectedRule = currentRule selectedRule = currentRule
selectedRuleIndex = currentRuleIndex selectedRuleIndex = currentRuleIndex
break match break match

View File

@ -6,6 +6,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"github.com/sagernet/fswatch" "github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -13,7 +14,6 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"

View File

@ -10,6 +10,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -17,7 +18,6 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"

View File

@ -5,8 +5,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"sync/atomic"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/service/filemanager" "github.com/sagernet/sing/service/filemanager"

View File

@ -3,9 +3,9 @@ package ssmapi
import ( import (
"net" "net"
"sync" "sync"
"sync/atomic"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )

View File

@ -15,7 +15,6 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-quic" "github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@ -30,7 +29,7 @@ type Client struct {
tlsConfig tls.Config tlsConfig tls.Config
quicConfig *quic.Config quicConfig *quic.Config
connAccess sync.Mutex connAccess sync.Mutex
conn atomic.TypedValue[quic.Connection] conn common.TypedValue[quic.Connection]
rawConn net.Conn rawConn net.Conn
} }

View File

@ -5,6 +5,7 @@ import (
"net/netip" "net/netip"
"time" "time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@ -17,6 +18,8 @@ type Device interface {
N.Dialer N.Dialer
Start() error Start() error
SetDevice(device *device.Device) SetDevice(device *device.Device)
Inet4Address() netip.Addr
Inet6Address() netip.Addr
} }
type DeviceOptions struct { type DeviceOptions struct {
@ -35,9 +38,14 @@ type DeviceOptions struct {
func NewDevice(options DeviceOptions) (Device, error) { func NewDevice(options DeviceOptions) (Device, error) {
if !options.System { if !options.System {
return newStackDevice(options) return newStackDevice(options)
} else if options.Handler == nil { } else if !tun.WithGVisor {
return newSystemDevice(options) return newSystemDevice(options)
} else { } else {
return newSystemStackDevice(options) return newSystemStackDevice(options)
} }
} }
type NatDevice interface {
Device
CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
}

View File

@ -0,0 +1,103 @@
package wireguard
import (
"context"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/logger"
)
var _ Device = (*natDeviceWrapper)(nil)
type natDeviceWrapper struct {
Device
ctx context.Context
logger logger.ContextLogger
packetOutbound chan *buf.Buffer
rewriter *ping.Rewriter
buffer [][]byte
}
func NewNATDevice(ctx context.Context, logger logger.ContextLogger, upstream Device) NatDevice {
wrapper := &natDeviceWrapper{
Device: upstream,
ctx: ctx,
logger: logger,
packetOutbound: make(chan *buf.Buffer, 256),
rewriter: ping.NewRewriter(ctx, logger, upstream.Inet4Address(), upstream.Inet6Address()),
}
return wrapper
}
func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
select {
case packet := <-d.packetOutbound:
defer packet.Release()
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
return 1, nil
default:
}
return d.Device.Read(bufs, sizes, offset)
}
func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
for _, buffer := range bufs {
handled, err := d.rewriter.WriteBack(buffer[offset:])
if handled {
if err != nil {
return 0, err
}
} else {
d.buffer = append(d.buffer, buffer)
}
}
if len(d.buffer) > 0 {
_, err := d.Device.Write(d.buffer, offset)
if err != nil {
return 0, err
}
d.buffer = d.buffer[:0]
}
return 0, nil
}
func (d *natDeviceWrapper) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
ctx := log.ContextWithNewID(d.ctx)
session := tun.DirectRouteSession{
Source: metadata.Source.Addr,
Destination: metadata.Destination.Addr,
}
d.rewriter.CreateSession(session, routeContext)
d.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return &natDestination{device: d, session: session}, nil
}
var _ tun.DirectRouteDestination = (*natDestination)(nil)
type natDestination struct {
device *natDeviceWrapper
session tun.DirectRouteSession
closed atomic.Bool
}
func (d *natDestination) WritePacket(buffer *buf.Buffer) error {
d.device.rewriter.RewritePacket(buffer.Bytes())
d.device.packetOutbound <- buffer
return nil
}
func (d *natDestination) Close() error {
d.closed.Store(true)
d.device.rewriter.DeleteSession(d.session)
return nil
}
func (d *natDestination) IsClosed() bool {
return d.closed.Load()
}

View File

@ -5,7 +5,9 @@ package wireguard
import ( import (
"context" "context"
"net" "net"
"net/netip"
"os" "os"
"time"
"github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip" "github.com/sagernet/gvisor/pkg/tcpip"
@ -14,9 +16,14 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4" "github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6" "github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
"github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@ -24,30 +31,40 @@ import (
wgTun "github.com/sagernet/wireguard-go/tun" wgTun "github.com/sagernet/wireguard-go/tun"
) )
var _ Device = (*stackDevice)(nil) var _ NatDevice = (*stackDevice)(nil)
type stackDevice struct { type stackDevice struct {
stack *stack.Stack ctx context.Context
mtu uint32 logger log.ContextLogger
events chan wgTun.Event stack *stack.Stack
outbound chan *stack.PacketBuffer mtu uint32
done chan struct{} events chan wgTun.Event
dispatcher stack.NetworkDispatcher outbound chan *stack.PacketBuffer
addr4 tcpip.Address packetOutbound chan *buf.Buffer
addr6 tcpip.Address done chan struct{}
dispatcher stack.NetworkDispatcher
inet4Address netip.Addr
inet6Address netip.Addr
} }
func newStackDevice(options DeviceOptions) (*stackDevice, error) { func newStackDevice(options DeviceOptions) (*stackDevice, error) {
tunDevice := &stackDevice{ tunDevice := &stackDevice{
mtu: options.MTU, ctx: options.Context,
events: make(chan wgTun.Event, 1), logger: options.Logger,
outbound: make(chan *stack.PacketBuffer, 256), mtu: options.MTU,
done: make(chan struct{}), events: make(chan wgTun.Event, 1),
outbound: make(chan *stack.PacketBuffer, 256),
packetOutbound: make(chan *buf.Buffer, 256),
done: make(chan struct{}),
} }
ipStack, err := tun.NewGVisorStack((*wireEndpoint)(tunDevice)) ipStack, err := tun.NewGVisorStackWithOptions((*wireEndpoint)(tunDevice), stack.NICOptions{}, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var (
inet4Address netip.Addr
inet6Address netip.Addr
)
for _, prefix := range options.Address { for _, prefix := range options.Address {
addr := tun.AddressFromAddr(prefix.Addr()) addr := tun.AddressFromAddr(prefix.Addr())
protoAddr := tcpip.ProtocolAddress{ protoAddr := tcpip.ProtocolAddress{
@ -57,10 +74,12 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
}, },
} }
if prefix.Addr().Is4() { if prefix.Addr().Is4() {
tunDevice.addr4 = addr inet4Address = prefix.Addr()
tunDevice.inet4Address = inet4Address
protoAddr.Protocol = ipv4.ProtocolNumber protoAddr.Protocol = ipv4.ProtocolNumber
} else { } else {
tunDevice.addr6 = addr inet6Address = prefix.Addr()
tunDevice.inet6Address = inet6Address
protoAddr.Protocol = ipv6.ProtocolNumber protoAddr.Protocol = ipv6.ProtocolNumber
} }
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{}) gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
@ -72,6 +91,10 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
if options.Handler != nil { if options.Handler != nil {
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket) ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket) ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout)
icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
} }
return tunDevice, nil return tunDevice, nil
} }
@ -88,10 +111,10 @@ func (w *stackDevice) DialContext(ctx context.Context, network string, destinati
var networkProtocol tcpip.NetworkProtocolNumber var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() { if destination.IsIPv4() {
networkProtocol = header.IPv4ProtocolNumber networkProtocol = header.IPv4ProtocolNumber
bind.Addr = w.addr4 bind.Addr = tun.AddressFromAddr(w.inet4Address)
} else { } else {
networkProtocol = header.IPv6ProtocolNumber networkProtocol = header.IPv6ProtocolNumber
bind.Addr = w.addr6 bind.Addr = tun.AddressFromAddr(w.inet4Address)
} }
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkTCP: case N.NetworkTCP:
@ -118,10 +141,10 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
var networkProtocol tcpip.NetworkProtocolNumber var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() { if destination.IsIPv4() {
networkProtocol = header.IPv4ProtocolNumber networkProtocol = header.IPv4ProtocolNumber
bind.Addr = w.addr4 bind.Addr = tun.AddressFromAddr(w.inet4Address)
} else { } else {
networkProtocol = header.IPv6ProtocolNumber networkProtocol = header.IPv6ProtocolNumber
bind.Addr = w.addr6 bind.Addr = tun.AddressFromAddr(w.inet4Address)
} }
udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol) udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol)
if err != nil { if err != nil {
@ -130,6 +153,14 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
return udpConn, nil return udpConn, nil
} }
func (w *stackDevice) Inet4Address() netip.Addr {
return w.inet4Address
}
func (w *stackDevice) Inet6Address() netip.Addr {
return w.inet6Address
}
func (w *stackDevice) SetDevice(device *device.Device) { func (w *stackDevice) SetDevice(device *device.Device) {
} }
@ -144,20 +175,24 @@ func (w *stackDevice) File() *os.File {
func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
select { select {
case packetBuffer, ok := <-w.outbound: case packet, ok := <-w.outbound:
if !ok { if !ok {
return 0, os.ErrClosed return 0, os.ErrClosed
} }
defer packetBuffer.DecRef() defer packet.DecRef()
p := bufs[0] var copyN int
p = p[offset:] /*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) {
n := 0 copyN += copy(bufs[0][offset+copyN:], view.AsSlice())
for _, slice := range packetBuffer.AsSlices() { })*/
n += copy(p[n:], slice) for _, view := range packet.AsSlices() {
copyN += copy(bufs[0][offset+copyN:], view)
} }
sizes[0] = n sizes[0] = copyN
count = 1 return 1, nil
return case packet := <-w.packetOutbound:
defer packet.Release()
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
return 1, nil
case <-w.done: case <-w.done:
return 0, os.ErrClosed return 0, os.ErrClosed
} }
@ -217,6 +252,23 @@ func (w *stackDevice) BatchSize() int {
return 1 return 1
} }
func (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
ctx := log.ContextWithNewID(w.ctx)
destination, err := ping.ConnectGVisor(
ctx, w.logger,
metadata.Source.Addr, metadata.Destination.Addr,
routeContext,
w.stack,
w.inet4Address, w.inet6Address,
timeout,
)
if err != nil {
return nil, err
}
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return destination, nil
}
var _ stack.LinkEndpoint = (*wireEndpoint)(nil) var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
type wireEndpoint stackDevice type wireEndpoint stackDevice

View File

@ -22,22 +22,42 @@ import (
var _ Device = (*systemDevice)(nil) var _ Device = (*systemDevice)(nil)
type systemDevice struct { type systemDevice struct {
options DeviceOptions options DeviceOptions
dialer N.Dialer dialer N.Dialer
device tun.Tun device tun.Tun
batchDevice tun.LinuxTUN batchDevice tun.LinuxTUN
events chan wgTun.Event events chan wgTun.Event
closeOnce sync.Once closeOnce sync.Once
inet4Address netip.Addr
inet6Address netip.Addr
} }
func newSystemDevice(options DeviceOptions) (*systemDevice, error) { func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
if options.Name == "" { if options.Name == "" {
options.Name = tun.CalculateInterfaceName("wg") options.Name = tun.CalculateInterfaceName("wg")
} }
var inet4Address netip.Addr
var inet6Address netip.Addr
if len(options.Address) > 0 {
if prefix := common.Find(options.Address, func(it netip.Prefix) bool {
return it.Addr().Is4()
}); prefix.IsValid() {
inet4Address = prefix.Addr()
}
}
if len(options.Address) > 0 {
if prefix := common.Find(options.Address, func(it netip.Prefix) bool {
return it.Addr().Is6()
}); prefix.IsValid() {
inet6Address = prefix.Addr()
}
}
return &systemDevice{ return &systemDevice{
options: options, options: options,
dialer: options.CreateDialer(options.Name), dialer: options.CreateDialer(options.Name),
events: make(chan wgTun.Event, 1), events: make(chan wgTun.Event, 1),
inet4Address: inet4Address,
inet6Address: inet6Address,
}, nil }, nil
} }
@ -49,6 +69,14 @@ func (w *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr
return w.dialer.ListenPacket(ctx, destination) return w.dialer.ListenPacket(ctx, destination)
} }
func (w *systemDevice) Inet4Address() netip.Addr {
return w.inet4Address
}
func (w *systemDevice) Inet6Address() netip.Addr {
return w.inet6Address
}
func (w *systemDevice) SetDevice(device *device.Device) { func (w *systemDevice) SetDevice(device *device.Device) {
} }

View File

@ -3,16 +3,26 @@
package wireguard package wireguard
import ( import (
"context"
"net/netip" "net/netip"
"time"
"github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip" "github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/header" "github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
"github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/wireguard-go/device" "github.com/sagernet/wireguard-go/device"
) )
@ -20,6 +30,8 @@ var _ Device = (*systemStackDevice)(nil)
type systemStackDevice struct { type systemStackDevice struct {
*systemDevice *systemDevice
ctx context.Context
logger logger.ContextLogger
stack *stack.Stack stack *stack.Stack
endpoint *deviceEndpoint endpoint *deviceEndpoint
writeBufs [][]byte writeBufs [][]byte
@ -34,13 +46,45 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
mtu: options.MTU, mtu: options.MTU,
done: make(chan struct{}), done: make(chan struct{}),
} }
ipStack, err := tun.NewGVisorStack(endpoint) ipStack, err := tun.NewGVisorStackWithOptions(endpoint, stack.NICOptions{}, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket) var (
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket) inet4Address netip.Addr
inet6Address netip.Addr
)
for _, prefix := range options.Address {
addr := tun.AddressFromAddr(prefix.Addr())
protoAddr := tcpip.ProtocolAddress{
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: addr,
PrefixLen: prefix.Bits(),
},
}
if prefix.Addr().Is4() {
inet4Address = prefix.Addr()
protoAddr.Protocol = ipv4.ProtocolNumber
} else {
inet6Address = prefix.Addr()
protoAddr.Protocol = ipv6.ProtocolNumber
}
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
if gErr != nil {
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String())
}
}
if options.Handler != nil {
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout)
icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
}
return &systemStackDevice{ return &systemStackDevice{
ctx: options.Context,
logger: options.Logger,
systemDevice: system, systemDevice: system,
stack: ipStack, stack: ipStack,
endpoint: endpoint, endpoint: endpoint,
@ -116,6 +160,23 @@ func (w *systemStackDevice) writeStack(packet []byte) bool {
return true return true
} }
func (w *systemStackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
ctx := log.ContextWithNewID(w.ctx)
destination, err := ping.ConnectGVisor(
ctx, w.logger,
metadata.Source.Addr, metadata.Destination.Addr,
routeContext,
w.stack,
w.inet4Address, w.inet6Address,
timeout,
)
if err != nil {
return nil, err
}
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return destination, nil
}
type deviceEndpoint struct { type deviceEndpoint struct {
mtu uint32 mtu uint32
done chan struct{} done chan struct{}

View File

@ -10,8 +10,11 @@ import (
"os" "os"
"reflect" "reflect"
"strings" "strings"
"time"
"unsafe" "unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
@ -31,6 +34,7 @@ type Endpoint struct {
ipcConf string ipcConf string
allowedAddress []netip.Prefix allowedAddress []netip.Prefix
tunDevice Device tunDevice Device
natDevice NatDevice
device *device.Device device *device.Device
allowedIPs *device.AllowedIPs allowedIPs *device.AllowedIPs
pause pause.Manager pause pause.Manager
@ -114,12 +118,17 @@ func NewEndpoint(options EndpointOptions) (*Endpoint, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "create WireGuard device") return nil, E.Cause(err, "create WireGuard device")
} }
natDevice, isNatDevice := tunDevice.(NatDevice)
if !isNatDevice {
natDevice = NewNATDevice(options.Context, options.Logger, tunDevice)
}
return &Endpoint{ return &Endpoint{
options: options, options: options,
peers: peers, peers: peers,
ipcConf: ipcConf, ipcConf: ipcConf,
allowedAddress: allowedAddresses, allowedAddress: allowedAddresses,
tunDevice: tunDevice, tunDevice: tunDevice,
natDevice: natDevice,
}, nil }, nil
} }
@ -179,7 +188,13 @@ func (e *Endpoint) Start(resolve bool) error {
e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
}, },
} }
wgDevice := device.NewDevice(e.options.Context, e.tunDevice, bind, logger, e.options.Workers) var deviceInput Device
if e.natDevice != nil {
deviceInput = e.natDevice
} else {
deviceInput = e.tunDevice
}
wgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers)
e.tunDevice.SetDevice(wgDevice) e.tunDevice.SetDevice(wgDevice)
ipcConf := e.ipcConf ipcConf := e.ipcConf
for _, peer := range e.peers { for _, peer := range e.peers {
@ -229,6 +244,13 @@ func (e *Endpoint) Lookup(address netip.Addr) *device.Peer {
return e.allowedIPs.Lookup(address.AsSlice()) return e.allowedIPs.Lookup(address.AsSlice())
} }
func (e *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
if e.natDevice == nil {
return nil, os.ErrInvalid
}
return e.natDevice.CreateDestination(metadata, routeContext, timeout)
}
func (e *Endpoint) onPauseUpdated(event int) { func (e *Endpoint) onPauseUpdated(event int) {
switch event { switch event {
case pause.EventDevicePaused, pause.EventNetworkPause: case pause.EventDevicePaused, pause.EventNetworkPause: