Compare commits

...

37 Commits

Author SHA1 Message Date
世界
8d044232af Update documentation 2022-09-09 15:42:33 +08:00
世界
aa7e85caa7 Update dependencies
Add half close for smux
Update gVisor to 20220905.0
2022-09-09 14:44:18 +08:00
zakuwaki
46a8f24400 Optional proxyproto header 2022-09-09 14:44:18 +08:00
世界
87bc292296 Add comment filter for config 2022-09-09 14:44:18 +08:00
世界
ac539ace70 Add system tun stack 2022-09-09 14:44:18 +08:00
世界
a15b13978f Set default tun mtu to 9000 like clash
IDK why, maybe faster in a local speed test?
2022-09-09 14:44:18 +08:00
世界
0c975db0a6 Set udp dontfrag by default 2022-09-09 14:44:18 +08:00
世界
cb4fea0240 Refactor wireguard & add tun support 2022-09-09 14:44:18 +08:00
世界
8e7957d440 Add support for use with android VPNService 2022-09-09 14:44:18 +08:00
世界
f7bed32c6f Bump version 2022-09-09 14:43:42 +08:00
世界
ef7f2d82c0 Fix match 4in6 address in ip_cidr 2022-09-09 14:07:02 +08:00
世界
7aa97a332e Fix documentation 2022-09-09 13:54:02 +08:00
世界
7c30dde96b Minor fixes 2022-09-08 18:33:59 +08:00
GyDi
9cef2a0a8f Fix clashapi log level format error 2022-09-08 18:04:06 +08:00
世界
f376683fc3 Update documentation 2022-09-07 23:10:36 +08:00
世界
4b61d6e875 Fix hysteria stream error 2022-09-07 19:16:20 +08:00
世界
7d83e350fd Refine test 2022-09-07 19:16:20 +08:00
世界
500ba69548 Fix processing vmess termination signal 2022-09-07 19:16:20 +08:00
世界
9a422549b1 Fix json format error message 2022-09-07 13:23:26 +08:00
世界
3b48fa455e Fix naive inbound temporary 2022-09-07 12:30:54 +08:00
zakuwaki
ef013e0639 Suppress accept proxyproto failed #65 2022-09-06 23:16:31 +08:00
世界
8f8437a88d Fix wireguard reconnect 2022-09-06 00:11:43 +08:00
世界
1b091c9b07 Update documentation 2022-09-04 13:15:10 +08:00
世界
4801b6f057 Fix DNS routing 2022-09-04 12:49:38 +08:00
世界
9078bc2de5 Fix write trojan udp 2022-09-03 16:58:55 +08:00
世界
b69464dfe9 Update documentation for dial fields 2022-09-03 13:02:41 +08:00
世界
62fa48293a Merge dialer options 2022-09-03 12:55:10 +08:00
世界
b206d0889b Fix dial parallel in direct outbound 2022-09-03 12:01:48 +08:00
世界
ee691d81bf Fix write zero 2022-09-03 09:25:30 +08:00
void aire()
56876a67cc Fix documentation typo (#60) 2022-09-02 19:04:03 +08:00
世界
4a0df713aa Add ws compatibility test 2022-09-01 20:32:47 +08:00
世界
ef801cbfbe Fix server install script 2022-09-01 20:32:47 +08:00
世界
9378fc88d2 Add with_wireguard to default server tag 2022-09-01 20:16:20 +08:00
世界
f46bfcc3d8 Move unstable branch to dev-next 2022-08-31 23:45:42 +08:00
0x7d274284
ccdb238843 Fix documentation typo (#57) 2022-08-31 23:42:36 +08:00
世界
f1f61b4e2b Fix install documentation 2022-08-31 23:37:30 +08:00
世界
a44cb745d9 Fix write log timestamp 2022-08-31 23:35:43 +08:00
104 changed files with 1763 additions and 793 deletions

View File

@@ -1,10 +1,5 @@
#!/usr/bin/env bash
PROJECTS=$(dirname "$0")/../..
go get -x github.com/sagernet/sing@$(git -C $PROJECTS/sing rev-parse HEAD)
go get -x github.com/sagernet/sing-dns@$(git -C $PROJECTS/sing-dns rev-parse HEAD)
go get -x github.com/sagernet/sing-tun@$(git -C $PROJECTS/sing-tun rev-parse HEAD)
go get -x github.com/sagernet/sing-shadowsocks@$(git -C $PROJECTS/sing-shadowsocks rev-parse HEAD)
go get -x github.com/sagernet/sing-vmess@$(git -C $PROJECTS/sing-vmess rev-parse HEAD)
go get -x github.com/sagernet/$1@$(git -C $PROJECTS/$1 rev-parse HEAD)
go mod tidy

View File

@@ -4,6 +4,7 @@ on:
push:
branches:
- dev
- dev-next
paths-ignore:
- '**.md'
- '.github/**'
@@ -11,6 +12,7 @@ on:
pull_request:
branches:
- dev
- dev-next
jobs:
build:

View File

@@ -2,7 +2,7 @@ name: Generate Documents
on:
push:
branches:
- main
- dev
paths:
- docs/**
- .github/workflows/mkdocs.yml

View File

@@ -7,8 +7,8 @@ ENV GOPROXY ${GOPROXY}
ENV CGO_ENABLED=0
RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse HEAD) \
&& go build -v -trimpath -tags 'with_quic,with_acme,with_wireguard,with_clash_api' \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& go build -v -trimpath -tags 'no_gvisor,with_quic,with_wireguard,with_acme' \
-o /go/bin/sing-box \
-ldflags "-X github.com/sagernet/sing-box/constant.Commit=${COMMIT} -w -s -buildid=" \
./cmd/sing-box

View File

@@ -38,7 +38,7 @@ func format() error {
return E.Cause(err, "read config")
}
var options option.Options
err = json.Unmarshal(configContent, &options)
err = options.UnmarshalJSON(configContent)
if err != nil {
return E.Cause(err, "decode config")
}

View File

@@ -9,7 +9,6 @@ import (
"syscall"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
@@ -46,7 +45,7 @@ func readConfig() (option.Options, error) {
return option.Options{}, E.Cause(err, "read config")
}
var options option.Options
err = json.Unmarshal(configContent, &options)
err = options.UnmarshalJSON(configContent)
if err != nil {
return option.Options{}, E.Cause(err, "decode config")
}

View File

@@ -1,6 +1,13 @@
package baderror
import "strings"
import (
"context"
"io"
"net"
"strings"
E "github.com/sagernet/sing/common/exceptions"
)
func Contains(err error, msgList ...string) bool {
for _, msg := range msgList {
@@ -10,3 +17,46 @@ func Contains(err error, msgList ...string) bool {
}
return false
}
func WrapH2(err error) error {
if err == nil {
return nil
}
err = E.Unwrap(err)
if err == io.ErrUnexpectedEOF {
return io.EOF
}
if Contains(err, "client disconnected", "body closed by handler") {
return net.ErrClosed
}
return err
}
func WrapGRPC(err error) error {
// grpc uses stupid internal error types
if err == nil {
return nil
}
if Contains(err, "EOF") {
return io.EOF
}
if Contains(err, "Canceled") {
return context.Canceled
}
if Contains(err,
"the client connection is closing",
"server closed the stream without sending trailers") {
return net.ErrClosed
}
return err
}
func WrapQUIC(err error) error {
if err == nil {
return nil
}
if Contains(err, "canceled with error code 0") {
return net.ErrClosed
}
return err
}

View File

@@ -1,26 +0,0 @@
package baderror
import (
"context"
"io"
"net"
)
func WrapGRPC(err error) error {
// grpc uses stupid internal error types
if err == nil {
return nil
}
if Contains(err, "EOF") {
return io.EOF
}
if Contains(err, "Canceled") {
return context.Canceled
}
if Contains(err,
"the client connection is closing",
"server closed the stream without sending trailers") {
return net.ErrClosed
}
return err
}

View File

@@ -1,22 +0,0 @@
package baderror
import (
"io"
"net"
E "github.com/sagernet/sing/common/exceptions"
)
func WrapH2(err error) error {
if err == nil {
return nil
}
err = E.Unwrap(err)
if err == io.ErrUnexpectedEOF {
return io.EOF
}
if Contains(err, "client disconnected", "body closed by handler") {
return net.ErrClosed
}
return err
}

View File

@@ -112,6 +112,10 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
if options.TCPFastOpen {
warnTFOOnUnsupportedPlatform.Check()
}
if !options.UDPFragment {
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
}
var bindUDPAddr string
udpDialer := dialer
var bindAddress netip.Addr

View File

@@ -10,15 +10,12 @@ import (
)
func New(router adapter.Router, options option.DialerOptions) N.Dialer {
var dialer N.Dialer
if options.Detour == "" {
return NewDefault(router, options)
dialer = NewDefault(router, options)
} else {
return NewDetour(router, options.Detour)
dialer = NewDetour(router, options.Detour)
}
}
func NewOutbound(router adapter.Router, options option.OutboundDialerOptions) N.Dialer {
dialer := New(router, options.DialerOptions)
domainStrategy := dns.DomainStrategy(options.DomainStrategy)
if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))

128
common/json/comment.go Normal file
View File

@@ -0,0 +1,128 @@
package json
import (
"bufio"
"io"
)
// kanged from v2ray
type commentFilterState = byte
const (
commentFilterStateContent commentFilterState = iota
commentFilterStateEscape
commentFilterStateDoubleQuote
commentFilterStateDoubleQuoteEscape
commentFilterStateSingleQuote
commentFilterStateSingleQuoteEscape
commentFilterStateComment
commentFilterStateSlash
commentFilterStateMultilineComment
commentFilterStateMultilineCommentStar
)
type CommentFilter struct {
br *bufio.Reader
state commentFilterState
}
func NewCommentFilter(reader io.Reader) io.Reader {
return &CommentFilter{br: bufio.NewReader(reader)}
}
func (v *CommentFilter) Read(b []byte) (int, error) {
p := b[:0]
for len(p) < len(b)-2 {
x, err := v.br.ReadByte()
if err != nil {
if len(p) == 0 {
return 0, err
}
return len(p), nil
}
switch v.state {
case commentFilterStateContent:
switch x {
case '"':
v.state = commentFilterStateDoubleQuote
p = append(p, x)
case '\'':
v.state = commentFilterStateSingleQuote
p = append(p, x)
case '\\':
v.state = commentFilterStateEscape
case '#':
v.state = commentFilterStateComment
case '/':
v.state = commentFilterStateSlash
default:
p = append(p, x)
}
case commentFilterStateEscape:
p = append(p, '\\', x)
v.state = commentFilterStateContent
case commentFilterStateDoubleQuote:
switch x {
case '"':
v.state = commentFilterStateContent
p = append(p, x)
case '\\':
v.state = commentFilterStateDoubleQuoteEscape
default:
p = append(p, x)
}
case commentFilterStateDoubleQuoteEscape:
p = append(p, '\\', x)
v.state = commentFilterStateDoubleQuote
case commentFilterStateSingleQuote:
switch x {
case '\'':
v.state = commentFilterStateContent
p = append(p, x)
case '\\':
v.state = commentFilterStateSingleQuoteEscape
default:
p = append(p, x)
}
case commentFilterStateSingleQuoteEscape:
p = append(p, '\\', x)
v.state = commentFilterStateSingleQuote
case commentFilterStateComment:
if x == '\n' {
v.state = commentFilterStateContent
p = append(p, '\n')
}
case commentFilterStateSlash:
switch x {
case '/':
v.state = commentFilterStateComment
case '*':
v.state = commentFilterStateMultilineComment
default:
p = append(p, '/', x)
}
case commentFilterStateMultilineComment:
switch x {
case '*':
v.state = commentFilterStateMultilineCommentStar
case '\n':
p = append(p, '\n')
}
case commentFilterStateMultilineCommentStar:
switch x {
case '/':
v.state = commentFilterStateContent
case '*':
// Stay
case '\n':
p = append(p, '\n')
default:
v.state = commentFilterStateMultilineComment
}
default:
panic("Unknown state.")
}
}
return len(p), nil
}

View File

@@ -13,6 +13,7 @@ import (
type Listener struct {
net.Listener
AcceptNoHeader bool
}
func (l *Listener) Accept() (net.Conn, error) {
@@ -22,7 +23,7 @@ func (l *Listener) Accept() (net.Conn, error) {
}
bufReader := std_bufio.NewReader(conn)
header, err := proxyproto.Read(bufReader)
if err != nil {
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
return nil, err
}
if bufReader.Buffered() > 0 {
@@ -33,8 +34,11 @@ func (l *Listener) Accept() (net.Conn, error) {
}
conn = bufio.NewCachedConn(conn, cache)
}
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
Source: M.SocksaddrFromNet(header.SourceAddr),
Destination: M.SocksaddrFromNet(header.DestinationAddr),
}}, nil
if header != nil {
return &bufio.AddrConn{Conn: conn, Metadata: M.Metadata{
Source: M.SocksaddrFromNet(header.SourceAddr),
Destination: M.SocksaddrFromNet(header.DestinationAddr),
}}, nil
}
return conn, nil
}

View File

@@ -20,7 +20,7 @@ type systemProxy struct {
isMixed bool
}
func (p *systemProxy) update() error {
func (p *systemProxy) update(event int) error {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
if p.interfaceName == newInterfaceName {
return nil
@@ -88,7 +88,7 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er
port: port,
isMixed: isMixed,
}
err := proxy.update()
err := proxy.update(tun.EventInterfaceUpdate)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,6 @@
package constant
var (
Version = "1.0-beta3"
Version = "1.1-beta1"
Commit = ""
)

View File

@@ -1,9 +1,78 @@
#### 1.1-beta1
* Add support for use with android VPNService **1**
* Add tun support for WireGuard outbound **2**
* Add system tun stack **3**
* Add comment filter for config **4**
* Add option for allow optional proxy protocol header
* Add half close for smux
* Set UDP DF by default **5**
* Set default tun mtu to 9000
* Update gVisor to 20220905.0
*1*:
In previous versions, Android VPN would not work with tun enabled.
The usage of tun over VPN and VPN over tun is now supported, see [Tun Inbound](/configuration/inbound/tun#auto_route).
*2*:
In previous releases, WireGuard outbound support was backed by the lower performance gVisor virtual interface.
It achieves the same performance as wireguard-go by providing automatic system interface support.
*3*:
It does not depend on gVisor and has better performance in some cases.
It is less compatible and may not be available in some environments.
*4*:
Annotated json configuration files are now supported.
*5*:
UDP fragmentation is now blocked by default.
Including shadowsocks-libev, shadowsocks-rust and quic-go all disable segmentation by default.
See [Dial Fields](/configuration/shared/dial#udp_fragment)
and [Listen Fields](/configuration/shared/listen#udp_fragment).
#### 1.0.1
* Fix match 4in6 address in ip_cidr
* Fix clash api log level format error
* Fix clash api unknown proxy type
#### 1.0
* Fix wireguard reconnect
* Fix naive inbound
* Fix json format error message
* Fix processing vmess termination signal
* Fix hysteria stream error
* Fix listener close when proxyproto failed
#### 1.0-rc1
* Fix write log timestamp
* Fix write zero
* Fix dial parallel in direct outbound
* Fix write trojan udp
* Fix DNS routing
* Add attribute support for geosite
* Update documentation for [Dial Fields](/configuration/shared/dial)
#### 1.0-beta3
* Add [chained inbound](/configuration/shared/listen#detour) support
* Add process_path rule item
* Add macOS redirect support
* Add ShadowTLS [Inbound](/configuration/inbound/shadowtls), [Outbound](/configuration/outbound/shadowtls) and [Examples](/examples/shadowtls)
* Add ShadowTLS [Inbound](/configuration/inbound/shadowtls), [Outbound](/configuration/outbound/shadowtls)
and [Examples](/examples/shadowtls)
* Fix search android package in non-owner users
* Fix socksaddr type condition
* Fix smux session status

View File

@@ -12,7 +12,7 @@
"interface_name": "tun0",
"inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/128",
"mtu": 1500,
"mtu": 9000,
"auto_route": true,
"strict_route": true,
"endpoint_independent_nat": false,
@@ -80,6 +80,10 @@ Set the default route to the Tun.
To avoid traffic loopback, set `route.auto_detect_interface` or `route.default_interface` or `outbound.bind_interface`
!!! note "Use with Android VPN"
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
#### strict_route
Enforce strict routing rules in Linux when `auto_route` is enabled:
@@ -92,6 +96,10 @@ not be accessible by others.
#### endpoint_independent_nat
!!! info ""
This item is only available on the gvisor stack, other stacks are endpoint-independent NAT by default.
Enable endpoint-independent NAT.
Performance may degrade slightly, so it is not recommended to enable on when it is not needed.
@@ -104,10 +112,11 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes).
TCP/IP stack.
| Stack | Upstream | Status |
|------------------|-----------------------------------------------------------------------|-------------------|
| gVisor (default) | [google/gvisor](https://github.com/google/gvisor) | recommended |
| LWIP | [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
| Stack | Description | Status |
|------------------|--------------------------------------------------------------------------------|-------------------|
| gVisor (default) | Based on [google/gvisor](https://github.com/google/gvisor) | recommended |
| system | Less compatibility and sometimes better performance. | recommended |
| LWIP | Based on [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
!!! warning ""

View File

@@ -12,7 +12,7 @@
"interface_name": "tun0",
"inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/128",
"mtu": 1500,
"mtu": 9000,
"auto_route": true,
"strict_route": true,
"endpoint_independent_nat": false,
@@ -80,6 +80,10 @@ tun 接口的 IPv6 前缀。
为避免流量环回,请设置 `route.auto_detect_interface``route.default_interface``outbound.bind_interface`
!!! note "与 Android VPN 一起使用"
VPN 默认优先于 tun。要使 tun 经过 VPN启用 `route.override_android_vpn`
#### strict_route
在 Linux 中启用 `auto_route` 时执行严格的路由规则。
@@ -103,10 +107,11 @@ UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)。
TCP/IP 栈。
| 栈 | 上游 | 状态 |
|------------------|-----------------------------------------------------------------------|-------|
| gVisor (default) | [google/gvisor](https://github.com/google/gvisor) | 推荐 |
| LWIP | [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 |
| 栈 | 描述 | 状态 |
|------------------|--------------------------------------------------------------------------|-------|
| gVisor (default) | 基于 [google/gvisor](https://github.com/google/gvisor) | 推荐 |
| system | 兼容性较差,有时性能更好。 | 推荐 |
| LWIP | 基于 [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 |
!!! warning ""

View File

@@ -7,8 +7,9 @@
"server": "127.0.0.1",
"server_port": 1080,
"system_interface": false,
"interface_name": "wg0",
"local_address": [
"10.0.0.1",
"10.0.0.2/32"
],
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
@@ -39,11 +40,21 @@ The server address.
The server port.
#### system_interface
Use system tun support.
Requires privileges and cannot conflict with system interfaces.
#### interface_name
Custom device name when `system_interface` enabled.
#### local_address
==Required==
List of IP (v4 or v6) addresses (optionally with CIDR masks) to be assigned to the interface.
List of IP (v4 or v6) address prefixes to be assigned to the interface.
#### private_key

View File

@@ -7,8 +7,9 @@
"server": "127.0.0.1",
"server_port": 1080,
"system_interface": false,
"interface_name": "wg0",
"local_address": [
"10.0.0.1",
"10.0.0.2/32"
],
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
@@ -39,13 +40,23 @@
服务器端口。
#### system_interface
使用系统 tun 支持。
需要特权且不能与系统接口冲突。
#### interface_name
启用 `system_interface` 时的自定义设备名称。
#### local_address
==必填==
接口的 IPv4/IPv6 地址或地址段的列表您。
要分配给接口的 IPv4 或 v6地址列表(可以选择带有 CIDR 掩码)
要分配给接口的 IPv4 或 v6地址列表。
#### private_key

View File

@@ -10,6 +10,7 @@
"rules": [],
"final": "",
"auto_detect_interface": false,
"override_android_vpn": false,
"default_interface": "en0",
"default_mark": 233
}
@@ -34,17 +35,25 @@ Default outbound tag. the first outbound will be used if empty.
Only supported on Linux, Windows and macOS.
Bind outbound connections to the default NIC by default to prevent routing loops under Tun.
Bind outbound connections to the default NIC by default to prevent routing loops under tun.
Takes no effect if `outbound.bind_interface` is set.
#### override_android_vpn
!!! error ""
Only supported on Android.
Accept Android VPN as upstream NIC when `auto_detect_interface` enabled.
#### default_interface
!!! error ""
Only supported on Linux, Windows and macOS.
Bind outbound connections to the specified NIC by default to prevent routing loops under Tun.
Bind outbound connections to the specified NIC by default to prevent routing loops under tun.
Takes no effect if `auto_detect_interface` is set.

View File

@@ -10,6 +10,7 @@
"rules": [],
"final": "",
"auto_detect_interface": false,
"override_android_vpn": false,
"default_interface": "en0",
"default_mark": 233
}
@@ -34,17 +35,25 @@
仅支持 Linux、Windows 和 macOS。
默认将出站连接绑定到默认网卡,以防止在 Tun 下出现路由环路。
默认将出站连接绑定到默认网卡,以防止在 tun 下出现路由环路。
如果设置了 `outbound.bind_interface` 设置,则不生效。
#### override_android_vpn
!!! error ""
仅支持 Android。
启用 `auto_detect_interface` 时接受 Android VPN 作为上游网卡。
#### default_interface
!!! error ""
仅支持 Linux、Windows 和 macOS。
默认将出站连接绑定到指定网卡,以防止在 Tun 下出现路由环路。
默认将出站连接绑定到指定网卡,以防止在 tun 下出现路由环路。
如果设置了 `auto_detect_interface` 设置,则不生效。

View File

@@ -9,6 +9,7 @@
"reuse_addr": false,
"connect_timeout": "5s",
"tcp_fast_open": false,
"udp_fragment": false,
"domain_strategy": "prefer_ipv6",
"fallback_delay": "300ms"
}
@@ -16,12 +17,14 @@
### Fields
| Field | Available Context |
|---------------------------------------------------------------------------------------------------------------------|-------------------|
| `bind_interface` /`bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` not set |
#### detour
The tag of the upstream outbound.
Other dial fields will be ignored when enabled.
#### bind_interface
The network interface to bind to.
@@ -42,6 +45,14 @@ Set netfilter routing mark.
Reuse listener address.
#### tcp_fast_open
Enable TCP Fast Open.
#### udp_fragment
Enable UDP fragmentation.
#### connect_timeout
Connect timeout, in golang's Duration format.
@@ -57,13 +68,16 @@ One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
If set, the requested domain name will be resolved to IP before connect.
`dns.strategy` will be used if empty.
| Outbound | Effected domains | Fallback Value |
|----------|--------------------------|-------------------------------------------|
| `direct` | Domain in request | Take `inbound.domain_strategy` if not set |
| others | Domain in server address | / |
#### fallback_delay
The length of time to wait before spawning a RFC 6555 Fast Fallback connection.
That is, is the amount of time to wait for IPv6 to succeed before assuming
that IPv6 is misconfigured and falling back to IPv4 if `prefer_ipv4` is set.
That is, is the amount of time to wait for connection to succeed before assuming
that IPv4/IPv6 is misconfigured and falling back to other type of addresses.
If zero, a default delay of 300ms is used.
Only take effect when `domain_strategy` is `prefer_ipv4` or `prefer_ipv6`.
Only take effect when `domain_strategy` is set.

View File

@@ -9,6 +9,7 @@
"reuse_addr": false,
"connect_timeout": "5s",
"tcp_fast_open": false,
"udp_fragment": false,
"domain_strategy": "prefer_ipv6",
"fallback_delay": "300ms"
}
@@ -16,6 +17,11 @@
### 字段
| 字段 | 可用上下文 |
|---------------------------------------------------------------------------------------------------------------------|--------------|
| `bind_interface` /`bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open`/ `udp_fragment` /`connect_timeout` | `detour` 未设置 |
#### detour
上游出站的标签。
@@ -42,6 +48,14 @@
重用监听地址。
#### tcp_fast_open
启用 TCP Fast Open。
#### udp_fragment
启用 UDP 分段。
#### connect_timeout
连接超时,采用 golang 的 Duration 格式。

View File

@@ -5,24 +5,27 @@
"listen": "::",
"listen_port": 5353,
"tcp_fast_open": false,
"udp_fragment": false,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"udp_timeout": 300,
"proxy_protocol": false,
"proxy_protocol_accept_no_header": false,
"detour": "another-in"
}
```
### Fields
| Field | Available Context |
|------------------|-------------------------------------------------------------------|
| `listen` | Needs to listen on TCP or UDP. |
| `listen_port` | Needs to listen on TCP or UDP. |
| `tcp_fast_open` | Needs to listen on TCP. |
| `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
| `proxy_protocol` | Needs to listen on TCP. |
| Field | Available Context |
|-----------------------------------|-------------------------------------------------------------------|
| `listen` | Needs to listen on TCP or UDP. |
| `listen_port` | Needs to listen on TCP or UDP. |
| `tcp_fast_open` | Needs to listen on TCP. |
| `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
| `proxy_protocol` | Needs to listen on TCP. |
| `proxy_protocol_accept_no_header` | When `proxy_protocol` enabled |
#### listen
@@ -36,7 +39,11 @@ Listen port.
#### tcp_fast_open
Enable tcp fast open for listener.
Enable TCP Fast Open.
#### udp_fragment
Enable UDP fragmentation.
#### sniff
@@ -66,6 +73,10 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes).
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
#### proxy_protocol_accept_no_header
Accept connections without Proxy Protocol header.
#### detour
If set, connections will be forwarded to the specified inbound.

View File

@@ -5,21 +5,26 @@
"listen": "::",
"listen_port": 5353,
"tcp_fast_open": false,
"udp_fragment": false,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"udp_timeout": 300,
"proxy_protocol": false,
"proxy_protocol_accept_no_header": false,
"detour": "another-in"
}
```
| 字段 | 可用上下文 |
|------------------|-------------------------------------|
| `listen` | 需要监听 TCP 或 UDP。 |
| `listen_port` | 需要监听 TCP 或 UDP。 |
| `tcp_fast_open` | 需要监听 TCP |
| `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 |
| `proxy_protocol` | 需要监听 TCP。 |
| 字段 | 可用上下文 |
|-----------------------------------|-------------------------------------|
| `listen` | 需要监听 TCP 或 UDP。 |
| `listen_port` | 需要监听 TCP 或 UDP。 |
| `tcp_fast_open` | 需要监听 TCP。 |
| `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 |
| `proxy_protocol` | 需要监听 TCP。 |
| `proxy_protocol_accept_no_header` | `proxy_protocol` 启用时 |
### 字段
@@ -35,7 +40,11 @@
#### tcp_fast_open
为监听器启用 TCP 快速打开
启用 TCP Fast Open
#### udp_fragment
启用 UDP 分段。
#### sniff
@@ -65,6 +74,10 @@ UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)。
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
#### proxy_protocol_accept_no_header
接受没有代理协议标头的连接。
#### detour
如果设置,连接将被转发到指定的入站。

View File

@@ -35,7 +35,7 @@
"password": "8JCsPssfgS8tiRwiMlhARg==",
"detour": "shadowtls-out",
"multiplex": {
"enabled": 1,
"enabled": true,
"max_connections": 4,
"min_streams": 4
}
@@ -52,4 +52,4 @@
}
]
}
```
```

View File

@@ -11,7 +11,7 @@ the public internet.
##### on Linux
`auto-route` cannot automatically hijack DNS requests with `systemd-resoled` enabled, you can switch to NetworkManager.
`auto-route` cannot automatically hijack DNS requests with `systemd-resolved` enabled, you can switch to NetworkManager.
#### System proxy

View File

@@ -10,7 +10,7 @@
##### Linux
`auto-route` 无法自动劫持 DNS 请求如果 `systemd-resoled` 开启, 您可以切换到 NetworkManager.
`auto-route` 无法自动劫持 DNS 请求如果 `systemd-resolved` 开启, 您可以切换到 NetworkManager.
#### 系统代理

View File

@@ -36,7 +36,7 @@ func getConfigs(logFactory log.Factory) func(w http.ResponseWriter, r *http.Requ
logLevel := logFactory.Level()
if logLevel == log.LevelTrace {
logLevel = log.LevelDebug
} else if logLevel > log.LevelError {
} else if logLevel < log.LevelError {
logLevel = log.LevelError
}
render.JSON(w, r, &configSchema{

View File

@@ -75,11 +75,13 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
clashType = "Shadowsocks"
case C.TypeVMess:
clashType = "Vmess"
case C.TypeTrojan:
clashType = "Trojan"
case C.TypeSelector:
clashType = "Selector"
isGroup = true
default:
clashType = "Unknown"
clashType = "Socks"
}
info.Put("type", clashType)
info.Put("name", detour.Tag())

20
go.mod
View File

@@ -20,25 +20,27 @@ require (
github.com/pires/go-proxyproto v0.6.2
github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
github.com/sagernet/sing v0.0.0-20220829115648-e09c9f3fc812
github.com/sagernet/sing v0.0.0-20220905164441-f3d346256d4a
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
github.com/sagernet/sing-tun v0.0.0-20220828031750-185b6c880a83
github.com/sagernet/sing-vmess v0.0.0-20220829020559-33915075430c
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
github.com/sagernet/sing-tun v0.0.0-20220909054347-575ac2941790
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f
github.com/sagernet/smux v0.0.0-20220907034654-1acb8471c15a
github.com/spf13/cobra v1.5.0
github.com/stretchr/testify v1.8.0
go.uber.org/atomic v1.10.0
go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7
golang.org/x/sys v0.0.0-20220908164124-27713097b956
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0
google.golang.org/grpc v1.49.0
google.golang.org/protobuf v1.28.1
gvisor.dev/gvisor v0.0.0-20220819163037-ba6e795b139a
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
)
//replace github.com/sagernet/sing => ../sing
require (
github.com/ajg/form v1.5.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -57,7 +59,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/netlink v0.0.0-20220826133217-3fb4ff92ea17 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go.uber.org/multierr v1.6.0 // indirect

36
go.sum
View File

@@ -129,24 +129,24 @@ github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a h1:SE3Xn4GOQ+kx
github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a/go.mod h1:Q+ZXyesnkjV5B70B1ixk65ecKrlJ2jz0atv3fPKsVVo=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
github.com/sagernet/netlink v0.0.0-20220826133217-3fb4ff92ea17 h1:zvm6IrIgo4rLizJCHkH+SWUBhm+jyjjozX031QdAlj8=
github.com/sagernet/netlink v0.0.0-20220826133217-3fb4ff92ea17/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTYRwpwvEm3nc4eRdxk6vtRbouLVZAzk=
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.0.0-20220829115648-e09c9f3fc812 h1:z5AqoJcMy+MaD9pstLt08c95t2Txlzvp5pk4lqXBQPc=
github.com/sagernet/sing v0.0.0-20220829115648-e09c9f3fc812/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
github.com/sagernet/sing v0.0.0-20220905164441-f3d346256d4a h1:Bqt+eYP7vJocAgAVAXC0B0ZN0uMr6g6exAoF3Ado2pg=
github.com/sagernet/sing v0.0.0-20220905164441-f3d346256d4a/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666 h1:XUTocA/Ek0dFxUX+xJCWMPPFZCn2GC/uLrBjTSr1vHY=
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666/go.mod h1:eDyH7AJmqBGjZQdQmpZIzlbTREudZuWDExMuGKgjRVM=
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
github.com/sagernet/sing-tun v0.0.0-20220828031750-185b6c880a83 h1:SoWiHYuOCVedqA7T/CJSZUUrcPGKQb2wFKEq8DphiAI=
github.com/sagernet/sing-tun v0.0.0-20220828031750-185b6c880a83/go.mod h1:76r07HS1WRcEI4mE9pFsohfTBUt1j/G9Avz6DaOP3VU=
github.com/sagernet/sing-vmess v0.0.0-20220829020559-33915075430c h1:92Gn78/z/t6CkzZ4XWG/uPiCxhUmjPULFEHFMDY6K8k=
github.com/sagernet/sing-vmess v0.0.0-20220829020559-33915075430c/go.mod h1:82O6gzbxLha/W/jxSVQbsqf2lVdRTjMIgyLug0lpJps=
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
github.com/sagernet/sing-tun v0.0.0-20220909054347-575ac2941790 h1:SnIhaHWVk3tTTx/zFEor9zRItcbzTYMoC8F22FAZ/Iw=
github.com/sagernet/sing-tun v0.0.0-20220909054347-575ac2941790/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f h1:6l9aXZqAl1JqXJWi89KHpWnM/moQUPGG+XiwMc+yD0A=
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
github.com/sagernet/smux v0.0.0-20220907034654-1acb8471c15a h1:GCNwsN8MEckpjGJjK3qjQBQ9qHsoXB9B/KHUWBvE1V4=
github.com/sagernet/smux v0.0.0-20220907034654-1acb8471c15a/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -212,8 +212,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7 h1:1WGATo9HAhkWMbfyuVU0tEFP88OIkUvwaHFveQPvzCQ=
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -244,8 +244,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -276,8 +276,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b h1:qgrKnOfe1zyURRNdmDlGbN32i38Zjmw0B1+TMdHcOvg=
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b/go.mod h1:6y4CqPAy54NwiN4nC8K+R1eMpQDB1P2d25qmunh2RSA=
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0 h1:5ZkdpbduT/g+9OtbSDvbF3KvfQG45CtH/ppO8FUmvCQ=
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0/go.mod h1:enML0deDxY1ux+B6ANGiwtg0yAJi1rctkTpcHNAVPyg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -326,8 +326,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20220819163037-ba6e795b139a h1:W1h3JsEzYWg7eD4908iHv49p7AOx7JPKsoh/fsxgylM=
gvisor.dev/gvisor v0.0.0-20220819163037-ba6e795b139a/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=

View File

@@ -29,7 +29,7 @@ func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
}
if a.listenOptions.ProxyProtocol {
a.logger.Debug("proxy protocol enabled")
tcpListener = &proxyproto.Listener{Listener: tcpListener}
tcpListener = &proxyproto.Listener{Listener: tcpListener, AcceptNoHeader: a.listenOptions.ProxyProtocolAcceptNoHeader}
}
a.tcpListener = tcpListener
return tcpListener, err
@@ -40,7 +40,11 @@ func (a *myInboundAdapter) loopTCPIn() {
for {
conn, err := tcpListener.Accept()
if err != nil {
return
if E.IsClosed(err) {
return
}
a.logger.Error("accept: ", err)
continue
}
go a.injectTCP(conn, adapter.InboundContext{})
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -17,11 +18,15 @@ import (
func (a *myInboundAdapter) ListenUDP() (net.PacketConn, error) {
bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
udpConn, err := net.ListenUDP(M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr())
var lc net.ListenConfig
if !a.listenOptions.UDPFragment {
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
}
udpConn, err := lc.ListenPacket(a.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
if err != nil {
return nil, err
}
a.udpConn = udpConn
a.udpConn = udpConn.(*net.UDPConn)
a.udpAddr = bindAddr
a.logger.Info("udp server started at ", udpConn.LocalAddr())
return udpConn, err

View File

@@ -20,7 +20,6 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -361,7 +360,7 @@ func (c *naiveH1Conn) WriteBuffer(buffer *buf.Buffer) error {
n, err = bufio.Copy(w, c.Conn)
}
return n, wrapHttpError(err)
}*/
}
func (c *naiveH1Conn) ReadFrom(r io.Reader) (n int64, err error) {
if c.writePadding < kFirstPaddings {
@@ -371,13 +370,14 @@ func (c *naiveH1Conn) ReadFrom(r io.Reader) (n int64, err error) {
}
return n, wrapHttpError(err)
}
*/
func (c *naiveH1Conn) Upstream() any {
return c.Conn
}
func (c *naiveH1Conn) ReaderReplaceable() bool {
return c.readRemaining == kFirstPaddings
return c.readPadding == kFirstPaddings
}
func (c *naiveH1Conn) WriterReplaceable() bool {
@@ -539,7 +539,7 @@ func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
n, err = bufio.Copy(w, c.reader)
}
return n, wrapHttpError(err)
}*/
}
func (c *naiveH2Conn) ReadFrom(r io.Reader) (n int64, err error) {
if c.writePadding < kFirstPaddings {
@@ -548,7 +548,7 @@ func (c *naiveH2Conn) ReadFrom(r io.Reader) (n int64, err error) {
n, err = bufio.Copy(c.writer, r)
}
return n, wrapHttpError(err)
}
}*/
func (c *naiveH2Conn) Close() error {
return common.Close(
@@ -586,7 +586,7 @@ func (c *naiveH2Conn) UpstreamWriter() any {
}
func (c *naiveH2Conn) ReaderReplaceable() bool {
return c.readRemaining == kFirstPaddings
return c.readPadding == kFirstPaddings
}
func (c *naiveH2Conn) WriterReplaceable() bool {

View File

@@ -40,11 +40,11 @@ type Tun struct {
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (*Tun, error) {
tunName := options.InterfaceName
if tunName == "" {
tunName = tun.DefaultInterfaceName()
tunName = tun.CalculateInterfaceName("")
}
tunMTU := options.MTU
if tunMTU == 0 {
tunMTU = 1500
tunMTU = 9000
}
var udpTimeout int64
if options.UDPTimeout != 0 {
@@ -77,8 +77,8 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
tunOptions: tun.Options{
Name: tunName,
MTU: tunMTU,
Inet4Address: options.Inet4Address.Build(),
Inet6Address: options.Inet6Address.Build(),
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
AutoRoute: options.AutoRoute,
StrictRoute: options.StrictRoute,
IncludeUID: includeUID,
@@ -86,6 +86,8 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
IncludeAndroidUser: options.IncludeAndroidUser,
IncludePackage: options.IncludePackage,
ExcludePackage: options.ExcludePackage,
InterfaceMonitor: router.InterfaceMonitor(),
TableIndex: 2022,
},
endpointIndependentNat: options.EndpointIndependentNat,
udpTimeout: udpTimeout,
@@ -142,7 +144,18 @@ func (t *Tun) Start() error {
return E.Cause(err, "configure tun interface")
}
t.tunIf = tunIf
t.tunStack, err = tun.NewStack(t.ctx, t.stack, tunIf, t.tunOptions.MTU, t.endpointIndependentNat, t.udpTimeout, t)
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
Context: t.ctx,
Tun: tunIf,
MTU: t.tunOptions.MTU,
Name: t.tunOptions.Name,
Inet4Address: t.tunOptions.Inet4Address,
Inet6Address: t.tunOptions.Inet6Address,
EndpointIndependentNat: t.endpointIndependentNat,
UDPTimeout: t.udpTimeout,
Handler: t,
Logger: t.logger,
})
if err != nil {
return err
}

View File

@@ -71,7 +71,7 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
case f.DisableTimestamp:
message = levelString + " " + message
case f.FullTimestamp:
message = F.ToString(int(timestamp.Sub(f.BaseTime)/time.Second)) + " " + levelString + " " + message
message = timestamp.Format(f.TimestampFormat) + " " + levelString + " " + message
default:
message = levelString + "[" + xd(int(timestamp.Sub(f.BaseTime)/time.Second), 4) + "] " + message
}
@@ -136,7 +136,7 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string
case f.DisableTimestamp:
message = levelString + " " + message
case f.FullTimestamp:
message = F.ToString(int(timestamp.Sub(f.BaseTime)/time.Second)) + " " + levelString + " " + message
message = timestamp.Format(f.TimestampFormat) + " " + levelString + " " + message
default:
message = levelString + "[" + xd(int(timestamp.Sub(f.BaseTime)/time.Second), 4) + "] " + message
}

View File

@@ -20,7 +20,7 @@ type _Options struct {
type Options _Options
func (o *Options) UnmarshalJSON(content []byte) error {
decoder := json.NewDecoder(bytes.NewReader(content))
decoder := json.NewDecoder(json.NewCommentFilter(bytes.NewReader(content)))
decoder.DisallowUnknownFields()
err := decoder.Decode((*_Options)(o))
if err == nil {

View File

@@ -8,7 +8,7 @@ type DirectInboundOptions struct {
}
type DirectOutboundOptions struct {
OutboundDialerOptions
DialerOptions
OverrideAddress string `json:"override_address,omitempty"`
OverridePort uint16 `json:"override_port,omitempty"`
ProxyProtocol uint8 `json:"proxy_protocol,omitempty"`

View File

@@ -17,7 +17,7 @@ type HysteriaInboundOptions struct {
}
type HysteriaOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ServerOptions
Up string `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`

View File

@@ -111,11 +111,13 @@ type InboundOptions struct {
}
type ListenOptions struct {
Listen ListenAddress `json:"listen"`
ListenPort uint16 `json:"listen_port,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
UDPTimeout int64 `json:"udp_timeout,omitempty"`
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
Detour string `json:"detour,omitempty"`
Listen ListenAddress `json:"listen"`
ListenPort uint16 `json:"listen_port,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
UDPFragment bool `json:"udp_fragment,omitempty"`
UDPTimeout int64 `json:"udp_timeout,omitempty"`
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"`
Detour string `json:"detour,omitempty"`
InboundOptions
}

View File

@@ -113,10 +113,7 @@ type DialerOptions struct {
ReuseAddr bool `json:"reuse_addr,omitempty"`
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
}
type OutboundDialerOptions struct {
DialerOptions
UDPFragment bool `json:"udp_fragment,omitempty"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
FallbackDelay Duration `json:"fallback_delay,omitempty"`
}

View File

@@ -16,6 +16,7 @@ type RouteOptions struct {
Final string `json:"final,omitempty"`
FindProcess bool `json:"find_process,omitempty"`
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
DefaultInterface string `json:"default_interface,omitempty"`
DefaultMark int `json:"default_mark,omitempty"`
}

View File

@@ -22,7 +22,7 @@ type ShadowsocksDestination struct {
}
type ShadowsocksOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ServerOptions
Method string `json:"method"`
Password string `json:"password"`

View File

@@ -11,7 +11,7 @@ type ShadowTLSHandshakeOptions struct {
}
type ShadowTLSOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ServerOptions
TLS *OutboundTLSOptions `json:"tls,omitempty"`
}

View File

@@ -15,7 +15,7 @@ type HTTPMixedInboundOptions struct {
}
type SocksOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ServerOptions
Version string `json:"version,omitempty"`
Username string `json:"username,omitempty"`
@@ -25,7 +25,7 @@ type SocksOutboundOptions struct {
}
type HTTPOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ServerOptions
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`

View File

@@ -1,7 +1,7 @@
package option
type SSHOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ServerOptions
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`

View File

@@ -1,7 +1,7 @@
package option
type TorOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ExecutablePath string `json:"executable_path,omitempty"`
ExtraArgs []string `json:"extra_args,omitempty"`
DataDirectory string `json:"data_directory,omitempty"`

View File

@@ -15,7 +15,7 @@ type TrojanUser struct {
}
type TrojanOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ServerOptions
Password string `json:"password"`
Network NetworkList `json:"network,omitempty"`

View File

@@ -1,21 +1,21 @@
package option
type TunInboundOptions struct {
InterfaceName string `json:"interface_name,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Inet4Address *ListenPrefix `json:"inet4_address,omitempty"`
Inet6Address *ListenPrefix `json:"inet6_address,omitempty"`
AutoRoute bool `json:"auto_route,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"`
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"`
ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"`
ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"`
IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"`
IncludePackage Listable[string] `json:"include_package,omitempty"`
ExcludePackage Listable[string] `json:"exclude_package,omitempty"`
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `json:"udp_timeout,omitempty"`
Stack string `json:"stack,omitempty"`
InterfaceName string `json:"interface_name,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Inet4Address Listable[ListenPrefix] `json:"inet4_address,omitempty"`
Inet6Address Listable[ListenPrefix] `json:"inet6_address,omitempty"`
AutoRoute bool `json:"auto_route,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"`
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"`
ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"`
ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"`
IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"`
IncludePackage Listable[string] `json:"include_package,omitempty"`
ExcludePackage Listable[string] `json:"exclude_package,omitempty"`
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `json:"udp_timeout,omitempty"`
Stack string `json:"stack,omitempty"`
InboundOptions
}

View File

@@ -184,9 +184,6 @@ func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error {
return nil
}
func (p *ListenPrefix) Build() netip.Prefix {
if p == nil {
return netip.Prefix{}
}
return netip.Prefix(*p)
func (p ListenPrefix) Build() netip.Prefix {
return netip.Prefix(p)
}

View File

@@ -14,7 +14,7 @@ type VMessUser struct {
}
type VMessOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ServerOptions
UUID string `json:"uuid"`
Security string `json:"security"`

View File

@@ -1,12 +1,14 @@
package option
type WireGuardOutboundOptions struct {
OutboundDialerOptions
DialerOptions
ServerOptions
LocalAddress Listable[string] `json:"local_address"`
PrivateKey string `json:"private_key"`
PeerPublicKey string `json:"peer_public_key"`
PreSharedKey string `json:"pre_shared_key,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Network NetworkList `json:"network,omitempty"`
SystemInterface bool `json:"system_interface,omitempty"`
InterfaceName string `json:"interface_name,omitempty"`
LocalAddress Listable[ListenPrefix] `json:"local_address"`
PrivateKey string `json:"private_key"`
PeerPublicKey string `json:"peer_public_key"`
PreSharedKey string `json:"pre_shared_key,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Network NetworkList `json:"network,omitempty"`
}

View File

@@ -4,12 +4,14 @@ import (
"context"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -17,11 +19,16 @@ import (
"github.com/pires/go-proxyproto"
)
var _ adapter.Outbound = (*Direct)(nil)
var (
_ adapter.Outbound = (*Direct)(nil)
_ N.ParallelDialer = (*Direct)(nil)
)
type Direct struct {
myOutboundAdapter
dialer N.Dialer
domainStrategy dns.DomainStrategy
fallbackDelay time.Duration
overrideOption int
overrideDestination M.Socksaddr
proxyProto uint8
@@ -36,8 +43,10 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti
logger: logger,
tag: tag,
},
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
proxyProto: options.ProxyProtocol,
domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay),
dialer: dialer.New(router, options.DialerOptions),
proxyProto: options.ProxyProtocol,
}
if options.ProxyProtocol > 2 {
return nil, E.New("invalid proxy protocol option: ", options.ProxyProtocol)
@@ -99,6 +108,53 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M.
return conn, nil
}
func (h *Direct) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) {
ctx, metadata := adapter.AppendContext(ctx)
originDestination := metadata.Destination
metadata.Outbound = h.tag
metadata.Destination = destination
switch h.overrideOption {
case 1, 2:
// override address
return h.DialContext(ctx, network, destination)
case 3:
destination.Port = h.overrideDestination.Port
}
network = N.NetworkName(network)
switch network {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination)
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
var domainStrategy dns.DomainStrategy
if h.domainStrategy != dns.DomainStrategyAsIS {
domainStrategy = h.domainStrategy
} else {
domainStrategy = metadata.DomainStrategy
}
conn, err := N.DialParallel(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.fallbackDelay)
if err != nil {
return nil, err
}
if h.proxyProto > 0 {
source := metadata.Source
if !source.IsValid() {
source = M.SocksaddrFromNet(conn.LocalAddr())
}
if originDestination.Addr.Is6() {
source = M.SocksaddrFrom(netip.AddrFrom16(source.Addr.As16()), source.Port)
}
header := proxyproto.HeaderProxyFromAddrs(h.proxyProto, source.TCPAddr(), originDestination.TCPAddr())
_, err = header.WriteTo(conn)
if err != nil {
conn.Close()
return nil, E.Cause(err, "write proxy protocol header")
}
}
return conn, nil
}
func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
ctx, metadata := adapter.AppendContext(ctx)
metadata.Outbound = h.tag

View File

@@ -77,12 +77,9 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap
if err != nil {
return err
}
if len(message.Questions) > 0 {
question := message.Questions[0]
metadata.Domain = string(question.Name.Data[:question.Name.Length-1])
}
metadataInQuery := metadata
go func() error {
response, err := d.router.Exchange(ctx, &message)
response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message)
if err != nil {
return err
}
@@ -125,13 +122,10 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
if err != nil {
return err
}
if len(message.Questions) > 0 {
question := message.Questions[0]
metadata.Domain = string(question.Name.Data[:question.Name.Length-1])
}
timeout.Update()
metadataInQuery := metadata
go func() error {
response, err := d.router.Exchange(ctx, &message)
response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message)
if err != nil {
return err
}

View File

@@ -24,7 +24,7 @@ type HTTP struct {
}
func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) {
detour, err := dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLS))
detour, err := dialer.NewTLS(dialer.New(router, options.DialerOptions), options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}

View File

@@ -117,7 +117,7 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
tag: tag,
},
ctx: ctx,
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
dialer: dialer.New(router, options.DialerOptions),
serverAddr: options.ServerOptions.Build(),
tlsConfig: tlsConfig,
quicConfig: quicConfig,

View File

@@ -44,7 +44,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
logger: logger,
tag: tag,
},
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
dialer: dialer.New(router, options.DialerOptions),
method: method,
serverAddr: options.ServerOptions.Build(),
uot: options.UoT,

View File

@@ -34,7 +34,7 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
logger: logger,
tag: tag,
},
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
dialer: dialer.New(router, options.DialerOptions),
serverAddr: options.ServerOptions.Build(),
}
if options.TLS == nil || !options.TLS.Enabled {

View File

@@ -25,7 +25,7 @@ type Socks struct {
}
func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) {
detour := dialer.NewOutbound(router, options.OutboundDialerOptions)
detour := dialer.New(router, options.DialerOptions)
var version socks.Version
var err error
if options.Version != "" {

View File

@@ -47,7 +47,7 @@ func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger
tag: tag,
},
ctx: ctx,
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
dialer: dialer.New(router, options.DialerOptions),
serverAddr: options.ServerOptions.Build(),
user: options.User,
hostKeyAlgorithms: options.HostKeyAlgorithms,

View File

@@ -66,7 +66,7 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger
tag: tag,
},
ctx: ctx,
proxy: NewProxyListener(ctx, logger, dialer.NewOutbound(router, options.OutboundDialerOptions)),
proxy: NewProxyListener(ctx, logger, dialer.New(router, options.DialerOptions)),
startConf: &startConf,
options: options.Options,
}, nil

