Compare commits

..

14 Commits

Author SHA1 Message Date
世界
8de0fad9f5 documentation: Bump version 2023-11-07 10:20:05 +08:00
世界
e05bf6308e Fix build script 2023-11-07 10:20:05 +08:00
世界
a20a0cb455 Add broadcast filter 2023-11-07 10:20:05 +08:00
世界
d29f7475d2 documentation: Bump version 2023-11-06 19:37:45 +08:00
世界
aaa6702863 Fix Host ignored in v2ray websocket transport 2023-11-05 23:27:11 +08:00
世界
bb928f096a Fix missing default next proto in hysteria2 2023-11-05 23:11:40 +08:00
johnthecoderpro
9f01d5c5b4 Fix download geo resources 2023-11-05 16:03:15 +08:00
世界
11629a931b Update release script 2023-11-05 16:02:18 +08:00
世界
126f825241 Update dependencies 2023-11-05 16:02:10 +08:00
世界
998cc7bd22 Add multicast filter for tun 2023-11-04 08:04:17 +08:00
世界
3efccaa8f5 Update dependencies 2023-10-31 18:24:58 +08:00
世界
d57b35ec30 documentation: Add privacy policy for android 2023-10-31 17:32:18 +08:00
世界
e82dab027d documentation: Bump version 2023-10-30 13:59:49 +08:00
世界
9350f3983b docs: Remove obsolete fields 2023-10-30 13:59:49 +08:00
30 changed files with 352 additions and 368 deletions

View File

@@ -84,6 +84,9 @@ upload_android:
release_android: lib_android update_android_version build_android upload_android
publish_android:
cd ../sing-box-for-android && ./gradlew :app:publishReleaseBundle
publish_android_appcenter:
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadRelease
build_ios:
@@ -149,10 +152,8 @@ update_apple_version:
go run ./cmd/internal/update_apple_version
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
rm -rf dist
release_apple_beta: update_apple_version release_ios release_macos release_tvos
rm -rf dist
test:
@go test -v ./... && \

View File

