Add snell protocol
This commit is contained in:
@@ -11,6 +11,7 @@ const (
|
|||||||
TypeHTTP = "http"
|
TypeHTTP = "http"
|
||||||
TypeMixed = "mixed"
|
TypeMixed = "mixed"
|
||||||
TypeShadowsocks = "shadowsocks"
|
TypeShadowsocks = "shadowsocks"
|
||||||
|
TypeSnell = "snell"
|
||||||
TypeVMess = "vmess"
|
TypeVMess = "vmess"
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
@@ -67,6 +68,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "Mixed"
|
return "Mixed"
|
||||||
case TypeShadowsocks:
|
case TypeShadowsocks:
|
||||||
return "Shadowsocks"
|
return "Shadowsocks"
|
||||||
|
case TypeSnell:
|
||||||
|
return "Snell"
|
||||||
case TypeVMess:
|
case TypeVMess:
|
||||||
return "VMess"
|
return "VMess"
|
||||||
case TypeTrojan:
|
case TypeTrojan:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
||||||
| `vless` | [VLESS](./vless/) | TCP |
|
| `vless` | [VLESS](./vless/) | TCP |
|
||||||
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
||||||
|
| `snell` | [Snell](./snell/) | TCP |
|
||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
||||||
| `vless` | [VLESS](./vless/) | TCP |
|
| `vless` | [VLESS](./vless/) | TCP |
|
||||||
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
||||||
|
| `snell` | [Snell](./snell/) | TCP |
|
||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "snell",
|
||||||
|
"tag": "snell-in",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"version": 5,
|
||||||
|
"psk": "password",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "sekai",
|
||||||
|
"userkey": "user-password"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"obfs_mode": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version 6 Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "snell",
|
||||||
|
"tag": "snell-in",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"version": 6,
|
||||||
|
"psk": "password",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "sekai",
|
||||||
|
"userkey": "user-password"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### version
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The Snell protocol version, one of `5` `6`.
|
||||||
|
|
||||||
|
Version `5` supports HTTP obfuscation (`obfs_mode`); version `6` replaces it
|
||||||
|
with traffic shaping (`mode`) and requires a `psk` of 12 to 255 bytes.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
Since we intentionally do not support the QUIC proxy mode of Snell v5, the v5 wire protocol
|
||||||
|
is effectively identical to v4, so no separate v4 server or v5 client is provided.
|
||||||
|
|
||||||
|
#### psk
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The pre-shared key.
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
Snell users.
|
||||||
|
|
||||||
|
When set, the server runs in multi-user mode: each entry has a `name` (optional, used in
|
||||||
|
logs) and a `userkey` (the user's key). The top-level `psk` remains the server key.
|
||||||
|
|
||||||
|
#### obfs_mode
|
||||||
|
|
||||||
|
==Version 5 only==
|
||||||
|
|
||||||
|
HTTP obfuscation mode, one of `none` `http`.
|
||||||
|
|
||||||
|
`none` is used by default.
|
||||||
|
|
||||||
|
#### mode
|
||||||
|
|
||||||
|
==Version 6 only==
|
||||||
|
|
||||||
|
Traffic shaping mode, one of `default` `unshaped` `unsafe-raw`.
|
||||||
|
|
||||||
|
`default` is used by default.
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "snell",
|
||||||
|
"tag": "snell-in",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"version": 5,
|
||||||
|
"psk": "password",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "sekai",
|
||||||
|
"userkey": "user-password"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"obfs_mode": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 版本 6 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "snell",
|
||||||
|
"tag": "snell-in",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"version": 6,
|
||||||
|
"psk": "password",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "sekai",
|
||||||
|
"userkey": "user-password"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### version
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
Snell 协议版本,`5` `6` 之一。
|
||||||
|
|
||||||
|
版本 `5` 支持 HTTP 混淆(`obfs_mode`);版本 `6` 以流量整形(`mode`)取而代之,并要求
|
||||||
|
`psk` 长度为 12 到 255 字节。
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
由于我们有意不支持 Snell v5 的 QUIC 代理模式,v5 的线路协议实际上与 v4 没有区别,
|
||||||
|
因此不提供独立的 v4 服务器和 v5 客户端。
|
||||||
|
|
||||||
|
#### psk
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
预共享密钥。
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
Snell 用户。
|
||||||
|
|
||||||
|
设置后,服务器运行于多用户模式:每一项包含 `name`(可选,用于日志)和 `userkey`
|
||||||
|
(用户密钥)。顶层的 `psk` 仍作为服务器密钥。
|
||||||
|
|
||||||
|
#### obfs_mode
|
||||||
|
|
||||||
|
==仅版本 5==
|
||||||
|
|
||||||
|
HTTP 混淆模式,`none` `http` 之一。
|
||||||
|
|
||||||
|
默认为 `none`。
|
||||||
|
|
||||||
|
#### mode
|
||||||
|
|
||||||
|
==仅版本 6==
|
||||||
|
|
||||||
|
流量整形模式,`default` `unshaped` `unsafe-raw` 之一。
|
||||||
|
|
||||||
|
默认为 `default`。
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
| `tuic` | [TUIC](./tuic/) |
|
| `tuic` | [TUIC](./tuic/) |
|
||||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||||
| `anytls` | [AnyTLS](./anytls/) |
|
| `anytls` | [AnyTLS](./anytls/) |
|
||||||
|
| `snell` | [Snell](./snell/) |
|
||||||
| `tor` | [Tor](./tor/) |
|
| `tor` | [Tor](./tor/) |
|
||||||
| `ssh` | [SSH](./ssh/) |
|
| `ssh` | [SSH](./ssh/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
| `tuic` | [TUIC](./tuic/) |
|
| `tuic` | [TUIC](./tuic/) |
|
||||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||||
| `anytls` | [AnyTLS](./anytls/) |
|
| `anytls` | [AnyTLS](./anytls/) |
|
||||||
|
| `snell` | [Snell](./snell/) |
|
||||||
| `tor` | [Tor](./tor/) |
|
| `tor` | [Tor](./tor/) |
|
||||||
| `ssh` | [SSH](./ssh/) |
|
| `ssh` | [SSH](./ssh/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "snell",
|
||||||
|
"tag": "snell-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
"version": 4,
|
||||||
|
"psk": "password",
|
||||||
|
"userkey": "",
|
||||||
|
"reuse": false,
|
||||||
|
"network": "tcp",
|
||||||
|
"obfs_mode": "",
|
||||||
|
"obfs_host": "",
|
||||||
|
|
||||||
|
... // Dial Fields
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version 6 Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "snell",
|
||||||
|
"tag": "snell-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
"version": 6,
|
||||||
|
"psk": "password",
|
||||||
|
"userkey": "",
|
||||||
|
"reuse": false,
|
||||||
|
"network": "tcp",
|
||||||
|
"mode": "",
|
||||||
|
|
||||||
|
... // Dial Fields
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The server address.
|
||||||
|
|
||||||
|
#### server_port
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The server port.
|
||||||
|
|
||||||
|
#### version
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The Snell protocol version, one of `4` `6`.
|
||||||
|
|
||||||
|
Version `4` supports HTTP obfuscation (`obfs_mode` / `obfs_host`); version `6`
|
||||||
|
replaces it with traffic shaping (`mode`) and requires a `psk` of 12 to 255
|
||||||
|
bytes.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
Since we intentionally do not support the QUIC proxy mode of Snell v5, the v5 wire protocol
|
||||||
|
is effectively identical to v4, so no separate v4 server or v5 client is provided.
|
||||||
|
|
||||||
|
#### psk
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The pre-shared key.
|
||||||
|
|
||||||
|
#### userkey
|
||||||
|
|
||||||
|
The user key, used to authenticate against a multi-user server.
|
||||||
|
|
||||||
|
#### reuse
|
||||||
|
|
||||||
|
Enable connection reuse (the Snell v2 `CONNECT` command).
|
||||||
|
|
||||||
|
#### network
|
||||||
|
|
||||||
|
Enabled network
|
||||||
|
|
||||||
|
One of `tcp` `udp`.
|
||||||
|
|
||||||
|
Both is enabled by default.
|
||||||
|
|
||||||
|
#### obfs_mode
|
||||||
|
|
||||||
|
==Version 4 only==
|
||||||
|
|
||||||
|
HTTP obfuscation mode, one of `none` `http`.
|
||||||
|
|
||||||
|
`none` is used by default.
|
||||||
|
|
||||||
|
#### obfs_host
|
||||||
|
|
||||||
|
==Version 4 only==
|
||||||
|
|
||||||
|
The HTTP `Host` header sent when `obfs_mode` is `http`.
|
||||||
|
|
||||||
|
`bing.com` is used by default.
|
||||||
|
|
||||||
|
#### mode
|
||||||
|
|
||||||
|
==Version 6 only==
|
||||||
|
|
||||||
|
Traffic shaping mode, one of `default` `unshaped` `unsafe-raw`.
|
||||||
|
|
||||||
|
`default` is used by default.
|
||||||
|
|
||||||
|
### Dial Fields
|
||||||
|
|
||||||
|
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "snell",
|
||||||
|
"tag": "snell-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
"version": 4,
|
||||||
|
"psk": "password",
|
||||||
|
"userkey": "",
|
||||||
|
"reuse": false,
|
||||||
|
"network": "tcp",
|
||||||
|
"obfs_mode": "",
|
||||||
|
"obfs_host": "",
|
||||||
|
|
||||||
|
... // 拨号字段
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 版本 6 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "snell",
|
||||||
|
"tag": "snell-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
"version": 6,
|
||||||
|
"psk": "password",
|
||||||
|
"userkey": "",
|
||||||
|
"reuse": false,
|
||||||
|
"network": "tcp",
|
||||||
|
"mode": "",
|
||||||
|
|
||||||
|
... // 拨号字段
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
服务器地址。
|
||||||
|
|
||||||
|
#### server_port
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
服务器端口。
|
||||||
|
|
||||||
|
#### version
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
Snell 协议版本,`4` `6` 之一。
|
||||||
|
|
||||||
|
版本 `4` 支持 HTTP 混淆(`obfs_mode` / `obfs_host`);版本 `6` 以流量整形(`mode`)
|
||||||
|
取而代之,并要求 `psk` 长度为 12 到 255 字节。
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
由于我们有意不支持 Snell v5 的 QUIC 代理模式,v5 的线路协议实际上与 v4 没有区别,
|
||||||
|
因此不提供独立的 v4 服务器和 v5 客户端。
|
||||||
|
|
||||||
|
#### psk
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
预共享密钥。
|
||||||
|
|
||||||
|
#### userkey
|
||||||
|
|
||||||
|
用户密钥,用于向多用户服务器进行认证。
|
||||||
|
|
||||||
|
#### reuse
|
||||||
|
|
||||||
|
启用连接复用(Snell v2 `CONNECT` 命令)。
|
||||||
|
|
||||||
|
#### network
|
||||||
|
|
||||||
|
启用的网络协议。
|
||||||
|
|
||||||
|
`tcp` 或 `udp`。
|
||||||
|
|
||||||
|
默认所有。
|
||||||
|
|
||||||
|
#### obfs_mode
|
||||||
|
|
||||||
|
==仅版本 4==
|
||||||
|
|
||||||
|
HTTP 混淆模式,`none` `http` 之一。
|
||||||
|
|
||||||
|
默认为 `none`。
|
||||||
|
|
||||||
|
#### obfs_host
|
||||||
|
|
||||||
|
==仅版本 4==
|
||||||
|
|
||||||
|
`obfs_mode` 为 `http` 时发送的 HTTP `Host` 头。
|
||||||
|
|
||||||
|
默认为 `bing.com`。
|
||||||
|
|
||||||
|
#### mode
|
||||||
|
|
||||||
|
==仅版本 6==
|
||||||
|
|
||||||
|
流量整形模式,`default` `unshaped` `unsafe-raw` 之一。
|
||||||
|
|
||||||
|
默认为 `default`。
|
||||||
|
|
||||||
|
### 拨号字段
|
||||||
|
|
||||||
|
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||||
@@ -41,13 +41,14 @@ require (
|
|||||||
github.com/sagernet/gomobile v0.1.12
|
github.com/sagernet/gomobile v0.1.12
|
||||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
||||||
github.com/sagernet/sing v0.8.12-0.20260702074557-63e2fd680d8a
|
github.com/sagernet/sing v0.8.12-0.20260702081104-2ded2af32d3d
|
||||||
github.com/sagernet/sing-cloudflared v0.1.2
|
github.com/sagernet/sing-cloudflared v0.1.2
|
||||||
github.com/sagernet/sing-mux v0.3.5
|
github.com/sagernet/sing-mux v0.3.5
|
||||||
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7
|
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1
|
github.com/sagernet/sing-shadowtls v0.2.1
|
||||||
|
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814
|
||||||
github.com/sagernet/sing-tun v0.8.12-0.20260629021427-b3c6babbd353
|
github.com/sagernet/sing-tun v0.8.12-0.20260629021427-b3c6babbd353
|
||||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb
|
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||||
|
|||||||
@@ -258,8 +258,8 @@ github.com/sagernet/nftables v0.3.0-mod.2 h1:ck2KMU02OxL1eDFgGaWYglMDpoOZ7OHzxje
|
|||||||
github.com/sagernet/nftables v0.3.0-mod.2/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ=
|
github.com/sagernet/nftables v0.3.0-mod.2/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ=
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||||
github.com/sagernet/sing v0.8.12-0.20260702074557-63e2fd680d8a h1:I8FFJ8IOS+vUSNIq7L1pzBv0fMI507FEpe2CzmT0Jz0=
|
github.com/sagernet/sing v0.8.12-0.20260702081104-2ded2af32d3d h1:BhsQU0Iug1tU4xR52cjm8Sc+LBo+KwdyLTRn3ie9moo=
|
||||||
github.com/sagernet/sing v0.8.12-0.20260702074557-63e2fd680d8a/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
|
github.com/sagernet/sing v0.8.12-0.20260702081104-2ded2af32d3d/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
|
||||||
github.com/sagernet/sing-cloudflared v0.1.2 h1:rEz98+q2nvbNlQXaDXJQlRKngK81FHK+rm7pNCSLXB4=
|
github.com/sagernet/sing-cloudflared v0.1.2 h1:rEz98+q2nvbNlQXaDXJQlRKngK81FHK+rm7pNCSLXB4=
|
||||||
github.com/sagernet/sing-cloudflared v0.1.2/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y=
|
github.com/sagernet/sing-cloudflared v0.1.2/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y=
|
||||||
github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI=
|
github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI=
|
||||||
@@ -272,6 +272,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
|
|||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkGCKNXhbaM=
|
github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkGCKNXhbaM=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
|
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814 h1:xfnkRpjVRVeJhVvDZA8PzTLlKGTb1o2kdI4uv1YymXo=
|
||||||
|
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY=
|
||||||
github.com/sagernet/sing-tun v0.8.12-0.20260629021427-b3c6babbd353 h1:HA0TGrBQSFfvcoVXL1DxzF+i8pmaGOGb33jdReB7L4s=
|
github.com/sagernet/sing-tun v0.8.12-0.20260629021427-b3c6babbd353 h1:HA0TGrBQSFfvcoVXL1DxzF+i8pmaGOGb33jdReB7L4s=
|
||||||
github.com/sagernet/sing-tun v0.8.12-0.20260629021427-b3c6babbd353/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
|
github.com/sagernet/sing-tun v0.8.12-0.20260629021427-b3c6babbd353/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
|
||||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0=
|
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0=
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/protocol/redirect"
|
"github.com/sagernet/sing-box/protocol/redirect"
|
||||||
"github.com/sagernet/sing-box/protocol/shadowsocks"
|
"github.com/sagernet/sing-box/protocol/shadowsocks"
|
||||||
"github.com/sagernet/sing-box/protocol/shadowtls"
|
"github.com/sagernet/sing-box/protocol/shadowtls"
|
||||||
|
"github.com/sagernet/sing-box/protocol/snell"
|
||||||
"github.com/sagernet/sing-box/protocol/socks"
|
"github.com/sagernet/sing-box/protocol/socks"
|
||||||
"github.com/sagernet/sing-box/protocol/ssh"
|
"github.com/sagernet/sing-box/protocol/ssh"
|
||||||
"github.com/sagernet/sing-box/protocol/tor"
|
"github.com/sagernet/sing-box/protocol/tor"
|
||||||
@@ -60,6 +61,7 @@ func InboundRegistry() *inbound.Registry {
|
|||||||
mixed.RegisterInbound(registry)
|
mixed.RegisterInbound(registry)
|
||||||
|
|
||||||
shadowsocks.RegisterInbound(registry)
|
shadowsocks.RegisterInbound(registry)
|
||||||
|
snell.RegisterInbound(registry)
|
||||||
vmess.RegisterInbound(registry)
|
vmess.RegisterInbound(registry)
|
||||||
trojan.RegisterInbound(registry)
|
trojan.RegisterInbound(registry)
|
||||||
naive.RegisterInbound(registry)
|
naive.RegisterInbound(registry)
|
||||||
@@ -87,6 +89,7 @@ func OutboundRegistry() *outbound.Registry {
|
|||||||
socks.RegisterOutbound(registry)
|
socks.RegisterOutbound(registry)
|
||||||
http.RegisterOutbound(registry)
|
http.RegisterOutbound(registry)
|
||||||
shadowsocks.RegisterOutbound(registry)
|
shadowsocks.RegisterOutbound(registry)
|
||||||
|
snell.RegisterOutbound(registry)
|
||||||
vmess.RegisterOutbound(registry)
|
vmess.RegisterOutbound(registry)
|
||||||
trojan.RegisterOutbound(registry)
|
trojan.RegisterOutbound(registry)
|
||||||
registerNaiveOutbound(registry)
|
registerNaiveOutbound(registry)
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ nav:
|
|||||||
- TUIC: configuration/inbound/tuic.md
|
- TUIC: configuration/inbound/tuic.md
|
||||||
- Hysteria2: configuration/inbound/hysteria2.md
|
- Hysteria2: configuration/inbound/hysteria2.md
|
||||||
- AnyTLS: configuration/inbound/anytls.md
|
- AnyTLS: configuration/inbound/anytls.md
|
||||||
|
- Snell: configuration/inbound/snell.md
|
||||||
- Tun: configuration/inbound/tun.md
|
- Tun: configuration/inbound/tun.md
|
||||||
- Redirect: configuration/inbound/redirect.md
|
- Redirect: configuration/inbound/redirect.md
|
||||||
- TProxy: configuration/inbound/tproxy.md
|
- TProxy: configuration/inbound/tproxy.md
|
||||||
@@ -180,6 +181,7 @@ nav:
|
|||||||
- TUIC: configuration/outbound/tuic.md
|
- TUIC: configuration/outbound/tuic.md
|
||||||
- Hysteria2: configuration/outbound/hysteria2.md
|
- Hysteria2: configuration/outbound/hysteria2.md
|
||||||
- AnyTLS: configuration/outbound/anytls.md
|
- AnyTLS: configuration/outbound/anytls.md
|
||||||
|
- Snell: configuration/outbound/snell.md
|
||||||
- Tor: configuration/outbound/tor.md
|
- Tor: configuration/outbound/tor.md
|
||||||
- SSH: configuration/outbound/ssh.md
|
- SSH: configuration/outbound/ssh.md
|
||||||
- DNS: configuration/outbound/dns.md
|
- DNS: configuration/outbound/dns.md
|
||||||
|
|||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _SnellInboundOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
Version int `json:"version"`
|
||||||
|
PSK string `json:"psk"`
|
||||||
|
Users []SnellUser `json:"users,omitempty"`
|
||||||
|
ObfsOptions SnellObfsServerOptions `json:"-"`
|
||||||
|
V6Options SnellV6Options `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnellInboundOptions _SnellInboundOptions
|
||||||
|
|
||||||
|
func (o *SnellInboundOptions) UnmarshalJSON(content []byte) error {
|
||||||
|
err := json.Unmarshal(content, (*_SnellInboundOptions)(o))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var versionOptions any
|
||||||
|
switch o.Version {
|
||||||
|
case 5:
|
||||||
|
versionOptions = &o.ObfsOptions
|
||||||
|
case 6:
|
||||||
|
versionOptions = &o.V6Options
|
||||||
|
case 0:
|
||||||
|
return E.New("snell: missing version")
|
||||||
|
default:
|
||||||
|
return E.New("snell: unsupported version: ", o.Version)
|
||||||
|
}
|
||||||
|
return badjson.UnmarshallExcluded(content, (*_SnellInboundOptions)(o), versionOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o SnellInboundOptions) MarshalJSON() ([]byte, error) {
|
||||||
|
var versionOptions any
|
||||||
|
switch o.Version {
|
||||||
|
case 5:
|
||||||
|
versionOptions = o.ObfsOptions
|
||||||
|
case 6:
|
||||||
|
versionOptions = o.V6Options
|
||||||
|
case 0:
|
||||||
|
return nil, E.New("snell: missing version")
|
||||||
|
default:
|
||||||
|
return nil, E.New("snell: unsupported version: ", o.Version)
|
||||||
|
}
|
||||||
|
return badjson.MarshallObjects((_SnellInboundOptions)(o), versionOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
type _SnellOutboundOptions struct {
|
||||||
|
DialerOptions
|
||||||
|
ServerOptions
|
||||||
|
Version int `json:"version"`
|
||||||
|
PSK string `json:"psk"`
|
||||||
|
UserKey string `json:"userkey,omitempty"`
|
||||||
|
Reuse bool `json:"reuse,omitempty"`
|
||||||
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
ObfsOptions SnellObfsClientOptions `json:"-"`
|
||||||
|
V6Options SnellV6Options `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnellOutboundOptions _SnellOutboundOptions
|
||||||
|
|
||||||
|
func (o *SnellOutboundOptions) UnmarshalJSON(content []byte) error {
|
||||||
|
err := json.Unmarshal(content, (*_SnellOutboundOptions)(o))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var versionOptions any
|
||||||
|
switch o.Version {
|
||||||
|
case 4:
|
||||||
|
versionOptions = &o.ObfsOptions
|
||||||
|
case 6:
|
||||||
|
versionOptions = &o.V6Options
|
||||||
|
case 0:
|
||||||
|
return E.New("snell: missing version")
|
||||||
|
default:
|
||||||
|
return E.New("snell: unsupported version: ", o.Version)
|
||||||
|
}
|
||||||
|
return badjson.UnmarshallExcluded(content, (*_SnellOutboundOptions)(o), versionOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o SnellOutboundOptions) MarshalJSON() ([]byte, error) {
|
||||||
|
var versionOptions any
|
||||||
|
switch o.Version {
|
||||||
|
case 4:
|
||||||
|
versionOptions = o.ObfsOptions
|
||||||
|
case 6:
|
||||||
|
versionOptions = o.V6Options
|
||||||
|
case 0:
|
||||||
|
return nil, E.New("snell: missing version")
|
||||||
|
default:
|
||||||
|
return nil, E.New("snell: unsupported version: ", o.Version)
|
||||||
|
}
|
||||||
|
return badjson.MarshallObjects((_SnellOutboundOptions)(o), versionOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnellObfsServerOptions struct {
|
||||||
|
ObfsMode string `json:"obfs_mode,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnellUser struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
UserKey string `json:"userkey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnellObfsClientOptions struct {
|
||||||
|
ObfsMode string `json:"obfs_mode,omitempty"`
|
||||||
|
ObfsHost string `json:"obfs_host,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnellV6Options struct {
|
||||||
|
Mode string `json:"mode,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
package snell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
|
"github.com/sagernet/sing-box/common/uot"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
snellprotocol "github.com/sagernet/sing-snell"
|
||||||
|
"github.com/sagernet/sing-snell/snellv5"
|
||||||
|
"github.com/sagernet/sing-snell/snellv6"
|
||||||
|
"github.com/sagernet/sing/common/auth"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterInbound(registry *inbound.Registry) {
|
||||||
|
inbound.Register[option.SnellInboundOptions](registry, C.TypeSnell, NewInbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.TCPInjectableInbound = (*Inbound)(nil)
|
||||||
|
|
||||||
|
type Inbound struct {
|
||||||
|
inbound.Adapter
|
||||||
|
router adapter.ConnectionRouterEx
|
||||||
|
logger logger.ContextLogger
|
||||||
|
listener *listener.Listener
|
||||||
|
service snellprotocol.Service
|
||||||
|
users []option.SnellUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SnellInboundOptions) (adapter.Inbound, error) {
|
||||||
|
inbound := &Inbound{
|
||||||
|
Adapter: inbound.NewAdapter(C.TypeSnell, tag),
|
||||||
|
router: uot.NewRouter(router, logger),
|
||||||
|
logger: logger,
|
||||||
|
users: options.Users,
|
||||||
|
}
|
||||||
|
var userList []int
|
||||||
|
var keyList [][]byte
|
||||||
|
if len(options.Users) > 0 {
|
||||||
|
userList = make([]int, len(options.Users))
|
||||||
|
keyList = make([][]byte, len(options.Users))
|
||||||
|
for index, user := range options.Users {
|
||||||
|
userList[index] = index
|
||||||
|
keyList[index] = []byte(user.UserKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
switch options.Version {
|
||||||
|
case 5:
|
||||||
|
var obfsMode snellprotocol.ObfsMode
|
||||||
|
obfsMode, err = snellprotocol.ParseObfsMode(options.ObfsOptions.ObfsMode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serviceOptions := snellv5.ServiceOptions{
|
||||||
|
PSK: []byte(options.PSK),
|
||||||
|
ObfsMode: obfsMode,
|
||||||
|
Handler: inbound,
|
||||||
|
}
|
||||||
|
if len(options.Users) > 0 {
|
||||||
|
var service *snellv5.MultiService[int]
|
||||||
|
service, err = snellv5.NewMultiService[int](serviceOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = service.UpdateUsers(userList, keyList)
|
||||||
|
inbound.service = service
|
||||||
|
} else {
|
||||||
|
inbound.service, err = snellv5.NewService(serviceOptions)
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
var mode snellv6.Mode
|
||||||
|
mode, err = snellv6.ParseMode(options.V6Options.Mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serviceOptions := snellv6.ServerOptions{
|
||||||
|
PSK: []byte(options.PSK),
|
||||||
|
Mode: mode,
|
||||||
|
Handler: inbound,
|
||||||
|
}
|
||||||
|
if len(options.Users) > 0 {
|
||||||
|
var service *snellv6.MultiService[int]
|
||||||
|
service, err = snellv6.NewMultiService[int](serviceOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = service.UpdateUsers(userList, keyList)
|
||||||
|
inbound.service = service
|
||||||
|
} else {
|
||||||
|
inbound.service, err = snellv6.NewService(serviceOptions)
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
return nil, E.New("snell: missing version")
|
||||||
|
default:
|
||||||
|
return nil, E.New("snell: unsupported version: ", options.Version)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inbound.listener = listener.New(listener.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Logger: logger,
|
||||||
|
Network: []string{N.NetworkTCP},
|
||||||
|
Listen: options.ListenOptions,
|
||||||
|
ConnectionHandler: inbound,
|
||||||
|
})
|
||||||
|
return inbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return h.listener.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) Close() error {
|
||||||
|
return h.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
|
||||||
|
if err != nil {
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
|
if E.IsClosedOrCanceled(err) {
|
||||||
|
h.logger.DebugContext(ctx, "connection closed: ", err)
|
||||||
|
} else {
|
||||||
|
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
_, metadata := adapter.ExtendContext(ctx)
|
||||||
|
if source.IsValid() {
|
||||||
|
metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
metadata.Destination = destination
|
||||||
|
}
|
||||||
|
h.newConnection(ctx, conn, *metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
_, metadata := adapter.ExtendContext(ctx)
|
||||||
|
if source.IsValid() {
|
||||||
|
metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
metadata.Destination = destination
|
||||||
|
}
|
||||||
|
h.newPacketConnection(ctx, conn, *metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
metadata.Inbound = h.Tag()
|
||||||
|
metadata.InboundType = h.Type()
|
||||||
|
if len(h.users) > 0 {
|
||||||
|
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||||
|
if !loaded {
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := h.users[userIndex].Name
|
||||||
|
if user == "" {
|
||||||
|
user = F.ToString(userIndex)
|
||||||
|
} else {
|
||||||
|
metadata.User = user
|
||||||
|
}
|
||||||
|
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
||||||
|
} else {
|
||||||
|
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||||
|
}
|
||||||
|
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
metadata.Inbound = h.Tag()
|
||||||
|
metadata.InboundType = h.Type()
|
||||||
|
if len(h.users) > 0 {
|
||||||
|
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||||
|
if !loaded {
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := h.users[userIndex].Name
|
||||||
|
if user == "" {
|
||||||
|
user = F.ToString(userIndex)
|
||||||
|
} else {
|
||||||
|
metadata.User = user
|
||||||
|
}
|
||||||
|
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection from ", metadata.Source)
|
||||||
|
} else {
|
||||||
|
h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||||
|
}
|
||||||
|
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package snell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"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"
|
||||||
|
snellprotocol "github.com/sagernet/sing-snell"
|
||||||
|
"github.com/sagernet/sing-snell/snellv4"
|
||||||
|
"github.com/sagernet/sing-snell/snellv6"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.SnellOutboundOptions](registry, C.TypeSnell, NewOutbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Outbound struct {
|
||||||
|
outbound.Adapter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
dialer N.Dialer
|
||||||
|
client snellClient
|
||||||
|
serverAddr M.Socksaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
type snellClient interface {
|
||||||
|
snellprotocol.Method
|
||||||
|
DialContext(ctx context.Context, destination M.Socksaddr) (net.Conn, error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SnellOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverAddr := options.ServerOptions.Build()
|
||||||
|
var client snellClient
|
||||||
|
switch options.Version {
|
||||||
|
case 4:
|
||||||
|
var obfsMode snellprotocol.ObfsMode
|
||||||
|
obfsMode, err = snellprotocol.ParseObfsMode(options.ObfsOptions.ObfsMode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client, err = snellv4.NewClient(snellv4.ClientOptions{
|
||||||
|
PSK: []byte(options.PSK),
|
||||||
|
UserKey: []byte(options.UserKey),
|
||||||
|
Reuse: options.Reuse,
|
||||||
|
ObfsMode: obfsMode,
|
||||||
|
ObfsHost: options.ObfsOptions.ObfsHost,
|
||||||
|
Dialer: outboundDialer,
|
||||||
|
Server: serverAddr,
|
||||||
|
})
|
||||||
|
case 6:
|
||||||
|
var mode snellv6.Mode
|
||||||
|
mode, err = snellv6.ParseMode(options.V6Options.Mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client, err = snellv6.NewClient(snellv6.ClientOptions{
|
||||||
|
PSK: []byte(options.PSK),
|
||||||
|
UserKey: []byte(options.UserKey),
|
||||||
|
Mode: mode,
|
||||||
|
Reuse: options.Reuse,
|
||||||
|
Dialer: outboundDialer,
|
||||||
|
Server: serverAddr,
|
||||||
|
})
|
||||||
|
case 0:
|
||||||
|
return nil, E.New("snell: missing version")
|
||||||
|
default:
|
||||||
|
return nil, E.New("snell: unsupported version: ", options.Version)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outbound := &Outbound{
|
||||||
|
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSnell, tag, options.Network.Build(), options.DialerOptions),
|
||||||
|
logger: logger,
|
||||||
|
dialer: outboundDialer,
|
||||||
|
client: client,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
}
|
||||||
|
return outbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
|
metadata.Outbound = h.Tag()
|
||||||
|
metadata.Destination = destination
|
||||||
|
networkName := N.NetworkName(network)
|
||||||
|
switch networkName {
|
||||||
|
case N.NetworkTCP:
|
||||||
|
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
|
return h.client.DialContext(ctx, destination)
|
||||||
|
case N.NetworkUDP:
|
||||||
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
|
conn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
packetConn, err := h.client.DialPacketConn(conn)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bufio.NewBindPacketConn(packetConn, destination), nil
|
||||||
|
default:
|
||||||
|
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
|
metadata.Outbound = h.Tag()
|
||||||
|
metadata.Destination = destination
|
||||||
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
|
conn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
packetConn, err := h.client.DialPacketConn(conn)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return packetConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) Close() error {
|
||||||
|
return h.client.Close()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user