View File

@@ -13,6 +13,7 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2ray"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -40,7 +41,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
logger: logger,
tag: tag,
},
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
dialer: dialer.New(router, options.DialerOptions),
serverAddr: options.ServerOptions.Build(),
key: trojan.Key(options.Password),
}
@@ -125,7 +126,7 @@ func (h *trojanDialer) DialContext(ctx context.Context, network string, destinat
case N.NetworkTCP:
return trojan.NewClientConn(conn, h.key, destination), nil
case N.NetworkUDP:
return trojan.NewClientPacketConn(conn, h.key), nil
return &bufio.BindPacketConn{PacketConn: trojan.NewClientPacketConn(conn, h.key), Addr: destination}, nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
@@ -136,5 +137,5 @@ func (h *trojanDialer) ListenPacket(ctx context.Context, destination M.Socksaddr
if err != nil {
return nil, err
}
return conn.(*trojan.ClientPacketConn), nil
return conn.(net.PacketConn), nil
}

View File

@@ -42,7 +42,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
logger: logger,
tag: tag,
},
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
dialer: dialer.New(router, options.DialerOptions),
serverAddr: options.ServerOptions.Build(),
}
var err error

View File

@@ -8,49 +8,30 @@ import (
"encoding/hex"
"fmt"
"net"
"net/netip"
"os"
"strings"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/wireguard"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
var _ adapter.Outbound = (*WireGuard)(nil)
type WireGuard struct {
myOutboundAdapter
ctx context.Context
serverAddr M.Socksaddr
dialer N.Dialer
endpoint conn.Endpoint
device *device.Device
tunDevice *wireTunDevice
connAccess sync.Mutex
conn *wireConn
bind *wireguard.ClientBind
device *device.Device
tunDevice wireguard.Device
}
func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) {
@@ -62,39 +43,13 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
logger: logger,
tag: tag,
},
ctx: ctx,
serverAddr: options.ServerOptions.Build(),
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
}
var endpointIp netip.Addr
if !outbound.serverAddr.IsFqdn() {
endpointIp = outbound.serverAddr.Addr
} else {
endpointIp = netip.AddrFrom4([4]byte{127, 0, 0, 1})
}
outbound.endpoint = conn.StdNetEndpoint(netip.AddrPortFrom(endpointIp, outbound.serverAddr.Port))
localAddress := make([]tcpip.AddressWithPrefix, len(options.LocalAddress))
if len(localAddress) == 0 {
peerAddr := options.ServerOptions.Build()
outbound.bind = wireguard.NewClientBind(ctx, dialer.New(router, options.DialerOptions), peerAddr)
localPrefixes := common.Map(options.LocalAddress, option.ListenPrefix.Build)
if len(localPrefixes) == 0 {
return nil, E.New("missing local address")
}
for index, address := range options.LocalAddress {
if strings.Contains(address, "/") {
prefix, err := netip.ParsePrefix(address)
if err != nil {
return nil, E.Cause(err, "parse local address prefix ", address)
}
localAddress[index] = tcpip.AddressWithPrefix{
Address: tcpip.Address(prefix.Addr().AsSlice()),
PrefixLen: prefix.Bits(),
}
} else {
addr, err := netip.ParseAddr(address)
if err != nil {
return nil, E.Cause(err, "parse local address ", address)
}
localAddress[index] = tcpip.Address(addr.AsSlice()).WithPrefix()
}
}
var privateKey, peerPublicKey, preSharedKey string
{
bytes, err := base64.StdEncoding.DecodeString(options.PrivateKey)
@@ -119,13 +74,13 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
}
ipcConf := "private_key=" + privateKey
ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nendpoint=" + outbound.endpoint.DstToString()
ipcConf += "\nendpoint=" + peerAddr.String()
if preSharedKey != "" {
ipcConf += "\npreshared_key=" + preSharedKey
}
var has4, has6 bool
for _, address := range localAddress {
if address.Address.To4() != "" {
for _, address := range localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
has6 = true
@@ -141,11 +96,17 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
if mtu == 0 {
mtu = 1408
}
wireDevice, err := newWireDevice(localAddress, mtu)
if err != nil {
return nil, err
var wireTunDevice wireguard.Device
var err error
if !options.SystemInterface {
wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu)
} else {
wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
}
wgDevice := device.NewDevice(wireDevice, (*wireClientBind)(outbound), &device.Logger{
if err != nil {
return nil, E.Cause(err, "create WireGuard device")
}
wgDevice := device.NewDevice(wireTunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) {
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
},
@@ -161,7 +122,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
return nil, E.Cause(err, "setup wireguard")
}
outbound.device = wgDevice
outbound.tunDevice = wireDevice
outbound.tunDevice = wireTunDevice
return outbound, nil
}
@@ -172,54 +133,19 @@ func (w *WireGuard) DialContext(ctx context.Context, network string, destination
case N.NetworkUDP:
w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
addr := tcpip.FullAddress{
NIC: defaultNIC,
Port: destination.Port,
}
if destination.IsFqdn() {
addrs, err := w.router.LookupDefault(ctx, destination.Fqdn)
if err != nil {
return nil, err
}
addr.Addr = tcpip.Address(addrs[0].AsSlice())
} else {
addr.Addr = tcpip.Address(destination.Addr.AsSlice())
}
bind := tcpip.FullAddress{
NIC: defaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() {
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = w.tunDevice.addr4
} else {
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = w.tunDevice.addr6
}
switch N.NetworkName(network) {
case N.NetworkTCP:
return gonet.DialTCPWithBind(ctx, w.tunDevice.stack, bind, addr, networkProtocol)
case N.NetworkUDP:
return gonet.DialUDP(w.tunDevice.stack, &bind, &addr, networkProtocol)
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
return N.DialSerial(ctx, w.tunDevice, network, destination, addrs)
}
return w.tunDevice.DialContext(ctx, network, destination)
}
func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
bind := tcpip.FullAddress{
NIC: defaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() || w.tunDevice.addr6 == "" {
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = w.tunDevice.addr4
} else {
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = w.tunDevice.addr6
}
return gonet.DialUDP(w.tunDevice.stack, &bind, nil, networkProtocol)
return w.tunDevice.ListenPacket(ctx, destination)
}
func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
@@ -231,303 +157,13 @@ func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn,
}
func (w *WireGuard) Start() error {
w.tunDevice.events <- tun.EventUp
return nil
return w.tunDevice.Start()
}
func (w *WireGuard) Close() error {
return common.Close(
common.PtrOrNil(w.tunDevice),
w.tunDevice,
common.PtrOrNil(w.device),
common.PtrOrNil(w.conn),
common.PtrOrNil(w.bind),
)
}
var _ conn.Bind = (*wireClientBind)(nil)
type wireClientBind WireGuard
func (c *wireClientBind) connect() (*wireConn, error) {
c.connAccess.Lock()
defer c.connAccess.Unlock()
if c.conn != nil {
select {
case <-c.conn.done:
default:
return c.conn, nil
}
}
udpConn, err := c.dialer.DialContext(c.ctx, "udp", c.serverAddr)
if err != nil {
return nil, &wireError{err}
}
c.conn = &wireConn{
Conn: udpConn,
done: make(chan struct{}),
}
return c.conn, nil
}
func (c *wireClientBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
return []conn.ReceiveFunc{c.receive}, 0, nil
}
func (c *wireClientBind) receive(b []byte) (n int, ep conn.Endpoint, err error) {
udpConn, err := c.connect()
if err != nil {
return
}
n, err = udpConn.Read(b)
if err != nil {
udpConn.Close()
err = &wireError{err}
}
ep = c.endpoint
return
}
func (c *wireClientBind) Close() error {
c.connAccess.Lock()
defer c.connAccess.Unlock()
common.Close(common.PtrOrNil(c.conn))
return nil
}
func (c *wireClientBind) SetMark(mark uint32) error {
return nil
}
func (c *wireClientBind) Send(b []byte, ep conn.Endpoint) error {
udpConn, err := c.connect()
if err != nil {
return err
}
_, err = udpConn.Write(b)
if err != nil {
udpConn.Close()
}
return err
}
func (c *wireClientBind) ParseEndpoint(s string) (conn.Endpoint, error) {
return c.endpoint, nil
}
type wireError struct {
cause error
}
func (w *wireError) Error() string {
return w.cause.Error()
}
func (w *wireError) Timeout() bool {
if cause, causeNet := w.cause.(net.Error); causeNet {
return cause.Timeout()
}
return false
}
func (w *wireError) Temporary() bool {
return true
}
func (w *wireError) Unwrap() error {
return w.cause
}
type wireConn struct {
net.Conn
access sync.Mutex
done chan struct{}
}
func (w *wireConn) Close() error {
w.access.Lock()
defer w.access.Unlock()
select {
case <-w.done:
return net.ErrClosed
default:
}
w.Conn.Close()
close(w.done)
return nil
}
var _ tun.Device = (*wireTunDevice)(nil)
const defaultNIC tcpip.NICID = 1
type wireTunDevice struct {
stack *stack.Stack
mtu uint32
events chan tun.Event
outbound chan *stack.PacketBuffer
dispatcher stack.NetworkDispatcher
done chan struct{}
addr4 tcpip.Address
addr6 tcpip.Address
}
func newWireDevice(localAddresses []tcpip.AddressWithPrefix, mtu uint32) (*wireTunDevice, error) {
ipStack := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
HandleLocal: true,
})
tunDevice := &wireTunDevice{
stack: ipStack,
mtu: mtu,
events: make(chan tun.Event, 4),
outbound: make(chan *stack.PacketBuffer, 256),
done: make(chan struct{}),
}
err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
if err != nil {
return nil, E.New(err.String())
}
for _, addr := range localAddresses {
var protoAddr tcpip.ProtocolAddress
if len(addr.Address) == net.IPv4len {
tunDevice.addr4 = addr.Address
protoAddr = tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: addr,
}
} else {
tunDevice.addr6 = addr.Address
protoAddr = tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: addr,
}
}
err = ipStack.AddProtocolAddress(defaultNIC, protoAddr, stack.AddressProperties{})
if err != nil {
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String())
}
}
sOpt := tcpip.TCPSACKEnabled(true)
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
cOpt := tcpip.CongestionControlOption("cubic")
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt)
ipStack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: defaultNIC})
ipStack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: defaultNIC})
return tunDevice, nil
}
func (w *wireTunDevice) File() *os.File {
return nil
}
func (w *wireTunDevice) Read(p []byte, offset int) (n int, err error) {
packetBuffer, ok := <-w.outbound
if !ok {
return 0, os.ErrClosed
}
defer packetBuffer.DecRef()
p = p[offset:]
for _, slice := range packetBuffer.AsSlices() {
n += copy(p[n:], slice)
}
return
}
func (w *wireTunDevice) Write(p []byte, offset int) (n int, err error) {
p = p[offset:]
if len(p) == 0 {
return
}
var networkProtocol tcpip.NetworkProtocolNumber
switch header.IPVersion(p) {
case header.IPv4Version:
networkProtocol = header.IPv4ProtocolNumber
case header.IPv6Version:
networkProtocol = header.IPv6ProtocolNumber
}
packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: bufferv2.MakeWithData(p),
})
defer packetBuffer.DecRef()
w.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer)
n = len(p)
return
}
func (w *wireTunDevice) Flush() error {
return nil
}
func (w *wireTunDevice) MTU() (int, error) {
return int(w.mtu), nil
}
func (w *wireTunDevice) Name() (string, error) {
return "sing-box", nil
}
func (w *wireTunDevice) Events() chan tun.Event {
return w.events
}
func (w *wireTunDevice) Close() error {
select {
case <-w.done:
return os.ErrClosed
default:
}
close(w.done)
w.stack.Close()
for _, endpoint := range w.stack.CleanupEndpoints() {
endpoint.Abort()
}
w.stack.Wait()
close(w.outbound)
return nil
}
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
type wireEndpoint wireTunDevice
func (ep *wireEndpoint) MTU() uint32 {
return ep.mtu
}
func (ep *wireEndpoint) MaxHeaderLength() uint16 {
return 0
}
func (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
func (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityNone
}
func (ep *wireEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
ep.dispatcher = dispatcher
}
func (ep *wireEndpoint) IsAttached() bool {
return ep.dispatcher != nil
}
func (ep *wireEndpoint) Wait() {
}
func (ep *wireEndpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (ep *wireEndpoint) AddHeader(buffer *stack.PacketBuffer) {
}
func (ep *wireEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) {
for _, packetBuffer := range list.AsSlice() {
packetBuffer.IncRef()
ep.outbound <- packetBuffer
}
return list.Len(), nil
}

View File

@@ -10,7 +10,7 @@ DIR=$(dirname "$0")
PROJECT=$DIR/../..
pushd $PROJECT
go install -v -trimpath -ldflags "-s -w -buildid=" -tags "no_gvisor" ./cmd/sing-box
go install -v -trimpath -ldflags "-s -w -buildid=" -tags no_gvisor,with_quic,with_wireguard,with_acme ./cmd/sing-box
popd
sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e -o pipefail
curl -o go.tar.gz https://go.dev/dl/go1.19.linux-amd64.tar.gz
curl -Lo go.tar.gz https://go.dev/dl/go1.19.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go.tar.gz
rm go.tar.gz

View File

@@ -10,7 +10,7 @@ DIR=$(dirname "$0")
PROJECT=$DIR/../..
pushd $PROJECT
go install -v -trimpath -ldflags "-s -w -buildid=" -tags no_gvisor,with_quic,with_acme ./cmd/sing-box
go install -v -trimpath -ldflags "-s -w -buildid=" -tags no_gvisor,with_quic,with_wireguard,with_acme ./cmd/sing-box
popd
sudo systemctl stop sing-box

View File

@@ -244,7 +244,7 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
needInterfaceMonitor := options.AutoDetectInterface ||
C.IsDarwin && common.Any(inbounds, func(inbound option.Inbound) bool {
return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy
return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || C.IsAndroid && inbound.TunOptions.AutoRoute
})
if router.interfaceBindManager != nil || needInterfaceMonitor {
@@ -258,12 +258,24 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
}
if router.networkMonitor != nil && needInterfaceMonitor {
interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor)
interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, tun.DefaultInterfaceMonitorOptions{
OverrideAndroidVPN: options.OverrideAndroidVPN,
})
if err != nil {
return nil, E.New("auto_detect_interface unsupported on current platform")
}
interfaceMonitor.RegisterCallback(func() error {
router.logger.Info("updated default interface ", router.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", router.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()))
interfaceMonitor.RegisterCallback(func(event int) error {
if C.IsAndroid {
var vpnStatus string
if router.interfaceMonitor.AndroidVPNEnabled() {
vpnStatus = "enabled"
} else {
vpnStatus = "disabled"
}
router.logger.Info("updated default interface ", router.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", router.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus)
} else {
router.logger.Info("updated default interface ", router.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", router.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()))
}
return nil
})
router.interfaceMonitor = interfaceMonitor
@@ -667,12 +679,12 @@ func (r *Router) match(ctx context.Context, metadata *adapter.InboundContext, de
}
processInfo, err := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort(), originDestination)
if err != nil {
r.logger.DebugContext(ctx, "failed to search process: ", err)
r.logger.InfoContext(ctx, "failed to search process: ", err)
} else {
if processInfo.ProcessPath != "" {
r.logger.DebugContext(ctx, "found process path: ", processInfo.ProcessPath)
r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath)
} else if processInfo.PackageName != "" {
r.logger.DebugContext(ctx, "found package name: ", processInfo.PackageName)
r.logger.InfoContext(ctx, "found package name: ", processInfo.PackageName)
} else if processInfo.UserId != -1 {
if /*needUserName &&*/ true {
osUser, _ := user.LookupId(F.ToString(processInfo.UserId))
@@ -681,9 +693,9 @@ func (r *Router) match(ctx context.Context, metadata *adapter.InboundContext, de
}
}
if processInfo.User != "" {
r.logger.DebugContext(ctx, "found user: ", processInfo.User)
r.logger.InfoContext(ctx, "found user: ", processInfo.User)
} else {
r.logger.DebugContext(ctx, "found user id: ", processInfo.UserId)
r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId)
}
}
metadata.ProcessInfo = processInfo

View File

@@ -52,6 +52,7 @@ func (r *Router) Exchange(ctx context.Context, message *dnsmessage.Message) (*dn
case dnsmessage.TypeAAAA:
metadata.IPVersion = 6
}
metadata.Domain = string(message.Questions[0].Name.Data[:message.Questions[0].Name.Length-1])
}
ctx, transport, strategy := r.matchDNS(ctx)
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
@@ -68,6 +69,8 @@ func (r *Router) Exchange(ctx context.Context, message *dnsmessage.Message) (*dn
func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
r.dnsLogger.DebugContext(ctx, "lookup domain ", domain)
ctx, metadata := adapter.AppendContext(ctx)
metadata.Domain = domain
ctx, transport, transportStrategy := r.matchDNS(ctx)
if strategy == dns.DomainStrategyAsIS {
strategy = transportStrategy

View File

@@ -59,13 +59,13 @@ func NewIPCIDRItem(isSource bool, prefixStrings []string) (*IPCIDRItem, error) {
func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {
if r.isSource {
return r.ipSet.Contains(metadata.Source.Addr)
return r.match(metadata.Source.Addr)
} else {
if metadata.Destination.IsIP() {
return r.ipSet.Contains(metadata.Destination.Addr)
return r.match(metadata.Destination.Addr)
} else {
for _, address := range metadata.DestinationAddresses {
if r.ipSet.Contains(address) {
if r.match(address) {
return true
}
}
@@ -74,6 +74,14 @@ func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {
return false
}
func (r *IPCIDRItem) match(address netip.Addr) bool {
if address.Is4In6() {
return r.ipSet.Contains(netip.AddrFrom4(address.As4()))
} else {
return r.ipSet.Contains(address)
}
}
func (r *IPCIDRItem) String() string {
return r.description
}

View File

@@ -22,6 +22,10 @@ func startInstance(t *testing.T, options option.Options) {
options.Log = &option.LogOptions{
Level: "trace",
}
} else {
options.Log = &option.LogOptions{
Level: "warning",
}
}
var instance *box.Box
var err error

View File

@@ -183,6 +183,7 @@ func testPingPongWithConn(t *testing.T, port uint16, cc func() (net.Conn, error)
if err != nil {
return err
}
defer c.Close()
pingCh, pongCh, test := newPingPongPair()
go func() {
@@ -245,6 +246,7 @@ func testPingPongWithPacketConn(t *testing.T, port uint16, pcc func() (net.Packe
if err != nil {
return err
}
defer pc.Close()
go func() {
if _, err := pc.WriteTo([]byte("ping"), rAddr); err != nil {
@@ -301,6 +303,7 @@ func testLargeDataWithConn(t *testing.T, port uint16, cc func() (net.Conn, error
if err != nil {
return err
}
defer c.Close()
go func() {
c, err := l.Accept()
@@ -432,6 +435,7 @@ func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.Pack
if err != nil {
return err
}
defer pc.Close()
go func() {
sendHash, err := writeRandData(pc, rAddr)

View File

@@ -0,0 +1,52 @@
{
"log": {
"loglevel": "debug"
},
"inbounds": [
{
"listen": "127.0.0.1",
"port": "1080",
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true,
"ip": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "127.0.0.1",
"port": 1234,
"users": [
{
"id": ""
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"serverName": "example.org",
"certificates": [
{
"certificateFile": "/path/to/certificate.crt",
"keyFile": "/path/to/private.key"
}
]
},
"wsSettings": {
"maxEarlyData": 2048,
"earlyDataHeaderName": ""
}
}
}
]
}

View File

@@ -0,0 +1,41 @@
{
"log": {
"loglevel": "debug"
},
"inbounds": [
{
"listen": "0.0.0.0",
"port": 1234,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"serverName": "example.org",
"certificates": [
{
"certificateFile": "/path/to/certificate.crt",
"keyFile": "/path/to/private.key"
}
]
},
"wsSettings": {
"maxEarlyData": 2048,
"earlyDataHeaderName": ""
}
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
]
}

View File

@@ -1,6 +1,6 @@
[Interface]
PrivateKey = gHWUGzTh5YCEV6k8dneVP537XhVtoQJPIlFNs2zsxlE=
Address = 10.0.0.1/24
Address = 10.0.0.1/32
ListenPort = 10000
[Peer]

View File

@@ -10,9 +10,6 @@ import (
func TestProxyProtocol(t *testing.T) {
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,

View File

@@ -10,13 +10,15 @@ require (
github.com/docker/docker v20.10.17+incompatible
github.com/docker/go-connections v0.4.0
github.com/gofrs/uuid v4.2.0+incompatible
github.com/sagernet/sing v0.0.0-20220829115648-e09c9f3fc812
github.com/sagernet/sing v0.0.0-20220903085538-02b9ca1cc133
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
github.com/spyzhov/ajson v0.7.1
github.com/stretchr/testify v1.8.0
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b
)
//replace github.com/sagernet/sing => ../../sing
require (
berty.tech/go-libtor v1.0.385 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
@@ -59,25 +61,25 @@ require (
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/netlink v0.0.0-20220826133217-3fb4ff92ea17 // indirect
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666 // indirect
github.com/sagernet/sing-dns v0.0.0-20220903082137-b1102b8fc961 // indirect
github.com/sagernet/sing-tun v0.0.0-20220828031750-185b6c880a83 // indirect
github.com/sagernet/sing-vmess v0.0.0-20220829020559-33915075430c // indirect
github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939 // indirect
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f // indirect
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.22.0 // indirect
go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d // indirect
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b // indirect
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect

View File

@@ -155,18 +155,18 @@ github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTY
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.0.0-20220829115648-e09c9f3fc812 h1:z5AqoJcMy+MaD9pstLt08c95t2Txlzvp5pk4lqXBQPc=
github.com/sagernet/sing v0.0.0-20220829115648-e09c9f3fc812/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666 h1:XUTocA/Ek0dFxUX+xJCWMPPFZCn2GC/uLrBjTSr1vHY=
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666/go.mod h1:eDyH7AJmqBGjZQdQmpZIzlbTREudZuWDExMuGKgjRVM=
github.com/sagernet/sing v0.0.0-20220903085538-02b9ca1cc133 h1:krnb8wKEFIdXhmJYlhJMbEcPsJFISy2fz90uHVz7hMU=
github.com/sagernet/sing v0.0.0-20220903085538-02b9ca1cc133/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
github.com/sagernet/sing-dns v0.0.0-20220903082137-b1102b8fc961 h1:5JeqhvCGV6AQQiAO0V67Loh2eyO3JNjIQnvRF8NnTE0=
github.com/sagernet/sing-dns v0.0.0-20220903082137-b1102b8fc961/go.mod h1:vKBBy4mNJRaFuJ8H6kYIOPofsZ1JT5mgdwIlebtvnZ4=
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
github.com/sagernet/sing-tun v0.0.0-20220828031750-185b6c880a83 h1:SoWiHYuOCVedqA7T/CJSZUUrcPGKQb2wFKEq8DphiAI=
github.com/sagernet/sing-tun v0.0.0-20220828031750-185b6c880a83/go.mod h1:76r07HS1WRcEI4mE9pFsohfTBUt1j/G9Avz6DaOP3VU=
github.com/sagernet/sing-vmess v0.0.0-20220829020559-33915075430c h1:92Gn78/z/t6CkzZ4XWG/uPiCxhUmjPULFEHFMDY6K8k=
github.com/sagernet/sing-vmess v0.0.0-20220829020559-33915075430c/go.mod h1:82O6gzbxLha/W/jxSVQbsqf2lVdRTjMIgyLug0lpJps=
github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939 h1:pB1Dh1NbwVrLhQhotr4O4Hs3yhiBzmg3AvnUyYjL4x4=
github.com/sagernet/smux v0.0.0-20220812084127-e2d085ee3939/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f h1:6l9aXZqAl1JqXJWi89KHpWnM/moQUPGG+XiwMc+yD0A=
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -205,8 +205,8 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
@@ -275,8 +275,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -311,8 +311,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b h1:qgrKnOfe1zyURRNdmDlGbN32i38Zjmw0B1+TMdHcOvg=
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b/go.mod h1:6y4CqPAy54NwiN4nC8K+R1eMpQDB1P2d25qmunh2RSA=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=

View File

@@ -14,9 +14,6 @@ func TestHysteriaSelf(t *testing.T) {
}
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -92,9 +89,6 @@ func TestHysteriaInbound(t *testing.T) {
}
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeHysteria,
@@ -145,9 +139,6 @@ func TestHysteriaOutbound(t *testing.T) {
},
})
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,

View File

@@ -13,9 +13,6 @@ func TestChainedInbound(t *testing.T) {
method := shadowaead_2022.List[0]
password := mkBase64(t, 16)
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -62,10 +59,8 @@ func TestChainedInbound(t *testing.T) {
ShadowsocksOptions: option.ShadowsocksOutboundOptions{
Method: method,
Password: password,
OutboundDialerOptions: option.OutboundDialerOptions{
DialerOptions: option.DialerOptions{
Detour: "detour-out",
},
DialerOptions: option.DialerOptions{
Detour: "detour-out",
},
},
},

View File

@@ -37,9 +37,6 @@ func testShadowsocksMux(t *testing.T, protocol string) {
method := shadowaead_2022.List[0]
password := mkBase64(t, 16)
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -101,9 +98,6 @@ func testShadowsocksMux(t *testing.T, protocol string) {
func testVMessMux(t *testing.T, protocol string) {
user, _ := uuid.NewV4()
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,

View File

@@ -13,9 +13,6 @@ import (
func TestNaiveInboundWithNginx(t *testing.T) {
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeNaive,
@@ -62,9 +59,6 @@ func TestNaiveInboundWithNginx(t *testing.T) {
func TestNaiveInbound(t *testing.T) {
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeNaive,
@@ -110,9 +104,6 @@ func TestNaiveHTTP3Inbound(t *testing.T) {
}
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeNaive,

View File

@@ -77,9 +77,6 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass
Cmd: []string{"-s", F.ToString("127.0.0.1:", serverPort), "-b", F.ToString("0.0.0.0:", clientPort), "-m", method, "-k", password, "-U"},
})
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeShadowsocks,
@@ -105,9 +102,6 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas
Cmd: []string{"-s", F.ToString("0.0.0.0:", serverPort), "-m", method, "-k", password, "-U"},
})
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -138,9 +132,6 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas
func testShadowsocksSelf(t *testing.T, method string, password string) {
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -199,9 +190,6 @@ func TestShadowsocksUoT(t *testing.T) {
method := shadowaead_2022.List[0]
password := mkBase64(t, 16)
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,

View File

@@ -60,10 +60,11 @@ func TestShadowTLS(t *testing.T) {
ShadowsocksOptions: option.ShadowsocksOutboundOptions{
Method: method,
Password: password,
OutboundDialerOptions: option.OutboundDialerOptions{
DialerOptions: option.DialerOptions{
Detour: "detour",
},
DialerOptions: option.DialerOptions{
Detour: "detour",
},
MultiplexOptions: &option.MultiplexOptions{
Enabled: true,
},
},
},
@@ -95,7 +96,7 @@ func TestShadowTLS(t *testing.T) {
}},
},
})
testTCP(t, clientPort, testPort)
testSuit(t, clientPort, testPort)
}
func TestShadowTLSOutbound(t *testing.T) {
@@ -131,10 +132,8 @@ func TestShadowTLSOutbound(t *testing.T) {
{
Type: C.TypeSocks,
SocksOptions: option.SocksOutboundOptions{
OutboundDialerOptions: option.OutboundDialerOptions{
DialerOptions: option.DialerOptions{
Detour: "detour",
},
DialerOptions: option.DialerOptions{
Detour: "detour",
},
},
},

View File

@@ -20,9 +20,6 @@ func TestTrojanOutbound(t *testing.T) {
},
})
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -58,9 +55,6 @@ func TestTrojanOutbound(t *testing.T) {
func TestTrojanSelf(t *testing.T) {
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,

View File

@@ -27,9 +27,6 @@ func testV2RayGRPCInbound(t *testing.T, forceLite bool) {
require.NoError(t, err)
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeVMess,
@@ -125,9 +122,6 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) {
},
})
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,

View File

@@ -11,31 +11,6 @@ import (
"github.com/stretchr/testify/require"
)
func TestV2RayWebscoketSelf(t *testing.T) {
t.Run("basic", func(t *testing.T) {
testV2RayTransportSelf(t, &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket,
})
})
t.Run("v2ray early data", func(t *testing.T) {
testV2RayTransportSelf(t, &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket,
WebsocketOptions: option.V2RayWebsocketOptions{
MaxEarlyData: 2048,
},
})
})
t.Run("xray early data", func(t *testing.T) {
testV2RayTransportSelf(t, &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket,
WebsocketOptions: option.V2RayWebsocketOptions{
MaxEarlyData: 2048,
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
},
})
})
}
func TestV2RayHTTPSelf(t *testing.T) {
testV2RayTransportSelf(t, &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeHTTP,
@@ -69,9 +44,6 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
require.NoError(t, err)
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -148,9 +120,6 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
require.NoError(t, err)
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -229,9 +198,6 @@ func TestVMessQUICSelf(t *testing.T) {
require.NoError(t, err)
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -307,9 +273,6 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO
user, err := uuid.DefaultGenerator.NewV4()
require.NoError(t, err)
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,

197
test/v2ray_ws_test.go Normal file
View File

@@ -0,0 +1,197 @@
package main
import (
"net/netip"
"os"
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/gofrs/uuid"
"github.com/spyzhov/ajson"
"github.com/stretchr/testify/require"
)
func TestV2RayWebsocket(t *testing.T) {
t.Run("self", func(t *testing.T) {
testV2RayTransportSelf(t, &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket,
})
})
t.Run("self-early-data", func(t *testing.T) {
testV2RayTransportSelf(t, &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket,
WebsocketOptions: option.V2RayWebsocketOptions{
MaxEarlyData: 2048,
},
})
})
t.Run("self-xray-early-data", func(t *testing.T) {
testV2RayTransportSelf(t, &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket,
WebsocketOptions: option.V2RayWebsocketOptions{
MaxEarlyData: 2048,
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
},
})
})
t.Run("inbound", func(t *testing.T) {
testV2RayWebsocketInbound(t, 0, "")
})
t.Run("inbound-early-data", func(t *testing.T) {
testV2RayWebsocketInbound(t, 2048, "")
})
t.Run("inbound-xray-early-data", func(t *testing.T) {
testV2RayWebsocketInbound(t, 2048, "Sec-WebSocket-Protocol")
})
t.Run("outbound", func(t *testing.T) {
testV2RayWebsocketOutbound(t, 0, "")
})
t.Run("outbound-early-data", func(t *testing.T) {
testV2RayWebsocketOutbound(t, 2048, "")
})
t.Run("outbound-xray-early-data", func(t *testing.T) {
testV2RayWebsocketOutbound(t, 2048, "Sec-WebSocket-Protocol")
})
}
func testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeaderName string) {
userId, err := uuid.DefaultGenerator.NewV4()
require.NoError(t, err)
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeVMess,
VMessOptions: option.VMessInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.ListenAddress(netip.IPv4Unspecified()),
ListenPort: serverPort,
},
Users: []option.VMessUser{
{
Name: "sekai",
UUID: userId.String(),
},
},
TLS: &option.InboundTLSOptions{
Enabled: true,
ServerName: "example.org",
CertificatePath: certPem,
KeyPath: keyPem,
},
Transport: &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket,
WebsocketOptions: option.V2RayWebsocketOptions{
MaxEarlyData: maxEarlyData,
EarlyDataHeaderName: earlyDataHeaderName,
},
},
},
},
},
})
content, err := os.ReadFile("config/vmess-ws-client.json")
require.NoError(t, err)
config, err := ajson.Unmarshal(content)
require.NoError(t, err)
config.MustKey("inbounds").MustIndex(0).MustKey("port").SetNumeric(float64(clientPort))
outbound := config.MustKey("outbounds").MustIndex(0)
settings := outbound.MustKey("settings").MustKey("vnext").MustIndex(0)
settings.MustKey("port").SetNumeric(float64(serverPort))
user := settings.MustKey("users").MustIndex(0)
user.MustKey("id").SetString(userId.String())
wsSettings := outbound.MustKey("streamSettings").MustKey("wsSettings")
wsSettings.MustKey("maxEarlyData").SetNumeric(float64(maxEarlyData))
wsSettings.MustKey("earlyDataHeaderName").SetString(earlyDataHeaderName)
content, err = ajson.Marshal(config)
require.NoError(t, err)
startDockerContainer(t, DockerOptions{
Image: ImageV2RayCore,
Ports: []uint16{serverPort, testPort},
EntryPoint: "v2ray",
Stdin: content,
Bind: map[string]string{
certPem: "/path/to/certificate.crt",
keyPem: "/path/to/private.key",
},
})
testSuitSimple(t, clientPort, testPort)
}
func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHeaderName string) {
userId, err := uuid.DefaultGenerator.NewV4()
require.NoError(t, err)
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
content, err := os.ReadFile("config/vmess-ws-server.json")
require.NoError(t, err)
config, err := ajson.Unmarshal(content)
require.NoError(t, err)
inbound := config.MustKey("inbounds").MustIndex(0)
inbound.MustKey("port").SetNumeric(float64(serverPort))
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(userId.String())
wsSettings := inbound.MustKey("streamSettings").MustKey("wsSettings")
wsSettings.MustKey("maxEarlyData").SetNumeric(float64(maxEarlyData))
wsSettings.MustKey("earlyDataHeaderName").SetString(earlyDataHeaderName)
content, err = ajson.Marshal(config)
require.NoError(t, err)
startDockerContainer(t, DockerOptions{
Image: ImageV2RayCore,
Ports: []uint16{serverPort, testPort},
EntryPoint: "v2ray",
Stdin: content,
Env: []string{"V2RAY_VMESS_AEAD_FORCED=false"},
Bind: map[string]string{
certPem: "/path/to/certificate.crt",
keyPem: "/path/to/private.key",
},
})
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
Tag: "mixed-in",
MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.ListenAddress(netip.IPv4Unspecified()),
ListenPort: clientPort,
},
},
},
},
Outbounds: []option.Outbound{
{
Type: C.TypeVMess,
Tag: "vmess-out",
VMessOptions: option.VMessOutboundOptions{
ServerOptions: option.ServerOptions{
Server: "127.0.0.1",
ServerPort: serverPort,
},
UUID: userId.String(),
Security: "zero",
TLS: &option.OutboundTLSOptions{
Enabled: true,
ServerName: "example.org",
CertificatePath: certPem,
},
Transport: &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket,
WebsocketOptions: option.V2RayWebsocketOptions{
MaxEarlyData: maxEarlyData,
EarlyDataHeaderName: earlyDataHeaderName,
},
},
},
},
},
})
testSuitSimple(t, clientPort, testPort)
}

View File

@@ -182,9 +182,6 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, al
})
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeVMess,
@@ -231,9 +228,6 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
})
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -267,9 +261,6 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) {
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,

View File

@@ -2,6 +2,7 @@ package main
import (
"net/netip"
"os"
"testing"
"time"
@@ -21,9 +22,6 @@ func TestWireGuard(t *testing.T) {
})
time.Sleep(5 * time.Second)
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "error",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
@@ -43,7 +41,7 @@ func TestWireGuard(t *testing.T) {
Server: "127.0.0.1",
ServerPort: serverPort,
},
LocalAddress: []string{"10.0.0.2/32"},
LocalAddress: []option.ListenPrefix{option.ListenPrefix(netip.MustParsePrefix("10.0.0.2/32"))},
PrivateKey: "qGnwlkZljMxeECW8fbwAWdvgntnbK7B8UmMFl3zM0mk=",
PeerPublicKey: "QsdcBm+oJw2oNv0cIFXLIq1E850lgTBonup4qnKEQBg=",
},
@@ -52,3 +50,49 @@ func TestWireGuard(t *testing.T) {
})
testSuitWg(t, clientPort, testPort)
}
func TestWireGuardSystem(t *testing.T) {
if os.Getuid() != 0 {
t.Skip("requires root")
}
startDockerContainer(t, DockerOptions{
Image: ImageBoringTun,
Cap: []string{"MKNOD", "NET_ADMIN", "NET_RAW"},
Ports: []uint16{serverPort, testPort},
Bind: map[string]string{
"wireguard.conf": "/etc/wireguard/wg0.conf",
},
Cmd: []string{"wg0"},
})
time.Sleep(5 * time.Second)
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.ListenAddress(netip.IPv4Unspecified()),
ListenPort: clientPort,
},
},
},
},
Outbounds: []option.Outbound{
{
Type: C.TypeWireGuard,
WireGuardOptions: option.WireGuardOutboundOptions{
InterfaceName: "wg",
ServerOptions: option.ServerOptions{
Server: "127.0.0.1",
ServerPort: serverPort,
},
LocalAddress: []option.ListenPrefix{option.ListenPrefix(netip.MustParsePrefix("10.0.0.2/32"))},
PrivateKey: "qGnwlkZljMxeECW8fbwAWdvgntnbK7B8UmMFl3zM0mk=",
PeerPublicKey: "QsdcBm+oJw2oNv0cIFXLIq1E850lgTBonup4qnKEQBg=",
},
},
},
})
time.Sleep(10 * time.Second)
testSuitWg(t, clientPort, testPort)
}

