mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Compare commits
8 Commits
dev-next
...
v1.7.0-alp
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c2ce1d0024 | ||
![]() |
c85b3fff14 | ||
![]() |
5ade21b174 | ||
![]() |
53e88eb4a1 | ||
![]() |
d3c8630dee | ||
![]() |
25c58c3be9 | ||
![]() |
28b14c90f9 | ||
![]() |
9f53b3e59b |
3
Makefile
3
Makefile
@ -84,6 +84,9 @@ upload_android:
|
||||
release_android: lib_android update_android_version build_android upload_android
|
||||
|
||||
publish_android:
|
||||
cd ../sing-box-for-android && ./gradlew :app:publishReleaseBundle
|
||||
|
||||
publish_android_appcenter:
|
||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadRelease
|
||||
|
||||
build_ios:
|
||||
|
104
adapter/conn_router.go
Normal file
104
adapter/conn_router.go
Normal 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)
|
||||
}
|
@ -2,14 +2,12 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
mdns "github.com/miekg/dns"
|
||||
@ -24,8 +22,7 @@ type Router interface {
|
||||
|
||||
FakeIPStore() FakeIPStore
|
||||
|
||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
ConnectionRouter
|
||||
|
||||
GeoIPReader() *geoip.Reader
|
||||
LoadGeosite(code string) (Rule, error)
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/bufio/deadline"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@ -44,7 +45,14 @@ func (d *DetourDialer) DialContext(ctx context.Context, network string, destinat
|
||||
if err != nil {
|
||||
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) {
|
||||
|
@ -6,7 +6,9 @@ import (
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -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
65
common/mux/router.go
Normal 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)
|
||||
}
|
32
common/mux/v2ray_legacy.go
Normal file
32
common/mux/v2ray_legacy.go
Normal 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
53
common/uot/router.go
Normal 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
3
constant/speed.go
Normal file
@ -0,0 +1,3 @@
|
||||
package constant
|
||||
|
||||
const MbpsToBps = 125000
|
@ -1,8 +1,9 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
V2RayTransportTypeHTTP = "http"
|
||||
V2RayTransportTypeWebsocket = "ws"
|
||||
V2RayTransportTypeQUIC = "quic"
|
||||
V2RayTransportTypeGRPC = "grpc"
|
||||
V2RayTransportTypeHTTP = "http"
|
||||
V2RayTransportTypeWebsocket = "ws"
|
||||
V2RayTransportTypeQUIC = "quic"
|
||||
V2RayTransportTypeGRPC = "grpc"
|
||||
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
|
||||
)
|
||||
|
@ -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
|
||||
|
||||
* 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
|
||||
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
|
||||
|
||||
@ -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
|
||||
* 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
|
||||
|
||||
* Fix compatibility with Android 14
|
||||
|
@ -62,7 +62,7 @@ Hysteria 用户
|
||||
|
||||
#### ignore_client_bandwidth
|
||||
|
||||
命令客户端使用 BBR 流量控制算法而不是 Hysteria CC。
|
||||
命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
|
||||
|
||||
与 `up_mbps` 和 `down_mbps` 冲突。
|
||||
|
||||
|
@ -8,7 +8,8 @@
|
||||
... // Listen Fields
|
||||
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@ -23,7 +24,8 @@
|
||||
"name": "sekai",
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
],
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@ -41,7 +43,8 @@
|
||||
"server_port": 8080,
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
],
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@ -82,3 +85,7 @@ Both if empty.
|
||||
| none | / |
|
||||
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
|
||||
| other methods | any string |
|
||||
|
||||
#### multiplex
|
||||
|
||||
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||
|
@ -8,7 +8,8 @@
|
||||
... // 监听字段
|
||||
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@ -23,7 +24,8 @@
|
||||
"name": "sekai",
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
],
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@ -41,7 +43,8 @@
|
||||
"server_port": 8080,
|
||||
"password": "PCD2Z4o12bKUoFa3cC97Hw=="
|
||||
}
|
||||
]
|
||||
],
|
||||
"multiplex": {}
|
||||
}
|
||||
```
|
||||
|
||||
@ -81,4 +84,8 @@ See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|---------------|------------------------------------------|
|
||||
| none | / |
|
||||
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
|
||||
| other methods | 任意字符串 |
|
||||
| other methods | 任意字符串 |
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||
|
@ -24,6 +24,7 @@
|
||||
"server_port": 8081
|
||||
}
|
||||
},
|
||||
"multiplex": {},
|
||||
"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.
|
||||
|
||||
#### multiplex
|
||||
|
||||
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
@ -24,6 +24,7 @@
|
||||
"server_port": 8081
|
||||
}
|
||||
},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@ -60,6 +61,10 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
如果不为空,ALPN 不在此列表中的 TLS 回退请求将被拒绝。
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
@ -48,7 +48,7 @@ TUIC 用户密码
|
||||
|
||||
#### congestion_control
|
||||
|
||||
QUIC 流量控制算法
|
||||
QUIC 拥塞控制算法
|
||||
|
||||
可选值: `cubic`, `new_reno`, `bbr`
|
||||
|
||||
|
@ -22,6 +22,12 @@
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"endpoint_independent_nat": false,
|
||||
"stack": "system",
|
||||
"include_interface": [
|
||||
@ -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.
|
||||
|
||||
#### inet4_route_exclude_address
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### inet6_route_exclude_address
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
!!! info ""
|
||||
|
@ -22,6 +22,12 @@
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"endpoint_independent_nat": false,
|
||||
"stack": "system",
|
||||
"include_interface": [
|
||||
@ -131,6 +137,14 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
#### inet4_route_exclude_address
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
#### inet6_route_exclude_address
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
启用独立于端点的 NAT。
|
||||
|
@ -15,6 +15,7 @@
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@ -49,6 +50,10 @@ Available values:
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### multiplex
|
||||
|
||||
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
@ -15,6 +15,7 @@
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@ -49,6 +50,10 @@ VLESS 子协议。
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
|
@ -15,6 +15,7 @@
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@ -44,6 +45,10 @@ VMess users.
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### multiplex
|
||||
|
||||
See [Multiplex](/configuration/shared/multiplex#inbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
@ -15,6 +15,7 @@
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"multiplex": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
@ -44,6 +45,10 @@ VMess 用户。
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
|
@ -44,7 +44,7 @@
|
||||
|
||||
最大带宽。
|
||||
|
||||
如果为空,将使用 BBR 流量控制算法而不是 Hysteria CC。
|
||||
如果为空,将使用 BBR 拥塞控制算法而不是 Hysteria CC。
|
||||
|
||||
#### obfs.type
|
||||
|
||||
|
@ -95,7 +95,7 @@ Conflict with `multiplex`.
|
||||
|
||||
#### multiplex
|
||||
|
||||
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
|
||||
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
|
@ -95,7 +95,7 @@ UDP over TCP 配置。
|
||||
|
||||
#### multiplex
|
||||
|
||||
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
|
@ -51,7 +51,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
#### multiplex
|
||||
|
||||
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
|
||||
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
|
@ -51,7 +51,7 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||
|
||||
#### multiplex
|
||||
|
||||
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
|
@ -51,7 +51,7 @@ TUIC 用户密码
|
||||
|
||||
#### congestion_control
|
||||
|
||||
QUIC 流量控制算法
|
||||
QUIC 拥塞控制算法
|
||||
|
||||
可选值: `cubic`, `new_reno`, `bbr`
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
"multiplex": {},
|
||||
"transport": {},
|
||||
|
||||
... // Dial Fields
|
||||
@ -68,6 +69,10 @@ UDP packet encoding, xudp is used by default.
|
||||
| packetaddr | Supported by v2ray 5+ |
|
||||
| xudp | Supported by xray |
|
||||
|
||||
#### multiplex
|
||||
|
||||
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
@ -12,6 +12,7 @@
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
"multiplex": {},
|
||||
"transport": {},
|
||||
|
||||
... // 拨号字段
|
||||
@ -68,6 +69,10 @@ UDP 包编码,默认使用 xudp。
|
||||
| packetaddr | 由 v2ray 5+ 支持 |
|
||||
| xudp | 由 xray 支持 |
|
||||
|
||||
#### multiplex
|
||||
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
|
@ -15,8 +15,8 @@
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
"multiplex": {},
|
||||
"transport": {},
|
||||
"multiplex": {},
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
@ -96,7 +96,7 @@ UDP packet encoding.
|
||||
|
||||
#### multiplex
|
||||
|
||||
Multiplex configuration, see [Multiplex](/configuration/shared/multiplex).
|
||||
See [Multiplex](/configuration/shared/multiplex#outbound) for details.
|
||||
|
||||
#### transport
|
||||
|
||||
|
@ -96,7 +96,7 @@ UDP 包编码。
|
||||
|
||||
#### multiplex
|
||||
|
||||
多路复用配置, 参阅 [多路复用](/zh/configuration/shared/multiplex)。
|
||||
参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
|
@ -7,28 +7,26 @@
|
||||
"tcp_fast_open": false,
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"udp_timeout": 300,
|
||||
"detour": "another-in",
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"sniff_timeout": "300ms",
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"udp_timeout": 300,
|
||||
"proxy_protocol": false,
|
||||
"proxy_protocol_accept_no_header": false,
|
||||
"detour": "another-in"
|
||||
"udp_disable_domain_unmapping": false
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
| Field | Available Context |
|
||||
|-----------------------------------|-------------------------------------------------------------------|
|
||||
| `listen` | Needs to listen on TCP or UDP. |
|
||||
| `listen_port` | Needs to listen on TCP or UDP. |
|
||||
| `tcp_fast_open` | Needs to listen on TCP. |
|
||||
| `tcp_multi_path` | Needs to listen on TCP. |
|
||||
| `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
|
||||
| `proxy_protocol` | Needs to listen on TCP. |
|
||||
| `proxy_protocol_accept_no_header` | When `proxy_protocol` enabled |
|
||||
| Field | Available Context |
|
||||
|--------------------------------|-------------------------------------------------------------------|
|
||||
| `listen` | Needs to listen on TCP or UDP. |
|
||||
| `listen_port` | Needs to listen on TCP or UDP. |
|
||||
| `tcp_fast_open` | Needs to listen on TCP. |
|
||||
| `tcp_multi_path` | Needs to listen on TCP. |
|
||||
| `udp_timeout` | Needs to assemble UDP connections, currently Tun and Shadowsocks. |
|
||||
| `udp_disable_domain_unmapping` | Needs to listen on UDP and accept domain UDP addresses. |
|
||||
|
||||
#### listen
|
||||
|
||||
@ -56,6 +54,16 @@ Enable TCP Multi Path.
|
||||
|
||||
Enable UDP fragmentation.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
|
||||
#### detour
|
||||
|
||||
If set, connections will be forwarded to the specified inbound.
|
||||
|
||||
Requires target inbound support, see [Injectable](/configuration/inbound/#fields).
|
||||
|
||||
#### sniff
|
||||
|
||||
Enable sniffing.
|
||||
@ -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.
|
||||
|
||||
#### 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
|
||||
|
||||
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).
|
||||
This option is used for compatibility with clients that
|
||||
do not support receiving UDP packets with domain addresses, such as Surge.
|
||||
|
@ -7,14 +7,13 @@
|
||||
"tcp_fast_open": false,
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"udp_timeout": 300,
|
||||
"detour": "another-in",
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"sniff_timeout": "300ms",
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"udp_timeout": 300,
|
||||
"proxy_protocol": false,
|
||||
"proxy_protocol_accept_no_header": false,
|
||||
"detour": "another-in"
|
||||
"udp_disable_domain_unmapping": false
|
||||
}
|
||||
```
|
||||
|
||||
@ -26,8 +25,7 @@
|
||||
| `tcp_fast_open` | 需要监听 TCP。 |
|
||||
| `tcp_multi_path` | 需要监听 TCP。 |
|
||||
| `udp_timeout` | 需要组装 UDP 连接, 当前为 Tun 和 Shadowsocks。 |
|
||||
| `proxy_protocol` | 需要监听 TCP。 |
|
||||
| `proxy_protocol_accept_no_header` | `proxy_protocol` 启用时 |
|
||||
|
|
||||
|
||||
### 字段
|
||||
|
||||
@ -57,6 +55,16 @@
|
||||
|
||||
启用 UDP 分段。
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
||||
|
||||
#### detour
|
||||
|
||||
如果设置,连接将被转发到指定的入站。
|
||||
|
||||
需要目标入站支持,参阅 [注入支持](/zh/configuration/inbound/#_3)。
|
||||
|
||||
#### sniff
|
||||
|
||||
启用协议探测。
|
||||
@ -83,20 +91,8 @@
|
||||
|
||||
如果 `sniff_override_destination` 生效,它的值将作为后备。
|
||||
|
||||
#### udp_timeout
|
||||
#### udp_disable_domain_unmapping
|
||||
|
||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
||||
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
|
||||
|
||||
#### proxy_protocol
|
||||
|
||||
解析连接头中的 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)。
|
||||
|
||||
#### proxy_protocol_accept_no_header
|
||||
|
||||
接受没有代理协议标头的连接。
|
||||
|
||||
#### detour
|
||||
|
||||
如果设置,连接将被转发到指定的入站。
|
||||
|
||||
需要目标入站支持,参阅 [注入支持](/zh/configuration/inbound/#_3)。
|
||||
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
|
||||
|
@ -1,8 +1,14 @@
|
||||
### Server Requirements
|
||||
### Inbound
|
||||
|
||||
`sing-box` :)
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"padding": false,
|
||||
"brutal": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Structure
|
||||
### Outbound
|
||||
|
||||
```json
|
||||
{
|
||||
@ -11,11 +17,27 @@
|
||||
"max_connections": 4,
|
||||
"min_streams": 4,
|
||||
"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
|
||||
|
||||
@ -59,3 +81,6 @@ Conflict with `max_connections` and `min_streams`.
|
||||
|
||||
Enable padding.
|
||||
|
||||
#### brutal
|
||||
|
||||
See [TCP Brutal](/configuration/shared/tcp-brutal) for details.
|
||||
|
@ -1,8 +1,14 @@
|
||||
### 服务器要求
|
||||
### 入站
|
||||
|
||||
`sing-box` :)
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"padding": false,
|
||||
"brutal": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 结构
|
||||
### 出站
|
||||
|
||||
```json
|
||||
{
|
||||
@ -10,11 +16,27 @@
|
||||
"protocol": "smux",
|
||||
"max_connections": 4,
|
||||
"min_streams": 4,
|
||||
"max_streams": 0
|
||||
"max_streams": 0,
|
||||
"padding": false,
|
||||
"brutal": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
### 入站字段
|
||||
|
||||
#### enabled
|
||||
|
||||
启用多路复用支持。
|
||||
|
||||
#### padding
|
||||
|
||||
如果启用,将拒绝非填充连接。
|
||||
|
||||
#### brutal
|
||||
|
||||
参阅 [TCP Brutal](/zh/configuration/shared/tcp-brutal)。
|
||||
|
||||
### 出站字段
|
||||
|
||||
#### enabled
|
||||
|
||||
@ -58,3 +80,6 @@
|
||||
|
||||
启用填充。
|
||||
|
||||
#### brutal
|
||||
|
||||
参阅 [TCP Brutal](/zh/configuration/shared/tcp-brutal)。
|
28
docs/configuration/shared/tcp-brutal.md
Normal file
28
docs/configuration/shared/tcp-brutal.md
Normal 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.
|
28
docs/configuration/shared/tcp-brutal.zh.md
Normal file
28
docs/configuration/shared/tcp-brutal.zh.md
Normal 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 为单位。
|
@ -15,6 +15,7 @@ Available transports:
|
||||
* WebSocket
|
||||
* QUIC
|
||||
* gRPC
|
||||
* HTTPUpgrade
|
||||
|
||||
!!! 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.
|
||||
|
||||
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.
|
||||
|
@ -14,6 +14,7 @@ V2Ray Transport 是 v2ray 发明的一组私有协议,并污染了其他协议
|
||||
* WebSocket
|
||||
* QUIC
|
||||
* gRPC
|
||||
* HTTPUpgrade
|
||||
|
||||
!!! warning "与 v2ray-core 的区别"
|
||||
|
||||
@ -183,3 +184,32 @@ gRPC 服务名称。
|
||||
如果启用,客户端传输即使没有活动连接也会发送 keepalive ping。如果禁用,则在没有活动连接时,将忽略 `idle_timeout` 和 `ping_timeout`,并且不会发送 keepalive ping。
|
||||
|
||||
默认禁用。
|
||||
|
||||
### HTTPUpgrade
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "httpupgrade",
|
||||
"host": "",
|
||||
"path": "",
|
||||
"headers": {}
|
||||
}
|
||||
```
|
||||
|
||||
#### host
|
||||
|
||||
主机域名。
|
||||
|
||||
默认服务器将验证。
|
||||
|
||||
#### path
|
||||
|
||||
HTTP 请求路径
|
||||
|
||||
默认服务器将验证。
|
||||
|
||||
#### headers
|
||||
|
||||
HTTP 请求的额外标头。
|
||||
|
||||
默认服务器将写入响应。
|
||||
|
@ -2,12 +2,14 @@ package clashapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/websocket"
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
@ -27,16 +29,16 @@ type Memory struct {
|
||||
|
||||
func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var conn net.Conn
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
conn, _, _, err = ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
render.Status(r, http.StatusOK)
|
||||
}
|
||||
@ -63,13 +65,12 @@ func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r
|
||||
}); err != nil {
|
||||
break
|
||||
}
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
err = wsutil.WriteServerText(conn, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/websocket"
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"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) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !websocket.IsWebSocketUpgrade(r) {
|
||||
if r.Header.Get("Upgrade") != "websocket" {
|
||||
snapshot := trafficManager.Snapshot()
|
||||
render.JSON(w, r, snapshot)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
conn, _, _, err := ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -56,7 +57,7 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW
|
||||
if err := json.NewEncoder(buf).Encode(snapshot); err != nil {
|
||||
return err
|
||||
}
|
||||
return conn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
return wsutil.WriteServerText(conn, buf.Bytes())
|
||||
}
|
||||
|
||||
if err = sendSnapshot(); err != nil {
|
||||
|
@ -25,7 +25,8 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
"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/cors"
|
||||
@ -314,7 +315,7 @@ func authentication(serverSecret string) func(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// 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")
|
||||
if token != serverSecret {
|
||||
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 {
|
||||
Up int64 `json:"up"`
|
||||
Down int64 `json:"down"`
|
||||
@ -364,16 +359,17 @@ type Traffic struct {
|
||||
|
||||
func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var conn net.Conn
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
conn, _, _, err = ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
render.Status(r, http.StatusOK)
|
||||
}
|
||||
@ -392,11 +388,11 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter,
|
||||
break
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
err = wsutil.WriteServerText(conn, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -432,16 +428,16 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
|
||||
}
|
||||
defer logFactory.UnSubscribe(subscription)
|
||||
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
var conn net.Conn
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
conn, _, _, err = ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
render.Status(r, http.StatusOK)
|
||||
}
|
||||
@ -465,11 +461,11 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if wsConn == nil {
|
||||
if conn == nil {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
err = wsutil.WriteServerText(conn, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -115,7 +115,11 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
|
||||
if len(options.IncludeAndroidUser) > 0 {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ var _ TunOptions = (*tunOptions)(nil)
|
||||
|
||||
type tunOptions struct {
|
||||
*tun.Options
|
||||
routeRanges []netip.Prefix
|
||||
option.TunPlatformOptions
|
||||
}
|
||||
|
||||
@ -91,11 +92,15 @@ func (o *tunOptions) GetStrictRoute() bool {
|
||||
}
|
||||
|
||||
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 {
|
||||
return mapRoutePrefix(o.Inet6RouteAddress)
|
||||
return mapRoutePrefix(common.Filter(o.routeRanges, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is6()
|
||||
}))
|
||||
}
|
||||
|
||||
func (o *tunOptions) GetIncludePackage() StringIterator {
|
||||
|
10
go.mod
10
go.mod
@ -26,20 +26,20 @@ require (
|
||||
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab
|
||||
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028
|
||||
github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2
|
||||
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-shadowsocks v0.2.5
|
||||
github.com/sagernet/sing-shadowsocks2 v0.1.4
|
||||
github.com/sagernet/sing-shadowtls v0.1.4
|
||||
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6
|
||||
github.com/sagernet/sing-tun v0.1.17-0.20231103103951-3540ea7680d8
|
||||
github.com/sagernet/sing-vmess v0.1.8
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
|
||||
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.uber.org/zap v1.26.0
|
||||
@ -61,6 +61,8 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
|
21
go.sum
21
go.sum
@ -31,6 +31,10 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
@ -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/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028 h1:6GbQt7SC9y5Imrq5jDMbXDSaNiMhJ8KBjhjtQRuqQvE=
|
||||
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 h1:PW18IgRodvppd09d4mewYM3Hedu3PtFERN8yOqkTVk0=
|
||||
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/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c=
|
||||
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
|
||||
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
|
||||
github.com/sagernet/sing-mux v0.1.4-0.20231102172319-a36b95857a9b h1:zfF0WjELB9E6eHrF1m4SeZ+tiGFFrI2GVeoRoQj/0lg=
|
||||
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/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
|
||||
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-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6 h1:4yEXBqQoUgXj7qPSLD6lr+z9/KfsvixO9JUA2i5xnM8=
|
||||
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 h1:MAxenoNTNwOu1rhKCjWNNoTP9BrzEI9KE5E8VMOnysY=
|
||||
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/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
|
||||
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/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
|
||||
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed h1:90a510OeE9siSJoYsI8nSjPmA+u5ROMDts/ZkdNsuXY=
|
||||
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
@ -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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -22,7 +22,7 @@ type myInboundAdapter struct {
|
||||
protocol string
|
||||
network []string
|
||||
ctx context.Context
|
||||
router adapter.Router
|
||||
router adapter.ConnectionRouter
|
||||
logger log.ContextLogger
|
||||
tag string
|
||||
listenOptions option.ListenOptions
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/common/uot"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -35,7 +36,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
||||
protocol: C.TypeHTTP,
|
||||
network: []string{N.NetworkTCP},
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/uot"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -37,7 +38,7 @@ func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||
protocol: C.TypeMixed,
|
||||
network: []string{N.NetworkTCP},
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/common/uot"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
@ -43,7 +44,7 @@ func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||
protocol: C.TypeNaive,
|
||||
network: options.Network.Build(),
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
|
||||
"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"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -48,21 +50,27 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
||||
protocol: C.TypeShadowsocks,
|
||||
network: options.Network.Build(),
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
},
|
||||
}
|
||||
|
||||
inbound.connHandler = 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
|
||||
if options.UDPTimeout != 0 {
|
||||
udpTimeout = options.UDPTimeout
|
||||
} else {
|
||||
udpTimeout = int64(C.UDPTimeout.Seconds())
|
||||
}
|
||||
var err error
|
||||
switch {
|
||||
case options.Method == shadowsocks.MethodNone:
|
||||
inbound.service = shadowsocks.NewNoneService(options.UDPTimeout, inbound.upstreamContextHandler())
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
|
||||
"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"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -38,7 +40,7 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
|
||||
protocol: C.TypeShadowsocks,
|
||||
network: options.Network.Build(),
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
@ -46,16 +48,18 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
|
||||
}
|
||||
inbound.connHandler = 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
|
||||
if options.UDPTimeout != 0 {
|
||||
udpTimeout = options.UDPTimeout
|
||||
} else {
|
||||
udpTimeout = int64(C.UDPTimeout.Seconds())
|
||||
}
|
||||
var (
|
||||
service shadowsocks.MultiService[int]
|
||||
err error
|
||||
)
|
||||
var service shadowsocks.MultiService[int]
|
||||
if common.Contains(shadowaead_2022.List, options.Method) {
|
||||
service, err = shadowaead_2022.NewMultiServiceWithPassword[int](
|
||||
options.Method,
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
|
||||
"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"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -34,7 +36,7 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.
|
||||
protocol: C.TypeShadowsocks,
|
||||
network: options.Network.Build(),
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
@ -43,6 +45,11 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.
|
||||
}
|
||||
inbound.connHandler = 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
|
||||
if options.UDPTimeout != 0 {
|
||||
udpTimeout = options.UDPTimeout
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/uot"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -30,7 +31,7 @@ func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||
protocol: C.TypeSOCKS,
|
||||
network: []string{N.NetworkTCP},
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
|
@ -71,23 +71,25 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
logger: logger,
|
||||
inboundOptions: options.InboundOptions,
|
||||
tunOptions: tun.Options{
|
||||
Name: options.InterfaceName,
|
||||
MTU: tunMTU,
|
||||
Inet4Address: options.Inet4Address,
|
||||
Inet6Address: options.Inet6Address,
|
||||
AutoRoute: options.AutoRoute,
|
||||
StrictRoute: options.StrictRoute,
|
||||
IncludeInterface: options.IncludeInterface,
|
||||
ExcludeInterface: options.ExcludeInterface,
|
||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
||||
IncludeUID: includeUID,
|
||||
ExcludeUID: excludeUID,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
InterfaceMonitor: router.InterfaceMonitor(),
|
||||
TableIndex: 2022,
|
||||
Name: options.InterfaceName,
|
||||
MTU: tunMTU,
|
||||
Inet4Address: options.Inet4Address,
|
||||
Inet6Address: options.Inet6Address,
|
||||
AutoRoute: options.AutoRoute,
|
||||
StrictRoute: options.StrictRoute,
|
||||
IncludeInterface: options.IncludeInterface,
|
||||
ExcludeInterface: options.ExcludeInterface,
|
||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
|
||||
IncludeUID: includeUID,
|
||||
ExcludeUID: excludeUID,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
InterfaceMonitor: router.InterfaceMonitor(),
|
||||
TableIndex: 2022,
|
||||
},
|
||||
endpointIndependentNat: options.EndpointIndependentNat,
|
||||
udpTimeout: udpTimeout,
|
||||
|
@ -6,7 +6,9 @@ import (
|
||||
"os"
|
||||
|
||||
"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/uot"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -42,7 +44,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||
protocol: C.TypeVLESS,
|
||||
network: []string{N.NetworkTCP},
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
@ -50,6 +52,11 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||
ctx: ctx,
|
||||
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.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int {
|
||||
return index
|
||||
@ -59,7 +66,6 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||
return it.Flow
|
||||
}))
|
||||
inbound.service = service
|
||||
var err error
|
||||
if options.TLS != nil {
|
||||
inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||
if err != nil {
|
||||
|
@ -6,7 +6,9 @@ import (
|
||||
"os"
|
||||
|
||||
"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/uot"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -42,7 +44,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||
protocol: C.TypeVMess,
|
||||
network: []string{N.NetworkTCP},
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
router: uot.NewRouter(router, logger),
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
@ -50,6 +52,11 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||
ctx: ctx,
|
||||
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
|
||||
if timeFunc := ntp.TimeFuncFromContext(ctx); timeFunc != nil {
|
||||
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...)
|
||||
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
|
||||
}), common.Map(options.Users, func(it option.VMessUser) string {
|
||||
return it.UUID
|
||||
|
@ -75,6 +75,7 @@ nav:
|
||||
- Multiplex: configuration/shared/multiplex.md
|
||||
- V2Ray Transport: configuration/shared/v2ray-transport.md
|
||||
- UDP over TCP: configuration/shared/udp-over-tcp.md
|
||||
- TCP Brutal: configuration/shared/tcp-brutal.md
|
||||
- Inbound:
|
||||
- configuration/inbound/index.md
|
||||
- Direct: configuration/inbound/direct.md
|
||||
|
@ -120,10 +120,11 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
|
||||
}
|
||||
|
||||
type InboundOptions struct {
|
||||
SniffEnabled bool `json:"sniff,omitempty"`
|
||||
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
|
||||
SniffTimeout Duration `json:"sniff_timeout,omitempty"`
|
||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
||||
SniffEnabled bool `json:"sniff,omitempty"`
|
||||
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
|
||||
SniffTimeout Duration `json:"sniff_timeout,omitempty"`
|
||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
||||
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
|
||||
}
|
||||
|
||||
type ListenOptions struct {
|
||||
|
23
option/multiplex.go
Normal file
23
option/multiplex.go
Normal 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"`
|
||||
}
|
@ -154,12 +154,3 @@ type ServerOptions struct {
|
||||
func (o ServerOptions) Build() M.Socksaddr {
|
||||
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"`
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ type ShadowsocksInboundOptions struct {
|
||||
Password string `json:"password,omitempty"`
|
||||
Users []ShadowsocksUser `json:"users,omitempty"`
|
||||
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
||||
Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
}
|
||||
|
||||
type ShadowsocksUser struct {
|
||||
@ -23,11 +24,11 @@ type ShadowsocksDestination struct {
|
||||
type ShadowsocksOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
Method string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Plugin string `json:"plugin,omitempty"`
|
||||
PluginOptions string `json:"plugin_opts,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
UDPOverTCPOptions *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
|
||||
MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"`
|
||||
Method string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Plugin string `json:"plugin,omitempty"`
|
||||
PluginOptions string `json:"plugin_opts,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
|
||||
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
}
|
||||
|
@ -17,11 +17,11 @@ type HTTPMixedInboundOptions struct {
|
||||
type SocksOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
Version string `json:"version,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
UDPOverTCPOptions *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPOutboundOptions struct {
|
||||
|
@ -6,6 +6,7 @@ type TrojanInboundOptions struct {
|
||||
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||
Fallback *ServerOptions `json:"fallback,omitempty"`
|
||||
FallbackForALPN map[string]*ServerOptions `json:"fallback_for_alpn,omitempty"`
|
||||
Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
}
|
||||
|
||||
@ -17,9 +18,9 @@ type TrojanUser struct {
|
||||
type TrojanOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
Password string `json:"password"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Multiplex *MultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
Password string `json:"password"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
}
|
||||
|
@ -3,26 +3,28 @@ package option
|
||||
import "net/netip"
|
||||
|
||||
type TunInboundOptions struct {
|
||||
InterfaceName string `json:"interface_name,omitempty"`
|
||||
MTU uint32 `json:"mtu,omitempty"`
|
||||
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
|
||||
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
|
||||
AutoRoute bool `json:"auto_route,omitempty"`
|
||||
StrictRoute bool `json:"strict_route,omitempty"`
|
||||
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
|
||||
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
|
||||
IncludeInterface Listable[string] `json:"include_interface,omitempty"`
|
||||
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
|
||||
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
|
||||
IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"`
|
||||
ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"`
|
||||
IncludePackage Listable[string] `json:"include_package,omitempty"`
|
||||
ExcludePackage Listable[string] `json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `json:"udp_timeout,omitempty"`
|
||||
Stack string `json:"stack,omitempty"`
|
||||
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
||||
InterfaceName string `json:"interface_name,omitempty"`
|
||||
MTU uint32 `json:"mtu,omitempty"`
|
||||
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
|
||||
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
|
||||
AutoRoute bool `json:"auto_route,omitempty"`
|
||||
StrictRoute bool `json:"strict_route,omitempty"`
|
||||
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
|
||||
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
|
||||
Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"`
|
||||
Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
|
||||
IncludeInterface Listable[string] `json:"include_interface,omitempty"`
|
||||
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
|
||||
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
|
||||
IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"`
|
||||
ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"`
|
||||
IncludePackage Listable[string] `json:"include_package,omitempty"`
|
||||
ExcludePackage Listable[string] `json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `json:"udp_timeout,omitempty"`
|
||||
Stack string `json:"stack,omitempty"`
|
||||
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
||||
InboundOptions
|
||||
}
|
||||
|
@ -7,11 +7,12 @@ import (
|
||||
)
|
||||
|
||||
type _V2RayTransportOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
HTTPOptions V2RayHTTPOptions `json:"-"`
|
||||
WebsocketOptions V2RayWebsocketOptions `json:"-"`
|
||||
QUICOptions V2RayQUICOptions `json:"-"`
|
||||
GRPCOptions V2RayGRPCOptions `json:"-"`
|
||||
Type string `json:"type,omitempty"`
|
||||
HTTPOptions V2RayHTTPOptions `json:"-"`
|
||||
WebsocketOptions V2RayWebsocketOptions `json:"-"`
|
||||
QUICOptions V2RayQUICOptions `json:"-"`
|
||||
GRPCOptions V2RayGRPCOptions `json:"-"`
|
||||
HTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:"-"`
|
||||
}
|
||||
|
||||
type V2RayTransportOptions _V2RayTransportOptions
|
||||
@ -29,6 +30,8 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) {
|
||||
v = o.QUICOptions
|
||||
case C.V2RayTransportTypeGRPC:
|
||||
v = o.GRPCOptions
|
||||
case C.V2RayTransportTypeHTTPUpgrade:
|
||||
v = o.HTTPUpgradeOptions
|
||||
default:
|
||||
return nil, E.New("unknown transport type: " + o.Type)
|
||||
}
|
||||
@ -50,6 +53,8 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
|
||||
v = &o.QUICOptions
|
||||
case C.V2RayTransportTypeGRPC:
|
||||
v = &o.GRPCOptions
|
||||
case C.V2RayTransportTypeHTTPUpgrade:
|
||||
v = &o.HTTPUpgradeOptions
|
||||
default:
|
||||
return E.New("unknown transport type: " + o.Type)
|
||||
}
|
||||
@ -85,3 +90,9 @@ type V2RayGRPCOptions struct {
|
||||
PermitWithoutStream bool `json:"permit_without_stream,omitempty"`
|
||||
ForceLite bool `json:"-"` // for test
|
||||
}
|
||||
|
||||
type V2RayHTTPUpgradeOptions struct {
|
||||
Host string `json:"host,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Headers HTTPHeader `json:"headers,omitempty"`
|
||||
}
|
||||
|
@ -2,9 +2,10 @@ package option
|
||||
|
||||
type VLESSInboundOptions struct {
|
||||
ListenOptions
|
||||
Users []VLESSUser `json:"users,omitempty"`
|
||||
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
Users []VLESSUser `json:"users,omitempty"`
|
||||
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||
Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
}
|
||||
|
||||
type VLESSUser struct {
|
||||
@ -16,11 +17,11 @@ type VLESSUser struct {
|
||||
type VLESSOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
UUID string `json:"uuid"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Multiplex *MultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
PacketEncoding *string `json:"packet_encoding,omitempty"`
|
||||
UUID string `json:"uuid"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
PacketEncoding *string `json:"packet_encoding,omitempty"`
|
||||
}
|
||||
|
@ -2,9 +2,10 @@ package option
|
||||
|
||||
type VMessInboundOptions struct {
|
||||
ListenOptions
|
||||
Users []VMessUser `json:"users,omitempty"`
|
||||
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
Users []VMessUser `json:"users,omitempty"`
|
||||
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||
Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
}
|
||||
|
||||
type VMessUser struct {
|
||||
@ -16,14 +17,14 @@ type VMessUser struct {
|
||||
type VMessOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
UUID string `json:"uuid"`
|
||||
Security string `json:"security"`
|
||||
AlterId int `json:"alter_id,omitempty"`
|
||||
GlobalPadding bool `json:"global_padding,omitempty"`
|
||||
AuthenticatedLength bool `json:"authenticated_length,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
PacketEncoding string `json:"packet_encoding,omitempty"`
|
||||
Multiplex *MultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
UUID string `json:"uuid"`
|
||||
Security string `json:"security"`
|
||||
AlterId int `json:"alter_id,omitempty"`
|
||||
GlobalPadding bool `json:"global_padding,omitempty"`
|
||||
AuthenticatedLength bool `json:"authenticated_length,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
PacketEncoding string `json:"packet_encoding,omitempty"`
|
||||
Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
}
|
||||
|
@ -121,9 +121,13 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
||||
}
|
||||
if destinationAddress.IsValid() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -166,7 +170,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
|
||||
if metadata.Destination.IsFqdn() {
|
||||
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
}
|
||||
if natConn, loaded := common.Cast[*bufio.NATPacketConn](conn); loaded {
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
natConn.UpdateDestination(destinationAddress)
|
||||
}
|
||||
}
|
||||
|
@ -62,9 +62,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
|
||||
uotOptions := common.PtrValueOrDefault(options.UDPOverTCP)
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -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),
|
||||
resolve: version == socks.Version4,
|
||||
}
|
||||
uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
|
||||
uotOptions := common.PtrValueOrDefault(options.UDPOverTCP)
|
||||
if uotOptions.Enabled {
|
||||
outbound.uotClient = &uot.Client{
|
||||
Dialer: outbound.client,
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
"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/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@ -27,6 +26,7 @@ import (
|
||||
"github.com/sagernet/sing-box/outbound"
|
||||
"github.com/sagernet/sing-box/transport/fakeip"
|
||||
"github.com/sagernet/sing-dns"
|
||||
mux "github.com/sagernet/sing-mux"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-vmess"
|
||||
"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
|
||||
switch metadata.Destination.Fqdn {
|
||||
case mux.Destination.Fqdn:
|
||||
r.logger.InfoContext(ctx, "inbound multiplex connection")
|
||||
handler := adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r)
|
||||
return mux.HandleConnection(ctx, handler, r.logger, conn, adapter.UpstreamMetadata(metadata))
|
||||
return E.New("global multiplex is deprecated since sing-box v1.7.0, enable multiplex in inbound options instead.")
|
||||
case vmess.MuxDestination.Fqdn:
|
||||
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
|
||||
return vmess.HandleMuxConnection(ctx, conn, adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r))
|
||||
return E.New("global multiplex (v2ray legacy) not supported since sing-box v1.7.0.")
|
||||
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.RoutePacketConnection(ctx, uot.NewConn(conn, *request), metadata)
|
||||
return E.New("global UoT not supported since sing-box v1.7.0.")
|
||||
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 E.New("global UoT (legacy) not supported since sing-box v1.7.0.")
|
||||
}
|
||||
|
||||
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
|
||||
|
10
test/go.mod
10
test/go.mod
@ -11,7 +11,7 @@ require (
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
|
||||
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-quic v0.1.3-0.20231026034240-fa3d997246b6
|
||||
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-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // 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/netlink v0.0.0-20220905062125-8043b4a9aa97 // 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-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/smux v0.0.0-20230312102458-337ec2a5af37 // 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/websocket v0.0.0-20220913015213-615516348b4e // 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/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
|
21
test/go.sum
21
test/go.sum
@ -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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/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/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028 h1:6GbQt7SC9y5Imrq5jDMbXDSaNiMhJ8KBjhjtQRuqQvE=
|
||||
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 h1:PW18IgRodvppd09d4mewYM3Hedu3PtFERN8yOqkTVk0=
|
||||
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/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c=
|
||||
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
|
||||
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
|
||||
github.com/sagernet/sing-mux v0.1.4-0.20231102172319-a36b95857a9b h1:zfF0WjELB9E6eHrF1m4SeZ+tiGFFrI2GVeoRoQj/0lg=
|
||||
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/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
|
||||
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-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6 h1:4yEXBqQoUgXj7qPSLD6lr+z9/KfsvixO9JUA2i5xnM8=
|
||||
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 h1:iYopGh4nbVsvQ+oR2Xkc+hO5s+aacsmq5nQkEFPPhxY=
|
||||
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/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
|
||||
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/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
|
||||
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed h1:90a510OeE9siSJoYsI8nSjPmA+u5ROMDts/ZkdNsuXY=
|
||||
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||
github.com/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -18,7 +18,7 @@ var muxProtocols = []string{
|
||||
}
|
||||
|
||||
func TestVMessSMux(t *testing.T) {
|
||||
testVMessMux(t, option.MultiplexOptions{
|
||||
testVMessMux(t, option.OutboundMultiplexOptions{
|
||||
Enabled: true,
|
||||
Protocol: "smux",
|
||||
})
|
||||
@ -27,7 +27,7 @@ func TestVMessSMux(t *testing.T) {
|
||||
func TestShadowsocksMux(t *testing.T) {
|
||||
for _, protocol := range muxProtocols {
|
||||
t.Run(protocol, func(t *testing.T) {
|
||||
testShadowsocksMux(t, option.MultiplexOptions{
|
||||
testShadowsocksMux(t, option.OutboundMultiplexOptions{
|
||||
Enabled: true,
|
||||
Protocol: protocol,
|
||||
})
|
||||
@ -36,7 +36,7 @@ func TestShadowsocksMux(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShadowsockH2Mux(t *testing.T) {
|
||||
testShadowsocksMux(t, option.MultiplexOptions{
|
||||
testShadowsocksMux(t, option.OutboundMultiplexOptions{
|
||||
Enabled: true,
|
||||
Protocol: "h2mux",
|
||||
Padding: true,
|
||||
@ -44,14 +44,14 @@ func TestShadowsockH2Mux(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShadowsockSMuxPadding(t *testing.T) {
|
||||
testShadowsocksMux(t, option.MultiplexOptions{
|
||||
testShadowsocksMux(t, option.OutboundMultiplexOptions{
|
||||
Enabled: true,
|
||||
Protocol: "smux",
|
||||
Padding: true,
|
||||
})
|
||||
}
|
||||
|
||||
func testShadowsocksMux(t *testing.T, options option.MultiplexOptions) {
|
||||
func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) {
|
||||
method := shadowaead_2022.List[0]
|
||||
password := mkBase64(t, 16)
|
||||
startInstance(t, option.Options{
|
||||
@ -90,9 +90,9 @@ func testShadowsocksMux(t *testing.T, options option.MultiplexOptions) {
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
Method: method,
|
||||
Password: password,
|
||||
MultiplexOptions: &options,
|
||||
Method: method,
|
||||
Password: password,
|
||||
Multiplex: &options,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -110,7 +110,7 @@ func testShadowsocksMux(t *testing.T, options option.MultiplexOptions) {
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func testVMessMux(t *testing.T, options option.MultiplexOptions) {
|
||||
func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) {
|
||||
user, _ := uuid.NewV4()
|
||||
startInstance(t, option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
@ -136,6 +136,9 @@ func testVMessMux(t *testing.T, options option.MultiplexOptions) {
|
||||
UUID: user.String(),
|
||||
},
|
||||
},
|
||||
Multiplex: &option.InboundMultiplexOptions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -232,7 +232,7 @@ func TestShadowsocksUoT(t *testing.T) {
|
||||
},
|
||||
Method: method,
|
||||
Password: password,
|
||||
UDPOverTCPOptions: &option.UDPOverTCPOptions{
|
||||
UDPOverTCP: &option.UDPOverTCPOptions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
|
16
test/v2ray_httpupgrade_test.go
Normal file
16
test/v2ray_httpupgrade_test.go
Normal 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,
|
||||
})
|
||||
})
|
||||
}
|
@ -8,6 +8,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/v2rayhttp"
|
||||
"github.com/sagernet/sing-box/transport/v2rayhttpupgrade"
|
||||
"github.com/sagernet/sing-box/transport/v2raywebsocket"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
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)
|
||||
case C.V2RayTransportTypeGRPC:
|
||||
return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler)
|
||||
case C.V2RayTransportTypeHTTPUpgrade:
|
||||
return v2rayhttpupgrade.NewServer(ctx, options.HTTPUpgradeOptions, tlsConfig, handler)
|
||||
default:
|
||||
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:
|
||||
return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig)
|
||||
case C.V2RayTransportTypeWebsocket:
|
||||
return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil
|
||||
return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig)
|
||||
case C.V2RayTransportTypeQUIC:
|
||||
if tlsConfig == nil {
|
||||
return nil, C.ErrTLSRequired
|
||||
}
|
||||
return NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig)
|
||||
|
||||
case C.V2RayTransportTypeHTTPUpgrade:
|
||||
return v2rayhttpupgrade.NewClient(ctx, dialer, serverAddr, options.HTTPUpgradeOptions, tlsConfig)
|
||||
default:
|
||||
return nil, E.New("unknown transport type: " + options.Type)
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
conn.setup(nil, err)
|
||||
} else if response.StatusCode != 200 {
|
||||
response.Body.Close()
|
||||
conn.setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status))
|
||||
conn.setup(nil, E.New("unexpected status: ", response.Status))
|
||||
} else {
|
||||
conn.setup(response.Body, nil)
|
||||
}
|
||||
|
@ -35,10 +35,6 @@ type Server struct {
|
||||
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) {
|
||||
server := &Server{
|
||||
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))
|
||||
}
|
||||
|
||||
func (s *Server) Network() []string {
|
||||
return []string{N.NetworkTCP}
|
||||
}
|
||||
|
||||
func (s *Server) Serve(listener net.Listener) error {
|
||||
if s.tlsConfig != nil {
|
||||
if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||
|
@ -81,7 +81,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
uri.Path = options.Path
|
||||
err := sHTTP.URLSetPath(&uri, options.Path)
|
||||
if err != nil {
|
||||
return nil, E.New("failed to set path: " + err.Error())
|
||||
return nil, E.New("parse path: " + err.Error())
|
||||
}
|
||||
client.url = &uri
|
||||
return client, nil
|
||||
@ -143,7 +143,7 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
|
||||
conn.Setup(nil, err)
|
||||
} else if response.StatusCode != 200 {
|
||||
response.Body.Close()
|
||||
conn.Setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status))
|
||||
conn.Setup(nil, E.New("unexpected status: ", response.Status))
|
||||
} else {
|
||||
conn.Setup(response.Body, nil)
|
||||
}
|
||||
|
@ -40,10 +40,6 @@ type Server struct {
|
||||
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) {
|
||||
server := &Server{
|
||||
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))
|
||||
}
|
||||
|
||||
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 {
|
||||
|
118
transport/v2rayhttpupgrade/client.go
Normal file
118
transport/v2rayhttpupgrade/client.go
Normal 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
|
||||
}
|
139
transport/v2rayhttpupgrade/server.go
Normal file
139
transport/v2rayhttpupgrade/server.go
Normal 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))
|
||||
}
|
@ -5,58 +5,37 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||
"github.com/sagernet/websocket"
|
||||
"github.com/sagernet/ws"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayClientTransport = (*Client)(nil)
|
||||
|
||||
type Client struct {
|
||||
dialer *websocket.Dialer
|
||||
dialer N.Dialer
|
||||
tlsConfig tls.Config
|
||||
serverAddr M.Socksaddr
|
||||
requestURL url.URL
|
||||
requestURLString string
|
||||
headers http.Header
|
||||
maxEarlyData uint32
|
||||
earlyDataHeaderName string
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayWebsocketOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {
|
||||
wsDialer := &websocket.Dialer{
|
||||
ReadBufferSize: 4 * 1024,
|
||||
WriteBufferSize: 4 * 1024,
|
||||
HandshakeTimeout: time.Second * 8,
|
||||
}
|
||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayWebsocketOptions, tlsConfig tls.Config) (*Client, error) {
|
||||
if tlsConfig != nil {
|
||||
if len(tlsConfig.NextProtos()) == 0 {
|
||||
tlsConfig.SetNextProtos([]string{"http/1.1"})
|
||||
}
|
||||
wsDialer.NetDialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConn, err := tls.ClientHandshake(ctx, conn, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &deadConn{tlsConn}, nil
|
||||
}
|
||||
} else {
|
||||
wsDialer.NetDialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &deadConn{conn}, nil
|
||||
}
|
||||
}
|
||||
var requestURL url.URL
|
||||
if tlsConfig == nil {
|
||||
@ -68,37 +47,62 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
requestURL.Path = options.Path
|
||||
err := sHTTP.URLSetPath(&requestURL, options.Path)
|
||||
if err != nil {
|
||||
return nil
|
||||
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
|
||||
}
|
||||
if headers.Get("User-Agent") == "" {
|
||||
headers.Set("User-Agent", "Go-http-client/1.1")
|
||||
}
|
||||
return &Client{
|
||||
wsDialer,
|
||||
dialer,
|
||||
tlsConfig,
|
||||
serverAddr,
|
||||
requestURL,
|
||||
requestURL.String(),
|
||||
headers,
|
||||
options.MaxEarlyData,
|
||||
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) {
|
||||
if c.maxEarlyData <= 0 {
|
||||
conn, response, err := c.dialer.DialContext(ctx, c.requestURLString, c.headers)
|
||||
if err == nil {
|
||||
return &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}, nil
|
||||
conn, err := c.dialContext(ctx, &c.requestURL, c.headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, wrapDialError(response, err)
|
||||
return conn, nil
|
||||
} else {
|
||||
return &EarlyWebsocketConn{Client: c, ctx: ctx, create: make(chan struct{})}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func wrapDialError(response *http.Response, err error) error {
|
||||
if response == nil {
|
||||
return err
|
||||
}
|
||||
return E.Extend(err, "HTTP ", response.StatusCode, " ", response.Status)
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package v2raywebsocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
@ -13,50 +13,96 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/websocket"
|
||||
"github.com/sagernet/ws"
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
)
|
||||
|
||||
type WebsocketConn struct {
|
||||
*websocket.Conn
|
||||
net.Conn
|
||||
*Writer
|
||||
remoteAddr net.Addr
|
||||
reader io.Reader
|
||||
state ws.State
|
||||
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{
|
||||
Conn: wsConn,
|
||||
remoteAddr: remoteAddr,
|
||||
Writer: NewWriter(wsConn, true),
|
||||
Conn: conn,
|
||||
state: state,
|
||||
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 {
|
||||
err := c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(C.TCPTimeout))
|
||||
if err != nil {
|
||||
return c.Conn.Close()
|
||||
c.Conn.SetWriteDeadline(time.Now().Add(C.TCPTimeout))
|
||||
frame := ws.NewCloseFrame(ws.NewCloseFrameBody(
|
||||
ws.StatusNormalClosure, "",
|
||||
))
|
||||
if c.state == ws.StateClientSide {
|
||||
frame = ws.MaskFrameInPlace(frame)
|
||||
}
|
||||
ws.WriteFrame(c.Conn, frame)
|
||||
c.Conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
||||
var header ws.Header
|
||||
for {
|
||||
if c.reader == nil {
|
||||
_, c.reader, err = c.NextReader()
|
||||
n, err = c.reader.Read(b)
|
||||
if n > 0 {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
if !E.IsMulti(err, io.EOF, wsutil.ErrNoFrameAdvance) {
|
||||
return
|
||||
}
|
||||
header, err = c.reader.NextFrame()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if header.OpCode.IsControl() {
|
||||
err = c.controlHandler(header, c.reader)
|
||||
if err != nil {
|
||||
err = wrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
n, err = c.reader.Read(b)
|
||||
if E.IsMulti(err, io.EOF) {
|
||||
c.reader = nil
|
||||
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
|
||||
}
|
||||
n = len(p)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) RemoteAddr() net.Addr {
|
||||
@ -83,11 +129,7 @@ func (c *WebsocketConn) NeedAdditionalReadDeadline() bool {
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) Upstream() any {
|
||||
return c.Conn.NetConn()
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) UpstreamWriter() any {
|
||||
return c.Writer
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
type EarlyWebsocketConn struct {
|
||||
@ -113,8 +155,7 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
||||
var (
|
||||
earlyData []byte
|
||||
lateData []byte
|
||||
conn *websocket.Conn
|
||||
response *http.Response
|
||||
conn *WebsocketConn
|
||||
err error
|
||||
)
|
||||
if len(content) > int(c.maxEarlyData) {
|
||||
@ -128,19 +169,16 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
||||
if c.earlyDataHeaderName == "" {
|
||||
requestURL := c.requestURL
|
||||
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 {
|
||||
headers := c.headers.Clone()
|
||||
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 {
|
||||
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 {
|
||||
return wrapDialError(response, err)
|
||||
}
|
||||
c.conn = &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}
|
||||
c.conn = conn
|
||||
if len(lateData) > 0 {
|
||||
_, err = c.conn.Write(lateData)
|
||||
}
|
||||
@ -224,13 +262,3 @@ func (c *EarlyWebsocketConn) Upstream() any {
|
||||
func (c *EarlyWebsocketConn) LazyHeadroom() bool {
|
||||
return c.conn == nil
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
|
||||
return io.EOF
|
||||
}
|
||||
if websocket.IsCloseError(err, websocket.CloseAbnormalClosure) {
|
||||
return net.ErrClosed
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
package v2raywebsocket
|
||||
|
||||
import _ "unsafe"
|
||||
|
||||
//go:linkname maskBytes github.com/sagernet/websocket.maskBytes
|
||||
func maskBytes(key [4]byte, pos int, b []byte) int
|
@ -20,7 +20,7 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
sHttp "github.com/sagernet/sing/protocol/http"
|
||||
"github.com/sagernet/websocket"
|
||||
"github.com/sagernet/ws"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayServerTransport = (*Server)(nil)
|
||||
@ -58,13 +58,6 @@ func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsCon
|
||||
return server, nil
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
HandshakeTimeout: C.TCPTimeout,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if s.maxEarlyData == 0 || s.earlyDataHeaderName != "" {
|
||||
if request.URL.Path != s.path {
|
||||
@ -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"))
|
||||
return
|
||||
}
|
||||
wsConn, err := upgrader.Upgrade(writer, request, nil)
|
||||
wsConn, reader, _, err := ws.UpgradeHTTP(request, writer)
|
||||
if err != nil {
|
||||
s.invalidRequest(writer, request, 0, E.Cause(err, "upgrade websocket connection"))
|
||||
return
|
||||
}
|
||||
var metadata M.Metadata
|
||||
metadata.Source = sHttp.SourceAddress(request)
|
||||
conn = NewServerConn(wsConn, metadata.Source.TCPAddr())
|
||||
conn = NewConn(wsConn, reader.Reader, metadata.Source.TCPAddr(), ws.StateServerSide)
|
||||
if len(earlyData) > 0 {
|
||||
conn = bufio.NewCachedConn(conn, buf.As(earlyData))
|
||||
}
|
||||
|
@ -2,36 +2,27 @@ package v2raywebsocket
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/websocket"
|
||||
"github.com/sagernet/ws"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
*websocket.Conn
|
||||
writer N.ExtendedWriter
|
||||
isServer bool
|
||||
}
|
||||
|
||||
func NewWriter(conn *websocket.Conn, isServer bool) *Writer {
|
||||
func NewWriter(writer io.Writer, state ws.State) *Writer {
|
||||
return &Writer{
|
||||
conn,
|
||||
bufio.NewExtendedWriter(conn.NetConn()),
|
||||
isServer,
|
||||
bufio.NewExtendedWriter(writer),
|
||||
state == ws.StateServerSide,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
err = w.Conn.WriteMessage(websocket.BinaryMessage, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
|
||||
var payloadBitLength int
|
||||
dataLen := buffer.Len()
|
||||
@ -52,7 +43,7 @@ func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
|
||||
}
|
||||
|
||||
header := buffer.ExtendHeader(headerLen)
|
||||
header[0] = websocket.BinaryMessage | 1<<7
|
||||
header[0] = byte(ws.OpBinary) | 0x80
|
||||
if w.isServer {
|
||||
header[1] = 0
|
||||
} else {
|
||||
@ -72,16 +63,12 @@ func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
|
||||
if !w.isServer {
|
||||
maskKey := rand.Uint32()
|
||||
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)
|
||||
}
|
||||
|
||||
func (w *Writer) Upstream() any {
|
||||
return w.Conn.NetConn()
|
||||
}
|
||||
|
||||
func (w *Writer) FrontHeadroom() int {
|
||||
return 14
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user