Compare commits

...

8 Commits

Author SHA1 Message Date
世界
c2ce1d0024
documentation: Bump version 2023-11-03 18:42:04 +08:00
世界
c85b3fff14
Migrate multiplex and UoT server to inbound &
Add tcp-brutal support for multiplex
2023-11-03 18:42:01 +08:00
世界
5ade21b174
Add support for v2ray http upgrade transport 2023-11-03 18:41:59 +08:00
世界
53e88eb4a1
Add exclude route support for tun 2023-11-03 18:41:54 +08:00
世界
d3c8630dee
Add udp_disable_domain_unmapping inbound listen option 2023-11-03 18:41:20 +08:00
世界
25c58c3be9
Migrate to gobwas/ws 2023-11-03 18:41:20 +08:00
世界
28b14c90f9
Update release script 2023-11-03 18:40:44 +08:00
世界
9f53b3e59b
Add global unicast filter for tun 2023-11-03 18:40:44 +08:00
92 changed files with 1371 additions and 447 deletions

View File

@ -84,6 +84,9 @@ 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:

104
adapter/conn_router.go Normal file
View File

@ -0,0 +1,104 @@
package adapter
import (
"context"
"net"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type ConnectionRouter interface {
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
func NewRouteHandler(
metadata InboundContext,
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeHandlerWrapper{
metadata: metadata,
router: router,
logger: logger,
}
}
func NewRouteContextHandler(
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeContextHandlerWrapper{
router: router,
logger: logger,
}
}
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
type routeHandlerWrapper struct {
metadata InboundContext
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
type routeContextHandlerWrapper struct {
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}

View File

@ -2,14 +2,12 @@ package adapter
import ( import (
"context" "context"
"net"
"net/netip" "net/netip"
"github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geoip"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
mdns "github.com/miekg/dns" mdns "github.com/miekg/dns"
@ -24,8 +22,7 @@ type Router interface {
FakeIPStore() FakeIPStore FakeIPStore() FakeIPStore
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error ConnectionRouter
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
GeoIPReader() *geoip.Reader GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error) LoadGeosite(code string) (Rule, error)

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

@ -6,7 +6,9 @@ import (
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) { type Client = mux.Client
func NewClientWithOptions(dialer N.Dialer, options option.OutboundMultiplexOptions) (*Client, error) {
if !options.Enabled { if !options.Enabled {
return nil, nil return nil, nil
} }

View File

@ -1,14 +0,0 @@
package mux
import (
"github.com/sagernet/sing-mux"
)
type (
Client = mux.Client
)
var (
Destination = mux.Destination
HandleConnection = mux.HandleConnection
)

65
common/mux/router.go Normal file
View File

@ -0,0 +1,65 @@
package mux
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-mux"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
type Router struct {
router adapter.ConnectionRouter
service *mux.Service
}
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
if !options.Enabled {
return router, nil
}
var brutalOptions mux.BrutalOptions
if options.Brutal != nil && options.Brutal.Enabled {
brutalOptions = mux.BrutalOptions{
Enabled: true,
SendBPS: uint64(options.Brutal.UpMbps * C.MbpsToBps),
ReceiveBPS: uint64(options.Brutal.DownMbps * C.MbpsToBps),
}
if brutalOptions.SendBPS < mux.BrutalMinSpeedBPS {
return nil, E.New("brutal: invalid upload speed")
}
if brutalOptions.ReceiveBPS < mux.BrutalMinSpeedBPS {
return nil, E.New("brutal: invalid download speed")
}
}
service, err := mux.NewService(mux.ServiceOptions{
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
return log.ContextWithNewID(ctx)
},
Logger: logger,
Handler: adapter.NewRouteContextHandler(router, logger),
Padding: options.Padding,
Brutal: brutalOptions,
})
if err != nil {
return nil, err
}
return &Router{router, service}, nil
}
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination == mux.Destination {
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
} else {
return r.router.RouteConnection(ctx, conn, metadata)
}
}
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

@ -0,0 +1,32 @@
package mux
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
type V2RayLegacyRouter struct {
router adapter.ConnectionRouter
logger logger.ContextLogger
}
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
return &V2RayLegacyRouter{router, logger}
}
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
}
return r.router.RouteConnection(ctx, conn, metadata)
}
func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}

53
common/uot/router.go Normal file
View File

@ -0,0 +1,53 @@
package uot
import (
"context"
"net"
"net/netip"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/uot"
)
var _ adapter.ConnectionRouter = (*Router)(nil)
type Router struct {
router adapter.ConnectionRouter
logger logger.ContextLogger
}
func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
return &Router{router, logger}
}
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
switch metadata.Destination.Fqdn {
case uot.MagicAddress:
request, err := uot.ReadRequest(conn)
if err != nil {
return E.Cause(err, "read UoT request")
}
if request.IsConnect {
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
} else {
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
}
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = request.Destination
return r.router.RoutePacketConnection(ctx, uot.NewConn(conn, *request), metadata)
case uot.LegacyMagicAddress:
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
}
return r.router.RouteConnection(ctx, conn, metadata)
}
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}

3
constant/speed.go Normal file
View File

@ -0,0 +1,3 @@
package constant
const MbpsToBps = 125000

View File

@ -1,8 +1,9 @@
package constant package constant
const ( const (
V2RayTransportTypeHTTP = "http" V2RayTransportTypeHTTP = "http"
V2RayTransportTypeWebsocket = "ws" V2RayTransportTypeWebsocket = "ws"
V2RayTransportTypeQUIC = "quic" V2RayTransportTypeQUIC = "quic"
V2RayTransportTypeGRPC = "grpc" V2RayTransportTypeGRPC = "grpc"
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
) )

View File

@ -1,3 +1,31 @@
#### 1.7.0-alpha.5
* Fixes and improvements
#### 1.7.0-alpha.4
* Migrate multiplex and UoT server to inbound **1**
* Add TCP Brutal support for multiplex **2**
**1**:
Starting in 1.7.0, multiplexing support is no longer enabled by default and needs to be turned on explicitly in inbound options.
**2**
Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server, see [TCP Brutal](/configuration/shared/tcp-brutal) for details.
#### 1.7.0-alpha.3
* Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **1**
* Fixes and improvements
**1**:
Introduced in V2Ray 5.10.0.
The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.
#### 1.6.0 #### 1.6.0
* Fixes and improvements * Fixes and improvements
@ -22,6 +50,23 @@ This update is intended to address the multi-send defects of the old implementat
Based on discussions with the original author, the brutal CC and QUIC protocol parameters of 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 the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 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
**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
@ -83,6 +128,24 @@ the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
* Update golang.org/x/net to v0.17.0 * Update golang.org/x/net to v0.17.0
* 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

View File

@ -62,7 +62,7 @@ Hysteria 用户
#### ignore_client_bandwidth #### ignore_client_bandwidth
命令客户端使用 BBR 流量控制算法而不是 Hysteria CC。 命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
`up_mbps``down_mbps` 冲突。 `up_mbps``down_mbps` 冲突。

View File

@ -8,7 +8,8 @@
... // Listen Fields ... // Listen Fields
"method": "2022-blake3-aes-128-gcm", "method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==" "password": "8JCsPssfgS8tiRwiMlhARg==",
"multiplex": {}
} }
``` ```
@ -23,7 +24,8 @@
"name": "sekai", "name": "sekai",
"password": "PCD2Z4o12bKUoFa3cC97Hw==" "password": "PCD2Z4o12bKUoFa3cC97Hw=="
} }
] ],
"multiplex": {}
} }
``` ```
@ -41,7 +43,8 @@
"server_port": 8080, "server_port": 8080,
"password": "PCD2Z4o12bKUoFa3cC97Hw==" "password": "PCD2Z4o12bKUoFa3cC97Hw=="
} }
] ],
"multiplex": {}
} }
``` ```
@ -82,3 +85,7 @@ 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 |
#### multiplex
See [Multiplex](/configuration/shared/multiplex#inbound) for details.

View File

@ -8,7 +8,8 @@
... // 监听字段 ... // 监听字段
"method": "2022-blake3-aes-128-gcm", "method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==" "password": "8JCsPssfgS8tiRwiMlhARg==",
"multiplex": {}
} }
``` ```
@ -23,7 +24,8 @@
"name": "sekai", "name": "sekai",
"password": "PCD2Z4o12bKUoFa3cC97Hw==" "password": "PCD2Z4o12bKUoFa3cC97Hw=="
} }
] ],
"multiplex": {}
} }
``` ```
@ -41,7 +43,8 @@
"server_port": 8080, "server_port": 8080,
"password": "PCD2Z4o12bKUoFa3cC97Hw==" "password": "PCD2Z4o12bKUoFa3cC97Hw=="
} }
] ],
"multiplex": {}
} }
``` ```
@ -81,4 +84,8 @@ See [Listen Fields](/configuration/shared/listen) for details.
|---------------|------------------------------------------| |---------------|------------------------------------------|
| none | / | | none | / |
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` | | 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
| other methods | 任意字符串 | | other methods | 任意字符串 |
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。

View File

@ -24,6 +24,7 @@
"server_port": 8081 "server_port": 8081
} }
}, },
"multiplex": {},
"transport": {} "transport": {}
} }
``` ```
@ -58,6 +59,10 @@ Fallback server configuration for specified ALPN.
If not empty, TLS fallback requests with ALPN not in this table will be rejected. If not empty, TLS fallback requests with ALPN not in this table will be rejected.
#### multiplex
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
#### transport #### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport). V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).

View File

@ -24,6 +24,7 @@
"server_port": 8081 "server_port": 8081
} }
}, },
"multiplex": {},
"transport": {} "transport": {}
} }
``` ```
@ -60,6 +61,10 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
如果不为空ALPN 不在此列表中的 TLS 回退请求将被拒绝。 如果不为空ALPN 不在此列表中的 TLS 回退请求将被拒绝。
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
#### transport #### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。 V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。

View File

@ -48,7 +48,7 @@ TUIC 用户密码
#### congestion_control #### congestion_control
QUIC 流量控制算法 QUIC 拥塞控制算法
可选值: `cubic`, `new_reno`, `bbr` 可选值: `cubic`, `new_reno`, `bbr`

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

@ -15,6 +15,7 @@
} }
], ],
"tls": {}, "tls": {},
"multiplex": {},
"transport": {} "transport": {}
} }
``` ```
@ -49,6 +50,10 @@ Available values:
TLS configuration, see [TLS](/configuration/shared/tls/#inbound). TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
#### multiplex
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
#### transport #### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport). V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).

View File

@ -15,6 +15,7 @@
} }
], ],
"tls": {}, "tls": {},
"multiplex": {},
"transport": {} "transport": {}
} }
``` ```
@ -49,6 +50,10 @@ VLESS 子协议。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
#### transport #### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。 V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。

View File

@ -15,6 +15,7 @@
} }
], ],
"tls": {}, "tls": {},
"multiplex": {},
"transport": {} "transport": {}
} }
``` ```
@ -44,6 +45,10 @@ VMess users.
TLS configuration, see [TLS](/configuration/shared/tls/#inbound). TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
#### multiplex
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
#### transport #### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport). V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).

View File

@ -15,6 +15,7 @@
} }
], ],
"tls": {}, "tls": {},
"multiplex": {},
"transport": {} "transport": {}
} }
``` ```
@ -44,6 +45,10 @@ VMess 用户。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
#### transport #### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。 V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。

View File

@ -44,7 +44,7 @@
最大带宽。 最大带宽。
如果为空,将使用 BBR 流量控制算法而不是 Hysteria CC。 如果为空,将使用 BBR 拥塞控制算法而不是 Hysteria CC。
#### obfs.type #### obfs.type

View File

@ -95,7 +95,7 @@ Conflict with `multiplex`.
#### multiplex #### multiplex
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex). See [Multiplex](/configuration/shared/multiplex#outbound) for details.
### Dial Fields ### Dial Fields

View File

@ -95,7 +95,7 @@ UDP over TCP 配置。
#### multiplex #### multiplex
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。 参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
### 拨号字段 ### 拨号字段

View File

@ -51,7 +51,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
#### multiplex #### multiplex
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex). See [Multiplex](/configuration/shared/multiplex#outbound) for details.
#### transport #### transport

View File

@ -51,7 +51,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
#### multiplex #### multiplex
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。 参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
#### transport #### transport

View File

@ -51,7 +51,7 @@ TUIC 用户密码
#### congestion_control #### congestion_control
QUIC 流量控制算法 QUIC 拥塞控制算法
可选值: `cubic`, `new_reno`, `bbr` 可选值: `cubic`, `new_reno`, `bbr`

View File

@ -12,6 +12,7 @@
"network": "tcp", "network": "tcp",
"tls": {}, "tls": {},
"packet_encoding": "", "packet_encoding": "",
"multiplex": {},
"transport": {}, "transport": {},
... // Dial Fields ... // Dial Fields
@ -68,6 +69,10 @@ UDP packet encoding, xudp is used by default.
| packetaddr | Supported by v2ray 5+ | | packetaddr | Supported by v2ray 5+ |
| xudp | Supported by xray | | xudp | Supported by xray |
#### multiplex
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
#### transport #### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport). V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).

View File

@ -12,6 +12,7 @@
"network": "tcp", "network": "tcp",
"tls": {}, "tls": {},
"packet_encoding": "", "packet_encoding": "",
"multiplex": {},
"transport": {}, "transport": {},
... // 拨号字段 ... // 拨号字段
@ -68,6 +69,10 @@ UDP 包编码,默认使用 xudp。
| packetaddr | 由 v2ray 5+ 支持 | | packetaddr | 由 v2ray 5+ 支持 |
| xudp | 由 xray 支持 | | xudp | 由 xray 支持 |
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
#### transport #### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。 V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。

View File

@ -15,8 +15,8 @@
"network": "tcp", "network": "tcp",
"tls": {}, "tls": {},
"packet_encoding": "", "packet_encoding": "",
"multiplex": {},
"transport": {}, "transport": {},
"multiplex": {},
... // Dial Fields ... // Dial Fields
} }
@ -96,7 +96,7 @@ UDP packet encoding.
#### multiplex #### multiplex
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex). See [Multiplex](/configuration/shared/multiplex#outbound) for details.
#### transport #### transport

View File

@ -96,7 +96,7 @@ UDP 包编码。
#### multiplex #### multiplex
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。 参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
#### transport #### transport

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

@ -1,8 +1,14 @@
### Server Requirements ### Inbound
`sing-box` :) ```json
{
"enabled": true,
"padding": false,
"brutal": {}
}
```
### Structure ### Outbound
```json ```json
{ {
@ -11,11 +17,27 @@
"max_connections": 4, "max_connections": 4,
"min_streams": 4, "min_streams": 4,
"max_streams": 0, "max_streams": 0,
"padding": false "padding": false,
"brutal": {}
} }
``` ```
### Fields
### Inbound Fields
#### enabled
Enable multiplex support.
#### padding
If enabled, non-padded connections will be rejected.
#### brutal
See [TCP Brutal](/configuration/shared/tcp-brutal) for details.
### Outbound Fields
#### enabled #### enabled
@ -59,3 +81,6 @@ Conflict with `max_connections` and `min_streams`.
Enable padding. Enable padding.
#### brutal
See [TCP Brutal](/configuration/shared/tcp-brutal) for details.

View File

@ -1,8 +1,14 @@
### 服务器要求 ### 入站
`sing-box` :) ```json
{
"enabled": true,
"padding": false,
"brutal": {}
}
```
### 结构 ### 出站
```json ```json
{ {
@ -10,11 +16,27 @@
"protocol": "smux", "protocol": "smux",
"max_connections": 4, "max_connections": 4,
"min_streams": 4, "min_streams": 4,
"max_streams": 0 "max_streams": 0,
"padding": false,
"brutal": {}
} }
``` ```
### 字段 ### 入站字段
#### enabled
启用多路复用支持。
#### padding
如果启用,将拒绝非填充连接。
#### brutal
参阅 [TCP Brutal](/zh/configuration/shared/tcp-brutal)。
### 出站字段
#### enabled #### enabled
@ -58,3 +80,6 @@
启用填充。 启用填充。
#### brutal
参阅 [TCP Brutal](/zh/configuration/shared/tcp-brutal)。

View File

@ -0,0 +1,28 @@
### Server Requirements
* Linux
* `brutal` congestion control algorithm kernel module installed
See [tcp-brutal](https://github.com/apernet/tcp-brutal) for details.
### Structure
```json
{
"enabled": true,
"up_mbps": 100,
"down_mbps": 100
}
```
### Fields
#### enabled
Enable TCP Brutal congestion control algorithm。
#### up_mbps, down_mbps
==Required==
Upload and download bandwidth, in Mbps.

View File

@ -0,0 +1,28 @@
### 服务器要求
* Linux
* `brutal` 拥塞控制算法内核模块已安装
参阅 [tcp-brutal](https://github.com/apernet/tcp-brutal)。
### 结构
```json
{
"enabled": true,
"up_mbps": 100,
"down_mbps": 100
}
```
### 字段
#### enabled
启用 TCP Brutal 拥塞控制算法。
#### up_mbps, down_mbps
==必填==
上传和下载带宽,以 Mbps 为单位。

View File

@ -15,6 +15,7 @@ Available transports:
* WebSocket * WebSocket
* QUIC * QUIC
* gRPC * gRPC
* HTTPUpgrade
!!! warning "Difference from v2ray-core" !!! warning "Difference from v2ray-core"
@ -184,3 +185,32 @@ In standard gRPC client:
If enabled, the client transport sends keepalive pings even with no active connections. If disabled, when there are no active connections, `idle_timeout` and `ping_timeout` will be ignored and no keepalive pings will be sent. If enabled, the client transport sends keepalive pings even with no active connections. If disabled, when there are no active connections, `idle_timeout` and `ping_timeout` will be ignored and no keepalive pings will be sent.
Disabled by default. Disabled by default.
### HTTPUpgrade
```json
{
"type": "httpupgrade",
"host": "",
"path": "",
"headers": {}
}
```
#### host
Host domain.
The server will verify if not empty.
#### path
Path of HTTP request.
The server will verify if not empty.
#### headers
Extra headers of HTTP request.
The server will write in response if not empty.

View File

@ -14,6 +14,7 @@ V2Ray Transport 是 v2ray 发明的一组私有协议,并污染了其他协议
* WebSocket * WebSocket
* QUIC * QUIC
* gRPC * gRPC
* HTTPUpgrade
!!! warning "与 v2ray-core 的区别" !!! warning "与 v2ray-core 的区别"
@ -183,3 +184,32 @@ gRPC 服务名称。
如果启用,客户端传输即使没有活动连接也会发送 keepalive ping。如果禁用则在没有活动连接时将忽略 `idle_timeout``ping_timeout`,并且不会发送 keepalive ping。 如果启用,客户端传输即使没有活动连接也会发送 keepalive ping。如果禁用则在没有活动连接时将忽略 `idle_timeout``ping_timeout`,并且不会发送 keepalive ping。
默认禁用。 默认禁用。
### HTTPUpgrade
```json
{
"type": "httpupgrade",
"host": "",
"path": "",
"headers": {}
}
```
#### host
主机域名。
默认服务器将验证。
#### path
HTTP 请求路径
默认服务器将验证。
#### headers
HTTP 请求的额外标头。
默认服务器将写入响应。

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 {

10
go.mod
View File

@ -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.4-0.20231102172319-a36b95857a9b
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.20231103103951-3540ea7680d8
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

21
go.sum
View File

@ -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,12 +114,12 @@ 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.4-0.20231102172319-a36b95857a9b h1:zfF0WjELB9E6eHrF1m4SeZ+tiGFFrI2GVeoRoQj/0lg=
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0= github.com/sagernet/sing-mux v0.1.4-0.20231102172319-a36b95857a9b/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg= github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc= github.com/sagernet/sing-quic v0.1.3-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=
@ -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.20231103103951-3540ea7680d8 h1:MAxenoNTNwOu1rhKCjWNNoTP9BrzEI9KE5E8VMOnysY=
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.20231103103951-3540ea7680d8/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=

View File

@ -22,7 +22,7 @@ type myInboundAdapter struct {
protocol string protocol string
network []string network []string
ctx context.Context ctx context.Context
router adapter.Router router adapter.ConnectionRouter
logger log.ContextLogger logger log.ContextLogger
tag string tag string
listenOptions option.ListenOptions listenOptions option.ListenOptions

View File

@ -8,6 +8,7 @@ import (
"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"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@ -35,7 +36,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
protocol: C.TypeHTTP, protocol: C.TypeHTTP,
network: []string{N.NetworkTCP}, network: []string{N.NetworkTCP},
ctx: ctx, ctx: ctx,
router: router, router: uot.NewRouter(router, logger),
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@ -37,7 +38,7 @@ func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogg
protocol: C.TypeMixed, protocol: C.TypeMixed,
network: []string{N.NetworkTCP}, network: []string{N.NetworkTCP},
ctx: ctx, ctx: ctx,
router: router, router: uot.NewRouter(router, logger),
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,

View File

@ -13,6 +13,7 @@ import (
"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"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@ -43,7 +44,7 @@ func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogg
protocol: C.TypeNaive, protocol: C.TypeNaive,
network: options.Network.Build(), network: options.Network.Build(),
ctx: ctx, ctx: ctx,
router: router, router: uot.NewRouter(router, logger),
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,

View File

@ -6,6 +6,8 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@ -48,21 +50,27 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
protocol: C.TypeShadowsocks, protocol: C.TypeShadowsocks,
network: options.Network.Build(), network: options.Network.Build(),
ctx: ctx, ctx: ctx,
router: router, router: uot.NewRouter(router, logger),
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,
}, },
} }
inbound.connHandler = inbound inbound.connHandler = inbound
inbound.packetHandler = inbound inbound.packetHandler = inbound
var err error
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
var udpTimeout int64 var udpTimeout int64
if options.UDPTimeout != 0 { if options.UDPTimeout != 0 {
udpTimeout = options.UDPTimeout udpTimeout = options.UDPTimeout
} else { } else {
udpTimeout = int64(C.UDPTimeout.Seconds()) udpTimeout = int64(C.UDPTimeout.Seconds())
} }
var err error
switch { switch {
case options.Method == shadowsocks.MethodNone: case options.Method == shadowsocks.MethodNone:
inbound.service = shadowsocks.NewNoneService(options.UDPTimeout, inbound.upstreamContextHandler()) inbound.service = shadowsocks.NewNoneService(options.UDPTimeout, inbound.upstreamContextHandler())

View File

@ -6,6 +6,8 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@ -38,7 +40,7 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
protocol: C.TypeShadowsocks, protocol: C.TypeShadowsocks,
network: options.Network.Build(), network: options.Network.Build(),
ctx: ctx, ctx: ctx,
router: router, router: uot.NewRouter(router, logger),
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,
@ -46,16 +48,18 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
} }
inbound.connHandler = inbound inbound.connHandler = inbound
inbound.packetHandler = inbound inbound.packetHandler = inbound
var err error
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
var udpTimeout int64 var udpTimeout int64
if options.UDPTimeout != 0 { if options.UDPTimeout != 0 {
udpTimeout = options.UDPTimeout udpTimeout = options.UDPTimeout
} else { } else {
udpTimeout = int64(C.UDPTimeout.Seconds()) udpTimeout = int64(C.UDPTimeout.Seconds())
} }
var ( var service shadowsocks.MultiService[int]
service shadowsocks.MultiService[int]
err error
)
if common.Contains(shadowaead_2022.List, options.Method) { if common.Contains(shadowaead_2022.List, options.Method) {
service, err = shadowaead_2022.NewMultiServiceWithPassword[int]( service, err = shadowaead_2022.NewMultiServiceWithPassword[int](
options.Method, options.Method,

View File

@ -6,6 +6,8 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@ -34,7 +36,7 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.
protocol: C.TypeShadowsocks, protocol: C.TypeShadowsocks,
network: options.Network.Build(), network: options.Network.Build(),
ctx: ctx, ctx: ctx,
router: router, router: uot.NewRouter(router, logger),
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,
@ -43,6 +45,11 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.
} }
inbound.connHandler = inbound inbound.connHandler = inbound
inbound.packetHandler = inbound inbound.packetHandler = inbound
var err error
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
var udpTimeout int64 var udpTimeout int64
if options.UDPTimeout != 0 { if options.UDPTimeout != 0 {
udpTimeout = options.UDPTimeout udpTimeout = options.UDPTimeout

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@ -30,7 +31,7 @@ func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogg
protocol: C.TypeSOCKS, protocol: C.TypeSOCKS,
network: []string{N.NetworkTCP}, network: []string{N.NetworkTCP},
ctx: ctx, ctx: ctx,
router: router, router: uot.NewRouter(router, logger),
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,

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

@ -6,7 +6,9 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@ -42,7 +44,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
protocol: C.TypeVLESS, protocol: C.TypeVLESS,
network: []string{N.NetworkTCP}, network: []string{N.NetworkTCP},
ctx: ctx, ctx: ctx,
router: router, router: uot.NewRouter(router, logger),
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,
@ -50,6 +52,11 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
ctx: ctx, ctx: ctx,
users: options.Users, users: options.Users,
} }
var err error
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
service := vless.NewService[int](logger, adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) service := vless.NewService[int](logger, adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound))
service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int { service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int {
return index return index
@ -59,7 +66,6 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
return it.Flow return it.Flow
})) }))
inbound.service = service inbound.service = service
var err error
if options.TLS != nil { if options.TLS != nil {
inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
if err != nil { if err != nil {

View File

@ -6,7 +6,9 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@ -42,7 +44,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
protocol: C.TypeVMess, protocol: C.TypeVMess,
network: []string{N.NetworkTCP}, network: []string{N.NetworkTCP},
ctx: ctx, ctx: ctx,
router: router, router: uot.NewRouter(router, logger),
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,
@ -50,6 +52,11 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
ctx: ctx, ctx: ctx,
users: options.Users, users: options.Users,
} }
var err error
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
var serviceOptions []vmess.ServiceOption var serviceOptions []vmess.ServiceOption
if timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil { if timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {
serviceOptions = append(serviceOptions, vmess.ServiceWithTimeFunc(timeFunc)) serviceOptions = append(serviceOptions, vmess.ServiceWithTimeFunc(timeFunc))
@ -59,7 +66,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
} }
service := vmess.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), serviceOptions...) service := vmess.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), serviceOptions...)
inbound.service = service inbound.service = service
err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.VMessUser) int { err = service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.VMessUser) int {
return index return index
}), common.Map(options.Users, func(it option.VMessUser) string { }), common.Map(options.Users, func(it option.VMessUser) string {
return it.UUID return it.UUID

View File

@ -75,6 +75,7 @@ nav:
- Multiplex: configuration/shared/multiplex.md - Multiplex: configuration/shared/multiplex.md
- V2Ray Transport: configuration/shared/v2ray-transport.md - V2Ray Transport: configuration/shared/v2ray-transport.md
- UDP over TCP: configuration/shared/udp-over-tcp.md - UDP over TCP: configuration/shared/udp-over-tcp.md
- TCP Brutal: configuration/shared/tcp-brutal.md
- Inbound: - Inbound:
- configuration/inbound/index.md - configuration/inbound/index.md
- Direct: configuration/inbound/direct.md - Direct: configuration/inbound/direct.md

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 {

23
option/multiplex.go Normal file
View File

@ -0,0 +1,23 @@
package option
type InboundMultiplexOptions struct {
Enabled bool `json:"enabled,omitempty"`
Padding bool `json:"padding,omitempty"`
Brutal *BrutalOptions `json:"brutal,omitempty"`
}
type OutboundMultiplexOptions struct {
Enabled bool `json:"enabled,omitempty"`
Protocol string `json:"protocol,omitempty"`
MaxConnections int `json:"max_connections,omitempty"`
MinStreams int `json:"min_streams,omitempty"`
MaxStreams int `json:"max_streams,omitempty"`
Padding bool `json:"padding,omitempty"`
Brutal bool `json:"brutal,omitempty"`
}
type BrutalOptions struct {
Enabled bool `json:"enabled,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
}