@@ -6,7 +6,6 @@ import (
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -45,14 +44,7 @@ func (d *DetourDialer) DialContext(ctx context.Context, network string, destinat
if err != nil {
return nil, err
}
conn, err := dialer.DialContext(ctx, network, destination)
if err != nil {
return nil, err
}
if deadline.NeedAdditionalReadDeadline(conn) {
conn = deadline.NewConn(conn)
}
return conn, nil
return dialer.DialContext(ctx, network, destination)
}
func (d *DetourDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {

View File

@@ -1,20 +1,36 @@
#### 1.7.0-alpha.2
#### 1.6.2
* Fix bugs introduced in 1.7.0-alpha.1
#### 1.7.0-alpha.1
* Add [exclude route support](/configuration/inbound/tun) for TUN inbound
* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen) **1**
* Fixes and improvements
#### 1.6.1
* Our [Android client](/installation/clients/sfa) is now available in the Google Play Store ▶️
* Fixes and improvements
#### 1.6.0
* Fixes and improvements
Important changes since 1.5:
* Our [Apple tvOS client](/installation/clients/sft) is now available in the App Store 🍎
* Update BBR congestion control for TUIC and Hysteria2 **1**
* Update brutal congestion control for Hysteria2
* Add `brutal_debug` option for Hysteria2
* Update legacy Hysteria protocol **2**
* Add TLS self sign key pair generate command
* Remove [Deprecated Features](/deprecated) by agreement
**1**:
If enabled, for UDP proxy requests addressed to a domain,
the original packet address will be sent in the response instead of the mapped domain.
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
This update is intended to address the multi-send defects of the old implementation and may introduce new issues.
**2**
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of
the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
This option is used for compatibility with clients that
do not support receiving UDP packets with domain addresses, such as Surge.
#### 1.5.5

View File

@@ -22,12 +22,6 @@
"::/1",
"8000::/1"
],
"inet4_route_exclude_address": [
"192.168.0.0/16"
],
"inet6_route_exclude_address": [
"fc00::/7"
],
"endpoint_independent_nat": false,
"stack": "system",
"include_interface": [
@@ -136,14 +130,6 @@ Use custom routes instead of default when `auto_route` is enabled.
Use custom routes instead of default when `auto_route` is enabled.
#### inet4_route_exclude_address
Exclude custom routes when `auto_route` is enabled.
#### inet6_route_exclude_address
Exclude custom routes when `auto_route` is enabled.
#### endpoint_independent_nat
!!! info ""

View File

@@ -22,12 +22,6 @@
"::/1",
"8000::/1"
],
"inet4_route_exclude_address": [
"192.168.0.0/16"
],
"inet6_route_exclude_address": [
"fc00::/7"
],
"endpoint_independent_nat": false,
"stack": "system",
"include_interface": [
@@ -137,14 +131,6 @@ tun 接口的 IPv6 前缀。
启用 `auto_route` 时使用自定义路由而不是默认路由。
#### inet4_route_exclude_address
启用 `auto_route` 时排除自定义路由。
#### inet6_route_exclude_address
启用 `auto_route` 时排除自定义路由。
#### endpoint_independent_nat
启用独立于端点的 NAT。

View File

@@ -7,26 +7,28 @@
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"udp_timeout": 300,
"detour": "another-in",
"sniff": false,
"sniff_override_destination": false,
"sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6",
"udp_disable_domain_unmapping": false
"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. |
| `tcp_multi_path` | Needs to listen on TCP. |
| `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
| `udp_disable_domain_unmapping` | Needs to listen on UDP and accept domain UDP addresses. |
| 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. |
| `tcp_multi_path` | 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
@@ -54,16 +56,6 @@ Enable TCP Multi Path.
Enable UDP fragmentation.
#### udp_timeout
UDP NAT expiration time in seconds, default is 300 (5 minutes).
#### detour
If set, connections will be forwarded to the specified inbound.
Requires target inbound support, see [Injectable](/configuration/inbound/#fields).
#### sniff
Enable sniffing.
@@ -90,10 +82,20 @@ If set, the requested domain name will be resolved to IP before routing.
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
#### udp_disable_domain_unmapping
#### udp_timeout
If enabled, for UDP proxy requests addressed to a domain,
the original packet address will be sent in the response instead of the mapped domain.
UDP NAT expiration time in seconds, default is 300 (5 minutes).
This option is used for compatibility with clients that
do not support receiving UDP packets with domain addresses, such as Surge.
#### proxy_protocol
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.
Requires target inbound support, see [Injectable](/configuration/inbound/#fields).

View File

@@ -7,13 +7,14 @@
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"udp_timeout": 300,
"detour": "another-in",
"sniff": false,
"sniff_override_destination": false,
"sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6",
"udp_disable_domain_unmapping": false
"udp_timeout": 300,
"proxy_protocol": false,
"proxy_protocol_accept_no_header": false,
"detour": "another-in"
}
```
@@ -25,7 +26,8 @@
| `tcp_fast_open` | 需要监听 TCP。 |
| `tcp_multi_path` | 需要监听 TCP。 |
| `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 |
|
| `proxy_protocol` | 需要监听 TCP。 |
| `proxy_protocol_accept_no_header` | `proxy_protocol` 启用时 |
### 字段
@@ -55,16 +57,6 @@
启用 UDP 分段。
#### udp_timeout
UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)。
#### detour
如果设置,连接将被转发到指定的入站。
需要目标入站支持,参阅 [注入支持](/zh/configuration/inbound/#_3)。
#### sniff
启用协议探测。
@@ -91,8 +83,20 @@ UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)。
如果 `sniff_override_destination` 生效,它的值将作为后备。
#### udp_disable_domain_unmapping
#### udp_timeout
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域
UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
#### proxy_protocol
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
#### proxy_protocol_accept_no_header
接受没有代理协议标头的连接。
#### detour
如果设置,连接将被转发到指定的入站。
需要目标入站支持,参阅 [注入支持](/zh/configuration/inbound/#_3)。

View File

@@ -8,7 +8,7 @@ Experimental Android client for sing-box.
#### Download
* [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest)
* [Play Store](https://play.google.com/store/apps/details?id=io.nekohasekai.sfa)
* [Github Releases](https://github.com/SagerNet/sing-box/releases)
#### Note
@@ -16,3 +16,8 @@ Experimental Android client for sing-box.
* User Agent in remote profile request is `SFA/$version ($version_code; sing-box $sing_box_version)`
* The working directory is located at `/sdcard/Android/data/io.nekohasekai.sfa/files` (External files directory)
* Crash logs is located in `$working_directory/stderr.log`
#### Privacy policy
* SFA did not collect or share personal data.
* The data generated by the software is always on your device.

View File

@@ -16,3 +16,8 @@
* 远程配置文件请求中的 User Agent 为 `SFA/$version ($version_code; sing-box $sing_box_version)`
* 工作目录位于 `/sdcard/Android/data/io.nekohasekai.sfa/files` (外部文件目录)
* 崩溃日志位于 `$working_directory/stderr.log`
#### 隐私政策
* SFA 不收集或共享个人数据。
* 软件生成的数据始终在您的设备上。

View File

@@ -2,14 +2,12 @@ package clashapi
import (
"bytes"
"net"
"net/http"
"time"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
"github.com/sagernet/ws"
"github.com/sagernet/ws/wsutil"
"github.com/sagernet/websocket"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
@@ -29,16 +27,16 @@ type Memory struct {
func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var conn net.Conn
if r.Header.Get("Upgrade") == "websocket" {
var wsConn *websocket.Conn
if websocket.IsWebSocketUpgrade(r) {
var err error
conn, _, _, err = ws.UpgradeHTTP(r, w)
wsConn, err = upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
}
if conn == nil {
if wsConn == nil {
w.Header().Set("Content-Type", "application/json")
render.Status(r, http.StatusOK)
}
@@ -65,12 +63,13 @@ func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r
}); err != nil {
break
}
if conn == nil {
if wsConn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsutil.WriteServerText(conn, buf.Bytes())
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
}
if err != nil {
break
}

View File

@@ -9,8 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
"github.com/sagernet/ws"
"github.com/sagernet/ws/wsutil"
"github.com/sagernet/websocket"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
@@ -26,13 +25,13 @@ func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manag
func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Upgrade") != "websocket" {
if !websocket.IsWebSocketUpgrade(r) {
snapshot := trafficManager.Snapshot()
render.JSON(w, r, snapshot)
return
}
conn, _, _, err := ws.UpgradeHTTP(r, w)
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
@@ -57,7 +56,7 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW
if err := json.NewEncoder(buf).Encode(snapshot); err != nil {
return err
}
return wsutil.WriteServerText(conn, buf.Bytes())
return conn.WriteMessage(websocket.TextMessage, buf.Bytes())
}
if err = sendSnapshot(); err != nil {

View File

@@ -25,8 +25,7 @@ import (
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager"
"github.com/sagernet/ws"
"github.com/sagernet/ws/wsutil"
"github.com/sagernet/websocket"
"github.com/go-chi/chi/v5"
"github.com/go-chi/cors"
@@ -315,7 +314,7 @@ func authentication(serverSecret string) func(next http.Handler) http.Handler {
}
// Browser websocket not support custom header
if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" {
if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" {
token := r.URL.Query().Get("token")
if token != serverSecret {
render.Status(r, http.StatusUnauthorized)
@@ -352,6 +351,12 @@ func hello(redirect bool) func(w http.ResponseWriter, r *http.Request) {
}
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type Traffic struct {
Up int64 `json:"up"`
Down int64 `json:"down"`
@@ -359,17 +364,16 @@ type Traffic struct {
func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var conn net.Conn
if r.Header.Get("Upgrade") == "websocket" {
var wsConn *websocket.Conn
if websocket.IsWebSocketUpgrade(r) {
var err error
conn, _, _, err = ws.UpgradeHTTP(r, w)
wsConn, err = upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
}
if conn == nil {
if wsConn == nil {
w.Header().Set("Content-Type", "application/json")
render.Status(r, http.StatusOK)
}
@@ -388,11 +392,11 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter,
break
}
if conn == nil {
if wsConn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsutil.WriteServerText(conn, buf.Bytes())
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
}
if err != nil {
@@ -428,16 +432,16 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
}
defer logFactory.UnSubscribe(subscription)
var conn net.Conn
if r.Header.Get("Upgrade") == "websocket" {
conn, _, _, err = ws.UpgradeHTTP(r, w)
var wsConn *websocket.Conn
if websocket.IsWebSocketUpgrade(r) {
var err error
wsConn, err = upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
}
if conn == nil {
if wsConn == nil {
w.Header().Set("Content-Type", "application/json")
render.Status(r, http.StatusOK)
}
@@ -461,11 +465,11 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
if err != nil {
break
}
if conn == nil {
if wsConn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsutil.WriteServerText(conn, buf.Bytes())
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
}
if err != nil {

View File

@@ -115,11 +115,7 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
if len(options.IncludeAndroidUser) > 0 {
return nil, E.New("android: unsupported android_user option")
}
routeRanges, err := options.BuildAutoRouteRanges()
if err != nil {
return nil, err
}
tunFd, err := w.iif.OpenTun(&tunOptions{options, routeRanges, platformOptions})
tunFd, err := w.iif.OpenTun(&tunOptions{options, platformOptions})
if err != nil {
return nil, err
}

View File

@@ -60,7 +60,6 @@ var _ TunOptions = (*tunOptions)(nil)
type tunOptions struct {
*tun.Options
routeRanges []netip.Prefix
option.TunPlatformOptions
}
@@ -92,15 +91,11 @@ func (o *tunOptions) GetStrictRoute() bool {
}
func (o *tunOptions) GetInet4RouteAddress() RoutePrefixIterator {
return mapRoutePrefix(common.Filter(o.routeRanges, func(it netip.Prefix) bool {
return it.Addr().Is4()
}))
return mapRoutePrefix(o.Inet4RouteAddress)
}
func (o *tunOptions) GetInet6RouteAddress() RoutePrefixIterator {
return mapRoutePrefix(common.Filter(o.routeRanges, func(it netip.Prefix) bool {
return it.Addr().Is6()
}))
return mapRoutePrefix(o.Inet6RouteAddress)
}
func (o *tunOptions) GetIncludePackage() StringIterator {

16
go.mod
View File

@@ -5,7 +5,7 @@ go 1.20
require (
berty.tech/go-libtor v1.0.385
github.com/caddyserver/certmagic v0.19.2
github.com/cloudflare/circl v1.3.5
github.com/cloudflare/circl v1.3.6
github.com/cretz/bine v0.2.0
github.com/fsnotify/fsnotify v1.7.0
github.com/go-chi/chi/v5 v5.0.10
@@ -26,27 +26,27 @@ require (
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2
github.com/sagernet/sing v0.2.17
github.com/sagernet/sing-dns v0.1.10
github.com/sagernet/sing-mux v0.1.3
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6
github.com/sagernet/sing-quic v0.1.3
github.com/sagernet/sing-shadowsocks v0.2.5
github.com/sagernet/sing-shadowsocks2 v0.1.4
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1
github.com/sagernet/sing-tun v0.1.19
github.com/sagernet/sing-vmess v0.1.8
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed
github.com/spf13/cobra v1.7.0
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4
go.uber.org/zap v1.26.0
go4.org/netipx v0.0.0-20230824141953-6213f710f925
golang.org/x/crypto v0.14.0
golang.org/x/net v0.17.0
golang.org/x/sys v0.13.0
golang.org/x/sys v0.14.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0
@@ -61,8 +61,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect

35
go.sum
View File

@@ -9,9 +9,9 @@ github.com/caddyserver/certmagic v0.19.2/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.5 h1:g+wWynZqVALYAlpSQFAa7TscDnUK8mKYtrxMpw6AUKo=
github.com/cloudflare/circl v1.3.5/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
@@ -31,10 +31,6 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -114,22 +110,22 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2 h1:PW18IgRodvppd09d4mewYM3Hedu3PtFERN8yOqkTVk0=
github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
github.com/sagernet/sing v0.2.17 h1:vMPKb3MV0Aa5ws4dCJkRI8XEjrsUcDn810czd0FwmzI=
github.com/sagernet/sing v0.2.17/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing-dns v0.1.10 h1:iIU7nRBlUYj+fF2TaktGIvRiTFFrHwSMedLQsvlTZCI=
github.com/sagernet/sing-dns v0.1.10/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c=
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
github.com/sagernet/sing-quic v0.1.3 h1:YfSPGQdlE6YspjPSlQJaVH333leFiYQM8JX7TumsWQs=
github.com/sagernet/sing-quic v0.1.3/go.mod h1:wvGU7MYih+cpJV2VrrpSGyjZIFSmUyqzawzmDyqeWJA=
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
github.com/sagernet/sing-shadowsocks2 v0.1.4 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+MUhpQnAux728=
github.com/sagernet/sing-shadowsocks2 v0.1.4/go.mod h1:Mgdee99NxxNd5Zld3ixIs18yVs4x2dI2VTDDE1N14Wc=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1 h1:QxC+myHDZ0BnkIEqXE0lWUzfYEVlhhQdSCo7mOMm7x4=
github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1/go.mod h1:4ACZp3C6TDSy1rsMrfwtSyLrKPtm9Wm2eKHwhYIojbU=
github.com/sagernet/sing-tun v0.1.19 h1:Xp69PWltr0Ga8GFhVusQEV4rx7XUMlqSV/FZnJcWF/k=
github.com/sagernet/sing-tun v0.1.19/go.mod h1:YA4MqRgYbO+igD07xt5WyRLjmwcXD5oRFy2itQbUVK0=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
@@ -138,14 +134,14 @@ github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGV
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed h1:90a510OeE9siSJoYsI8nSjPmA+u5ROMDts/ZkdNsuXY=
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -193,9 +189,8 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@@ -71,25 +71,23 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
logger: logger,
inboundOptions: options.InboundOptions,
tunOptions: tun.Options{
Name: options.InterfaceName,
MTU: tunMTU,
Inet4Address: options.Inet4Address,
Inet6Address: options.Inet6Address,
AutoRoute: options.AutoRoute,
StrictRoute: options.StrictRoute,
IncludeInterface: options.IncludeInterface,
ExcludeInterface: options.ExcludeInterface,
Inet4RouteAddress: options.Inet4RouteAddress,
Inet6RouteAddress: options.Inet6RouteAddress,
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
IncludeUID: includeUID,
ExcludeUID: excludeUID,
IncludeAndroidUser: options.IncludeAndroidUser,
IncludePackage: options.IncludePackage,
ExcludePackage: options.ExcludePackage,
InterfaceMonitor: router.InterfaceMonitor(),
TableIndex: 2022,
Name: options.InterfaceName,
MTU: tunMTU,
Inet4Address: options.Inet4Address,
Inet6Address: options.Inet6Address,
AutoRoute: options.AutoRoute,
StrictRoute: options.StrictRoute,
IncludeInterface: options.IncludeInterface,
ExcludeInterface: options.ExcludeInterface,
Inet4RouteAddress: options.Inet4RouteAddress,
Inet6RouteAddress: options.Inet6RouteAddress,
IncludeUID: includeUID,
ExcludeUID: excludeUID,
IncludeAndroidUser: options.IncludeAndroidUser,
IncludePackage: options.IncludePackage,
ExcludePackage: options.ExcludePackage,
InterfaceMonitor: router.InterfaceMonitor(),
TableIndex: 2022,
},
endpointIndependentNat: options.EndpointIndependentNat,
udpTimeout: udpTimeout,

View File

@@ -120,11 +120,10 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
}
type InboundOptions struct {
SniffEnabled bool `json:"sniff,omitempty"`
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
SniffTimeout Duration `json:"sniff_timeout,omitempty"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
SniffEnabled bool `json:"sniff,omitempty"`
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
SniffTimeout Duration `json:"sniff_timeout,omitempty"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
}
type ListenOptions struct {

View File

@@ -3,28 +3,26 @@ package option
import "net/netip"
type TunInboundOptions struct {
InterfaceName string `json:"interface_name,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
AutoRoute bool `json:"auto_route,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"`
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"`
Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
IncludeInterface Listable[string] `json:"include_interface,omitempty"`
ExcludeInterface Listable[string] `json:"exclude_interface,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"`
Platform *TunPlatformOptions `json:"platform,omitempty"`
InterfaceName string `json:"interface_name,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
AutoRoute bool `json:"auto_route,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"`
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
IncludeInterface Listable[string] `json:"include_interface,omitempty"`
ExcludeInterface Listable[string] `json:"exclude_interface,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"`
Platform *TunPlatformOptions `json:"platform,omitempty"`
InboundOptions
}

View File

@@ -121,13 +121,9 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
}
if destinationAddress.IsValid() {
if metadata.Destination.IsFqdn() {
if metadata.InboundOptions.UDPDisableDomainUnmapping {
outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
} else {
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
}
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
}
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
if natConn, loaded := common.Cast[*bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
}
}
@@ -170,7 +166,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
if metadata.Destination.IsFqdn() {
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
}
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
if natConn, loaded := common.Cast[*bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
}
}

View File

@@ -157,12 +157,6 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error {
filemanager.MkdirAll(r.ctx, parentDir, 0o755)
}
saveFile, err := filemanager.Create(r.ctx, savePath)
if err != nil {
return E.Cause(err, "open output file: ", downloadURL)
}
defer saveFile.Close()
httpClient := &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2: true,
@@ -182,7 +176,16 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error {
return err
}
defer response.Body.Close()
saveFile, err := filemanager.Create(r.ctx, savePath)
if err != nil {
return E.Cause(err, "open output file: ", downloadURL)
}
_, err = io.Copy(saveFile, response.Body)
saveFile.Close()
if err != nil {
filemanager.Remove(r.ctx, savePath)
}
return err
}
@@ -209,12 +212,6 @@ func (r *Router) downloadGeositeDatabase(savePath string) error {
filemanager.MkdirAll(r.ctx, parentDir, 0o755)
}
saveFile, err := filemanager.Create(r.ctx, savePath)
if err != nil {
return E.Cause(err, "open output file: ", downloadURL)
}
defer saveFile.Close()
httpClient := &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2: true,
@@ -234,7 +231,16 @@ func (r *Router) downloadGeositeDatabase(savePath string) error {
return err
}
defer response.Body.Close()
saveFile, err := filemanager.Create(r.ctx, savePath)
if err != nil {
return E.Cause(err, "open output file: ", downloadURL)
}
_, err = io.Copy(saveFile, response.Body)
saveFile.Close()
if err != nil {
filemanager.Remove(r.ctx, savePath)
}
return err
}

View File

@@ -7,7 +7,7 @@ require github.com/sagernet/sing-box v0.0.0
replace github.com/sagernet/sing-box => ../
require (
github.com/docker/docker v24.0.6+incompatible
github.com/docker/docker v24.0.7+incompatible
github.com/docker/go-connections v0.4.0
github.com/gofrs/uuid/v5 v5.0.0
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
@@ -28,7 +28,7 @@ require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/caddyserver/certmagic v0.19.2 // indirect
github.com/cloudflare/circl v1.3.5 // indirect
github.com/cloudflare/circl v1.3.6 // indirect
github.com/cretz/bine v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.5.0 // indirect

View File

@@ -12,8 +12,8 @@ github.com/caddyserver/certmagic v0.19.2/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.5 h1:g+wWynZqVALYAlpSQFAa7TscDnUK8mKYtrxMpw6AUKo=
github.com/cloudflare/circl v1.3.5/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
@@ -24,8 +24,8 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE=
github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=

View File

@@ -50,7 +50,7 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks
case C.V2RayTransportTypeGRPC:
return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig)
case C.V2RayTransportTypeWebsocket:
return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil
return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig)
case C.V2RayTransportTypeQUIC:
if tlsConfig == nil {
return nil, C.ErrTLSRequired

View File

@@ -81,7 +81,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
uri.Path = options.Path
err := sHTTP.URLSetPath(&uri, options.Path)
if err != nil {
return nil, E.New("failed to set path: " + err.Error())
return nil, E.Cause(err, "parse path")
}
client.url = &uri
return client, nil

View File

@@ -5,36 +5,58 @@ import (
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
sHTTP "github.com/sagernet/sing/protocol/http"
"github.com/sagernet/ws"
"github.com/sagernet/websocket"
)
var _ adapter.V2RayClientTransport = (*Client)(nil)
type Client struct {
dialer N.Dialer
tlsConfig tls.Config
serverAddr M.Socksaddr
dialer *websocket.Dialer
requestURL url.URL
requestURLString string
headers http.Header
maxEarlyData uint32
earlyDataHeaderName string
}
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayWebsocketOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayWebsocketOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
wsDialer := &websocket.Dialer{
ReadBufferSize: 4 * 1024,
WriteBufferSize: 4 * 1024,
HandshakeTimeout: time.Second * 8,
}
if tlsConfig != nil {
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"http/1.1"})
}
wsDialer.NetDialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
if err != nil {
return nil, err
}
tlsConn, err := tls.ClientHandshake(ctx, conn, tlsConfig)
if err != nil {
return nil, err
}
return &deadConn{tlsConn}, nil
}
} else {
wsDialer.NetDialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
if err != nil {
return nil, err
}
return &deadConn{conn}, nil
}
}
var requestURL url.URL
if tlsConfig == nil {
@@ -46,59 +68,43 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
requestURL.Path = options.Path
err := sHTTP.URLSetPath(&requestURL, options.Path)
if err != nil {
return nil
}
if !strings.HasPrefix(requestURL.Path, "/") {
requestURL.Path = "/" + requestURL.Path
return nil, E.Cause(err, "parse path")
}
headers := make(http.Header)
for key, value := range options.Headers {
headers[key] = value
if key == "Host" {
if len(value) > 1 {
return nil, E.New("multiple Host headers")
}
requestURL.Host = value[0]
}
}
return &Client{
dialer,
tlsConfig,
serverAddr,
wsDialer,
requestURL,
requestURL.String(),
headers,
options.MaxEarlyData,
options.EarlyDataHeaderName,
}
}
func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers http.Header) (*WebsocketConn, error) {
conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)
if err != nil {
return nil, err
}
if c.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig)
if err != nil {
return nil, err
}
}
conn.SetDeadline(time.Now().Add(C.TCPTimeout))
var protocols []string
if protocolHeader := headers.Get("Sec-WebSocket-Protocol"); protocolHeader != "" {
protocols = []string{protocolHeader}
headers.Del("Sec-WebSocket-Protocol")
}
reader, _, err := ws.Dialer{Header: ws.HandshakeHeaderHTTP(headers), Protocols: protocols}.Upgrade(conn, requestURL)
conn.SetDeadline(time.Time{})
if err != nil {
return nil, err
}
return NewConn(conn, reader, nil, ws.StateClientSide), nil
}, nil
}
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
if c.maxEarlyData <= 0 {
conn, err := c.dialContext(ctx, &c.requestURL, c.headers)
if err != nil {
return nil, err
conn, response, err := c.dialer.DialContext(ctx, c.requestURLString, c.headers)
if err == nil {
return &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}, nil
}
return conn, nil
return nil, wrapDialError(response, err)
} else {
return &EarlyWebsocketConn{Client: c, ctx: ctx, create: make(chan struct{})}, nil
}
}
func wrapDialError(response *http.Response, err error) error {
if response == nil {
return err
}
return E.Extend(err, "HTTP ", response.StatusCode, " ", response.Status)
}

View File

@@ -1,11 +1,11 @@
package v2raywebsocket
import (
"bufio"
"context"
"encoding/base64"
"io"
"net"
"net/http"
"os"
"sync"
"time"
@@ -13,96 +13,50 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/ws"
"github.com/sagernet/ws/wsutil"
"github.com/sagernet/websocket"
)
type WebsocketConn struct {
net.Conn
*websocket.Conn
*Writer
state ws.State
reader *wsutil.Reader
controlHandler wsutil.FrameHandlerFunc
remoteAddr net.Addr
remoteAddr net.Addr
reader io.Reader
}
func NewConn(conn net.Conn, br *bufio.Reader, remoteAddr net.Addr, state ws.State) *WebsocketConn {
controlHandler := wsutil.ControlFrameHandler(conn, state)
var reader io.Reader
if br != nil && br.Buffered() > 0 {
reader = br
} else {
reader = conn
}
func NewServerConn(wsConn *websocket.Conn, remoteAddr net.Addr) *WebsocketConn {
return &WebsocketConn{
Conn: conn,
state: state,
reader: &wsutil.Reader{
Source: reader,
State: state,
SkipHeaderCheck: !debug.Enabled,
OnIntermediate: controlHandler,
},
controlHandler: controlHandler,
remoteAddr: remoteAddr,
Writer: NewWriter(conn, state),
Conn: wsConn,
remoteAddr: remoteAddr,
Writer: NewWriter(wsConn, true),
}
}
func (c *WebsocketConn) Close() error {
c.Conn.SetWriteDeadline(time.Now().Add(C.TCPTimeout))
frame := ws.NewCloseFrame(ws.NewCloseFrameBody(
ws.StatusNormalClosure, "",
))
if c.state == ws.StateClientSide {
frame = ws.MaskFrameInPlace(frame)
err := c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(C.TCPTimeout))
if err != nil {
return c.Conn.Close()
}
ws.WriteFrame(c.Conn, frame)
c.Conn.Close()
return nil
}
func (c *WebsocketConn) Read(b []byte) (n int, err error) {
var header ws.Header
for {
if c.reader == nil {
_, c.reader, err = c.NextReader()
if err != nil {
err = wrapError(err)
return
}
}
n, err = c.reader.Read(b)
if n > 0 {
err = nil
return
}
if !E.IsMulti(err, io.EOF, wsutil.ErrNoFrameAdvance) {
return
}
header, err = c.reader.NextFrame()
if err != nil {
return
}
if header.OpCode.IsControl() {
err = c.controlHandler(header, c.reader)
if err != nil {
return
}
if E.IsMulti(err, io.EOF) {
c.reader = nil
continue
}
if header.OpCode&ws.OpBinary == 0 {
err = c.reader.Discard()
if err != nil {
return
}
continue
}
}
}
func (c *WebsocketConn) Write(p []byte) (n int, err error) {
err = wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p)
if err != nil {
err = wrapError(err)
return
}
n = len(p)
return
}
func (c *WebsocketConn) RemoteAddr() net.Addr {
@@ -129,7 +83,11 @@ func (c *WebsocketConn) NeedAdditionalReadDeadline() bool {
}
func (c *WebsocketConn) Upstream() any {
return c.Conn
return c.Conn.NetConn()
}
func (c *WebsocketConn) UpstreamWriter() any {
return c.Writer
}
type EarlyWebsocketConn struct {
@@ -155,7 +113,8 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
var (
earlyData []byte
lateData []byte
conn *WebsocketConn
conn *websocket.Conn
response *http.Response
err error
)
if len(content) > int(c.maxEarlyData) {
@@ -169,16 +128,19 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
if c.earlyDataHeaderName == "" {
requestURL := c.requestURL
requestURL.Path += earlyDataString
conn, err = c.dialContext(c.ctx, &requestURL, c.headers)
conn, response, err = c.dialer.DialContext(c.ctx, requestURL.String(), c.headers)
} else {
headers := c.headers.Clone()
headers.Set(c.earlyDataHeaderName, earlyDataString)
conn, err = c.dialContext(c.ctx, &c.requestURL, headers)
conn, response, err = c.dialer.DialContext(c.ctx, c.requestURLString, headers)
}
} else {
conn, err = c.dialContext(c.ctx, &c.requestURL, c.headers)
conn, response, err = c.dialer.DialContext(c.ctx, c.requestURLString, c.headers)
}
c.conn = conn
if err != nil {
return wrapDialError(response, err)
}
c.conn = &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}
if len(lateData) > 0 {
_, err = c.conn.Write(lateData)
}
@@ -262,3 +224,13 @@ func (c *EarlyWebsocketConn) Upstream() any {
func (c *EarlyWebsocketConn) LazyHeadroom() bool {
return c.conn == nil
}
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,6 @@
package v2raywebsocket
import _ "unsafe"
//go:linkname maskBytes github.com/sagernet/websocket.maskBytes
func maskBytes(key [4]byte, pos int, b []byte) int

View File

@@ -20,7 +20,7 @@ import (
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
sHttp "github.com/sagernet/sing/protocol/http"
"github.com/sagernet/ws"
"github.com/sagernet/websocket"
)
var _ adapter.V2RayServerTransport = (*Server)(nil)
@@ -58,6 +58,13 @@ func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsCon
return server, nil
}
var upgrader = websocket.Upgrader{
HandshakeTimeout: C.TCPTimeout,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
if s.maxEarlyData == 0 || s.earlyDataHeaderName != "" {
if request.URL.Path != s.path {
@@ -88,14 +95,14 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
s.invalidRequest(writer, request, http.StatusBadRequest, E.Cause(err, "decode early data"))
return
}
wsConn, reader, _, err := ws.UpgradeHTTP(request, writer)
wsConn, err := upgrader.Upgrade(writer, request, nil)
if err != nil {
s.invalidRequest(writer, request, 0, E.Cause(err, "upgrade websocket connection"))
return
}
var metadata M.Metadata
metadata.Source = sHttp.SourceAddress(request)
conn = NewConn(wsConn, reader.Reader, metadata.Source.TCPAddr(), ws.StateServerSide)
conn = NewServerConn(wsConn, metadata.Source.TCPAddr())
if len(earlyData) > 0 {
conn = bufio.NewCachedConn(conn, buf.As(earlyData))
}

View File

@@ -2,27 +2,36 @@ package v2raywebsocket
import (
"encoding/binary"
"io"
"math/rand"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/ws"
"github.com/sagernet/websocket"
)
type Writer struct {
*websocket.Conn
writer N.ExtendedWriter
isServer bool
}
func NewWriter(writer io.Writer, state ws.State) *Writer {
func NewWriter(conn *websocket.Conn, isServer bool) *Writer {
return &Writer{
bufio.NewExtendedWriter(writer),
state == ws.StateServerSide,
conn,
bufio.NewExtendedWriter(conn.NetConn()),
isServer,
}
}
func (w *Writer) Write(p []byte) (n int, err error) {
err = w.Conn.WriteMessage(websocket.BinaryMessage, p)
if err != nil {
return
}
return len(p), nil
}
func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
var payloadBitLength int
dataLen := buffer.Len()
@@ -43,7 +52,7 @@ func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
}
header := buffer.ExtendHeader(headerLen)
header[0] = byte(ws.OpBinary) | 0x80
header[0] = websocket.BinaryMessage | 1<<7
if w.isServer {
header[1] = 0
} else {
@@ -63,12 +72,16 @@ func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
if !w.isServer {
maskKey := rand.Uint32()
binary.BigEndian.PutUint32(header[1+payloadBitLength:], maskKey)
ws.Cipher(data, *(*[4]byte)(header[1+payloadBitLength:]), 0)
maskBytes(*(*[4]byte)(header[1+payloadBitLength:]), 0, data)
}
return w.writer.WriteBuffer(buffer)
}
func (w *Writer) Upstream() any {
return w.Conn.NetConn()
}
func (w *Writer) FrontHeadroom() int {
return 14
}