Add proxy support for ICMP echo request

This commit is contained in:
世界 2025-02-17 22:00:57 +08:00
parent aa47149382
commit 7cb1cacd89
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
27 changed files with 620 additions and 96 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

@ -317,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

@ -40,4 +40,5 @@ const (
const ( const (
RuleActionRejectMethodDefault = "default" RuleActionRejectMethodDefault = "default"
RuleActionRejectMethodDrop = "drop" RuleActionRejectMethodDrop = "drop"
RuleActionRejectMethodReply = "reply"
) )

View File

@ -5,12 +5,12 @@ import (
"errors" "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" 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" "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"

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

@ -2,6 +2,10 @@
icon: material/new-box icon: material/new-box
--- ---
!!! quote "Changes in sing-box 1.13.0"
:material-alert: [reject](#reject)
!!! quote "Changes in sing-box 1.12.0" !!! quote "Changes in sing-box 1.12.0"
:material-plus: [tls_fragment](#tls_fragment) :material-plus: [tls_fragment](#tls_fragment)
@ -42,6 +46,10 @@ See `route-options` fields below.
### reject ### 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 ```json
{ {
"action": "reject", "action": "reject",
@ -58,9 +66,17 @@ For non-tun connections and already established connections, will just be closed
#### method #### method
For TCP and UDP connections:
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets. - `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop 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 #### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.

View File

@ -2,6 +2,10 @@
icon: material/new-box icon: material/new-box
--- ---
!!! quote "sing-box 1.13.0 中的更改"
:material-alert: [reject](#reject)
!!! quote "sing-box 1.12.0 中的更改" !!! quote "sing-box 1.12.0 中的更改"
:material-plus: [tls_fragment](#tls_fragment) :material-plus: [tls_fragment](#tls_fragment)
@ -38,6 +42,10 @@ icon: material/new-box
### reject ### reject
!!! quote "sing-box 1.13.0 中的更改"
自 sing-box 1.13.0 起,您可以通过 `reject` 动作拒绝或直接回复ICMP 回显ping请求。
```json ```json
{ {
"action": "reject", "action": "reject",
@ -54,9 +62,17 @@ icon: material/new-box
#### method #### method
对于 TCP 和 UDP 连接:
- `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达。 - `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达。
- `drop`: 丢弃数据包。 - `drop`: 丢弃数据包。
对于 ICMP 回显请求:
- `default`: 回复 ICMP 主机不可达。
- `drop`: 丢弃数据包。
- `reply`: 回复以 ICMP 回显应答。
#### no_drop #### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop` 如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`

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.20250825114712-2aeec120ce28 github.com/sagernet/sing v0.7.6-0.20250826155514-8bdb5fee4568
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.20250827122908-b76e852f59b0
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-sing-box-1.12-mod.1 github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1
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.20250825114712-2aeec120ce28 h1:C8Lnqd0Q+C15kwaMiDsfq5S45rhhaQMBG91TT+6oFVo= github.com/sagernet/sing v0.7.6-0.20250826155514-8bdb5fee4568 h1:0bBD73wG4Rmn1ZyMYsvHwgoDz9tFnX8BzvjbAEPoavg=
github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.7.6-0.20250826155514-8bdb5fee4568/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.20250827122908-b76e852f59b0 h1:Usid4HU1TKrtao2fv/wyubdOkBHpbHdwgU9KUzWXQMM=
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.20250827122908-b76e852f59b0/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-sing-box-1.12-mod.1 h1:gMC0q+0VvZBotZMZ9G0R8ZMEIT/Q6KnXbw0/OgMjmdk= github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1 h1:cWM1iPwqIE1t06ft80wpvFB4xbhOpIFI+TFnTw2gnbs=
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1/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

@ -282,6 +282,7 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error {
case "", C.RuleActionRejectMethodDefault: case "", C.RuleActionRejectMethodDefault:
r.Method = C.RuleActionRejectMethodDefault r.Method = C.RuleActionRejectMethodDefault
case C.RuleActionRejectMethodDrop: case C.RuleActionRejectMethodDrop:
case C.RuleActionRejectMethodReply:
default: default:
return E.New("unknown reject method: " + r.Method) return E.New("unknown reject method: " + r.Method)
} }

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

@ -3,6 +3,7 @@ package group
import ( import (
"context" "context"
"net" "net"
"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"
@ -10,6 +11,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"
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"
@ -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 { func RealTag(detour adapter.Outbound) string {
if group, isGroup := detour.(adapter.OutboundGroup); isGroup { if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
return group.Now() return group.Now()

View File

@ -14,6 +14,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"
tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"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"
@ -170,6 +171,21 @@ func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose) 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 { type URLTestGroup struct {
ctx context.Context ctx context.Context
router adapter.Router router adapter.Router

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

@ -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"
@ -112,6 +113,9 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
} }
case *R.RuleActionReject: case *R.RuleActionReject:
buf.ReleaseMulti(buffers) buf.ReleaseMulti(buffers)
if action.Method == C.RuleActionRejectMethodReply {
return E.New("reject method `reply` is not supported for TCP connections")
}
return action.Error(ctx) return action.Error(ctx)
case *R.RuleActionHijackDNS: case *R.RuleActionHijackDNS:
for _, buffer := range buffers { for _, buffer := range buffers {
@ -227,6 +231,9 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
} }
case *R.RuleActionReject: case *R.RuleActionReject:
N.ReleaseMultiPacketBuffer(packetBuffers) N.ReleaseMultiPacketBuffer(packetBuffers)
if action.Method == C.RuleActionRejectMethodReply {
return E.New("reject method `reply` is not supported for UDP connections")
}
return action.Error(ctx) return action.Error(ctx)
case *R.RuleActionHijackDNS: case *R.RuleActionHijackDNS:
return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose) return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)
@ -258,19 +265,47 @@ 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:
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 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 +498,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 +512,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,7 +6,6 @@ import (
"net/netip" "net/netip"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -325,9 +324,11 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
var returnErr error var returnErr error
switch r.Method { switch r.Method {
case C.RuleActionRejectMethodDefault: case C.RuleActionRejectMethodDefault:
returnErr = &RejectedError{syscall.ECONNREFUSED} returnErr = &RejectedError{tun.ErrReset}
case C.RuleActionRejectMethodDrop: case C.RuleActionRejectMethodDrop:
return &RejectedError{tun.ErrDrop} return &RejectedError{tun.ErrDrop}
case C.RuleActionRejectMethodReply:
return nil
default: default:
panic(F.ToString("unknown reject method: ", r.Method)) panic(F.ToString("unknown reject method: ", r.Method))
} }

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
} }
@ -87,11 +110,17 @@ func (w *stackDevice) DialContext(ctx context.Context, network string, destinati
} }
var networkProtocol tcpip.NetworkProtocolNumber var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() { if destination.IsIPv4() {
if !w.inet4Address.IsValid() {
return nil, E.New("missing IPv4 local address")
}
networkProtocol = header.IPv4ProtocolNumber networkProtocol = header.IPv4ProtocolNumber
bind.Addr = w.addr4 bind.Addr = tun.AddressFromAddr(w.inet4Address)
} else { } else {
if !w.inet6Address.IsValid() {
return nil, E.New("missing IPv6 local address")
}
networkProtocol = header.IPv6ProtocolNumber networkProtocol = header.IPv6ProtocolNumber
bind.Addr = w.addr6 bind.Addr = tun.AddressFromAddr(w.inet6Address)
} }
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkTCP: case N.NetworkTCP:
@ -118,10 +147,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 +159,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 +181,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 +258,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: