From f84129ca79c030581a97641fc15eab119acdd5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 17 Feb 2025 22:00:57 +0800 Subject: [PATCH] Add proxy support for ICMP echo request --- adapter/outbound.go | 8 ++ adapter/router.go | 4 +- common/dialer/default.go | 8 ++ constant/rule.go | 1 + docs/configuration/route/rule.md | 13 ++- docs/configuration/route/rule.zh.md | 13 ++- docs/configuration/route/rule_action.md | 16 +++ docs/configuration/route/rule_action.zh.md | 16 +++ go.mod | 50 ++++----- go.sum | 104 +++++++++--------- option/rule_action.go | 1 + protocol/direct/outbound.go | 18 +++- protocol/group/selector.go | 10 ++ protocol/group/urltest.go | 16 +++ protocol/tailscale/endpoint.go | 77 ++++++++++++-- protocol/tun/inbound.go | 20 +++- protocol/wireguard/endpoint.go | 27 ++++- protocol/wireguard/outbound.go | 8 +- route/route.go | 56 ++++++++-- route/rule/rule_action.go | 5 +- transport/wireguard/device.go | 10 +- transport/wireguard/device_nat.go | 103 ++++++++++++++++++ transport/wireguard/device_stack.go | 118 +++++++++++++++------ transport/wireguard/device_system.go | 46 ++++++-- transport/wireguard/device_system_stack.go | 67 +++++++++++- transport/wireguard/endpoint.go | 24 ++++- 26 files changed, 676 insertions(+), 163 deletions(-) create mode 100644 transport/wireguard/device_nat.go diff --git a/adapter/outbound.go b/adapter/outbound.go index 517aa0fec..91fb9c650 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -3,9 +3,11 @@ package adapter import ( "context" "net/netip" + "time" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" N "github.com/sagernet/sing/common/network" ) @@ -20,10 +22,16 @@ type Outbound interface { } type OutboundWithPreferredRoutes interface { + Outbound PreferredDomain(domain string) 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 { option.OutboundOptionsRegistry CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) diff --git a/adapter/router.go b/adapter/router.go index 0b7c8f4f7..522a0d9d3 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -6,8 +6,10 @@ import ( "net" "net/http" "sync" + "time" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-tun" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" @@ -19,7 +21,7 @@ import ( type Router interface { Lifecycle ConnectionRouter - PreMatch(metadata InboundContext) error + PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) ConnectionRouterEx RuleSet(tag string) (RuleSet, bool) NeedWIFIState() bool diff --git a/common/dialer/default.go b/common/dialer/default.go index 081116259..7eac37296 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -315,6 +315,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) { if strategy == nil { strategy = d.networkStrategy diff --git a/constant/rule.go b/constant/rule.go index 714414503..b565a39d6 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -40,4 +40,5 @@ const ( const ( RuleActionRejectMethodDefault = "default" RuleActionRejectMethodDrop = "drop" + RuleActionRejectMethodReply = "reply" ) diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 89eecaaf1..a6e89d7b6 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -7,7 +7,8 @@ icon: material/new-box :material-plus: [interface_address](#interface_address) :material-plus: [network_interface_address](#network_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" @@ -226,7 +227,15 @@ Sniffed client type, see [Protocol Sniff](/configuration/route/sniff/) for detai #### 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 diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 100344da8..a90607d48 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -7,7 +7,8 @@ icon: material/new-box :material-plus: [interface_address](#interface_address) :material-plus: [network_interface_address](#network_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 中的更改" @@ -223,7 +224,15 @@ icon: material/new-box #### 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 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index c975af2b0..a57de60fb 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-alert: [reject](#reject) + !!! quote "Changes in sing-box 1.12.0" :material-plus: [tls_fragment](#tls_fragment) @@ -42,6 +46,10 @@ See `route-options` fields below. ### reject +!!! quote "Changes in sing-box 1.13.0" + + Since sing-box 1.13.0, you can reject (or directly reply to) ICMP echo (ping) requests using `reject` action. + ```json { "action": "reject", @@ -58,9 +66,17 @@ For non-tun connections and already established connections, will just be closed #### method +For TCP and UDP connections: + - `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets. - `drop`: Drop packets. +For ICMP echo requests: + +- `default`: Reply with ICMP host unreachable. +- `drop`: Drop packets. +- `reply`: Reply with ICMP echo reply. + #### no_drop If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 0081e8279..98ea12271 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-alert: [reject](#reject) + !!! quote "sing-box 1.12.0 中的更改" :material-plus: [tls_fragment](#tls_fragment) @@ -38,6 +42,10 @@ icon: material/new-box ### reject +!!! quote "sing-box 1.13.0 中的更改" + + 自 sing-box 1.13.0 起,您可以通过 `reject` 动作拒绝(或直接回复)ICMP 回显(ping)请求。 + ```json { "action": "reject", @@ -54,9 +62,17 @@ icon: material/new-box #### method +对于 TCP 和 UDP 连接: + - `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。 - `drop`: 丢弃数据包。 +对于 ICMP 回显请求: + +- `default`: 回复 ICMP 主机不可达。 +- `drop`: 丢弃数据包。 +- `reply`: 回复以 ICMP 回显应答。 + #### no_drop 如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 diff --git a/go.mod b/go.mod index f8e34facb..5d5d64145 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sagernet/sing-box -go 1.23.1 +go 1.24.7 require ( github.com/anytls/sing-anytls v0.0.11 @@ -25,30 +25,30 @@ require ( github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.8 - github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb - github.com/sagernet/quic-go v0.52.0-sing-box-mod.3 - github.com/sagernet/sing v0.7.14 + github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506 + github.com/sagernet/quic-go v0.54.0-sing-box-mod.3 + github.com/sagernet/sing v0.8.0-beta.6 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.5.2 + github.com/sagernet/sing-quic v0.6.0-beta.3 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.7.3 + github.com/sagernet/sing-tun v0.8.0-beta.11 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.2 - github.com/sagernet/wireguard-go v0.0.1-beta.7 + github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 + github.com/sagernet/wireguard-go v0.0.2-beta.1 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.9.1 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/vishvananda/netns v0.0.5 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.41.0 - golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 - golang.org/x/mod v0.27.0 - golang.org/x/net v0.43.0 - golang.org/x/sys v0.35.0 + golang.org/x/crypto v0.42.0 + golang.org/x/exp v0.0.0-20250911091902-df9299821621 + golang.org/x/mod v0.28.0 + golang.org/x/net v0.44.0 + golang.org/x/sys v0.36.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 @@ -63,7 +63,6 @@ require ( github.com/akutz/memconn v0.1.0 // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect @@ -73,8 +72,8 @@ require ( github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/gaissmai/bart v0.11.1 // indirect - github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect + github.com/gaissmai/bart v0.18.0 // indirect + github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect @@ -84,16 +83,13 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect - github.com/illarion/gonotify/v2 v2.0.3 // indirect + github.com/illarion/gonotify/v3 v3.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/libdns/libdns v1.1.0 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect @@ -110,23 +106,23 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect - github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect + github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.37.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect diff --git a/go.sum b/go.sum index 5c1ee5a7b..35610419b 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= @@ -40,16 +38,16 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= +github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= +github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -75,22 +73,16 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= +github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= +github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU= @@ -102,8 +94,6 @@ github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IX github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ= github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g= github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8= @@ -159,36 +149,36 @@ 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/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo= 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-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= +github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506 h1:x/t3XqWshOlWqRuumpvbUvjtEr/6mJuBXAVovPefbUg= +github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506/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/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/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.52.0-sing-box-mod.3 h1:ySqffGm82rPqI1TUPqmtHIYd12pfEGScygnOxjTL56w= -github.com/sagernet/quic-go v0.52.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= -github.com/sagernet/sing v0.7.14 h1:5QQRDCUvYNOMyVp3LuK/hYEBAIv0VsbD3x/l9zH467s= -github.com/sagernet/sing v0.7.14/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/quic-go v0.54.0-sing-box-mod.3 h1:12pJN/zdpRltLG8l8JA65QYy/a+Mz938yAN3ZQUinbo= +github.com/sagernet/quic-go v0.54.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= +github.com/sagernet/sing v0.8.0-beta.6 h1:GXv1j1xWHihx6ptyOXh0yp4jUqJoNjCqD8d+AI9rnLU= +github.com/sagernet/sing v0.8.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.5.2 h1:I3vlfRImhr0uLwRS3b3ib70RMG9FcXtOKKUDz3eKRWc= -github.com/sagernet/sing-quic v0.5.2/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI= +github.com/sagernet/sing-quic v0.6.0-beta.3 h1:Z2vt49f9vNtHc9BbF9foI859n4+NAOV3gBeB1LuzL1Q= +github.com/sagernet/sing-quic v0.6.0-beta.3/go.mod h1:2/swrSS6wG6MyQA5Blq31VEWitHgBju+yZE8cPK1J5I= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= 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/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.7.3 h1:MFnAir+l24ElEyxdfwtY8mqvUUL9nPnL9TDYLkOmVes= -github.com/sagernet/sing-tun v0.7.3/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= +github.com/sagernet/sing-tun v0.8.0-beta.11 h1:xVi8VcVkvz2o+3v1PLv5MOkFpiVCwjLjucVlmigDi5c= +github.com/sagernet/sing-tun v0.8.0-beta.11/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= 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/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.2 h1:MO7s4ni2bSfAOhcan2rdQSWCztkMXmqyg6jYPZp8bEE= -github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.2/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/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= +github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 h1:Ceg+9Ug+qAFgEchGodlHmMOY2h7KktQQDAyuoIsPbos= +github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4/go.mod h1:YdN/avjce8sqPFLT9E1uEh8gPewNSnC41U4ZhBJ+ACw= +github.com/sagernet/wireguard-go v0.0.2-beta.1 h1:Zy7+dXpdViaRhP1pjtcMRfcbb7mWSxduCKzyEYbSJvk= +github.com/sagernet/wireguard-go v0.0.2-beta.1/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -197,14 +187,12 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= @@ -215,6 +203,8 @@ github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+y github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw= +github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= @@ -255,21 +245,21 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= -golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= -golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -277,20 +267,20 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -312,6 +302,8 @@ gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= +gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= diff --git a/option/rule_action.go b/option/rule_action.go index 914edb846..e28b58eb3 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -282,6 +282,7 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { case "", C.RuleActionRejectMethodDefault: r.Method = C.RuleActionRejectMethodDefault case C.RuleActionRejectMethodDrop: + case C.RuleActionRejectMethodReply: default: return E.New("unknown reject method: " + r.Method) } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 58cdb0028..1f24f0c9c 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -13,6 +13,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -28,10 +31,12 @@ var ( _ N.ParallelDialer = (*Outbound)(nil) _ dialer.ParallelNetworkDialer = (*Outbound)(nil) _ dialer.DirectDialer = (*Outbound)(nil) + _ adapter.DirectRouteOutbound = (*Outbound)(nil) ) type Outbound struct { outbound.Adapter + ctx context.Context logger logger.ContextLogger dialer dialer.ParallelInterfaceDialer domainStrategy C.DomainStrategy @@ -57,7 +62,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } 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, //nolint:staticcheck domainStrategy: C.DomainStrategy(options.DomainStrategy), @@ -145,6 +151,16 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return conn, nil } +func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + ctx := log.ContextWithNewID(h.ctx) + destination, err := ping.ConnectDestination(ctx, h.logger, common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control, metadata.Destination.Addr, routeContext, timeout) + if err != nil { + return nil, err + } + 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) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() diff --git a/protocol/group/selector.go b/protocol/group/selector.go index 439283bd6..f3f7377b6 100644 --- a/protocol/group/selector.go +++ b/protocol/group/selector.go @@ -3,6 +3,7 @@ package group import ( "context" "net" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" @@ -10,6 +11,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -174,6 +176,14 @@ func (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, } } +func (s *Selector) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + selected := s.selected.Load() + if !common.Contains(selected.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag()) + } + return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) +} + func RealTag(detour adapter.Outbound) string { if group, isGroup := detour.(adapter.OutboundGroup); isGroup { return group.Now() diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index 4bdef9fb4..26967279d 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -14,6 +14,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/batch" E "github.com/sagernet/sing/common/exceptions" @@ -170,6 +171,21 @@ func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose) } +func (s *URLTest) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + s.group.Touch() + selected := s.group.selectedOutboundTCP + if selected == nil { + selected, _ = s.group.Select(N.NetworkTCP) + } + if selected == nil { + return nil, E.New("missing supported outbound") + } + if !common.Contains(selected.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag()) + } + return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) +} + type URLTestGroup struct { ctx context.Context router adapter.Router diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 1e67c3924..ab9790885 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -21,6 +21,7 @@ import ( "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" "github.com/sagernet/gvisor/pkg/tcpip/header" "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/udp" "github.com/sagernet/sing-box/adapter" @@ -30,7 +31,9 @@ import ( "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/control" @@ -57,7 +60,10 @@ import ( "go4.org/netipx" ) -var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) +var ( + _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) + _ adapter.DirectRouteOutbound = (*Endpoint)(nil) +) func init() { version.SetVersion("sing-box " + C.Version) @@ -77,6 +83,7 @@ type Endpoint struct { platformInterface platform.Interface server *tsnet.Server stack *stack.Stack + icmpForwarder *tun.ICMPForwarder filter *atomic.Pointer[filter.Filter] onReconfigHook wgengine.ReconfigListener @@ -177,7 +184,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL }, } 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, router: router, logger: logger, @@ -242,9 +249,12 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { return gonet.TranslateNetstackError(gErr) } ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket) - udpForwarder := tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout) - ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket) + ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout).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.icmpForwarder = icmpForwarder localBackend := t.server.ExportLocalBackend() perfs := &ipn.MaskedPrefs{ @@ -433,7 +443,7 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n 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() if tsFilter != nil { var ipProto ipproto.Proto @@ -442,22 +452,41 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina ipProto = ipproto.TCP case N.NetworkUDP: ipProto = ipproto.UDP + case N.NetworkICMP: + if !destination.IsIPv6() { + ipProto = ipproto.ICMPv4 + } else { + ipProto = ipproto.ICMPv6 + } } response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto) switch response { case filter.Drop: - return syscall.ECONNRESET + return nil, syscall.ECONNREFUSED 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(), InboundType: t.Type(), + IPVersion: ipVersion, Network: network, Source: source, 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) { @@ -500,6 +529,27 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } +func (t *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + 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 { routeDomains := t.routeDomains.Load() if routeDomains == nil { @@ -527,6 +577,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)) { return } + var inet4Address, inet6Address netip.Addr + for _, address := range cfg.Addresses { + if address.Addr().Is4() { + inet4Address = address.Addr() + } else if address.Addr().Is6() { + inet6Address = address.Addr() + } + } + t.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address) t.cfg = cfg t.dnsCfg = dnsCfg diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index fc69309c0..3f013598a 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -18,6 +18,7 @@ import ( "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -454,15 +455,28 @@ func (t *Inbound) Close() error { ) } -func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { - return t.router.PreMatch(adapter.InboundContext{ +func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := t.router.PreMatch(adapter.InboundContext{ Inbound: t.tag, InboundType: C.TypeTun, + IPVersion: ipVersion, Network: network, Source: source, Destination: destination, 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) { diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 207670c20..811c6bb47 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -12,7 +12,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/transport/wireguard" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" 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) { 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, router: router, dnsRouter: service.FromContext[adapter.DNSRouter](ctx), @@ -124,14 +126,27 @@ func (w *Endpoint) Close() error { return w.endpoint.Close() } -func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { - return w.router.PreMatch(adapter.InboundContext{ +func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := w.router.PreMatch(adapter.InboundContext{ Inbound: w.Tag(), InboundType: w.Type(), + IPVersion: ipVersion, Network: network, Source: source, 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) { @@ -220,3 +235,7 @@ func (w *Endpoint) PreferredDomain(domain string) bool { func (w *Endpoint) PreferredAddress(address netip.Addr) bool { return w.endpoint.Lookup(address) != nil } + +func (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + return w.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout) +} diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index fa58d959f..5b08c6a7a 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -4,6 +4,7 @@ import ( "context" "net" "net/netip" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" @@ -13,6 +14,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/wireguard" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "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) } 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, dnsRouter: service.FromContext[adapter.DNSRouter](ctx), logger: logger, @@ -168,3 +170,7 @@ func (o *Outbound) PreferredDomain(domain string) bool { func (o *Outbound) PreferredAddress(address netip.Addr) bool { 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) +} diff --git a/route/route.go b/route/route.go index 0213f354b..7e87e97fb 100644 --- a/route/route.go +++ b/route/route.go @@ -16,6 +16,7 @@ import ( "github.com/sagernet/sing-box/option" R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-mux" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing-vmess" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" @@ -113,6 +114,9 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad } case *R.RuleActionReject: buf.ReleaseMulti(buffers) + if action.Method == C.RuleActionRejectMethodReply { + return E.New("reject method `reply` is not supported for TCP connections") + } return action.Error(ctx) case *R.RuleActionHijackDNS: for _, buffer := range buffers { @@ -228,6 +232,9 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } case *R.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) + if action.Method == C.RuleActionRejectMethodReply { + return E.New("reject method `reply` is not supported for UDP connections") + } return action.Error(ctx) case *R.RuleActionHijackDNS: return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose) @@ -259,19 +266,47 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m 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) if err != nil { - return err + return nil, err } - if selectedRule == nil { - return nil + if selectedRule != nil { + switch action := selectedRule.Action().(type) { + case *R.RuleActionReject: + switch metadata.Network { + case N.NetworkTCP: + if action.Method == C.RuleActionRejectMethodReply { + return nil, E.New("reject method `reply` is not supported for TCP connections") + } + case N.NetworkUDP: + if action.Method == C.RuleActionRejectMethodReply { + return nil, E.New("reject method `reply` is not supported for UDP connections") + } + } + 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 !isReject { - return nil + if selectedRule != nil || metadata.Network != N.NetworkICMP { + return nil, 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( @@ -464,7 +499,7 @@ match: fatalErr = newErr return } - } else { + } else if metadata.Network != N.NetworkICMP { selectedRule = currentRule selectedRuleIndex = currentRuleIndex break match @@ -478,8 +513,7 @@ match: actionType := currentRule.Action().Type() if actionType == C.RuleActionTypeRoute || actionType == C.RuleActionTypeReject || - actionType == C.RuleActionTypeHijackDNS || - (actionType == C.RuleActionTypeSniff && preMatch) { + actionType == C.RuleActionTypeHijackDNS { selectedRule = currentRule selectedRuleIndex = currentRuleIndex break match diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 62d5410a5..5c08109af 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -6,7 +6,6 @@ import ( "net/netip" "strings" "sync" - "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -325,9 +324,11 @@ func (r *RuleActionReject) Error(ctx context.Context) error { var returnErr error switch r.Method { case C.RuleActionRejectMethodDefault: - returnErr = &RejectedError{syscall.ECONNREFUSED} + returnErr = &RejectedError{tun.ErrReset} case C.RuleActionRejectMethodDrop: return &RejectedError{tun.ErrDrop} + case C.RuleActionRejectMethodReply: + return nil default: panic(F.ToString("unknown reject method: ", r.Method)) } diff --git a/transport/wireguard/device.go b/transport/wireguard/device.go index 7a17b8f35..4dd615c58 100644 --- a/transport/wireguard/device.go +++ b/transport/wireguard/device.go @@ -5,6 +5,7 @@ import ( "net/netip" "time" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" @@ -17,6 +18,8 @@ type Device interface { N.Dialer Start() error SetDevice(device *device.Device) + Inet4Address() netip.Addr + Inet6Address() netip.Addr } type DeviceOptions struct { @@ -35,9 +38,14 @@ type DeviceOptions struct { func NewDevice(options DeviceOptions) (Device, error) { if !options.System { return newStackDevice(options) - } else if options.Handler == nil { + } else if !tun.WithGVisor { return newSystemDevice(options) } else { return newSystemStackDevice(options) } } + +type NatDevice interface { + Device + CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) +} diff --git a/transport/wireguard/device_nat.go b/transport/wireguard/device_nat.go new file mode 100644 index 000000000..e5a28c1b1 --- /dev/null +++ b/transport/wireguard/device_nat.go @@ -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() +} diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index f9440f02f..a190baba4 100644 --- a/transport/wireguard/device_stack.go +++ b/transport/wireguard/device_stack.go @@ -5,7 +5,9 @@ package wireguard import ( "context" "net" + "net/netip" "os" + "time" "github.com/sagernet/gvisor/pkg/buffer" "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/ipv6" "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/udp" + "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" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -24,30 +31,40 @@ import ( wgTun "github.com/sagernet/wireguard-go/tun" ) -var _ Device = (*stackDevice)(nil) +var _ NatDevice = (*stackDevice)(nil) type stackDevice struct { - stack *stack.Stack - mtu uint32 - events chan wgTun.Event - outbound chan *stack.PacketBuffer - done chan struct{} - dispatcher stack.NetworkDispatcher - addr4 tcpip.Address - addr6 tcpip.Address + ctx context.Context + logger log.ContextLogger + stack *stack.Stack + mtu uint32 + events chan wgTun.Event + outbound chan *stack.PacketBuffer + packetOutbound chan *buf.Buffer + done chan struct{} + dispatcher stack.NetworkDispatcher + inet4Address netip.Addr + inet6Address netip.Addr } func newStackDevice(options DeviceOptions) (*stackDevice, error) { tunDevice := &stackDevice{ - mtu: options.MTU, - events: make(chan wgTun.Event, 1), - outbound: make(chan *stack.PacketBuffer, 256), - done: make(chan struct{}), + ctx: options.Context, + logger: options.Logger, + mtu: options.MTU, + 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 { return nil, err } + var ( + inet4Address netip.Addr + inet6Address netip.Addr + ) for _, prefix := range options.Address { addr := tun.AddressFromAddr(prefix.Addr()) protoAddr := tcpip.ProtocolAddress{ @@ -57,10 +74,12 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) { }, } if prefix.Addr().Is4() { - tunDevice.addr4 = addr + inet4Address = prefix.Addr() + tunDevice.inet4Address = inet4Address protoAddr.Protocol = ipv4.ProtocolNumber } else { - tunDevice.addr6 = addr + inet6Address = prefix.Addr() + tunDevice.inet6Address = inet6Address protoAddr.Protocol = ipv6.ProtocolNumber } gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{}) @@ -72,6 +91,10 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) { 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 tunDevice, nil } @@ -87,11 +110,17 @@ func (w *stackDevice) DialContext(ctx context.Context, network string, destinati } var networkProtocol tcpip.NetworkProtocolNumber if destination.IsIPv4() { + if !w.inet4Address.IsValid() { + return nil, E.New("missing IPv4 local address") + } networkProtocol = header.IPv4ProtocolNumber - bind.Addr = w.addr4 + bind.Addr = tun.AddressFromAddr(w.inet4Address) } else { + if !w.inet6Address.IsValid() { + return nil, E.New("missing IPv6 local address") + } networkProtocol = header.IPv6ProtocolNumber - bind.Addr = w.addr6 + bind.Addr = tun.AddressFromAddr(w.inet6Address) } switch N.NetworkName(network) { case N.NetworkTCP: @@ -118,10 +147,10 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) var networkProtocol tcpip.NetworkProtocolNumber if destination.IsIPv4() { networkProtocol = header.IPv4ProtocolNumber - bind.Addr = w.addr4 + bind.Addr = tun.AddressFromAddr(w.inet4Address) } else { networkProtocol = header.IPv6ProtocolNumber - bind.Addr = w.addr6 + bind.Addr = tun.AddressFromAddr(w.inet4Address) } udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol) if err != nil { @@ -130,6 +159,14 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) 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) { } @@ -144,20 +181,24 @@ func (w *stackDevice) File() *os.File { func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { select { - case packetBuffer, ok := <-w.outbound: + case packet, ok := <-w.outbound: if !ok { return 0, os.ErrClosed } - defer packetBuffer.DecRef() - p := bufs[0] - p = p[offset:] - n := 0 - for _, slice := range packetBuffer.AsSlices() { - n += copy(p[n:], slice) + defer packet.DecRef() + var copyN int + /*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) { + copyN += copy(bufs[0][offset+copyN:], view.AsSlice()) + })*/ + for _, view := range packet.AsSlices() { + copyN += copy(bufs[0][offset+copyN:], view) } - sizes[0] = n - count = 1 - return + sizes[0] = copyN + return 1, nil + case packet := <-w.packetOutbound: + defer packet.Release() + sizes[0] = copy(bufs[0][offset:], packet.Bytes()) + return 1, nil case <-w.done: return 0, os.ErrClosed } @@ -217,6 +258,23 @@ func (w *stackDevice) BatchSize() int { 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) type wireEndpoint stackDevice diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index fa54f3329..162a5cbf5 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -22,22 +22,42 @@ import ( var _ Device = (*systemDevice)(nil) type systemDevice struct { - options DeviceOptions - dialer N.Dialer - device tun.Tun - batchDevice tun.LinuxTUN - events chan wgTun.Event - closeOnce sync.Once + options DeviceOptions + dialer N.Dialer + device tun.Tun + batchDevice tun.LinuxTUN + events chan wgTun.Event + closeOnce sync.Once + inet4Address netip.Addr + inet6Address netip.Addr } func newSystemDevice(options DeviceOptions) (*systemDevice, error) { if options.Name == "" { 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{ - options: options, - dialer: options.CreateDialer(options.Name), - events: make(chan wgTun.Event, 1), + options: options, + dialer: options.CreateDialer(options.Name), + events: make(chan wgTun.Event, 1), + inet4Address: inet4Address, + inet6Address: inet6Address, }, nil } @@ -49,6 +69,14 @@ func (w *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr 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) { } diff --git a/transport/wireguard/device_system_stack.go b/transport/wireguard/device_system_stack.go index 4249e53ea..94fd6f4f9 100644 --- a/transport/wireguard/device_system_stack.go +++ b/transport/wireguard/device_system_stack.go @@ -3,16 +3,26 @@ package wireguard import ( + "context" "net/netip" + "time" "github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/tcpip" "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/transport/icmp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "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/ping" "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" "github.com/sagernet/wireguard-go/device" ) @@ -20,6 +30,8 @@ var _ Device = (*systemStackDevice)(nil) type systemStackDevice struct { *systemDevice + ctx context.Context + logger logger.ContextLogger stack *stack.Stack endpoint *deviceEndpoint writeBufs [][]byte @@ -34,13 +46,45 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) { mtu: options.MTU, done: make(chan struct{}), } - ipStack, err := tun.NewGVisorStack(endpoint) + ipStack, err := tun.NewGVisorStackWithOptions(endpoint, stack.NICOptions{}, true) if err != nil { return nil, err } - 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) + var ( + 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{ + ctx: options.Context, + logger: options.Logger, systemDevice: system, stack: ipStack, endpoint: endpoint, @@ -116,6 +160,23 @@ func (w *systemStackDevice) writeStack(packet []byte) bool { 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 { mtu uint32 done chan struct{} diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 2adf78321..12718b913 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -10,8 +10,11 @@ import ( "os" "reflect" "strings" + "time" "unsafe" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -31,6 +34,7 @@ type Endpoint struct { ipcConf string allowedAddress []netip.Prefix tunDevice Device + natDevice NatDevice device *device.Device allowedIPs *device.AllowedIPs pause pause.Manager @@ -114,12 +118,17 @@ func NewEndpoint(options EndpointOptions) (*Endpoint, error) { if err != nil { return nil, E.Cause(err, "create WireGuard device") } + natDevice, isNatDevice := tunDevice.(NatDevice) + if !isNatDevice { + natDevice = NewNATDevice(options.Context, options.Logger, tunDevice) + } return &Endpoint{ options: options, peers: peers, ipcConf: ipcConf, allowedAddress: allowedAddresses, tunDevice: tunDevice, + natDevice: natDevice, }, nil } @@ -179,7 +188,13 @@ func (e *Endpoint) Start(resolve bool) error { 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) ipcConf := e.ipcConf for _, peer := range e.peers { @@ -229,6 +244,13 @@ func (e *Endpoint) Lookup(address netip.Addr) *device.Peer { 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) { switch event { case pause.EventDevicePaused, pause.EventNetworkPause: