Compare commits

...

5 Commits

Author SHA1 Message Date
世界
00870d2833 save 2025-02-18 17:30:03 +08:00
世界
f2d26f5842 Add ping support for WireGuard endpoint 2025-02-17 22:05:57 +08:00
世界
baa68b5c6f documentation: Make it clear that auth key is not required for Tailscale 2025-02-16 22:12:12 +08:00
世界
9a6fb1e0c4 Fix linter 2025-02-16 11:49:30 +08:00
世界
8f16eec0de Add back port hopping to hysteria 1 2025-02-16 11:49:24 +08:00
39 changed files with 628 additions and 106 deletions

View File

@@ -21,7 +21,7 @@ linters-settings:
- -SA1003 - -SA1003
run: run:
go: "1.23" go: "1.24"
build-tags: build-tags:
- with_gvisor - with_gvisor
- with_quic - with_quic

View File

@@ -5,6 +5,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-tun"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@@ -18,6 +19,11 @@ type Outbound interface {
N.Dialer N.Dialer
} }
type DirectRouteOutbound interface {
Outbound
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext) (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

@@ -8,6 +8,7 @@ import (
"sync" "sync"
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 +20,7 @@ import (
type Router interface { type Router interface {
Lifecycle Lifecycle
ConnectionRouter ConnectionRouter
PreMatch(metadata InboundContext) error PreMatch(metadata InboundContext, context tun.DirectRouteContext) (tun.DirectRouteDestination, error)
ConnectionRouterEx ConnectionRouterEx
RuleSet(tag string) (RuleSet, bool) RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool NeedWIFIState() bool

View File

@@ -37,6 +37,10 @@ Example: `$HOME/.tailscale`
#### auth_key #### auth_key
!!! note
Auth key is not required. By default, sing-box will log the login URL (or popup a notification on graphical clients).
The auth key to create the node. If the node is already created (from state previously stored), then this field is not The auth key to create the node. If the node is already created (from state previously stored), then this field is not
used. used.

View File

@@ -1,3 +1,12 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [server_ports](#server_ports)
:material-plus: [hop_interval](#hop_interval)
### Structure ### Structure
```json ```json
@@ -7,6 +16,10 @@
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 1080, "server_port": 1080,
"server_ports": [
"2080:3000"
],
"hop_interval": "",
"up": "100 Mbps", "up": "100 Mbps",
"up_mbps": 100, "up_mbps": 100,
"down": "100 Mbps", "down": "100 Mbps",
@@ -38,6 +51,22 @@ The server address.
The server port. The server port.
#### server_ports
!!! question "Since sing-box 1.12.0"
Server port range list.
Conflicts with `server_port`.
#### hop_interval
!!! question "Since sing-box 1.12.0"
Port hopping interval.
`30s` is used by default.
#### up, down #### up, down
==Required== ==Required==

View File

@@ -1,3 +1,12 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [server_ports](#server_ports)
:material-plus: [hop_interval](#hop_interval)
### 结构 ### 结构
```json ```json
@@ -7,6 +16,10 @@
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 1080, "server_port": 1080,
"server_ports": [
"2080:3000"
],
"hop_interval": "",
"up": "100 Mbps", "up": "100 Mbps",
"up_mbps": 100, "up_mbps": 100,
"down": "100 Mbps", "down": "100 Mbps",
@@ -38,6 +51,22 @@
服务器端口。 服务器端口。
#### server_ports
!!! question "自 sing-box 1.12.0 起"
服务器端口范围列表。
`server_port` 冲突。
#### hop_interval
!!! question "自 sing-box 1.12.0 起"
端口跳跃间隔。
默认使用 `30s`
#### up, down #### up, down
==必填== ==必填==

6
go.mod
View File

@@ -25,16 +25,16 @@ 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.4 github.com/sagernet/gomobile v0.1.4
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/gvisor v0.0.0-20250217052116-ed66b6946f72
github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/quic-go v0.49.0-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.2-0.20250210105917-3464ed3babc0 github.com/sagernet/sing v0.6.2-0.20250210105917-3464ed3babc0
github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-mux v0.3.1
github.com/sagernet/sing-quic v0.4.0 github.com/sagernet/sing-quic v0.4.1-beta.1
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.0 github.com/sagernet/sing-shadowtls v0.2.0
github.com/sagernet/sing-tun v0.6.1 github.com/sagernet/sing-tun v0.6.2-0.20250217135654-784bb584392f
github.com/sagernet/sing-vmess v0.2.0 github.com/sagernet/sing-vmess v0.2.0
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/tailscale v1.79.0-mod.1 github.com/sagernet/tailscale v1.79.0-mod.1

12
go.sum
View File

@@ -171,8 +171,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.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY= github.com/sagernet/gomobile v0.1.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY=
github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff h1:mlohw3360Wg1BNGook/UHnISXhUx4Gd/3tVLs5T0nSs= github.com/sagernet/gvisor v0.0.0-20250217052116-ed66b6946f72 h1:Jgv6N59yiVMEwimTcFV1EVcu2Aa7R2Wh1ZAYNzWP2qA=
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= github.com/sagernet/gvisor v0.0.0-20250217052116-ed66b6946f72/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw=
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=
@@ -186,16 +186,16 @@ github.com/sagernet/sing v0.6.2-0.20250210105917-3464ed3babc0 h1:8gTBdpb2JeNq4oA
github.com/sagernet/sing v0.6.2-0.20250210105917-3464ed3babc0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.2-0.20250210105917-3464ed3babc0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI=
github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78= github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78=
github.com/sagernet/sing-quic v0.4.0 h1:E4geazHk/UrJTXMlT+CBCKmn8V86RhtNeczWtfeoEFc= github.com/sagernet/sing-quic v0.4.1-beta.1 h1:V2VfMckT3EQR3ZdfSzJgZZDsvfZZH42QAZpnOnHKa0s=
github.com/sagernet/sing-quic v0.4.0/go.mod h1:c+CytOEyeN20KCTFIP8YQUkNDVFLSzjrEPqP7Hlnxys= github.com/sagernet/sing-quic v0.4.1-beta.1/go.mod h1:c+CytOEyeN20KCTFIP8YQUkNDVFLSzjrEPqP7Hlnxys=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk=
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo=
github.com/sagernet/sing-tun v0.6.1 h1:4l0+gnEKcGjlWfUVTD+W0BRApqIny/lU2ZliurE+VMo= github.com/sagernet/sing-tun v0.6.2-0.20250217135654-784bb584392f h1:VEtmCNfk8RuZcYz/Xx63F2QjcgG3z/7Pa0uiJciQo+Y=
github.com/sagernet/sing-tun v0.6.1/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-tun v0.6.2-0.20250217135654-784bb584392f/go.mod h1:UiOi1ombGaAzWkGSgH4qcP7Zpq8FjWc1uQmleK8oPCE=
github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI= github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI=
github.com/sagernet/sing-vmess v0.2.0/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= github.com/sagernet/sing-vmess v0.2.0/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=

View File

@@ -1,5 +1,7 @@
package option package option
import "github.com/sagernet/sing/common/json/badoption"
type HysteriaInboundOptions struct { type HysteriaInboundOptions struct {
ListenOptions ListenOptions
Up string `json:"up,omitempty"` Up string `json:"up,omitempty"`
@@ -24,16 +26,18 @@ type HysteriaUser struct {
type HysteriaOutboundOptions struct { type HysteriaOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
Up string `json:"up,omitempty"` ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
UpMbps int `json:"up_mbps,omitempty"` HopInterval badoption.Duration `json:"hop_interval,omitempty"`
Down string `json:"down,omitempty"` Up string `json:"up,omitempty"`
DownMbps int `json:"down_mbps,omitempty"` UpMbps int `json:"up_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"` Down string `json:"down,omitempty"`
Auth []byte `json:"auth,omitempty"` DownMbps int `json:"down_mbps,omitempty"`
AuthString string `json:"auth_str,omitempty"` Obfs string `json:"obfs,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"` Auth []byte `json:"auth,omitempty"`
ReceiveWindow uint64 `json:"recv_window,omitempty"` AuthString string `json:"auth_str,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"` ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
Network NetworkList `json:"network,omitempty"` ReceiveWindow uint64 `json:"recv_window,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Network NetworkList `json:"network,omitempty"`
OutboundTLSOptionsContainer OutboundTLSOptionsContainer
} }

View File

@@ -12,6 +12,11 @@ import (
mDNS "github.com/miekg/dns" mDNS "github.com/miekg/dns"
) )
var (
DefaultNetworks = []string{N.NetworkTCP, N.NetworkUDP}
DefaultIPNetworks = []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMPv4, N.NetworkICMPv6}
)
type NetworkList string type NetworkList string
func (v *NetworkList) UnmarshalJSON(content []byte) error { func (v *NetworkList) UnmarshalJSON(content []byte) error {
@@ -37,9 +42,9 @@ func (v *NetworkList) UnmarshalJSON(content []byte) error {
return nil return nil
} }
func (v NetworkList) Build() []string { func (v NetworkList) Build(defaultNetworks []string) []string {
if v == "" { if v == "" {
return []string{N.NetworkTCP, N.NetworkUDP} return defaultNetworks
} }
return strings.Split(string(v), "\n") return strings.Split(string(v), "\n")
} }

View File

@@ -60,7 +60,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
inbound.listener = listener.New(listener.Options{ inbound.listener = listener.New(listener.Options{
Context: ctx, Context: ctx,
Logger: logger, Logger: logger,
Network: options.Network.Build(), Network: options.Network.Build(option.DefaultIPNetworks),
Listen: options.ListenOptions, Listen: options.ListenOptions,
ConnectionHandler: inbound, ConnectionHandler: inbound,
PacketHandler: inbound, PacketHandler: inbound,

View File

@@ -2,6 +2,7 @@ package direct
import ( import (
"context" "context"
"github.com/sagernet/sing-tun"
"net" "net"
"net/netip" "net/netip"
"time" "time"
@@ -27,6 +28,7 @@ func RegisterOutbound(registry *outbound.Registry) {
var ( var (
_ N.ParallelDialer = (*Outbound)(nil) _ N.ParallelDialer = (*Outbound)(nil)
_ dialer.ParallelNetworkDialer = (*Outbound)(nil) _ dialer.ParallelNetworkDialer = (*Outbound)(nil)
_ adapter.DirectRouteOutbound = (*Outbound)(nil)
) )
type Outbound struct { type Outbound struct {
@@ -50,7 +52,7 @@ 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, option.DefaultIPNetworks, options.DialerOptions),
logger: logger, logger: logger,
//nolint:staticcheck //nolint:staticcheck
domainStrategy: C.DomainStrategy(options.DomainStrategy), domainStrategy: C.DomainStrategy(options.DomainStrategy),
@@ -242,6 +244,10 @@ func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.
return conn, newDestination, nil return conn, newDestination, nil
} }
func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
}
/*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { /*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) {
return E.New("reject loopback connection to ", metadata.Destination) return E.New("reject loopback connection to ", metadata.Destination)

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"net" "net"
"os" "os"
"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"
@@ -51,7 +52,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil { if err != nil {
return nil, err return nil, err
} }
networkList := options.Network.Build() networkList := options.Network.Build(option.DefaultIPNetworks)
var password string var password string
if options.AuthString != "" { if options.AuthString != "" {
password = options.AuthString password = options.AuthString
@@ -76,17 +77,18 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
} }
client, err := hysteria.NewClient(hysteria.ClientOptions{ client, err := hysteria.NewClient(hysteria.ClientOptions{
Context: ctx, Context: ctx,
Dialer: outboundDialer, Dialer: outboundDialer,
Logger: logger, Logger: logger,
ServerAddress: options.ServerOptions.Build(), ServerAddress: options.ServerOptions.Build(),
SendBPS: sendBps, ServerPorts: options.ServerPorts,
ReceiveBPS: receiveBps, HopInterval: time.Duration(options.HopInterval),
XPlusPassword: options.Obfs, SendBPS: sendBps,
Password: password, ReceiveBPS: receiveBps,
TLSConfig: tlsConfig, XPlusPassword: options.Obfs,
UDPDisabled: !common.Contains(networkList, N.NetworkUDP), Password: password,
TLSConfig: tlsConfig,
UDPDisabled: !common.Contains(networkList, N.NetworkUDP),
ConnReceiveWindow: options.ReceiveWindowConn, ConnReceiveWindow: options.ReceiveWindowConn,
StreamReceiveWindow: options.ReceiveWindow, StreamReceiveWindow: options.ReceiveWindow,
DisableMTUDiscovery: options.DisableMTUDiscovery, DisableMTUDiscovery: options.DisableMTUDiscovery,

View File

@@ -64,7 +64,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil { if err != nil {
return nil, err return nil, err
} }
networkList := options.Network.Build() networkList := options.Network.Build(option.DefaultIPNetworks)
client, err := hysteria2.NewClient(hysteria2.ClientOptions{ client, err := hysteria2.NewClient(hysteria2.ClientOptions{
Context: ctx, Context: ctx,
Dialer: outboundDialer, Dialer: outboundDialer,

View File

@@ -57,7 +57,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
Listen: options.ListenOptions, Listen: options.ListenOptions,
}), }),
networkIsDefault: options.Network == "", networkIsDefault: options.Network == "",
network: options.Network.Build(), network: options.Network.Build(option.DefaultIPNetworks),
authenticator: auth.NewAuthenticator(options.Users), authenticator: auth.NewAuthenticator(options.Users),
} }
if common.Contains(inbound.network, N.NetworkUDP) { if common.Contains(inbound.network, N.NetworkUDP) {

View File

@@ -53,7 +53,7 @@ func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLog
tproxy.listener = listener.New(listener.Options{ tproxy.listener = listener.New(listener.Options{
Context: ctx, Context: ctx,
Logger: logger, Logger: logger,
Network: options.Network.Build(), Network: options.Network.Build(option.DefaultIPNetworks),
Listen: options.ListenOptions, Listen: options.ListenOptions,
ConnectionHandler: tproxy, ConnectionHandler: tproxy,
OOBPacketHandler: tproxy, OOBPacketHandler: tproxy,

View File

@@ -84,7 +84,7 @@ func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
inbound.listener = listener.New(listener.Options{ inbound.listener = listener.New(listener.Options{
Context: ctx, Context: ctx,
Logger: logger, Logger: logger,
Network: options.Network.Build(), Network: options.Network.Build(option.DefaultIPNetworks),
Listen: options.ListenOptions, Listen: options.ListenOptions,
ConnectionHandler: inbound, ConnectionHandler: inbound,
PacketHandler: inbound, PacketHandler: inbound,

View File

@@ -92,7 +92,7 @@ func newMultiInbound(ctx context.Context, router adapter.Router, logger log.Cont
inbound.listener = listener.New(listener.Options{ inbound.listener = listener.New(listener.Options{
Context: ctx, Context: ctx,
Logger: logger, Logger: logger,
Network: options.Network.Build(), Network: options.Network.Build(option.DefaultIPNetworks),
Listen: options.ListenOptions, Listen: options.ListenOptions,
ConnectionHandler: inbound, ConnectionHandler: inbound,
PacketHandler: inbound, PacketHandler: inbound,

View File

@@ -77,7 +77,7 @@ func newRelayInbound(ctx context.Context, router adapter.Router, logger log.Cont
inbound.listener = listener.New(listener.Options{ inbound.listener = listener.New(listener.Options{
Context: ctx, Context: ctx,
Logger: logger, Logger: logger,
Network: options.Network.Build(), Network: options.Network.Build(option.DefaultIPNetworks),
Listen: options.ListenOptions, Listen: options.ListenOptions,
ConnectionHandler: inbound, ConnectionHandler: inbound,
PacketHandler: inbound, PacketHandler: inbound,

View File

@@ -49,7 +49,7 @@ 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.TypeShadowsocks, tag, options.Network.Build(), options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, tag, options.Network.Build(option.DefaultIPNetworks), options.DialerOptions),
logger: logger, logger: logger,
dialer: outboundDialer, dialer: outboundDialer,
method: method, method: method,

View File

@@ -51,7 +51,7 @@ 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.TypeSOCKS, tag, options.Network.Build(), options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, tag, options.Network.Build(option.DefaultIPNetworks), options.DialerOptions),
dnsRouter: service.FromContext[adapter.DNSRouter](ctx), dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
logger: logger, logger: logger,
client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password),

View File

@@ -18,6 +18,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"
@@ -205,8 +206,10 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
ipStack := t.server.ExportNetstack().ExportIPStack() ipStack := t.server.ExportNetstack().ExportIPStack()
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
localBackend := t.server.ExportLocalBackend() localBackend := t.server.ExportLocalBackend()
@@ -377,7 +380,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) (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
@@ -390,9 +393,9 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
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{ return t.router.PreMatch(adapter.InboundContext{
@@ -401,7 +404,7 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
}) }, routeContext)
} }
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) {

View File

@@ -43,7 +43,7 @@ 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.TypeTrojan, tag, options.Network.Build(), options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTrojan, tag, options.Network.Build(option.DefaultIPNetworks), options.DialerOptions),
logger: logger, logger: logger,
dialer: outboundDialer, dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),

View File

@@ -80,7 +80,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err return nil, err
} }
return &Outbound{ return &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTUIC, tag, options.Network.Build(), options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTUIC, tag, options.Network.Build(option.DefaultIPNetworks), options.DialerOptions),
logger: logger, logger: logger,
client: client, client: client,
udpStream: options.UDPOverStream, udpStream: options.UDPOverStream,

View File

@@ -8,6 +8,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -438,15 +439,21 @@ 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) (tun.DirectRouteDestination, error) {
return t.router.PreMatch(adapter.InboundContext{ routeDestination, err := t.router.PreMatch(adapter.InboundContext{
Inbound: t.tag, Inbound: t.tag,
InboundType: C.TypeTun, InboundType: C.TypeTun,
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
InboundOptions: t.inboundOptions, InboundOptions: t.inboundOptions,
}) }, routeContext)
if err != nil {
if !E.IsMulti(err, tun.ErrDrop, syscall.ECONNREFUSED) {
t.logger.Warn(err)
}
}
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

@@ -46,7 +46,7 @@ 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.TypeVLESS, tag, options.Network.Build(), options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVLESS, tag, options.Network.Build(option.DefaultIPNetworks), options.DialerOptions),
logger: logger, logger: logger,
dialer: outboundDialer, dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),

View File

@@ -46,7 +46,7 @@ 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.TypeVMess, tag, options.Network.Build(), options.DialerOptions), Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVMess, tag, options.Network.Build(option.DefaultIPNetworks), options.DialerOptions),
logger: logger, logger: logger,
dialer: outboundDialer, dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),

View File

@@ -13,6 +13,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"
"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"
@@ -43,7 +44,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.NetworkICMPv4, N.NetworkICMPv6}, options.DialerOptions),
ctx: ctx, ctx: ctx,
router: router, router: router,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx), dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
@@ -132,14 +133,14 @@ func (w *Endpoint) InterfaceUpdated() {
return return
} }
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, context tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
return w.router.PreMatch(adapter.InboundContext{ return w.router.PreMatch(adapter.InboundContext{
Inbound: w.Tag(), Inbound: w.Tag(),
InboundType: w.Type(), InboundType: w.Type(),
Network: network, Network: network,
Source: source, Source: source,
Destination: destination, Destination: destination,
}) }, context)
} }
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 +221,12 @@ func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
} }
return w.endpoint.ListenPacket(ctx, destination) return w.endpoint.ListenPacket(ctx, destination)
} }
func (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
destination, err := w.endpoint.NewDirectRouteConnection(metadata, routeContext)
if err != nil {
return nil, err
}
w.logger.Info("linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
return destination, nil
}

View File

@@ -45,7 +45,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, option.DefaultNetworks, options.DialerOptions),
ctx: ctx, ctx: ctx,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx), dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
logger: logger, logger: logger,

View File

@@ -18,6 +18,7 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule" "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"
@@ -271,19 +272,36 @@ 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) (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 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)
} }
rejectAction, isReject := selectedRule.Action().(*rule.RuleActionReject) switch action := selectedRule.Action().(type) {
if !isReject { case *rule.RuleActionReject:
return nil return nil, action.Error(context.Background())
case *rule.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)
default:
return nil, nil
} }
return rejectAction.Error(context.Background())
} }
func (r *Router) matchRule( func (r *Router) matchRule(

View File

@@ -62,6 +62,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
dialOptions = append(dialOptions, grpc.WithContextDialer(func(ctx context.Context, server string) (net.Conn, error) { dialOptions = append(dialOptions, grpc.WithContextDialer(func(ctx context.Context, server string) (net.Conn, error) {
return dialer.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(server)) return dialer.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(server))
})) }))
//nolint:staticcheck
dialOptions = append(dialOptions, grpc.WithReturnConnectionError()) dialOptions = append(dialOptions, grpc.WithReturnConnectionError())
return &Client{ return &Client{
ctx: ctx, ctx: ctx,
@@ -84,7 +85,6 @@ func (c *Client) connect() (*grpc.ClientConn, error) {
return conn, nil return conn, nil
} }
//nolint:staticcheck //nolint:staticcheck
//goland:noinspection GoDeprecation
conn, err := grpc.DialContext(c.ctx, c.serverAddr, c.dialOptions...) conn, err := grpc.DialContext(c.ctx, c.serverAddr, c.dialOptions...)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -18,6 +18,7 @@ type GRPCConn struct {
} }
func NewGRPCConn(service GunService) *GRPCConn { func NewGRPCConn(service GunService) *GRPCConn {
//nolint:staticcheck
if client, isClient := service.(GunService_TunClient); isClient { if client, isClient := service.(GunService_TunClient); isClient {
service = &clientConnWrapper{client} service = &clientConnWrapper{client}
} }

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 {
@@ -41,3 +44,8 @@ func NewDevice(options DeviceOptions) (Device, error) {
return newSystemStackDevice(options) return newSystemStackDevice(options)
} }
} }
type NatDevice interface {
Device
CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error)
}

View File

@@ -0,0 +1,85 @@
package wireguard
import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
)
var _ Device = (*natDeviceWrapper)(nil)
type natDeviceWrapper struct {
Device
gVisorOutbound
packetOutbound chan *buf.Buffer
mapping *tun.NatMapping
writer *tun.NatWriter
buffer [][]byte
}
func NewNATDevice(upstream Device, ipRewrite bool) NatDevice {
wrapper := &natDeviceWrapper{
Device: upstream,
gVisorOutbound: newGVisorOutbound(),
packetOutbound: make(chan *buf.Buffer, 256),
mapping: tun.NewNatMapping(ipRewrite),
}
if ipRewrite {
wrapper.writer = tun.NewNatWriter(upstream.Inet4Address(), upstream.Inet6Address())
}
return wrapper
}
func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
for _, buffer := range bufs {
handled, err := d.mapping.WritePacket(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) (tun.DirectRouteDestination, error) {
session := tun.DirectRouteSession{
Source: metadata.Source.Addr,
Destination: metadata.Destination.Addr,
}
d.mapping.CreateSession(session, routeContext)
return &natDestinationWrapper{d, session}, nil
}
var _ tun.DirectRouteDestination = (*natDestinationWrapper)(nil)
type natDestinationWrapper struct {
device *natDeviceWrapper
session tun.DirectRouteSession
}
func (d *natDestinationWrapper) WritePacket(buffer *buf.Buffer) error {
if d.device.writer != nil {
d.device.writer.RewritePacket(buffer.Bytes())
}
d.device.packetOutbound <- buffer
return nil
}
func (d *natDestinationWrapper) Close() error {
d.device.mapping.DeleteSession(d.session)
return nil
}
func (d *natDestinationWrapper) Timeout() bool {
return false
}

View File

@@ -0,0 +1,48 @@
//go:build with_gvisor
package wireguard
import (
"github.com/sagernet/gvisor/pkg/tcpip/stack"
)
type gVisorOutbound struct {
outbound chan *stack.PacketBuffer
}
func newGVisorOutbound() gVisorOutbound {
return gVisorOutbound{
outbound: make(chan *stack.PacketBuffer, 256),
}
}
func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
select {
case packet := <-d.outbound:
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] = copyN
return 1, nil
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 *natDestinationWrapper) WritePacketBuffer(packetBuffer *stack.PacketBuffer) error {
println("read from wg")
if d.device.writer != nil {
d.device.writer.RewritePacketBuffer(packetBuffer)
}
d.device.outbound <- packetBuffer
return nil
}

View File

@@ -0,0 +1,20 @@
//go:build !with_gvisor
package wireguard
type gVisorOutbound struct{}
func newGVisorOutbound() gVisorOutbound {
return gVisorOutbound{}
}
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)
}

View File

@@ -5,6 +5,7 @@ package wireguard
import ( import (
"context" "context"
"net" "net"
"net/netip"
"os" "os"
"github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/buffer"
@@ -14,9 +15,12 @@ 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-tun" "github.com/sagernet/sing-tun"
"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,25 +28,30 @@ 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 stack *stack.Stack
mtu uint32 mtu uint32
events chan wgTun.Event events chan wgTun.Event
outbound chan *stack.PacketBuffer outbound chan *stack.PacketBuffer
done chan struct{} packetOutbound chan *buf.Buffer
dispatcher stack.NetworkDispatcher done chan struct{}
addr4 tcpip.Address dispatcher stack.NetworkDispatcher
addr6 tcpip.Address addr4 tcpip.Address
addr6 tcpip.Address
mapping *tun.NatMapping
writer *tun.NatWriter
} }
func newStackDevice(options DeviceOptions) (*stackDevice, error) { func newStackDevice(options DeviceOptions) (*stackDevice, error) {
tunDevice := &stackDevice{ tunDevice := &stackDevice{
mtu: options.MTU, mtu: options.MTU,
events: make(chan wgTun.Event, 1), events: make(chan wgTun.Event, 1),
outbound: make(chan *stack.PacketBuffer, 256), outbound: make(chan *stack.PacketBuffer, 256),
done: make(chan struct{}), packetOutbound: make(chan *buf.Buffer, 256),
done: make(chan struct{}),
mapping: tun.NewNatMapping(true),
} }
ipStack, err := tun.NewGVisorStack((*wireEndpoint)(tunDevice)) ipStack, err := tun.NewGVisorStack((*wireEndpoint)(tunDevice))
if err != nil { if err != nil {
@@ -68,10 +77,14 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String()) return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String())
} }
} }
tunDevice.writer = tun.NewNatWriter(tunDevice.Inet4Address(), tunDevice.Inet6Address())
tunDevice.stack = ipStack tunDevice.stack = ipStack
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)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
} }
return tunDevice, nil return tunDevice, nil
} }
@@ -130,6 +143,14 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
return udpConn, nil return udpConn, nil
} }
func (w *stackDevice) Inet4Address() netip.Addr {
return netip.AddrFrom4(w.addr4.As4())
}
func (w *stackDevice) Inet6Address() netip.Addr {
return netip.AddrFrom16(w.addr6.As16())
}
func (w *stackDevice) SetDevice(device *device.Device) { func (w *stackDevice) SetDevice(device *device.Device) {
} }
@@ -144,20 +165,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
} }
@@ -169,6 +194,14 @@ func (w *stackDevice) Write(bufs [][]byte, offset int) (count int, err error) {
if len(b) == 0 { if len(b) == 0 {
continue continue
} }
handled, err := w.mapping.WritePacket(b)
if handled {
if err != nil {
return count, err
}
count++
continue
}
var networkProtocol tcpip.NetworkProtocolNumber var networkProtocol tcpip.NetworkProtocolNumber
switch header.IPVersion(b) { switch header.IPVersion(b) {
case header.IPv4Version: case header.IPv4Version:
@@ -282,3 +315,157 @@ func (ep *wireEndpoint) Close() {
func (ep *wireEndpoint) SetOnCloseAction(f func()) { func (ep *wireEndpoint) SetOnCloseAction(f func()) {
} }
func (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
/* var wq waiter.Queue
ep, err := raw.NewEndpoint(w.stack, ipv4.ProtocolNumber, icmp.ProtocolNumber4, &wq)
if err != nil {
return nil, E.Cause(gonet.TranslateNetstackError(err), "create endpoint")
}
err = ep.Connect(tcpip.FullAddress{
NIC: tun.DefaultNIC,
Port: metadata.Destination.Port,
Addr: tun.AddressFromAddr(metadata.Destination.Addr),
})
if err != nil {
ep.Close()
return nil, E.Cause(gonet.TranslateNetstackError(err), "ICMP connect ", metadata.Destination)
}
fmt.Println("linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
destination := &endpointNatDestination{
ep: ep,
wq: &wq,
context: routeContext,
}
go destination.loopRead()
return destination, nil*/
session := tun.DirectRouteSession{
Source: metadata.Source.Addr,
Destination: metadata.Destination.Addr,
}
w.mapping.CreateSession(session, routeContext)
return &stackNatDestination{
device: w,
session: session,
}, nil
}
type stackNatDestination struct {
device *stackDevice
session tun.DirectRouteSession
}
func (d *stackNatDestination) WritePacket(buffer *buf.Buffer) error {
if d.device.writer != nil {
d.device.writer.RewritePacket(buffer.Bytes())
}
d.device.packetOutbound <- buffer
return nil
}
func (d *stackNatDestination) WritePacketBuffer(buffer *stack.PacketBuffer) error {
if d.device.writer != nil {
d.device.writer.RewritePacketBuffer(buffer)
}
d.device.outbound <- buffer
return nil
}
func (d *stackNatDestination) Close() error {
d.device.mapping.DeleteSession(d.session)
return nil
}
func (d *stackNatDestination) Timeout() bool {
return false
}
/*type endpointNatDestination struct {
ep tcpip.Endpoint
wq *waiter.Queue
networkProto tcpip.NetworkProtocolNumber
context tun.DirectRouteContext
done chan struct{}
}
func (d *endpointNatDestination) loopRead() {
for {
println("start read")
buffer, err := commonRead(d.ep, d.wq, d.done)
if err != nil {
log.Error(err)
return
}
println("done read")
ipHdr := header.IPv4(buffer.Bytes())
if ipHdr.TransportProtocol() != header.ICMPv4ProtocolNumber {
buffer.Release()
continue
}
icmpHdr := header.ICMPv4(ipHdr.Payload())
if icmpHdr.Type() != header.ICMPv4EchoReply {
buffer.Release()
continue
}
fmt.Println("read echo reply")
_ = d.context.WritePacket(ipHdr)
buffer.Release()
}
}
func commonRead(ep tcpip.Endpoint, wq *waiter.Queue, done chan struct{}) (*buf.Buffer, error) {
buffer := buf.NewPacket()
result, err := ep.Read(buffer, tcpip.ReadOptions{})
if err != nil {
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.ReadableEvents)
wq.EventRegister(&waitEntry)
defer wq.EventUnregister(&waitEntry)
for {
result, err = ep.Read(buffer, tcpip.ReadOptions{})
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
break
}
select {
case <-notifyCh:
case <-done:
buffer.Release()
return nil, context.DeadlineExceeded
}
}
}
return nil, gonet.TranslateNetstackError(err)
}
buffer.Truncate(result.Count)
return buffer, nil
}
func (d *endpointNatDestination) WritePacket(buffer *buf.Buffer) error {
_, err := d.ep.Write(buffer, tcpip.WriteOptions{})
if err != nil {
return gonet.TranslateNetstackError(err)
}
return nil
}
func (d *endpointNatDestination) WritePacketBuffer(buffer *stack.PacketBuffer) error {
data := buffer.ToView().AsSlice()
println("write echo request buffer :" + fmt.Sprint(data))
_, err := d.ep.Write(bytes.NewReader(data), tcpip.WriteOptions{})
if err != nil {
log.Error(err)
return gonet.TranslateNetstackError(err)
}
return nil
}
func (d *endpointNatDestination) Close() error {
d.ep.Abort()
close(d.done)
return nil
}
func (d *endpointNatDestination) Timeout() bool {
return false
}
*/

View File

@@ -28,16 +28,36 @@ type systemDevice struct {
batchDevice tun.LinuxTUN batchDevice tun.LinuxTUN
events chan wgTun.Event events chan wgTun.Event
closeOnce sync.Once closeOnce sync.Once
addr4 netip.Addr
addr6 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),
addr4: inet4Address,
addr6: 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.addr4
}
func (w *systemDevice) Inet6Address() netip.Addr {
return w.addr6
}
func (w *systemDevice) SetDevice(device *device.Device) { func (w *systemDevice) SetDevice(device *device.Device) {
} }

View File

@@ -10,6 +10,8 @@ import (
"os" "os"
"strings" "strings"
"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"
@@ -29,6 +31,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
pauseManager pause.Manager pauseManager pause.Manager
pauseCallback *list.Element[pause.Callback] pauseCallback *list.Element[pause.Callback]
@@ -111,12 +114,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(tunDevice, true)
}
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
} }
@@ -176,7 +184,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 {
@@ -194,6 +208,20 @@ func (e *Endpoint) Start(resolve bool) error {
return nil return nil
} }
func (e *Endpoint) Close() error {
if e.device != nil {
e.device.Close()
}
if e.pauseCallback != nil {
e.pauseManager.UnregisterCallback(e.pauseCallback)
}
return nil
}
func (e *Endpoint) BindUpdate() error {
return e.device.BindUpdate()
}
func (e *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { func (e *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if !destination.Addr.IsValid() { if !destination.Addr.IsValid() {
return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination") return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination")
@@ -208,18 +236,11 @@ func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return e.tunDevice.ListenPacket(ctx, destination) return e.tunDevice.ListenPacket(ctx, destination)
} }
func (e *Endpoint) BindUpdate() error { func (e *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
return e.device.BindUpdate() if e.natDevice == nil {
} return nil, os.ErrInvalid
func (e *Endpoint) Close() error {
if e.device != nil {
e.device.Close()
} }
if e.pauseCallback != nil { return e.natDevice.CreateDestination(metadata, routeContext)
e.pauseManager.UnregisterCallback(e.pauseCallback)
}
return nil
} }
func (e *Endpoint) onPauseUpdated(event int) { func (e *Endpoint) onPauseUpdated(event int) {