Compare commits

..

5 Commits

Author SHA1 Message Date
世界
331d16a279 documentation: Bump version 2023-10-30 21:47:03 +08:00
世界
b217fedc21 Add exclude route support for tun 2023-10-30 21:46:57 +08:00
世界
c67c4a54d7 Add udp_disable_domain_unmapping inbound listen option 2023-10-30 21:41:36 +08:00
世界
3711534823 Migrate to gobwas/ws 2023-10-30 21:41:27 +08:00
世界
126f72d9a6 docs: Remove obsolete fields 2023-10-30 21:41:21 +08:00
30 changed files with 366 additions and 350 deletions

View File

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

View File

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

View File

@@ -1,36 +1,20 @@
#### 1.6.2 #### 1.7.0-alpha.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 * 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**: **1**:
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested. If enabled, for UDP proxy requests addressed to a domain,
This update is intended to address the multi-send defects of the old implementation and may introduce new issues. the original packet address will be sent in the response instead of the mapped domain.
**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 #### 1.5.5

View File

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

View File

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

View File

@@ -7,28 +7,26 @@
"tcp_fast_open": false, "tcp_fast_open": false,
"tcp_multi_path": false, "tcp_multi_path": false,
"udp_fragment": false, "udp_fragment": false,
"udp_timeout": 300,
"detour": "another-in",
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_timeout": "300ms", "sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"udp_timeout": 300, "udp_disable_domain_unmapping": false
"proxy_protocol": false,
"proxy_protocol_accept_no_header": false,
"detour": "another-in"
} }
``` ```
### Fields ### Fields
| Field | Available Context | | Field | Available Context |
|-----------------------------------|-------------------------------------------------------------------| |--------------------------------|-------------------------------------------------------------------|
| `listen` | Needs to listen on TCP or UDP. | | `listen` | Needs to listen on TCP or UDP. |
| `listen_port` | 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_fast_open` | Needs to listen on TCP. |
| `tcp_multi_path` | 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_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
| `proxy_protocol` | Needs to listen on TCP. | | `udp_disable_domain_unmapping` | Needs to listen on UDP and accept domain UDP addresses. |
| `proxy_protocol_accept_no_header` | When `proxy_protocol` enabled |
#### listen #### listen
@@ -56,6 +54,16 @@ Enable TCP Multi Path.
Enable UDP fragmentation. 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 #### sniff
Enable sniffing. Enable sniffing.
@@ -82,20 +90,10 @@ 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. If `sniff_override_destination` is in effect, its value will be taken as a fallback.
#### udp_timeout #### udp_disable_domain_unmapping
UDP NAT expiration time in seconds, default is 300 (5 minutes). 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.
#### proxy_protocol This option is used for compatibility with clients that
do not support receiving UDP packets with domain addresses, such as Surge.
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,14 +7,13 @@
"tcp_fast_open": false, "tcp_fast_open": false,
"tcp_multi_path": false, "tcp_multi_path": false,
"udp_fragment": false, "udp_fragment": false,
"udp_timeout": 300,
"detour": "another-in",
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_timeout": "300ms", "sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"udp_timeout": 300, "udp_disable_domain_unmapping": false
"proxy_protocol": false,
"proxy_protocol_accept_no_header": false,
"detour": "another-in"
} }
``` ```
@@ -26,8 +25,7 @@
| `tcp_fast_open` | 需要监听 TCP。 | | `tcp_fast_open` | 需要监听 TCP。 |
| `tcp_multi_path` | 需要监听 TCP。 | | `tcp_multi_path` | 需要监听 TCP。 |
| `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 | | `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 |
| `proxy_protocol` | 需要监听 TCP。 | |
| `proxy_protocol_accept_no_header` | `proxy_protocol` 启用时 |
### 字段 ### 字段
@@ -57,6 +55,16 @@
启用 UDP 分段。 启用 UDP 分段。
#### udp_timeout
UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)。
#### detour
如果设置,连接将被转发到指定的入站。
需要目标入站支持,参阅 [注入支持](/zh/configuration/inbound/#_3)。
#### sniff #### sniff
启用协议探测。 启用协议探测。
@@ -83,20 +91,8 @@
如果 `sniff_override_destination` 生效,它的值将作为后备。 如果 `sniff_override_destination` 生效,它的值将作为后备。
#### udp_timeout #### udp_disable_domain_unmapping
UDP NAT 过期时间,以秒为单位,默认为 3005 分钟) 如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域
#### proxy_protocol 此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
解析连接头中的 [代理协议](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 #### Download
* [Play Store](https://play.google.com/store/apps/details?id=io.nekohasekai.sfa) * [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest)
* [Github Releases](https://github.com/SagerNet/sing-box/releases) * [Github Releases](https://github.com/SagerNet/sing-box/releases)
#### Note #### Note
@@ -16,8 +16,3 @@ Experimental Android client for sing-box.
* User Agent in remote profile request is `SFA/$version ($version_code; sing-box $sing_box_version)` * 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) * 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` * 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,8 +16,3 @@
* 远程配置文件请求中的 User Agent 为 `SFA/$version ($version_code; sing-box $sing_box_version)` * 远程配置文件请求中的 User Agent 为 `SFA/$version ($version_code; sing-box $sing_box_version)`
* 工作目录位于 `/sdcard/Android/data/io.nekohasekai.sfa/files` (外部文件目录) * 工作目录位于 `/sdcard/Android/data/io.nekohasekai.sfa/files` (外部文件目录)
* 崩溃日志位于 `$working_directory/stderr.log` * 崩溃日志位于 `$working_directory/stderr.log`
#### 隐私政策
* SFA 不收集或共享个人数据。
* 软件生成的数据始终在您的设备上。

