mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
11 Commits
v1.1-beta2
...
v1.1-beta4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1db7f45370 | ||
|
|
b271e19a23 | ||
|
|
79b6bdfda1 | ||
|
|
38088f28b0 | ||
|
|
dfb8b5f2fa | ||
|
|
9913e0e025 | ||
|
|
ce567ffdde | ||
|
|
5a9913eca5 | ||
|
|
eaf1ace681 | ||
|
|
a2d1f89922 | ||
|
|
7e09beb0c3 |
2
Makefile
2
Makefile
@@ -64,7 +64,7 @@ test:
|
||||
@go test -v . && \
|
||||
pushd test && \
|
||||
go mod tidy && \
|
||||
go test -v -tags with_quic,with_wireguard,with_grpc,with_ech,with_utls . && \
|
||||
go test -v -tags with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr . && \
|
||||
popd
|
||||
|
||||
clean:
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
|
||||
}
|
||||
}
|
||||
|
||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (net.Conn, error) {
|
||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
||||
tlsConn := config.Client(conn)
|
||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -15,6 +15,8 @@ type (
|
||||
)
|
||||
|
||||
type Config interface {
|
||||
NextProtos() []string
|
||||
SetNextProtos(nextProto []string)
|
||||
Config() (*STDConfig, error)
|
||||
Client(conn net.Conn) Conn
|
||||
}
|
||||
@@ -28,6 +30,7 @@ type ServerConfig interface {
|
||||
type Conn interface {
|
||||
net.Conn
|
||||
HandshakeContext(ctx context.Context) error
|
||||
ConnectionState() tls.ConnectionState
|
||||
}
|
||||
|
||||
func ParseTLSVersion(version string) (uint16, error) {
|
||||
|
||||
@@ -4,6 +4,7 @@ package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"net"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/cloudflaretls"
|
||||
cftls "github.com/sagernet/sing-box/transport/cloudflaretls"
|
||||
"github.com/sagernet/sing-dns"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
@@ -21,7 +22,15 @@ import (
|
||||
)
|
||||
|
||||
type echClientConfig struct {
|
||||
config *tls.Config
|
||||
config *cftls.Config
|
||||
}
|
||||
|
||||
func (e *echClientConfig) NextProtos() []string {
|
||||
return e.config.NextProtos
|
||||
}
|
||||
|
||||
func (e *echClientConfig) SetNextProtos(nextProto []string) {
|
||||
e.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
func (e *echClientConfig) Config() (*STDConfig, error) {
|
||||
@@ -29,7 +38,29 @@ func (e *echClientConfig) Config() (*STDConfig, error) {
|
||||
}
|
||||
|
||||
func (e *echClientConfig) Client(conn net.Conn) Conn {
|
||||
return tls.Client(conn, e.config)
|
||||
return &echConnWrapper{cftls.Client(conn, e.config)}
|
||||
}
|
||||
|
||||
type echConnWrapper struct {
|
||||
*cftls.Conn
|
||||
}
|
||||
|
||||
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
return tls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
DidResume: state.DidResume,
|
||||
CipherSuite: state.CipherSuite,
|
||||
NegotiatedProtocol: state.NegotiatedProtocol,
|
||||
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
||||
ServerName: state.ServerName,
|
||||
PeerCertificates: state.PeerCertificates,
|
||||
VerifiedChains: state.VerifiedChains,
|
||||
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
||||
OCSPResponse: state.OCSPResponse,
|
||||
TLSUnique: state.TLSUnique,
|
||||
}
|
||||
}
|
||||
|
||||
func newECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
@@ -45,7 +76,7 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
return nil, E.New("missing server_name or insecure=true")
|
||||
}
|
||||
|
||||
var tlsConfig tls.Config
|
||||
var tlsConfig cftls.Config
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
@@ -55,7 +86,7 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
tlsConfig.InsecureSkipVerify = options.Insecure
|
||||
} else if options.DisableSNI {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
||||
tlsConfig.VerifyConnection = func(state cftls.ConnectionState) error {
|
||||
verifyOptions := x509.VerifyOptions{
|
||||
DNSName: serverName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
@@ -87,7 +118,7 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
if options.CipherSuites != nil {
|
||||
find:
|
||||
for _, cipherSuite := range options.CipherSuites {
|
||||
for _, tlsCipherSuite := range tls.CipherSuites() {
|
||||
for _, tlsCipherSuite := range cftls.CipherSuites() {
|
||||
if cipherSuite == tlsCipherSuite.Name {
|
||||
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
||||
continue find
|
||||
@@ -124,7 +155,7 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientConfig, err := tls.UnmarshalECHConfigs(clientConfigContent)
|
||||
clientConfig, err := cftls.UnmarshalECHConfigs(clientConfigContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -137,8 +168,8 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
|
||||
const typeHTTPS = 65
|
||||
|
||||
func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]tls.ECHConfig, error) {
|
||||
return func(ctx context.Context, serverName string) ([]tls.ECHConfig, error) {
|
||||
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 := &dnsmessage.Message{
|
||||
Header: dnsmessage.Header{
|
||||
RecursionDesired: true,
|
||||
@@ -176,7 +207,7 @@ func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serve
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode ECH config")
|
||||
}
|
||||
return tls.UnmarshalECHConfigs(echConfig)
|
||||
return cftls.UnmarshalECHConfigs(echConfig)
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -99,6 +99,14 @@ func newStdClient(serverAddress string, options option.OutboundTLSOptions) (Conf
|
||||
return &stdClientConfig{&tlsConfig}, nil
|
||||
}
|
||||
|
||||
func (s *stdClientConfig) NextProtos() []string {
|
||||
return s.config.NextProtos
|
||||
}
|
||||
|
||||
func (s *stdClientConfig) SetNextProtos(nextProto []string) {
|
||||
s.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
func (s *stdClientConfig) Config() (*STDConfig, error) {
|
||||
return s.config, nil
|
||||
}
|
||||
|
||||
@@ -26,6 +26,14 @@ type STDServerConfig struct {
|
||||
watcher *fsnotify.Watcher
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) NextProtos() []string {
|
||||
return c.config.NextProtos
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
||||
c.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
func newSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
if !options.Enabled {
|
||||
return nil, nil
|
||||
|
||||
@@ -22,6 +22,14 @@ type utlsClientConfig struct {
|
||||
id utls.ClientHelloID
|
||||
}
|
||||
|
||||
func (e *utlsClientConfig) NextProtos() []string {
|
||||
return e.config.NextProtos
|
||||
}
|
||||
|
||||
func (e *utlsClientConfig) SetNextProtos(nextProto []string) {
|
||||
e.config.NextProtos = nextProto
|
||||
}
|
||||
|
||||
func (e *utlsClientConfig) Config() (*STDConfig, error) {
|
||||
return nil, E.New("unsupported usage for uTLS")
|
||||
}
|
||||
@@ -38,6 +46,24 @@ func (c *utlsConnWrapper) HandshakeContext(ctx context.Context) error {
|
||||
return c.Conn.Handshake()
|
||||
}
|
||||
|
||||
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
return tls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
DidResume: state.DidResume,
|
||||
CipherSuite: state.CipherSuite,
|
||||
NegotiatedProtocol: state.NegotiatedProtocol,
|
||||
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
||||
ServerName: state.ServerName,
|
||||
PeerCertificates: state.PeerCertificates,
|
||||
VerifiedChains: state.VerifiedChains,
|
||||
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
||||
OCSPResponse: state.OCSPResponse,
|
||||
TLSUnique: state.TLSUnique,
|
||||
}
|
||||
}
|
||||
|
||||
func newUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
var serverName string
|
||||
if options.ServerName != "" {
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
TypeTun = "tun"
|
||||
TypeRedirect = "redirect"
|
||||
TypeTProxy = "tproxy"
|
||||
TypeDirect = "direct"
|
||||
TypeBlock = "block"
|
||||
TypeDNS = "dns"
|
||||
TypeSocks = "socks"
|
||||
TypeHTTP = "http"
|
||||
TypeMixed = "mixed"
|
||||
TypeShadowsocks = "shadowsocks"
|
||||
TypeVMess = "vmess"
|
||||
TypeTrojan = "trojan"
|
||||
TypeNaive = "naive"
|
||||
TypeWireGuard = "wireguard"
|
||||
TypeHysteria = "hysteria"
|
||||
TypeTor = "tor"
|
||||
TypeSSH = "ssh"
|
||||
TypeShadowTLS = "shadowtls"
|
||||
TypeTun = "tun"
|
||||
TypeRedirect = "redirect"
|
||||
TypeTProxy = "tproxy"
|
||||
TypeDirect = "direct"
|
||||
TypeBlock = "block"
|
||||
TypeDNS = "dns"
|
||||
TypeSocks = "socks"
|
||||
TypeHTTP = "http"
|
||||
TypeMixed = "mixed"
|
||||
TypeShadowsocks = "shadowsocks"
|
||||
TypeVMess = "vmess"
|
||||
TypeTrojan = "trojan"
|
||||
TypeNaive = "naive"
|
||||
TypeWireGuard = "wireguard"
|
||||
TypeHysteria = "hysteria"
|
||||
TypeTor = "tor"
|
||||
TypeSSH = "ssh"
|
||||
TypeShadowTLS = "shadowtls"
|
||||
TypeShadowsocksR = "shadowsocksr"
|
||||
TypeVLESS = "vless"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package constant
|
||||
|
||||
var (
|
||||
Version = "1.1-beta2"
|
||||
Version = "1.1-beta4"
|
||||
Commit = ""
|
||||
)
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
#### 1.1-beta4
|
||||
|
||||
* Add internal simple-obfs and v2ray-plugin [Shadowsocks plugins](/configuration/outbound/shadowsocks#plugin)
|
||||
* Add [ShadowsocksR outbound](/configuration/outbound/shadowsocksr)
|
||||
* Add [VLESS outbound and XUDP](/configuration/outbound/vless)
|
||||
* Skip wait for hysteria tcp handshake response
|
||||
* Fix socks4 client
|
||||
* Fix hysteria inbound
|
||||
* Fix concurrent write
|
||||
|
||||
#### 1.0.3
|
||||
|
||||
* Fix socks4 client
|
||||
* Fix hysteria inbound
|
||||
* Fix concurrent write
|
||||
|
||||
#### 1.1-beta3
|
||||
|
||||
* Fix using custom TLS client in http2 client
|
||||
* Fix bugs in 1.1-beta2
|
||||
|
||||
#### 1.1-beta2
|
||||
|
||||
* Add Clash mode and persistence support **1**
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"server_port": 1080,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"plugin": "",
|
||||
"plugin_opts": "",
|
||||
"network": "udp",
|
||||
"udp_over_tcp": false,
|
||||
"multiplex": {},
|
||||
@@ -65,6 +67,16 @@ Legacy encryption methods:
|
||||
|
||||
The shadowsocks password.
|
||||
|
||||
#### plugin
|
||||
|
||||
Shadowsocks SIP003 plugin, implemented in internal.
|
||||
|
||||
Only `obfs-local` and `v2ray-plugin` are supported.
|
||||
|
||||
#### plugin_opts
|
||||
|
||||
Shadowsocks SIP003 plugin options.
|
||||
|
||||
#### network
|
||||
|
||||
Enabled network
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"server_port": 1080,
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"plugin": "",
|
||||
"plugin_opts": "",
|
||||
"network": "udp",
|
||||
"udp_over_tcp": false,
|
||||
"multiplex": {},
|
||||
@@ -65,6 +67,16 @@
|
||||
|
||||
Shadowsocks 密码。
|
||||
|
||||
#### plugin
|
||||
|
||||
Shadowsocks SIP003 插件,由内部实现。
|
||||
|
||||
仅支持 `obfs-local` 和 `v2ray-plugin`。
|
||||
|
||||
#### plugin_opts
|
||||
|
||||
Shadowsocks SIP003 插件参数。
|
||||
|
||||
#### network
|
||||
|
||||
启用的网络协议
|
||||
|
||||
106
docs/configuration/outbound/shadowsocksr.md
Normal file
106
docs/configuration/outbound/shadowsocksr.md
Normal file
@@ -0,0 +1,106 @@
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "shadowsocksr",
|
||||
"tag": "ssr-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"method": "aes-128-cfb",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"obfs": "plain",
|
||||
"obfs_param": "",
|
||||
"protocol": "origin",
|
||||
"protocol_param": "",
|
||||
"network": "udp",
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
The ShadowsocksR protocol is obsolete and unmaintained. This outbound is provided for compatibility only.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
ShadowsocksR is not included by default, see [Installation](/#installation).
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
The server address.
|
||||
|
||||
#### server_port
|
||||
|
||||
==Required==
|
||||
|
||||
The server port.
|
||||
|
||||
#### method
|
||||
|
||||
==Required==
|
||||
|
||||
Encryption methods:
|
||||
|
||||
* `aes-128-ctr`
|
||||
* `aes-192-ctr`
|
||||
* `aes-256-ctr`
|
||||
* `aes-128-cfb`
|
||||
* `aes-192-cfb`
|
||||
* `aes-256-cfb`
|
||||
* `rc4-md5`
|
||||
* `chacha20-ietf`
|
||||
* `xchacha20`
|
||||
|
||||
#### password
|
||||
|
||||
==Required==
|
||||
|
||||
The shadowsocks password.
|
||||
|
||||
#### obfs
|
||||
|
||||
The ShadowsocksR obfuscate.
|
||||
|
||||
* plain
|
||||
* http_simple
|
||||
* http_post
|
||||
* random_head
|
||||
* tls1.2_ticket_auth
|
||||
|
||||
#### obfs_param
|
||||
|
||||
The ShadowsocksR obfuscate parameter.
|
||||
|
||||
#### protocol
|
||||
|
||||
The ShadowsocksR protocol.
|
||||
|
||||
* origin
|
||||
* verify_sha1
|
||||
* auth_sha1_v4
|
||||
* auth_aes128_md5
|
||||
* auth_aes128_sha1
|
||||
* auth_chain_a
|
||||
* auth_chain_b
|
||||
|
||||
#### protocol_param
|
||||
|
||||
The ShadowsocksR protocol parameter.
|
||||
|
||||
#### network
|
||||
|
||||
Enabled network
|
||||
|
||||
One of `tcp` `udp`.
|
||||
|
||||
Both is enabled by default.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial) for details.
|
||||
106
docs/configuration/outbound/shadowsocksr.zh.md
Normal file
106
docs/configuration/outbound/shadowsocksr.zh.md
Normal file
@@ -0,0 +1,106 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "shadowsocksr",
|
||||
"tag": "ssr-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"method": "aes-128-cfb",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg==",
|
||||
"obfs": "plain",
|
||||
"obfs_param": "",
|
||||
"protocol": "origin",
|
||||
"protocol_param": "",
|
||||
"network": "udp",
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
ShadowsocksR 协议已过时且无人维护。 提供此出站仅出于兼容性目的。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含被 ShadowsocksR,参阅 [安装](/zh/#_2)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
服务器地址。
|
||||
|
||||
#### server_port
|
||||
|
||||
==必填==
|
||||
|
||||
服务器端口。
|
||||
|
||||
#### method
|
||||
|
||||
==必填==
|
||||
|
||||
加密方法:
|
||||
|
||||
* `aes-128-ctr`
|
||||
* `aes-192-ctr`
|
||||
* `aes-256-ctr`
|
||||
* `aes-128-cfb`
|
||||
* `aes-192-cfb`
|
||||
* `aes-256-cfb`
|
||||
* `rc4-md5`
|
||||
* `chacha20-ietf`
|
||||
* `xchacha20`
|
||||
|
||||
#### password
|
||||
|
||||
==必填==
|
||||
|
||||
Shadowsocks 密码。
|
||||
|
||||
#### obfs
|
||||
|
||||
ShadowsocksR 混淆。
|
||||
|
||||
* plain
|
||||
* http_simple
|
||||
* http_post
|
||||
* random_head
|
||||
* tls1.2_ticket_auth
|
||||
|
||||
#### obfs_param
|
||||
|
||||
ShadowsocksR 混淆参数。
|
||||
|
||||
#### protocol
|
||||
|
||||
ShadowsocksR 协议。
|
||||
|
||||
* origin
|
||||
* verify_sha1
|
||||
* auth_sha1_v4
|
||||
* auth_aes128_md5
|
||||
* auth_aes128_sha1
|
||||
* auth_chain_a
|
||||
* auth_chain_b
|
||||
|
||||
#### protocol_param
|
||||
|
||||
ShadowsocksR 协议参数。
|
||||
|
||||
#### network
|
||||
|
||||
启用的网络协议
|
||||
|
||||
`tcp` 或 `udp`。
|
||||
|
||||
默认所有。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||
70
docs/configuration/outbound/vless.md
Normal file
70
docs/configuration/outbound/vless.md
Normal file
@@ -0,0 +1,70 @@
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
"transport": {},
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
The VLESS protocol is architecturally coupled to v2ray and is unmaintained. This outbound is provided for compatibility purposes only.
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
The server address.
|
||||
|
||||
#### server_port
|
||||
|
||||
==Required==
|
||||
|
||||
The server port.
|
||||
|
||||
#### uuid
|
||||
|
||||
==Required==
|
||||
|
||||
The VLESS user id.
|
||||
|
||||
#### network
|
||||
|
||||
Enabled network
|
||||
|
||||
One of `tcp` `udp`.
|
||||
|
||||
Both is enabled by default.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
#### packet_encoding
|
||||
|
||||
| Encoding | Description |
|
||||
|------------|-----------------------|
|
||||
| (none) | Disabled |
|
||||
| packetaddr | Supported by v2ray 5+ |
|
||||
| xudp | Supported by xray |
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial) for details.
|
||||
70
docs/configuration/outbound/vless.zh.md
Normal file
70
docs/configuration/outbound/vless.zh.md
Normal file
@@ -0,0 +1,70 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
"transport": {},
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
VLESS 协议与 v2ray 架构耦合且无人维护。 提供此出站仅出于兼容性目的。
|
||||
|
||||
### 字段
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
服务器地址。
|
||||
|
||||
#### server_port
|
||||
|
||||
==必填==
|
||||
|
||||
服务器端口。
|
||||
|
||||
#### uuid
|
||||
|
||||
==必填==
|
||||
|
||||
VLESS 用户 ID。
|
||||
|
||||
#### network
|
||||
|
||||
启用的网络协议。
|
||||
|
||||
`tcp` 或 `udp`。
|
||||
|
||||
默认所有。
|
||||
|
||||
#### tls
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||
|
||||
#### packet_encoding
|
||||
|
||||
| 编码 | 描述 |
|
||||
|------------|---------------|
|
||||
| (空) | 禁用 |
|
||||
| packetaddr | 由 v2ray 5+ 支持 |
|
||||
| xudp | 由 xray 支持 |
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||
@@ -27,6 +27,7 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat
|
||||
| `with_quic` | Build with QUIC support, see [QUIC and HTTP3 dns transports](./configuration/dns/server), [Naive inbound](./configuration/inbound/naive), [Hysteria Inbound](./configuration/inbound/hysteria), [Hysteria Outbound](./configuration/outbound/hysteria) and [V2Ray Transport#QUIC](./configuration/shared/v2ray-transport#quic). |
|
||||
| `with_grpc` | Build with standard gRPC support, see [V2Ray Transport#gRPC](./configuration/shared/v2ray-transport#grpc). |
|
||||
| `with_wireguard` | Build with WireGuard support, see [WireGuard outbound](./configuration/outbound/wireguard). |
|
||||
| `with_shadowsocksr` | Build with ShadowsocksR support, see [ShadowsocksR outbound](./configuration/outbound/shadowsocksr). |
|
||||
| `with_ech` | Build with TLS ECH extension support for TLS outbound, see [TLS](./configuration/shared/tls#ech). |
|
||||
| `with_utls` | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](./configuration/shared/tls#utls). |
|
||||
| `with_acme` | Build with ACME TLS certificate issuer support, see [TLS](./configuration/shared/tls). |
|
||||
|
||||
@@ -27,6 +27,7 @@ go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@lat
|
||||
| `with_quic` | 启用 QUIC 支持,参阅 [QUIC 和 HTTP3 DNS 传输层](./configuration/dns/server),[Naive 入站](./configuration/inbound/naive),[Hysteria 入站](./configuration/inbound/hysteria),[Hysteria 出站](./configuration/outbound/hysteria) 和 [V2Ray 传输层#QUIC](./configuration/shared/v2ray-transport#quic)。 |
|
||||
| `with_grpc` | 启用标准 gRPC 支持,参阅 [V2Ray 传输层#gRPC](./configuration/shared/v2ray-transport#grpc)。 |
|
||||
| `with_wireguard` | 启用 WireGuard 支持,参阅 [WireGuard 出站](./configuration/outbound/wireguard)。 |
|
||||
| `with_shadowsocksr` | 启用 ShadowsocksR 支持,参阅 [ShadowsocksR 出站](./configuration/outbound/shadowsocksr)。 |
|
||||
| `with_ech` | 启用 TLS ECH 扩展支持,参阅 [TLS](./configuration/shared/tls#ech)。 |
|
||||
| `with_utls` | 启用 [uTLS](https://github.com/refraction-networking/utls) 支持, 参阅 [TLS](./configuration/shared/tls#utls)。 |
|
||||
| `with_acme` | 启用 ACME TLS 证书签发支持,参阅 [TLS](./configuration/shared/tls)。 |
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/websocket"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func connectionRouter(trafficManager *trafficontrol.Manager) http.Handler {
|
||||
|
||||
@@ -22,11 +22,11 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/websocket"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var _ adapter.ClashServer = (*Server)(nil)
|
||||
|
||||
11
go.mod
11
go.mod
@@ -13,7 +13,6 @@ require (
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/gofrs/uuid v4.3.0+incompatible
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/yamux v0.1.1
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mholt/acmez v1.0.4
|
||||
@@ -23,12 +22,14 @@ require (
|
||||
github.com/refraction-networking/utls v1.1.2
|
||||
github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a
|
||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
|
||||
github.com/sagernet/sing v0.0.0-20220910144724-62c4ebdbcb3f
|
||||
github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0
|
||||
github.com/sagernet/sing v0.0.0-20220913004915-27ddefbb8921
|
||||
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||
github.com/sagernet/sing-tun v0.0.0-20220909114108-a6b5a9289ecb
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f
|
||||
github.com/sagernet/smux v0.0.0-20220907034654-1acb8471c15a
|
||||
github.com/sagernet/sing-tun v0.0.0-20220911034209-c7dd5d457e24
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12
|
||||
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
|
||||
github.com/stretchr/testify v1.8.0
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
|
||||
25
go.sum
25
go.sum
@@ -77,8 +77,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
@@ -143,20 +141,24 @@ 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-20220910144724-62c4ebdbcb3f h1:w1TJq7Lw3It35tDyMsZLtYz4T2msf1UK9JxC85L5+sk=
|
||||
github.com/sagernet/sing v0.0.0-20220910144724-62c4ebdbcb3f/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
|
||||
github.com/sagernet/sing v0.0.0-20220913004915-27ddefbb8921 h1:xUHzlIbdlPV/fkToIO9futp9lmKIY+72ezk/whQ8XsI=
|
||||
github.com/sagernet/sing v0.0.0-20220913004915-27ddefbb8921/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666 h1:XUTocA/Ek0dFxUX+xJCWMPPFZCn2GC/uLrBjTSr1vHY=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666/go.mod h1:eDyH7AJmqBGjZQdQmpZIzlbTREudZuWDExMuGKgjRVM=
|
||||
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-20220909114108-a6b5a9289ecb h1:/swVU2mgwDwZ9l67v1Sim1ias/ZmriGTxQLnMakPhtQ=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220909114108-a6b5a9289ecb/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f h1:6l9aXZqAl1JqXJWi89KHpWnM/moQUPGG+XiwMc+yD0A=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
|
||||
github.com/sagernet/smux v0.0.0-20220907034654-1acb8471c15a h1:GCNwsN8MEckpjGJjK3qjQBQ9qHsoXB9B/KHUWBvE1V4=
|
||||
github.com/sagernet/smux v0.0.0-20220907034654-1acb8471c15a/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220911034209-c7dd5d457e24 h1:LsmPeFvj4GhiV5Y7Rm8I845XysdxVN4MQmfZ36P5bmw=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220911034209-c7dd5d457e24/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/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/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@@ -192,6 +194,7 @@ 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=
|
||||
@@ -245,6 +248,7 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -264,6 +268,7 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho=
|
||||
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/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=
|
||||
|
||||
@@ -3,7 +3,6 @@ package inbound
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/settings"
|
||||
@@ -42,7 +41,6 @@ type myInboundAdapter struct {
|
||||
tcpListener net.Listener
|
||||
udpConn *net.UDPConn
|
||||
udpAddr M.Socksaddr
|
||||
packetAccess sync.RWMutex
|
||||
packetOutboundClosed chan struct{}
|
||||
packetOutbound chan *myInboundPacket
|
||||
}
|
||||
|
||||
@@ -208,17 +208,12 @@ func (s *myInboundPacketAdapter) Upstream() any {
|
||||
}
|
||||
|
||||
func (s *myInboundPacketAdapter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
s.packetAccess.RLock()
|
||||
defer s.packetAccess.RUnlock()
|
||||
|
||||
select {
|
||||
case s.packetOutbound <- &myInboundPacket{buffer, destination}:
|
||||
return nil
|
||||
case <-s.packetOutboundClosed:
|
||||
return os.ErrClosed
|
||||
default:
|
||||
}
|
||||
|
||||
s.packetOutbound <- &myInboundPacket{buffer, destination}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *myInboundPacketAdapter) Close() error {
|
||||
|
||||
@@ -255,12 +255,6 @@ func (h *Hysteria) acceptStream(ctx context.Context, conn quic.Connection, strea
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = hysteria.WriteServerResponse(stream, hysteria.ServerResponse{
|
||||
OK: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Inbound = h.tag
|
||||
metadata.InboundType = C.TypeHysteria
|
||||
@@ -270,9 +264,16 @@ func (h *Hysteria) acceptStream(ctx context.Context, conn quic.Connection, strea
|
||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
|
||||
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr())
|
||||
metadata.Destination = M.ParseSocksaddrHostPort(request.Host, request.Port)
|
||||
|
||||
if !request.UDP {
|
||||
err = hysteria.WriteServerResponse(stream, hysteria.ServerResponse{
|
||||
OK: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
return h.router.RouteConnection(ctx, hysteria.NewConn(stream, metadata.Destination), metadata)
|
||||
return h.router.RouteConnection(ctx, hysteria.NewConn(stream, metadata.Destination, false), metadata)
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||
var id uint32
|
||||
@@ -282,6 +283,13 @@ func (h *Hysteria) acceptStream(ctx context.Context, conn quic.Connection, strea
|
||||
h.udpSessions[id] = nCh
|
||||
h.udpSessionId += 1
|
||||
h.udpAccess.Unlock()
|
||||
err = hysteria.WriteServerResponse(stream, hysteria.ServerResponse{
|
||||
OK: true,
|
||||
UDPSessionID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
packetConn := hysteria.NewPacketConn(conn, stream, id, metadata.Destination, nCh, common.Closer(func() error {
|
||||
h.udpAccess.Lock()
|
||||
if ch, ok := h.udpSessions[id]; ok {
|
||||
|
||||
@@ -84,6 +84,8 @@ nav:
|
||||
- WireGuard: configuration/outbound/wireguard.md
|
||||
- Hysteria: configuration/outbound/hysteria.md
|
||||
- ShadowTLS: configuration/outbound/shadowtls.md
|
||||
- ShadowsocksR: configuration/outbound/shadowsocksr.md
|
||||
- VLESS: configuration/outbound/vless.md
|
||||
- Tor: configuration/outbound/tor.md
|
||||
- SSH: configuration/outbound/ssh.md
|
||||
- DNS: configuration/outbound/dns.md
|
||||
|
||||
@@ -8,20 +8,22 @@ import (
|
||||
)
|
||||
|
||||
type _Outbound struct {
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
DirectOptions DirectOutboundOptions `json:"-"`
|
||||
SocksOptions SocksOutboundOptions `json:"-"`
|
||||
HTTPOptions HTTPOutboundOptions `json:"-"`
|
||||
ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"`
|
||||
VMessOptions VMessOutboundOptions `json:"-"`
|
||||
TrojanOptions TrojanOutboundOptions `json:"-"`
|
||||
WireGuardOptions WireGuardOutboundOptions `json:"-"`
|
||||
HysteriaOptions HysteriaOutboundOptions `json:"-"`
|
||||
TorOptions TorOutboundOptions `json:"-"`
|
||||
SSHOptions SSHOutboundOptions `json:"-"`
|
||||
ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"`
|
||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
DirectOptions DirectOutboundOptions `json:"-"`
|
||||
SocksOptions SocksOutboundOptions `json:"-"`
|
||||
HTTPOptions HTTPOutboundOptions `json:"-"`
|
||||
ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"`
|
||||
VMessOptions VMessOutboundOptions `json:"-"`
|
||||
TrojanOptions TrojanOutboundOptions `json:"-"`
|
||||
WireGuardOptions WireGuardOutboundOptions `json:"-"`
|
||||
HysteriaOptions HysteriaOutboundOptions `json:"-"`
|
||||
TorOptions TorOutboundOptions `json:"-"`
|
||||
SSHOptions SSHOutboundOptions `json:"-"`
|
||||
ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"`
|
||||
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
|
||||
VLESSOptions VLESSOutboundOptions `json:"-"`
|
||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||
}
|
||||
|
||||
type Outbound _Outbound
|
||||
@@ -53,6 +55,10 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
|
||||
v = h.SSHOptions
|
||||
case C.TypeShadowTLS:
|
||||
v = h.ShadowTLSOptions
|
||||
case C.TypeShadowsocksR:
|
||||
v = h.ShadowsocksROptions
|
||||
case C.TypeVLESS:
|
||||
v = h.VLESSOptions
|
||||
case C.TypeSelector:
|
||||
v = h.SelectorOptions
|
||||
default:
|
||||
@@ -92,6 +98,10 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
||||
v = &h.SSHOptions
|
||||
case C.TypeShadowTLS:
|
||||
v = &h.ShadowTLSOptions
|
||||
case C.TypeShadowsocksR:
|
||||
v = &h.ShadowsocksROptions
|
||||
case C.TypeVLESS:
|
||||
v = &h.VLESSOptions
|
||||
case C.TypeSelector:
|
||||
v = &h.SelectorOptions
|
||||
default:
|
||||
|
||||
@@ -26,6 +26,8 @@ type ShadowsocksOutboundOptions struct {
|
||||
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"`
|
||||
UoT bool `json:"udp_over_tcp,omitempty"`
|
||||
MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"`
|
||||
|
||||
13
option/shadowsocksr.go
Normal file
13
option/shadowsocksr.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package option
|
||||
|
||||
type ShadowsocksROutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
Method string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Obfs string `json:"obfs,omitempty"`
|
||||
ObfsParam string `json:"obfs_param,omitempty"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
ProtocolParam string `json:"protocol_param,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
}
|
||||
11
option/vless.go
Normal file
11
option/vless.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package option
|
||||
|
||||
type VLESSOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
UUID string `json:"uuid"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
PacketEncoding string `json:"packet_encoding,omitempty"`
|
||||
}
|
||||
@@ -41,6 +41,10 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
||||
return NewSSH(ctx, router, logger, options.Tag, options.SSHOptions)
|
||||
case C.TypeShadowTLS:
|
||||
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
|
||||
case C.TypeShadowsocksR:
|
||||
return NewShadowsocksR(ctx, router, logger, options.Tag, options.ShadowsocksROptions)
|
||||
case C.TypeVLESS:
|
||||
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
||||
case C.TypeSelector:
|
||||
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
||||
default:
|
||||
|
||||
@@ -276,16 +276,7 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination
|
||||
stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
response, err := hysteria.ReadServerResponse(stream)
|
||||
if err != nil {
|
||||
stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
if !response.OK {
|
||||
stream.Close()
|
||||
return nil, E.New("remote error: ", response.Message)
|
||||
}
|
||||
return hysteria.NewConn(stream, destination), nil
|
||||
return hysteria.NewConn(stream, destination, true), nil
|
||||
case N.NetworkUDP:
|
||||
conn, err := h.ListenPacket(ctx, destination)
|
||||
if err != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/sip003"
|
||||
"github.com/sagernet/sing-shadowsocks"
|
||||
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
||||
"github.com/sagernet/sing/common"
|
||||
@@ -27,6 +28,7 @@ type Shadowsocks struct {
|
||||
dialer N.Dialer
|
||||
method shadowsocks.Method
|
||||
serverAddr M.Socksaddr
|
||||
plugin sip003.Plugin
|
||||
uot bool
|
||||
multiplexDialer N.Dialer
|
||||
}
|
||||
@@ -49,6 +51,12 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
||||
serverAddr: options.ServerOptions.Build(),
|
||||
uot: options.UoT,
|
||||
}
|
||||
if options.Plugin != "" {
|
||||
outbound.plugin, err = sip003.CreatePlugin(options.Plugin, options.PluginOptions, router, outbound.dialer, outbound.serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !options.UoT {
|
||||
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
|
||||
if err != nil {
|
||||
@@ -135,7 +143,13 @@ type shadowsocksDialer Shadowsocks
|
||||
func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
outConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||
var outConn net.Conn
|
||||
var err error
|
||||
if h.plugin != nil {
|
||||
outConn, err = h.plugin.DialContext(ctx)
|
||||
} else {
|
||||
outConn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
140
outbound/shadowsocksr.go
Normal file
140
outbound/shadowsocksr.go
Normal file
@@ -0,0 +1,140 @@
|
||||
//go:build with_shadowsocksr
|
||||
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/shadowsocksr"
|
||||
"github.com/sagernet/shadowsocksr/obfs"
|
||||
"github.com/sagernet/shadowsocksr/protocol"
|
||||
"github.com/sagernet/shadowsocksr/ssr"
|
||||
"github.com/sagernet/shadowsocksr/streamCipher"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-shadowsocks"
|
||||
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
||||
"github.com/sagernet/sing/common"
|
||||
"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"
|
||||
)
|
||||
|
||||
var _ adapter.Outbound = (*ShadowsocksR)(nil)
|
||||
|
||||
type ShadowsocksR struct {
|
||||
myOutboundAdapter
|
||||
dialer N.Dialer
|
||||
serverAddr M.Socksaddr
|
||||
method shadowsocks.Method
|
||||
cipher string
|
||||
password string
|
||||
obfs string
|
||||
obfsParams *ssr.ServerInfo
|
||||
protocol string
|
||||
protocolParams *ssr.ServerInfo
|
||||
}
|
||||
|
||||
func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (*ShadowsocksR, error) {
|
||||
outbound := &ShadowsocksR{
|
||||
myOutboundAdapter: myOutboundAdapter{
|
||||
protocol: C.TypeShadowsocksR,
|
||||
network: options.Network.Build(),
|
||||
router: router,
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
},
|
||||
dialer: dialer.New(router, options.DialerOptions),
|
||||
serverAddr: options.ServerOptions.Build(),
|
||||
cipher: options.Method,
|
||||
password: options.Password,
|
||||
obfs: options.Obfs,
|
||||
protocol: options.Protocol,
|
||||
}
|
||||
var err error
|
||||
outbound.method, err = shadowimpl.FetchMethod(options.Method, options.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = streamCipher.NewStreamCipher(options.Method, options.Password); err != nil {
|
||||
return nil, E.New(strings.ToLower(err.Error()))
|
||||
}
|
||||
if obfs.NewObfs(options.Obfs) == nil {
|
||||
return nil, E.New("unknown obfs: " + options.Obfs)
|
||||
}
|
||||
outbound.obfsParams = &ssr.ServerInfo{
|
||||
Host: outbound.serverAddr.AddrString(),
|
||||
Port: outbound.serverAddr.Port,
|
||||
TcpMss: 1460,
|
||||
Param: options.ObfsParam,
|
||||
}
|
||||
if protocol.NewProtocol(options.Protocol) == nil {
|
||||
return nil, E.New("unknown protocol: " + options.Protocol)
|
||||
}
|
||||
outbound.protocolParams = &ssr.ServerInfo{
|
||||
Host: outbound.serverAddr.AddrString(),
|
||||
Port: outbound.serverAddr.Port,
|
||||
TcpMss: 1460,
|
||||
Param: options.Protocol,
|
||||
}
|
||||
if outbound.method == nil {
|
||||
outbound.network = common.Filter(outbound.network, func(it string) bool { return it == N.NetworkTCP })
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (h *ShadowsocksR) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
switch network {
|
||||
case N.NetworkTCP:
|
||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||
conn, err := h.dialer.DialContext(ctx, network, h.serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cipher, err := streamCipher.NewStreamCipher(h.cipher, h.password)
|
||||
if err != nil {
|
||||
return nil, E.New(strings.ToLower(err.Error()))
|
||||
}
|
||||
ssConn := shadowsocksr.NewSSTCPConn(conn, cipher)
|
||||
ssConn.IObfs = obfs.NewObfs(h.obfs)
|
||||
ssConn.IObfs.SetServerInfo(h.obfsParams)
|
||||
ssConn.IProtocol = protocol.NewProtocol(h.protocol)
|
||||
ssConn.IProtocol.SetServerInfo(h.protocolParams)
|
||||
err = M.SocksaddrSerializer.WriteAddrPort(ssConn, destination)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "write request")
|
||||
}
|
||||
return ssConn, nil
|
||||
case N.NetworkUDP:
|
||||
conn, err := h.ListenPacket(ctx, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &bufio.BindPacketConn{PacketConn: conn, Addr: destination}, nil
|
||||
default:
|
||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ShadowsocksR) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||
outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.method.DialPacketConn(outConn), nil
|
||||
}
|
||||
|
||||
func (h *ShadowsocksR) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
return NewConnection(ctx, h, conn, metadata)
|
||||
}
|
||||
|
||||
func (h *ShadowsocksR) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return NewPacketConnection(ctx, h, conn, metadata)
|
||||
}
|
||||
16
outbound/shadowsocksr_stub.go
Normal file
16
outbound/shadowsocksr_stub.go
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build !with_shadowsocksr
|
||||
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) {
|
||||
return nil, E.New(`ShadowsocksR is not included in this build, rebuild with -tags with_shadowsocksr`)
|
||||
}
|
||||
@@ -60,7 +60,11 @@ func (s *ShadowTLS) DialContext(ctx context.Context, network string, destination
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tls.ClientHandshake(ctx, conn, s.tlsConfig)
|
||||
_, err = tls.ClientHandshake(ctx, conn, s.tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (s *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
|
||||
@@ -20,8 +20,9 @@ var _ adapter.Outbound = (*Socks)(nil)
|
||||
|
||||
type Socks struct {
|
||||
myOutboundAdapter
|
||||
client *socks.Client
|
||||
uot bool
|
||||
client *socks.Client
|
||||
resolve bool
|
||||
uot bool
|
||||
}
|
||||
|
||||
func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) {
|
||||
@@ -45,6 +46,7 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio
|
||||
tag: tag,
|
||||
},
|
||||
socks.NewClient(detour, options.ServerOptions.Build(), version, options.Username, options.Password),
|
||||
version == socks.Version4,
|
||||
options.UoT,
|
||||
}, nil
|
||||
}
|
||||
@@ -72,6 +74,13 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S
|
||||
default:
|
||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||
}
|
||||
if destination.IsFqdn() {
|
||||
addrs, err := h.router.LookupDefault(ctx, destination.Fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return N.DialSerial(ctx, h.client, network, destination, addrs)
|
||||
}
|
||||
return h.client.DialContext(ctx, network, destination)
|
||||
}
|
||||
|
||||
|
||||
146
outbound/vless.go
Normal file
146
outbound/vless.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"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-vmess/packetaddr"
|
||||
"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"
|
||||
)
|
||||
|
||||
var _ adapter.Outbound = (*VLESS)(nil)
|
||||
|
||||
type VLESS struct {
|
||||
myOutboundAdapter
|
||||
dialer N.Dialer
|
||||
client *vless.Client
|
||||
serverAddr M.Socksaddr
|
||||
tlsConfig tls.Config
|
||||
transport adapter.V2RayClientTransport
|
||||
packetAddr bool
|
||||
xudp bool
|
||||
}
|
||||
|
||||
func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) {
|
||||
outbound := &VLESS{
|
||||
myOutboundAdapter: myOutboundAdapter{
|
||||
protocol: C.TypeVLESS,
|
||||
network: options.Network.Build(),
|
||||
router: router,
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
},
|
||||
dialer: dialer.New(router, options.DialerOptions),
|
||||
serverAddr: options.ServerOptions.Build(),
|
||||
}
|
||||
var err error
|
||||
if options.TLS != nil {
|
||||
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if options.Transport != nil {
|
||||
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
|
||||
}
|
||||
}
|
||||
switch options.PacketEncoding {
|
||||
case "":
|
||||
case "packetaddr":
|
||||
outbound.packetAddr = true
|
||||
case "xudp":
|
||||
outbound.xudp = true
|
||||
default:
|
||||
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
||||
}
|
||||
outbound.client, err = vless.NewClient(options.UUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (h *VLESS) Close() error {
|
||||
return common.Close(h.transport)
|
||||
}
|
||||
|
||||
func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
ctx, metadata := adapter.AppendContext(ctx)
|
||||
metadata.Outbound = h.tag
|
||||
metadata.Destination = destination
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if h.transport != nil {
|
||||
conn, err = h.transport.DialContext(ctx)
|
||||
} else {
|
||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||
if err == nil && h.tlsConfig != nil {
|
||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
case N.NetworkUDP:
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||
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
|
||||
default:
|
||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||
ctx, metadata := adapter.AppendContext(ctx)
|
||||
metadata.Outbound = h.tag
|
||||
metadata.Destination = destination
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if h.transport != nil {
|
||||
conn, err = h.transport.DialContext(ctx)
|
||||
} else {
|
||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||
if err == nil && h.tlsConfig != nil {
|
||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if h.xudp {
|
||||
return h.client.DialXUDPPacketConn(conn, destination), nil
|
||||
} else if h.packetAddr {
|
||||
return packetaddr.NewConn(h.client.DialPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil
|
||||
} else {
|
||||
return h.client.DialPacketConn(conn, destination), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
return NewEarlyConnection(ctx, h, conn, metadata)
|
||||
}
|
||||
|
||||
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return NewPacketConnection(ctx, h, conn, metadata)
|
||||
}
|
||||
@@ -53,8 +53,8 @@ func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
|
||||
dialUDP := func() (net.PacketConn, error) {
|
||||
return dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", testPort))
|
||||
}
|
||||
// require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||
// require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
||||
require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||
require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
||||
require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
|
||||
require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
|
||||
|
||||
@@ -80,6 +80,22 @@ func testSuitSimple(t *testing.T, clientPort uint16, testPort uint16) {
|
||||
}
|
||||
require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||
require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
||||
require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||
require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
||||
}
|
||||
|
||||
func testSuitSimple1(t *testing.T, clientPort uint16, testPort uint16) {
|
||||
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
|
||||
dialTCP := func() (net.Conn, error) {
|
||||
return dialer.DialContext(context.Background(), "tcp", M.ParseSocksaddrHostPort("127.0.0.1", testPort))
|
||||
}
|
||||
dialUDP := func() (net.PacketConn, error) {
|
||||
return dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", testPort))
|
||||
}
|
||||
require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||
require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
||||
require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||
require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
|
||||
}
|
||||
|
||||
func testSuitWg(t *testing.T, clientPort uint16, testPort uint16) {
|
||||
@@ -94,8 +110,8 @@ func testSuitWg(t *testing.T, clientPort uint16, testPort uint16) {
|
||||
}
|
||||
return bufio.NewUnbindPacketConn(conn), nil
|
||||
}
|
||||
require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||
require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
||||
require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
|
||||
require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
|
||||
// require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||
// require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ const (
|
||||
ImageHysteria = "tobyxdd/hysteria:latest"
|
||||
ImageNginx = "nginx:stable"
|
||||
ImageShadowTLS = "ghcr.io/ihciah/shadow-tls:latest"
|
||||
ImageShadowsocksR = "teddysun/shadowsocks-r:latest"
|
||||
ImageXRayCore = "teddysun/xray:latest"
|
||||
)
|
||||
|
||||
var allImages = []string{
|
||||
@@ -49,6 +51,8 @@ var allImages = []string{
|
||||
ImageHysteria,
|
||||
ImageNginx,
|
||||
ImageShadowTLS,
|
||||
ImageShadowsocksR,
|
||||
ImageXRayCore,
|
||||
}
|
||||
|
||||
var localIP = netip.MustParseAddr("127.0.0.1")
|
||||
@@ -385,21 +389,24 @@ func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.Pack
|
||||
hashMap := map[int][]byte{}
|
||||
mux := sync.Mutex{}
|
||||
for i := 0; i < times; i++ {
|
||||
buf := make([]byte, chunkSize)
|
||||
if _, err = rand.Read(buf[1:]); err != nil {
|
||||
t.Log(err.Error())
|
||||
continue
|
||||
}
|
||||
buf[0] = byte(i)
|
||||
go func(idx int) {
|
||||
buf := make([]byte, chunkSize)
|
||||
if _, err := rand.Read(buf[1:]); err != nil {
|
||||
t.Log(err.Error())
|
||||
return
|
||||
}
|
||||
buf[0] = byte(idx)
|
||||
|
||||
hash := md5.Sum(buf)
|
||||
mux.Lock()
|
||||
hashMap[i] = hash[:]
|
||||
mux.Unlock()
|
||||
if _, err = pc.WriteTo(buf, addr); err != nil {
|
||||
t.Log(err)
|
||||
continue
|
||||
}
|
||||
hash := md5.Sum(buf)
|
||||
mux.Lock()
|
||||
hashMap[idx] = hash[:]
|
||||
mux.Unlock()
|
||||
|
||||
if _, err := pc.WriteTo(buf, addr); err != nil {
|
||||
t.Log(err.Error())
|
||||
return
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
return hashMap, nil
|
||||
|
||||
19
test/config/shadowsocksr.json
Normal file
19
test/config/shadowsocksr.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"server": "0.0.0.0",
|
||||
"server_ipv6": "::",
|
||||
"server_port": 10000,
|
||||
"local_address": "127.0.0.1",
|
||||
"local_port": 1080,
|
||||
"password": "password0",
|
||||
"timeout": 120,
|
||||
"method": "aes-256-cfb",
|
||||
"protocol": "origin",
|
||||
"protocol_param": "",
|
||||
"obfs": "plain",
|
||||
"obfs_param": "",
|
||||
"redirect": "",
|
||||
"dns_ipv6": false,
|
||||
"fast_open": true,
|
||||
"workers": 1,
|
||||
"forbidden_ip": ""
|
||||
}
|
||||
25
test/config/vless-server.json
Normal file
25
test/config/vless-server.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "0.0.0.0",
|
||||
"port": 1234,
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"decryption": "none",
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
test/go.mod
23
test/go.mod
@@ -7,14 +7,14 @@ require github.com/sagernet/sing-box v0.0.0
|
||||
replace github.com/sagernet/sing-box => ../
|
||||
|
||||
require (
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/docker/docker v20.10.18+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/gofrs/uuid v4.2.0+incompatible
|
||||
github.com/sagernet/sing v0.0.0-20220905164441-f3d346256d4a
|
||||
github.com/gofrs/uuid v4.3.0+incompatible
|
||||
github.com/sagernet/sing v0.0.0-20220913004915-27ddefbb8921
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||
github.com/spyzhov/ajson v0.7.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
|
||||
)
|
||||
|
||||
//replace github.com/sagernet/sing => ../../sing
|
||||
@@ -39,7 +39,6 @@ require (
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
||||
@@ -66,12 +65,15 @@ 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/sing-dns v0.0.0-20220903082137-b1102b8fc961 // indirect
|
||||
github.com/sagernet/sing-tun v0.0.0-20220909114108-a6b5a9289ecb // indirect
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f // indirect
|
||||
github.com/sagernet/smux v0.0.0-20220907034654-1acb8471c15a // indirect
|
||||
github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 // indirect
|
||||
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666 // indirect
|
||||
github.com/sagernet/sing-tun v0.0.0-20220911034209-c7dd5d457e24 // indirect
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 // 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/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.22.0 // indirect
|
||||
@@ -79,7 +81,7 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
|
||||
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f // indirect
|
||||
@@ -89,7 +91,6 @@ require (
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.3.0 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
||||
|
||||
50
test/go.sum
50
test/go.sum
@@ -32,8 +32,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE=
|
||||
github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc=
|
||||
github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
@@ -57,8 +57,8 @@ github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
|
||||
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -89,8 +89,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
@@ -163,20 +161,24 @@ 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-20220905164441-f3d346256d4a h1:Bqt+eYP7vJocAgAVAXC0B0ZN0uMr6g6exAoF3Ado2pg=
|
||||
github.com/sagernet/sing v0.0.0-20220905164441-f3d346256d4a/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220903082137-b1102b8fc961 h1:5JeqhvCGV6AQQiAO0V67Loh2eyO3JNjIQnvRF8NnTE0=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220903082137-b1102b8fc961/go.mod h1:vKBBy4mNJRaFuJ8H6kYIOPofsZ1JT5mgdwIlebtvnZ4=
|
||||
github.com/sagernet/sing v0.0.0-20220913004915-27ddefbb8921 h1:xUHzlIbdlPV/fkToIO9futp9lmKIY+72ezk/whQ8XsI=
|
||||
github.com/sagernet/sing v0.0.0-20220913004915-27ddefbb8921/go.mod h1:kZvzh1VDa/Dg/Bt5WaYKU0jl5ept8KKDpl3Ay4gRtRQ=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666 h1:XUTocA/Ek0dFxUX+xJCWMPPFZCn2GC/uLrBjTSr1vHY=
|
||||
github.com/sagernet/sing-dns v0.0.0-20220822023312-3e086b06d666/go.mod h1:eDyH7AJmqBGjZQdQmpZIzlbTREudZuWDExMuGKgjRVM=
|
||||
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-20220909114108-a6b5a9289ecb h1:/swVU2mgwDwZ9l67v1Sim1ias/ZmriGTxQLnMakPhtQ=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220909114108-a6b5a9289ecb/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f h1:6l9aXZqAl1JqXJWi89KHpWnM/moQUPGG+XiwMc+yD0A=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220907073918-72d7fdf6825f/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
|
||||
github.com/sagernet/smux v0.0.0-20220907034654-1acb8471c15a h1:GCNwsN8MEckpjGJjK3qjQBQ9qHsoXB9B/KHUWBvE1V4=
|
||||
github.com/sagernet/smux v0.0.0-20220907034654-1acb8471c15a/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220911034209-c7dd5d457e24 h1:LsmPeFvj4GhiV5Y7Rm8I845XysdxVN4MQmfZ36P5bmw=
|
||||
github.com/sagernet/sing-tun v0.0.0-20220911034209-c7dd5d457e24/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/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=
|
||||
@@ -197,6 +199,8 @@ github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
@@ -214,6 +218,7 @@ 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=
|
||||
@@ -251,8 +256,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7 h1:1WGATo9HAhkWMbfyuVU0tEFP88OIkUvwaHFveQPvzCQ=
|
||||
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -275,6 +280,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -290,8 +296,9 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho=
|
||||
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/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=
|
||||
@@ -370,9 +377,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -9,9 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestHysteriaSelf(t *testing.T) {
|
||||
if !C.QUIC_AVAILABLE {
|
||||
t.Skip("QUIC not included")
|
||||
}
|
||||
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||
startInstance(t, option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
@@ -80,13 +77,10 @@ func TestHysteriaSelf(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuitSimple(t, clientPort, testPort)
|
||||
testSuitSimple1(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func TestHysteriaInbound(t *testing.T) {
|
||||
if !C.QUIC_AVAILABLE {
|
||||
t.Skip("QUIC not included")
|
||||
}
|
||||
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||
startInstance(t, option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
@@ -124,9 +118,6 @@ func TestHysteriaInbound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHysteriaOutbound(t *testing.T) {
|
||||
if !C.QUIC_AVAILABLE {
|
||||
t.Skip("QUIC not included")
|
||||
}
|
||||
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||
startDockerContainer(t, DockerOptions{
|
||||
Image: ImageHysteria,
|
||||
@@ -171,5 +162,5 @@ func TestHysteriaOutbound(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuitSimple(t, clientPort, testPort)
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
@@ -99,9 +99,6 @@ func TestNaiveInbound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNaiveHTTP3Inbound(t *testing.T) {
|
||||
if !C.QUIC_AVAILABLE {
|
||||
t.Skip("QUIC not included")
|
||||
}
|
||||
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||
startInstance(t, option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
|
||||
48
test/shadowsocksr_test.go
Normal file
48
test/shadowsocksr_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
func TestShadowsocksR(t *testing.T) {
|
||||
startDockerContainer(t, DockerOptions{
|
||||
Image: ImageShadowsocksR,
|
||||
Ports: []uint16{serverPort, testPort},
|
||||
Bind: map[string]string{
|
||||
"shadowsocksr.json": "/etc/shadowsocks-r/config.json",
|
||||
},
|
||||
})
|
||||
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.TypeShadowsocksR,
|
||||
ShadowsocksROptions: option.ShadowsocksROutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
Method: "aes-256-cfb",
|
||||
Password: "password0",
|
||||
Obfs: "plain",
|
||||
Protocol: "origin",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
@@ -122,3 +122,62 @@ func TestTrojanSelf(t *testing.T) {
|
||||
})
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func TestTrojanPlainSelf(t *testing.T) {
|
||||
startInstance(t, option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
{
|
||||
Type: C.TypeMixed,
|
||||
Tag: "mixed-in",
|
||||
MixedOptions: option.HTTPMixedInboundOptions{
|
||||
ListenOptions: option.ListenOptions{
|
||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||
ListenPort: clientPort,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: C.TypeTrojan,
|
||||
TrojanOptions: option.TrojanInboundOptions{
|
||||
ListenOptions: option.ListenOptions{
|
||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||
ListenPort: serverPort,
|
||||
},
|
||||
Users: []option.TrojanUser{
|
||||
{
|
||||
Name: "sekai",
|
||||
Password: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Outbounds: []option.Outbound{
|
||||
{
|
||||
Type: C.TypeDirect,
|
||||
},
|
||||
{
|
||||
Type: C.TypeTrojan,
|
||||
Tag: "trojan-out",
|
||||
TrojanOptions: option.TrojanOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
Password: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
Route: &option.RouteOptions{
|
||||
Rules: []option.Rule{
|
||||
{
|
||||
DefaultOptions: option.DefaultRule{
|
||||
Inbound: []string{"mixed-in"},
|
||||
Outbound: "trojan-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) {
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuitSimple(t, clientPort, testPort)
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func TestV2RayGRPCLite(t *testing.T) {
|
||||
|
||||
@@ -266,7 +266,7 @@ func TestVMessQUICSelf(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuitSimple(t, clientPort, testPort)
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportOptions) {
|
||||
@@ -330,5 +330,5 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuitSimple(t, clientPort, testPort)
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
@@ -193,5 +193,5 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuitSimple(t, clientPort, testPort)
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
120
test/vless_test.go
Normal file
120
test/vless_test.go
Normal file
@@ -0,0 +1,120 @@
|
||||
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 TestVLESS(t *testing.T) {
|
||||
content, err := os.ReadFile("config/vless-server.json")
|
||||
require.NoError(t, err)
|
||||
config, err := ajson.Unmarshal(content)
|
||||
require.NoError(t, err)
|
||||
|
||||
user := newUUID()
|
||||
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: ImageV2RayCore,
|
||||
Ports: []uint16{serverPort, testPort},
|
||||
EntryPoint: "v2ray",
|
||||
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.TypeVLESS,
|
||||
VLESSOptions: option.VLESSOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
UUID: user.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func TestVLESSXRay(t *testing.T) {
|
||||
testVLESSXray(t, "")
|
||||
}
|
||||
|
||||
func TestVLESSXUDP(t *testing.T) {
|
||||
testVLESSXray(t, "xudp")
|
||||
}
|
||||
|
||||
func testVLESSXray(t *testing.T, packetEncoding string) {
|
||||
content, err := os.ReadFile("config/vless-server.json")
|
||||
require.NoError(t, err)
|
||||
config, err := ajson.Unmarshal(content)
|
||||
require.NoError(t, err)
|
||||
|
||||
user := newUUID()
|
||||
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.TypeVLESS,
|
||||
VLESSOptions: option.VLESSOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
UUID: user.String(),
|
||||
PacketEncoding: packetEncoding,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
@@ -13,21 +13,24 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newUUID() uuid.UUID {
|
||||
user, _ := uuid.DefaultGenerator.NewV4()
|
||||
return user
|
||||
}
|
||||
|
||||
func _TestVMessAuto(t *testing.T) {
|
||||
security := "auto"
|
||||
user, err := uuid.DefaultGenerator.NewV4()
|
||||
require.NoError(t, err)
|
||||
t.Run("self", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, false, false, false)
|
||||
testVMessSelf(t, security, 0, false, false, false)
|
||||
})
|
||||
t.Run("packetaddr", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, false, false, true)
|
||||
testVMessSelf(t, security, 0, false, false, true)
|
||||
})
|
||||
t.Run("inbound", func(t *testing.T) {
|
||||
testVMessInboundWithV2Ray(t, security, user, 0, false)
|
||||
testVMessInboundWithV2Ray(t, security, 0, false)
|
||||
})
|
||||
t.Run("outbound", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
|
||||
testVMessOutboundWithV2Ray(t, security, false, false, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,102 +59,97 @@ func TestVMess(t *testing.T) {
|
||||
}
|
||||
|
||||
func testVMess0(t *testing.T, security string) {
|
||||
user, err := uuid.DefaultGenerator.NewV4()
|
||||
require.NoError(t, err)
|
||||
t.Run("self", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, false, false, false)
|
||||
testVMessSelf(t, security, 0, false, false, false)
|
||||
})
|
||||
t.Run("self-legacy", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 1, false, false, false)
|
||||
testVMessSelf(t, security, 1, false, false, false)
|
||||
})
|
||||
t.Run("packetaddr", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, false, false, true)
|
||||
testVMessSelf(t, security, 0, false, false, true)
|
||||
})
|
||||
t.Run("outbound", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
|
||||
testVMessOutboundWithV2Ray(t, security, false, false, 0)
|
||||
})
|
||||
t.Run("outbound-legacy", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, false, false, 1)
|
||||
testVMessOutboundWithV2Ray(t, security, false, false, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func testVMess1(t *testing.T, security string) {
|
||||
user, err := uuid.DefaultGenerator.NewV4()
|
||||
require.NoError(t, err)
|
||||
t.Run("self", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, false, false, false)
|
||||
testVMessSelf(t, security, 0, false, false, false)
|
||||
})
|
||||
t.Run("self-legacy", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 1, false, false, false)
|
||||
testVMessSelf(t, security, 1, false, false, false)
|
||||
})
|
||||
t.Run("packetaddr", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, false, false, true)
|
||||
testVMessSelf(t, security, 0, false, false, true)
|
||||
})
|
||||
t.Run("inbound", func(t *testing.T) {
|
||||
testVMessInboundWithV2Ray(t, security, user, 0, false)
|
||||
testVMessInboundWithV2Ray(t, security, 0, false)
|
||||
})
|
||||
t.Run("outbound", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
|
||||
testVMessOutboundWithV2Ray(t, security, false, false, 0)
|
||||
})
|
||||
t.Run("outbound-legacy", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, false, false, 1)
|
||||
testVMessOutboundWithV2Ray(t, security, false, false, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func testVMess2(t *testing.T, security string) {
|
||||
user, err := uuid.DefaultGenerator.NewV4()
|
||||
require.NoError(t, err)
|
||||
t.Run("self", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, false, false, false)
|
||||
testVMessSelf(t, security, 0, false, false, false)
|
||||
})
|
||||
t.Run("self-padding", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, true, false, false)
|
||||
testVMessSelf(t, security, 0, true, false, false)
|
||||
})
|
||||
t.Run("self-authid", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, false, true, false)
|
||||
testVMessSelf(t, security, 0, false, true, false)
|
||||
})
|
||||
t.Run("self-padding-authid", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, true, true, false)
|
||||
testVMessSelf(t, security, 0, true, true, false)
|
||||
})
|
||||
t.Run("self-legacy", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 1, false, false, false)
|
||||
testVMessSelf(t, security, 1, false, false, false)
|
||||
})
|
||||
t.Run("self-legacy-padding", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 1, true, false, false)
|
||||
testVMessSelf(t, security, 1, true, false, false)
|
||||
})
|
||||
t.Run("packetaddr", func(t *testing.T) {
|
||||
testVMessSelf(t, security, user, 0, false, false, true)
|
||||
testVMessSelf(t, security, 0, false, false, true)
|
||||
})
|
||||
t.Run("inbound", func(t *testing.T) {
|
||||
testVMessInboundWithV2Ray(t, security, user, 0, false)
|
||||
testVMessInboundWithV2Ray(t, security, 0, false)
|
||||
})
|
||||
t.Run("inbound-authid", func(t *testing.T) {
|
||||
testVMessInboundWithV2Ray(t, security, user, 0, true)
|
||||
testVMessInboundWithV2Ray(t, security, 0, true)
|
||||
})
|
||||
t.Run("inbound-legacy", func(t *testing.T) {
|
||||
testVMessInboundWithV2Ray(t, security, user, 64, false)
|
||||
testVMessInboundWithV2Ray(t, security, 64, false)
|
||||
})
|
||||
t.Run("outbound", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
|
||||
testVMessOutboundWithV2Ray(t, security, false, false, 0)
|
||||
})
|
||||
t.Run("outbound-padding", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, true, false, 0)
|
||||
testVMessOutboundWithV2Ray(t, security, true, false, 0)
|
||||
})
|
||||
t.Run("outbound-authid", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, false, true, 0)
|
||||
testVMessOutboundWithV2Ray(t, security, false, true, 0)
|
||||
})
|
||||
t.Run("outbound-padding-authid", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, true, true, 0)
|
||||
testVMessOutboundWithV2Ray(t, security, true, true, 0)
|
||||
})
|
||||
t.Run("outbound-legacy", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, false, false, 1)
|
||||
testVMessOutboundWithV2Ray(t, security, false, false, 1)
|
||||
})
|
||||
t.Run("outbound-legacy-padding", func(t *testing.T) {
|
||||
testVMessOutboundWithV2Ray(t, security, user, true, false, 1)
|
||||
testVMessOutboundWithV2Ray(t, security, true, false, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, alterId int, authenticatedLength bool) {
|
||||
func testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authenticatedLength bool) {
|
||||
userId := newUUID()
|
||||
content, err := os.ReadFile("config/vmess-client.json")
|
||||
require.NoError(t, err)
|
||||
config, err := ajson.Unmarshal(content)
|
||||
@@ -161,7 +159,7 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, al
|
||||
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(uuid.String())
|
||||
user.MustKey("id").SetString(userId.String())
|
||||
user.MustKey("alterId").SetNumeric(float64(alterId))
|
||||
user.MustKey("security").SetString(security)
|
||||
var experiments string
|
||||
@@ -193,7 +191,7 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, al
|
||||
Users: []option.VMessUser{
|
||||
{
|
||||
Name: "sekai",
|
||||
UUID: uuid.String(),
|
||||
UUID: userId.String(),
|
||||
AlterId: alterId,
|
||||
},
|
||||
},
|
||||
@@ -205,7 +203,8 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, al
|
||||
testSuitSimple(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, globalPadding bool, authenticatedLength bool, alterId int) {
|
||||
func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding bool, authenticatedLength bool, alterId int) {
|
||||
user := newUUID()
|
||||
content, err := os.ReadFile("config/vmess-server.json")
|
||||
require.NoError(t, err)
|
||||
config, err := ajson.Unmarshal(content)
|
||||
@@ -213,7 +212,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
|
||||
|
||||
inbound := config.MustKey("inbounds").MustIndex(0)
|
||||
inbound.MustKey("port").SetNumeric(float64(serverPort))
|
||||
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(uuid.String())
|
||||
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(user.String())
|
||||
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("alterId").SetNumeric(float64(alterId))
|
||||
|
||||
content, err = ajson.Marshal(config)
|
||||
@@ -248,7 +247,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
Security: security,
|
||||
UUID: uuid.String(),
|
||||
UUID: user.String(),
|
||||
GlobalPadding: globalPadding,
|
||||
AuthenticatedLength: authenticatedLength,
|
||||
AlterId: alterId,
|
||||
@@ -256,10 +255,11 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuitSimple(t, clientPort, testPort)
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) {
|
||||
func testVMessSelf(t *testing.T, security string, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) {
|
||||
user := newUUID()
|
||||
startInstance(t, option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
{
|
||||
@@ -282,7 +282,7 @@ func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, g
|
||||
Users: []option.VMessUser{
|
||||
{
|
||||
Name: "sekai",
|
||||
UUID: uuid.String(),
|
||||
UUID: user.String(),
|
||||
AlterId: alterId,
|
||||
},
|
||||
},
|
||||
@@ -302,7 +302,7 @@ func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, g
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
Security: security,
|
||||
UUID: uuid.String(),
|
||||
UUID: user.String(),
|
||||
AlterId: alterId,
|
||||
GlobalPadding: globalPadding,
|
||||
AuthenticatedLength: authenticatedLength,
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -50,49 +49,3 @@ func TestWireGuard(t *testing.T) {
|
||||
})
|
||||
testSuitWg(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func TestWireGuardSystem(t *testing.T) {
|
||||
if os.Getuid() != 0 {
|
||||
t.Skip("requires root")
|
||||
}
|
||||
startDockerContainer(t, DockerOptions{
|
||||
Image: ImageBoringTun,
|
||||
Cap: []string{"MKNOD", "NET_ADMIN", "NET_RAW"},
|
||||
Ports: []uint16{serverPort, testPort},
|
||||
Bind: map[string]string{
|
||||
"wireguard.conf": "/etc/wireguard/wg0.conf",
|
||||
},
|
||||
Cmd: []string{"wg0"},
|
||||
})
|
||||
time.Sleep(5 * time.Second)
|
||||
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.TypeWireGuard,
|
||||
WireGuardOptions: option.WireGuardOutboundOptions{
|
||||
InterfaceName: "wg",
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
LocalAddress: []option.ListenPrefix{option.ListenPrefix(netip.MustParsePrefix("10.0.0.2/32"))},
|
||||
PrivateKey: "qGnwlkZljMxeECW8fbwAWdvgntnbK7B8UmMFl3zM0mk=",
|
||||
PeerPublicKey: "QsdcBm+oJw2oNv0cIFXLIq1E850lgTBonup4qnKEQBg=",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
time.Sleep(10 * time.Second)
|
||||
testSuitWg(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
@@ -374,17 +374,35 @@ var _ net.Conn = (*Conn)(nil)
|
||||
|
||||
type Conn struct {
|
||||
quic.Stream
|
||||
destination M.Socksaddr
|
||||
responseWritten bool
|
||||
destination M.Socksaddr
|
||||
needReadResponse bool
|
||||
}
|
||||
|
||||
func NewConn(stream quic.Stream, destination M.Socksaddr) *Conn {
|
||||
func NewConn(stream quic.Stream, destination M.Socksaddr, isClient bool) *Conn {
|
||||
return &Conn{
|
||||
Stream: stream,
|
||||
destination: destination,
|
||||
Stream: stream,
|
||||
destination: destination,
|
||||
needReadResponse: isClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Read(p []byte) (n int, err error) {
|
||||
if c.needReadResponse {
|
||||
var response *ServerResponse
|
||||
response, err = ReadServerResponse(c.Stream)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
if !response.OK {
|
||||
c.Close()
|
||||
return 0, E.New("remote error: ", response.Message)
|
||||
}
|
||||
c.needReadResponse = false
|
||||
}
|
||||
return c.Stream.Read(p)
|
||||
}
|
||||
|
||||
func (c *Conn) LocalAddr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
@@ -394,7 +412,7 @@ func (c *Conn) RemoteAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (c *Conn) ReaderReplaceable() bool {
|
||||
return true
|
||||
return !c.needReadResponse
|
||||
}
|
||||
|
||||
func (c *Conn) WriterReplaceable() bool {
|
||||
|
||||
4
transport/simple-obfs/README.md
Normal file
4
transport/simple-obfs/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# simple-obfs
|
||||
|
||||
mod from https://github.com/Dreamacro/clash/transport/simple-obfs
|
||||
version: 1.11.8
|
||||
94
transport/simple-obfs/http.go
Normal file
94
transport/simple-obfs/http.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
B "github.com/sagernet/sing/common/buf"
|
||||
)
|
||||
|
||||
// HTTPObfs is shadowsocks http simple-obfs implementation
|
||||
type HTTPObfs struct {
|
||||
net.Conn
|
||||
host string
|
||||
port string
|
||||
buf []byte
|
||||
offset int
|
||||
firstRequest bool
|
||||
firstResponse bool
|
||||
}
|
||||
|
||||
func (ho *HTTPObfs) Read(b []byte) (int, error) {
|
||||
if ho.buf != nil {
|
||||
n := copy(b, ho.buf[ho.offset:])
|
||||
ho.offset += n
|
||||
if ho.offset == len(ho.buf) {
|
||||
B.Put(ho.buf)
|
||||
ho.buf = nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
if ho.firstResponse {
|
||||
buf := B.Get(B.BufferSize)
|
||||
n, err := ho.Conn.Read(buf)
|
||||
if err != nil {
|
||||
B.Put(buf)
|
||||
return 0, err
|
||||
}
|
||||
idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
|
||||
if idx == -1 {
|
||||
B.Put(buf)
|
||||
return 0, io.EOF
|
||||
}
|
||||
ho.firstResponse = false
|
||||
length := n - (idx + 4)
|
||||
n = copy(b, buf[idx+4:n])
|
||||
if length > n {
|
||||
ho.buf = buf[:idx+4+length]
|
||||
ho.offset = idx + 4 + n
|
||||
} else {
|
||||
B.Put(buf)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
return ho.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (ho *HTTPObfs) Write(b []byte) (int, error) {
|
||||
if ho.firstRequest {
|
||||
randBytes := make([]byte, 16)
|
||||
rand.Read(randBytes)
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
|
||||
req.Header.Set("Upgrade", "websocket")
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
req.Host = ho.host
|
||||
if ho.port != "80" {
|
||||
req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port)
|
||||
}
|
||||
req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes))
|
||||
req.ContentLength = int64(len(b))
|
||||
err := req.Write(ho.Conn)
|
||||
ho.firstRequest = false
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
return ho.Conn.Write(b)
|
||||
}
|
||||
|
||||
// NewHTTPObfs return a HTTPObfs
|
||||
func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn {
|
||||
return &HTTPObfs{
|
||||
Conn: conn,
|
||||
firstRequest: true,
|
||||
firstResponse: true,
|
||||
host: host,
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
200
transport/simple-obfs/tls.go
Normal file
200
transport/simple-obfs/tls.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
B "github.com/sagernet/sing/common/buf"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
||||
|
||||
const (
|
||||
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
|
||||
)
|
||||
|
||||
// TLSObfs is shadowsocks tls simple-obfs implementation
|
||||
type TLSObfs struct {
|
||||
net.Conn
|
||||
server string
|
||||
remain int
|
||||
firstRequest bool
|
||||
firstResponse bool
|
||||
}
|
||||
|
||||
func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
|
||||
buf := B.Get(discardN)
|
||||
_, err := io.ReadFull(to.Conn, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
B.Put(buf)
|
||||
|
||||
sizeBuf := make([]byte, 2)
|
||||
_, err = io.ReadFull(to.Conn, sizeBuf)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
length := int(binary.BigEndian.Uint16(sizeBuf))
|
||||
if length > len(b) {
|
||||
n, err := to.Conn.Read(b)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
to.remain = length - n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
return io.ReadFull(to.Conn, b[:length])
|
||||
}
|
||||
|
||||
func (to *TLSObfs) Read(b []byte) (int, error) {
|
||||
if to.remain > 0 {
|
||||
length := to.remain
|
||||
if length > len(b) {
|
||||
length = len(b)
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(to.Conn, b[:length])
|
||||
to.remain -= n
|
||||
return n, err
|
||||
}
|
||||
|
||||
if to.firstResponse {
|
||||
// type + ver + lensize + 91 = 96
|
||||
// type + ver + lensize + 1 = 6
|
||||
// type + ver = 3
|
||||
to.firstResponse = false
|
||||
return to.read(b, 105)
|
||||
}
|
||||
|
||||
// type + ver = 3
|
||||
return to.read(b, 3)
|
||||
}
|
||||
|
||||
func (to *TLSObfs) Write(b []byte) (int, error) {
|
||||
length := len(b)
|
||||
for i := 0; i < length; i += chunkSize {
|
||||
end := i + chunkSize
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
|
||||
n, err := to.write(b[i:end])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
|
||||
func (to *TLSObfs) write(b []byte) (int, error) {
|
||||
if to.firstRequest {
|
||||
helloMsg := makeClientHelloMsg(b, to.server)
|
||||
_, err := to.Conn.Write(helloMsg)
|
||||
to.firstRequest = false
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
buf := B.NewSize(5 + len(b))
|
||||
defer buf.Release()
|
||||
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(b)))
|
||||
buf.Write(b)
|
||||
_, err := to.Conn.Write(buf.Bytes())
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
// NewTLSObfs return a SimpleObfs
|
||||
func NewTLSObfs(conn net.Conn, server string) net.Conn {
|
||||
return &TLSObfs{
|
||||
Conn: conn,
|
||||
server: server,
|
||||
firstRequest: true,
|
||||
firstResponse: true,
|
||||
}
|
||||
}
|
||||
|
||||
func makeClientHelloMsg(data []byte, server string) []byte {
|
||||
random := make([]byte, 28)
|
||||
sessionID := make([]byte, 32)
|
||||
rand.Read(random)
|
||||
rand.Read(sessionID)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// handshake, TLS 1.0 version, length
|
||||
buf.WriteByte(22)
|
||||
buf.Write([]byte{0x03, 0x01})
|
||||
length := uint16(212 + len(data) + len(server))
|
||||
buf.WriteByte(byte(length >> 8))
|
||||
buf.WriteByte(byte(length & 0xff))
|
||||
|
||||
// clientHello, length, TLS 1.2 version
|
||||
buf.WriteByte(1)
|
||||
buf.WriteByte(0)
|
||||
binary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server)))
|
||||
buf.Write([]byte{0x03, 0x03})
|
||||
|
||||
// random with timestamp, sid len, sid
|
||||
binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
|
||||
buf.Write(random)
|
||||
buf.WriteByte(32)
|
||||
buf.Write(sessionID)
|
||||
|
||||
// cipher suites
|
||||
buf.Write([]byte{0x00, 0x38})
|
||||
buf.Write([]byte{
|
||||
0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f,
|
||||
0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a,
|
||||
0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d,
|
||||
0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff,
|
||||
})
|
||||
|
||||
// compression
|
||||
buf.Write([]byte{0x01, 0x00})
|
||||
|
||||
// extension length
|
||||
binary.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server)))
|
||||
|
||||
// session ticket
|
||||
buf.Write([]byte{0x00, 0x23})
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(data)))
|
||||
buf.Write(data)
|
||||
|
||||
// server name
|
||||
buf.Write([]byte{0x00, 0x00})
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(server)+5))
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(server)+3))
|
||||
buf.WriteByte(0)
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(server)))
|
||||
buf.Write([]byte(server))
|
||||
|
||||
// ec_point
|
||||
buf.Write([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02})
|
||||
|
||||
// groups
|
||||
buf.Write([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18})
|
||||
|
||||
// signature
|
||||
buf.Write([]byte{
|
||||
0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05,
|
||||
0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01,
|
||||
0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03,
|
||||
})
|
||||
|
||||
// encrypt then mac
|
||||
buf.Write([]byte{0x00, 0x16, 0x00, 0x00})
|
||||
|
||||
// extended master secret
|
||||
buf.Write([]byte{0x00, 0x17, 0x00, 0x00})
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
119
transport/sip003/args.go
Normal file
119
transport/sip003/args.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package sip003
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// mod from https://github.com/shadowsocks/v2ray-plugin/blob/master/args.go
|
||||
|
||||
// Args maps a string key to a list of values. It is similar to url.Values.
|
||||
type Args map[string][]string
|
||||
|
||||
// Get the first value associated with the given key. If there are any values
|
||||
// associated with the key, the value return has the value and ok is set to
|
||||
// true. If there are no values for the given key, value is "" and ok is false.
|
||||
// If you need access to multiple values, use the map directly.
|
||||
func (args Args) Get(key string) (value string, ok bool) {
|
||||
if args == nil {
|
||||
return "", false
|
||||
}
|
||||
vals, ok := args[key]
|
||||
if !ok || len(vals) == 0 {
|
||||
return "", false
|
||||
}
|
||||
return vals[0], true
|
||||
}
|
||||
|
||||
// Add Append value to the list of values for key.
|
||||
func (args Args) Add(key, value string) {
|
||||
args[key] = append(args[key], value)
|
||||
}
|
||||
|
||||
// Return the index of the next unescaped byte in s that is in the term set, or
|
||||
// else the length of the string if no terminators appear. Additionally return
|
||||
// the unescaped string up to the returned index.
|
||||
func indexUnescaped(s string, term []byte) (int, string, error) {
|
||||
var i int
|
||||
unesc := make([]byte, 0)
|
||||
for i = 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
// A terminator byte?
|
||||
if bytes.IndexByte(term, b) != -1 {
|
||||
break
|
||||
}
|
||||
if b == '\\' {
|
||||
i++
|
||||
if i >= len(s) {
|
||||
return 0, "", fmt.Errorf("nothing following final escape in %q", s)
|
||||
}
|
||||
b = s[i]
|
||||
}
|
||||
unesc = append(unesc, b)
|
||||
}
|
||||
return i, string(unesc), nil
|
||||
}
|
||||
|
||||
// ParsePluginOptions Parse a name–value mapping as from SS_PLUGIN_OPTIONS.
|
||||
//
|
||||
// "<value> is a k=v string value with options that are to be passed to the
|
||||
// transport. semicolons, equal signs and backslashes must be escaped
|
||||
// with a backslash."
|
||||
// Example: secret=nou;cache=/tmp/cache;secret=yes
|
||||
func ParsePluginOptions(s string) (opts Args, err error) {
|
||||
opts = make(Args)
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
i := 0
|
||||
for {
|
||||
var key, value string
|
||||
var offset, begin int
|
||||
|
||||
if i >= len(s) {
|
||||
break
|
||||
}
|
||||
begin = i
|
||||
// Read the key.
|
||||
offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(key) == 0 {
|
||||
err = fmt.Errorf("empty key in %q", s[begin:i])
|
||||
return
|
||||
}
|
||||
i += offset
|
||||
// End of string or no equals sign?
|
||||
if i >= len(s) || s[i] != '=' {
|
||||
opts.Add(key, "1")
|
||||
// Skip the semicolon.
|
||||
i++
|
||||
continue
|
||||
}
|
||||
// Skip the equals sign.
|
||||
i++
|
||||
// Read the value.
|
||||
offset, value, err = indexUnescaped(s[i:], []byte{';'})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
i += offset
|
||||
opts.Add(key, value)
|
||||
// Skip the semicolon.
|
||||
i++
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// Escape backslashes and all the bytes that are in set.
|
||||
func backslashEscape(s string, set []byte) string {
|
||||
var buf bytes.Buffer
|
||||
for _, b := range []byte(s) {
|
||||
if b == '\\' || bytes.IndexByte(set, b) != -1 {
|
||||
buf.WriteByte('\\')
|
||||
}
|
||||
buf.WriteByte(b)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
59
transport/sip003/obfs.go
Normal file
59
transport/sip003/obfs.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package sip003
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/transport/simple-obfs"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var _ Plugin = (*ObfsLocal)(nil)
|
||||
|
||||
func init() {
|
||||
RegisterPlugin("obfs-local", newObfsLocal)
|
||||
}
|
||||
|
||||
func newObfsLocal(pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
|
||||
var plugin ObfsLocal
|
||||
mode := "http"
|
||||
if obfsMode, loaded := pluginOpts.Get("obfs"); loaded {
|
||||
mode = obfsMode
|
||||
}
|
||||
if obfsHost, loaded := pluginOpts.Get("obfs-host"); loaded {
|
||||
plugin.host = obfsHost
|
||||
}
|
||||
switch mode {
|
||||
case "http":
|
||||
case "tls":
|
||||
plugin.tls = true
|
||||
default:
|
||||
return nil, E.New("unknown obfs mode ", mode)
|
||||
}
|
||||
plugin.port = F.ToString(serverAddr.Port)
|
||||
return &plugin, nil
|
||||
}
|
||||
|
||||
type ObfsLocal struct {
|
||||
dialer N.Dialer
|
||||
serverAddr M.Socksaddr
|
||||
tls bool
|
||||
host string
|
||||
port string
|
||||
}
|
||||
|
||||
func (o *ObfsLocal) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
conn, err := o.dialer.DialContext(ctx, N.NetworkTCP, o.serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !o.tls {
|
||||
return obfs.NewHTTPObfs(conn, o.host, o.port), nil
|
||||
} else {
|
||||
return obfs.NewTLSObfs(conn, o.host), nil
|
||||
}
|
||||
}
|
||||
38
transport/sip003/plugin.go
Normal file
38
transport/sip003/plugin.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package sip003
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type PluginConstructor func(pluginArgs Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error)
|
||||
|
||||
type Plugin interface {
|
||||
DialContext(ctx context.Context) (net.Conn, error)
|
||||
}
|
||||
|
||||
var plugins map[string]PluginConstructor
|
||||
|
||||
func RegisterPlugin(name string, constructor PluginConstructor) {
|
||||
if plugins == nil {
|
||||
plugins = make(map[string]PluginConstructor)
|
||||
}
|
||||
plugins[name] = constructor
|
||||
}
|
||||
|
||||
func CreatePlugin(name string, pluginArgs string, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
|
||||
pluginOptions, err := ParsePluginOptions(pluginArgs)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse plugin_opts")
|
||||
}
|
||||
constructor, loaded := plugins[name]
|
||||
if !loaded {
|
||||
return nil, E.New("plugin not found: ", name)
|
||||
}
|
||||
return constructor(pluginOptions, router, dialer, serverAddr)
|
||||
}
|
||||
80
transport/sip003/v2ray.go
Normal file
80
transport/sip003/v2ray.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package sip003
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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-box/transport/v2ray"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterPlugin("v2ray-plugin", newV2RayPlugin)
|
||||
}
|
||||
|
||||
func newV2RayPlugin(pluginOpts Args, router adapter.Router, dialer N.Dialer, serverAddr M.Socksaddr) (Plugin, error) {
|
||||
var tlsOptions option.OutboundTLSOptions
|
||||
if _, loaded := pluginOpts.Get("tls"); loaded {
|
||||
tlsOptions.Enabled = true
|
||||
}
|
||||
if certPath, certLoaded := pluginOpts.Get("cert"); certLoaded {
|
||||
tlsOptions.CertificatePath = certPath
|
||||
}
|
||||
if certRaw, certLoaded := pluginOpts.Get("certRaw"); certLoaded {
|
||||
certHead := "-----BEGIN CERTIFICATE-----"
|
||||
certTail := "-----END CERTIFICATE-----"
|
||||
fixedCert := certHead + "\n" + certRaw + "\n" + certTail
|
||||
tlsOptions.Certificate = fixedCert
|
||||
}
|
||||
|
||||
mode := "websocket"
|
||||
if modeOpt, loaded := pluginOpts.Get("mode"); loaded {
|
||||
mode = modeOpt
|
||||
}
|
||||
|
||||
host := "cloudfront.com"
|
||||
path := "/"
|
||||
|
||||
if hostOpt, loaded := pluginOpts.Get("host"); loaded {
|
||||
host = hostOpt
|
||||
}
|
||||
if pathOpt, loaded := pluginOpts.Get("path"); loaded {
|
||||
path = pathOpt
|
||||
}
|
||||
|
||||
var tlsClient tls.Config
|
||||
var err error
|
||||
if tlsOptions.Enabled {
|
||||
tlsClient, err = tls.NewClient(router, serverAddr.AddrString(), tlsOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var transportOptions option.V2RayTransportOptions
|
||||
switch mode {
|
||||
case "websocket":
|
||||
transportOptions = option.V2RayTransportOptions{
|
||||
Type: C.V2RayTransportTypeWebsocket,
|
||||
WebsocketOptions: option.V2RayWebsocketOptions{
|
||||
Headers: map[string]string{
|
||||
"Host": host,
|
||||
},
|
||||
Path: path,
|
||||
},
|
||||
}
|
||||
case "quic":
|
||||
transportOptions = option.V2RayTransportOptions{
|
||||
Type: C.V2RayTransportTypeQUIC,
|
||||
}
|
||||
default:
|
||||
return nil, E.New("v2ray-plugin: unknown mode: " + mode)
|
||||
}
|
||||
|
||||
return v2ray.NewClientTransport(context.Background(), dialer, serverAddr, transportOptions, tlsClient)
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayClientTransport = (*Client)(nil)
|
||||
@@ -27,27 +29,37 @@ type Client struct {
|
||||
ctx context.Context
|
||||
dialer N.Dialer
|
||||
serverAddr M.Socksaddr
|
||||
transport *http.Transport
|
||||
transport http.RoundTripper
|
||||
options option.V2RayGRPCOptions
|
||||
url *url.URL
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {
|
||||
return &Client{
|
||||
ctx: ctx,
|
||||
dialer: dialer,
|
||||
serverAddr: serverAddr,
|
||||
options: options,
|
||||
transport: &http.Transport{
|
||||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
var transport http.RoundTripper
|
||||
if tlsConfig == nil {
|
||||
transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||
transport = &http2.Transport{
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tls.ClientHandshake(ctx, conn, tlsConfig)
|
||||
},
|
||||
ForceAttemptHTTP2: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
return &Client{
|
||||
ctx: ctx,
|
||||
dialer: dialer,
|
||||
serverAddr: serverAddr,
|
||||
options: options,
|
||||
transport: transport,
|
||||
url: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: serverAddr.String(),
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/baderror"
|
||||
@@ -28,6 +29,7 @@ type GunConn struct {
|
||||
create chan struct{}
|
||||
err error
|
||||
readRemaining int
|
||||
writeAccess sync.Mutex
|
||||
}
|
||||
|
||||
func newGunConn(reader io.Reader, writer io.Writer, flusher http.Flusher) *GunConn {
|
||||
@@ -100,7 +102,9 @@ func (c *GunConn) Write(b []byte) (n int, err error) {
|
||||
grpcHeader := buf.Get(5)
|
||||
grpcPayloadLen := uint32(1 + varuintLen + len(b))
|
||||
binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen)
|
||||
c.writeAccess.Lock()
|
||||
_, err = bufio.Copy(c.writer, io.MultiReader(bytes.NewReader(grpcHeader), bytes.NewReader(protobufHeader[:varuintLen+1]), bytes.NewReader(b)))
|
||||
c.writeAccess.Unlock()
|
||||
buf.Put(grpcHeader)
|
||||
if c.flusher != nil {
|
||||
c.flusher.Flush()
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
sHttp "github.com/sagernet/sing/protocol/http"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayServerTransport = (*Server)(nil)
|
||||
@@ -87,6 +89,10 @@ func (s *Server) Serve(listener net.Listener) error {
|
||||
if s.httpServer.TLSConfig == nil {
|
||||
return s.httpServer.Serve(listener)
|
||||
} else {
|
||||
err := http2.ConfigureServer(s.httpServer, &http2.Server{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.httpServer.ServeTLS(listener, "", "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayClientTransport = (*Client)(nil)
|
||||
@@ -24,7 +26,7 @@ type Client struct {
|
||||
ctx context.Context
|
||||
dialer N.Dialer
|
||||
serverAddr M.Socksaddr
|
||||
client *http.Client
|
||||
transport http.RoundTripper
|
||||
http2 bool
|
||||
url *url.URL
|
||||
host []string
|
||||
@@ -33,6 +35,25 @@ type Client struct {
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {
|
||||
var transport http.RoundTripper
|
||||
if tlsConfig == nil {
|
||||
transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||
transport = &http2.Transport{
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tls.ClientHandshake(ctx, conn, tlsConfig)
|
||||
},
|
||||
}
|
||||
}
|
||||
client := &Client{
|
||||
ctx: ctx,
|
||||
dialer: dialer,
|
||||
@@ -40,27 +61,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
host: options.Host,
|
||||
method: options.Method,
|
||||
headers: make(http.Header),
|
||||
client: &http.Client{},
|
||||
transport: transport,
|
||||
http2: tlsConfig != nil,
|
||||
}
|
||||
if client.http2 {
|
||||
client.client.Transport = &http.Transport{
|
||||
DialTLSContext: 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 tls.ClientHandshake(ctx, conn, tlsConfig)
|
||||
},
|
||||
ForceAttemptHTTP2: true,
|
||||
}
|
||||
} else {
|
||||
client.client.Transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
}
|
||||
}
|
||||
if client.method == "" {
|
||||
client.method = "PUT"
|
||||
}
|
||||
@@ -145,17 +148,16 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
|
||||
}
|
||||
// Disable any compression method from server.
|
||||
request.Header.Set("Accept-Encoding", "identity")
|
||||
response, err := c.client.Do(request) // nolint: bodyclose
|
||||
if err != nil {
|
||||
pipeInWriter.Close()
|
||||
return nil, err
|
||||
}
|
||||
if response.StatusCode != 200 {
|
||||
pipeInWriter.Close()
|
||||
return nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status)
|
||||
}
|
||||
return &HTTPConn{
|
||||
response.Body,
|
||||
pipeInWriter,
|
||||
}, nil
|
||||
conn := newLateHTTPConn(pipeInWriter)
|
||||
go func() {
|
||||
response, err := c.transport.RoundTrip(request)
|
||||
if err != nil {
|
||||
conn.setup(nil, err)
|
||||
} else if response.StatusCode != 200 {
|
||||
conn.setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status))
|
||||
} else {
|
||||
conn.setup(response.Body, nil)
|
||||
}
|
||||
}()
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
@@ -14,9 +14,37 @@ import (
|
||||
type HTTPConn struct {
|
||||
reader io.Reader
|
||||
writer io.Writer
|
||||
create chan struct{}
|
||||
err error
|
||||
}
|
||||
|
||||
func newHTTPConn(reader io.Reader, writer io.Writer) HTTPConn {
|
||||
return HTTPConn{
|
||||
reader: reader,
|
||||
writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
func newLateHTTPConn(writer io.Writer) *HTTPConn {
|
||||
return &HTTPConn{
|
||||
create: make(chan struct{}),
|
||||
writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HTTPConn) setup(reader io.Reader, err error) {
|
||||
c.reader = reader
|
||||
c.err = err
|
||||
close(c.create)
|
||||
}
|
||||
|
||||
func (c *HTTPConn) Read(b []byte) (n int, err error) {
|
||||
if c.reader == nil {
|
||||
<-c.create
|
||||
if c.err != nil {
|
||||
return 0, c.err
|
||||
}
|
||||
}
|
||||
n, err = c.reader.Read(b)
|
||||
return n, baderror.WrapH2(err)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
sHttp "github.com/sagernet/sing/protocol/http"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayServerTransport = (*Server)(nil)
|
||||
@@ -110,10 +112,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
s.handler.NewConnection(request.Context(), conn, metadata)
|
||||
} else {
|
||||
conn := &ServerHTTPConn{
|
||||
HTTPConn{
|
||||
request.Body,
|
||||
writer,
|
||||
},
|
||||
newHTTPConn(request.Body, writer),
|
||||
writer.(http.Flusher),
|
||||
}
|
||||
s.handler.NewConnection(request.Context(), conn, metadata)
|
||||
@@ -128,6 +127,10 @@ func (s *Server) Serve(listener net.Listener) error {
|
||||
if s.httpServer.TLSConfig == nil {
|
||||
return s.httpServer.Serve(listener)
|
||||
} else {
|
||||
err := http2.ConfigureServer(s.httpServer, &http2.Server{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.httpServer.ServeTLS(listener, "", "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sagernet/websocket"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayClientTransport = (*Client)(nil)
|
||||
|
||||
@@ -11,8 +11,7 @@ import (
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sagernet/websocket"
|
||||
)
|
||||
|
||||
type WebsocketConn struct {
|
||||
|
||||
@@ -19,8 +19,7 @@ import (
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
sHttp "github.com/sagernet/sing/protocol/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sagernet/websocket"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayServerTransport = (*Server)(nil)
|
||||
|
||||
144
transport/vless/client.go
Normal file
144
transport/vless/client.go
Normal file
@@ -0,0 +1,144 @@
|
||||
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
|
||||
}
|
||||
104
transport/vless/protocol.go
Normal file
104
transport/vless/protocol.go
Normal file
@@ -0,0 +1,104 @@
|
||||
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
|
||||
}
|
||||
174
transport/vless/xudp.go
Normal file
174
transport/vless/xudp.go
Normal file
@@ -0,0 +1,174 @@
|
||||
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