View File

@ -154,12 +154,3 @@ type ServerOptions struct {
func (o ServerOptions) Build() M.Socksaddr { func (o ServerOptions) Build() M.Socksaddr {
return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) return M.ParseSocksaddrHostPort(o.Server, o.ServerPort)
} }
type MultiplexOptions struct {
Enabled bool `json:"enabled,omitempty"`
Protocol string `json:"protocol,omitempty"`
MaxConnections int `json:"max_connections,omitempty"`
MinStreams int `json:"min_streams,omitempty"`
MaxStreams int `json:"max_streams,omitempty"`
Padding bool `json:"padding,omitempty"`
}

View File

@ -7,6 +7,7 @@ type ShadowsocksInboundOptions struct {
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
Users []ShadowsocksUser `json:"users,omitempty"` Users []ShadowsocksUser `json:"users,omitempty"`
Destinations []ShadowsocksDestination `json:"destinations,omitempty"` Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
} }
type ShadowsocksUser struct { type ShadowsocksUser struct {
@ -23,11 +24,11 @@ type ShadowsocksDestination struct {
type ShadowsocksOutboundOptions struct { type ShadowsocksOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
Method string `json:"method"` Method string `json:"method"`
Password string `json:"password"` Password string `json:"password"`
Plugin string `json:"plugin,omitempty"` Plugin string `json:"plugin,omitempty"`
PluginOptions string `json:"plugin_opts,omitempty"` PluginOptions string `json:"plugin_opts,omitempty"`
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
UDPOverTCPOptions *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"` Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
} }

View File

@ -17,11 +17,11 @@ type HTTPMixedInboundOptions struct {
type SocksOutboundOptions struct { type SocksOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
UDPOverTCPOptions *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
} }
type HTTPOutboundOptions struct { type HTTPOutboundOptions struct {

View File

@ -6,6 +6,7 @@ type TrojanInboundOptions struct {
TLS *InboundTLSOptions `json:"tls,omitempty"` TLS *InboundTLSOptions `json:"tls,omitempty"`
Fallback *ServerOptions `json:"fallback,omitempty"` Fallback *ServerOptions `json:"fallback,omitempty"`
FallbackForALPN map[string]*ServerOptions `json:"fallback_for_alpn,omitempty"` FallbackForALPN map[string]*ServerOptions `json:"fallback_for_alpn,omitempty"`
Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"` Transport *V2RayTransportOptions `json:"transport,omitempty"`
} }
@ -17,9 +18,9 @@ type TrojanUser struct {
type TrojanOutboundOptions struct { type TrojanOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
Password string `json:"password"` Password string `json:"password"`
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
TLS *OutboundTLSOptions `json:"tls,omitempty"` TLS *OutboundTLSOptions `json:"tls,omitempty"`
Multiplex *MultiplexOptions `json:"multiplex,omitempty"` Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"` Transport *V2RayTransportOptions `json:"transport,omitempty"`
} }

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

@ -7,11 +7,12 @@ import (
) )
type _V2RayTransportOptions struct { type _V2RayTransportOptions struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
HTTPOptions V2RayHTTPOptions `json:"-"` HTTPOptions V2RayHTTPOptions `json:"-"`
WebsocketOptions V2RayWebsocketOptions `json:"-"` WebsocketOptions V2RayWebsocketOptions `json:"-"`
QUICOptions V2RayQUICOptions `json:"-"` QUICOptions V2RayQUICOptions `json:"-"`
GRPCOptions V2RayGRPCOptions `json:"-"` GRPCOptions V2RayGRPCOptions `json:"-"`
HTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:"-"`
} }
type V2RayTransportOptions _V2RayTransportOptions type V2RayTransportOptions _V2RayTransportOptions
@ -29,6 +30,8 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) {
v = o.QUICOptions v = o.QUICOptions
case C.V2RayTransportTypeGRPC: case C.V2RayTransportTypeGRPC:
v = o.GRPCOptions v = o.GRPCOptions
case C.V2RayTransportTypeHTTPUpgrade:
v = o.HTTPUpgradeOptions
default: default:
return nil, E.New("unknown transport type: " + o.Type) return nil, E.New("unknown transport type: " + o.Type)
} }
@ -50,6 +53,8 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
v = &o.QUICOptions v = &o.QUICOptions
case C.V2RayTransportTypeGRPC: case C.V2RayTransportTypeGRPC:
v = &o.GRPCOptions v = &o.GRPCOptions
case C.V2RayTransportTypeHTTPUpgrade:
v = &o.HTTPUpgradeOptions
default: default:
return E.New("unknown transport type: " + o.Type) return E.New("unknown transport type: " + o.Type)
} }
@ -85,3 +90,9 @@ type V2RayGRPCOptions struct {
PermitWithoutStream bool `json:"permit_without_stream,omitempty"` PermitWithoutStream bool `json:"permit_without_stream,omitempty"`
ForceLite bool `json:"-"` // for test ForceLite bool `json:"-"` // for test
} }
type V2RayHTTPUpgradeOptions struct {
Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
Headers HTTPHeader `json:"headers,omitempty"`
}

View File

@ -2,9 +2,10 @@ package option
type VLESSInboundOptions struct { type VLESSInboundOptions struct {
ListenOptions ListenOptions
Users []VLESSUser `json:"users,omitempty"` Users []VLESSUser `json:"users,omitempty"`
TLS *InboundTLSOptions `json:"tls,omitempty"` TLS *InboundTLSOptions `json:"tls,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"` Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"`
} }
type VLESSUser struct { type VLESSUser struct {
@ -16,11 +17,11 @@ type VLESSUser struct {
type VLESSOutboundOptions struct { type VLESSOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
UUID string `json:"uuid"` UUID string `json:"uuid"`
Flow string `json:"flow,omitempty"` Flow string `json:"flow,omitempty"`
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
TLS *OutboundTLSOptions `json:"tls,omitempty"` TLS *OutboundTLSOptions `json:"tls,omitempty"`
Multiplex *MultiplexOptions `json:"multiplex,omitempty"` Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"` Transport *V2RayTransportOptions `json:"transport,omitempty"`
PacketEncoding *string `json:"packet_encoding,omitempty"` PacketEncoding *string `json:"packet_encoding,omitempty"`
} }

View File

@ -2,9 +2,10 @@ package option
type VMessInboundOptions struct { type VMessInboundOptions struct {
ListenOptions ListenOptions
Users []VMessUser `json:"users,omitempty"` Users []VMessUser `json:"users,omitempty"`
TLS *InboundTLSOptions `json:"tls,omitempty"` TLS *InboundTLSOptions `json:"tls,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"` Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"`
} }
type VMessUser struct { type VMessUser struct {
@ -16,14 +17,14 @@ type VMessUser struct {
type VMessOutboundOptions struct { type VMessOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
UUID string `json:"uuid"` UUID string `json:"uuid"`
Security string `json:"security"` Security string `json:"security"`
AlterId int `json:"alter_id,omitempty"` AlterId int `json:"alter_id,omitempty"`
GlobalPadding bool `json:"global_padding,omitempty"` GlobalPadding bool `json:"global_padding,omitempty"`
AuthenticatedLength bool `json:"authenticated_length,omitempty"` AuthenticatedLength bool `json:"authenticated_length,omitempty"`
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
TLS *OutboundTLSOptions `json:"tls,omitempty"` TLS *OutboundTLSOptions `json:"tls,omitempty"`
PacketEncoding string `json:"packet_encoding,omitempty"` PacketEncoding string `json:"packet_encoding,omitempty"`
Multiplex *MultiplexOptions `json:"multiplex,omitempty"` Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"` Transport *V2RayTransportOptions `json:"transport,omitempty"`
} }

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

@ -62,9 +62,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
return nil, err return nil, err
} }
} }
uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions) uotOptions := common.PtrValueOrDefault(options.UDPOverTCP)
if !uotOptions.Enabled { if !uotOptions.Enabled {
outbound.multiplexDialer, err = mux.NewClientWithOptions((*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions)) outbound.multiplexDialer, err = mux.NewClientWithOptions((*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -54,7 +54,7 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio
client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password),
resolve: version == socks.Version4, resolve: version == socks.Version4,
} }
uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions) uotOptions := common.PtrValueOrDefault(options.UDPOverTCP)
if uotOptions.Enabled { if uotOptions.Enabled {
outbound.uotClient = &uot.Client{ outbound.uotClient = &uot.Client{
Dialer: outbound.client, Dialer: outbound.client,

View File

@ -16,7 +16,6 @@ import (
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geoip"
"github.com/sagernet/sing-box/common/geosite" "github.com/sagernet/sing-box/common/geosite"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/common/sniff" "github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@ -27,6 +26,7 @@ import (
"github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/transport/fakeip" "github.com/sagernet/sing-box/transport/fakeip"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
mux "github.com/sagernet/sing-mux"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-vmess" "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
@ -606,30 +606,13 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
metadata.Network = N.NetworkTCP metadata.Network = N.NetworkTCP
switch metadata.Destination.Fqdn { switch metadata.Destination.Fqdn {
case mux.Destination.Fqdn: case mux.Destination.Fqdn:
r.logger.InfoContext(ctx, "inbound multiplex connection") return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in inbound options instead.")
handler := adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r)
return mux.HandleConnection(ctx, handler, r.logger, conn, adapter.UpstreamMetadata(metadata))
case vmess.MuxDestination.Fqdn: case vmess.MuxDestination.Fqdn:
r.logger.InfoContext(ctx, "inbound legacy multiplex connection") return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.")
return vmess.HandleMuxConnection(ctx, conn, adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r))
case uot.MagicAddress: case uot.MagicAddress:
request, err := uot.ReadRequest(conn) return E.New("global UoT not supported since sing-box v1.7.0.")
if err != nil {
return E.Cause(err, "read UoT request")
}
if request.IsConnect {
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
} else {
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
}
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = request.Destination
return r.RoutePacketConnection(ctx, uot.NewConn(conn, *request), metadata)
case uot.LegacyMagicAddress: case uot.LegacyMagicAddress:
r.logger.InfoContext(ctx, "inbound legacy UoT connection") return E.New("global UoT (legacy) not supported since sing-box v1.7.0.")
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
} }
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) { if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {

View File

@ -11,7 +11,7 @@ require (
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
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-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
@ -40,6 +40,8 @@ require (
github.com/go-chi/render v1.0.3 // indirect github.com/go-chi/render v1.0.3 // 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/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // 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
@ -73,15 +75,15 @@ require (
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab // indirect github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-mux v0.1.3 // indirect github.com/sagernet/sing-mux v0.1.4-0.20231102172319-a36b95857a9b // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6 // indirect github.com/sagernet/sing-tun v0.1.17-0.20231103030016-7f4ef65c32eb // indirect
github.com/sagernet/sing-vmess v0.1.8 // indirect github.com/sagernet/sing-vmess v0.1.8 // indirect
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect

View File

@ -43,6 +43,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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@ -127,12 +131,12 @@ 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.4-0.20231102172319-a36b95857a9b h1:zfF0WjELB9E6eHrF1m4SeZ+tiGFFrI2GVeoRoQj/0lg=
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0= github.com/sagernet/sing-mux v0.1.4-0.20231102172319-a36b95857a9b/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg= github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc= github.com/sagernet/sing-quic v0.1.3-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=
@ -141,8 +145,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.20231103030016-7f4ef65c32eb h1:iYopGh4nbVsvQ+oR2Xkc+hO5s+aacsmq5nQkEFPPhxY=
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.20231103030016-7f4ef65c32eb/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=
@ -151,10 +155,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/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0= github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0=
@ -222,6 +226,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=

View File

@ -18,7 +18,7 @@ var muxProtocols = []string{
} }
func TestVMessSMux(t *testing.T) { func TestVMessSMux(t *testing.T) {
testVMessMux(t, option.MultiplexOptions{ testVMessMux(t, option.OutboundMultiplexOptions{
Enabled: true, Enabled: true,
Protocol: "smux", Protocol: "smux",
}) })
@ -27,7 +27,7 @@ func TestVMessSMux(t *testing.T) {
func TestShadowsocksMux(t *testing.T) { func TestShadowsocksMux(t *testing.T) {
for _, protocol := range muxProtocols { for _, protocol := range muxProtocols {
t.Run(protocol, func(t *testing.T) { t.Run(protocol, func(t *testing.T) {
testShadowsocksMux(t, option.MultiplexOptions{ testShadowsocksMux(t, option.OutboundMultiplexOptions{
Enabled: true, Enabled: true,
Protocol: protocol, Protocol: protocol,
}) })
@ -36,7 +36,7 @@ func TestShadowsocksMux(t *testing.T) {
} }
func TestShadowsockH2Mux(t *testing.T) { func TestShadowsockH2Mux(t *testing.T) {
testShadowsocksMux(t, option.MultiplexOptions{ testShadowsocksMux(t, option.OutboundMultiplexOptions{
Enabled: true, Enabled: true,
Protocol: "h2mux", Protocol: "h2mux",
Padding: true, Padding: true,
@ -44,14 +44,14 @@ func TestShadowsockH2Mux(t *testing.T) {
} }
func TestShadowsockSMuxPadding(t *testing.T) { func TestShadowsockSMuxPadding(t *testing.T) {
testShadowsocksMux(t, option.MultiplexOptions{ testShadowsocksMux(t, option.OutboundMultiplexOptions{
Enabled: true, Enabled: true,
Protocol: "smux", Protocol: "smux",
Padding: true, Padding: true,
}) })
} }
func testShadowsocksMux(t *testing.T, options option.MultiplexOptions) { func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) {
method := shadowaead_2022.List[0] method := shadowaead_2022.List[0]
password := mkBase64(t, 16) password := mkBase64(t, 16)
startInstance(t, option.Options{ startInstance(t, option.Options{
@ -90,9 +90,9 @@ func testShadowsocksMux(t *testing.T, options option.MultiplexOptions) {
Server: "127.0.0.1", Server: "127.0.0.1",
ServerPort: serverPort, ServerPort: serverPort,
}, },
Method: method, Method: method,
Password: password, Password: password,
MultiplexOptions: &options, Multiplex: &options,
}, },
}, },
}, },
@ -110,7 +110,7 @@ func testShadowsocksMux(t *testing.T, options option.MultiplexOptions) {
testSuit(t, clientPort, testPort) testSuit(t, clientPort, testPort)
} }
func testVMessMux(t *testing.T, options option.MultiplexOptions) { func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) {
user, _ := uuid.NewV4() user, _ := uuid.NewV4()
startInstance(t, option.Options{ startInstance(t, option.Options{
Inbounds: []option.Inbound{ Inbounds: []option.Inbound{
@ -136,6 +136,9 @@ func testVMessMux(t *testing.T, options option.MultiplexOptions) {
UUID: user.String(), UUID: user.String(),
}, },
}, },
Multiplex: &option.InboundMultiplexOptions{
Enabled: true,
},
}, },
}, },
}, },

View File

@ -232,7 +232,7 @@ func TestShadowsocksUoT(t *testing.T) {
}, },
Method: method, Method: method,
Password: password, Password: password,
UDPOverTCPOptions: &option.UDPOverTCPOptions{ UDPOverTCP: &option.UDPOverTCPOptions{
Enabled: true, Enabled: true,
}, },
}, },

View File

@ -0,0 +1,16 @@
package main
import (
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
)
func TestV2RayHTTPUpgrade(t *testing.T) {
t.Run("self", func(t *testing.T) {
testV2RayTransportSelf(t, &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeHTTPUpgrade,
})
})
}

View File

@ -8,6 +8,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing-box/transport/v2rayhttp"
"github.com/sagernet/sing-box/transport/v2rayhttpupgrade"
"github.com/sagernet/sing-box/transport/v2raywebsocket" "github.com/sagernet/sing-box/transport/v2raywebsocket"
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"
@ -35,6 +36,8 @@ func NewServerTransport(ctx context.Context, options option.V2RayTransportOption
return NewQUICServer(ctx, options.QUICOptions, tlsConfig, handler) return NewQUICServer(ctx, options.QUICOptions, tlsConfig, handler)
case C.V2RayTransportTypeGRPC: case C.V2RayTransportTypeGRPC:
return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler) return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler)
case C.V2RayTransportTypeHTTPUpgrade:
return v2rayhttpupgrade.NewServer(ctx, options.HTTPUpgradeOptions, tlsConfig, handler)
default: default:
return nil, E.New("unknown transport type: " + options.Type) return nil, E.New("unknown transport type: " + options.Type)
} }
@ -50,13 +53,14 @@ 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), nil return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig)
case C.V2RayTransportTypeQUIC: case C.V2RayTransportTypeQUIC:
if tlsConfig == nil { if tlsConfig == nil {
return nil, C.ErrTLSRequired return nil, C.ErrTLSRequired
} }
return NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig) return NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig)
case C.V2RayTransportTypeHTTPUpgrade:
return v2rayhttpupgrade.NewClient(ctx, dialer, serverAddr, options.HTTPUpgradeOptions, tlsConfig)
default: default:
return nil, E.New("unknown transport type: " + options.Type) return nil, E.New("unknown transport type: " + options.Type)
} }

View File

@ -100,7 +100,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
conn.setup(nil, err) conn.setup(nil, err)
} else if response.StatusCode != 200 { } else if response.StatusCode != 200 {
response.Body.Close() response.Body.Close()
conn.setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status)) conn.setup(nil, E.New("unexpected status: ", response.Status))
} else { } else {
conn.setup(response.Body, nil) conn.setup(response.Body, nil)
} }

