mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Compare commits
5 Commits
dev-next
...
v1.7.0-alp
Author | SHA1 | Date | |
---|---|---|---|
![]() |
331d16a279 | ||
![]() |
b217fedc21 | ||
![]() |
c67c4a54d7 | ||
![]() |
3711534823 | ||
![]() |
126f72d9a6 |
@ -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) {
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
#### 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
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
* Fix IPv6 `auto_route` for Linux **1**
|
* Fix IPv6 `auto_route` for Linux **1**
|
||||||
@ -12,25 +30,108 @@ When `auto_route` is enabled and `strict_route` is disabled, the device can now
|
|||||||
|
|
||||||
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
|
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
|
||||||
|
|
||||||
|
|
||||||
|
#### 1.6.0-rc.4
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-rc.1
|
||||||
|
|
||||||
|
* Add legacy builds for old Windows and macOS systems **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Built using Go 1.20, the last version that will run on Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave.
|
||||||
|
|
||||||
|
#### 1.6.0-beta.4
|
||||||
|
|
||||||
|
* Fix IPv6 `auto_route` for Linux **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
When `auto_route` is enabled and `strict_route` is disabled, the device can now be reached from external IPv6 addresses.
|
||||||
|
|
||||||
#### 1.5.4
|
#### 1.5.4
|
||||||
|
|
||||||
* Fix Clash cache crash on arm32 devices
|
* Fix Clash cache crash on arm32 devices
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-beta.3
|
||||||
|
|
||||||
|
* Update the legacy Hysteria protocol **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
#### 1.6.0-beta.2
|
||||||
|
|
||||||
|
* Add TLS self sign key pair generate command
|
||||||
|
* Update brutal congestion control for Hysteria2
|
||||||
|
* Fix Clash cache crash on arm32 devices
|
||||||
|
* Update golang.org/x/net to v0.17.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.5.3
|
#### 1.5.3
|
||||||
|
|
||||||
* Fix compatibility with Android 14
|
* Fix compatibility with Android 14
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-beta.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.5
|
||||||
|
|
||||||
|
* Fix compatibility with Android 14
|
||||||
|
* Update BBR congestion control for TUIC and Hysteria2 **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
|
||||||
|
This update is intended to fix a memory leak flaw in the new implementation introduced in 1.6.0-alpha.1 and may
|
||||||
|
introduce new issues.
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.4
|
||||||
|
|
||||||
|
* Add `brutal_debug` option for Hysteria2
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.5.2
|
#### 1.5.2
|
||||||
|
|
||||||
* Our [Apple tvOS client](/installation/clients/sft) is now available in the App Store 🍎
|
* Our [Apple tvOS client](/installation/clients/sft) is now available in the App Store 🍎
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.2
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.5.1
|
#### 1.5.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.6.0-alpha.1
|
||||||
|
|
||||||
|
* Update BBR congestion control for TUIC and Hysteria2 **1**
|
||||||
|
* Update quic-go to v0.39.0
|
||||||
|
* Update gVisor to 20230814.0
|
||||||
|
* Remove [Deprecated Features](/deprecated) by agreement
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
#### 1.5.0
|
#### 1.5.0
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
@ -82,49 +82,3 @@ Both if empty.
|
|||||||
| none | / |
|
| none | / |
|
||||||
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
|
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
|
||||||
| other methods | any string |
|
| other methods | any string |
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
#### tcp_fast_open
|
|
||||||
|
|
||||||
Enable tcp fast open for listener.
|
|
||||||
|
|
||||||
#### sniff
|
|
||||||
|
|
||||||
Enable sniffing.
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
|
||||||
|
|
||||||
#### sniff_override_destination
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
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_timeout
|
|
||||||
|
|
||||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
|
||||||
|
|
||||||
#### proxy_protocol
|
|
||||||
|
|
||||||
Parse [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
|
@ -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 ""
|
||||||
|
@ -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。
|
||||||
|
@ -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).
|
|
||||||
|
@ -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 过期时间,以秒为单位,默认为 300(5 分钟)。
|
||||||
|
|
||||||
|
#### 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 过期时间,以秒为单位,默认为 300(5 分钟)。
|
如果启用,对于地址为域的 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)。
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
8
go.mod
8
go.mod
@ -26,20 +26,20 @@ 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.16-0.20231021090846-8002db54c028
|
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-0.20231026034240-fa3d997246b6
|
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.17-0.20231026060825-efd9884154a6
|
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/sagernet/ws v0.0.0-20231030053741-7d481eb31bed
|
||||||
github.com/spf13/cobra v1.7.0
|
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
|
||||||
@ -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
|
||||||
|
17
go.sum
17
go.sum
@ -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,8 +114,8 @@ 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.16-0.20231021090846-8002db54c028 h1:6GbQt7SC9y5Imrq5jDMbXDSaNiMhJ8KBjhjtQRuqQvE=
|
github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2 h1:PW18IgRodvppd09d4mewYM3Hedu3PtFERN8yOqkTVk0=
|
||||||
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
|
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=
|
||||||
@ -124,8 +128,8 @@ github.com/sagernet/sing-shadowsocks2 v0.1.4 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+M
|
|||||||
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.17-0.20231026060825-efd9884154a6 h1:4yEXBqQoUgXj7qPSLD6lr+z9/KfsvixO9JUA2i5xnM8=
|
github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1 h1:QxC+myHDZ0BnkIEqXE0lWUzfYEVlhhQdSCo7mOMm7x4=
|
||||||
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6/go.mod h1:w2+S+uWE94E/pQWSDdDdMIjwAEb645kuGPunr6ZllUg=
|
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,10 +138,10 @@ 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.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
@ -189,6 +193,7 @@ 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
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.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=
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
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 {
|
||||||
@ -70,35 +48,57 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
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
|
||||||
}
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
wsDialer,
|
dialer,
|
||||||
|
tlsConfig,
|
||||||
|
serverAddr,
|
||||||
requestURL,
|
requestURL,
|
||||||
requestURL.String(),
|
|
||||||
headers,
|
headers,
|
||||||
options.MaxEarlyData,
|
options.MaxEarlyData,
|
||||||
options.EarlyDataHeaderName,
|
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
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user