View File

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

View File

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

View File

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

View File

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

View File

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

16
go.mod
View File

@@ -5,7 +5,7 @@ go 1.20
require ( require (
berty.tech/go-libtor v1.0.385 berty.tech/go-libtor v1.0.385
github.com/caddyserver/certmagic v0.19.2 github.com/caddyserver/certmagic v0.19.2
github.com/cloudflare/circl v1.3.6 github.com/cloudflare/circl v1.3.5
github.com/cretz/bine v0.2.0 github.com/cretz/bine v0.2.0
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/go-chi/chi/v5 v5.0.10 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/gvisor v0.0.0-20230930141345-5fef6f2e17ab
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460 github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.2.17 github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2
github.com/sagernet/sing-dns v0.1.10 github.com/sagernet/sing-dns v0.1.10
github.com/sagernet/sing-mux v0.1.3 github.com/sagernet/sing-mux v0.1.3
github.com/sagernet/sing-quic v0.1.3 github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6
github.com/sagernet/sing-shadowsocks v0.2.5 github.com/sagernet/sing-shadowsocks v0.2.5
github.com/sagernet/sing-shadowsocks2 v0.1.4 github.com/sagernet/sing-shadowsocks2 v0.1.4
github.com/sagernet/sing-shadowtls v0.1.4 github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.1.19 github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1
github.com/sagernet/sing-vmess v0.1.8 github.com/sagernet/sing-vmess v0.1.8
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 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/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
github.com/spf13/cobra v1.8.0 github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
go.uber.org/zap v1.26.0 go.uber.org/zap v1.26.0
go4.org/netipx v0.0.0-20230824141953-6213f710f925 go4.org/netipx v0.0.0-20230824141953-6213f710f925
golang.org/x/crypto v0.14.0 golang.org/x/crypto v0.14.0
golang.org/x/net v0.17.0 golang.org/x/net v0.17.0
golang.org/x/sys v0.14.0 golang.org/x/sys v0.13.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.59.0 google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.31.0
@@ -61,6 +61,8 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-ole/go-ole v1.3.0 // 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/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/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // 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/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/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/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= github.com/cloudflare/circl v1.3.5 h1:g+wWynZqVALYAlpSQFAa7TscDnUK8mKYtrxMpw6AUKo=
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.5/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= 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 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
@@ -31,6 +31,10 @@ 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-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 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= 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= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -110,22 +114,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/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.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.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
github.com/sagernet/sing v0.2.17 h1:vMPKb3MV0Aa5ws4dCJkRI8XEjrsUcDn810czd0FwmzI= github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2 h1:PW18IgRodvppd09d4mewYM3Hedu3PtFERN8yOqkTVk0=
github.com/sagernet/sing v0.2.17/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
github.com/sagernet/sing-dns v0.1.10 h1:iIU7nRBlUYj+fF2TaktGIvRiTFFrHwSMedLQsvlTZCI= 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-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 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0= github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
github.com/sagernet/sing-quic v0.1.3 h1:YfSPGQdlE6YspjPSlQJaVH333leFiYQM8JX7TumsWQs= 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/go.mod h1:wvGU7MYih+cpJV2VrrpSGyjZIFSmUyqzawzmDyqeWJA= github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY= 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-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 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+MUhpQnAux728=
github.com/sagernet/sing-shadowsocks2 v0.1.4/go.mod h1:Mgdee99NxxNd5Zld3ixIs18yVs4x2dI2VTDDE1N14Wc= 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 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.1.19 h1:Xp69PWltr0Ga8GFhVusQEV4rx7XUMlqSV/FZnJcWF/k= github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1 h1:QxC+myHDZ0BnkIEqXE0lWUzfYEVlhhQdSCo7mOMm7x4=
github.com/sagernet/sing-tun v0.1.19/go.mod h1:YA4MqRgYbO+igD07xt5WyRLjmwcXD5oRFy2itQbUVK0= github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1/go.mod h1:4ACZp3C6TDSy1rsMrfwtSyLrKPtm9Wm2eKHwhYIojbU=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc= 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/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
@@ -134,14 +138,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/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 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM= 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 h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk= 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 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 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/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -189,8 +193,9 @@ 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.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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

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

View File

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

View File

@@ -3,26 +3,28 @@ package option
import "net/netip" import "net/netip"
type TunInboundOptions struct { type TunInboundOptions struct {
InterfaceName string `json:"interface_name,omitempty"` InterfaceName string `json:"interface_name,omitempty"`
MTU uint32 `json:"mtu,omitempty"` MTU uint32 `json:"mtu,omitempty"`
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"` Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"` Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
AutoRoute bool `json:"auto_route,omitempty"` AutoRoute bool `json:"auto_route,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"` StrictRoute bool `json:"strict_route,omitempty"`
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"` Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"` Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
IncludeInterface Listable[string] `json:"include_interface,omitempty"` Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"`
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"` Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
IncludeUID Listable[uint32] `json:"include_uid,omitempty"` IncludeInterface Listable[string] `json:"include_interface,omitempty"`
IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"` ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"` IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"` IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"`
IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"` ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"`
IncludePackage Listable[string] `json:"include_package,omitempty"` ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"`
ExcludePackage Listable[string] `json:"exclude_package,omitempty"` IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"`
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` IncludePackage Listable[string] `json:"include_package,omitempty"`
UDPTimeout int64 `json:"udp_timeout,omitempty"` ExcludePackage Listable[string] `json:"exclude_package,omitempty"`
Stack string `json:"stack,omitempty"` EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
Platform *TunPlatformOptions `json:"platform,omitempty"` UDPTimeout int64 `json:"udp_timeout,omitempty"`
Stack string `json:"stack,omitempty"`
Platform *TunPlatformOptions `json:"platform,omitempty"`
InboundOptions InboundOptions
} }

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ require github.com/sagernet/sing-box v0.0.0
replace github.com/sagernet/sing-box => ../ replace github.com/sagernet/sing-box => ../
require ( require (
github.com/docker/docker v24.0.7+incompatible github.com/docker/docker v24.0.6+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/gofrs/uuid/v5 v5.0.0 github.com/gofrs/uuid/v5 v5.0.0
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460 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/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/caddyserver/certmagic v0.19.2 // indirect github.com/caddyserver/certmagic v0.19.2 // indirect
github.com/cloudflare/circl v1.3.6 // indirect github.com/cloudflare/circl v1.3.5 // indirect
github.com/cretz/bine v0.2.0 // indirect github.com/cretz/bine v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.5.0 // 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/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/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/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= github.com/cloudflare/circl v1.3.5 h1:g+wWynZqVALYAlpSQFAa7TscDnUK8mKYtrxMpw6AUKo=
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.5/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= 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 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= 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/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 h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.6+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 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 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: case C.V2RayTransportTypeGRPC:
return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig) return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig)
case C.V2RayTransportTypeWebsocket: case C.V2RayTransportTypeWebsocket:
return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig) return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil
case C.V2RayTransportTypeQUIC: case C.V2RayTransportTypeQUIC:
if tlsConfig == nil { if tlsConfig == nil {
return nil, C.ErrTLSRequired 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 uri.Path = options.Path
err := sHTTP.URLSetPath(&uri, options.Path) err := sHTTP.URLSetPath(&uri, options.Path)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse path") return nil, E.New("failed to set path: " + err.Error())
} }
client.url = &uri client.url = &uri
return client, nil return client, nil

View File

@@ -5,58 +5,36 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
sHTTP "github.com/sagernet/sing/protocol/http" sHTTP "github.com/sagernet/sing/protocol/http"
"github.com/sagernet/websocket" "github.com/sagernet/ws"
) )
var _ adapter.V2RayClientTransport = (*Client)(nil) var _ adapter.V2RayClientTransport = (*Client)(nil)
type Client struct { type Client struct {
dialer *websocket.Dialer dialer N.Dialer
tlsConfig tls.Config
serverAddr M.Socksaddr
requestURL url.URL requestURL url.URL
requestURLString string
headers http.Header headers http.Header
maxEarlyData uint32 maxEarlyData uint32
earlyDataHeaderName string earlyDataHeaderName string
} }
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayWebsocketOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayWebsocketOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {
wsDialer := &websocket.Dialer{
ReadBufferSize: 4 * 1024,
WriteBufferSize: 4 * 1024,
HandshakeTimeout: time.Second * 8,
}
if tlsConfig != nil { if tlsConfig != nil {
if len(tlsConfig.NextProtos()) == 0 { if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"http/1.1"}) 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 var requestURL url.URL
if tlsConfig == nil { if tlsConfig == nil {
@@ -68,43 +46,59 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
requestURL.Path = options.Path requestURL.Path = options.Path
err := sHTTP.URLSetPath(&requestURL, options.Path) err := sHTTP.URLSetPath(&requestURL, options.Path)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse path") return nil
}
if !strings.HasPrefix(requestURL.Path, "/") {
requestURL.Path = "/" + requestURL.Path
} }
headers := make(http.Header) headers := make(http.Header)
for key, value := range options.Headers { for key, value := range options.Headers {
headers[key] = value headers[key] = value
if key == "Host" {
if len(value) > 1 {
return nil, E.New("multiple Host headers")
}
requestURL.Host = value[0]
}
} }
return &Client{ return &Client{
wsDialer, dialer,
tlsConfig,
serverAddr,
requestURL, requestURL,
requestURL.String(),
headers, headers,
options.MaxEarlyData, options.MaxEarlyData,
options.EarlyDataHeaderName, options.EarlyDataHeaderName,
}, nil }
}
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
} }
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
if c.maxEarlyData <= 0 { if c.maxEarlyData <= 0 {
conn, response, err := c.dialer.DialContext(ctx, c.requestURLString, c.headers) conn, err := c.dialContext(ctx, &c.requestURL, c.headers)
if err == nil { if err != nil {
return &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}, nil return nil, err
} }
return nil, wrapDialError(response, err) return conn, nil
} else { } else {
return &EarlyWebsocketConn{Client: c, ctx: ctx, create: make(chan struct{})}, nil 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 package v2raywebsocket
import ( import (
"bufio"
"context" "context"
"encoding/base64" "encoding/base64"
"io" "io"
"net" "net"
"net/http"
"os" "os"
"sync" "sync"
"time" "time"
@@ -13,50 +13,96 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/websocket" "github.com/sagernet/ws"
"github.com/sagernet/ws/wsutil"
) )
type WebsocketConn struct { type WebsocketConn struct {
*websocket.Conn net.Conn
*Writer *Writer
remoteAddr net.Addr state ws.State
reader io.Reader reader *wsutil.Reader
controlHandler wsutil.FrameHandlerFunc
remoteAddr net.Addr
} }
func NewServerConn(wsConn *websocket.Conn, remoteAddr net.Addr) *WebsocketConn { 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
}
return &WebsocketConn{ return &WebsocketConn{
Conn: wsConn, Conn: conn,
remoteAddr: remoteAddr, state: state,
Writer: NewWriter(wsConn, true), reader: &wsutil.Reader{
Source: reader,
State: state,
SkipHeaderCheck: !debug.Enabled,
OnIntermediate: controlHandler,
},
controlHandler: controlHandler,
remoteAddr: remoteAddr,
Writer: NewWriter(conn, state),
} }
} }
func (c *WebsocketConn) Close() error { func (c *WebsocketConn) Close() error {
err := c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(C.TCPTimeout)) c.Conn.SetWriteDeadline(time.Now().Add(C.TCPTimeout))
if err != nil { frame := ws.NewCloseFrame(ws.NewCloseFrameBody(
return c.Conn.Close() ws.StatusNormalClosure, "",
))
if c.state == ws.StateClientSide {
frame = ws.MaskFrameInPlace(frame)
} }
ws.WriteFrame(c.Conn, frame)
c.Conn.Close()
return nil return nil
} }
func (c *WebsocketConn) Read(b []byte) (n int, err error) { func (c *WebsocketConn) Read(b []byte) (n int, err error) {
var header ws.Header
for { for {
if c.reader == nil { n, err = c.reader.Read(b)
_, c.reader, err = c.NextReader() 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 { if err != nil {
err = wrapError(err)
return return
} }
}
n, err = c.reader.Read(b)
if E.IsMulti(err, io.EOF) {
c.reader = nil
continue continue
} }
err = wrapError(err) 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 {
return return
} }
n = len(p)
return
} }
func (c *WebsocketConn) RemoteAddr() net.Addr { func (c *WebsocketConn) RemoteAddr() net.Addr {
@@ -83,11 +129,7 @@ func (c *WebsocketConn) NeedAdditionalReadDeadline() bool {
} }
func (c *WebsocketConn) Upstream() any { func (c *WebsocketConn) Upstream() any {
return c.Conn.NetConn() return c.Conn
}
func (c *WebsocketConn) UpstreamWriter() any {
return c.Writer
} }
type EarlyWebsocketConn struct { type EarlyWebsocketConn struct {
@@ -113,8 +155,7 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
var ( var (
earlyData []byte earlyData []byte
lateData []byte lateData []byte
conn *websocket.Conn conn *WebsocketConn
response *http.Response
err error err error
) )
if len(content) > int(c.maxEarlyData) { if len(content) > int(c.maxEarlyData) {
@@ -128,19 +169,16 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
if c.earlyDataHeaderName == "" { if c.earlyDataHeaderName == "" {
requestURL := c.requestURL requestURL := c.requestURL
requestURL.Path += earlyDataString requestURL.Path += earlyDataString
conn, response, err = c.dialer.DialContext(c.ctx, requestURL.String(), c.headers) conn, err = c.dialContext(c.ctx, &requestURL, c.headers)
} else { } else {
headers := c.headers.Clone() headers := c.headers.Clone()
headers.Set(c.earlyDataHeaderName, earlyDataString) headers.Set(c.earlyDataHeaderName, earlyDataString)
conn, response, err = c.dialer.DialContext(c.ctx, c.requestURLString, headers) conn, err = c.dialContext(c.ctx, &c.requestURL, headers)
} }
} else { } else {
conn, response, err = c.dialer.DialContext(c.ctx, c.requestURLString, c.headers) conn, err = c.dialContext(c.ctx, &c.requestURL, c.headers)
} }
if err != nil { c.conn = conn
return wrapDialError(response, err)
}
c.conn = &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}
if len(lateData) > 0 { if len(lateData) > 0 {
_, err = c.conn.Write(lateData) _, err = c.conn.Write(lateData)
} }
@@ -224,13 +262,3 @@ func (c *EarlyWebsocketConn) Upstream() any {
func (c *EarlyWebsocketConn) LazyHeadroom() bool { func (c *EarlyWebsocketConn) LazyHeadroom() bool {
return c.conn == nil 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

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

View File

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