View File

@ -35,10 +35,6 @@ type Server struct {
path string path string
} }
func (s *Server) Network() []string {
return []string{N.NetworkTCP}
}
func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
server := &Server{ server := &Server{
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
@ -92,6 +88,10 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques
s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr))
} }
func (s *Server) Network() []string {
return []string{N.NetworkTCP}
}
func (s *Server) Serve(listener net.Listener) error { func (s *Server) Serve(listener net.Listener) error {
if s.tlsConfig != nil { if s.tlsConfig != nil {
if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {

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.New("failed to set path: " + err.Error()) return nil, E.New("parse path: " + err.Error())
} }
client.url = &uri client.url = &uri
return client, nil return client, nil
@ -143,7 +143,7 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
conn.Setup(nil, err) conn.Setup(nil, err)
} else if response.StatusCode != 200 { } else if response.StatusCode != 200 {
response.Body.Close() response.Body.Close()
conn.Setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status)) conn.Setup(nil, E.New("unexpected status: ", response.Status))
} else { } else {
conn.Setup(response.Body, nil) conn.Setup(response.Body, nil)
} }

View File

@ -40,10 +40,6 @@ type Server struct {
headers http.Header headers http.Header
} }
func (s *Server) Network() []string {
return []string{N.NetworkTCP}
}
func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
server := &Server{ server := &Server{
ctx: ctx, ctx: ctx,
@ -153,6 +149,10 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques
s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr))
} }
func (s *Server) Network() []string {
return []string{N.NetworkTCP}
}
func (s *Server) Serve(listener net.Listener) error { func (s *Server) Serve(listener net.Listener) error {
if s.tlsConfig != nil { if s.tlsConfig != nil {
if len(s.tlsConfig.NextProtos()) == 0 { if len(s.tlsConfig.NextProtos()) == 0 {

View File

@ -0,0 +1,118 @@
package v2rayhttpupgrade
import (
std_bufio "bufio"
"context"
"net"
"net/http"
"net/url"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
sHTTP "github.com/sagernet/sing/protocol/http"
)
var _ adapter.V2RayClientTransport = (*Client)(nil)
type Client struct {
dialer N.Dialer
tlsConfig tls.Config
serverAddr M.Socksaddr
requestURL url.URL
headers http.Header
host string
}
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.Config) (*Client, error) {
if tlsConfig != nil {
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"http/1.1"})
}
}
var host string
if options.Host != "" {
host = options.Host
} else if tlsConfig != nil && tlsConfig.ServerName() != "" {
host = tlsConfig.ServerName()
} else {
host = serverAddr.String()
}
var requestURL url.URL
if tlsConfig == nil {
requestURL.Scheme = "http"
} else {
requestURL.Scheme = "https"
}
requestURL.Host = serverAddr.String()
requestURL.Path = options.Path
err := sHTTP.URLSetPath(&requestURL, options.Path)
if err != nil {
return nil, E.Cause(err, "parse path")
}
if !strings.HasPrefix(requestURL.Path, "/") {
requestURL.Path = "/" + requestURL.Path
}
headers := make(http.Header)
for key, value := range options.Headers {
headers[key] = value
}
return &Client{
dialer: dialer,
tlsConfig: tlsConfig,
serverAddr: serverAddr,
requestURL: requestURL,
headers: headers,
host: host,
}, nil
}
func (c *Client) DialContext(ctx context.Context) (net.Conn, 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
}
}
request := &http.Request{
Method: http.MethodGet,
URL: &c.requestURL,
Header: c.headers.Clone(),
Host: c.host,
}
request.Header.Set("Connection", "Upgrade")
request.Header.Set("Upgrade", "websocket")
err = request.Write(conn)
if err != nil {
return nil, err
}
bufReader := std_bufio.NewReader(conn)
response, err := http.ReadResponse(bufReader, request)
if err != nil {
return nil, err
}
if response.StatusCode != 101 ||
!strings.EqualFold(response.Header.Get("Connection"), "upgrade") ||
!strings.EqualFold(response.Header.Get("Upgrade"), "websocket") {
return nil, E.New("unexpected status: ", response.Status)
}
if bufReader.Buffered() > 0 {
buffer := buf.NewSize(bufReader.Buffered())
_, err = buffer.ReadFullFrom(bufReader, buffer.Len())
if err != nil {
return nil, err
}
conn = bufio.NewCachedConn(conn, buffer)
}
return conn, nil
}