View File

@@ -6,6 +6,7 @@ import (
"syscall"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing-box/common/baderror"
"github.com/sagernet/sing/common"
)
@@ -38,6 +39,16 @@ type StreamWrapper struct {
quic.Stream
}
func (s *StreamWrapper) Read(p []byte) (n int, err error) {
n, err = s.Stream.Read(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) Write(p []byte) (n int, err error) {
n, err = s.Stream.Write(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) LocalAddr() net.Addr {
return s.Conn.LocalAddr()
}
@@ -50,14 +61,6 @@ func (s *StreamWrapper) Upstream() any {
return s.Stream
}
func (s *StreamWrapper) ReaderReplaceable() bool {
return true
}
func (s *StreamWrapper) WriterReplaceable() bool {
return true
}
func (s *StreamWrapper) Close() error {
s.CancelRead(0)
s.Stream.Close()

View File

@@ -167,6 +167,9 @@ func (c *EarlyWebsocketConn) SetWriteDeadline(t time.Time) error {
func wrapError(err error) error {
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
return io.EOF
}
if websocket.IsCloseError(err, websocket.CloseAbnormalClosure) {
return net.ErrClosed
}
return err

View File

@@ -0,0 +1,132 @@
package wireguard
import (
"context"
"net"
"sync"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"golang.zx2c4.com/wireguard/conn"
)
var _ conn.Bind = (*ClientBind)(nil)
type ClientBind struct {
ctx context.Context
dialer N.Dialer
peerAddr M.Socksaddr
connAccess sync.Mutex
conn *wireConn
}
func NewClientBind(ctx context.Context, dialer N.Dialer, peerAddr M.Socksaddr) *ClientBind {
return &ClientBind{
ctx: ctx,
dialer: dialer,
peerAddr: peerAddr,
}
}
func (c *ClientBind) connect() (*wireConn, error) {
serverConn := c.conn
if serverConn != nil {
select {
case <-serverConn.done:
serverConn = nil
default:
return serverConn, nil
}
}
c.connAccess.Lock()
defer c.connAccess.Unlock()
serverConn = c.conn
if serverConn != nil {
select {
case <-serverConn.done:
serverConn = nil
default:
return serverConn, nil
}
}
udpConn, err := c.dialer.DialContext(c.ctx, "udp", c.peerAddr)
if err != nil {
return nil, &wireError{err}
}
c.conn = &wireConn{
Conn: udpConn,
done: make(chan struct{}),
}
return c.conn, nil
}
func (c *ClientBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
return []conn.ReceiveFunc{c.receive}, 0, nil
}
func (c *ClientBind) receive(b []byte) (n int, ep conn.Endpoint, err error) {
udpConn, err := c.connect()
if err != nil {
err = &wireError{err}
return
}
n, err = udpConn.Read(b)
if err != nil {
udpConn.Close()
err = &wireError{err}
}
ep = Endpoint(c.peerAddr)
return
}
func (c *ClientBind) Close() error {
c.connAccess.Lock()
defer c.connAccess.Unlock()
common.Close(common.PtrOrNil(c.conn))
return nil
}
func (c *ClientBind) SetMark(mark uint32) error {
return nil
}
func (c *ClientBind) Send(b []byte, ep conn.Endpoint) error {
udpConn, err := c.connect()
if err != nil {
return err
}
_, err = udpConn.Write(b)
if err != nil {
udpConn.Close()
}
return err
}
func (c *ClientBind) ParseEndpoint(s string) (conn.Endpoint, error) {
return Endpoint(c.peerAddr), nil
}
func (c *ClientBind) Endpoint() conn.Endpoint {
return Endpoint(c.peerAddr)
}
type wireConn struct {
net.Conn
access sync.Mutex
done chan struct{}
}
func (w *wireConn) Close() error {
w.access.Lock()
defer w.access.Unlock()
select {
case <-w.done:
return net.ErrClosed
default:
}
w.Conn.Close()
close(w.done)
return nil
}

View File

@@ -0,0 +1,14 @@
package wireguard
import (
N "github.com/sagernet/sing/common/network"
"golang.zx2c4.com/wireguard/tun"
)
type Device interface {
tun.Device
N.Dialer
Start() error
// NewEndpoint() (stack.LinkEndpoint, error)
}

View File

@@ -0,0 +1,254 @@
//go:build !no_gvisor
package wireguard
import (
"context"
"net"
"net/netip"
"os"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
var _ Device = (*StackDevice)(nil)
const defaultNIC tcpip.NICID = 1
type StackDevice struct {
stack *stack.Stack
mtu uint32
events chan tun.Event
outbound chan *stack.PacketBuffer
dispatcher stack.NetworkDispatcher
done chan struct{}
addr4 tcpip.Address
addr6 tcpip.Address
}
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, error) {
ipStack := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
HandleLocal: true,
})
tunDevice := &StackDevice{
stack: ipStack,
mtu: mtu,
events: make(chan tun.Event, 1),
outbound: make(chan *stack.PacketBuffer, 256),
done: make(chan struct{}),
}
err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
if err != nil {
return nil, E.New(err.String())
}
for _, prefix := range localAddresses {
addr := tcpip.Address(prefix.Addr().AsSlice())
protoAddr := tcpip.ProtocolAddress{
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: addr,
PrefixLen: prefix.Bits(),
},
}
if prefix.Addr().Is4() {
tunDevice.addr4 = addr
protoAddr.Protocol = ipv4.ProtocolNumber
} else {
tunDevice.addr6 = addr
protoAddr.Protocol = ipv6.ProtocolNumber
}
err = ipStack.AddProtocolAddress(defaultNIC, protoAddr, stack.AddressProperties{})
if err != nil {
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String())
}
}
sOpt := tcpip.TCPSACKEnabled(true)
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
cOpt := tcpip.CongestionControlOption("cubic")
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt)
ipStack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: defaultNIC})
ipStack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: defaultNIC})
return tunDevice, nil
}
func (w *StackDevice) NewEndpoint() (stack.LinkEndpoint, error) {
return (*wireEndpoint)(w), nil
}
func (w *StackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
addr := tcpip.FullAddress{
NIC: defaultNIC,
Port: destination.Port,
Addr: tcpip.Address(destination.Addr.AsSlice()),
}
bind := tcpip.FullAddress{
NIC: defaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() {
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = w.addr4
} else {
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = w.addr6
}
switch N.NetworkName(network) {
case N.NetworkTCP:
return gonet.DialTCPWithBind(ctx, w.stack, bind, addr, networkProtocol)
case N.NetworkUDP:
return gonet.DialUDP(w.stack, &bind, &addr, networkProtocol)
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
}
func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
bind := tcpip.FullAddress{
NIC: defaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() || w.addr6 == "" {
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = w.addr4
} else {
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = w.addr6
}
return gonet.DialUDP(w.stack, &bind, nil, networkProtocol)
}
func (w *StackDevice) Start() error {
w.events <- tun.EventUp
return nil
}
func (w *StackDevice) File() *os.File {
return nil
}
func (w *StackDevice) Read(p []byte, offset int) (n int, err error) {
packetBuffer, ok := <-w.outbound
if !ok {
return 0, os.ErrClosed
}
defer packetBuffer.DecRef()
p = p[offset:]
for _, slice := range packetBuffer.AsSlices() {
n += copy(p[n:], slice)
}
return
}
func (w *StackDevice) Write(p []byte, offset int) (n int, err error) {
p = p[offset:]
if len(p) == 0 {
return
}
var networkProtocol tcpip.NetworkProtocolNumber
switch header.IPVersion(p) {
case header.IPv4Version:
networkProtocol = header.IPv4ProtocolNumber
case header.IPv6Version:
networkProtocol = header.IPv6ProtocolNumber
}
packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: bufferv2.MakeWithData(p),
})
defer packetBuffer.DecRef()
w.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer)
n = len(p)
return
}
func (w *StackDevice) Flush() error {
return nil
}
func (w *StackDevice) MTU() (int, error) {
return int(w.mtu), nil
}
func (w *StackDevice) Name() (string, error) {
return "sing-box", nil
}
func (w *StackDevice) Events() chan tun.Event {
return w.events
}
func (w *StackDevice) Close() error {
select {
case <-w.done:
return os.ErrClosed
default:
}
close(w.done)
w.stack.Close()
for _, endpoint := range w.stack.CleanupEndpoints() {
endpoint.Abort()
}
w.stack.Wait()
close(w.outbound)
return nil
}
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
type wireEndpoint StackDevice
func (ep *wireEndpoint) MTU() uint32 {
return ep.mtu
}
func (ep *wireEndpoint) MaxHeaderLength() uint16 {
return 0
}
func (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
func (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityNone
}
func (ep *wireEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
ep.dispatcher = dispatcher
}
func (ep *wireEndpoint) IsAttached() bool {
return ep.dispatcher != nil
}
func (ep *wireEndpoint) Wait() {
}
func (ep *wireEndpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (ep *wireEndpoint) AddHeader(buffer *stack.PacketBuffer) {
}
func (ep *wireEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) {
for _, packetBuffer := range list.AsSlice() {
packetBuffer.IncRef()
ep.outbound <- packetBuffer
}
return list.Len(), nil
}

View File

@@ -0,0 +1,9 @@
//go:build no_gvisor
package wireguard
import "github.com/sagernet/sing-tun"
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (Device, error) {
return nil, tun.ErrGVisorNotIncluded
}

View File

@@ -0,0 +1,110 @@
package wireguard
import (
"context"
"net"
"net/netip"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
wgTun "golang.zx2c4.com/wireguard/tun"
)
var _ Device = (*SystemDevice)(nil)
type SystemDevice struct {
dialer N.Dialer
device tun.Tun
name string
mtu int
events chan wgTun.Event
}
/*func (w *SystemDevice) NewEndpoint() (stack.LinkEndpoint, error) {
gTun, isGTun := w.device.(tun.GVisorTun)
if !isGTun {
return nil, tun.ErrGVisorUnsupported
}
return gTun.NewEndpoint()
}*/
func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes []netip.Prefix, mtu uint32) (*SystemDevice, error) {
var inet4Addresses []netip.Prefix
var inet6Addresses []netip.Prefix
for _, prefixes := range localPrefixes {
if prefixes.Addr().Is4() {
inet4Addresses = append(inet4Addresses, prefixes)
} else {
inet6Addresses = append(inet6Addresses, prefixes)
}
}
if interfaceName == "" {
interfaceName = tun.CalculateInterfaceName("wg")
}
tunInterface, err := tun.Open(tun.Options{
Name: interfaceName,
Inet4Address: inet4Addresses,
Inet6Address: inet6Addresses,
MTU: mtu,
})
if err != nil {
return nil, err
}
return &SystemDevice{
dialer.NewDefault(router, option.DialerOptions{
BindInterface: interfaceName,
}),
tunInterface, interfaceName, int(mtu), make(chan wgTun.Event),
}, nil
}
func (w *SystemDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return w.dialer.DialContext(ctx, network, destination)
}
func (w *SystemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return w.dialer.ListenPacket(ctx, destination)
}
func (w *SystemDevice) Start() error {
w.events <- wgTun.EventUp
return nil
}
func (w *SystemDevice) File() *os.File {
return nil
}
func (w *SystemDevice) Read(bytes []byte, index int) (int, error) {
return w.device.Read(bytes[index-tunPacketOffset:])
}
func (w *SystemDevice) Write(bytes []byte, index int) (int, error) {
return w.device.Write(bytes[index:])
}
func (w *SystemDevice) Flush() error {
return nil
}
func (w *SystemDevice) MTU() (int, error) {
return w.mtu, nil
}
func (w *SystemDevice) Name() (string, error) {
return w.name, nil
}
func (w *SystemDevice) Events() chan wgTun.Event {
return w.events
}
func (w *SystemDevice) Close() error {
return w.device.Close()
}

View File

@@ -0,0 +1,37 @@
package wireguard
import (
"net/netip"
M "github.com/sagernet/sing/common/metadata"
"golang.zx2c4.com/wireguard/conn"
)
var _ conn.Endpoint = (*Endpoint)(nil)
type Endpoint M.Socksaddr
func (e Endpoint) ClearSrc() {
}
func (e Endpoint) SrcToString() string {
return ""
}
func (e Endpoint) DstToString() string {
return (M.Socksaddr)(e).String()
}
func (e Endpoint) DstToBytes() []byte {
b, _ := (M.Socksaddr)(e).AddrPort().MarshalBinary()
return b
}
func (e Endpoint) DstIP() netip.Addr {
return (M.Socksaddr)(e).Addr
}
func (e Endpoint) SrcIP() netip.Addr {
return netip.Addr{}
}

Some files were not shown because too many files have changed in this diff Show More