mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 10:07:20 +10:00
Compare commits
15 Commits
v1.1-beta5
...
v1.1-beta7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9856b73cb5 | ||
|
|
f42356fbcb | ||
|
|
d0b467671a | ||
|
|
c18c545798 | ||
|
|
693ef293ac | ||
|
|
a006627795 | ||
|
|
0738b184e4 | ||
|
|
42524ba04e | ||
|
|
63fc95b96d | ||
|
|
ab436fc137 | ||
|
|
1546770bfd | ||
|
|
f4b2099488 | ||
|
|
a2c4d68031 | ||
|
|
cfe14f2817 | ||
|
|
a5402ffb69 |
12
Makefile
12
Makefile
@@ -1,7 +1,8 @@
|
||||
NAME = sing-box
|
||||
COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_clash_api
|
||||
PARAMS = -v -trimpath -tags '$(TAGS)' -ldflags '-s -w -buildid='
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||
MAIN = ./cmd/sing-box
|
||||
|
||||
.PHONY: test release
|
||||
@@ -59,10 +60,15 @@ release_install:
|
||||
go install -v github.com/tcnksm/ghr@latest
|
||||
|
||||
test:
|
||||
@go test -v . && \
|
||||
@go test -v ./... && \
|
||||
cd test && \
|
||||
go mod tidy && \
|
||||
go test -v -tags with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr .
|
||||
go test -v -tags "$(TAGS_TEST)" .
|
||||
test_stdio:
|
||||
@go test -v ./... && \
|
||||
cd test && \
|
||||
go mod tidy && \
|
||||
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
||||
|
||||
clean:
|
||||
rm -rf bin dist
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
@@ -12,6 +13,7 @@ type ClashServer interface {
|
||||
Mode() string
|
||||
StoreSelected() bool
|
||||
CacheFile() ClashCacheFile
|
||||
HistoryStorage() *urltest.HistoryStorage
|
||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func WrapH2(err error) error {
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return io.EOF
|
||||
}
|
||||
if Contains(err, "client disconnected", "body closed by handler") {
|
||||
if Contains(err, "client disconnected", "body closed by handler", "response body closed", "; CANCEL") {
|
||||
return net.ErrClosed
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -110,7 +110,13 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
if options.TCPFastOpen {
|
||||
warnTFOOnUnsupportedPlatform.Check()
|
||||
}
|
||||
if !options.UDPFragment {
|
||||
var udpFragment bool
|
||||
if options.UDPFragment != nil {
|
||||
udpFragment = *options.UDPFragment
|
||||
} else {
|
||||
udpFragment = options.UDPFragmentDefault
|
||||
}
|
||||
if !udpFragment {
|
||||
dialer.Control = control.Append(dialer.Control, control.DisableUDPFragment())
|
||||
listener.Control = control.Append(listener.Control, control.DisableUDPFragment())
|
||||
}
|
||||
|
||||
@@ -466,10 +466,7 @@ func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if buffer.FreeLen() < int(length) {
|
||||
return destination, io.ErrShortBuffer
|
||||
}
|
||||
_, err = io.ReadFull(c.ExtendedConn, buffer.Extend(int(length)))
|
||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package mux
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -158,9 +157,6 @@ func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksad
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if buffer.FreeLen() < int(length) {
|
||||
return destination, io.ErrShortBuffer
|
||||
}
|
||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||
if err != nil {
|
||||
return
|
||||
@@ -223,9 +219,6 @@ func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Soc
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if buffer.FreeLen() < int(length) {
|
||||
return destination, io.ErrShortBuffer
|
||||
}
|
||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/task"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
@@ -49,5 +50,8 @@ func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContex
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return &adapter.InboundContext{Protocol: C.ProtocolDNS}, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package sniff
|
||||
|
||||
import _ "unsafe" // for linkname
|
||||
|
||||
//go:linkname IsDomainName net.isDomainName
|
||||
func IsDomainName(domain string) bool
|
||||
@@ -165,8 +165,6 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
return &echClientConfig{&tlsConfig}, nil
|
||||
}
|
||||
|
||||
const typeHTTPS = 65
|
||||
|
||||
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||
return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||
message := &mDNS.Msg{
|
||||
|
||||
@@ -25,4 +25,5 @@ const (
|
||||
|
||||
const (
|
||||
TypeSelector = "selector"
|
||||
TypeURLTest = "urltest"
|
||||
)
|
||||
|
||||
@@ -3,10 +3,11 @@ package constant
|
||||
import "time"
|
||||
|
||||
const (
|
||||
TCPTimeout = 5 * time.Second
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
QUICTimeout = 30 * time.Second
|
||||
STUNTimeout = 15 * time.Second
|
||||
UDPTimeout = 5 * time.Minute
|
||||
TCPTimeout = 5 * time.Second
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
QUICTimeout = 30 * time.Second
|
||||
STUNTimeout = 15 * time.Second
|
||||
UDPTimeout = 5 * time.Minute
|
||||
DefaultURLTestInterval = 1 * time.Minute
|
||||
)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package constant
|
||||
|
||||
var Version = "1.1-beta5"
|
||||
var Version = "1.1-beta6"
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
#### 1.1-beta7
|
||||
|
||||
* Add v2ray mux and XUDP support for VMess inbound
|
||||
* Add XUDP support for VMess outbound
|
||||
* Disable DF on direct outbound by default
|
||||
* Fix bugs in 1.1-beta6
|
||||
|
||||
#### 1.1-beta6
|
||||
|
||||
* Add [URLTest outbound](/configuration/outbound/urltest)
|
||||
* Fix bugs in 1.1-beta5
|
||||
|
||||
#### 1.1-beta5
|
||||
|
||||
* Print tags in version command
|
||||
|
||||
@@ -15,21 +15,24 @@
|
||||
|
||||
### Fields
|
||||
|
||||
| Type | Format |
|
||||
|---------------|------------------------------|
|
||||
| `direct` | [Direct](./direct) |
|
||||
| `block` | [Block](./block) |
|
||||
| `socks` | [SOCKS](./socks) |
|
||||
| `http` | [HTTP](./http) |
|
||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
||||
| `vmess` | [VMess](./vmess) |
|
||||
| `trojan` | [Trojan](./trojan) |
|
||||
| `wireguard` | [Wireguard](./wireguard) |
|
||||
| `hysteria` | [Hysteria](./hysteria) |
|
||||
| `tor` | [Tor](./tor) |
|
||||
| `ssh` | [SSH](./ssh) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
| `selector` | [Selector](./selector) |
|
||||
| Type | Format |
|
||||
|----------------|--------------------------------|
|
||||
| `direct` | [Direct](./direct) |
|
||||
| `block` | [Block](./block) |
|
||||
| `socks` | [SOCKS](./socks) |
|
||||
| `http` | [HTTP](./http) |
|
||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
||||
| `vmess` | [VMess](./vmess) |
|
||||
| `trojan` | [Trojan](./trojan) |
|
||||
| `wireguard` | [Wireguard](./wireguard) |
|
||||
| `hysteria` | [Hysteria](./hysteria) |
|
||||
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||
| `vless` | [VLESS](./vless) |
|
||||
| `tor` | [Tor](./tor) |
|
||||
| `ssh` | [SSH](./ssh) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
| `selector` | [Selector](./selector) |
|
||||
| `urltest` | [URLTest](./urltest) |
|
||||
|
||||
#### tag
|
||||
|
||||
|
||||
@@ -15,21 +15,24 @@
|
||||
|
||||
### 字段
|
||||
|
||||
| 类型 | 格式 |
|
||||
|---------------|------------------------------|
|
||||
| `direct` | [Direct](./direct) |
|
||||
| `block` | [Block](./block) |
|
||||
| `socks` | [SOCKS](./socks) |
|
||||
| `http` | [HTTP](./http) |
|
||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
||||
| `vmess` | [VMess](./vmess) |
|
||||
| `trojan` | [Trojan](./trojan) |
|
||||
| `wireguard` | [Wireguard](./wireguard) |
|
||||
| `hysteria` | [Hysteria](./hysteria) |
|
||||
| `tor` | [Tor](./tor) |
|
||||
| `ssh` | [SSH](./ssh) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
| `selector` | [Selector](./selector) |
|
||||
| 类型 | 格式 |
|
||||
|----------------|--------------------------------|
|
||||
| `direct` | [Direct](./direct) |
|
||||
| `block` | [Block](./block) |
|
||||
| `socks` | [SOCKS](./socks) |
|
||||
| `http` | [HTTP](./http) |
|
||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
||||
| `vmess` | [VMess](./vmess) |
|
||||
| `trojan` | [Trojan](./trojan) |
|
||||
| `wireguard` | [Wireguard](./wireguard) |
|
||||
| `hysteria` | [Hysteria](./hysteria) |
|
||||
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||
| `vless` | [VLESS](./vless) |
|
||||
| `tor` | [Tor](./tor) |
|
||||
| `ssh` | [SSH](./ssh) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
| `selector` | [Selector](./selector) |
|
||||
| `urltest` | [URLTest](./urltest) |
|
||||
|
||||
#### tag
|
||||
|
||||
|
||||
37
docs/configuration/outbound/urltest.md
Normal file
37
docs/configuration/outbound/urltest.md
Normal file
@@ -0,0 +1,37 @@
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "urltest",
|
||||
"tag": "auto",
|
||||
|
||||
"outbounds": [
|
||||
"proxy-a",
|
||||
"proxy-b",
|
||||
"proxy-c"
|
||||
],
|
||||
"url": "http://www.gstatic.com/generate_204",
|
||||
"interval": "1m",
|
||||
"tolerance": 50
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### outbounds
|
||||
|
||||
==Required==
|
||||
|
||||
List of outbound tags to test.
|
||||
|
||||
#### url
|
||||
|
||||
The URL to test. `http://www.gstatic.com/generate_204` will be used if empty.
|
||||
|
||||
#### interval
|
||||
|
||||
The test interval. `1m` will be used if empty.
|
||||
|
||||
#### tolerance
|
||||
|
||||
The test tolerance in milliseconds. `50` will be used if empty.
|
||||
37
docs/configuration/outbound/urltest.zh.md
Normal file
37
docs/configuration/outbound/urltest.zh.md
Normal file
@@ -0,0 +1,37 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "urltest",
|
||||
"tag": "auto",
|
||||
|
||||
"outbounds": [
|
||||
"proxy-a",
|
||||
"proxy-b",
|
||||
"proxy-c"
|
||||
],
|
||||
"url": "http://www.gstatic.com/generate_204",
|
||||
"interval": "1m",
|
||||
"tolerance": 50
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
#### outbounds
|
||||
|
||||
==必填==
|
||||
|
||||
用于测试的出站标签列表。
|
||||
|
||||
#### url
|
||||
|
||||
用于测试的链接。默认使用 `http://www.gstatic.com/generate_204`。
|
||||
|
||||
#### interval
|
||||
|
||||
测试间隔。 默认使用 `1m`。
|
||||
|
||||
#### tolerance
|
||||
|
||||
以毫秒为单位的测试容差。 默认使用 `50`。
|
||||
@@ -14,7 +14,7 @@
|
||||
"authenticated_length": true,
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_addr": false,
|
||||
"packet_encoding": "",
|
||||
"multiplex": {},
|
||||
"transport": {},
|
||||
|
||||
@@ -84,9 +84,13 @@ Both is enabled by default.
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
#### packet_addr
|
||||
#### packet_encoding
|
||||
|
||||
Enable packetaddr support.
|
||||
| Encoding | Description |
|
||||
|------------|-----------------------|
|
||||
| (none) | Disabled |
|
||||
| packetaddr | Supported by v2ray 5+ |
|
||||
| xudp | Supported by xray |
|
||||
|
||||
#### multiplex
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"authenticated_length": true,
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_addr": false,
|
||||
"packet_encoding": "",
|
||||
"multiplex": {},
|
||||
"transport": {},
|
||||
|
||||
@@ -84,9 +84,13 @@ VMess 用户 ID。
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||
|
||||
#### packet_addr
|
||||
#### packet_encoding
|
||||
|
||||
启用 packetaddr 支持。
|
||||
| 编码 | 描述 |
|
||||
|------------|---------------|
|
||||
| (空) | 禁用 |
|
||||
| packetaddr | 由 v2ray 5+ 支持 |
|
||||
| xudp | 由 xray 支持 |
|
||||
|
||||
#### multiplex
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler
|
||||
func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
||||
var info badjson.JSONObject
|
||||
var clashType string
|
||||
var isGroup bool
|
||||
switch detour.Type() {
|
||||
case C.TypeDirect:
|
||||
clashType = "Direct"
|
||||
@@ -91,7 +90,8 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
||||
clashType = "SSH"
|
||||
case C.TypeSelector:
|
||||
clashType = "Selector"
|
||||
isGroup = true
|
||||
case C.TypeURLTest:
|
||||
clashType = "URLTest"
|
||||
default:
|
||||
clashType = "Direct"
|
||||
}
|
||||
@@ -104,10 +104,9 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
||||
} else {
|
||||
info.Put("history", []*urltest.History{})
|
||||
}
|
||||
if isGroup {
|
||||
selector := detour.(adapter.OutboundGroup)
|
||||
info.Put("now", selector.Now())
|
||||
info.Put("all", selector.All())
|
||||
if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
|
||||
info.Put("now", group.Now())
|
||||
info.Put("all", group.All())
|
||||
}
|
||||
return &info
|
||||
}
|
||||
|
||||
@@ -144,6 +144,10 @@ func (s *Server) CacheFile() adapter.ClashCacheFile {
|
||||
return s.cacheFile
|
||||
}
|
||||
|
||||
func (s *Server) HistoryStorage() *urltest.HistoryStorage {
|
||||
return s.urlTestHistory
|
||||
}
|
||||
|
||||
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
|
||||
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
||||
return tracker, tracker
|
||||
|
||||
10
go.mod
10
go.mod
@@ -23,11 +23,11 @@ require (
|
||||
github.com/pires/go-proxyproto v0.6.2
|
||||
github.com/refraction-networking/utls v1.1.2
|
||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
|
||||
github.com/sagernet/sing v0.0.0-20220915031330-38f39bc0c690
|
||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8
|
||||
github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f
|
||||
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||
github.com/sagernet/sing-tun v0.0.0-20220915032336-60b1da576469
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12
|
||||
github.com/sagernet/sing-tun v0.0.0-20220922083325-80ee99472704
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f
|
||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
|
||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
||||
github.com/spf13/cobra v1.5.0
|
||||
@@ -35,7 +35,7 @@ require (
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
go.uber.org/atomic v1.10.0
|
||||
go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
|
||||
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b
|
||||
|
||||
20
go.sum
20
go.sum
@@ -145,16 +145,16 @@ github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTY
|
||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
||||
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.0.0-20220915031330-38f39bc0c690 h1:pvaLdkDmsGN2K46vf8rorAhYGFvKPuQNzcofuy3aXXg=
|
||||
github.com/sagernet/sing v0.0.0-20220915031330-38f39bc0c690/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8 h1:Iyfl+Rm5jcDvXuy/jpOBI3eu35ujci50tkqYHHwwg+8=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8/go.mod h1:bPVnJ5gJ0WmUfN1bJP9Cis0ab8SSByx6JVzyLJjDMwA=
|
||||
github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f h1:GX416thAwyc0vHBOal/qplvdhFgYO2dHD5GqADCJ0Ig=
|
||||
github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b h1:cXCMNJ9heZ+c6l+qUcku60x9KyXo4SOAaJfg/6spOmU=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220915032336-60b1da576469 h1:tvGUJsOqxZ3ofAY9undQfQ+JCWvmIwLpIOC+XaBFO88=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220915032336-60b1da576469/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 h1:4HYGbTDDemgBVTmaspXbkgjJlXc3hYVjNxSddJndq8Y=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220922083325-80ee99472704 h1:DOQQXQbB2gq4n2FuMHrL07HRs2naCCsuiu/9l1JFb9A=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220922083325-80ee99472704/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f h1:xyJ3Wbibcug4DxLi/FCHX2Td667SfieyZv645b8+eEE=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
|
||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
|
||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
||||
@@ -196,8 +196,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY=
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||
|
||||
@@ -190,7 +190,7 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
|
||||
if err != nil {
|
||||
t.NewError(ctx, err)
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error {
|
||||
@@ -212,7 +212,7 @@ func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstre
|
||||
if err != nil {
|
||||
t.NewError(ctx, err)
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tun) NewError(ctx context.Context, err error) {
|
||||
|
||||
@@ -90,6 +90,7 @@ nav:
|
||||
- SSH: configuration/outbound/ssh.md
|
||||
- DNS: configuration/outbound/dns.md
|
||||
- Selector: configuration/outbound/selector.md
|
||||
- URLTest: configuration/outbound/urltest.md
|
||||
- FAQ:
|
||||
- faq/index.md
|
||||
- Known Issues: faq/known-issues.md
|
||||
|
||||
@@ -14,3 +14,10 @@ type SelectorOutboundOptions struct {
|
||||
Outbounds []string `json:"outbounds"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
type URLTestOutboundOptions struct {
|
||||
Outbounds []string `json:"outbounds"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Interval Duration `json:"interval,omitempty"`
|
||||
Tolerance uint16 `json:"tolerance,omitempty"`
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ type _Outbound struct {
|
||||
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
|
||||
VLESSOptions VLESSOutboundOptions `json:"-"`
|
||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||
URLTestOptions URLTestOutboundOptions `json:"-"`
|
||||
}
|
||||
|
||||
type Outbound _Outbound
|
||||
@@ -61,6 +62,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
|
||||
v = h.VLESSOptions
|
||||
case C.TypeSelector:
|
||||
v = h.SelectorOptions
|
||||
case C.TypeURLTest:
|
||||
v = h.URLTestOptions
|
||||
default:
|
||||
return nil, E.New("unknown outbound type: ", h.Type)
|
||||
}
|
||||
@@ -104,6 +107,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
||||
v = &h.VLESSOptions
|
||||
case C.TypeSelector:
|
||||
v = &h.SelectorOptions
|
||||
case C.TypeURLTest:
|
||||
v = &h.URLTestOptions
|
||||
default:
|
||||
return E.New("unknown outbound type: ", h.Type)
|
||||
}
|
||||
@@ -115,17 +120,18 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
||||
}
|
||||
|
||||
type DialerOptions struct {
|
||||
Detour string `json:"detour,omitempty"`
|
||||
BindInterface string `json:"bind_interface,omitempty"`
|
||||
BindAddress *ListenAddress `json:"bind_address,omitempty"`
|
||||
ProtectPath string `json:"protect_path,omitempty"`
|
||||
RoutingMark int `json:"routing_mark,omitempty"`
|
||||
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
||||
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
|
||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||
UDPFragment bool `json:"udp_fragment,omitempty"`
|
||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
||||
FallbackDelay Duration `json:"fallback_delay,omitempty"`
|
||||
Detour string `json:"detour,omitempty"`
|
||||
BindInterface string `json:"bind_interface,omitempty"`
|
||||
BindAddress *ListenAddress `json:"bind_address,omitempty"`
|
||||
ProtectPath string `json:"protect_path,omitempty"`
|
||||
RoutingMark int `json:"routing_mark,omitempty"`
|
||||
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
||||
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
|
||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
||||
UDPFragmentDefault bool `json:"-"`
|
||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
||||
FallbackDelay Duration `json:"fallback_delay,omitempty"`
|
||||
}
|
||||
|
||||
type ServerOptions struct {
|
||||
|
||||
@@ -23,7 +23,7 @@ type VMessOutboundOptions struct {
|
||||
AuthenticatedLength bool `json:"authenticated_length,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
PacketAddr bool `json:"packet_addr,omitempty"`
|
||||
PacketEncoding string `json:"packet_encoding,omitempty"`
|
||||
Multiplex *MultiplexOptions `json:"multiplex,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
||||
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
||||
case C.TypeSelector:
|
||||
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
||||
case C.TypeURLTest:
|
||||
return NewURLTest(router, logger, options.Tag, options.URLTestOptions)
|
||||
default:
|
||||
return nil, E.New("unknown outbound type: ", options.Type)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ type Direct struct {
|
||||
}
|
||||
|
||||
func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) {
|
||||
options.UDPFragmentDefault = true
|
||||
outbound := &Direct{
|
||||
myOutboundAdapter: myOutboundAdapter{
|
||||
protocol: C.TypeDirect,
|
||||
|
||||
287
outbound/urltest.go
Normal file
287
outbound/urltest.go
Normal file
@@ -0,0 +1,287 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var (
|
||||
_ adapter.Outbound = (*URLTest)(nil)
|
||||
_ adapter.OutboundGroup = (*URLTest)(nil)
|
||||
)
|
||||
|
||||
type URLTest struct {
|
||||
myOutboundAdapter
|
||||
tags []string
|
||||
link string
|
||||
interval time.Duration
|
||||
tolerance uint16
|
||||
group *URLTestGroup
|
||||
}
|
||||
|
||||
func NewURLTest(router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
|
||||
outbound := &URLTest{
|
||||
myOutboundAdapter: myOutboundAdapter{
|
||||
protocol: C.TypeURLTest,
|
||||
router: router,
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
},
|
||||
tags: options.Outbounds,
|
||||
link: options.URL,
|
||||
interval: time.Duration(options.Interval),
|
||||
tolerance: options.Tolerance,
|
||||
}
|
||||
if len(outbound.tags) == 0 {
|
||||
return nil, E.New("missing tags")
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (s *URLTest) Network() []string {
|
||||
if s.group == nil {
|
||||
return []string{N.NetworkTCP, N.NetworkUDP}
|
||||
}
|
||||
return s.group.Select(N.NetworkTCP).Network()
|
||||
}
|
||||
|
||||
func (s *URLTest) Start() error {
|
||||
outbounds := make([]adapter.Outbound, 0, len(s.tags))
|
||||
for i, tag := range s.tags {
|
||||
detour, loaded := s.router.Outbound(tag)
|
||||
if !loaded {
|
||||
return E.New("outbound ", i, " not found: ", tag)
|
||||
}
|
||||
outbounds = append(outbounds, detour)
|
||||
}
|
||||
s.group = NewURLTestGroup(s.router, s.logger, outbounds, s.link, s.interval, s.tolerance)
|
||||
return s.group.Start()
|
||||
}
|
||||
|
||||
func (s URLTest) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(s.group),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *URLTest) Now() string {
|
||||
return s.group.Select(N.NetworkTCP).Tag()
|
||||
}
|
||||
|
||||
func (s *URLTest) All() []string {
|
||||
return s.tags
|
||||
}
|
||||
|
||||
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
outbound := s.group.Select(network)
|
||||
conn, err := outbound.DialContext(ctx, network, destination)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
s.logger.ErrorContext(ctx, err)
|
||||
go s.group.checkOutbounds()
|
||||
outbounds := s.group.Fallback(outbound)
|
||||
for _, fallback := range outbounds {
|
||||
conn, err = fallback.DialContext(ctx, network, destination)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
outbound := s.group.Select(N.NetworkUDP)
|
||||
conn, err := outbound.ListenPacket(ctx, destination)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
s.logger.ErrorContext(ctx, err)
|
||||
go s.group.checkOutbounds()
|
||||
outbounds := s.group.Fallback(outbound)
|
||||
for _, fallback := range outbounds {
|
||||
conn, err = fallback.ListenPacket(ctx, destination)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
return NewConnection(ctx, s, conn, metadata)
|
||||
}
|
||||
|
||||
func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return NewPacketConnection(ctx, s, conn, metadata)
|
||||
}
|
||||
|
||||
type URLTestGroup struct {
|
||||
router adapter.Router
|
||||
logger log.Logger
|
||||
outbounds []adapter.Outbound
|
||||
link string
|
||||
interval time.Duration
|
||||
tolerance uint16
|
||||
history *urltest.HistoryStorage
|
||||
|
||||
ticker *time.Ticker
|
||||
close chan struct{}
|
||||
}
|
||||
|
||||
func NewURLTestGroup(router adapter.Router, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16) *URLTestGroup {
|
||||
if link == "" {
|
||||
//goland:noinspection HttpUrlsUsage
|
||||
link = "http://www.gstatic.com/generate_204"
|
||||
}
|
||||
if interval == 0 {
|
||||
interval = C.DefaultURLTestInterval
|
||||
}
|
||||
if tolerance == 0 {
|
||||
tolerance = 50
|
||||
}
|
||||
var history *urltest.HistoryStorage
|
||||
if clashServer := router.ClashServer(); clashServer != nil {
|
||||
history = clashServer.HistoryStorage()
|
||||
} else {
|
||||
history = urltest.NewHistoryStorage()
|
||||
}
|
||||
return &URLTestGroup{
|
||||
router: router,
|
||||
logger: logger,
|
||||
outbounds: outbounds,
|
||||
link: link,
|
||||
interval: interval,
|
||||
tolerance: tolerance,
|
||||
history: history,
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) Start() error {
|
||||
g.ticker = time.NewTicker(g.interval)
|
||||
go g.loopCheck()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) Close() error {
|
||||
g.ticker.Stop()
|
||||
close(g.close)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) Select(network string) adapter.Outbound {
|
||||
var minDelay uint16
|
||||
var minTime time.Time
|
||||
var minOutbound adapter.Outbound
|
||||
for _, detour := range g.outbounds {
|
||||
if !common.Contains(detour.Network(), network) {
|
||||
continue
|
||||
}
|
||||
history := g.history.LoadURLTestHistory(RealTag(detour))
|
||||
if history == nil {
|
||||
continue
|
||||
}
|
||||
if minDelay == 0 || minDelay > history.Delay+g.tolerance || minDelay > history.Delay-g.tolerance && minTime.Before(history.Time) {
|
||||
minDelay = history.Delay
|
||||
minTime = history.Time
|
||||
minOutbound = detour
|
||||
}
|
||||
}
|
||||
if minOutbound == nil {
|
||||
for _, detour := range g.outbounds {
|
||||
if !common.Contains(detour.Network(), network) {
|
||||
continue
|
||||
}
|
||||
minOutbound = detour
|
||||
break
|
||||
}
|
||||
}
|
||||
return minOutbound
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) Fallback(used adapter.Outbound) []adapter.Outbound {
|
||||
outbounds := make([]adapter.Outbound, 0, len(g.outbounds)-1)
|
||||
for _, detour := range g.outbounds {
|
||||
if detour != used {
|
||||
outbounds = append(outbounds, detour)
|
||||
}
|
||||
}
|
||||
sort.Slice(outbounds, func(i, j int) bool {
|
||||
oi := outbounds[i]
|
||||
oj := outbounds[j]
|
||||
hi := g.history.LoadURLTestHistory(RealTag(oi))
|
||||
if hi == nil {
|
||||
return false
|
||||
}
|
||||
hj := g.history.LoadURLTestHistory(RealTag(oj))
|
||||
if hj == nil {
|
||||
return false
|
||||
}
|
||||
return hi.Delay < hj.Delay
|
||||
})
|
||||
return outbounds
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) loopCheck() {
|
||||
go g.checkOutbounds()
|
||||
for {
|
||||
select {
|
||||
case <-g.close:
|
||||
return
|
||||
case <-g.ticker.C:
|
||||
g.checkOutbounds()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) checkOutbounds() {
|
||||
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum[any](10))
|
||||
checked := make(map[string]bool)
|
||||
for _, detour := range g.outbounds {
|
||||
tag := detour.Tag()
|
||||
realTag := RealTag(detour)
|
||||
if checked[realTag] {
|
||||
continue
|
||||
}
|
||||
history := g.history.LoadURLTestHistory(realTag)
|
||||
if history != nil && time.Now().Sub(history.Time) < g.interval {
|
||||
continue
|
||||
}
|
||||
checked[realTag] = true
|
||||
p, loaded := g.router.Outbound(realTag)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
b.Go(realTag, func() (any, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout)
|
||||
defer cancel()
|
||||
t, err := urltest.URLTest(ctx, g.link, p)
|
||||
if err != nil {
|
||||
g.logger.Debug("outbound ", tag, " unavailable: ", err)
|
||||
g.history.DeleteURLTestHistory(realTag)
|
||||
} else {
|
||||
g.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
||||
g.history.StoreURLTestHistory(realTag, &urltest.History{
|
||||
Time: time.Now(),
|
||||
Delay: t,
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
}
|
||||
b.Wait()
|
||||
}
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/v2ray"
|
||||
"github.com/sagernet/sing-box/transport/vless"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
"github.com/sagernet/sing-vmess/vless"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -104,7 +105,7 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S
|
||||
return h.client.DialEarlyConn(conn, destination), nil
|
||||
case N.NetworkUDP:
|
||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||
return h.client.DialPacketConn(conn, destination), nil
|
||||
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||
default:
|
||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||
}
|
||||
@@ -129,11 +130,11 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
|
||||
return nil, err
|
||||
}
|
||||
if h.xudp {
|
||||
return h.client.DialXUDPPacketConn(conn, destination), nil
|
||||
return h.client.DialEarlyXUDPPacketConn(conn, destination), nil
|
||||
} else if h.packetAddr {
|
||||
return packetaddr.NewConn(h.client.DialPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil
|
||||
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination)), nil
|
||||
} else {
|
||||
return h.client.DialPacketConn(conn, destination), nil
|
||||
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/v2ray"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
"github.com/sagernet/sing/common"
|
||||
@@ -31,6 +32,7 @@ type VMess struct {
|
||||
tlsConfig tls.Config
|
||||
transport adapter.V2RayClientTransport
|
||||
packetAddr bool
|
||||
xudp bool
|
||||
}
|
||||
|
||||
func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) {
|
||||
@@ -62,8 +64,14 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if outbound.multiplexDialer == nil && options.PacketAddr {
|
||||
switch options.PacketEncoding {
|
||||
case "":
|
||||
case "packetaddr":
|
||||
outbound.packetAddr = true
|
||||
case "xudp":
|
||||
outbound.xudp = true
|
||||
default:
|
||||
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
||||
}
|
||||
var clientOptions []vmess.ClientOption
|
||||
if options.GlobalPadding {
|
||||
@@ -176,7 +184,9 @@ func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
||||
return nil, err
|
||||
}
|
||||
if h.packetAddr {
|
||||
return packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil
|
||||
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination)), nil
|
||||
} else if h.xudp {
|
||||
return h.client.DialEarlyXUDPPacketConn(conn, destination), nil
|
||||
} else {
|
||||
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
if err == nil {
|
||||
metadata.Protocol = sniffMetadata.Protocol
|
||||
metadata.Domain = sniffMetadata.Domain
|
||||
if metadata.SniffOverrideDestination && sniff.IsDomainName(metadata.Domain) {
|
||||
if metadata.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Fqdn: metadata.Domain,
|
||||
Port: metadata.Destination.Port,
|
||||
@@ -631,7 +631,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
if err == nil {
|
||||
metadata.Protocol = sniffMetadata.Protocol
|
||||
metadata.Domain = sniffMetadata.Domain
|
||||
if metadata.SniffOverrideDestination && sniff.IsDomainName(metadata.Domain) {
|
||||
if metadata.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Fqdn: metadata.Domain,
|
||||
Port: metadata.Destination.Port,
|
||||
|
||||
41
test/config/vmess-mux-client.json
Normal file
41
test/config/vmess-mux-client.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": "1080",
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"auth": "noauth",
|
||||
"udp": true,
|
||||
"ip": "127.0.0.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "vmess",
|
||||
"settings": {
|
||||
"vnext": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 1234,
|
||||
"users": [
|
||||
{
|
||||
"id": "",
|
||||
"alterId": 0,
|
||||
"security": "none",
|
||||
"experiments": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"mux": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
14
test/go.mod
14
test/go.mod
@@ -10,7 +10,7 @@ require (
|
||||
github.com/docker/docker v20.10.18+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/gofrs/uuid v4.3.0+incompatible
|
||||
github.com/sagernet/sing v0.0.0-20220914045234-93cc53b60cee
|
||||
github.com/sagernet/sing v0.0.0-20220916071326-834794b006ea
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||
github.com/spyzhov/ajson v0.7.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
@@ -21,6 +21,7 @@ require (
|
||||
|
||||
require (
|
||||
berty.tech/go-libtor v1.0.385 // indirect
|
||||
github.com/Dreamacro/clash v1.11.8 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
@@ -65,13 +66,12 @@ require (
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
|
||||
github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 // indirect
|
||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8 // indirect
|
||||
github.com/sagernet/sing-tun v0.0.0-20220914100102-057dd738a7f7 // indirect
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 // indirect
|
||||
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b // indirect
|
||||
github.com/sagernet/sing-tun v0.0.0-20220916073459-0032242c9617 // indirect
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220917033734-9b634758039d // indirect
|
||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
|
||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
@@ -86,7 +86,7 @@ require (
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b // indirect
|
||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
|
||||
35
test/go.sum
35
test/go.sum
@@ -5,6 +5,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Dreamacro/clash v1.11.8 h1:t/sy3/tiihRlvV3SsliYFjj8rKpbLw5IJ2PymiHcwS8=
|
||||
github.com/Dreamacro/clash v1.11.8/go.mod h1:LsWCcJFoKuL1C5F2c0m/1690wihTHYSU3J+im09yTwQ=
|
||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
@@ -105,8 +107,8 @@ github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8t
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
@@ -161,27 +163,25 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTYRwpwvEm3nc4eRdxk6vtRbouLVZAzk=
|
||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
||||
github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 h1:lQ4RFWY/wBYmXl/zJJCwQbhiEIbgEqC7j+nhZYkgwmU=
|
||||
github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0/go.mod h1:xSHLCsdgy5QIozXFEv6uDgMWzyrRdFFjr3TgL+juu6g=
|
||||
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.0.0-20220914045234-93cc53b60cee h1:+3w7+QWnhWi3Qz7+Xcais8zViHRUPIkmxq3eYZm/zvk=
|
||||
github.com/sagernet/sing v0.0.0-20220914045234-93cc53b60cee/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8 h1:Iyfl+Rm5jcDvXuy/jpOBI3eu35ujci50tkqYHHwwg+8=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8/go.mod h1:bPVnJ5gJ0WmUfN1bJP9Cis0ab8SSByx6JVzyLJjDMwA=
|
||||
github.com/sagernet/sing v0.0.0-20220916071326-834794b006ea h1:ZAWvZdeByPBBz3Vs+w3Erbh+DDo7D4biokoPhXl0nNU=
|
||||
github.com/sagernet/sing v0.0.0-20220916071326-834794b006ea/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b h1:cXCMNJ9heZ+c6l+qUcku60x9KyXo4SOAaJfg/6spOmU=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220914100102-057dd738a7f7 h1:zdvFDYMz8s0e9UmOxMk0wNGOKh64KfeWpx8UAbJJI60=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220914100102-057dd738a7f7/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 h1:4HYGbTDDemgBVTmaspXbkgjJlXc3hYVjNxSddJndq8Y=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220916073459-0032242c9617 h1:fNTNmylhB/UjoBLusmrFu2B1fat4OCkDkQXTgrE7ZsE=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220916073459-0032242c9617/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220917033734-9b634758039d h1:/GNWxSrQj4chFYk2jahIXNPdvUa9DW8IdgyqYFbq9oQ=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220917033734-9b634758039d/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
|
||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
|
||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
||||
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/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spyzhov/ajson v0.7.1 h1:1MDIlPc6x0zjNtpa7tDzRAyFAvRX+X8ZsvtYz5lZg6A=
|
||||
github.com/spyzhov/ajson v0.7.1/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
|
||||
@@ -218,7 +218,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
||||
@@ -266,8 +265,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -295,10 +294,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc h1:dpclq5m2YrqPGStKmtw7IcNbKLfbIqKXvNxDJKdIKYc=
|
||||
golang.org/x/sys v0.0.0-20220913120320-3275c407cedc/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -334,8 +333,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0 h1:5ZkdpbduT/g+9OtbSDvbF3KvfQG45CtH/ppO8FUmvCQ=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0/go.mod h1:enML0deDxY1ux+B6ANGiwtg0yAJi1rctkTpcHNAVPyg=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b h1:qgrKnOfe1zyURRNdmDlGbN32i38Zjmw0B1+TMdHcOvg=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b/go.mod h1:6y4CqPAy54NwiN4nC8K+R1eMpQDB1P2d25qmunh2RSA=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
|
||||
109
test/mux_cool_test.go
Normal file
109
test/mux_cool_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
|
||||
"github.com/spyzhov/ajson"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMuxCoolServer(t *testing.T) {
|
||||
userId := newUUID()
|
||||
content, err := os.ReadFile("config/vmess-mux-client.json")
|
||||
require.NoError(t, err)
|
||||
config, err := ajson.Unmarshal(content)
|
||||
require.NoError(t, err)
|
||||
|
||||
config.MustKey("inbounds").MustIndex(0).MustKey("port").SetNumeric(float64(clientPort))
|
||||
outbound := config.MustKey("outbounds").MustIndex(0).MustKey("settings").MustKey("vnext").MustIndex(0)
|
||||
outbound.MustKey("port").SetNumeric(float64(serverPort))
|
||||
user := outbound.MustKey("users").MustIndex(0)
|
||||
user.MustKey("id").SetString(userId.String())
|
||||
|
||||
content, err = ajson.Marshal(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
startDockerContainer(t, DockerOptions{
|
||||
Image: ImageV2RayCore,
|
||||
Ports: []uint16{serverPort, testPort},
|
||||
EntryPoint: "v2ray",
|
||||
Stdin: content,
|
||||
})
|
||||
|
||||
startInstance(t, option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
{
|
||||
Type: C.TypeVMess,
|
||||
VMessOptions: option.VMessInboundOptions{
|
||||
ListenOptions: option.ListenOptions{
|
||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||
ListenPort: serverPort,
|
||||
},
|
||||
Users: []option.VMessUser{
|
||||
{
|
||||
Name: "sekai",
|
||||
UUID: userId.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func TestMuxCoolClient(t *testing.T) {
|
||||
user := newUUID()
|
||||
content, err := os.ReadFile("config/vmess-server.json")
|
||||
require.NoError(t, err)
|
||||
config, err := ajson.Unmarshal(content)
|
||||
require.NoError(t, err)
|
||||
|
||||
inbound := config.MustKey("inbounds").MustIndex(0)
|
||||
inbound.MustKey("port").SetNumeric(float64(serverPort))
|
||||
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(user.String())
|
||||
|
||||
content, err = ajson.Marshal(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
startDockerContainer(t, DockerOptions{
|
||||
Image: ImageXRayCore,
|
||||
Ports: []uint16{serverPort, testPort},
|
||||
EntryPoint: "xray",
|
||||
Stdin: content,
|
||||
})
|
||||
|
||||
startInstance(t, option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
{
|
||||
Type: C.TypeMixed,
|
||||
MixedOptions: option.HTTPMixedInboundOptions{
|
||||
ListenOptions: option.ListenOptions{
|
||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||
ListenPort: clientPort,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Outbounds: []option.Outbound{
|
||||
{
|
||||
Type: C.TypeVMess,
|
||||
VMessOptions: option.VMessOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
UUID: user.String(),
|
||||
PacketEncoding: "xudp",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
@@ -306,7 +306,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo
|
||||
AlterId: alterId,
|
||||
GlobalPadding: globalPadding,
|
||||
AuthenticatedLength: authenticatedLength,
|
||||
PacketAddr: packetAddr,
|
||||
PacketEncoding: "packetaddr",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -487,6 +487,25 @@ func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) er
|
||||
})
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
msg := <-c.msgCh
|
||||
if msg == nil {
|
||||
err = net.ErrClosed
|
||||
return
|
||||
}
|
||||
n = copy(p, msg.Data)
|
||||
addr = M.ParseSocksaddrHostPort(msg.Host, msg.Port).UDPAddr()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
err = c.WritePacket(buf.As(p), M.SocksaddrFromNet(addr))
|
||||
if err == nil {
|
||||
n = len(p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *PacketConn) LocalAddr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
@@ -507,14 +526,6 @@ func (c *PacketConn) SetWriteDeadline(t time.Time) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
return 0, nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *PacketConn) Read(b []byte) (n int, err error) {
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
package vless
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
func NewClient(userId string) (*Client, error) {
|
||||
user := uuid.FromStringOrNil(userId)
|
||||
if user == uuid.Nil {
|
||||
user = uuid.NewV5(user, userId)
|
||||
}
|
||||
return &Client{key: user.Bytes()}, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialEarlyConn(conn net.Conn, destination M.Socksaddr) *Conn {
|
||||
return &Conn{Conn: conn, key: c.key, destination: destination}
|
||||
}
|
||||
|
||||
func (c *Client) DialPacketConn(conn net.Conn, destination M.Socksaddr) *PacketConn {
|
||||
return &PacketConn{Conn: conn, key: c.key, destination: destination}
|
||||
}
|
||||
|
||||
func (c *Client) DialXUDPPacketConn(conn net.Conn, destination M.Socksaddr) *XUDPConn {
|
||||
return &XUDPConn{Conn: conn, key: c.key, destination: destination}
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
key []byte
|
||||
destination M.Socksaddr
|
||||
requestWritten bool
|
||||
responseRead bool
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
if !c.responseRead {
|
||||
err = ReadResponse(c.Conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.responseRead = true
|
||||
}
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
if !c.requestWritten {
|
||||
err = WriteRequest(c.Conn, Request{c.key, CommandTCP, c.destination}, b)
|
||||
if err == nil {
|
||||
n = len(b)
|
||||
}
|
||||
c.requestWritten = true
|
||||
return
|
||||
}
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
type PacketConn struct {
|
||||
net.Conn
|
||||
key []byte
|
||||
destination M.Socksaddr
|
||||
requestWritten bool
|
||||
responseRead bool
|
||||
}
|
||||
|
||||
func (c *PacketConn) Read(b []byte) (n int, err error) {
|
||||
if !c.responseRead {
|
||||
err = ReadResponse(c.Conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.responseRead = true
|
||||
}
|
||||
var length uint16
|
||||
err = binary.Read(c.Conn, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if cap(b) < int(length) {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
return io.ReadFull(c.Conn, b[:length])
|
||||
}
|
||||
|
||||
func (c *PacketConn) Write(b []byte) (n int, err error) {
|
||||
if !c.requestWritten {
|
||||
err = WritePacketRequest(c.Conn, Request{c.key, CommandUDP, c.destination}, b)
|
||||
if err == nil {
|
||||
n = len(b)
|
||||
}
|
||||
c.requestWritten = true
|
||||
return
|
||||
}
|
||||
err = binary.Write(c.Conn, binary.BigEndian, uint16(len(b)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
defer buffer.Release()
|
||||
dataLen := buffer.Len()
|
||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(dataLen))
|
||||
if !c.requestWritten {
|
||||
err := WritePacketRequest(c.Conn, Request{c.key, CommandUDP, c.destination}, buffer.Bytes())
|
||||
c.requestWritten = true
|
||||
return err
|
||||
}
|
||||
return common.Error(c.Conn.Write(buffer.Bytes()))
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
n, err = c.Read(p)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return c.Write(p)
|
||||
}
|
||||
|
||||
func (c *PacketConn) FrontHeadroom() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (c *PacketConn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package vless
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = 0
|
||||
CommandTCP = 1
|
||||
CommandUDP = 2
|
||||
CommandMux = 3
|
||||
NetworkUDP = 2
|
||||
)
|
||||
|
||||
var AddressSerializer = M.NewSerializer(
|
||||
M.AddressFamilyByte(0x01, M.AddressFamilyIPv4),
|
||||
M.AddressFamilyByte(0x02, M.AddressFamilyFqdn),
|
||||
M.AddressFamilyByte(0x03, M.AddressFamilyIPv6),
|
||||
M.PortThenAddress(),
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
UUID []byte
|
||||
Command byte
|
||||
Destination M.Socksaddr
|
||||
}
|
||||
|
||||
func WriteRequest(writer io.Writer, request Request, payload []byte) error {
|
||||
var requestLen int
|
||||
requestLen += 1 // version
|
||||
requestLen += 16 // uuid
|
||||
requestLen += 1 // protobuf length
|
||||
requestLen += 1 // command
|
||||
requestLen += AddressSerializer.AddrPortLen(request.Destination)
|
||||
requestLen += len(payload)
|
||||
_buffer := buf.StackNewSize(requestLen)
|
||||
defer common.KeepAlive(_buffer)
|
||||
buffer := common.Dup(_buffer)
|
||||
defer buffer.Release()
|
||||
common.Must(
|
||||
buffer.WriteByte(Version),
|
||||
common.Error(buffer.Write(request.UUID)),
|
||||
buffer.WriteByte(0),
|
||||
buffer.WriteByte(CommandTCP),
|
||||
AddressSerializer.WriteAddrPort(buffer, request.Destination),
|
||||
common.Error(buffer.Write(payload)),
|
||||
)
|
||||
return common.Error(writer.Write(buffer.Bytes()))
|
||||
}
|
||||
|
||||
func WritePacketRequest(writer io.Writer, request Request, payload []byte) error {
|
||||
var requestLen int
|
||||
requestLen += 1 // version
|
||||
requestLen += 16 // uuid
|
||||
requestLen += 1 // protobuf length
|
||||
requestLen += 1 // command
|
||||
requestLen += AddressSerializer.AddrPortLen(request.Destination)
|
||||
if len(payload) > 0 {
|
||||
requestLen += 2
|
||||
requestLen += len(payload)
|
||||
}
|
||||
_buffer := buf.StackNewSize(requestLen)
|
||||
defer common.KeepAlive(_buffer)
|
||||
buffer := common.Dup(_buffer)
|
||||
defer buffer.Release()
|
||||
common.Must(
|
||||
buffer.WriteByte(Version),
|
||||
common.Error(buffer.Write(request.UUID)),
|
||||
buffer.WriteByte(0),
|
||||
buffer.WriteByte(CommandUDP),
|
||||
AddressSerializer.WriteAddrPort(buffer, request.Destination),
|
||||
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
|
||||
common.Error(buffer.Write(payload)),
|
||||
)
|
||||
return common.Error(writer.Write(buffer.Bytes()))
|
||||
}
|
||||
|
||||
func ReadResponse(reader io.Reader) error {
|
||||
version, err := rw.ReadByte(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if version != Version {
|
||||
return E.New("unknown version: ", version)
|
||||
}
|
||||
protobufLength, err := rw.ReadByte(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if protobufLength > 0 {
|
||||
err = rw.SkipN(reader, int(protobufLength))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
package vless
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
type XUDPConn struct {
|
||||
net.Conn
|
||||
key []byte
|
||||
destination M.Socksaddr
|
||||
requestWritten bool
|
||||
responseRead bool
|
||||
}
|
||||
|
||||
func (c *XUDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
return 0, nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *XUDPConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||
start := buffer.Start()
|
||||
if !c.responseRead {
|
||||
err = ReadResponse(c.Conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.responseRead = true
|
||||
buffer.FullReset()
|
||||
}
|
||||
_, err = buffer.ReadFullFrom(c.Conn, 6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var length uint16
|
||||
err = binary.Read(buffer, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header, err := buffer.ReadBytes(4)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch header[2] {
|
||||
case 1:
|
||||
// frame new
|
||||
return M.Socksaddr{}, E.New("unexpected frame new")
|
||||
case 2:
|
||||
// frame keep
|
||||
if length != 4 {
|
||||
_, err = buffer.ReadFullFrom(c.Conn, int(length)-2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buffer.Advance(1)
|
||||
destination, err = AddressSerializer.ReadAddrPort(buffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
_, err = buffer.ReadFullFrom(c.Conn, 2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
destination = c.destination
|
||||
}
|
||||
case 3:
|
||||
// frame end
|
||||
return M.Socksaddr{}, io.EOF
|
||||
case 4:
|
||||
// frame keep alive
|
||||
default:
|
||||
return M.Socksaddr{}, E.New("unexpected frame: ", buffer.Byte(2))
|
||||
}
|
||||
// option error
|
||||
if header[3]&2 == 2 {
|
||||
return M.Socksaddr{}, E.Cause(net.ErrClosed, "remote closed")
|
||||
}
|
||||
// option data
|
||||
if header[3]&1 != 1 {
|
||||
buffer.Resize(start, 0)
|
||||
return c.ReadPacket(buffer)
|
||||
} else {
|
||||
err = binary.Read(buffer, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buffer.Resize(start, 0)
|
||||
_, err = buffer.ReadFullFrom(c.Conn, int(length))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *XUDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
destination := M.SocksaddrFromNet(addr)
|
||||
headerLen := c.frontHeadroom(AddressSerializer.AddrPortLen(destination))
|
||||
buffer := buf.NewSize(headerLen + len(p))
|
||||
buffer.Advance(headerLen)
|
||||
common.Must1(buffer.Write(p))
|
||||
err = c.WritePacket(buffer, destination)
|
||||
if err == nil {
|
||||
n = len(p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *XUDPConn) frontHeadroom(addrLen int) int {
|
||||
if !c.requestWritten {
|
||||
var headerLen int
|
||||
headerLen += 1 // version
|
||||
headerLen += 16 // uuid
|
||||
headerLen += 1 // protobuf length
|
||||
headerLen += 1 // command
|
||||
headerLen += 2 // frame len
|
||||
headerLen += 5 // frame header
|
||||
headerLen += addrLen
|
||||
headerLen += 2 // payload len
|
||||
return headerLen
|
||||
} else {
|
||||
return 7 + addrLen + 2
|
||||
}
|
||||
}
|
||||
|
||||
func (c *XUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
defer buffer.Release()
|
||||
dataLen := buffer.Len()
|
||||
addrLen := M.SocksaddrSerializer.AddrPortLen(destination)
|
||||
if !c.requestWritten {
|
||||
header := buf.With(buffer.ExtendHeader(c.frontHeadroom(addrLen)))
|
||||
common.Must(
|
||||
header.WriteByte(Version),
|
||||
common.Error(header.Write(c.key)),
|
||||
header.WriteByte(0),
|
||||
header.WriteByte(CommandMux),
|
||||
binary.Write(header, binary.BigEndian, uint16(5+addrLen)),
|
||||
header.WriteByte(0),
|
||||
header.WriteByte(0),
|
||||
header.WriteByte(1), // frame type new
|
||||
header.WriteByte(1), // option data
|
||||
header.WriteByte(NetworkUDP),
|
||||
AddressSerializer.WriteAddrPort(header, destination),
|
||||
binary.Write(header, binary.BigEndian, uint16(dataLen)),
|
||||
)
|
||||
c.requestWritten = true
|
||||
} else {
|
||||
header := buffer.ExtendHeader(c.frontHeadroom(addrLen))
|
||||
binary.BigEndian.PutUint16(header, uint16(5+addrLen))
|
||||
header[2] = 0
|
||||
header[3] = 0
|
||||
header[4] = 2 // frame keep
|
||||
header[5] = 1 // option data
|
||||
header[6] = NetworkUDP
|
||||
err := AddressSerializer.WriteAddrPort(buf.With(header[7:]), destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
binary.BigEndian.PutUint16(header[7+addrLen:], uint16(dataLen))
|
||||
}
|
||||
return common.Error(c.Conn.Write(buffer.Bytes()))
|
||||
}
|
||||
|
||||
func (c *XUDPConn) FrontHeadroom() int {
|
||||
return c.frontHeadroom(M.MaxSocksaddrLength)
|
||||
}
|
||||
|
||||
func (c *XUDPConn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
Reference in New Issue
Block a user