View File

@ -0,0 +1,139 @@
package v2rayhttpupgrade
import (
"context"
"net"
"net/http"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
sHttp "github.com/sagernet/sing/protocol/http"
)
var _ adapter.V2RayServerTransport = (*Server)(nil)
type Server struct {
ctx context.Context
tlsConfig tls.ServerConfig
handler adapter.V2RayServerTransportHandler
httpServer *http.Server
host string
path string
headers http.Header
}
func NewServer(ctx context.Context, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
server := &Server{
ctx: ctx,
tlsConfig: tlsConfig,
handler: handler,
host: options.Host,
path: options.Path,
headers: options.Headers.Build(),
}
if !strings.HasPrefix(server.path, "/") {
server.path = "/" + server.path
}
server.httpServer = &http.Server{
Handler: server,
ReadHeaderTimeout: C.TCPTimeout,
MaxHeaderBytes: http.DefaultMaxHeaderBytes,
BaseContext: func(net.Listener) context.Context {
return ctx
},
TLSNextProto: make(map[string]func(*http.Server, *tls.STDConn, http.Handler)),
}
return server, nil
}
type httpFlusher interface {
FlushError() error
}
func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
host := request.Host
if len(s.host) > 0 && host != s.host {
s.invalidRequest(writer, request, http.StatusBadRequest, E.New("bad host: ", host))
return
}
if !strings.HasPrefix(request.URL.Path, s.path) {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path))
return
}
if request.Method != http.MethodGet {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("bad method: ", request.Method))
return
}
if !strings.EqualFold(request.Header.Get("Connection"), "upgrade") {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("not a upgrade request"))
return
}
if !strings.EqualFold(request.Header.Get("Upgrade"), "websocket") {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("not a websocket request"))
return
}
if request.Header.Get("Sec-WebSocket-Key") != "" {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("real websocket request received"))
return
}
writer.Header().Set("Connection", "upgrade")
writer.Header().Set("Upgrade", "websocket")
writer.WriteHeader(http.StatusSwitchingProtocols)
if flusher, isFlusher := writer.(httpFlusher); isFlusher {
err := flusher.FlushError()
if err != nil {
s.invalidRequest(writer, request, http.StatusInternalServerError, E.New("flush response"))
}
}
hijacker, canHijack := writer.(http.Hijacker)
if !canHijack {
s.invalidRequest(writer, request, http.StatusInternalServerError, E.New("invalid connection, maybe HTTP/2"))
return
}
conn, _, err := hijacker.Hijack()
if err != nil {
s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed"))
return
}
var metadata M.Metadata
metadata.Source = sHttp.SourceAddress(request)
s.handler.NewConnection(request.Context(), conn, metadata)
}
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {
if statusCode > 0 {
writer.WriteHeader(statusCode)
}
s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr))
}
func (s *Server) Network() []string {
return []string{N.NetworkTCP}
}
func (s *Server) Serve(listener net.Listener) error {
if s.tlsConfig != nil {
if len(s.tlsConfig.NextProtos()) == 0 {
s.tlsConfig.SetNextProtos([]string{"http/1.1"})
}
listener = aTLS.NewListener(listener, s.tlsConfig)
}
return s.httpServer.Serve(listener)
}
func (s *Server) ServePacket(listener net.PacketConn) error {
return os.ErrInvalid
}
func (s *Server) Close() error {
return common.Close(common.PtrOrNil(s.httpServer))
}

View File

@ -5,58 +5,37 @@ 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" 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) (*Client, error) {
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,37 +47,62 @@ 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 return nil, E.Cause(err, "parse path")
}
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 headers.Get("User-Agent") == "" {
headers.Set("User-Agent", "Go-http-client/1.1")
}
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
} }