mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-25 01:51:29 +00:00
Add hysteria and acme TLS certificate issuer (#18)
* Add hysteria client/server * Add acme TLS certificate issuer
This commit is contained in:
parent
3dfa99efe1
commit
d1c3dd0ee1
|
@ -20,11 +20,7 @@ type TLSDialer struct {
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
func TLSConfig(serverAddress string, options option.OutboundTLSOptions) (*tls.Config, error) {
|
||||||
if !options.Enabled {
|
|
||||||
return dialer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
|
@ -105,9 +101,20 @@ func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOpt
|
||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
|
return &tlsConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||||
|
if !options.Enabled {
|
||||||
|
return dialer, nil
|
||||||
|
}
|
||||||
|
tlsConfig, err := TLSConfig(serverAddress, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &TLSDialer{
|
return &TLSDialer{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
config: &tlsConfig,
|
config: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ const (
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
|
TypeHysteria = "hysteria"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
138
docs/configuration/inbound/hysteria.md
Normal file
138
docs/configuration/inbound/hysteria.md
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "hysteria",
|
||||||
|
"tag": "hysteria-in",
|
||||||
|
|
||||||
|
"listen": "::",
|
||||||
|
"listen_port": 443,
|
||||||
|
"sniff": false,
|
||||||
|
"sniff_override_destination": false,
|
||||||
|
"domain_strategy": "prefer_ipv6",
|
||||||
|
|
||||||
|
"up": "100 Mbps",
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down": "100 Mbps",
|
||||||
|
"down_mbps": 100,
|
||||||
|
"obfs": "fuck me till the daylight",
|
||||||
|
"auth": "",
|
||||||
|
"auth_str": "password",
|
||||||
|
"recv_window_conn": 0,
|
||||||
|
"recv_window_client": 0,
|
||||||
|
"max_conn_client": 0,
|
||||||
|
"disable_mtu_discovery": false,
|
||||||
|
"tls": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
QUIC, which is required by hysteria is not included by default, see [Installation](/#Installation).
|
||||||
|
|
||||||
|
### Listen Fields
|
||||||
|
|
||||||
|
#### listen
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Listen address.
|
||||||
|
|
||||||
|
#### listen_port
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Listen port.
|
||||||
|
|
||||||
|
#### sniff
|
||||||
|
|
||||||
|
Enable sniffing.
|
||||||
|
|
||||||
|
See [Sniff](/configuration/route/sniff/) for details.
|
||||||
|
|
||||||
|
#### sniff_override_destination
|
||||||
|
|
||||||
|
Override the connection destination address with the sniffed domain.
|
||||||
|
|
||||||
|
If the domain name is invalid (like tor), this will not work.
|
||||||
|
|
||||||
|
#### domain_strategy
|
||||||
|
|
||||||
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
|
|
||||||
|
If set, the requested domain name will be resolved to IP before routing.
|
||||||
|
|
||||||
|
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
|
||||||
|
|
||||||
|
### Hysteria Fields
|
||||||
|
|
||||||
|
#### up, down
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Format: `[Integer] [Unit]` e.g. `100 Mbps, 640 KBps, 2 Gbps`
|
||||||
|
|
||||||
|
Supported units (case sensitive, b = bits, B = bytes, 8b=1B):
|
||||||
|
|
||||||
|
bps (bits per second)
|
||||||
|
Bps (bytes per second)
|
||||||
|
Kbps (kilobits per second)
|
||||||
|
KBps (kilobytes per second)
|
||||||
|
Mbps (megabits per second)
|
||||||
|
MBps (megabytes per second)
|
||||||
|
Gbps (gigabits per second)
|
||||||
|
GBps (gigabytes per second)
|
||||||
|
Tbps (terabits per second)
|
||||||
|
TBps (terabytes per second)
|
||||||
|
|
||||||
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
`up, down` in Mbps.
|
||||||
|
|
||||||
|
#### obfs
|
||||||
|
|
||||||
|
Obfuscated password.
|
||||||
|
|
||||||
|
#### auth
|
||||||
|
|
||||||
|
Authentication password, in base64.
|
||||||
|
|
||||||
|
#### auth_str
|
||||||
|
|
||||||
|
Authentication password.
|
||||||
|
|
||||||
|
#### recv_window_conn
|
||||||
|
|
||||||
|
The QUIC stream-level flow control window for receiving data.
|
||||||
|
|
||||||
|
`15728640 (15 MB/s)` will be used if empty.
|
||||||
|
|
||||||
|
#### recv_window_client
|
||||||
|
|
||||||
|
The QUIC connection-level flow control window for receiving data.
|
||||||
|
|
||||||
|
`67108864 (64 MB/s)` will be used if empty.
|
||||||
|
|
||||||
|
#### max_conn_client
|
||||||
|
|
||||||
|
The maximum number of QUIC concurrent bidirectional streams that a peer is allowed to open.
|
||||||
|
|
||||||
|
`1024` will be used if empty.
|
||||||
|
|
||||||
|
#### disable_mtu_discovery
|
||||||
|
|
||||||
|
Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.
|
||||||
|
|
||||||
|
Force enabled on for systems other than Linux and Windows (according to upstream).
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
TLS configuration, see [TLS inbound structure](/configuration/shared/tls/#inbound-structure).
|
|
@ -23,6 +23,7 @@
|
||||||
| `vmess` | [VMess](./vmess) |
|
| `vmess` | [VMess](./vmess) |
|
||||||
| `trojan` | [Trojan](./trojan) |
|
| `trojan` | [Trojan](./trojan) |
|
||||||
| `naive` | [Naive](./naive) |
|
| `naive` | [Naive](./naive) |
|
||||||
|
| `hysteria` | [Hysteria](./hysteria) |
|
||||||
| `tun` | [Tun](./tun) |
|
| `tun` | [Tun](./tun) |
|
||||||
| `redirect` | [Redirect](./redirect) |
|
| `redirect` | [Redirect](./redirect) |
|
||||||
| `tproxy` | [TProxy](./tproxy) |
|
| `tproxy` | [TProxy](./tproxy) |
|
||||||
|
|
173
docs/configuration/outbound/hysteria.md
Normal file
173
docs/configuration/outbound/hysteria.md
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "hysteria",
|
||||||
|
"tag": "hysteria-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
|
||||||
|
"up": "100 Mbps",
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down": "100 Mbps",
|
||||||
|
"down_mbps": 100,
|
||||||
|
"obfs": "fuck me till the daylight",
|
||||||
|
"auth": "",
|
||||||
|
"auth_str": "password",
|
||||||
|
"recv_window_conn": 0,
|
||||||
|
"recv_window": 0,
|
||||||
|
"disable_mtu_discovery": false,
|
||||||
|
"network": "tcp",
|
||||||
|
"tls": {},
|
||||||
|
|
||||||
|
"detour": "upstream-out",
|
||||||
|
"bind_interface": "en0",
|
||||||
|
"routing_mark": 1234,
|
||||||
|
"reuse_addr": false,
|
||||||
|
"connect_timeout": "5s",
|
||||||
|
"domain_strategy": "prefer_ipv6",
|
||||||
|
"fallback_delay": "300ms"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
QUIC, which is required by hysteria is not included by default, see [Installation](/#Installation).
|
||||||
|
|
||||||
|
### Hysteria Fields
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The server address.
|
||||||
|
|
||||||
|
#### server_port
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The server port.
|
||||||
|
|
||||||
|
#### up, down
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Format: `[Integer] [Unit]` e.g. `100 Mbps, 640 KBps, 2 Gbps`
|
||||||
|
|
||||||
|
Supported units (case sensitive, b = bits, B = bytes, 8b=1B):
|
||||||
|
|
||||||
|
bps (bits per second)
|
||||||
|
Bps (bytes per second)
|
||||||
|
Kbps (kilobits per second)
|
||||||
|
KBps (kilobytes per second)
|
||||||
|
Mbps (megabits per second)
|
||||||
|
MBps (megabytes per second)
|
||||||
|
Gbps (gigabits per second)
|
||||||
|
GBps (gigabytes per second)
|
||||||
|
Tbps (terabits per second)
|
||||||
|
TBps (terabytes per second)
|
||||||
|
|
||||||
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
`up, down` in Mbps.
|
||||||
|
|
||||||
|
#### obfs
|
||||||
|
|
||||||
|
Obfuscated password.
|
||||||
|
|
||||||
|
#### auth
|
||||||
|
|
||||||
|
Authentication password, in base64.
|
||||||
|
|
||||||
|
#### auth_str
|
||||||
|
|
||||||
|
Authentication password.
|
||||||
|
|
||||||
|
#### recv_window_conn
|
||||||
|
|
||||||
|
The QUIC stream-level flow control window for receiving data.
|
||||||
|
|
||||||
|
`15728640 (15 MB/s)` will be used if empty.
|
||||||
|
|
||||||
|
#### recv_window
|
||||||
|
|
||||||
|
The QUIC connection-level flow control window for receiving data.
|
||||||
|
|
||||||
|
`67108864 (64 MB/s)` will be used if empty.
|
||||||
|
|
||||||
|
#### disable_mtu_discovery
|
||||||
|
|
||||||
|
Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.
|
||||||
|
|
||||||
|
Force enabled on for systems other than Linux and Windows (according to upstream).
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
TLS configuration, see [TLS inbound structure](/configuration/shared/tls/#inbound-structure).
|
||||||
|
|
||||||
|
#### network
|
||||||
|
|
||||||
|
Enabled network
|
||||||
|
|
||||||
|
One of `tcp` `udp`.
|
||||||
|
|
||||||
|
Both is enabled by default.
|
||||||
|
|
||||||
|
### Dial Fields
|
||||||
|
|
||||||
|
#### detour
|
||||||
|
|
||||||
|
The tag of the upstream outbound.
|
||||||
|
|
||||||
|
Other dial fields will be ignored when enabled.
|
||||||
|
|
||||||
|
#### bind_interface
|
||||||
|
|
||||||
|
The network interface to bind to.
|
||||||
|
|
||||||
|
#### routing_mark
|
||||||
|
|
||||||
|
!!! error ""
|
||||||
|
|
||||||
|
Linux only
|
||||||
|
|
||||||
|
The iptables routing mark.
|
||||||
|
|
||||||
|
#### reuse_addr
|
||||||
|
|
||||||
|
Reuse listener address.
|
||||||
|
|
||||||
|
#### connect_timeout
|
||||||
|
|
||||||
|
Connect timeout, in golang's Duration format.
|
||||||
|
|
||||||
|
A duration string is a possibly signed sequence of
|
||||||
|
decimal numbers, each with optional fraction and a unit suffix,
|
||||||
|
such as "300ms", "-1.5h" or "2h45m".
|
||||||
|
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||||
|
|
||||||
|
#### domain_strategy
|
||||||
|
|
||||||
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
|
|
||||||
|
If set, the server domain name will be resolved to IP before connecting.
|
||||||
|
|
||||||
|
`dns.strategy` will be used if empty.
|
||||||
|
|
||||||
|
#### fallback_delay
|
||||||
|
|
||||||
|
The length of time to wait before spawning a RFC 6555 Fast Fallback connection.
|
||||||
|
That is, is the amount of time to wait for IPv6 to succeed before assuming
|
||||||
|
that IPv6 is misconfigured and falling back to IPv4 if `prefer_ipv4` is set.
|
||||||
|
If zero, a default delay of 300ms is used.
|
||||||
|
|
||||||
|
Only take effect when `domain_strategy` is `prefer_ipv4` or `prefer_ipv6`.
|
|
@ -23,6 +23,7 @@
|
||||||
| `vmess` | [VMess](./vmess) |
|
| `vmess` | [VMess](./vmess) |
|
||||||
| `trojan` | [Trojan](./trojan) |
|
| `trojan` | [Trojan](./trojan) |
|
||||||
| `wireguard` | [Wireguard](./wireguard) |
|
| `wireguard` | [Wireguard](./wireguard) |
|
||||||
|
| `hysteria` | [Hysteria](./hysteria) |
|
||||||
| `dns` | [DNS](./dns) |
|
| `dns` | [DNS](./dns) |
|
||||||
| `selector` | [Selector](./selector) |
|
| `selector` | [Selector](./selector) |
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,25 @@
|
||||||
"certificate": "",
|
"certificate": "",
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"key": "",
|
"key": "",
|
||||||
"key_path": ""
|
"key_path": "",
|
||||||
|
"acme": {
|
||||||
|
"domain": [],
|
||||||
|
"data_directory": "",
|
||||||
|
"default_server_name": "",
|
||||||
|
"email": "",
|
||||||
|
"provider": "",
|
||||||
|
"disable_http_challenge": false,
|
||||||
|
"disable_tls_alpn_challenge": false,
|
||||||
|
"alternative_http_port": 0,
|
||||||
|
"alternative_tls_port": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
ACME is not included by default, see [Installation](/#Installation).
|
||||||
|
|
||||||
### Outbound Structure
|
### Outbound Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
@ -59,6 +74,10 @@ Cipher suite values:
|
||||||
* `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256`
|
* `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256`
|
||||||
* `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256`
|
* `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256`
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
You can ignore the JSON Array [] tag when the content is only one item
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
#### enabled
|
#### enabled
|
||||||
|
@ -135,6 +154,56 @@ The server private key, in PEM format.
|
||||||
|
|
||||||
The path to the server private key, in PEM format.
|
The path to the server private key, in PEM format.
|
||||||
|
|
||||||
|
### ACME Fields
|
||||||
|
|
||||||
|
#### domain
|
||||||
|
|
||||||
|
List of domain.
|
||||||
|
|
||||||
|
ACME will be disabled if empty.
|
||||||
|
|
||||||
|
#### data_directory
|
||||||
|
|
||||||
|
The directory to store ACME data.
|
||||||
|
|
||||||
|
`$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic` will be used if empty.
|
||||||
|
|
||||||
|
#### default_server_name
|
||||||
|
|
||||||
|
Server name to use when choosing a certificate if the ClientHello's ServerName field is empty.
|
||||||
|
|
||||||
|
#### email
|
||||||
|
|
||||||
|
The email address to use when creating or selecting an existing ACME server account
|
||||||
|
|
||||||
|
#### provider
|
||||||
|
|
||||||
|
The ACME CA provider to use.
|
||||||
|
|
||||||
|
| Value | Provider |
|
||||||
|
|-------------------------|---------------|
|
||||||
|
| `letsenctypt (default)` | Let's Encrypt |
|
||||||
|
| `zerossl` | ZeroSSL |
|
||||||
|
| `https://...` | Custom |
|
||||||
|
|
||||||
|
#### disable_http_challenge
|
||||||
|
|
||||||
|
Disable all HTTP challenges.
|
||||||
|
|
||||||
|
#### disable_tls_alpn_challenge
|
||||||
|
|
||||||
|
Disable all TLS-ALPN challenges
|
||||||
|
|
||||||
|
#### alternative_http_port
|
||||||
|
|
||||||
|
The alternate port to use for the ACME HTTP challenge; if non-empty, this port will be used instead of 80 to spin up a
|
||||||
|
listener for the HTTP challenge.
|
||||||
|
|
||||||
|
#### alternative_tls_port
|
||||||
|
|
||||||
|
The alternate port to use for the ACME TLS-ALPN challenge; the system must forward 443 to this port for challenge to
|
||||||
|
succeed.
|
||||||
|
|
||||||
### Reload
|
### Reload
|
||||||
|
|
||||||
For server configuration, certificate and key will be automatically reloaded if modified.
|
For server configuration, certificate and key will be automatically reloaded if modified.
|
|
@ -18,12 +18,13 @@ Install with options:
|
||||||
go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest
|
go install -v -tags with_clash_api github.com/sagernet/sing-box/cmd/sing-box@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
| Build Tag | Description |
|
| Build Tag | Description |
|
||||||
|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
|
|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `with_quic` | Build with QUIC support, see [QUIC and HTTP3 dns transports](./configuration/dns/server) and [Naive inbound](./configuration/inbound/naive). |
|
| `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) and [Hysteria Outbound](./configuration/outbound/hysteria). |
|
||||||
| `with_clash_api` | Build with Clash api support, see [Experimental](./configuration/experimental#clash-api-fields). |
|
| `with_acme` | Build with ACME TLS certificate issuer support, see [TLS](./configuration/shared/tls). |
|
||||||
| `no_gvisor` | Build without gVisor tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
| `with_clash_api` | Build with Clash api support, see [Experimental](./configuration/experimental#clash-api-fields). |
|
||||||
| `with_lwip` (CGO required) | Build with LWIP tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
| `no_gvisor` | Build without gVisor tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
||||||
|
| `with_lwip` (CGO required) | Build with LWIP tun stack support, see [Tun inbound](./configuration/inbound/tun#stack). |
|
||||||
|
|
||||||
The binary is built under $GOPATH/bin
|
The binary is built under $GOPATH/bin
|
||||||
|
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -14,8 +14,9 @@ require (
|
||||||
github.com/hashicorp/yamux v0.1.1
|
github.com/hashicorp/yamux v0.1.1
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0
|
github.com/oschwald/maxminddb-golang v1.10.0
|
||||||
|
github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a
|
||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
|
||||||
github.com/sagernet/sing v0.0.0-20220819003212-2424b1e2fac1
|
github.com/sagernet/sing v0.0.0-20220819041823-35c336a016c0
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9
|
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||||
github.com/sagernet/sing-tun v0.0.0-20220819003411-1cc817596b08
|
github.com/sagernet/sing-tun v0.0.0-20220819003411-1cc817596b08
|
||||||
|
@ -38,11 +39,12 @@ require (
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/libdns/libdns v0.2.1 // indirect
|
||||||
github.com/marten-seemann/qpack v0.2.1 // indirect
|
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
|
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
|
||||||
|
github.com/mholt/acmez v1.0.4 // indirect
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
@ -50,6 +52,8 @@ require (
|
||||||
github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a // indirect
|
github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
|
go.uber.org/zap v1.22.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
@ -57,7 +61,6 @@ require (
|
||||||
golang.org/x/tools v0.1.10 // indirect
|
golang.org/x/tools v0.1.10 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.1.7 // indirect
|
lukechampine.com/blake3 v1.1.7 // indirect
|
||||||
|
|
39
go.sum
39
go.sum
|
@ -1,5 +1,7 @@
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/database64128/tfo-go v1.1.1 h1:jcaCQBkEZZxV1t2wfOwt41WJKzgcNtLV7nGOm+hmZ3w=
|
github.com/database64128/tfo-go v1.1.1 h1:jcaCQBkEZZxV1t2wfOwt41WJKzgcNtLV7nGOm+hmZ3w=
|
||||||
github.com/database64128/tfo-go v1.1.1/go.mod h1:b1wrRNZr7NKZhWQ8LSTvqo1r2ppLdYXZLIUDCPOgJrI=
|
github.com/database64128/tfo-go v1.1.1/go.mod h1:b1wrRNZr7NKZhWQ8LSTvqo1r2ppLdYXZLIUDCPOgJrI=
|
||||||
|
@ -45,13 +47,15 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||||
|
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
||||||
|
@ -60,6 +64,8 @@ github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKA
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
|
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||||
|
github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80=
|
||||||
|
github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
@ -73,9 +79,13 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
||||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a h1:SE3Xn4GOQ+kxbgGa2Xp0H2CCsx1o2pVTt0f+hmfuHH4=
|
||||||
|
github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a/go.mod h1:Q+ZXyesnkjV5B70B1ixk65ecKrlJ2jz0atv3fPKsVVo=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a h1:iNtsfGMenajBUGZ/1yAzl1v3p+t/7IJ/ilQXq9haRZ8=
|
github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a h1:iNtsfGMenajBUGZ/1yAzl1v3p+t/7IJ/ilQXq9haRZ8=
|
||||||
|
@ -84,8 +94,8 @@ github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTY
|
||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
||||||
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-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-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.0.0-20220819003212-2424b1e2fac1 h1:+YC0/ygsJc4Z8qhd7ypsbWgMSm+UWN+QK+PW7I19K4Q=
|
github.com/sagernet/sing v0.0.0-20220819041823-35c336a016c0 h1:jFtynAm5qU1WXIs4FBxi7nLtTVMNXIv/hgO0V/BxmuE=
|
||||||
github.com/sagernet/sing v0.0.0-20220819003212-2424b1e2fac1/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220819041823-35c336a016c0/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9 h1:XgXSOJv8e7+98SJvg1f0luuPR33r4yFcmzxb3R//BTI=
|
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9 h1:XgXSOJv8e7+98SJvg1f0luuPR33r4yFcmzxb3R//BTI=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9/go.mod h1:MAHy2IKZAA101t3Gr2x0ldwn6XuAs2cjGzSzHy5RhWk=
|
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9/go.mod h1:MAHy2IKZAA101t3Gr2x0ldwn6XuAs2cjGzSzHy5RhWk=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
||||||
|
@ -102,7 +112,9 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
@ -110,8 +122,16 @@ github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695AP
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/yuin/goldmark v1.2.1/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=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||||
|
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||||
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
|
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||||
|
go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
|
||||||
|
go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
@ -119,17 +139,20 @@ golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUH
|
||||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
||||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -150,11 +173,15 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
|
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
|
||||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
@ -163,9 +190,11 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -192,10 +221,12 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/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.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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
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-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=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20220801010827-addd1f7b3e97 h1:zncudP85ZlJelPsgxZXN00Rl5M5j7QuDK27L35Ez01M=
|
gvisor.dev/gvisor v0.0.0-20220801010827-addd1f7b3e97 h1:zncudP85ZlJelPsgxZXN00Rl5M5j7QuDK27L35Ez01M=
|
||||||
|
|
|
@ -37,6 +37,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
||||||
return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions)
|
return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions)
|
||||||
case C.TypeNaive:
|
case C.TypeNaive:
|
||||||
return NewNaive(ctx, router, logger, options.Tag, options.NaiveOptions)
|
return NewNaive(ctx, router, logger, options.Tag, options.NaiveOptions)
|
||||||
|
case C.TypeHysteria:
|
||||||
|
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown inbound type: ", options.Type)
|
return nil, E.New("unknown inbound type: ", options.Type)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
||||||
authenticator: auth.NewAuthenticator(options.Users),
|
authenticator: auth.NewAuthenticator(options.Users),
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
tlsConfig, err := NewTLSConfig(logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := NewTLSConfig(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
319
inbound/hysteria.go
Normal file
319
inbound/hysteria.go
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/quic-go/congestion"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/transport/hysteria"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
|
"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.Inbound = (*Hysteria)(nil)
|
||||||
|
|
||||||
|
type Hysteria struct {
|
||||||
|
ctx context.Context
|
||||||
|
router adapter.Router
|
||||||
|
logger log.ContextLogger
|
||||||
|
tag string
|
||||||
|
listenOptions option.ListenOptions
|
||||||
|
quicConfig *quic.Config
|
||||||
|
tlsConfig *TLSConfig
|
||||||
|
authKey []byte
|
||||||
|
xplusKey []byte
|
||||||
|
sendBPS uint64
|
||||||
|
recvBPS uint64
|
||||||
|
listener quic.Listener
|
||||||
|
udpAccess sync.RWMutex
|
||||||
|
udpSessionId uint32
|
||||||
|
udpSessions map[uint32]chan *hysteria.UDPMessage
|
||||||
|
udpDefragger hysteria.Defragger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) {
|
||||||
|
quicConfig := &quic.Config{
|
||||||
|
InitialStreamReceiveWindow: options.ReceiveWindowConn,
|
||||||
|
MaxStreamReceiveWindow: options.ReceiveWindowConn,
|
||||||
|
InitialConnectionReceiveWindow: options.ReceiveWindowClient,
|
||||||
|
MaxConnectionReceiveWindow: options.ReceiveWindowClient,
|
||||||
|
MaxIncomingStreams: int64(options.MaxConnClient),
|
||||||
|
KeepAlivePeriod: hysteria.KeepAlivePeriod,
|
||||||
|
DisablePathMTUDiscovery: options.DisableMTUDiscovery || !(C.IsLinux || C.IsWindows),
|
||||||
|
EnableDatagrams: true,
|
||||||
|
}
|
||||||
|
if options.ReceiveWindowConn == 0 {
|
||||||
|
quicConfig.InitialStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
|
||||||
|
quicConfig.MaxStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
|
||||||
|
}
|
||||||
|
if options.ReceiveWindowClient == 0 {
|
||||||
|
quicConfig.InitialConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
|
||||||
|
quicConfig.MaxConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
|
||||||
|
}
|
||||||
|
if quicConfig.MaxIncomingStreams == 0 {
|
||||||
|
quicConfig.MaxIncomingStreams = hysteria.DefaultMaxIncomingStreams
|
||||||
|
}
|
||||||
|
var auth []byte
|
||||||
|
if len(options.Auth) > 0 {
|
||||||
|
auth = options.Auth
|
||||||
|
} else {
|
||||||
|
auth = []byte(options.AuthString)
|
||||||
|
}
|
||||||
|
var xplus []byte
|
||||||
|
if options.Obfs != "" {
|
||||||
|
xplus = []byte(options.Obfs)
|
||||||
|
}
|
||||||
|
var up, down uint64
|
||||||
|
if len(options.Up) > 0 {
|
||||||
|
up = hysteria.StringToBps(options.Up)
|
||||||
|
if up == 0 {
|
||||||
|
return nil, E.New("invalid up speed format: ", options.Up)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
up = uint64(options.UpMbps) * hysteria.MbpsToBps
|
||||||
|
}
|
||||||
|
if len(options.Down) > 0 {
|
||||||
|
down = hysteria.StringToBps(options.Down)
|
||||||
|
if down == 0 {
|
||||||
|
return nil, E.New("invalid down speed format: ", options.Down)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
down = uint64(options.DownMbps) * hysteria.MbpsToBps
|
||||||
|
}
|
||||||
|
if up < hysteria.MinSpeedBPS {
|
||||||
|
return nil, E.New("invalid up speed")
|
||||||
|
}
|
||||||
|
if down < hysteria.MinSpeedBPS {
|
||||||
|
return nil, E.New("invalid down speed")
|
||||||
|
}
|
||||||
|
inbound := &Hysteria{
|
||||||
|
ctx: ctx,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
tag: tag,
|
||||||
|
quicConfig: quicConfig,
|
||||||
|
listenOptions: options.ListenOptions,
|
||||||
|
authKey: auth,
|
||||||
|
xplusKey: xplus,
|
||||||
|
sendBPS: up,
|
||||||
|
recvBPS: down,
|
||||||
|
udpSessions: make(map[uint32]chan *hysteria.UDPMessage),
|
||||||
|
}
|
||||||
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
|
return nil, errTLSRequired
|
||||||
|
}
|
||||||
|
if len(options.TLS.ALPN) == 0 {
|
||||||
|
options.TLS.ALPN = []string{hysteria.DefaultALPN}
|
||||||
|
}
|
||||||
|
tlsConfig, err := NewTLSConfig(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inbound.tlsConfig = tlsConfig
|
||||||
|
return inbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) Type() string {
|
||||||
|
return C.TypeHysteria
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) Tag() string {
|
||||||
|
return h.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) Start() error {
|
||||||
|
listenAddr := M.SocksaddrFrom(netip.Addr(h.listenOptions.Listen), h.listenOptions.ListenPort)
|
||||||
|
var packetConn net.PacketConn
|
||||||
|
var err error
|
||||||
|
packetConn, err = net.ListenUDP(M.NetworkFromNetAddr("udp", listenAddr.Addr), listenAddr.UDPAddr())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(h.xplusKey) > 0 {
|
||||||
|
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
||||||
|
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
||||||
|
}
|
||||||
|
err = h.tlsConfig.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listener, err := quic.Listen(packetConn, h.tlsConfig.Config(), h.quicConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.listener = listener
|
||||||
|
h.logger.Info("udp server started at ", listener.Addr())
|
||||||
|
go h.acceptLoop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) acceptLoop() {
|
||||||
|
for {
|
||||||
|
ctx := log.ContextWithNewID(h.ctx)
|
||||||
|
conn, err := h.listener.Accept(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.logger.InfoContext(ctx, "inbound connection from ", conn.RemoteAddr())
|
||||||
|
go func() {
|
||||||
|
hErr := h.accept(ctx, conn)
|
||||||
|
if hErr != nil {
|
||||||
|
conn.CloseWithError(0, "")
|
||||||
|
NewError(h.logger, ctx, E.Cause(hErr, "process connection from ", conn.RemoteAddr()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) accept(ctx context.Context, conn quic.Connection) error {
|
||||||
|
controlStream, err := conn.AcceptStream(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clientHello, err := hysteria.ReadClientHello(controlStream)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(clientHello.Auth, h.authKey) {
|
||||||
|
err = hysteria.WriteServerHello(controlStream, hysteria.ServerHello{
|
||||||
|
Message: "wrong password",
|
||||||
|
})
|
||||||
|
return E.Errors(E.New("wrong password: ", string(clientHello.Auth)), err)
|
||||||
|
}
|
||||||
|
if clientHello.SendBPS == 0 || clientHello.RecvBPS == 0 {
|
||||||
|
return E.New("invalid rate from client")
|
||||||
|
}
|
||||||
|
serverSendBPS, serverRecvBPS := clientHello.RecvBPS, clientHello.SendBPS
|
||||||
|
if h.sendBPS > 0 && serverSendBPS > h.sendBPS {
|
||||||
|
serverSendBPS = h.sendBPS
|
||||||
|
}
|
||||||
|
if h.recvBPS > 0 && serverRecvBPS > h.recvBPS {
|
||||||
|
serverRecvBPS = h.recvBPS
|
||||||
|
}
|
||||||
|
err = hysteria.WriteServerHello(controlStream, hysteria.ServerHello{
|
||||||
|
OK: true,
|
||||||
|
SendBPS: serverSendBPS,
|
||||||
|
RecvBPS: serverRecvBPS,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn.SetCongestionControl(hysteria.NewBrutalSender(congestion.ByteCount(serverSendBPS)))
|
||||||
|
go h.udpRecvLoop(conn)
|
||||||
|
for {
|
||||||
|
var stream quic.Stream
|
||||||
|
stream, err = conn.AcceptStream(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
hErr := h.acceptStream(ctx, conn /*&hysteria.StreamWrapper{Stream: stream}*/, stream)
|
||||||
|
if hErr != nil {
|
||||||
|
stream.Close()
|
||||||
|
NewError(h.logger, ctx, E.Cause(hErr, "process stream from ", conn.RemoteAddr()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) udpRecvLoop(conn quic.Connection) {
|
||||||
|
for {
|
||||||
|
packet, err := conn.ReceiveMessage()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message, err := hysteria.ParseUDPMessage(packet)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("parse udp message: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dfMsg := h.udpDefragger.Feed(message)
|
||||||
|
if dfMsg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h.udpAccess.RLock()
|
||||||
|
ch, ok := h.udpSessions[dfMsg.SessionID]
|
||||||
|
if ok {
|
||||||
|
select {
|
||||||
|
case ch <- dfMsg:
|
||||||
|
// OK
|
||||||
|
default:
|
||||||
|
// Silently drop the message when the channel is full
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.udpAccess.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) acceptStream(ctx context.Context, conn quic.Connection, stream quic.Stream) error {
|
||||||
|
request, err := hysteria.ReadClientRequest(stream)
|
||||||
|
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
|
||||||
|
metadata.SniffEnabled = h.listenOptions.SniffEnabled
|
||||||
|
metadata.SniffOverrideDestination = h.listenOptions.SniffOverrideDestination
|
||||||
|
metadata.DomainStrategy = dns.DomainStrategy(h.listenOptions.DomainStrategy)
|
||||||
|
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
|
||||||
|
metadata.Destination = M.ParseSocksaddrHostPort(request.Host, request.Port)
|
||||||
|
if !request.UDP {
|
||||||
|
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||||
|
metadata.Network = N.NetworkTCP
|
||||||
|
return h.router.RouteConnection(ctx, hysteria.NewConn(stream, metadata.Destination), metadata)
|
||||||
|
} else {
|
||||||
|
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||||
|
var id uint32
|
||||||
|
h.udpAccess.Lock()
|
||||||
|
id = h.udpSessionId
|
||||||
|
nCh := make(chan *hysteria.UDPMessage, 1024)
|
||||||
|
h.udpSessions[id] = nCh
|
||||||
|
h.udpSessionId += 1
|
||||||
|
h.udpAccess.Unlock()
|
||||||
|
metadata.Network = N.NetworkUDP
|
||||||
|
packetConn := hysteria.NewPacketConn(conn, stream, id, metadata.Destination, nCh, common.Closer(func() error {
|
||||||
|
h.udpAccess.Lock()
|
||||||
|
if ch, ok := h.udpSessions[id]; ok {
|
||||||
|
close(ch)
|
||||||
|
delete(h.udpSessions, id)
|
||||||
|
}
|
||||||
|
h.udpAccess.Unlock()
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
go packetConn.Hold()
|
||||||
|
return h.router.RoutePacketConnection(ctx, packetConn, metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) Close() error {
|
||||||
|
h.udpAccess.Lock()
|
||||||
|
for _, session := range h.udpSessions {
|
||||||
|
close(session)
|
||||||
|
}
|
||||||
|
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
|
||||||
|
h.udpAccess.Unlock()
|
||||||
|
return common.Close(
|
||||||
|
h.listener,
|
||||||
|
common.PtrOrNil(h.tlsConfig),
|
||||||
|
)
|
||||||
|
}
|
16
inbound/hysteria_stub.go
Normal file
16
inbound/hysteria_stub.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build !with_quic
|
||||||
|
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
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 NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {
|
||||||
|
return nil, E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)
|
||||||
|
}
|
|
@ -45,10 +45,7 @@ type Naive struct {
|
||||||
h3Server any
|
h3Server any
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var errTLSRequired = E.New("TLS required")
|
||||||
ErrNaiveTLSRequired = E.New("TLS required")
|
|
||||||
ErrNaiveMissingUsers = E.New("missing users")
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (*Naive, error) {
|
func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (*Naive, error) {
|
||||||
inbound := &Naive{
|
inbound := &Naive{
|
||||||
|
@ -61,12 +58,12 @@ func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||||
authenticator: auth.NewAuthenticator(options.Users),
|
authenticator: auth.NewAuthenticator(options.Users),
|
||||||
}
|
}
|
||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, ErrNaiveTLSRequired
|
return nil, errTLSRequired
|
||||||
}
|
}
|
||||||
if len(options.Users) == 0 {
|
if len(options.Users) == 0 {
|
||||||
return nil, ErrNaiveMissingUsers
|
return nil, E.New("missing users")
|
||||||
}
|
}
|
||||||
tlsConfig, err := NewTLSConfig(logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := NewTLSConfig(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -195,6 +192,8 @@ func (n *Naive) newConnection(ctx context.Context, conn net.Conn, source, destin
|
||||||
metadata.Network = N.NetworkTCP
|
metadata.Network = N.NetworkTCP
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
|
n.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||||
|
n.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||||
hErr := n.router.RouteConnection(ctx, conn, metadata)
|
hErr := n.router.RouteConnection(ctx, conn, metadata)
|
||||||
if hErr != nil {
|
if hErr != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
|
@ -17,6 +19,7 @@ var _ adapter.Service = (*TLSConfig)(nil)
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
|
acmeService adapter.Service
|
||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
certificatePath string
|
certificatePath string
|
||||||
|
@ -29,14 +32,18 @@ func (c *TLSConfig) Config() *tls.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TLSConfig) Start() error {
|
func (c *TLSConfig) Start() error {
|
||||||
if c.certificatePath == "" && c.keyPath == "" {
|
if c.acmeService != nil {
|
||||||
|
return c.acmeService.Start()
|
||||||
|
} else {
|
||||||
|
if c.certificatePath == "" && c.keyPath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := c.startWatcher()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := c.startWatcher()
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn("create fsnotify watcher: ", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TLSConfig) startWatcher() error {
|
func (c *TLSConfig) startWatcher() error {
|
||||||
|
@ -109,17 +116,31 @@ func (c *TLSConfig) reloadKeyPair() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TLSConfig) Close() error {
|
func (c *TLSConfig) Close() error {
|
||||||
|
if c.acmeService != nil {
|
||||||
|
return c.acmeService.Close()
|
||||||
|
}
|
||||||
if c.watcher != nil {
|
if c.watcher != nil {
|
||||||
return c.watcher.Close()
|
return c.watcher.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTLSConfig(logger log.Logger, options option.InboundTLSOptions) (*TLSConfig, error) {
|
func NewTLSConfig(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*TLSConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var tlsConfig tls.Config
|
var tlsConfig *tls.Config
|
||||||
|
var acmeService adapter.Service
|
||||||
|
var err error
|
||||||
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
|
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tlsConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
tlsConfig.NextProtos = []string{}
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
tlsConfig.ServerName = options.ServerName
|
tlsConfig.ServerName = options.ServerName
|
||||||
}
|
}
|
||||||
|
@ -153,39 +174,42 @@ func NewTLSConfig(logger log.Logger, options option.InboundTLSOptions) (*TLSConf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var certificate []byte
|
var certificate []byte
|
||||||
if options.Certificate != "" {
|
|
||||||
certificate = []byte(options.Certificate)
|
|
||||||
} else if options.CertificatePath != "" {
|
|
||||||
content, err := os.ReadFile(options.CertificatePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "read certificate")
|
|
||||||
}
|
|
||||||
certificate = content
|
|
||||||
}
|
|
||||||
var key []byte
|
var key []byte
|
||||||
if options.Key != "" {
|
if acmeService == nil {
|
||||||
key = []byte(options.Key)
|
if options.Certificate != "" {
|
||||||
} else if options.KeyPath != "" {
|
certificate = []byte(options.Certificate)
|
||||||
content, err := os.ReadFile(options.KeyPath)
|
} else if options.CertificatePath != "" {
|
||||||
if err != nil {
|
content, err := os.ReadFile(options.CertificatePath)
|
||||||
return nil, E.Cause(err, "read key")
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read certificate")
|
||||||
|
}
|
||||||
|
certificate = content
|
||||||
}
|
}
|
||||||
key = content
|
if options.Key != "" {
|
||||||
|
key = []byte(options.Key)
|
||||||
|
} else if options.KeyPath != "" {
|
||||||
|
content, err := os.ReadFile(options.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read key")
|
||||||
|
}
|
||||||
|
key = content
|
||||||
|
}
|
||||||
|
if certificate == nil {
|
||||||
|
return nil, E.New("missing certificate")
|
||||||
|
}
|
||||||
|
if key == nil {
|
||||||
|
return nil, E.New("missing key")
|
||||||
|
}
|
||||||
|
keyPair, err := tls.X509KeyPair(certificate, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse x509 key pair")
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
||||||
}
|
}
|
||||||
if certificate == nil {
|
|
||||||
return nil, E.New("missing certificate")
|
|
||||||
}
|
|
||||||
if key == nil {
|
|
||||||
return nil, E.New("missing key")
|
|
||||||
}
|
|
||||||
keyPair, err := tls.X509KeyPair(certificate, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse x509 key pair")
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
|
||||||
return &TLSConfig{
|
return &TLSConfig{
|
||||||
config: &tlsConfig,
|
config: tlsConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
acmeService: acmeService,
|
||||||
certificate: certificate,
|
certificate: certificate,
|
||||||
key: key,
|
key: key,
|
||||||
certificatePath: options.CertificatePath,
|
certificatePath: options.CertificatePath,
|
||||||
|
|
66
inbound/tls_acme.go
Normal file
66
inbound/tls_acme.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
//go:build with_acme
|
||||||
|
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/certmagic"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type acmeWrapper struct {
|
||||||
|
ctx context.Context
|
||||||
|
cfg *certmagic.Config
|
||||||
|
domain []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *acmeWrapper) Start() error {
|
||||||
|
return w.cfg.ManageSync(w.ctx, w.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *acmeWrapper) Close() error {
|
||||||
|
w.cfg.Unmanage(w.domain)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
||||||
|
var acmeServer string
|
||||||
|
switch options.Provider {
|
||||||
|
case "", "letsencrypt":
|
||||||
|
acmeServer = certmagic.LetsEncryptProductionCA
|
||||||
|
case "zerossl":
|
||||||
|
acmeServer = certmagic.ZeroSSLProductionCA
|
||||||
|
default:
|
||||||
|
if !strings.HasPrefix(options.Provider, "https://") {
|
||||||
|
return nil, nil, E.New("unsupported acme provider: " + options.Provider)
|
||||||
|
}
|
||||||
|
acmeServer = options.Provider
|
||||||
|
}
|
||||||
|
var storage certmagic.Storage
|
||||||
|
if options.DataDirectory != "" {
|
||||||
|
storage = &certmagic.FileStorage{
|
||||||
|
Path: options.DataDirectory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config := certmagic.New(certmagic.NewCache(certmagic.CacheOptions{}), certmagic.Config{
|
||||||
|
DefaultServerName: options.DefaultServerName,
|
||||||
|
Issuers: []certmagic.Issuer{
|
||||||
|
&certmagic.ACMEIssuer{
|
||||||
|
CA: acmeServer,
|
||||||
|
Email: options.Email,
|
||||||
|
Agreed: true,
|
||||||
|
DisableHTTPChallenge: options.DisableHTTPChallenge,
|
||||||
|
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||||
|
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||||
|
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Storage: storage,
|
||||||
|
})
|
||||||
|
return config.TLSConfig(), &acmeWrapper{ctx, config, options.Domain}, nil
|
||||||
|
}
|
16
inbound/tls_acme_stub.go
Normal file
16
inbound/tls_acme_stub.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build !with_acme
|
||||||
|
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
||||||
|
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
tlsConfig, err := NewTLSConfig(logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := NewTLSConfig(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
tlsConfig, err := NewTLSConfig(logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := NewTLSConfig(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ nav:
|
||||||
- VMess: configuration/inbound/vmess.md
|
- VMess: configuration/inbound/vmess.md
|
||||||
- Trojan: configuration/inbound/trojan.md
|
- Trojan: configuration/inbound/trojan.md
|
||||||
- Naive: configuration/inbound/naive.md
|
- Naive: configuration/inbound/naive.md
|
||||||
|
- Hysteria: configuration/inbound/hysteria.md
|
||||||
- Tun: configuration/inbound/tun.md
|
- Tun: configuration/inbound/tun.md
|
||||||
- Redirect: configuration/inbound/redirect.md
|
- Redirect: configuration/inbound/redirect.md
|
||||||
- TProxy: configuration/inbound/tproxy.md
|
- TProxy: configuration/inbound/tproxy.md
|
||||||
|
@ -63,6 +64,7 @@ nav:
|
||||||
- VMess: configuration/outbound/vmess.md
|
- VMess: configuration/outbound/vmess.md
|
||||||
- Trojan: configuration/outbound/trojan.md
|
- Trojan: configuration/outbound/trojan.md
|
||||||
- WireGuard: configuration/outbound/wireguard.md
|
- WireGuard: configuration/outbound/wireguard.md
|
||||||
|
- Hysteria: configuration/outbound/hysteria.md
|
||||||
- DNS: configuration/outbound/dns.md
|
- DNS: configuration/outbound/dns.md
|
||||||
- Selector: configuration/outbound/selector.md
|
- Selector: configuration/outbound/selector.md
|
||||||
- Route:
|
- Route:
|
||||||
|
|
34
option/hysteria.go
Normal file
34
option/hysteria.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package option
|
||||||
|
|
||||||
|
type HysteriaInboundOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
Up string `json:"up,omitempty"`
|
||||||
|
UpMbps int `json:"up_mbps,omitempty"`
|
||||||
|
Down string `json:"down,omitempty"`
|
||||||
|
DownMbps int `json:"down_mbps,omitempty"`
|
||||||
|
Obfs string `json:"obfs,omitempty"`
|
||||||
|
Auth []byte `json:"auth,omitempty"`
|
||||||
|
AuthString string `json:"auth_str,omitempty"`
|
||||||
|
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
|
||||||
|
ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"`
|
||||||
|
MaxConnClient int `json:"max_conn_client,omitempty"`
|
||||||
|
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
|
||||||
|
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaOutboundOptions struct {
|
||||||
|
OutboundDialerOptions
|
||||||
|
ServerOptions
|
||||||
|
Up string `json:"up,omitempty"`
|
||||||
|
UpMbps int `json:"up_mbps,omitempty"`
|
||||||
|
Down string `json:"down,omitempty"`
|
||||||
|
DownMbps int `json:"down_mbps,omitempty"`
|
||||||
|
Obfs string `json:"obfs,omitempty"`
|
||||||
|
Auth []byte `json:"auth,omitempty"`
|
||||||
|
AuthString string `json:"auth_str,omitempty"`
|
||||||
|
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
|
||||||
|
ReceiveWindow uint64 `json:"recv_window,omitempty"`
|
||||||
|
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
|
||||||
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ type _Inbound struct {
|
||||||
VMessOptions VMessInboundOptions `json:"-"`
|
VMessOptions VMessInboundOptions `json:"-"`
|
||||||
TrojanOptions TrojanInboundOptions `json:"-"`
|
TrojanOptions TrojanInboundOptions `json:"-"`
|
||||||
NaiveOptions NaiveInboundOptions `json:"-"`
|
NaiveOptions NaiveInboundOptions `json:"-"`
|
||||||
|
HysteriaOptions HysteriaInboundOptions `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Inbound _Inbound
|
type Inbound _Inbound
|
||||||
|
@ -49,6 +50,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
|
||||||
v = h.TrojanOptions
|
v = h.TrojanOptions
|
||||||
case C.TypeNaive:
|
case C.TypeNaive:
|
||||||
v = h.NaiveOptions
|
v = h.NaiveOptions
|
||||||
|
case C.TypeHysteria:
|
||||||
|
v = h.HysteriaOptions
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown inbound type: ", h.Type)
|
return nil, E.New("unknown inbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
|
@ -84,6 +87,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
|
||||||
v = &h.TrojanOptions
|
v = &h.TrojanOptions
|
||||||
case C.TypeNaive:
|
case C.TypeNaive:
|
||||||
v = &h.NaiveOptions
|
v = &h.NaiveOptions
|
||||||
|
case C.TypeHysteria:
|
||||||
|
v = &h.HysteriaOptions
|
||||||
default:
|
default:
|
||||||
return E.New("unknown inbound type: ", h.Type)
|
return E.New("unknown inbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ type _Outbound struct {
|
||||||
VMessOptions VMessOutboundOptions `json:"-"`
|
VMessOptions VMessOutboundOptions `json:"-"`
|
||||||
TrojanOptions TrojanOutboundOptions `json:"-"`
|
TrojanOptions TrojanOutboundOptions `json:"-"`
|
||||||
WireGuardOptions WireGuardOutboundOptions `json:"-"`
|
WireGuardOptions WireGuardOutboundOptions `json:"-"`
|
||||||
|
HysteriaOutbound HysteriaOutboundOptions `json:"-"`
|
||||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +42,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
|
||||||
v = h.TrojanOptions
|
v = h.TrojanOptions
|
||||||
case C.TypeWireGuard:
|
case C.TypeWireGuard:
|
||||||
v = h.WireGuardOptions
|
v = h.WireGuardOptions
|
||||||
|
case C.TypeHysteria:
|
||||||
|
v = h.HysteriaOutbound
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = h.SelectorOptions
|
v = h.SelectorOptions
|
||||||
default:
|
default:
|
||||||
|
@ -72,6 +75,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
||||||
v = &h.TrojanOptions
|
v = &h.TrojanOptions
|
||||||
case C.TypeWireGuard:
|
case C.TypeWireGuard:
|
||||||
v = &h.WireGuardOptions
|
v = &h.WireGuardOptions
|
||||||
|
case C.TypeHysteria:
|
||||||
|
v = &h.HysteriaOutbound
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = &h.SelectorOptions
|
v = &h.SelectorOptions
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -7,29 +7,42 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type InboundTLSOptions struct {
|
type InboundTLSOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
ServerName string `json:"server_name,omitempty"`
|
ServerName string `json:"server_name,omitempty"`
|
||||||
ALPN []string `json:"alpn,omitempty"`
|
ALPN Listable[string] `json:"alpn,omitempty"`
|
||||||
MinVersion string `json:"min_version,omitempty"`
|
MinVersion string `json:"min_version,omitempty"`
|
||||||
MaxVersion string `json:"max_version,omitempty"`
|
MaxVersion string `json:"max_version,omitempty"`
|
||||||
CipherSuites []string `json:"cipher_suites,omitempty"`
|
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
|
||||||
Certificate string `json:"certificate,omitempty"`
|
Certificate string `json:"certificate,omitempty"`
|
||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
KeyPath string `json:"key_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
|
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundACMEOptions struct {
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DataDirectory string `json:"data_directory,omitempty"`
|
||||||
|
DefaultServerName string `json:"default_server_name,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
DisableHTTPChallenge bool `json:"disable_http_challenge,omitempty"`
|
||||||
|
DisableTLSALPNChallenge bool `json:"disable_tls_alpn_challenge,omitempty"`
|
||||||
|
AlternativeHTTPPort uint16 `json:"alternative_http_port,omitempty"`
|
||||||
|
AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTLSOptions struct {
|
type OutboundTLSOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
DisableSNI bool `json:"disable_sni,omitempty"`
|
DisableSNI bool `json:"disable_sni,omitempty"`
|
||||||
ServerName string `json:"server_name,omitempty"`
|
ServerName string `json:"server_name,omitempty"`
|
||||||
Insecure bool `json:"insecure,omitempty"`
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
ALPN []string `json:"alpn,omitempty"`
|
ALPN Listable[string] `json:"alpn,omitempty"`
|
||||||
MinVersion string `json:"min_version,omitempty"`
|
MinVersion string `json:"min_version,omitempty"`
|
||||||
MaxVersion string `json:"max_version,omitempty"`
|
MaxVersion string `json:"max_version,omitempty"`
|
||||||
CipherSuites []string `json:"cipher_suites,omitempty"`
|
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
|
||||||
Certificate string `json:"certificate,omitempty"`
|
Certificate string `json:"certificate,omitempty"`
|
||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseTLSVersion(version string) (uint16, error) {
|
func ParseTLSVersion(version string) (uint16, error) {
|
||||||
|
|
|
@ -33,6 +33,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
||||||
return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions)
|
return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions)
|
||||||
case C.TypeWireGuard:
|
case C.TypeWireGuard:
|
||||||
return NewWireGuard(ctx, router, logger, options.Tag, options.WireGuardOptions)
|
return NewWireGuard(ctx, router, logger, options.Tag, options.WireGuardOptions)
|
||||||
|
case C.TypeHysteria:
|
||||||
|
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOutbound)
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
||||||
default:
|
default:
|
||||||
|
|
346
outbound/hysteria.go
Normal file
346
outbound/hysteria.go
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/quic-go/congestion"
|
||||||
|
"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-box/transport/hysteria"
|
||||||
|
"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 = (*Hysteria)(nil)
|
||||||
|
|
||||||
|
type Hysteria struct {
|
||||||
|
myOutboundAdapter
|
||||||
|
ctx context.Context
|
||||||
|
dialer N.Dialer
|
||||||
|
serverAddr M.Socksaddr
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
quicConfig *quic.Config
|
||||||
|
authKey []byte
|
||||||
|
xplusKey []byte
|
||||||
|
sendBPS uint64
|
||||||
|
recvBPS uint64
|
||||||
|
connAccess sync.Mutex
|
||||||
|
conn quic.Connection
|
||||||
|
udpAccess sync.RWMutex
|
||||||
|
udpSessions map[uint32]chan *hysteria.UDPMessage
|
||||||
|
udpDefragger hysteria.Defragger
|
||||||
|
}
|
||||||
|
|
||||||
|
var errTLSRequired = E.New("TLS required")
|
||||||
|
|
||||||
|
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (*Hysteria, error) {
|
||||||
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
|
return nil, errTLSRequired
|
||||||
|
}
|
||||||
|
tlsConfig, err := dialer.TLSConfig(options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig.MinVersion = tls.VersionTLS13
|
||||||
|
if len(tlsConfig.NextProtos) == 0 {
|
||||||
|
tlsConfig.NextProtos = []string{hysteria.DefaultALPN}
|
||||||
|
}
|
||||||
|
quicConfig := &quic.Config{
|
||||||
|
InitialStreamReceiveWindow: options.ReceiveWindowConn,
|
||||||
|
MaxStreamReceiveWindow: options.ReceiveWindowConn,
|
||||||
|
InitialConnectionReceiveWindow: options.ReceiveWindow,
|
||||||
|
MaxConnectionReceiveWindow: options.ReceiveWindow,
|
||||||
|
KeepAlivePeriod: hysteria.KeepAlivePeriod,
|
||||||
|
DisablePathMTUDiscovery: options.DisableMTUDiscovery,
|
||||||
|
EnableDatagrams: true,
|
||||||
|
}
|
||||||
|
if options.ReceiveWindowConn == 0 {
|
||||||
|
quicConfig.InitialStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
|
||||||
|
quicConfig.MaxStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
|
||||||
|
}
|
||||||
|
if options.ReceiveWindow == 0 {
|
||||||
|
quicConfig.InitialConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
|
||||||
|
quicConfig.MaxConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
|
||||||
|
}
|
||||||
|
if quicConfig.MaxIncomingStreams == 0 {
|
||||||
|
quicConfig.MaxIncomingStreams = hysteria.DefaultMaxIncomingStreams
|
||||||
|
}
|
||||||
|
var auth []byte
|
||||||
|
if len(options.Auth) > 0 {
|
||||||
|
auth = options.Auth
|
||||||
|
} else {
|
||||||
|
auth = []byte(options.AuthString)
|
||||||
|
}
|
||||||
|
var xplus []byte
|
||||||
|
if options.Obfs != "" {
|
||||||
|
xplus = []byte(options.Obfs)
|
||||||
|
}
|
||||||
|
var up, down uint64
|
||||||
|
if len(options.Up) > 0 {
|
||||||
|
up = hysteria.StringToBps(options.Up)
|
||||||
|
if up == 0 {
|
||||||
|
return nil, E.New("invalid up speed format: ", options.Up)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
up = uint64(options.UpMbps) * hysteria.MbpsToBps
|
||||||
|
}
|
||||||
|
if len(options.Down) > 0 {
|
||||||
|
down = hysteria.StringToBps(options.Down)
|
||||||
|
if down == 0 {
|
||||||
|
return nil, E.New("invalid down speed format: ", options.Down)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
down = uint64(options.DownMbps) * hysteria.MbpsToBps
|
||||||
|
}
|
||||||
|
if up < hysteria.MinSpeedBPS {
|
||||||
|
return nil, E.New("invalid up speed")
|
||||||
|
}
|
||||||
|
if down < hysteria.MinSpeedBPS {
|
||||||
|
return nil, E.New("invalid down speed")
|
||||||
|
}
|
||||||
|
return &Hysteria{
|
||||||
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
|
protocol: C.TypeHysteria,
|
||||||
|
network: options.Network.Build(),
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
tag: tag,
|
||||||
|
},
|
||||||
|
ctx: ctx,
|
||||||
|
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
|
||||||
|
serverAddr: options.ServerOptions.Build(),
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
quicConfig: quicConfig,
|
||||||
|
authKey: auth,
|
||||||
|
xplusKey: xplus,
|
||||||
|
sendBPS: up,
|
||||||
|
recvBPS: down,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) offer(ctx context.Context) (quic.Connection, error) {
|
||||||
|
conn := h.conn
|
||||||
|
if conn != nil && !common.Done(conn.Context()) {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
h.connAccess.Lock()
|
||||||
|
defer h.connAccess.Unlock()
|
||||||
|
h.udpAccess.Lock()
|
||||||
|
defer h.udpAccess.Unlock()
|
||||||
|
conn = h.conn
|
||||||
|
if conn != nil && !common.Done(conn.Context()) {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
conn, err := h.offerNew(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h.conn = conn
|
||||||
|
if common.Contains(h.network, N.NetworkUDP) {
|
||||||
|
for _, session := range h.udpSessions {
|
||||||
|
close(session)
|
||||||
|
}
|
||||||
|
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
|
||||||
|
h.udpDefragger = hysteria.Defragger{}
|
||||||
|
go h.udpRecvLoop(conn)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) {
|
||||||
|
udpConn, err := h.dialer.DialContext(h.ctx, "udp", h.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var packetConn net.PacketConn
|
||||||
|
packetConn = bufio.NewUnbindPacketConn(udpConn)
|
||||||
|
if h.xplusKey != nil {
|
||||||
|
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
||||||
|
}
|
||||||
|
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
||||||
|
quicConn, err := quic.Dial(packetConn, udpConn.RemoteAddr(), h.serverAddr.AddrString(), h.tlsConfig, h.quicConfig)
|
||||||
|
if err != nil {
|
||||||
|
packetConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
controlStream, err := quicConn.OpenStreamSync(ctx)
|
||||||
|
if err != nil {
|
||||||
|
packetConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = hysteria.WriteClientHello(controlStream, hysteria.ClientHello{
|
||||||
|
SendBPS: h.sendBPS,
|
||||||
|
RecvBPS: h.recvBPS,
|
||||||
|
Auth: h.authKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
packetConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverHello, err := hysteria.ReadServerHello(controlStream)
|
||||||
|
if err != nil {
|
||||||
|
packetConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !serverHello.OK {
|
||||||
|
packetConn.Close()
|
||||||
|
return nil, E.New("remote error: ", serverHello.Message)
|
||||||
|
}
|
||||||
|
quicConn.SetCongestionControl(hysteria.NewBrutalSender(congestion.ByteCount(serverHello.RecvBPS)))
|
||||||
|
return quicConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) udpRecvLoop(conn quic.Connection) {
|
||||||
|
for {
|
||||||
|
packet, err := conn.ReceiveMessage()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message, err := hysteria.ParseUDPMessage(packet)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("parse udp message: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dfMsg := h.udpDefragger.Feed(message)
|
||||||
|
if dfMsg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h.udpAccess.RLock()
|
||||||
|
ch, ok := h.udpSessions[dfMsg.SessionID]
|
||||||
|
if ok {
|
||||||
|
select {
|
||||||
|
case ch <- dfMsg:
|
||||||
|
// OK
|
||||||
|
default:
|
||||||
|
// Silently drop the message when the channel is full
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.udpAccess.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) Close() error {
|
||||||
|
h.connAccess.Lock()
|
||||||
|
defer h.connAccess.Unlock()
|
||||||
|
h.udpAccess.Lock()
|
||||||
|
defer h.udpAccess.Unlock()
|
||||||
|
if h.conn != nil {
|
||||||
|
h.conn.CloseWithError(0, "")
|
||||||
|
}
|
||||||
|
for _, session := range h.udpSessions {
|
||||||
|
close(session)
|
||||||
|
}
|
||||||
|
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) open(ctx context.Context) (quic.Connection, quic.Stream, error) {
|
||||||
|
conn, err := h.offer(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
stream, err := conn.OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return conn, &hysteria.StreamWrapper{Stream: stream}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP:
|
||||||
|
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
|
_, stream, err := h.open(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = hysteria.WriteClientRequest(stream, hysteria.ClientRequest{
|
||||||
|
Host: destination.AddrString(),
|
||||||
|
Port: destination.Port,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
|
case N.NetworkUDP:
|
||||||
|
conn, err := h.ListenPacket(ctx, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn.(*hysteria.PacketConn), nil
|
||||||
|
default:
|
||||||
|
return nil, E.New("unsupported network: ", network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
|
conn, stream, err := h.open(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = hysteria.WriteClientRequest(stream, hysteria.ClientRequest{
|
||||||
|
UDP: true,
|
||||||
|
Host: destination.AddrString(),
|
||||||
|
Port: destination.Port,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
stream.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response *hysteria.ServerResponse
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
h.udpAccess.Lock()
|
||||||
|
nCh := make(chan *hysteria.UDPMessage, 1024)
|
||||||
|
h.udpSessions[response.UDPSessionID] = nCh
|
||||||
|
h.udpAccess.Unlock()
|
||||||
|
packetConn := hysteria.NewPacketConn(conn, stream, response.UDPSessionID, destination, nCh, common.Closer(func() error {
|
||||||
|
h.udpAccess.Lock()
|
||||||
|
if ch, ok := h.udpSessions[response.UDPSessionID]; ok {
|
||||||
|
close(ch)
|
||||||
|
delete(h.udpSessions, response.UDPSessionID)
|
||||||
|
}
|
||||||
|
h.udpAccess.Unlock()
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
go packetConn.Hold()
|
||||||
|
return packetConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
return NewConnection(ctx, h, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hysteria) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return NewPacketConnection(ctx, h, conn, metadata)
|
||||||
|
}
|
16
outbound/hysteria_stub.go
Normal file
16
outbound/hysteria_stub.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build !with_quic
|
||||||
|
|
||||||
|
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 NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
return nil, E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)
|
||||||
|
}
|
|
@ -35,14 +35,6 @@ func startInstance(t *testing.T, options option.Options) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTCP(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))
|
|
||||||
}
|
|
||||||
require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
|
func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
|
||||||
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
|
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
|
||||||
dialTCP := func() (net.Conn, error) {
|
dialTCP := func() (net.Conn, error) {
|
||||||
|
@ -51,36 +43,34 @@ func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
|
||||||
dialUDP := func() (net.PacketConn, error) {
|
dialUDP := func() (net.PacketConn, error) {
|
||||||
return dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", testPort))
|
return dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", testPort))
|
||||||
}
|
}
|
||||||
/*t.Run("tcp", func(t *testing.T) {
|
// require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||||
t.Parallel()
|
// require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
||||||
var err error
|
|
||||||
for retry := 0; retry < 3; retry++ {
|
|
||||||
err = testLargeDataWithConn(t, testPort, dialTCP)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("udp", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
var err error
|
|
||||||
for retry := 0; retry < 3; retry++ {
|
|
||||||
err = testLargeDataWithPacketConn(t, testPort, dialUDP)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
})*/
|
|
||||||
//require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
|
||||||
//require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
|
||||||
require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
|
require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
|
||||||
require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
|
require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
|
||||||
|
|
||||||
// require.NoError(t, testPacketConnTimeout(t, dialUDP))
|
// require.NoError(t, testPacketConnTimeout(t, dialUDP))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testTCP(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))
|
||||||
|
}
|
||||||
|
require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSuitHy(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))
|
||||||
|
}
|
||||||
|
|
||||||
func testSuitWg(t *testing.T, clientPort uint16, testPort uint16) {
|
func testSuitWg(t *testing.T, clientPort uint16, testPort uint16) {
|
||||||
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
|
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
|
||||||
dialTCP := func() (net.Conn, error) {
|
dialTCP := func() (net.Conn, error) {
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -32,6 +34,7 @@ const (
|
||||||
ImageTrojan = "trojangfw/trojan:latest"
|
ImageTrojan = "trojangfw/trojan:latest"
|
||||||
ImageNaive = "pocat/naiveproxy:client"
|
ImageNaive = "pocat/naiveproxy:client"
|
||||||
ImageBoringTun = "ghcr.io/ntkme/boringtun:edge"
|
ImageBoringTun = "ghcr.io/ntkme/boringtun:edge"
|
||||||
|
ImageHysteria = "tobyxdd/hysteria:latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allImages = []string{
|
var allImages = []string{
|
||||||
|
@ -41,6 +44,7 @@ var allImages = []string{
|
||||||
ImageTrojan,
|
ImageTrojan,
|
||||||
ImageNaive,
|
ImageNaive,
|
||||||
ImageBoringTun,
|
ImageBoringTun,
|
||||||
|
ImageHysteria,
|
||||||
}
|
}
|
||||||
|
|
||||||
var localIP = netip.MustParseAddr("127.0.0.1")
|
var localIP = netip.MustParseAddr("127.0.0.1")
|
||||||
|
@ -89,6 +93,12 @@ func init() {
|
||||||
|
|
||||||
io.Copy(io.Discard, imageStream)
|
io.Copy(io.Discard, imageStream)
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
err = http.ListenAndServe("0.0.0.0:8965", nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPingPongPair() (chan []byte, chan []byte, func(t *testing.T) error) {
|
func newPingPongPair() (chan []byte, chan []byte, func(t *testing.T) error) {
|
||||||
|
@ -379,7 +389,6 @@ func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.Pack
|
||||||
mux.Lock()
|
mux.Lock()
|
||||||
hashMap[i] = hash[:]
|
hashMap[i] = hash[:]
|
||||||
mux.Unlock()
|
mux.Unlock()
|
||||||
println("write ti ", addr.String())
|
|
||||||
if _, err = pc.WriteTo(buf, addr); err != nil {
|
if _, err = pc.WriteTo(buf, addr); err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
continue
|
continue
|
||||||
|
|
12
test/config/hysteria-client.json
Normal file
12
test/config/hysteria-client.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"server": "127.0.0.1:10000",
|
||||||
|
"auth_str": "password",
|
||||||
|
"obfs": "fuck me till the daylight",
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down_mbps": 100,
|
||||||
|
"socks5": {
|
||||||
|
"listen": "127.0.0.1:10001"
|
||||||
|
},
|
||||||
|
"server_name": "example.org",
|
||||||
|
"ca": "/etc/hysteria/ca.pem"
|
||||||
|
}
|
9
test/config/hysteria-server.json
Normal file
9
test/config/hysteria-server.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"listen": ":10000",
|
||||||
|
"cert": "/etc/hysteria/cert.pem",
|
||||||
|
"key": "/etc/hysteria/key.pem",
|
||||||
|
"auth_str": "password",
|
||||||
|
"obfs": "fuck me till the daylight",
|
||||||
|
"up_mbps": 100,
|
||||||
|
"down_mbps": 100
|
||||||
|
}
|
10
test/go.mod
10
test/go.mod
|
@ -10,7 +10,7 @@ require (
|
||||||
github.com/docker/docker v20.10.17+incompatible
|
github.com/docker/docker v20.10.17+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/gofrs/uuid v4.2.0+incompatible
|
github.com/gofrs/uuid v4.2.0+incompatible
|
||||||
github.com/sagernet/sing v0.0.0-20220819003212-2424b1e2fac1
|
github.com/sagernet/sing v0.0.0-20220819041823-35c336a016c0
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||||
github.com/spyzhov/ajson v0.7.1
|
github.com/spyzhov/ajson v0.7.1
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
|
@ -34,12 +34,13 @@ require (
|
||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
||||||
github.com/kr/text v0.1.0 // indirect
|
github.com/libdns/libdns v0.2.1 // indirect
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||||
github.com/marten-seemann/qpack v0.2.1 // indirect
|
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
|
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
|
||||||
|
github.com/mholt/acmez v1.0.4 // indirect
|
||||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
|
@ -49,6 +50,7 @@ require (
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a // indirect
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||||
github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a // indirect
|
github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a // indirect
|
||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
|
||||||
|
@ -59,6 +61,8 @@ require (
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
go.uber.org/atomic v1.10.0 // 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
|
||||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
|
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
|
|
38
test/go.sum
38
test/go.sum
|
@ -4,6 +4,8 @@ github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6
|
||||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/database64128/tfo-go v1.1.1 h1:jcaCQBkEZZxV1t2wfOwt41WJKzgcNtLV7nGOm+hmZ3w=
|
github.com/database64128/tfo-go v1.1.1 h1:jcaCQBkEZZxV1t2wfOwt41WJKzgcNtLV7nGOm+hmZ3w=
|
||||||
github.com/database64128/tfo-go v1.1.1/go.mod h1:b1wrRNZr7NKZhWQ8LSTvqo1r2ppLdYXZLIUDCPOgJrI=
|
github.com/database64128/tfo-go v1.1.1/go.mod h1:b1wrRNZr7NKZhWQ8LSTvqo1r2ppLdYXZLIUDCPOgJrI=
|
||||||
|
@ -59,12 +61,15 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||||
|
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
||||||
|
@ -73,6 +78,8 @@ github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKA
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
|
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||||
|
github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80=
|
||||||
|
github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
|
||||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
|
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
|
||||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
@ -99,6 +106,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a h1:SE3Xn4GOQ+kxbgGa2Xp0H2CCsx1o2pVTt0f+hmfuHH4=
|
||||||
|
github.com/sagernet/certmagic v0.0.0-20220819042630-4a57f8b6853a/go.mod h1:Q+ZXyesnkjV5B70B1ixk65ecKrlJ2jz0atv3fPKsVVo=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a h1:iNtsfGMenajBUGZ/1yAzl1v3p+t/7IJ/ilQXq9haRZ8=
|
github.com/sagernet/netlink v0.0.0-20220816152750-7a75378bd31a h1:iNtsfGMenajBUGZ/1yAzl1v3p+t/7IJ/ilQXq9haRZ8=
|
||||||
|
@ -107,8 +116,8 @@ github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTY
|
||||||
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
|
||||||
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-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-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.0.0-20220819003212-2424b1e2fac1 h1:+YC0/ygsJc4Z8qhd7ypsbWgMSm+UWN+QK+PW7I19K4Q=
|
github.com/sagernet/sing v0.0.0-20220819041823-35c336a016c0 h1:jFtynAm5qU1WXIs4FBxi7nLtTVMNXIv/hgO0V/BxmuE=
|
||||||
github.com/sagernet/sing v0.0.0-20220819003212-2424b1e2fac1/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220819041823-35c336a016c0/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9 h1:XgXSOJv8e7+98SJvg1f0luuPR33r4yFcmzxb3R//BTI=
|
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9 h1:XgXSOJv8e7+98SJvg1f0luuPR33r4yFcmzxb3R//BTI=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9/go.mod h1:MAHy2IKZAA101t3Gr2x0ldwn6XuAs2cjGzSzHy5RhWk=
|
github.com/sagernet/sing-dns v0.0.0-20220819010310-839eab1578c9/go.mod h1:MAHy2IKZAA101t3Gr2x0ldwn6XuAs2cjGzSzHy5RhWk=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
||||||
|
@ -128,7 +137,9 @@ github.com/spyzhov/ajson v0.7.1/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzy
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
@ -137,8 +148,16 @@ 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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/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=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||||
|
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||||
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
|
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||||
|
go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
|
||||||
|
go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
@ -146,6 +165,7 @@ golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUH
|
||||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
@ -160,6 +180,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
||||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -184,12 +205,16 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
|
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
|
||||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
@ -198,6 +223,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
@ -205,6 +231,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -225,15 +252,18 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/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.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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
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-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=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
|
|
178
test/hysteria_test.go
Normal file
178
test/hysteria_test.go
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHysteriaSelf(t *testing.T) {
|
||||||
|
if !C.QUIC_AVAILABLE {
|
||||||
|
t.Skip("QUIC not included")
|
||||||
|
}
|
||||||
|
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Log: &option.LogOptions{
|
||||||
|
Level: "trace",
|
||||||
|
},
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeMixed,
|
||||||
|
Tag: "mixed-in",
|
||||||
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: clientPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeHysteria,
|
||||||
|
HysteriaOptions: option.HysteriaInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: serverPort,
|
||||||
|
},
|
||||||
|
UpMbps: 100,
|
||||||
|
DownMbps: 100,
|
||||||
|
AuthString: "password",
|
||||||
|
Obfs: "fuck me till the daylight",
|
||||||
|
TLS: &option.InboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
CertificatePath: certPem,
|
||||||
|
KeyPath: keyPem,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbounds: []option.Outbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeDirect,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeHysteria,
|
||||||
|
Tag: "hy-out",
|
||||||
|
HysteriaOutbound: option.HysteriaOutboundOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
ServerPort: serverPort,
|
||||||
|
},
|
||||||
|
UpMbps: 100,
|
||||||
|
DownMbps: 100,
|
||||||
|
AuthString: "password",
|
||||||
|
Obfs: "fuck me till the daylight",
|
||||||
|
CustomCA: caPem,
|
||||||
|
ServerName: "example.org",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Route: &option.RouteOptions{
|
||||||
|
Rules: []option.Rule{
|
||||||
|
{
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
Inbound: []string{"mixed-in"},
|
||||||
|
Outbound: "hy-out",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testSuitHy(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{
|
||||||
|
Log: &option.LogOptions{
|
||||||
|
Level: "trace",
|
||||||
|
},
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeHysteria,
|
||||||
|
HysteriaOptions: option.HysteriaInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: serverPort,
|
||||||
|
},
|
||||||
|
UpMbps: 100,
|
||||||
|
DownMbps: 100,
|
||||||
|
AuthString: "password",
|
||||||
|
Obfs: "fuck me till the daylight",
|
||||||
|
TLS: &option.InboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
CertificatePath: certPem,
|
||||||
|
KeyPath: keyPem,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
startDockerContainer(t, DockerOptions{
|
||||||
|
Image: ImageHysteria,
|
||||||
|
Ports: []uint16{serverPort, clientPort},
|
||||||
|
Cmd: []string{"-c", "/etc/hysteria/config.json", "client"},
|
||||||
|
Bind: map[string]string{
|
||||||
|
"hysteria-client.json": "/etc/hysteria/config.json",
|
||||||
|
caPem: "/etc/hysteria/ca.pem",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testSuit(t, clientPort, testPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHysteriaOutbound(t *testing.T) {
|
||||||
|
if !C.QUIC_AVAILABLE {
|
||||||
|
t.Skip("QUIC not included")
|
||||||
|
}
|
||||||
|
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||||
|
startDockerContainer(t, DockerOptions{
|
||||||
|
Image: ImageHysteria,
|
||||||
|
Ports: []uint16{serverPort, testPort},
|
||||||
|
Cmd: []string{"-c", "/etc/hysteria/config.json", "server"},
|
||||||
|
Bind: map[string]string{
|
||||||
|
"hysteria-server.json": "/etc/hysteria/config.json",
|
||||||
|
certPem: "/etc/hysteria/cert.pem",
|
||||||
|
keyPem: "/etc/hysteria/key.pem",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Log: &option.LogOptions{
|
||||||
|
Level: "trace",
|
||||||
|
},
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeMixed,
|
||||||
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: clientPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbounds: []option.Outbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeHysteria,
|
||||||
|
HysteriaOutbound: option.HysteriaOutboundOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
ServerPort: serverPort,
|
||||||
|
},
|
||||||
|
UpMbps: 100,
|
||||||
|
DownMbps: 100,
|
||||||
|
AuthString: "password",
|
||||||
|
Obfs: "fuck me till the daylight",
|
||||||
|
CustomCA: caPem,
|
||||||
|
ServerName: "example.org",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testSuitHy(t, clientPort, testPort)
|
||||||
|
}
|
149
transport/hysteria/brutal.go
Normal file
149
transport/hysteria/brutal.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package hysteria
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go/congestion"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
initMaxDatagramSize = 1252
|
||||||
|
|
||||||
|
pktInfoSlotCount = 4
|
||||||
|
minSampleCount = 50
|
||||||
|
minAckRate = 0.8
|
||||||
|
)
|
||||||
|
|
||||||
|
type BrutalSender struct {
|
||||||
|
rttStats congestion.RTTStatsProvider
|
||||||
|
bps congestion.ByteCount
|
||||||
|
maxDatagramSize congestion.ByteCount
|
||||||
|
pacer *pacer
|
||||||
|
|
||||||
|
pktInfoSlots [pktInfoSlotCount]pktInfo
|
||||||
|
ackRate float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type pktInfo struct {
|
||||||
|
Timestamp int64
|
||||||
|
AckCount uint64
|
||||||
|
LossCount uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBrutalSender(bps congestion.ByteCount) *BrutalSender {
|
||||||
|
bs := &BrutalSender{
|
||||||
|
bps: bps,
|
||||||
|
maxDatagramSize: initMaxDatagramSize,
|
||||||
|
ackRate: 1,
|
||||||
|
}
|
||||||
|
bs.pacer = newPacer(func() congestion.ByteCount {
|
||||||
|
return congestion.ByteCount(float64(bs.bps) / bs.ackRate)
|
||||||
|
})
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) SetRTTStatsProvider(rttStats congestion.RTTStatsProvider) {
|
||||||
|
b.rttStats = rttStats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) TimeUntilSend(bytesInFlight congestion.ByteCount) time.Time {
|
||||||
|
return b.pacer.TimeUntilSend()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) HasPacingBudget() bool {
|
||||||
|
return b.pacer.Budget(time.Now()) >= b.maxDatagramSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) CanSend(bytesInFlight congestion.ByteCount) bool {
|
||||||
|
return bytesInFlight < b.GetCongestionWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) GetCongestionWindow() congestion.ByteCount {
|
||||||
|
rtt := maxDuration(b.rttStats.LatestRTT(), b.rttStats.SmoothedRTT())
|
||||||
|
if rtt <= 0 {
|
||||||
|
return 10240
|
||||||
|
}
|
||||||
|
return congestion.ByteCount(float64(b.bps) * rtt.Seconds() * 1.5 / b.ackRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) OnPacketSent(sentTime time.Time, bytesInFlight congestion.ByteCount,
|
||||||
|
packetNumber congestion.PacketNumber, bytes congestion.ByteCount, isRetransmittable bool,
|
||||||
|
) {
|
||||||
|
b.pacer.SentPacket(sentTime, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes congestion.ByteCount,
|
||||||
|
priorInFlight congestion.ByteCount, eventTime time.Time,
|
||||||
|
) {
|
||||||
|
currentTimestamp := eventTime.Unix()
|
||||||
|
slot := currentTimestamp % pktInfoSlotCount
|
||||||
|
if b.pktInfoSlots[slot].Timestamp == currentTimestamp {
|
||||||
|
b.pktInfoSlots[slot].AckCount++
|
||||||
|
} else {
|
||||||
|
// uninitialized slot or too old, reset
|
||||||
|
b.pktInfoSlots[slot].Timestamp = currentTimestamp
|
||||||
|
b.pktInfoSlots[slot].AckCount = 1
|
||||||
|
b.pktInfoSlots[slot].LossCount = 0
|
||||||
|
}
|
||||||
|
b.updateAckRate(currentTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) OnPacketLost(number congestion.PacketNumber, lostBytes congestion.ByteCount,
|
||||||
|
priorInFlight congestion.ByteCount,
|
||||||
|
) {
|
||||||
|
currentTimestamp := time.Now().Unix()
|
||||||
|
slot := currentTimestamp % pktInfoSlotCount
|
||||||
|
if b.pktInfoSlots[slot].Timestamp == currentTimestamp {
|
||||||
|
b.pktInfoSlots[slot].LossCount++
|
||||||
|
} else {
|
||||||
|
// uninitialized slot or too old, reset
|
||||||
|
b.pktInfoSlots[slot].Timestamp = currentTimestamp
|
||||||
|
b.pktInfoSlots[slot].AckCount = 0
|
||||||
|
b.pktInfoSlots[slot].LossCount = 1
|
||||||
|
}
|
||||||
|
b.updateAckRate(currentTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) SetMaxDatagramSize(size congestion.ByteCount) {
|
||||||
|
b.maxDatagramSize = size
|
||||||
|
b.pacer.SetMaxDatagramSize(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) updateAckRate(currentTimestamp int64) {
|
||||||
|
minTimestamp := currentTimestamp - pktInfoSlotCount
|
||||||
|
var ackCount, lossCount uint64
|
||||||
|
for _, info := range b.pktInfoSlots {
|
||||||
|
if info.Timestamp < minTimestamp {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ackCount += info.AckCount
|
||||||
|
lossCount += info.LossCount
|
||||||
|
}
|
||||||
|
if ackCount+lossCount < minSampleCount {
|
||||||
|
b.ackRate = 1
|
||||||
|
}
|
||||||
|
rate := float64(ackCount) / float64(ackCount+lossCount)
|
||||||
|
if rate < minAckRate {
|
||||||
|
b.ackRate = minAckRate
|
||||||
|
}
|
||||||
|
b.ackRate = rate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) InSlowStart() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) InRecovery() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) MaybeExitSlowStart() {}
|
||||||
|
|
||||||
|
func (b *BrutalSender) OnRetransmissionTimeout(packetsRetransmitted bool) {}
|
||||||
|
|
||||||
|
func maxDuration(a, b time.Duration) time.Duration {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
65
transport/hysteria/frag.go
Normal file
65
transport/hysteria/frag.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package hysteria
|
||||||
|
|
||||||
|
func FragUDPMessage(m UDPMessage, maxSize int) []UDPMessage {
|
||||||
|
if m.Size() <= maxSize {
|
||||||
|
return []UDPMessage{m}
|
||||||
|
}
|
||||||
|
fullPayload := m.Data
|
||||||
|
maxPayloadSize := maxSize - m.HeaderSize()
|
||||||
|
off := 0
|
||||||
|
fragID := uint8(0)
|
||||||
|
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
|
||||||
|
var frags []UDPMessage
|
||||||
|
for off < len(fullPayload) {
|
||||||
|
payloadSize := len(fullPayload) - off
|
||||||
|
if payloadSize > maxPayloadSize {
|
||||||
|
payloadSize = maxPayloadSize
|
||||||
|
}
|
||||||
|
frag := m
|
||||||
|
frag.FragID = fragID
|
||||||
|
frag.FragCount = fragCount
|
||||||
|
frag.Data = fullPayload[off : off+payloadSize]
|
||||||
|
frags = append(frags, frag)
|
||||||
|
off += payloadSize
|
||||||
|
fragID++
|
||||||
|
}
|
||||||
|
return frags
|
||||||
|
}
|
||||||
|
|
||||||
|
type Defragger struct {
|
||||||
|
msgID uint16
|
||||||
|
frags []*UDPMessage
|
||||||
|
count uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Defragger) Feed(m UDPMessage) *UDPMessage {
|
||||||
|
if m.FragCount <= 1 {
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
if m.FragID >= m.FragCount {
|
||||||
|
// wtf is this?
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if m.MsgID != d.msgID {
|
||||||
|
// new message, clear previous state
|
||||||
|
d.msgID = m.MsgID
|
||||||
|
d.frags = make([]*UDPMessage, m.FragCount)
|
||||||
|
d.count = 1
|
||||||
|
d.frags[m.FragID] = &m
|
||||||
|
} else if d.frags[m.FragID] == nil {
|
||||||
|
d.frags[m.FragID] = &m
|
||||||
|
d.count++
|
||||||
|
if int(d.count) == len(d.frags) {
|
||||||
|
// all fragments received, assemble
|
||||||
|
var data []byte
|
||||||
|
for _, frag := range d.frags {
|
||||||
|
data = append(data, frag.Data...)
|
||||||
|
}
|
||||||
|
m.Data = data
|
||||||
|
m.FragID = 0
|
||||||
|
m.FragCount = 1
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
86
transport/hysteria/pacer.go
Normal file
86
transport/hysteria/pacer.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package hysteria
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go/congestion"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxBurstPackets = 10
|
||||||
|
minPacingDelay = time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
// The pacer implements a token bucket pacing algorithm.
|
||||||
|
type pacer struct {
|
||||||
|
budgetAtLastSent congestion.ByteCount
|
||||||
|
maxDatagramSize congestion.ByteCount
|
||||||
|
lastSentTime time.Time
|
||||||
|
getBandwidth func() congestion.ByteCount // in bytes/s
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPacer(getBandwidth func() congestion.ByteCount) *pacer {
|
||||||
|
p := &pacer{
|
||||||
|
budgetAtLastSent: maxBurstPackets * initMaxDatagramSize,
|
||||||
|
maxDatagramSize: initMaxDatagramSize,
|
||||||
|
getBandwidth: getBandwidth,
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pacer) SentPacket(sendTime time.Time, size congestion.ByteCount) {
|
||||||
|
budget := p.Budget(sendTime)
|
||||||
|
if size > budget {
|
||||||
|
p.budgetAtLastSent = 0
|
||||||
|
} else {
|
||||||
|
p.budgetAtLastSent = budget - size
|
||||||
|
}
|
||||||
|
p.lastSentTime = sendTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pacer) Budget(now time.Time) congestion.ByteCount {
|
||||||
|
if p.lastSentTime.IsZero() {
|
||||||
|
return p.maxBurstSize()
|
||||||
|
}
|
||||||
|
budget := p.budgetAtLastSent + (p.getBandwidth()*congestion.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9
|
||||||
|
return minByteCount(p.maxBurstSize(), budget)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pacer) maxBurstSize() congestion.ByteCount {
|
||||||
|
return maxByteCount(
|
||||||
|
congestion.ByteCount((minPacingDelay+time.Millisecond).Nanoseconds())*p.getBandwidth()/1e9,
|
||||||
|
maxBurstPackets*p.maxDatagramSize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeUntilSend returns when the next packet should be sent.
|
||||||
|
// It returns the zero value of time.Time if a packet can be sent immediately.
|
||||||
|
func (p *pacer) TimeUntilSend() time.Time {
|
||||||
|
if p.budgetAtLastSent >= p.maxDatagramSize {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return p.lastSentTime.Add(maxDuration(
|
||||||
|
minPacingDelay,
|
||||||
|
time.Duration(math.Ceil(float64(p.maxDatagramSize-p.budgetAtLastSent)*1e9/
|
||||||
|
float64(p.getBandwidth())))*time.Nanosecond,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pacer) SetMaxDatagramSize(s congestion.ByteCount) {
|
||||||
|
p.maxDatagramSize = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxByteCount(a, b congestion.ByteCount) congestion.ByteCount {
|
||||||
|
if a < b {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func minByteCount(a, b congestion.ByteCount) congestion.ByteCount {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
510
transport/hysteria/protocol.go
Normal file
510
transport/hysteria/protocol.go
Normal file
|
@ -0,0 +1,510 @@
|
||||||
|
package hysteria
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MbpsToBps = 125000
|
||||||
|
MinSpeedBPS = 16384
|
||||||
|
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
|
||||||
|
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
|
||||||
|
DefaultMaxIncomingStreams = 1024
|
||||||
|
DefaultALPN = "hysteria"
|
||||||
|
KeepAlivePeriod = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
const Version = 3
|
||||||
|
|
||||||
|
type ClientHello struct {
|
||||||
|
SendBPS uint64
|
||||||
|
RecvBPS uint64
|
||||||
|
Auth []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteClientHello(stream io.Writer, hello ClientHello) error {
|
||||||
|
var requestLen int
|
||||||
|
requestLen += 1 // version
|
||||||
|
requestLen += 8 // sendBPS
|
||||||
|
requestLen += 8 // recvBPS
|
||||||
|
requestLen += 2 // auth len
|
||||||
|
requestLen += len(hello.Auth)
|
||||||
|
_request := buf.StackNewSize(requestLen)
|
||||||
|
defer common.KeepAlive(_request)
|
||||||
|
request := common.Dup(_request)
|
||||||
|
defer request.Release()
|
||||||
|
common.Must(
|
||||||
|
request.WriteByte(Version),
|
||||||
|
binary.Write(request, binary.BigEndian, hello.SendBPS),
|
||||||
|
binary.Write(request, binary.BigEndian, hello.RecvBPS),
|
||||||
|
binary.Write(request, binary.BigEndian, uint16(len(hello.Auth))),
|
||||||
|
common.Error(request.Write(hello.Auth)),
|
||||||
|
)
|
||||||
|
return common.Error(stream.Write(request.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadClientHello(reader io.Reader) (*ClientHello, error) {
|
||||||
|
var version uint8
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if version != Version {
|
||||||
|
return nil, E.New("unsupported client version: ", version)
|
||||||
|
}
|
||||||
|
var clientHello ClientHello
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &clientHello.SendBPS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &clientHello.RecvBPS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var authLen uint16
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &authLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientHello.Auth = make([]byte, authLen)
|
||||||
|
_, err = io.ReadFull(reader, clientHello.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &clientHello, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerHello struct {
|
||||||
|
OK bool
|
||||||
|
SendBPS uint64
|
||||||
|
RecvBPS uint64
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadServerHello(stream io.Reader) (*ServerHello, error) {
|
||||||
|
var responseLen int
|
||||||
|
responseLen += 1 // ok
|
||||||
|
responseLen += 8 // sendBPS
|
||||||
|
responseLen += 8 // recvBPS
|
||||||
|
responseLen += 2 // message len
|
||||||
|
_response := buf.StackNewSize(responseLen)
|
||||||
|
defer common.KeepAlive(_response)
|
||||||
|
response := common.Dup(_response)
|
||||||
|
defer response.Release()
|
||||||
|
_, err := response.ReadFullFrom(stream, responseLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var serverHello ServerHello
|
||||||
|
serverHello.OK = response.Byte(0) == 1
|
||||||
|
serverHello.SendBPS = binary.BigEndian.Uint64(response.Range(1, 9))
|
||||||
|
serverHello.RecvBPS = binary.BigEndian.Uint64(response.Range(9, 17))
|
||||||
|
messageLen := binary.BigEndian.Uint16(response.Range(17, 19))
|
||||||
|
if messageLen == 0 {
|
||||||
|
return &serverHello, nil
|
||||||
|
}
|
||||||
|
message := make([]byte, messageLen)
|
||||||
|
_, err = io.ReadFull(stream, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverHello.Message = string(message)
|
||||||
|
return &serverHello, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteServerHello(stream io.Writer, hello ServerHello) error {
|
||||||
|
var responseLen int
|
||||||
|
responseLen += 1 // ok
|
||||||
|
responseLen += 8 // sendBPS
|
||||||
|
responseLen += 8 // recvBPS
|
||||||
|
responseLen += 2 // message len
|
||||||
|
responseLen += len(hello.Message)
|
||||||
|
_response := buf.StackNewSize(responseLen)
|
||||||
|
defer common.KeepAlive(_response)
|
||||||
|
response := common.Dup(_response)
|
||||||
|
defer response.Release()
|
||||||
|
if hello.OK {
|
||||||
|
common.Must(response.WriteByte(1))
|
||||||
|
} else {
|
||||||
|
common.Must(response.WriteByte(0))
|
||||||
|
}
|
||||||
|
common.Must(
|
||||||
|
binary.Write(response, binary.BigEndian, hello.SendBPS),
|
||||||
|
binary.Write(response, binary.BigEndian, hello.RecvBPS),
|
||||||
|
binary.Write(response, binary.BigEndian, uint16(len(hello.Message))),
|
||||||
|
common.Error(response.WriteString(hello.Message)),
|
||||||
|
)
|
||||||
|
return common.Error(stream.Write(response.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientRequest struct {
|
||||||
|
UDP bool
|
||||||
|
Host string
|
||||||
|
Port uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadClientRequest(stream io.Reader) (*ClientRequest, error) {
|
||||||
|
var clientRequest ClientRequest
|
||||||
|
err := binary.Read(stream, binary.BigEndian, &clientRequest.UDP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var hostLen uint16
|
||||||
|
err = binary.Read(stream, binary.BigEndian, &hostLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
host := make([]byte, hostLen)
|
||||||
|
_, err = io.ReadFull(stream, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientRequest.Host = string(host)
|
||||||
|
err = binary.Read(stream, binary.BigEndian, &clientRequest.Port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &clientRequest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteClientRequest(stream io.Writer, request ClientRequest) error {
|
||||||
|
var requestLen int
|
||||||
|
requestLen += 1 // udp
|
||||||
|
requestLen += 2 // host len
|
||||||
|
requestLen += len(request.Host)
|
||||||
|
requestLen += 2 // port
|
||||||
|
_buffer := buf.StackNewSize(requestLen)
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
if request.UDP {
|
||||||
|
common.Must(buffer.WriteByte(1))
|
||||||
|
} else {
|
||||||
|
common.Must(buffer.WriteByte(0))
|
||||||
|
}
|
||||||
|
common.Must(
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(len(request.Host))),
|
||||||
|
common.Error(buffer.WriteString(request.Host)),
|
||||||
|
binary.Write(buffer, binary.BigEndian, request.Port),
|
||||||
|
)
|
||||||
|
return common.Error(stream.Write(buffer.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerResponse struct {
|
||||||
|
OK bool
|
||||||
|
UDPSessionID uint32
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadServerResponse(stream io.Reader) (*ServerResponse, error) {
|
||||||
|
var responseLen int
|
||||||
|
responseLen += 1 // ok
|
||||||
|
responseLen += 4 // udp session id
|
||||||
|
responseLen += 2 // message len
|
||||||
|
_response := buf.StackNewSize(responseLen)
|
||||||
|
defer common.KeepAlive(_response)
|
||||||
|
response := common.Dup(_response)
|
||||||
|
defer response.Release()
|
||||||
|
_, err := response.ReadFullFrom(stream, responseLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var serverResponse ServerResponse
|
||||||
|
serverResponse.OK = response.Byte(0) == 1
|
||||||
|
serverResponse.UDPSessionID = binary.BigEndian.Uint32(response.Range(1, 5))
|
||||||
|
messageLen := binary.BigEndian.Uint16(response.Range(5, 7))
|
||||||
|
if messageLen == 0 {
|
||||||
|
return &serverResponse, nil
|
||||||
|
}
|
||||||
|
message := make([]byte, messageLen)
|
||||||
|
_, err = io.ReadFull(stream, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverResponse.Message = string(message)
|
||||||
|
return &serverResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteServerResponse(stream io.Writer, response ServerResponse) error {
|
||||||
|
var responseLen int
|
||||||
|
responseLen += 1 // ok
|
||||||
|
responseLen += 4 // udp session id
|
||||||
|
responseLen += 2 // message len
|
||||||
|
responseLen += len(response.Message)
|
||||||
|
_buffer := buf.StackNewSize(responseLen)
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
if response.OK {
|
||||||
|
common.Must(buffer.WriteByte(1))
|
||||||
|
} else {
|
||||||
|
common.Must(buffer.WriteByte(0))
|
||||||
|
}
|
||||||
|
common.Must(
|
||||||
|
binary.Write(buffer, binary.BigEndian, response.UDPSessionID),
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(len(response.Message))),
|
||||||
|
common.Error(buffer.WriteString(response.Message)),
|
||||||
|
)
|
||||||
|
return common.Error(stream.Write(buffer.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPMessage struct {
|
||||||
|
SessionID uint32
|
||||||
|
Host string
|
||||||
|
Port uint16
|
||||||
|
MsgID uint16 // doesn't matter when not fragmented, but must not be 0 when fragmented
|
||||||
|
FragID uint8 // doesn't matter when not fragmented, starts at 0 when fragmented
|
||||||
|
FragCount uint8 // must be 1 when not fragmented
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UDPMessage) HeaderSize() int {
|
||||||
|
return 4 + 2 + len(m.Host) + 2 + 2 + 1 + 1 + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m UDPMessage) Size() int {
|
||||||
|
return m.HeaderSize() + len(m.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseUDPMessage(packet []byte) (message UDPMessage, err error) {
|
||||||
|
reader := bytes.NewReader(packet)
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &message.SessionID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var hostLen uint16
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &hostLen)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = reader.Seek(int64(hostLen), io.SeekCurrent)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.Host = string(packet[6 : 6+hostLen])
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &message.Port)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &message.MsgID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &message.FragID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &message.FragCount)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var dataLen uint16
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &dataLen)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if reader.Len() != int(dataLen) {
|
||||||
|
err = E.New("invalid data length")
|
||||||
|
}
|
||||||
|
dataOffset := int(reader.Size()) - reader.Len()
|
||||||
|
message.Data = packet[dataOffset:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteUDPMessage(conn quic.Connection, message UDPMessage) error {
|
||||||
|
var messageLen int
|
||||||
|
messageLen += 4 // session id
|
||||||
|
messageLen += 2 // host len
|
||||||
|
messageLen += len(message.Host)
|
||||||
|
messageLen += 2 // port
|
||||||
|
messageLen += 2 // msg id
|
||||||
|
messageLen += 1 // frag id
|
||||||
|
messageLen += 1 // frag count
|
||||||
|
messageLen += 2 // data len
|
||||||
|
messageLen += len(message.Data)
|
||||||
|
_buffer := buf.StackNewSize(messageLen)
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
err := writeUDPMessage(conn, message, buffer)
|
||||||
|
if errSize, ok := err.(quic.ErrMessageToLarge); ok {
|
||||||
|
// need to frag
|
||||||
|
message.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
||||||
|
fragMsgs := FragUDPMessage(message, int(errSize))
|
||||||
|
for _, fragMsg := range fragMsgs {
|
||||||
|
buffer.FullReset()
|
||||||
|
err = writeUDPMessage(conn, fragMsg, buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeUDPMessage(conn quic.Connection, message UDPMessage, buffer *buf.Buffer) error {
|
||||||
|
common.Must(
|
||||||
|
binary.Write(buffer, binary.BigEndian, message.SessionID),
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(len(message.Host))),
|
||||||
|
common.Error(buffer.WriteString(message.Host)),
|
||||||
|
binary.Write(buffer, binary.BigEndian, message.Port),
|
||||||
|
binary.Write(buffer, binary.BigEndian, message.MsgID),
|
||||||
|
binary.Write(buffer, binary.BigEndian, message.FragID),
|
||||||
|
binary.Write(buffer, binary.BigEndian, message.FragCount),
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(len(message.Data))),
|
||||||
|
common.Error(buffer.Write(message.Data)),
|
||||||
|
)
|
||||||
|
return conn.SendMessage(buffer.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Conn = (*Conn)(nil)
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
quic.Stream
|
||||||
|
destination M.Socksaddr
|
||||||
|
responseWritten bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(stream quic.Stream, destination M.Socksaddr) *Conn {
|
||||||
|
return &Conn{
|
||||||
|
Stream: stream,
|
||||||
|
destination: destination,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) LocalAddr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) RemoteAddr() net.Addr {
|
||||||
|
return c.destination.TCPAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Upstream() any {
|
||||||
|
return c.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
session quic.Connection
|
||||||
|
stream quic.Stream
|
||||||
|
sessionId uint32
|
||||||
|
destination M.Socksaddr
|
||||||
|
msgCh <-chan *UDPMessage
|
||||||
|
closer io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConn(session quic.Connection, stream quic.Stream, sessionId uint32, destination M.Socksaddr, msgCh <-chan *UDPMessage, closer io.Closer) *PacketConn {
|
||||||
|
return &PacketConn{
|
||||||
|
session: session,
|
||||||
|
stream: stream,
|
||||||
|
sessionId: sessionId,
|
||||||
|
destination: destination,
|
||||||
|
msgCh: msgCh,
|
||||||
|
closer: closer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Hold() {
|
||||||
|
// Hold the stream until it's closed
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
for {
|
||||||
|
_, err := c.stream.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
msg := <-c.msgCh
|
||||||
|
if msg == nil {
|
||||||
|
err = net.ErrClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = common.Error(buffer.Write(msg.Data))
|
||||||
|
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadPacketThreadSafe() (buffer *buf.Buffer, destination M.Socksaddr, err error) {
|
||||||
|
msg := <-c.msgCh
|
||||||
|
if msg == nil {
|
||||||
|
err = net.ErrClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer = buf.As(msg.Data)
|
||||||
|
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
return WriteUDPMessage(c.session, UDPMessage{
|
||||||
|
SessionID: c.sessionId,
|
||||||
|
Host: destination.Unwrap().AddrString(),
|
||||||
|
Port: destination.Port,
|
||||||
|
FragCount: 1,
|
||||||
|
Data: buffer.Bytes(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) LocalAddr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) RemoteAddr() net.Addr {
|
||||||
|
return c.destination.UDPAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) SetDeadline(t time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
return 0, nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
return 0, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Read(b []byte) (n int, err error) {
|
||||||
|
return 0, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Write(b []byte) (n int, err error) {
|
||||||
|
return 0, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Close() error {
|
||||||
|
return common.Close(c.stream, c.closer)
|
||||||
|
}
|
36
transport/hysteria/speed.go
Normal file
36
transport/hysteria/speed.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package hysteria
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StringToBps(s string) uint64 {
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
m := regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`).FindStringSubmatch(s)
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var n uint64
|
||||||
|
switch m[2] {
|
||||||
|
case "K":
|
||||||
|
n = 1 << 10
|
||||||
|
case "M":
|
||||||
|
n = 1 << 20
|
||||||
|
case "G":
|
||||||
|
n = 1 << 30
|
||||||
|
case "T":
|
||||||
|
n = 1 << 40
|
||||||
|
default:
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
v, _ := strconv.ParseUint(m[1], 10, 64)
|
||||||
|
n = v * n
|
||||||
|
if m[3] == "b" {
|
||||||
|
// Bits, need to convert to bytes
|
||||||
|
n = n >> 3
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
56
transport/hysteria/wrap.go
Normal file
56
transport/hysteria/wrap.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package hysteria
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketConnWrapper struct {
|
||||||
|
net.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConnWrapper) SetReadBuffer(bytes int) error {
|
||||||
|
return common.MustCast[*net.UDPConn](c.PacketConn).SetReadBuffer(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConnWrapper) SetWriteBuffer(bytes int) error {
|
||||||
|
return common.MustCast[*net.UDPConn](c.PacketConn).SetWriteBuffer(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConnWrapper) SyscallConn() (syscall.RawConn, error) {
|
||||||
|
return common.MustCast[*net.UDPConn](c.PacketConn).SyscallConn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConnWrapper) File() (f *os.File, err error) {
|
||||||
|
return common.MustCast[*net.UDPConn](c.PacketConn).File()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConnWrapper) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamWrapper struct {
|
||||||
|
quic.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StreamWrapper) Upstream() any {
|
||||||
|
return s.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StreamWrapper) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StreamWrapper) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StreamWrapper) Close() error {
|
||||||
|
s.CancelRead(0)
|
||||||
|
s.Stream.Close()
|
||||||
|
return nil
|
||||||
|
}
|
119
transport/hysteria/xplus.go
Normal file
119
transport/hysteria/xplus.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package hysteria
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
const xplusSaltLen = 16
|
||||||
|
|
||||||
|
var errInalidPacket = E.New("invalid packet")
|
||||||
|
|
||||||
|
func NewXPlusPacketConn(conn net.PacketConn, key []byte) net.PacketConn {
|
||||||
|
vectorisedWriter, isVectorised := bufio.CreateVectorisedPacketWriter(conn)
|
||||||
|
if isVectorised {
|
||||||
|
return &VectorisedXPlusConn{
|
||||||
|
XPlusPacketConn: XPlusPacketConn{
|
||||||
|
PacketConn: conn,
|
||||||
|
key: key,
|
||||||
|
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
},
|
||||||
|
writer: vectorisedWriter,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &XPlusPacketConn{
|
||||||
|
PacketConn: conn,
|
||||||
|
key: key,
|
||||||
|
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type XPlusPacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
key []byte
|
||||||
|
randAccess sync.Mutex
|
||||||
|
rand *rand.Rand
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XPlusPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
n, addr, err = c.PacketConn.ReadFrom(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
} else if n < xplusSaltLen {
|
||||||
|
return 0, nil, errInalidPacket
|
||||||
|
}
|
||||||
|
key := sha256.Sum256(append(c.key, p[:xplusSaltLen]...))
|
||||||
|
for i := range p[xplusSaltLen:] {
|
||||||
|
p[i] = p[xplusSaltLen+i] ^ key[i%sha256.Size]
|
||||||
|
}
|
||||||
|
n -= xplusSaltLen
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XPlusPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
// can't use unsafe buffer on WriteTo
|
||||||
|
buffer := buf.NewSize(len(p) + xplusSaltLen)
|
||||||
|
defer buffer.Release()
|
||||||
|
salt := buffer.Extend(xplusSaltLen)
|
||||||
|
c.randAccess.Lock()
|
||||||
|
_, _ = c.rand.Read(salt)
|
||||||
|
c.randAccess.Unlock()
|
||||||
|
key := sha256.Sum256(append(c.key, salt...))
|
||||||
|
for i := range p {
|
||||||
|
common.Must(buffer.WriteByte(p[i] ^ key[i%sha256.Size]))
|
||||||
|
}
|
||||||
|
return c.PacketConn.WriteTo(buffer.Bytes(), addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XPlusPacketConn) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type VectorisedXPlusConn struct {
|
||||||
|
XPlusPacketConn
|
||||||
|
writer N.VectorisedPacketWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VectorisedXPlusConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
header := buf.NewSize(xplusSaltLen)
|
||||||
|
defer header.Release()
|
||||||
|
salt := header.Extend(xplusSaltLen)
|
||||||
|
c.randAccess.Lock()
|
||||||
|
_, _ = c.rand.Read(salt)
|
||||||
|
c.randAccess.Unlock()
|
||||||
|
key := sha256.Sum256(append(c.key, salt...))
|
||||||
|
for i := range p {
|
||||||
|
p[i] ^= key[i%sha256.Size]
|
||||||
|
}
|
||||||
|
return bufio.WriteVectorisedPacket(c.writer, [][]byte{header.Bytes(), p}, M.SocksaddrFromNet(addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *VectorisedXPlusConn) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
header := buf.NewSize(xplusSaltLen)
|
||||||
|
salt := header.Extend(xplusSaltLen)
|
||||||
|
c.randAccess.Lock()
|
||||||
|
_, _ = c.rand.Read(salt)
|
||||||
|
c.randAccess.Unlock()
|
||||||
|
key := sha256.Sum256(append(c.key, salt...))
|
||||||
|
var index int
|
||||||
|
for _, buffer := range buffers {
|
||||||
|
data := buffer.Bytes()
|
||||||
|
for i := range data {
|
||||||
|
data[i] ^= key[index%sha256.Size]
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffers = append([]*buf.Buffer{header}, buffers...)
|
||||||
|
return c.writer.WriteVectorisedPacket(buffers, destination)
|
||||||
|
}
|
Loading…
Reference in a new issue