mirror of
https://github.com/cedar2025/hysteria.git
synced 2025-09-09 10:01:45 +00:00
Merge pull request #36 from tobyxdd/wip-ng
Code Refactoring & Implementing SOCKS5 UDP with QUIC Unreliable Datagram
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
id: current-time
|
||||
|
||||
- name: Build
|
||||
uses: tobyxdd/go-cross-build@e269fe09a60b9a6a7325cb06075e32ab1b7c2d12
|
||||
uses: thatisuday/go-cross-build@v1.1.0
|
||||
env:
|
||||
TIME: "${{ steps.current-time.outputs.time }}"
|
||||
CGO_ENABLED: "0"
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -180,3 +180,4 @@ $RECYCLE.BIN/
|
||||
# End of https://www.gitignore.io/api/go,linux,macos,windows,intellij+all
|
||||
|
||||
cmd/relay/*.json
|
||||
hy_linux
|
||||
|
15
ACL.md
15
ACL.md
@@ -1,12 +1,14 @@
|
||||
# ACL File Format
|
||||
|
||||
ACL files describe how to process incoming requests. Both the server and the client support ACL and follow the identical syntax.
|
||||
ACL files describe how to process incoming requests. Both the server and the client support ACL and follow the identical
|
||||
syntax.
|
||||
|
||||
```
|
||||
action condition_type condition argument
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
direct domain evil.corp
|
||||
proxy domain-suffix google.com
|
||||
@@ -16,9 +18,12 @@ hijack cidr 192.168.1.1/24 127.0.0.1
|
||||
direct all
|
||||
```
|
||||
|
||||
A real-life ACL example of directly connecting to all China IPs (and its generator Python script) [can be found here](docs/acl).
|
||||
A real-life ACL example of directly connecting to all China IPs (and its generator Python
|
||||
script) [can be found here](docs/acl).
|
||||
|
||||
Hysteria acts according to the first matching rule in the file for each request. When there is no match, the default behavior is to proxy all connections. You can override this by adding a rule at the end of the file with the condition "all".
|
||||
Hysteria acts according to the first matching rule in the file for each request. When there is no match, the default
|
||||
behavior is to proxy all connections. You can override this by adding a rule at the end of the file with the condition
|
||||
`all`.
|
||||
|
||||
4 actions:
|
||||
|
||||
@@ -42,4 +47,6 @@ Hysteria acts according to the first matching rule in the file for each request.
|
||||
|
||||
`all` - match anything (usually placed at the end of the file as a default rule)
|
||||
|
||||
For domain requests, Hysteria will try to resolve the domains and match both domain & IP rules. In other words, an IP rule covers all connections that would end up connecting to this IP, regardless of whether the client requests with IP or domain.
|
||||
For domain requests, Hysteria will try to resolve the domains and match both domain & IP rules. In other words, an IP
|
||||
rule covers all connections that would end up connecting to this IP, regardless of whether the client requests with IP
|
||||
or domain.
|
@@ -7,6 +7,7 @@ ACL 文件描述如何处理传入请求。服务器和客户端都支持 ACL,
|
||||
```
|
||||
|
||||
例子:
|
||||
|
||||
```
|
||||
direct domain evil.corp
|
||||
proxy domain-suffix google.com
|
||||
|
27
LICENSE.md
27
LICENSE.md
@@ -1,21 +1,16 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Toby
|
||||
Copyright (c) 2021 Toby
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
272
README.md
272
README.md
@@ -3,169 +3,187 @@
|
||||
[![License][1]][2] [![Release][3]][4] [![Telegram][5]][6]
|
||||
|
||||
[1]: https://img.shields.io/github/license/tobyxdd/hysteria?style=flat-square
|
||||
|
||||
[2]: LICENSE.md
|
||||
|
||||
[3]: https://img.shields.io/github/v/release/tobyxdd/hysteria?style=flat-square
|
||||
|
||||
[4]: https://github.com/tobyxdd/hysteria/releases
|
||||
|
||||
[5]: https://img.shields.io/badge/chat-Telegram-blue?style=flat-square
|
||||
|
||||
[6]: https://t.me/hysteria_github
|
||||
|
||||
[中文 README](README.zh.md)
|
||||
|
||||
Hysteria is a set of relay & proxy utilities that are specifically optimized for harsh network environments (commonly seen in connecting to overseas servers from China). It's based on a modified version of the QUIC protocol, and can be considered a sequel to my previous (abandoned) project https://github.com/dragonite-network/dragonite-java
|
||||
Hysteria is a TCP relay & SOCKS5/HTTP proxy tool optimized for poor network environments (satellite networks,
|
||||
connections from China to foreign servers, etc.) powered by a custom version of QUIC protocol.
|
||||
|
||||
It is essentially a spiritual successor of my previous (now abandoned)
|
||||
project https://github.com/dragonite-network/dragonite-java
|
||||
|
||||
## Quick Start
|
||||
|
||||
(See the [advanced usage section](#advanced-usage) for the exact meaning of each argument)
|
||||
Note: The configs provided in this section are only for people to get started quickly and may not meet your needs.
|
||||
Please go to [Advanced usage](#advanced-usage) to see all the available options and their meanings.
|
||||
|
||||
### Proxy
|
||||
### Server
|
||||
|
||||
Server:
|
||||
```
|
||||
./cmd_linux_amd64 proxy server -listen :36712 -cert example.crt -key example.key -obfs BlueberryFaygo
|
||||
```
|
||||
A TLS certificate (not necessarily issued by a trusted CA) is required on the server side. If you are using a self-issued certificate, use `-ca` to specify your own CA file on clients, or `-insecure` to ignore all certificate errors (not recommended)
|
||||
Create a `config.json` under the root directory of the program:
|
||||
|
||||
Client:
|
||||
```
|
||||
./cmd_linux_amd64 proxy client -server example.com:36712 -socks5-addr localhost:1080 -up-mbps 10 -down-mbps 50 -obfs BlueberryFaygo
|
||||
```
|
||||
This will start a SOCKS5 proxy server on the client's localhost TCP 1080 available for use by other programs.
|
||||
|
||||
In addition to SOCKS5, it also supports HTTP proxy (`-http-addr` & `-http-timeout`). Both modes can be turned on simultaneously on different ports.
|
||||
|
||||
`-up-mbps 10 -down-mbps 50` tells the server that your bandwidth is 50 Mbps down, 10 Mbps up. Properly setting the client's upload and download speeds based on your network conditions is essential for it to work at optimal performance!
|
||||
|
||||
### Relay
|
||||
|
||||
Suppose you have a TCP program on your server at `localhost:8080` that you want to forward.
|
||||
|
||||
Server:
|
||||
```
|
||||
./cmd_linux_amd64 relay server -listen :36712 -remote localhost:8080 -cert example.crt -key example.key
|
||||
```json
|
||||
{
|
||||
"listen": ":36712",
|
||||
"cert": "/home/ubuntu/my_cert.crt",
|
||||
"key": "/home/ubuntu/my_key.crt",
|
||||
"obfs": "AMOGUS",
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100
|
||||
}
|
||||
```
|
||||
|
||||
Client:
|
||||
```
|
||||
./cmd_linux_amd64 relay client -server example.com:36712 -listen localhost:8080 -up-mbps 10 -down-mbps 50
|
||||
```
|
||||
All connections to client's localhost TCP 8080 will pass through the relay and connect to the server's `localhost:8080`
|
||||
A TLS certificate (not necessarily issued by a trusted CA) is required on the server side.
|
||||
|
||||
Some users may attempt to forward other encrypted proxy protocols such as Shadowsocks with relay. While this totally works, it's not optimal from a performance standpoint - our protocol itself uses TLS, considering that the proxy protocols being forwarded are also encrypted, and the fact that users mainly use them for HTTPS connections nowadays, you are essentially doing triple encryption. If you need a proxy, use our proxy mode.
|
||||
The (optional) `obfs` option obfuscates the protocol using the provided password, so that it is not apparent that this
|
||||
is Hysteria/QUIC, which could be useful for bypassing DPI blocking or QoS. If the passwords of the server and client do
|
||||
not match, no connection can be established. Therefore, this can also serve as a simple password authentication. For
|
||||
more advanced authentication schemes, see `auth` below.
|
||||
|
||||
`up_mbps` and `down_mbps` limit the maximum upload and download speed of the server for each client. These are also
|
||||
optional and can be removed if not needed.
|
||||
|
||||
To launch the server, simply run
|
||||
|
||||
```
|
||||
./cmd_linux_amd64 server
|
||||
```
|
||||
|
||||
If your config file is not named `config.json` or is in a different path, specify it with `-config`:
|
||||
|
||||
```
|
||||
./cmd_linux_amd64 server -config blah.json
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
Same as the server side, create a `config.json` under the root directory of the program:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": "example.com:36712",
|
||||
"obfs": "AMOGUS",
|
||||
"up_mbps": 10,
|
||||
"down_mbps": 50,
|
||||
"socks5": {
|
||||
"listen": "127.0.0.1:1080"
|
||||
},
|
||||
"http": {
|
||||
"listen": "127.0.0.1:8080"
|
||||
},
|
||||
"relay": {
|
||||
"listen": "127.0.0.1:2222",
|
||||
"remote": "123.123.123.123:22"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This config enables a SOCKS5 proxy (with both TCP & UDP support), an HTTP proxy, and a TCP relay to `123.123.123.123:22`
|
||||
at the same time. Please modify or remove these entries depending on your actual needs.
|
||||
|
||||
If your server certificate is not issued by a trusted CA, you need to specify the CA used
|
||||
with `"ca": "/path/to/file.ca"` on the client or use `"insecure": true` to ignore all certificate errors (not
|
||||
recommended).
|
||||
|
||||
`up_mbps` and `down_mbps` are mandatory on the client side. Please try to fill in these values as accurately as possible
|
||||
according to your network conditions. They are crucial for Hysteria to work in an optimal state.
|
||||
|
||||
Some users may attempt to forward other encrypted proxy protocols such as Shadowsocks with relay. While this technically
|
||||
works, it's not optimal from a performance standpoint - Hysteria itself uses TLS, considering that the proxy protocol
|
||||
being forwarded is also encrypted, and the fact that almost all sites are now using HTTPS, it essentially becomes triple
|
||||
encryption. If you need a proxy, just use our proxy modes.
|
||||
|
||||
## Comparison
|
||||
|
||||
Proxy Client: Guangzhou, China Mobile Broadband 100 Mbps
|
||||
|
||||
|
||||
Proxy Server: AWS US West Oregon (us-west-2)
|
||||
|
||||

|
||||
|
||||
## Advanced usage
|
||||
|
||||
The command line program supports loading configurations from both JSON files and arguments. Use `-config` to specify a JSON file. Config loaded from it can also be overwritten or extended with command line arguments.
|
||||
### Server
|
||||
|
||||
### Proxy server
|
||||
```json5
|
||||
{
|
||||
"listen": ":36712", // Listen address
|
||||
"cert": "/home/ubuntu/my_cert.crt", // Cert file
|
||||
"key": "/home/ubuntu/my_key.crt", // Key file
|
||||
"up_mbps": 100, // Max upload Mbps per client
|
||||
"down_mbps": 100, // Max download Mbps per client
|
||||
"disable_udp": false, // Disable UDP support
|
||||
"acl": "my_list.acl", // See ACL below
|
||||
"obfs": "AMOGUS", // Obfuscation password
|
||||
"auth": { // Authentication
|
||||
"mode": "password", // Mode, only supports "password" and "none" for now
|
||||
"config": {
|
||||
"password": "yubiyubi"
|
||||
}
|
||||
},
|
||||
"recv_window_conn": 33554432, // QUIC stream receive window
|
||||
"recv_window_client": 67108864, // QUIC connection receive window
|
||||
"max_conn_client": 4096 // Max concurrent connections per client
|
||||
}
|
||||
```
|
||||
|
||||
| Description | JSON config field | CLI argument |
|
||||
| --- | --- | --- |
|
||||
| Server listen address | listen | -listen |
|
||||
| Disable UDP support | disable_udp | -disable-udp |
|
||||
| Access control list | acl | -acl |
|
||||
| TLS certificate file | cert | -cert |
|
||||
| TLS key file | key | -key |
|
||||
| Authentication file | auth | -auth |
|
||||
| Max upload speed per client in Mbps | up_mbps | -up-mbps |
|
||||
| Max download speed per client in Mbps | down_mbps | -down-mbps |
|
||||
| Max receive window size per connection | recv_window_conn | -recv-window-conn |
|
||||
| Max receive window size per client | recv_window_client | -recv-window-client |
|
||||
| Max simultaneous connections allowed per client | max_conn_client | -max-conn-client |
|
||||
| Obfuscation key | obfs | -obfs |
|
||||
### Client
|
||||
|
||||
### Proxy client
|
||||
```json5
|
||||
{
|
||||
"server": "example.com:36712", // Server address
|
||||
"up_mbps": 10, // Max upload Mbps
|
||||
"down_mbps": 50, // Max download Mbps
|
||||
"socks5": {
|
||||
"listen": "127.0.0.1:1080", // SOCKS5 listen address
|
||||
"timeout": 300, // TCP timeout in seconds
|
||||
"disable_udp": false, // Disable UDP support
|
||||
"user": "me", // SOCKS5 authentication username
|
||||
"password": "lmaolmao" // SOCKS5 authentication password
|
||||
},
|
||||
"http": {
|
||||
"listen": "127.0.0.1:8080", // HTTP listen address
|
||||
"timeout": 300, // TCP timeout in seconds
|
||||
"user": "me", // HTTP authentication username
|
||||
"password": "lmaolmao", // HTTP authentication password
|
||||
"cert": "/home/ubuntu/my_cert.crt", // Cert file (HTTPS proxy)
|
||||
"key": "/home/ubuntu/my_key.crt" // Key file (HTTPS proxy)
|
||||
},
|
||||
"relay": {
|
||||
"listen": "127.0.0.1:2222", // Relay listen address
|
||||
"remote": "123.123.123.123:22", // Relay remote address
|
||||
"timeout": 300 // TCP timeout in seconds
|
||||
},
|
||||
"acl": "my_list.acl", // See ACL below
|
||||
"obfs": "AMOGUS", // Obfuscation password
|
||||
"auth": "[BASE64]", // Authentication payload in Base64
|
||||
"auth_str": "yubiyubi", // Authentication payload in string, mutually exclusive with the option above
|
||||
"insecure": false, // Ignore all certificate errors
|
||||
"ca": "my.ca", // Custom CA file
|
||||
"recv_window_conn": 33554432, // QUIC stream receive window
|
||||
"recv_window": 67108864 // QUIC connection receive window
|
||||
}
|
||||
```
|
||||
|
||||
| Description | JSON config field | CLI argument |
|
||||
| --- | --- | --- |
|
||||
| SOCKS5 listen address | socks5_addr | -socks5-addr |
|
||||
| SOCKS5 connection timeout in seconds | socks5_timeout | -socks5-timeout |
|
||||
| Disable SOCKS5 UDP support | socks5_disable_udp | -socks5-disable-udp |
|
||||
| SOCKS5 auth username | socks5_user | -socks5-user |
|
||||
| SOCKS5 auth password | socks5_password | -socks5-password |
|
||||
| HTTP listen address | http_addr | -http-addr |
|
||||
| HTTP connection timeout in seconds | http_timeout | -http-timeout |
|
||||
| HTTP basic auth username | http_user | -http-user |
|
||||
| HTTP basic auth password | http_password | -http-password |
|
||||
| HTTPS certificate file | https_cert | -http-cert |
|
||||
| HTTPS key file | https_key | -http-key |
|
||||
| Access control list | acl | -acl |
|
||||
| Server address | server | -server |
|
||||
| Authentication username | username | -username |
|
||||
| Authentication password | password | -password |
|
||||
| Ignore TLS certificate errors | insecure | -insecure |
|
||||
| Specify a trusted CA file | ca | -ca |
|
||||
| Upload speed in Mbps | up_mbps | -up-mbps |
|
||||
| Download speed in Mbps | down_mbps | -down-mbps |
|
||||
| Max receive window size per connection | recv_window_conn | -recv-window-conn |
|
||||
| Max receive window size | recv_window | -recv-window |
|
||||
| Obfuscation key | obfs | -obfs |
|
||||
|
||||
#### About SOCKS5
|
||||
|
||||
Supports TCP (CONNECT) and UDP (ASSOCIATE) commands. BIND is not supported and is not planned to be supported.
|
||||
|
||||
#### About ACL
|
||||
## ACL
|
||||
|
||||
[ACL File Format](ACL.md)
|
||||
|
||||
#### About proxy authentication
|
||||
## Logging
|
||||
|
||||
Proxy supports username and password authentication (sent encrypted with TLS). If the server starts with an authentication file, it will check for the existence of the corresponding username and password in this file when each user connects. A valid authentication file is a text file with a pair of username and password per line (separated by a space). Example:
|
||||
```
|
||||
admin K2MfcwyZNJy3
|
||||
shady_hacker smokeweed420
|
||||
The program outputs `DEBUG` level, text format logs via stdout by default.
|
||||
|
||||
This line is invalid and will be ignored
|
||||
```
|
||||
Changes to the file take effect immediately while the server is running.
|
||||
|
||||
#### About obfuscation
|
||||
|
||||
To prevent firewalls from potentially detecting & blocking the protocol, a simple XOR-based packet obfuscation mechanism has been built in. Note that clients and servers with different obfuscation settings are not be able to communicate at all.
|
||||
|
||||
### Relay server
|
||||
|
||||
| Description | JSON config field | CLI argument |
|
||||
| --- | --- | --- |
|
||||
| Server listen address | listen | -listen |
|
||||
| Remote relay address | remote | -remote |
|
||||
| TLS certificate file | cert | -cert |
|
||||
| TLS key file | key | -key |
|
||||
| Max upload speed per client in Mbps | up_mbps | -up-mbps |
|
||||
| Max download speed per client in Mbps | down_mbps | -down-mbps |
|
||||
| Max receive window size per connection | recv_window_conn | -recv-window-conn |
|
||||
| Max receive window size per client | recv_window_client | -recv-window-client |
|
||||
| Max simultaneous connections allowed per client | max_conn_client | -max-conn-client |
|
||||
| Obfuscation key | obfs | -obfs |
|
||||
|
||||
### Relay client
|
||||
|
||||
| Description | JSON config field | CLI argument |
|
||||
| --- | --- | --- |
|
||||
| TCP listen address | listen | -listen |
|
||||
| Server address | server | -server |
|
||||
| Client name presented to the server | name | -name |
|
||||
| Ignore TLS certificate errors | insecure | -insecure |
|
||||
| Specify a trusted CA file | ca | -ca |
|
||||
| Upload speed in Mbps | up_mbps | -up-mbps |
|
||||
| Download speed in Mbps | down_mbps | -down-mbps |
|
||||
| Max receive window size per connection | recv_window_conn | -recv-window-conn |
|
||||
| Max receive window size | recv_window | -recv-window |
|
||||
| Obfuscation key | obfs | -obfs |
|
||||
|
||||
## Logs
|
||||
|
||||
By default, the program outputs DEBUG level, text format logs via stdout.
|
||||
|
||||
To change the logging level, set `LOGGING_LEVEL` environment variable, which supports `panic`, `fatal`, `error`, `warn`, `info`, ` debug`, `trace`
|
||||
To change the logging level, use `LOGGING_LEVEL` environment variable. The available levels are `panic`, `fatal`
|
||||
, `error`, `warn`, `info`, ` debug`, `trace`
|
||||
|
||||
To print JSON instead, set `LOGGING_FORMATTER` to `json`
|
||||
|
||||
|
255
README.zh.md
255
README.zh.md
@@ -3,162 +3,169 @@
|
||||
[![License][1]][2] [![Release][3]][4] [![Telegram][5]][6]
|
||||
|
||||
[1]: https://img.shields.io/github/license/tobyxdd/hysteria?style=flat-square
|
||||
|
||||
[2]: LICENSE.md
|
||||
|
||||
[3]: https://img.shields.io/github/v/release/tobyxdd/hysteria?style=flat-square
|
||||
|
||||
[4]: https://github.com/tobyxdd/hysteria/releases
|
||||
|
||||
[5]: https://img.shields.io/badge/chat-Telegram-blue?style=flat-square
|
||||
|
||||
[6]: https://t.me/hysteria_github
|
||||
|
||||
Hysteria 是专门针对恶劣网络环境(常见于在中国访问海外服务器)进行优化的连接转发和代理工具(即所谓的双边加速)。其基于修改版的 QUIC 协议,可以理解为是我此前弃坑的项目 https://github.com/dragonite-network/dragonite-java 的续作。
|
||||
Hysteria 是专门针对恶劣网络环境(常见于卫星网络、在中国连接国外服务器等)进行优化的 TCP 连接转发和代理工具(即所谓的双边加速),
|
||||
基于修改版的 QUIC 协议。
|
||||
|
||||
可以理解为是我此前弃坑的项目 https://github.com/dragonite-network/dragonite-java 的续作。
|
||||
|
||||
## 快速入门
|
||||
|
||||
关于每个参数具体的含义请见 [高级用法](#高级用法)
|
||||
注意:本节提供的配置只是为了快速上手,可能无法满足你的需求。请到 [高级用法](#高级用法) 中查看所有可用选项及其含义。
|
||||
|
||||
### 代理
|
||||
### 服务器
|
||||
|
||||
服务端
|
||||
```
|
||||
./cmd_linux_amd64 proxy server -listen :36712 -cert example.crt -key example.key -obfs BlueberryFaygo
|
||||
```
|
||||
服务端需要一个 TLS 证书(不一定是由可信 CA 签发的有效证书)。如果你使用自签证书,请在客户端使用 `-ca` 指定你自己的 CA 文件,或者用 `-insecure` 忽略所有证书错误(不推荐)
|
||||
在目录下建立一个 `config.json`
|
||||
|
||||
客户端
|
||||
```
|
||||
./cmd_linux_amd64 proxy client -server example.com:36712 -socks5-addr localhost:1080 -up-mbps 10 -down-mbps 50 -obfs BlueberryFaygo
|
||||
```
|
||||
在客户端的本地 TCP 1080 上启动一个 SOCKS5 代理服务器供其他程序使用。
|
||||
|
||||
除了 SOCKS5 还支持 HTTP 代理 (`-http-addr` & `-http-timeout`)。两个模式可以同时开在不同端口。
|
||||
|
||||
`-up-mbps 10 -down-mbps 50` 是告诉服务端你的下行速度为 50 Mbps, 上行 10 Mbps。根据实际网络条件正确设置客户端的上传和下载速度十分重要!
|
||||
|
||||
### 转发
|
||||
|
||||
假设你想转发服务端上 `localhost:8080` 的一个 TCP 协议程序。
|
||||
|
||||
服务端
|
||||
```
|
||||
./cmd_linux_amd64 relay server -listen :36712 -remote localhost:8080 -cert example.crt -key example.key
|
||||
```json
|
||||
{
|
||||
"listen": ":36712",
|
||||
"cert": "/home/ubuntu/my_cert.crt",
|
||||
"key": "/home/ubuntu/my_key.crt",
|
||||
"obfs": "AMOGUS",
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100
|
||||
}
|
||||
```
|
||||
|
||||
客户端
|
||||
```
|
||||
./cmd_linux_amd64 relay client -server example.com:36712 -listen localhost:8080 -up-mbps 10 -down-mbps 50
|
||||
```
|
||||
所有到客户端本地 TCP 8080 的 TCP 连接都将通过转发,到服务器连接那里的 `localhost:8080`
|
||||
服务端必须要一个 TLS 证书(但并非一定要受信 CA 签发的)。
|
||||
|
||||
有些用户可能会尝试用这个功能转发其他加密代理协议,比如Shadowsocks。虽然这完全可行,但从性能的角度并不是最佳选择 - 我们的协议本身就有 TLS,转发的代理协议也是加密的,再加上用户用来访问 HTTPS 网站,等于做了三重加密。如果需要代理就用我们的代理模式。
|
||||
`obfs` 选项使用提供的密码对协议进行混淆,这样协议就不容易被检测出是 Hysteria/QUIC,可以用来绕过针对性的 DPI 屏蔽或者 QoS。
|
||||
如果服务端和客户端的密码不匹配就不能建立连接,因此这也可以作为一个简单的密码验证。对于更高级的验证方案请见下文 `auth`。
|
||||
|
||||
`up_mbps` 和 `down_mbps` 限制服务器对每个客户端的最大上传和下载速度。这些也是可选的,如果不需要可以移除。
|
||||
|
||||
要启动服务端,只需运行
|
||||
|
||||
```
|
||||
./cmd_linux_amd64 server
|
||||
```
|
||||
|
||||
如果你的配置文件没有命名为 `config.json` 或在别的路径,请用 `-config` 指定路径:
|
||||
|
||||
```
|
||||
./cmd_linux_amd64 server -config blah.json
|
||||
```
|
||||
|
||||
### 客户端
|
||||
|
||||
和服务器端一样,在程序根目录下建立一个`config.json`。
|
||||
|
||||
```json
|
||||
{
|
||||
"server": "example.com:36712",
|
||||
"obfs": "AMOGUS",
|
||||
"up_mbps": 10,
|
||||
"down_mbps": 50,
|
||||
"socks5": {
|
||||
"listen": "127.0.0.1:1080"
|
||||
},
|
||||
"http": {
|
||||
"listen": "127.0.0.1:8080"
|
||||
},
|
||||
"relay": {
|
||||
"listen": "127.0.0.1:2222",
|
||||
"remote": "123.123.123.123:22"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个配置同时开了 SOCK5 (支持 TCP & UDP) 代理,HTTP 代理和到 `123.123.123.123:22` 的 TCP 转发。请根据自己实际需要修改和删减。
|
||||
|
||||
如果你的服务端证书不是由受信任的 CA 签发的,需要用 `"ca": "/path/to/file.ca"` 指定使用的 CA 或者用 `"insecure": true` 忽略所有
|
||||
证书错误(不推荐)。
|
||||
|
||||
`up_mbps` 和 `down_mbps` 在客户端是必填选项,请根据实际网络情况尽量准确地填写,否则将影响 Hysteria 的使用体验。
|
||||
|
||||
有些用户可能会尝试用这个功能转发其他加密代理协议,比如 Shadowsocks。这样虽然可行,但从性能的角度不推荐 - Hysteria 本身就用 TLS,
|
||||
转发的代理协议也是加密的,再加上如今几乎所有网站都是 HTTPS 了,等于做了三重加密。如果需要代理就用我们的代理模式。
|
||||
|
||||
## 对比
|
||||
|
||||
代理客户端:广州移动宽带 100M
|
||||
|
||||
|
||||
代理服务端:AWS 美西 Oregon (us-west-2) (最差线路之一)
|
||||
|
||||

|
||||
|
||||
## 高级用法
|
||||
|
||||
命令行程序支持从 JSON 文件和参数加载配置。使用 `-config` 指定一个JSON文件。从文件加载的配置也可以被命令行参数覆盖或进一步扩展。
|
||||
### Server
|
||||
|
||||
### 代理 服务端
|
||||
```json5
|
||||
{
|
||||
"listen": ":36712", // 监听地址
|
||||
"cert": "/home/ubuntu/my_cert.crt", // 证书
|
||||
"key": "/home/ubuntu/my_key.crt", // 证书密钥
|
||||
"up_mbps": 100, // 单客户端最大上传速度
|
||||
"down_mbps": 100, // 单客户端最大下载速度
|
||||
"disable_udp": false, // 禁用 UDP 支持
|
||||
"acl": "my_list.acl", // 见下文 ACL
|
||||
"obfs": "AMOGUS", // 混淆密码
|
||||
"auth": { // 验证
|
||||
"mode": "password", // 验证模式,暂时只支持 "password" 与 "none"
|
||||
"config": {
|
||||
"password": "yubiyubi"
|
||||
}
|
||||
},
|
||||
"recv_window_conn": 33554432, // QUIC stream receive window
|
||||
"recv_window_client": 67108864, // QUIC connection receive window
|
||||
"max_conn_client": 4096 // 单客户端最大活跃连接数
|
||||
}
|
||||
```
|
||||
|
||||
| 描述 | JSON 字段 | 命令行参数 |
|
||||
| --- | --- | --- |
|
||||
| 服务端监听地址 | listen | -listen |
|
||||
| 禁用 UDP 支持 | disable_udp | -disable-udp |
|
||||
| ACL 规则文件 | acl | -acl |
|
||||
| TLS 证书文件 | cert | -cert |
|
||||
| TLS 密钥文件 | key | -key |
|
||||
| 用户名密码验证文件 | auth | -auth |
|
||||
| 单客户端最大上传速度 Mbps | up_mbps | -up-mbps |
|
||||
| 单客户端最大下载速度 Mbps | down_mbps | -down-mbps |
|
||||
| 单连接最大接收窗口大小 | recv_window_conn | -recv-window-conn |
|
||||
| 单客户端最大接收窗口大小 | recv_window_client | -recv-window-client |
|
||||
| 单客户端最大连接数 | max_conn_client | -max-conn-client |
|
||||
| 混淆密钥 | obfs | -obfs |
|
||||
### Client
|
||||
|
||||
### 代理 客户端
|
||||
```json5
|
||||
{
|
||||
"server": "example.com:36712", // 服务器地址
|
||||
"up_mbps": 10, // 最大上传速度
|
||||
"down_mbps": 50, // 最大下载速度
|
||||
"socks5": {
|
||||
"listen": "127.0.0.1:1080", // SOCKS5 监听地址
|
||||
"timeout": 300, // TCP 超时秒数
|
||||
"disable_udp": false, // 禁用 UDP 支持
|
||||
"user": "me", // SOCKS5 验证用户名
|
||||
"password": "lmaolmao" // SOCKS5 验证密码
|
||||
},
|
||||
"http": {
|
||||
"listen": "127.0.0.1:8080", // HTTP 监听地址
|
||||
"timeout": 300, // TCP 超时秒数
|
||||
"user": "me", // HTTP 验证用户名
|
||||
"password": "lmaolmao", // HTTP 验证密码
|
||||
"cert": "/home/ubuntu/my_cert.crt", // 证书 (变为 HTTPS 代理)
|
||||
"key": "/home/ubuntu/my_key.crt" // 证书密钥 (变为 HTTPS 代理)
|
||||
},
|
||||
"relay": {
|
||||
"listen": "127.0.0.1:2222", // 转发监听地址
|
||||
"remote": "123.123.123.123:22", // 转发目标地址
|
||||
"timeout": 300 // TCP 超时秒数
|
||||
},
|
||||
"acl": "my_list.acl", // 见下文 ACL
|
||||
"obfs": "AMOGUS", // 混淆密码
|
||||
"auth": "[BASE64]", // Base64 验证密钥
|
||||
"auth_str": "yubiyubi", // 字符串验证密钥,和上面的选项二选一
|
||||
"insecure": false, // 忽略一切证书错误
|
||||
"ca": "my.ca", // 自定义 CA
|
||||
"recv_window_conn": 33554432, // QUIC stream receive window
|
||||
"recv_window": 67108864 // QUIC connection receive window
|
||||
}
|
||||
```
|
||||
|
||||
| 描述 | JSON 字段 | 命令行参数 |
|
||||
| --- | --- | --- |
|
||||
| SOCKS5 监听地址 | socks5_addr | -socks5-addr |
|
||||
| SOCKS5 超时时间(秒) | socks5_timeout | -socks5-timeout |
|
||||
| 禁用 SOCKS5 UDP 支持 | socks5_disable_udp | -socks5-disable-udp |
|
||||
| SOCKS5 验证用户名 | socks5_user | -socks5-user |
|
||||
| SOCKS5 验证密码 | socks5_password | -socks5-password |
|
||||
| HTTP 监听地址 | http_addr | -http-addr |
|
||||
| HTTP 超时时间(秒) | http_timeout | -http-timeout |
|
||||
| HTTP 验证用户名 | http_user | -http-user |
|
||||
| HTTP 验证密码 | http_password | -http-password |
|
||||
| HTTPS 证书文件 | https_cert | -http-cert |
|
||||
| HTTPS 密钥文件 | https_key | -http-key |
|
||||
| ACL 规则文件 | acl | -acl |
|
||||
| 服务端地址 | server | -server |
|
||||
| 验证用户名 | username | -username |
|
||||
| 验证密码 | password | -password |
|
||||
| 忽略证书错误 | insecure | -insecure |
|
||||
| 指定可信 CA 文件 | ca | -ca |
|
||||
| 上传速度 Mbps | up_mbps | -up-mbps |
|
||||
| 下载速度 Mbps | down_mbps | -down-mbps |
|
||||
| 单连接最大接收窗口大小 | recv_window_conn | -recv-window-conn |
|
||||
| 总最大接收窗口大小 | recv_window | -recv-window |
|
||||
| 混淆密钥 | obfs | -obfs |
|
||||
|
||||
#### 关于 SOCKS5
|
||||
|
||||
支持 TCP (CONNECT) 和 UDP (ASSOCIATE),不支持 BIND 也无计划支持。
|
||||
|
||||
#### 关于 ACL
|
||||
## 关于 ACL
|
||||
|
||||
[ACL 文件格式](ACL.zh.md)
|
||||
|
||||
#### 关于用户名密码验证
|
||||
|
||||
代理支持用户名和密码认证(经过 TLS 加密发送)。如果服务器启动时指定了一个验证文件,当每个用户连接时,服务器会检查该文件中是否存在相应的用户名和密码。验证文件是一个文本文件,每行有一对用户名和密码(用空格分割)。比如:
|
||||
```
|
||||
admin K2MfcwyZNJy3
|
||||
shady_hacker smokeweed420
|
||||
|
||||
这行无效会被忽略
|
||||
```
|
||||
对文件的更改立即生效,即使服务端正在运行。
|
||||
|
||||
#### 关于混淆
|
||||
|
||||
为了防止各类防火墙今后可能检测并阻止协议,程序内置了简单的基于 XOR 的数据包混淆机制。注意客户端和服务器的混淆设置如果不同则完全无法通信。
|
||||
|
||||
### 转发 服务端
|
||||
|
||||
| 描述 | JSON 字段 | 命令行参数 |
|
||||
| --- | --- | --- |
|
||||
| 服务端监听地址 | listen | -listen |
|
||||
| 转发目标地址 | remote | -remote |
|
||||
| TLS 证书文件 | cert | -cert |
|
||||
| TLS 密钥文件 | key | -key |
|
||||
| 单客户端最大上传速度 Mbps | up_mbps | -up-mbps |
|
||||
| 单客户端最大下载速度 Mbps | down_mbps | -down-mbps |
|
||||
| 单连接最大接收窗口大小 | recv_window_conn | -recv-window-conn |
|
||||
| 单客户端最大接收窗口大小 | recv_window_client | -recv-window-client |
|
||||
| 单客户端最大连接数 | max_conn_client | -max-conn-client |
|
||||
| 混淆密钥 | obfs | -obfs |
|
||||
|
||||
### 转发 客户端
|
||||
|
||||
| 描述 | JSON 字段 | 命令行参数 |
|
||||
| --- | --- | --- |
|
||||
| TCP 监听地址 | listen | -listen |
|
||||
| 服务端地址 | server | -server |
|
||||
| 客户端名称 | name | -name |
|
||||
| 忽略证书错误 | insecure | -insecure |
|
||||
| 指定可信 CA 文件 | ca | -ca |
|
||||
| 上传速度 Mbps | up_mbps | -up-mbps |
|
||||
| 下载速度 Mbps | down_mbps | -down-mbps |
|
||||
| 单连接最大接收窗口大小 | recv_window_conn | -recv-window-conn |
|
||||
| 总最大接收窗口大小 | recv_window | -recv-window |
|
||||
| 混淆密钥 | obfs | -obfs |
|
||||
|
||||
## 日志
|
||||
|
||||
程序默认在 stdout 输出 DEBUG 级别,文字格式的日志。
|
||||
|
5
build.ps1
Normal file
5
build.ps1
Normal file
@@ -0,0 +1,5 @@
|
||||
$env:GOOS = "windows"
|
||||
go build -ldflags="-w -s" -o "hy_windows.exe" ./cmd
|
||||
|
||||
$env:GOOS = "linux"
|
||||
go build -ldflags="-w -s" -o "hy_linux" ./cmd
|
@@ -1 +0,0 @@
|
||||
go build -ldflags="-w -s" ./cmd
|
216
cmd/client.go
Normal file
216
cmd/client.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
hyHTTP "github.com/tobyxdd/hysteria/pkg/http"
|
||||
"github.com/tobyxdd/hysteria/pkg/obfs"
|
||||
"github.com/tobyxdd/hysteria/pkg/relay"
|
||||
"github.com/tobyxdd/hysteria/pkg/socks5"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func client(config *clientConfig) {
|
||||
logrus.WithField("config", config.String()).Info("Client configuration loaded")
|
||||
// TLS
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: config.Insecure,
|
||||
NextProtos: []string{tlsProtocolName},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
// Load CA
|
||||
if len(config.CustomCA) > 0 {
|
||||
bs, err := ioutil.ReadFile(config.CustomCA)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.CustomCA,
|
||||
}).Fatal("Failed to load CA")
|
||||
}
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(bs) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"file": config.CustomCA,
|
||||
}).Fatal("Failed to parse CA")
|
||||
}
|
||||
tlsConfig.RootCAs = cp
|
||||
}
|
||||
// QUIC config
|
||||
quicConfig := &quic.Config{
|
||||
MaxStreamReceiveWindow: config.ReceiveWindowConn,
|
||||
MaxConnectionReceiveWindow: config.ReceiveWindow,
|
||||
KeepAlive: true,
|
||||
EnableDatagrams: true,
|
||||
}
|
||||
if quicConfig.MaxStreamReceiveWindow == 0 {
|
||||
quicConfig.MaxStreamReceiveWindow = DefaultMaxReceiveStreamFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxConnectionReceiveWindow == 0 {
|
||||
quicConfig.MaxConnectionReceiveWindow = DefaultMaxReceiveConnectionFlowControlWindow
|
||||
}
|
||||
// Auth
|
||||
var auth []byte
|
||||
if len(config.Auth) > 0 {
|
||||
auth = config.Auth
|
||||
} else {
|
||||
auth = []byte(config.AuthString)
|
||||
}
|
||||
// Obfuscator
|
||||
var obfuscator core.Obfuscator
|
||||
if len(config.Obfs) > 0 {
|
||||
obfuscator = obfs.XORObfuscator(config.Obfs)
|
||||
}
|
||||
// ACL
|
||||
var aclEngine *acl.Engine
|
||||
if len(config.ACL) > 0 {
|
||||
var err error
|
||||
aclEngine, err = acl.LoadFromFile(config.ACL)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.ACL,
|
||||
}).Fatal("Failed to parse ACL")
|
||||
}
|
||||
}
|
||||
// Client
|
||||
client, err := core.NewClient(config.Server, auth, tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.CongestionControl {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
}, obfuscator)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize client")
|
||||
}
|
||||
defer client.Close()
|
||||
logrus.WithField("addr", config.Server).Info("Connected")
|
||||
|
||||
// Local
|
||||
errChan := make(chan error)
|
||||
if len(config.SOCKS5.Listen) > 0 {
|
||||
go func() {
|
||||
var authFunc func(user, password string) bool
|
||||
if config.SOCKS5.User != "" && config.SOCKS5.Password != "" {
|
||||
authFunc = func(user, password string) bool {
|
||||
return config.SOCKS5.User == user && config.SOCKS5.Password == password
|
||||
}
|
||||
}
|
||||
socks5server, err := socks5.NewServer(client, config.SOCKS5.Listen, authFunc,
|
||||
time.Duration(config.SOCKS5.Timeout)*time.Second, aclEngine, config.SOCKS5.DisableUDP,
|
||||
func(addr net.Addr, reqAddr string, action acl.Action, arg string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": actionToString(action, arg),
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("SOCKS5 TCP request")
|
||||
},
|
||||
func(addr net.Addr, reqAddr string, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Info("SOCKS5 TCP error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("SOCKS5 TCP EOF")
|
||||
}
|
||||
},
|
||||
func(addr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
}).Debug("SOCKS5 UDP associate")
|
||||
},
|
||||
func(addr net.Addr, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": addr.String(),
|
||||
}).Info("SOCKS5 UDP error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
}).Debug("SOCKS5 UDP EOF")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize SOCKS5 server")
|
||||
}
|
||||
logrus.WithField("addr", config.SOCKS5.Listen).Info("SOCKS5 server up and running")
|
||||
errChan <- socks5server.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
||||
if len(config.HTTP.Listen) > 0 {
|
||||
go func() {
|
||||
var authFunc func(user, password string) bool
|
||||
if config.HTTP.User != "" && config.HTTP.Password != "" {
|
||||
authFunc = func(user, password string) bool {
|
||||
return config.HTTP.User == user && config.HTTP.Password == password
|
||||
}
|
||||
}
|
||||
proxy, err := hyHTTP.NewProxyHTTPServer(client, time.Duration(config.HTTP.Timeout)*time.Second, aclEngine,
|
||||
func(reqAddr string, action acl.Action, arg string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": actionToString(action, arg),
|
||||
"dst": reqAddr,
|
||||
}).Debug("HTTP request")
|
||||
},
|
||||
authFunc)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize HTTP server")
|
||||
}
|
||||
if config.HTTP.Cert != "" && config.HTTP.Key != "" {
|
||||
logrus.WithField("addr", config.HTTP.Listen).Info("HTTPS server up and running")
|
||||
errChan <- http.ListenAndServeTLS(config.HTTP.Listen, config.HTTP.Cert, config.HTTP.Key, proxy)
|
||||
} else {
|
||||
logrus.WithField("addr", config.HTTP.Listen).Info("HTTP server up and running")
|
||||
errChan <- http.ListenAndServe(config.HTTP.Listen, proxy)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if len(config.Relay.Listen) > 0 {
|
||||
go func() {
|
||||
rl, err := relay.NewRelay(client, config.Relay.Listen, config.Relay.Remote,
|
||||
time.Duration(config.Relay.Timeout)*time.Second,
|
||||
func(addr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
}).Debug("TCP relay request")
|
||||
},
|
||||
func(addr net.Addr, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": addr.String(),
|
||||
}).Info("TCP relay error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
}).Debug("TCP relay EOF")
|
||||
}
|
||||
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize TCP relay")
|
||||
}
|
||||
logrus.WithField("addr", config.Relay.Listen).Info("TCP relay up and running")
|
||||
errChan <- rl.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
||||
err = <-errChan
|
||||
logrus.WithField("error", err).Fatal("Client shutdown")
|
||||
}
|
176
cmd/config.go
176
cmd/config.go
@@ -1,86 +1,128 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/yosuke-furukawa/json5/encoding/json5"
|
||||
)
|
||||
|
||||
const (
|
||||
mbpsToBps = 125000
|
||||
dialTimeout = 10 * time.Second
|
||||
mbpsToBps = 125000
|
||||
|
||||
DefaultMaxReceiveStreamFlowControlWindow = 33554432
|
||||
DefaultMaxReceiveConnectionFlowControlWindow = 67108864
|
||||
DefaultMaxIncomingStreams = 200
|
||||
DefaultMaxIncomingStreams = 1024
|
||||
|
||||
tlsProtocolName = "hysteria"
|
||||
)
|
||||
|
||||
func loadConfig(cfg interface{}, args []string) error {
|
||||
cfgVal := reflect.ValueOf(cfg).Elem()
|
||||
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
fsValMap := make(map[reflect.Value]interface{}, cfgVal.NumField())
|
||||
for i := 0; i < cfgVal.NumField(); i++ {
|
||||
structField := cfgVal.Type().Field(i)
|
||||
tag := structField.Tag
|
||||
switch structField.Type.Kind() {
|
||||
case reflect.String:
|
||||
fsValMap[cfgVal.Field(i)] =
|
||||
fs.String(jsonTagToFlagName(tag.Get("json")), "", tag.Get("desc"))
|
||||
case reflect.Int:
|
||||
fsValMap[cfgVal.Field(i)] =
|
||||
fs.Int(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc"))
|
||||
case reflect.Uint64:
|
||||
fsValMap[cfgVal.Field(i)] =
|
||||
fs.Uint64(jsonTagToFlagName(tag.Get("json")), 0, tag.Get("desc"))
|
||||
case reflect.Bool:
|
||||
var bf optionalBoolFlag
|
||||
fs.Var(&bf, jsonTagToFlagName(tag.Get("json")), tag.Get("desc"))
|
||||
fsValMap[cfgVal.Field(i)] = &bf
|
||||
}
|
||||
type serverConfig struct {
|
||||
Listen string `json:"listen"`
|
||||
CertFile string `json:"cert"`
|
||||
KeyFile string `json:"key"`
|
||||
// Optional below
|
||||
UpMbps int `json:"up_mbps"`
|
||||
DownMbps int `json:"down_mbps"`
|
||||
DisableUDP bool `json:"disable_udp"`
|
||||
ACL string `json:"acl"`
|
||||
Obfs string `json:"obfs"`
|
||||
Auth struct {
|
||||
Mode string `json:"mode"`
|
||||
Config json5.RawMessage `json:"config"`
|
||||
} `json:"auth"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn"`
|
||||
ReceiveWindowClient uint64 `json:"recv_window_client"`
|
||||
MaxConnClient int `json:"max_conn_client"`
|
||||
}
|
||||
|
||||
func (c *serverConfig) Check() error {
|
||||
if len(c.Listen) == 0 {
|
||||
return errors.New("no listen address")
|
||||
}
|
||||
configFile := fs.String("config", "", "Configuration file")
|
||||
// Parse
|
||||
if err := fs.Parse(args); err != nil {
|
||||
os.Exit(1)
|
||||
if len(c.CertFile) == 0 || len(c.KeyFile) == 0 {
|
||||
return errors.New("TLS cert or key not provided")
|
||||
}
|
||||
// Put together the config
|
||||
if len(*configFile) > 0 {
|
||||
cb, err := ioutil.ReadFile(*configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(cb, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.UpMbps < 0 || c.DownMbps < 0 {
|
||||
return errors.New("invalid speed")
|
||||
}
|
||||
// Flags override config from file
|
||||
for field, val := range fsValMap {
|
||||
switch v := val.(type) {
|
||||
case *string:
|
||||
if len(*v) > 0 {
|
||||
field.SetString(*v)
|
||||
}
|
||||
case *int:
|
||||
if *v != 0 {
|
||||
field.SetInt(int64(*v))
|
||||
}
|
||||
case *uint64:
|
||||
if *v != 0 {
|
||||
field.SetUint(*v)
|
||||
}
|
||||
case *optionalBoolFlag:
|
||||
if v.Exists {
|
||||
field.SetBool(v.Value)
|
||||
}
|
||||
}
|
||||
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
|
||||
(c.ReceiveWindowClient != 0 && c.ReceiveWindowClient < 65536) {
|
||||
return errors.New("invalid receive window size")
|
||||
}
|
||||
if c.MaxConnClient < 0 {
|
||||
return errors.New("invalid max connections per client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func jsonTagToFlagName(tag string) string {
|
||||
return strings.ReplaceAll(tag, "_", "-")
|
||||
func (c *serverConfig) String() string {
|
||||
return fmt.Sprintf("%+v", *c)
|
||||
}
|
||||
|
||||
type clientConfig struct {
|
||||
Server string `json:"server"`
|
||||
UpMbps int `json:"up_mbps"`
|
||||
DownMbps int `json:"down_mbps"`
|
||||
// Optional below
|
||||
SOCKS5 struct {
|
||||
Listen string `json:"listen"`
|
||||
Timeout int `json:"timeout"`
|
||||
DisableUDP bool `json:"disable_udp"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
} `json:"socks5"`
|
||||
HTTP struct {
|
||||
Listen string `json:"listen"`
|
||||
Timeout int `json:"timeout"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
} `json:"http"`
|
||||
Relay struct {
|
||||
Listen string `json:"listen"`
|
||||
Remote string `json:"remote"`
|
||||
Timeout int `json:"timeout"`
|
||||
} `json:"relay"`
|
||||
ACL string `json:"acl"`
|
||||
Obfs string `json:"obfs"`
|
||||
Auth []byte `json:"auth"`
|
||||
AuthString string `json:"auth_str"`
|
||||
Insecure bool `json:"insecure"`
|
||||
CustomCA string `json:"ca"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn"`
|
||||
ReceiveWindow uint64 `json:"recv_window"`
|
||||
}
|
||||
|
||||
func (c *clientConfig) Check() error {
|
||||
if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 && len(c.Relay.Listen) == 0 {
|
||||
return errors.New("no SOCKS5, HTTP or relay listen address")
|
||||
}
|
||||
if len(c.Relay.Listen) > 0 && len(c.Relay.Remote) == 0 {
|
||||
return errors.New("no relay remote address")
|
||||
}
|
||||
if c.SOCKS5.Timeout != 0 && c.SOCKS5.Timeout <= 4 {
|
||||
return errors.New("invalid SOCKS5 timeout")
|
||||
}
|
||||
if c.HTTP.Timeout != 0 && c.HTTP.Timeout <= 4 {
|
||||
return errors.New("invalid HTTP timeout")
|
||||
}
|
||||
if c.Relay.Timeout != 0 && c.Relay.Timeout <= 4 {
|
||||
return errors.New("invalid relay timeout")
|
||||
}
|
||||
if len(c.Server) == 0 {
|
||||
return errors.New("no server address")
|
||||
}
|
||||
if c.UpMbps <= 0 || c.DownMbps <= 0 {
|
||||
return errors.New("invalid speed")
|
||||
}
|
||||
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
|
||||
(c.ReceiveWindow != 0 && c.ReceiveWindow < 65536) {
|
||||
return errors.New("invalid receive window size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *clientConfig) String() string {
|
||||
return fmt.Sprintf("%+v", *c)
|
||||
}
|
||||
|
83
cmd/main.go
83
cmd/main.go
@@ -1,11 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/yosuke-furukawa/json5/encoding/json5"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Injected when compiling
|
||||
@@ -15,12 +17,10 @@ var (
|
||||
appDate = "Unknown"
|
||||
)
|
||||
|
||||
var modeMap = map[string]func(args []string){
|
||||
"relay server": relayServer,
|
||||
"relay client": relayClient,
|
||||
"proxy server": proxyServer,
|
||||
"proxy client": proxyClient,
|
||||
}
|
||||
var (
|
||||
configPath = flag.String("config", "config.json", "Config file")
|
||||
showVersion = flag.Bool("version", false, "Show version")
|
||||
)
|
||||
|
||||
func init() {
|
||||
logrus.SetOutput(os.Stdout)
|
||||
@@ -50,37 +50,68 @@ func init() {
|
||||
TimestampFormat: tsFormat,
|
||||
})
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 2 && strings.ToLower(strings.TrimSpace(os.Args[1])) == "version" {
|
||||
if *showVersion {
|
||||
// Print version and quit
|
||||
fmt.Printf("%-10s%s\n", "Version:", appVersion)
|
||||
fmt.Printf("%-10s%s\n", "Commit:", appCommit)
|
||||
fmt.Printf("%-10s%s\n", "Date:", appDate)
|
||||
return
|
||||
}
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println()
|
||||
fmt.Printf("Usage: %s MODE SUBMODE [OPTIONS]\n\n"+
|
||||
"Available mode/submode combinations: "+getModes()+"\n"+
|
||||
"Use -h to see the available options for a mode.\n\n", os.Args[0])
|
||||
return
|
||||
cb, err := ioutil.ReadFile(*configPath)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"file": *configPath,
|
||||
"error": err,
|
||||
}).Fatal("Failed to read configuration")
|
||||
}
|
||||
modeStr := fmt.Sprintf("%s %s", strings.ToLower(strings.TrimSpace(os.Args[1])),
|
||||
strings.ToLower(strings.TrimSpace(os.Args[2])))
|
||||
f := modeMap[modeStr]
|
||||
if f != nil {
|
||||
f(os.Args[3:])
|
||||
mode := flag.Arg(0)
|
||||
if strings.EqualFold(mode, "server") {
|
||||
// server mode
|
||||
c, err := parseServerConfig(cb)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"file": *configPath,
|
||||
"error": err,
|
||||
}).Fatal("Failed to parse server configuration")
|
||||
}
|
||||
server(c)
|
||||
} else if len(mode) == 0 || strings.EqualFold(mode, "client") {
|
||||
// client mode
|
||||
c, err := parseClientConfig(cb)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"file": *configPath,
|
||||
"error": err,
|
||||
}).Fatal("Failed to parse client configuration")
|
||||
}
|
||||
client(c)
|
||||
} else {
|
||||
fmt.Println("Invalid mode:", modeStr)
|
||||
// invalid
|
||||
fmt.Println()
|
||||
fmt.Printf("Usage: %s MODE [OPTIONS]\n\n"+
|
||||
"Available modes: server, client\n\n", os.Args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func getModes() string {
|
||||
modes := make([]string, 0, len(modeMap))
|
||||
for mode := range modeMap {
|
||||
modes = append(modes, mode)
|
||||
func parseServerConfig(cb []byte) (*serverConfig, error) {
|
||||
var c serverConfig
|
||||
err := json5.Unmarshal(cb, &c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return strings.Join(modes, ", ")
|
||||
return &c, c.Check()
|
||||
}
|
||||
|
||||
func parseClientConfig(cb []byte) (*clientConfig, error) {
|
||||
var c clientConfig
|
||||
err := json5.Unmarshal(cb, &c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, c.Check()
|
||||
}
|
||||
|
@@ -1,200 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
hyHTTP "github.com/tobyxdd/hysteria/pkg/http"
|
||||
"github.com/tobyxdd/hysteria/pkg/obfs"
|
||||
"github.com/tobyxdd/hysteria/pkg/socks5"
|
||||
)
|
||||
|
||||
func proxyClient(args []string) {
|
||||
var config proxyClientConfig
|
||||
err := loadConfig(&config, args)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Unable to load configuration")
|
||||
}
|
||||
if err := config.Check(); err != nil {
|
||||
logrus.WithField("error", err).Fatal("Configuration error")
|
||||
}
|
||||
logrus.WithField("config", config.String()).Info("Configuration loaded")
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: config.Insecure,
|
||||
NextProtos: []string{proxyTLSProtocol},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
// Load CA
|
||||
if len(config.CustomCAFile) > 0 {
|
||||
bs, err := ioutil.ReadFile(config.CustomCAFile)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.CustomCAFile,
|
||||
}).Fatal("Unable to load CA file")
|
||||
}
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(bs) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"file": config.CustomCAFile,
|
||||
}).Fatal("Unable to parse CA file")
|
||||
}
|
||||
tlsConfig.RootCAs = cp
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn,
|
||||
MaxReceiveConnectionFlowControlWindow: config.ReceiveWindow,
|
||||
KeepAlive: true,
|
||||
}
|
||||
if quicConfig.MaxReceiveStreamFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveStreamFlowControlWindow = DefaultMaxReceiveStreamFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow
|
||||
}
|
||||
|
||||
var obfuscator core.Obfuscator
|
||||
if len(config.Obfs) > 0 {
|
||||
obfuscator = obfs.XORObfuscator(config.Obfs)
|
||||
}
|
||||
|
||||
var aclEngine *acl.Engine
|
||||
if len(config.ACLFile) > 0 {
|
||||
aclEngine, err = acl.LoadFromFile(config.ACLFile)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.ACLFile,
|
||||
}).Fatal("Unable to parse ACL")
|
||||
}
|
||||
}
|
||||
|
||||
client, err := core.NewClient(config.ServerAddr, config.Username, config.Password, tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.ExternalSendAlgorithm {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
}, obfuscator)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Client initialization failed")
|
||||
}
|
||||
defer client.Close()
|
||||
logrus.WithField("addr", config.ServerAddr).Info("Connected")
|
||||
|
||||
errChan := make(chan error)
|
||||
|
||||
if len(config.SOCKS5Addr) > 0 {
|
||||
go func() {
|
||||
var authFunc func(user, password string) bool
|
||||
if config.SOCKS5User != "" && config.SOCKS5Password != "" {
|
||||
authFunc = func(user, password string) bool {
|
||||
return config.SOCKS5User == user && config.SOCKS5Password == password
|
||||
}
|
||||
}
|
||||
socks5server, err := socks5.NewServer(client, config.SOCKS5Addr, authFunc, config.SOCKS5Timeout, aclEngine,
|
||||
config.SOCKS5DisableUDP,
|
||||
func(addr net.Addr, reqAddr string, action acl.Action, arg string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": actionToString(action, arg),
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("New SOCKS5 TCP request")
|
||||
},
|
||||
func(addr net.Addr, reqAddr string, err error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("SOCKS5 TCP request closed")
|
||||
},
|
||||
func(addr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
}).Debug("New SOCKS5 UDP associate request")
|
||||
},
|
||||
func(addr net.Addr, err error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": addr.String(),
|
||||
}).Debug("SOCKS5 UDP associate request closed")
|
||||
},
|
||||
func(addr net.Addr, reqAddr string, action acl.Action, arg string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": actionToString(action, arg),
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("New SOCKS5 UDP tunnel")
|
||||
},
|
||||
func(addr net.Addr, reqAddr string, err error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("SOCKS5 UDP tunnel closed")
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("SOCKS5 server initialization failed")
|
||||
}
|
||||
logrus.WithField("addr", config.SOCKS5Addr).Info("SOCKS5 server up and running")
|
||||
errChan <- socks5server.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
||||
if len(config.HTTPAddr) > 0 {
|
||||
go func() {
|
||||
var authFunc func(user, password string) bool
|
||||
if config.HTTPUser != "" && config.HTTPPassword != "" {
|
||||
authFunc = func(user, password string) bool {
|
||||
return config.HTTPUser == user && config.HTTPPassword == password
|
||||
}
|
||||
}
|
||||
proxy, err := hyHTTP.NewProxyHTTPServer(client, time.Duration(config.HTTPTimeout)*time.Second, aclEngine,
|
||||
func(reqAddr string, action acl.Action, arg string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": actionToString(action, arg),
|
||||
"dst": reqAddr,
|
||||
}).Debug("New HTTP request")
|
||||
},
|
||||
authFunc)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("HTTP server initialization failed")
|
||||
}
|
||||
if config.HTTPSCert != "" && config.HTTPSKey != "" {
|
||||
logrus.WithField("addr", config.HTTPAddr).Info("HTTPS server up and running")
|
||||
errChan <- http.ListenAndServeTLS(config.HTTPAddr, config.HTTPSCert, config.HTTPSKey, proxy)
|
||||
} else {
|
||||
logrus.WithField("addr", config.HTTPAddr).Info("HTTP server up and running")
|
||||
errChan <- http.ListenAndServe(config.HTTPAddr, proxy)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
err = <-errChan
|
||||
logrus.WithField("error", err).Fatal("Client shutdown")
|
||||
}
|
||||
|
||||
func actionToString(action acl.Action, arg string) string {
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
return "Direct"
|
||||
case acl.ActionProxy:
|
||||
return "Proxy"
|
||||
case acl.ActionBlock:
|
||||
return "Block"
|
||||
case acl.ActionHijack:
|
||||
return "Hijack to " + arg
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const proxyTLSProtocol = "hysteria-proxy"
|
||||
|
||||
type proxyClientConfig struct {
|
||||
SOCKS5Addr string `json:"socks5_addr" desc:"SOCKS5 listen address"`
|
||||
SOCKS5Timeout int `json:"socks5_timeout" desc:"SOCKS5 connection timeout in seconds"`
|
||||
SOCKS5DisableUDP bool `json:"socks5_disable_udp" desc:"Disable SOCKS5 UDP support"`
|
||||
SOCKS5User string `json:"socks5_user" desc:"SOCKS5 auth username"`
|
||||
SOCKS5Password string `json:"socks5_password" desc:"SOCKS5 auth password"`
|
||||
HTTPAddr string `json:"http_addr" desc:"HTTP listen address"`
|
||||
HTTPTimeout int `json:"http_timeout" desc:"HTTP connection timeout in seconds"`
|
||||
HTTPUser string `json:"http_user" desc:"HTTP basic auth username"`
|
||||
HTTPPassword string `json:"http_password" desc:"HTTP basic auth password"`
|
||||
HTTPSCert string `json:"https_cert" desc:"HTTPS certificate file"`
|
||||
HTTPSKey string `json:"https_key" desc:"HTTPS key file"`
|
||||
ACLFile string `json:"acl" desc:"Access control list"`
|
||||
ServerAddr string `json:"server" desc:"Server address"`
|
||||
Username string `json:"username" desc:"Authentication username"`
|
||||
Password string `json:"password" desc:"Authentication password"`
|
||||
Insecure bool `json:"insecure" desc:"Ignore TLS certificate errors"`
|
||||
CustomCAFile string `json:"ca" desc:"Specify a trusted CA file"`
|
||||
UpMbps int `json:"up_mbps" desc:"Upload speed in Mbps"`
|
||||
DownMbps int `json:"down_mbps" desc:"Download speed in Mbps"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"`
|
||||
ReceiveWindow uint64 `json:"recv_window" desc:"Max receive window size"`
|
||||
Obfs string `json:"obfs" desc:"Obfuscation key"`
|
||||
}
|
||||
|
||||
func (c *proxyClientConfig) Check() error {
|
||||
if len(c.SOCKS5Addr) == 0 && len(c.HTTPAddr) == 0 {
|
||||
return errors.New("no SOCKS5 or HTTP listen address")
|
||||
}
|
||||
if c.SOCKS5Timeout != 0 && c.SOCKS5Timeout <= 4 {
|
||||
return errors.New("invalid SOCKS5 timeout")
|
||||
}
|
||||
if c.HTTPTimeout != 0 && c.HTTPTimeout <= 4 {
|
||||
return errors.New("invalid HTTP timeout")
|
||||
}
|
||||
if len(c.ServerAddr) == 0 {
|
||||
return errors.New("no server address")
|
||||
}
|
||||
if c.UpMbps <= 0 || c.DownMbps <= 0 {
|
||||
return errors.New("invalid speed")
|
||||
}
|
||||
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
|
||||
(c.ReceiveWindow != 0 && c.ReceiveWindow < 65536) {
|
||||
return errors.New("invalid receive window size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *proxyClientConfig) String() string {
|
||||
return fmt.Sprintf("%+v", *c)
|
||||
}
|
||||
|
||||
type proxyServerConfig struct {
|
||||
ListenAddr string `json:"listen" desc:"Server listen address"`
|
||||
DisableUDP bool `json:"disable_udp" desc:"Disable UDP support"`
|
||||
ACLFile string `json:"acl" desc:"Access control list"`
|
||||
CertFile string `json:"cert" desc:"TLS certificate file"`
|
||||
KeyFile string `json:"key" desc:"TLS key file"`
|
||||
AuthFile string `json:"auth" desc:"Authentication file"`
|
||||
UpMbps int `json:"up_mbps" desc:"Max upload speed per client in Mbps"`
|
||||
DownMbps int `json:"down_mbps" desc:"Max download speed per client in Mbps"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"`
|
||||
ReceiveWindowClient uint64 `json:"recv_window_client" desc:"Max receive window size per client"`
|
||||
MaxConnClient int `json:"max_conn_client" desc:"Max simultaneous connections allowed per client"`
|
||||
Obfs string `json:"obfs" desc:"Obfuscation key"`
|
||||
}
|
||||
|
||||
func (c *proxyServerConfig) Check() error {
|
||||
if len(c.ListenAddr) == 0 {
|
||||
return errors.New("no listen address")
|
||||
}
|
||||
if len(c.CertFile) == 0 || len(c.KeyFile) == 0 {
|
||||
return errors.New("TLS cert or key not provided")
|
||||
}
|
||||
if c.UpMbps < 0 || c.DownMbps < 0 {
|
||||
return errors.New("invalid speed")
|
||||
}
|
||||
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
|
||||
(c.ReceiveWindowClient != 0 && c.ReceiveWindowClient < 65536) {
|
||||
return errors.New("invalid receive window size")
|
||||
}
|
||||
if c.MaxConnClient < 0 {
|
||||
return errors.New("invalid max connections per client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *proxyServerConfig) String() string {
|
||||
return fmt.Sprintf("%+v", *c)
|
||||
}
|
@@ -1,298 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/tobyxdd/hysteria/pkg/obfs"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func proxyServer(args []string) {
|
||||
var config proxyServerConfig
|
||||
err := loadConfig(&config, args)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Unable to load configuration")
|
||||
}
|
||||
if err := config.Check(); err != nil {
|
||||
logrus.WithField("error", err).Fatal("Configuration error")
|
||||
}
|
||||
logrus.WithField("config", config.String()).Info("Configuration loaded")
|
||||
// Load cert
|
||||
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"cert": config.CertFile,
|
||||
"key": config.KeyFile,
|
||||
}).Fatal("Unable to load the certificate")
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
NextProtos: []string{proxyTLSProtocol},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn,
|
||||
MaxReceiveConnectionFlowControlWindow: config.ReceiveWindowClient,
|
||||
MaxIncomingStreams: int64(config.MaxConnClient),
|
||||
KeepAlive: true,
|
||||
}
|
||||
if quicConfig.MaxReceiveStreamFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveStreamFlowControlWindow = DefaultMaxReceiveStreamFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxIncomingStreams == 0 {
|
||||
quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams
|
||||
}
|
||||
|
||||
if len(config.AuthFile) == 0 {
|
||||
logrus.Warn("No authentication configured, this server can be used by anyone")
|
||||
}
|
||||
|
||||
var obfuscator core.Obfuscator
|
||||
if len(config.Obfs) > 0 {
|
||||
obfuscator = obfs.XORObfuscator(config.Obfs)
|
||||
}
|
||||
|
||||
var aclEngine *acl.Engine
|
||||
if len(config.ACLFile) > 0 {
|
||||
aclEngine, err = acl.LoadFromFile(config.ACLFile)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.ACLFile,
|
||||
}).Fatal("Unable to parse ACL")
|
||||
}
|
||||
aclEngine.DefaultAction = acl.ActionDirect
|
||||
}
|
||||
|
||||
server, err := core.NewServer(config.ListenAddr, tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.ExternalSendAlgorithm {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
},
|
||||
obfuscator,
|
||||
func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (core.AuthResult, string) {
|
||||
if len(config.AuthFile) == 0 {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"addr": addr.String(),
|
||||
"username": username,
|
||||
"up": sSend / mbpsToBps,
|
||||
"down": sRecv / mbpsToBps,
|
||||
}).Info("Client connected")
|
||||
return core.AuthResultSuccess, ""
|
||||
} else {
|
||||
// Need auth
|
||||
ok, err := checkAuth(config.AuthFile, username, password)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
"addr": addr.String(),
|
||||
"username": username,
|
||||
}).Error("Client authentication error")
|
||||
return core.AuthResultInternalError, "Server auth error"
|
||||
}
|
||||
if ok {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"addr": addr.String(),
|
||||
"username": username,
|
||||
"up": sSend / mbpsToBps,
|
||||
"down": sRecv / mbpsToBps,
|
||||
}).Info("Client authenticated")
|
||||
return core.AuthResultSuccess, ""
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"addr": addr.String(),
|
||||
"username": username,
|
||||
"up": sSend / mbpsToBps,
|
||||
"down": sRecv / mbpsToBps,
|
||||
}).Info("Client rejected due to invalid credential")
|
||||
return core.AuthResultInvalidCred, "Invalid credential"
|
||||
}
|
||||
}
|
||||
},
|
||||
func(addr net.Addr, username string, err error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
"addr": addr.String(),
|
||||
"username": username,
|
||||
}).Info("Client disconnected")
|
||||
},
|
||||
func(addr net.Addr, username string, id int, reqType core.ConnectionType, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) {
|
||||
packet := reqType == core.ConnectionTypePacket
|
||||
if packet && config.DisableUDP {
|
||||
return core.ConnectResultBlocked, "UDP disabled", nil
|
||||
}
|
||||
host, port, err := net.SplitHostPort(reqAddr)
|
||||
if err != nil {
|
||||
return core.ConnectResultFailed, err.Error(), nil
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
// IP request, clear host for ACL engine
|
||||
host = ""
|
||||
}
|
||||
action, arg := acl.ActionDirect, ""
|
||||
if aclEngine != nil {
|
||||
action, arg = aclEngine.Lookup(host, ip)
|
||||
}
|
||||
switch action {
|
||||
case acl.ActionDirect, acl.ActionProxy: // Treat proxy as direct on server side
|
||||
if !packet {
|
||||
// TCP
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": "direct",
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("New TCP request")
|
||||
conn, err := net.DialTimeout("tcp", reqAddr, dialTimeout)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"dst": reqAddr,
|
||||
}).Error("TCP error")
|
||||
return core.ConnectResultFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnectResultSuccess, "", conn
|
||||
} else {
|
||||
// UDP
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": "direct",
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("New UDP request")
|
||||
conn, err := net.Dial("udp", reqAddr)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"dst": reqAddr,
|
||||
}).Error("UDP error")
|
||||
return core.ConnectResultFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnectResultSuccess, "", conn
|
||||
}
|
||||
case acl.ActionBlock:
|
||||
if !packet {
|
||||
// TCP
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": "block",
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("New TCP request")
|
||||
return core.ConnectResultBlocked, "blocked by ACL", nil
|
||||
} else {
|
||||
// UDP
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": "block",
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("New UDP request")
|
||||
return core.ConnectResultBlocked, "blocked by ACL", nil
|
||||
}
|
||||
case acl.ActionHijack:
|
||||
hijackAddr := net.JoinHostPort(arg, port)
|
||||
if !packet {
|
||||
// TCP
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": "hijack",
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
"rdst": arg,
|
||||
}).Debug("New TCP request")
|
||||
conn, err := net.DialTimeout("tcp", hijackAddr, dialTimeout)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"dst": hijackAddr,
|
||||
}).Error("TCP error")
|
||||
return core.ConnectResultFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnectResultSuccess, "", conn
|
||||
} else {
|
||||
// UDP
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": "hijack",
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
"rdst": arg,
|
||||
}).Debug("New UDP request")
|
||||
conn, err := net.Dial("udp", hijackAddr)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"dst": hijackAddr,
|
||||
}).Error("UDP error")
|
||||
return core.ConnectResultFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnectResultSuccess, "", conn
|
||||
}
|
||||
default:
|
||||
return core.ConnectResultFailed, "server ACL error", nil
|
||||
}
|
||||
},
|
||||
func(addr net.Addr, username string, id int, reqType core.ConnectionType, reqAddr string, err error) {
|
||||
packet := reqType == core.ConnectionTypePacket
|
||||
if !packet {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("TCP request closed")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("UDP request closed")
|
||||
}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Server initialization failed")
|
||||
}
|
||||
defer server.Close()
|
||||
logrus.WithField("addr", config.ListenAddr).Info("Server up and running")
|
||||
|
||||
err = server.Serve()
|
||||
logrus.WithField("error", err).Fatal("Server shutdown")
|
||||
}
|
||||
|
||||
func checkAuth(authFile, username, password string) (bool, error) {
|
||||
f, err := os.Open(authFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
pair := strings.Fields(scanner.Text())
|
||||
if len(pair) != 2 {
|
||||
// Invalid format
|
||||
continue
|
||||
}
|
||||
if username == pair[0] && password == pair[1] {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
@@ -1,119 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/sirupsen/logrus"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/tobyxdd/hysteria/pkg/obfs"
|
||||
"github.com/tobyxdd/hysteria/pkg/utils"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func relayClient(args []string) {
|
||||
var config relayClientConfig
|
||||
err := loadConfig(&config, args)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Unable to load configuration")
|
||||
}
|
||||
if err := config.Check(); err != nil {
|
||||
logrus.WithField("error", err).Fatal("Configuration error")
|
||||
}
|
||||
if len(config.Name) == 0 {
|
||||
usr, err := user.Current()
|
||||
if err == nil {
|
||||
config.Name = usr.Name
|
||||
}
|
||||
}
|
||||
logrus.WithField("config", config.String()).Info("Configuration loaded")
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: config.Insecure,
|
||||
NextProtos: []string{relayTLSProtocol},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
// Load CA
|
||||
if len(config.CustomCAFile) > 0 {
|
||||
bs, err := ioutil.ReadFile(config.CustomCAFile)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.CustomCAFile,
|
||||
}).Fatal("Unable to load CA file")
|
||||
}
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(bs) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"file": config.CustomCAFile,
|
||||
}).Fatal("Unable to parse CA file")
|
||||
}
|
||||
tlsConfig.RootCAs = cp
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn,
|
||||
MaxReceiveConnectionFlowControlWindow: config.ReceiveWindow,
|
||||
KeepAlive: true,
|
||||
}
|
||||
if quicConfig.MaxReceiveStreamFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveStreamFlowControlWindow = DefaultMaxReceiveStreamFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow
|
||||
}
|
||||
|
||||
var obfuscator core.Obfuscator
|
||||
if len(config.Obfs) > 0 {
|
||||
obfuscator = obfs.XORObfuscator(config.Obfs)
|
||||
}
|
||||
|
||||
client, err := core.NewClient(config.ServerAddr, config.Name, "", tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.ExternalSendAlgorithm {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
}, obfuscator)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Client initialization failed")
|
||||
}
|
||||
defer client.Close()
|
||||
logrus.WithField("addr", config.ServerAddr).Info("Connected")
|
||||
|
||||
listener, err := net.Listen("tcp", config.ListenAddr)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("TCP listen failed")
|
||||
}
|
||||
defer listener.Close()
|
||||
logrus.WithField("addr", listener.Addr().String()).Info("TCP server listening")
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("TCP accept failed")
|
||||
}
|
||||
go relayClientHandleConn(conn, client)
|
||||
}
|
||||
}
|
||||
|
||||
func relayClientHandleConn(conn net.Conn, client *core.Client) {
|
||||
logrus.WithField("src", conn.RemoteAddr().String()).Debug("New connection")
|
||||
var closeErr error
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": closeErr,
|
||||
"src": conn.RemoteAddr().String(),
|
||||
}).Debug("Connection closed")
|
||||
}()
|
||||
rwc, err := client.Dial(false, "")
|
||||
if err != nil {
|
||||
closeErr = err
|
||||
return
|
||||
}
|
||||
defer rwc.Close()
|
||||
closeErr = utils.PipePair(conn, rwc, nil, nil)
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const relayTLSProtocol = "hysteria-relay"
|
||||
|
||||
type relayClientConfig struct {
|
||||
ListenAddr string `json:"listen" desc:"TCP listen address"`
|
||||
ServerAddr string `json:"server" desc:"Server address"`
|
||||
Name string `json:"name" desc:"Client name presented to the server"`
|
||||
Insecure bool `json:"insecure" desc:"Ignore TLS certificate errors"`
|
||||
CustomCAFile string `json:"ca" desc:"Specify a trusted CA file"`
|
||||
UpMbps int `json:"up_mbps" desc:"Upload speed in Mbps"`
|
||||
DownMbps int `json:"down_mbps" desc:"Download speed in Mbps"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"`
|
||||
ReceiveWindow uint64 `json:"recv_window" desc:"Max receive window size"`
|
||||
Obfs string `json:"obfs" desc:"Obfuscation key"`
|
||||
}
|
||||
|
||||
func (c *relayClientConfig) Check() error {
|
||||
if len(c.ListenAddr) == 0 {
|
||||
return errors.New("no listen address")
|
||||
}
|
||||
if len(c.ServerAddr) == 0 {
|
||||
return errors.New("no server address")
|
||||
}
|
||||
if c.UpMbps <= 0 || c.DownMbps <= 0 {
|
||||
return errors.New("invalid speed")
|
||||
}
|
||||
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
|
||||
(c.ReceiveWindow != 0 && c.ReceiveWindow < 65536) {
|
||||
return errors.New("invalid receive window size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *relayClientConfig) String() string {
|
||||
return fmt.Sprintf("%+v", *c)
|
||||
}
|
||||
|
||||
type relayServerConfig struct {
|
||||
ListenAddr string `json:"listen" desc:"Server listen address"`
|
||||
RemoteAddr string `json:"remote" desc:"Remote relay address"`
|
||||
CertFile string `json:"cert" desc:"TLS certificate file"`
|
||||
KeyFile string `json:"key" desc:"TLS key file"`
|
||||
UpMbps int `json:"up_mbps" desc:"Max upload speed per client in Mbps"`
|
||||
DownMbps int `json:"down_mbps" desc:"Max download speed per client in Mbps"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"`
|
||||
ReceiveWindowClient uint64 `json:"recv_window_client" desc:"Max receive window size per client"`
|
||||
MaxConnClient int `json:"max_conn_client" desc:"Max simultaneous connections allowed per client"`
|
||||
Obfs string `json:"obfs" desc:"Obfuscation key"`
|
||||
}
|
||||
|
||||
func (c *relayServerConfig) Check() error {
|
||||
if len(c.ListenAddr) == 0 {
|
||||
return errors.New("no listen address")
|
||||
}
|
||||
if len(c.RemoteAddr) == 0 {
|
||||
return errors.New("no remote address")
|
||||
}
|
||||
if len(c.CertFile) == 0 || len(c.KeyFile) == 0 {
|
||||
return errors.New("TLS cert or key not provided")
|
||||
}
|
||||
if c.UpMbps < 0 || c.DownMbps < 0 {
|
||||
return errors.New("invalid speed")
|
||||
}
|
||||
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
|
||||
(c.ReceiveWindowClient != 0 && c.ReceiveWindowClient < 65536) {
|
||||
return errors.New("invalid receive window size")
|
||||
}
|
||||
if c.MaxConnClient < 0 {
|
||||
return errors.New("invalid max connections per client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *relayServerConfig) String() string {
|
||||
return fmt.Sprintf("%+v", *c)
|
||||
}
|
@@ -1,121 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/sirupsen/logrus"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/tobyxdd/hysteria/pkg/obfs"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
func relayServer(args []string) {
|
||||
var config relayServerConfig
|
||||
err := loadConfig(&config, args)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Unable to load configuration")
|
||||
}
|
||||
if err := config.Check(); err != nil {
|
||||
logrus.WithField("error", err).Fatal("Configuration error")
|
||||
}
|
||||
logrus.WithField("config", config.String()).Info("Configuration loaded")
|
||||
// Load cert
|
||||
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"cert": config.CertFile,
|
||||
"key": config.KeyFile,
|
||||
}).Fatal("Unable to load the certificate")
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
NextProtos: []string{relayTLSProtocol},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
MaxReceiveStreamFlowControlWindow: config.ReceiveWindowConn,
|
||||
MaxReceiveConnectionFlowControlWindow: config.ReceiveWindowClient,
|
||||
MaxIncomingStreams: int64(config.MaxConnClient),
|
||||
KeepAlive: true,
|
||||
}
|
||||
if quicConfig.MaxReceiveStreamFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveStreamFlowControlWindow = DefaultMaxReceiveStreamFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxReceiveConnectionFlowControlWindow == 0 {
|
||||
quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxIncomingStreams == 0 {
|
||||
quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams
|
||||
}
|
||||
|
||||
var obfuscator core.Obfuscator
|
||||
if len(config.Obfs) > 0 {
|
||||
obfuscator = obfs.XORObfuscator(config.Obfs)
|
||||
}
|
||||
|
||||
server, err := core.NewServer(config.ListenAddr, tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.ExternalSendAlgorithm {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
},
|
||||
obfuscator,
|
||||
func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (core.AuthResult, string) {
|
||||
// No authentication logic in relay, just log username and speed
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"addr": addr.String(),
|
||||
"username": username,
|
||||
"up": sSend / mbpsToBps,
|
||||
"down": sRecv / mbpsToBps,
|
||||
}).Info("Client connected")
|
||||
return core.AuthResultSuccess, ""
|
||||
},
|
||||
func(addr net.Addr, username string, err error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
"addr": addr.String(),
|
||||
"username": username,
|
||||
}).Info("Client disconnected")
|
||||
},
|
||||
func(addr net.Addr, username string, id int, reqType core.ConnectionType, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) {
|
||||
packet := reqType == core.ConnectionTypePacket
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"id": id,
|
||||
}).Debug("New stream")
|
||||
if packet {
|
||||
return core.ConnectResultBlocked, "unsupported", nil
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", config.RemoteAddr, dialTimeout)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"dst": config.RemoteAddr,
|
||||
}).Error("TCP error")
|
||||
return core.ConnectResultFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnectResultSuccess, "", conn
|
||||
},
|
||||
func(addr net.Addr, username string, id int, reqType core.ConnectionType, reqAddr string, err error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"username": username,
|
||||
"src": addr.String(),
|
||||
"id": id,
|
||||
}).Debug("Stream closed")
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Server initialization failed")
|
||||
}
|
||||
defer server.Close()
|
||||
logrus.WithField("addr", config.ListenAddr).Info("Server up and running")
|
||||
|
||||
err = server.Serve()
|
||||
logrus.WithField("error", err).Fatal("Server shutdown")
|
||||
}
|
170
cmd/server.go
Normal file
170
cmd/server.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/tobyxdd/hysteria/pkg/obfs"
|
||||
"github.com/yosuke-furukawa/json5/encoding/json5"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
func server(config *serverConfig) {
|
||||
logrus.WithField("config", config.String()).Info("Server configuration loaded")
|
||||
// Load cert
|
||||
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"cert": config.CertFile,
|
||||
"key": config.KeyFile,
|
||||
}).Fatal("Failed to load the certificate")
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
NextProtos: []string{tlsProtocolName},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
// QUIC config
|
||||
quicConfig := &quic.Config{
|
||||
MaxStreamReceiveWindow: config.ReceiveWindowConn,
|
||||
MaxConnectionReceiveWindow: config.ReceiveWindowClient,
|
||||
MaxIncomingStreams: int64(config.MaxConnClient),
|
||||
KeepAlive: true,
|
||||
EnableDatagrams: true,
|
||||
}
|
||||
if quicConfig.MaxStreamReceiveWindow == 0 {
|
||||
quicConfig.MaxStreamReceiveWindow = DefaultMaxReceiveStreamFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxConnectionReceiveWindow == 0 {
|
||||
quicConfig.MaxConnectionReceiveWindow = DefaultMaxReceiveConnectionFlowControlWindow
|
||||
}
|
||||
if quicConfig.MaxIncomingStreams == 0 {
|
||||
quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams
|
||||
}
|
||||
// Auth
|
||||
var authFunc func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string)
|
||||
switch authMode := config.Auth.Mode; authMode {
|
||||
case "", "none":
|
||||
logrus.Warn("No authentication configured")
|
||||
authFunc = func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
|
||||
return true, "Welcome"
|
||||
}
|
||||
case "password":
|
||||
logrus.Info("Password authentication enabled")
|
||||
var pwdConfig map[string]string
|
||||
err = json5.Unmarshal(config.Auth.Config, &pwdConfig)
|
||||
if err != nil || len(pwdConfig["password"]) == 0 {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Invalid password authentication config")
|
||||
}
|
||||
pwd := pwdConfig["password"]
|
||||
authFunc = func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
|
||||
if string(auth) == pwd {
|
||||
return true, "Welcome"
|
||||
} else {
|
||||
return false, "Wrong password"
|
||||
}
|
||||
}
|
||||
default:
|
||||
logrus.WithField("mode", config.Auth.Mode).Fatal("Unsupported authentication mode")
|
||||
}
|
||||
// Obfuscator
|
||||
var obfuscator core.Obfuscator
|
||||
if len(config.Obfs) > 0 {
|
||||
obfuscator = obfs.XORObfuscator(config.Obfs)
|
||||
}
|
||||
// ACL
|
||||
var aclEngine *acl.Engine
|
||||
if len(config.ACL) > 0 {
|
||||
aclEngine, err = acl.LoadFromFile(config.ACL)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.ACL,
|
||||
}).Fatal("Failed to parse ACL")
|
||||
}
|
||||
aclEngine.DefaultAction = acl.ActionDirect
|
||||
}
|
||||
// Server
|
||||
server, err := core.NewServer(config.Listen, tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.CongestionControl {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
}, config.DisableUDP, aclEngine, obfuscator, authFunc,
|
||||
tcpRequestFunc, tcpErrorFunc, udpRequestFunc, udpErrorFunc)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize server")
|
||||
}
|
||||
defer server.Close()
|
||||
logrus.WithField("addr", config.Listen).Info("Server up and running")
|
||||
|
||||
err = server.Serve()
|
||||
logrus.WithField("error", err).Fatal("Server shutdown")
|
||||
}
|
||||
|
||||
func tcpRequestFunc(addr net.Addr, auth []byte, reqAddr string, action acl.Action, arg string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
"action": actionToString(action, arg),
|
||||
}).Debug("TCP request")
|
||||
}
|
||||
|
||||
func tcpErrorFunc(addr net.Addr, auth []byte, reqAddr string, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
"error": err,
|
||||
}).Info("TCP error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
"dst": reqAddr,
|
||||
}).Debug("TCP EOF")
|
||||
}
|
||||
}
|
||||
|
||||
func udpRequestFunc(addr net.Addr, auth []byte, sessionID uint32) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
"session": sessionID,
|
||||
}).Debug("UDP request")
|
||||
}
|
||||
|
||||
func udpErrorFunc(addr net.Addr, auth []byte, sessionID uint32, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
"session": sessionID,
|
||||
"error": err,
|
||||
}).Info("UDP error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
"session": sessionID,
|
||||
}).Debug("UDP EOF")
|
||||
}
|
||||
}
|
||||
|
||||
func actionToString(action acl.Action, arg string) string {
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
return "Direct"
|
||||
case acl.ActionProxy:
|
||||
return "Proxy"
|
||||
case acl.ActionBlock:
|
||||
return "Block"
|
||||
case acl.ActionHijack:
|
||||
return "Hijack to " + arg
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
28
cmd/utils.go
28
cmd/utils.go
@@ -1,28 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type optionalBoolFlag struct {
|
||||
Exists bool
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (flag *optionalBoolFlag) String() string {
|
||||
return strconv.FormatBool(flag.Value)
|
||||
}
|
||||
|
||||
func (flag *optionalBoolFlag) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flag.Exists = true
|
||||
flag.Value = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flag *optionalBoolFlag) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
19
docs/socks5/udpchk.py
Normal file
19
docs/socks5/udpchk.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import socks
|
||||
import socket
|
||||
|
||||
|
||||
def main():
|
||||
s = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.set_proxy(socks.SOCKS5, "127.0.0.1", 1080)
|
||||
# Raw DNS request
|
||||
req = b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x05\x62\x61\x69\x64\x75\x03\x63\x6f\x6d\x00\x00\x01\x00\x01"
|
||||
s.sendto(req, ("8.8.8.8", 53))
|
||||
(rsp, address) = s.recvfrom(4096)
|
||||
if rsp[0] == req[0] and rsp[1] == req[1]:
|
||||
print("UDP check passed")
|
||||
else:
|
||||
print("Invalid response")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
7
go.mod
7
go.mod
@@ -5,14 +5,15 @@ go 1.14
|
||||
require (
|
||||
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/lucas-clemente/quic-go v0.16.1
|
||||
github.com/lucas-clemente/quic-go v0.20.0
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997 // indirect
|
||||
github.com/txthinking/socks5 v0.0.0-20200327133705-caf148ab5e9d
|
||||
github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9 // indirect
|
||||
github.com/yosuke-furukawa/json5 v0.1.1
|
||||
)
|
||||
|
||||
replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.3.0-tquic-3
|
||||
replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.20.1-0.20210320001547-eb20d732ffed
|
||||
|
44
go.sum
44
go.sum
@@ -16,7 +16,6 @@ github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitf
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
@@ -36,15 +35,13 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -87,13 +84,15 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
|
||||
github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4 h1:RehYMOyRW8hPVEja1KBVsFVNSm35Jj9Mvs5yNoZZ28A=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.3 h1:XEZ1xGorVy9u+lJq+WXNE+hiqRYLNvJGYmwfwKQN2gU=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -148,14 +147,11 @@ github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tobyxdd/quic-go v0.3.0-tquic-3 h1:DjvbOSRydm1P+CYqg/Kb+awXELRpk01IhNhKiJTFCaQ=
|
||||
github.com/tobyxdd/quic-go v0.3.0-tquic-3/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg=
|
||||
github.com/tobyxdd/quic-go v0.20.1-0.20210320001547-eb20d732ffed h1:bhRIrbeMeMHYFqjNPkQaLWH+kd4gBjkdeKgZUJtokYE=
|
||||
github.com/tobyxdd/quic-go v0.20.1-0.20210320001547-eb20d732ffed/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk=
|
||||
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997 h1:vlDgnShahmE2XLslpr0hnzxfAmSj3JLX2CYi8Xct7G4=
|
||||
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
|
||||
github.com/txthinking/socks5 v0.0.0-20200327133705-caf148ab5e9d h1:V+Wyj2AqtLwLG7KnniV8QG+gEkENPsudZbivvLyX4kw=
|
||||
@@ -164,13 +160,15 @@ github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9 h1:ngJOce33YJJT1PFTfC
|
||||
github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/yosuke-furukawa/json5 v0.1.1 h1:0F9mNwTvOuDNH243hoPqvf+dxa5QsKnZzU20uNsh3ZI=
|
||||
github.com/yosuke-furukawa/json5 v0.1.1/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/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-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
@@ -179,7 +177,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -213,7 +211,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -222,7 +219,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 h1:joucsQqXmyBVxViHCPFjG3hx8JzIFSaym3l3MM/Jsdg=
|
||||
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -235,8 +233,10 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
@@ -251,12 +251,10 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -283,7 +281,5 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
@@ -6,17 +6,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxDatagramSize = 1252
|
||||
|
||||
ackRateMinSampleInterval = 4 * time.Second
|
||||
ackRateMaxSampleInterval = 20 * time.Second
|
||||
ackRateMinACKSampleCount = 100
|
||||
)
|
||||
|
||||
type BrutalSender struct {
|
||||
rttStats congestion.RTTStats
|
||||
bps congestion.ByteCount
|
||||
pacer *pacer
|
||||
rttStats congestion.RTTStatsProvider
|
||||
bps congestion.ByteCount
|
||||
maxDatagramSize congestion.ByteCount
|
||||
pacer *pacer
|
||||
|
||||
ackCount, lossCount uint64
|
||||
ackRate float64
|
||||
@@ -28,13 +27,13 @@ func NewBrutalSender(bps congestion.ByteCount) *BrutalSender {
|
||||
bs := &BrutalSender{
|
||||
bps: bps,
|
||||
}
|
||||
bs.pacer = newPacer(func() uint64 {
|
||||
return uint64(float64(bs.bps)/bs.getAckRate()) * 5 / 4
|
||||
bs.pacer = newPacer(func() congestion.ByteCount {
|
||||
return congestion.ByteCount(float64(bs.bps)/bs.getAckRate()) * 5 / 4
|
||||
})
|
||||
return bs
|
||||
}
|
||||
|
||||
func (b *BrutalSender) SetRTTStats(rttStats congestion.RTTStats) {
|
||||
func (b *BrutalSender) SetRTTStatsProvider(rttStats congestion.RTTStatsProvider) {
|
||||
b.rttStats = rttStats
|
||||
}
|
||||
|
||||
@@ -43,7 +42,7 @@ func (b *BrutalSender) TimeUntilSend(bytesInFlight congestion.ByteCount) time.Ti
|
||||
}
|
||||
|
||||
func (b *BrutalSender) HasPacingBudget() bool {
|
||||
return b.pacer.Budget(time.Now()) >= maxDatagramSize
|
||||
return b.pacer.Budget(time.Now()) >= b.maxDatagramSize
|
||||
}
|
||||
|
||||
func (b *BrutalSender) CanSend(bytesInFlight congestion.ByteCount) bool {
|
||||
@@ -60,7 +59,7 @@ func (b *BrutalSender) GetCongestionWindow() congestion.ByteCount {
|
||||
|
||||
func (b *BrutalSender) OnPacketSent(sentTime time.Time, bytesInFlight congestion.ByteCount,
|
||||
packetNumber congestion.PacketNumber, bytes congestion.ByteCount, isRetransmittable bool) {
|
||||
b.pacer.SentPacket(sentTime, uint64(bytes))
|
||||
b.pacer.SentPacket(sentTime, bytes)
|
||||
}
|
||||
|
||||
func (b *BrutalSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes congestion.ByteCount,
|
||||
@@ -75,6 +74,11 @@ func (b *BrutalSender) OnPacketLost(number congestion.PacketNumber, lostBytes co
|
||||
b.maybeUpdateACKRate()
|
||||
}
|
||||
|
||||
func (b *BrutalSender) SetMaxDatagramSize(size congestion.ByteCount) {
|
||||
b.maxDatagramSize = size
|
||||
b.pacer.SetMaxDatagramSize(size)
|
||||
}
|
||||
|
||||
func (b *BrutalSender) maybeUpdateACKRate() {
|
||||
now := time.Now()
|
||||
if !now.After(b.ackRateNextUpdateMin) {
|
||||
@@ -82,6 +86,7 @@ func (b *BrutalSender) maybeUpdateACKRate() {
|
||||
}
|
||||
// Min interval reached
|
||||
if b.ackCount >= ackRateMinACKSampleCount {
|
||||
// And enough samples, update ackRate now
|
||||
b.ackRate = float64(b.ackCount) / float64(b.ackCount+b.lossCount)
|
||||
b.ackCount, b.lossCount = 0, 0
|
||||
b.ackRateNextUpdateMin = now.Add(ackRateMinSampleInterval)
|
||||
|
@@ -1,27 +1,35 @@
|
||||
package congestion
|
||||
|
||||
import (
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxBurstSize = 10 * maxDatagramSize
|
||||
const (
|
||||
initMaxDatagramSize = 1252
|
||||
maxBurstPackets = 10
|
||||
minPacingDelay = time.Millisecond
|
||||
)
|
||||
|
||||
// The pacer implements a token bucket pacing algorithm.
|
||||
type pacer struct {
|
||||
bandwidthFunc func() uint64
|
||||
budgetAtLastSent uint64
|
||||
budgetAtLastSent congestion.ByteCount
|
||||
maxDatagramSize congestion.ByteCount
|
||||
lastSentTime time.Time
|
||||
getBandwidth func() congestion.ByteCount // in bytes/s
|
||||
}
|
||||
|
||||
func newPacer(bandwidthFunc func() uint64) *pacer {
|
||||
func newPacer(getBandwidth func() congestion.ByteCount) *pacer {
|
||||
p := &pacer{
|
||||
bandwidthFunc: bandwidthFunc,
|
||||
budgetAtLastSent: maxBurstPackets * initMaxDatagramSize,
|
||||
maxDatagramSize: initMaxDatagramSize,
|
||||
getBandwidth: getBandwidth,
|
||||
}
|
||||
p.budgetAtLastSent = maxBurstSize
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *pacer) SentPacket(sendTime time.Time, size uint64) {
|
||||
func (p *pacer) SentPacket(sendTime time.Time, size congestion.ByteCount) {
|
||||
budget := p.Budget(sendTime)
|
||||
if size > budget {
|
||||
p.budgetAtLastSent = 0
|
||||
@@ -31,26 +39,47 @@ func (p *pacer) SentPacket(sendTime time.Time, size uint64) {
|
||||
p.lastSentTime = sendTime
|
||||
}
|
||||
|
||||
func (p *pacer) Budget(now time.Time) uint64 {
|
||||
func (p *pacer) Budget(now time.Time) congestion.ByteCount {
|
||||
if p.lastSentTime.IsZero() {
|
||||
return maxBurstSize
|
||||
}
|
||||
budget := p.budgetAtLastSent + p.bandwidthFunc()*uint64(now.Sub(p.lastSentTime).Nanoseconds())/1e9
|
||||
if budget > maxBurstSize {
|
||||
return maxBurstSize
|
||||
} else {
|
||||
return budget
|
||||
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 >= maxDatagramSize {
|
||||
if p.budgetAtLastSent >= p.maxDatagramSize {
|
||||
return time.Time{}
|
||||
}
|
||||
d := time.Duration(math.Ceil(float64(maxDatagramSize-p.budgetAtLastSent)*1e9/float64(p.bandwidthFunc()))) * time.Nanosecond
|
||||
if d < time.Millisecond {
|
||||
return p.lastSentTime.Add(time.Millisecond)
|
||||
} else {
|
||||
return p.lastSentTime.Add(d)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@@ -1,49 +1,54 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/tobyxdd/hysteria/pkg/core/pb"
|
||||
"github.com/tobyxdd/hysteria/pkg/utils"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/lunixbochs/struc"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrClosed = errors.New("client closed")
|
||||
ErrClosed = errors.New("closed")
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
inboundBytes, outboundBytes uint64 // atomic
|
||||
type CongestionFactory func(refBPS uint64) congestion.CongestionControl
|
||||
|
||||
reconnectMutex sync.Mutex
|
||||
closed bool
|
||||
quicSession quic.Session
|
||||
serverAddr string
|
||||
username, password string
|
||||
tlsConfig *tls.Config
|
||||
quicConfig *quic.Config
|
||||
sendBPS, recvBPS uint64
|
||||
congestionFactory CongestionFactory
|
||||
obfuscator Obfuscator
|
||||
type Client struct {
|
||||
serverAddr string
|
||||
sendBPS, recvBPS uint64
|
||||
auth []byte
|
||||
congestionFactory CongestionFactory
|
||||
obfuscator Obfuscator
|
||||
|
||||
tlsConfig *tls.Config
|
||||
quicConfig *quic.Config
|
||||
|
||||
quicSession quic.Session
|
||||
reconnectMutex sync.Mutex
|
||||
closed bool
|
||||
|
||||
udpSessionMutex sync.RWMutex
|
||||
udpSessionMap map[uint32]chan *udpMessage
|
||||
}
|
||||
|
||||
func NewClient(serverAddr string, username string, password string, tlsConfig *tls.Config, quicConfig *quic.Config,
|
||||
func NewClient(serverAddr string, auth []byte, tlsConfig *tls.Config, quicConfig *quic.Config,
|
||||
sendBPS uint64, recvBPS uint64, congestionFactory CongestionFactory, obfuscator Obfuscator) (*Client, error) {
|
||||
c := &Client{
|
||||
serverAddr: serverAddr,
|
||||
username: username,
|
||||
password: password,
|
||||
tlsConfig: tlsConfig,
|
||||
quicConfig: quicConfig,
|
||||
sendBPS: sendBPS,
|
||||
recvBPS: recvBPS,
|
||||
auth: auth,
|
||||
congestionFactory: congestionFactory,
|
||||
obfuscator: obfuscator,
|
||||
tlsConfig: tlsConfig,
|
||||
quicConfig: quicConfig,
|
||||
}
|
||||
if err := c.connectToServer(); err != nil {
|
||||
return nil, err
|
||||
@@ -51,58 +56,6 @@ func NewClient(serverAddr string, username string, password string, tlsConfig *t
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) Dial(packet bool, addr string) (net.Conn, error) {
|
||||
stream, localAddr, remoteAddr, err := c.openStreamWithReconnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
req := &pb.ClientConnectRequest{Address: addr}
|
||||
if packet {
|
||||
req.Type = pb.ConnectionType_Packet
|
||||
} else {
|
||||
req.Type = pb.ConnectionType_Stream
|
||||
}
|
||||
err = writeClientConnectRequest(stream, req)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Read response
|
||||
resp, err := readServerConnectResponse(stream)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
if resp.Result != pb.ConnectResult_CONN_SUCCESS {
|
||||
_ = stream.Close()
|
||||
return nil, fmt.Errorf("server rejected the connection %s (msg: %s)",
|
||||
resp.Result.String(), resp.Message)
|
||||
}
|
||||
connWrap := &utils.QUICStreamWrapperConn{
|
||||
Orig: stream,
|
||||
PseudoLocalAddr: localAddr,
|
||||
PseudoRemoteAddr: remoteAddr,
|
||||
}
|
||||
if packet {
|
||||
return &utils.PacketWrapperConn{Orig: connWrap}, nil
|
||||
} else {
|
||||
return connWrap, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Stats() (uint64, uint64) {
|
||||
return atomic.LoadUint64(&c.inboundBytes), atomic.LoadUint64(&c.outboundBytes)
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.reconnectMutex.Lock()
|
||||
defer c.reconnectMutex.Unlock()
|
||||
err := c.quicSession.CloseWithError(closeErrorCodeGeneric, "generic")
|
||||
c.closed = true
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) connectToServer() error {
|
||||
serverUDPAddr, err := net.ResolveUDPAddr("udp", c.serverAddr)
|
||||
if err != nil {
|
||||
@@ -124,75 +77,282 @@ func (c *Client) connectToServer() error {
|
||||
return err
|
||||
}
|
||||
// Control stream
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), controlStreamTimeout)
|
||||
ctlStream, err := qs.OpenStreamSync(ctx)
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), protocolTimeout)
|
||||
stream, err := qs.OpenStreamSync(ctx)
|
||||
ctxCancel()
|
||||
if err != nil {
|
||||
_ = qs.CloseWithError(closeErrorCodeProtocolFailure, "control stream error")
|
||||
_ = qs.CloseWithError(closeErrorCodeProtocol, "protocol error")
|
||||
return err
|
||||
}
|
||||
result, msg, err := c.handleControlStream(qs, ctlStream)
|
||||
ok, msg, err := c.handleControlStream(qs, stream)
|
||||
if err != nil {
|
||||
_ = qs.CloseWithError(closeErrorCodeProtocolFailure, "control stream handling error")
|
||||
_ = qs.CloseWithError(closeErrorCodeProtocol, "protocol error")
|
||||
return err
|
||||
}
|
||||
if result != pb.AuthResult_AUTH_SUCCESS {
|
||||
_ = qs.CloseWithError(closeErrorCodeProtocolFailure, "authentication failure")
|
||||
return fmt.Errorf("authentication failure %s (msg: %s)", result.String(), msg)
|
||||
if !ok {
|
||||
_ = qs.CloseWithError(closeErrorCodeAuth, "auth error")
|
||||
return fmt.Errorf("auth error: %s", msg)
|
||||
}
|
||||
// All good
|
||||
c.udpSessionMap = make(map[uint32]chan *udpMessage)
|
||||
go c.handleMessage(qs)
|
||||
c.quicSession = qs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleControlStream(qs quic.Session, stream quic.Stream) (pb.AuthResult, string, error) {
|
||||
err := writeClientAuthRequest(stream, &pb.ClientAuthRequest{
|
||||
Credential: &pb.Credential{
|
||||
Username: c.username,
|
||||
Password: c.password,
|
||||
},
|
||||
Speed: &pb.Speed{
|
||||
SendBps: c.sendBPS,
|
||||
ReceiveBps: c.recvBPS,
|
||||
func (c *Client) handleControlStream(qs quic.Session, stream quic.Stream) (bool, string, error) {
|
||||
// Send client hello
|
||||
err := struc.Pack(stream, &clientHello{
|
||||
Rate: transmissionRate{
|
||||
SendBPS: c.sendBPS,
|
||||
RecvBPS: c.recvBPS,
|
||||
},
|
||||
Auth: c.auth,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
return false, "", err
|
||||
}
|
||||
// Response
|
||||
resp, err := readServerAuthResponse(stream)
|
||||
// Receive server hello
|
||||
var sh serverHello
|
||||
err = struc.Unpack(stream, &sh)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
return false, "", err
|
||||
}
|
||||
// Set the congestion accordingly
|
||||
if resp.Result == pb.AuthResult_AUTH_SUCCESS && c.congestionFactory != nil {
|
||||
qs.SetCongestion(c.congestionFactory(resp.Speed.ReceiveBps))
|
||||
if sh.OK && c.congestionFactory != nil {
|
||||
qs.SetCongestionControl(c.congestionFactory(sh.Rate.RecvBPS))
|
||||
}
|
||||
return resp.Result, resp.Message, nil
|
||||
return true, sh.Message, nil
|
||||
}
|
||||
|
||||
func (c *Client) openStreamWithReconnect() (quic.Stream, net.Addr, net.Addr, error) {
|
||||
func (c *Client) handleMessage(qs quic.Session) {
|
||||
for {
|
||||
msg, err := qs.ReceiveMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
var udpMsg udpMessage
|
||||
err = struc.Unpack(bytes.NewBuffer(msg), &udpMsg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
c.udpSessionMutex.RLock()
|
||||
ch, ok := c.udpSessionMap[udpMsg.SessionID]
|
||||
if ok {
|
||||
select {
|
||||
case ch <- &udpMsg:
|
||||
// OK
|
||||
default:
|
||||
// Silently drop the message when the channel is full
|
||||
}
|
||||
}
|
||||
c.udpSessionMutex.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) openStreamWithReconnect() (quic.Session, quic.Stream, error) {
|
||||
c.reconnectMutex.Lock()
|
||||
defer c.reconnectMutex.Unlock()
|
||||
if c.closed {
|
||||
return nil, nil, nil, ErrClosed
|
||||
return nil, nil, ErrClosed
|
||||
}
|
||||
stream, err := c.quicSession.OpenStream()
|
||||
if err == nil {
|
||||
// All good
|
||||
return stream, c.quicSession.LocalAddr(), c.quicSession.RemoteAddr(), nil
|
||||
return c.quicSession, stream, nil
|
||||
}
|
||||
// Something is wrong
|
||||
if nErr, ok := err.(net.Error); ok && nErr.Temporary() {
|
||||
// Temporary error, just return
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// Permanent error, need to reconnect
|
||||
if err := c.connectToServer(); err != nil {
|
||||
// Still error, oops
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// We are not going to try again even if it still fails the second time
|
||||
stream, err = c.quicSession.OpenStream()
|
||||
return stream, c.quicSession.LocalAddr(), c.quicSession.RemoteAddr(), err
|
||||
return c.quicSession, stream, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialTCP(addr string) (net.Conn, error) {
|
||||
session, stream, err := c.openStreamWithReconnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
err = struc.Pack(stream, &clientRequest{
|
||||
UDP: false,
|
||||
Address: addr,
|
||||
})
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Read response
|
||||
var sr serverResponse
|
||||
err = struc.Unpack(stream, &sr)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
if !sr.OK {
|
||||
_ = stream.Close()
|
||||
return nil, fmt.Errorf("connection rejected: %s", sr.Message)
|
||||
}
|
||||
return &quicConn{
|
||||
Orig: stream,
|
||||
PseudoLocalAddr: session.LocalAddr(),
|
||||
PseudoRemoteAddr: session.RemoteAddr(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialUDP() (UDPConn, error) {
|
||||
session, stream, err := c.openStreamWithReconnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
err = struc.Pack(stream, &clientRequest{
|
||||
UDP: true,
|
||||
})
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Read response
|
||||
var sr serverResponse
|
||||
err = struc.Unpack(stream, &sr)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
if !sr.OK {
|
||||
_ = stream.Close()
|
||||
return nil, fmt.Errorf("connection rejected: %s", sr.Message)
|
||||
}
|
||||
|
||||
// Create a session in the map
|
||||
c.udpSessionMutex.Lock()
|
||||
nCh := make(chan *udpMessage, 1024)
|
||||
// Store the current session map for CloseFunc below
|
||||
// to ensures that we are adding and removing sessions on the same map,
|
||||
// as reconnecting will reassign the map
|
||||
sessionMap := c.udpSessionMap
|
||||
sessionMap[sr.UDPSessionID] = nCh
|
||||
c.udpSessionMutex.Unlock()
|
||||
|
||||
pktConn := &quicPktConn{
|
||||
Session: session,
|
||||
Stream: stream,
|
||||
CloseFunc: func() {
|
||||
c.udpSessionMutex.Lock()
|
||||
if ch, ok := sessionMap[sr.UDPSessionID]; ok {
|
||||
close(ch)
|
||||
delete(sessionMap, sr.UDPSessionID)
|
||||
}
|
||||
c.udpSessionMutex.Unlock()
|
||||
},
|
||||
UDPSessionID: sr.UDPSessionID,
|
||||
MsgCh: nCh,
|
||||
}
|
||||
go pktConn.Hold()
|
||||
return pktConn, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.reconnectMutex.Lock()
|
||||
defer c.reconnectMutex.Unlock()
|
||||
err := c.quicSession.CloseWithError(closeErrorCodeGeneric, "")
|
||||
c.closed = true
|
||||
return err
|
||||
}
|
||||
|
||||
type quicConn struct {
|
||||
Orig quic.Stream
|
||||
PseudoLocalAddr net.Addr
|
||||
PseudoRemoteAddr net.Addr
|
||||
}
|
||||
|
||||
func (w *quicConn) Read(b []byte) (n int, err error) {
|
||||
return w.Orig.Read(b)
|
||||
}
|
||||
|
||||
func (w *quicConn) Write(b []byte) (n int, err error) {
|
||||
return w.Orig.Write(b)
|
||||
}
|
||||
|
||||
func (w *quicConn) Close() error {
|
||||
return w.Orig.Close()
|
||||
}
|
||||
|
||||
func (w *quicConn) LocalAddr() net.Addr {
|
||||
return w.PseudoLocalAddr
|
||||
}
|
||||
|
||||
func (w *quicConn) RemoteAddr() net.Addr {
|
||||
return w.PseudoRemoteAddr
|
||||
}
|
||||
|
||||
func (w *quicConn) SetDeadline(t time.Time) error {
|
||||
return w.Orig.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (w *quicConn) SetReadDeadline(t time.Time) error {
|
||||
return w.Orig.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (w *quicConn) SetWriteDeadline(t time.Time) error {
|
||||
return w.Orig.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type UDPConn interface {
|
||||
ReadFrom() ([]byte, string, error)
|
||||
WriteTo([]byte, string) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type quicPktConn struct {
|
||||
Session quic.Session
|
||||
Stream quic.Stream
|
||||
CloseFunc func()
|
||||
UDPSessionID uint32
|
||||
MsgCh <-chan *udpMessage
|
||||
}
|
||||
|
||||
func (c *quicPktConn) 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 *quicPktConn) ReadFrom() ([]byte, string, error) {
|
||||
msg := <-c.MsgCh
|
||||
if msg == nil {
|
||||
// Closed
|
||||
return nil, "", ErrClosed
|
||||
}
|
||||
return msg.Data, msg.Address, nil
|
||||
}
|
||||
|
||||
func (c *quicPktConn) WriteTo(p []byte, addr string) error {
|
||||
var msgBuf bytes.Buffer
|
||||
_ = struc.Pack(&msgBuf, &udpMessage{
|
||||
SessionID: c.UDPSessionID,
|
||||
Address: addr,
|
||||
Data: p,
|
||||
})
|
||||
return c.Session.SendMessage(msgBuf.Bytes())
|
||||
}
|
||||
|
||||
func (c *quicPktConn) Close() error {
|
||||
c.CloseFunc()
|
||||
return c.Stream.Close()
|
||||
}
|
||||
|
58
pkg/core/client_udp_test.go
Normal file
58
pkg/core/client_udp_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClientUDP(t *testing.T) {
|
||||
client, err := NewClient("toby.moe:36713", nil, &tls.Config{
|
||||
NextProtos: []string{"hysteria"},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}, &quic.Config{
|
||||
EnableDatagrams: true,
|
||||
}, 125000, 125000, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn, err := client.DialUDP()
|
||||
if err != nil {
|
||||
t.Fatal("conn DialUDP", err)
|
||||
}
|
||||
t.Run("8.8.8.8", func(t *testing.T) {
|
||||
dnsReq := []byte{0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x62, 0x61, 0x69, 0x64, 0x75, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01}
|
||||
err := conn.WriteTo(dnsReq, "8.8.8.8:53")
|
||||
if err != nil {
|
||||
t.Error("WriteTo", err)
|
||||
}
|
||||
buf, _, err := conn.ReadFrom()
|
||||
if err != nil {
|
||||
t.Error("ReadFrom", err)
|
||||
}
|
||||
if buf[0] != dnsReq[0] || buf[1] != dnsReq[1] {
|
||||
t.Error("invalid response")
|
||||
}
|
||||
})
|
||||
t.Run("1.1.1.1", func(t *testing.T) {
|
||||
dnsReq := []byte{0x66, 0x77, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x62, 0x61, 0x69, 0x64, 0x75, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01}
|
||||
err := conn.WriteTo(dnsReq, "1.1.1.1:53")
|
||||
if err != nil {
|
||||
t.Error("WriteTo", err)
|
||||
}
|
||||
buf, _, err := conn.ReadFrom()
|
||||
if err != nil {
|
||||
t.Error("ReadFrom", err)
|
||||
}
|
||||
if buf[0] != dnsReq[0] || buf[1] != dnsReq[1] {
|
||||
t.Error("invalid response")
|
||||
}
|
||||
})
|
||||
t.Run("Close", func(t *testing.T) {
|
||||
_ = conn.Close()
|
||||
_, _, err := conn.ReadFrom()
|
||||
if err != ErrClosed {
|
||||
t.Error("closed conn not returning the correct error")
|
||||
}
|
||||
})
|
||||
}
|
@@ -1,104 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/tobyxdd/hysteria/pkg/core/pb"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
closeErrorCodeGeneric = 0
|
||||
closeErrorCodeProtocolFailure = 1
|
||||
)
|
||||
|
||||
func readDataBlock(r io.Reader) ([]byte, error) {
|
||||
var sz uint32
|
||||
if err := binary.Read(r, controlProtocolEndian, &sz); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, sz)
|
||||
_, err := io.ReadFull(r, buf)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func writeDataBlock(w io.Writer, data []byte) error {
|
||||
sz := uint32(len(data))
|
||||
if err := binary.Write(w, controlProtocolEndian, &sz); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := w.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func readClientAuthRequest(r io.Reader) (*pb.ClientAuthRequest, error) {
|
||||
bs, err := readDataBlock(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var req pb.ClientAuthRequest
|
||||
err = proto.Unmarshal(bs, &req)
|
||||
return &req, err
|
||||
}
|
||||
|
||||
func writeClientAuthRequest(w io.Writer, req *pb.ClientAuthRequest) error {
|
||||
bs, err := proto.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeDataBlock(w, bs)
|
||||
}
|
||||
|
||||
func readServerAuthResponse(r io.Reader) (*pb.ServerAuthResponse, error) {
|
||||
bs, err := readDataBlock(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp pb.ServerAuthResponse
|
||||
err = proto.Unmarshal(bs, &resp)
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
func writeServerAuthResponse(w io.Writer, resp *pb.ServerAuthResponse) error {
|
||||
bs, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeDataBlock(w, bs)
|
||||
}
|
||||
|
||||
func readClientConnectRequest(r io.Reader) (*pb.ClientConnectRequest, error) {
|
||||
bs, err := readDataBlock(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var req pb.ClientConnectRequest
|
||||
err = proto.Unmarshal(bs, &req)
|
||||
return &req, err
|
||||
}
|
||||
|
||||
func writeClientConnectRequest(w io.Writer, req *pb.ClientConnectRequest) error {
|
||||
bs, err := proto.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeDataBlock(w, bs)
|
||||
}
|
||||
|
||||
func readServerConnectResponse(r io.Reader) (*pb.ServerConnectResponse, error) {
|
||||
bs, err := readDataBlock(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp pb.ServerConnectResponse
|
||||
err = proto.Unmarshal(bs, &resp)
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
func writeServerConnectResponse(w io.Writer, resp *pb.ServerConnectResponse) error {
|
||||
bs, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeDataBlock(w, bs)
|
||||
}
|
@@ -1,440 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: control.proto
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type AuthResult int32
|
||||
|
||||
const (
|
||||
AuthResult_AUTH_SUCCESS AuthResult = 0
|
||||
AuthResult_AUTH_INVALID_CRED AuthResult = 1
|
||||
AuthResult_AUTH_INTERNAL_ERROR AuthResult = 2
|
||||
)
|
||||
|
||||
var AuthResult_name = map[int32]string{
|
||||
0: "AUTH_SUCCESS",
|
||||
1: "AUTH_INVALID_CRED",
|
||||
2: "AUTH_INTERNAL_ERROR",
|
||||
}
|
||||
|
||||
var AuthResult_value = map[string]int32{
|
||||
"AUTH_SUCCESS": 0,
|
||||
"AUTH_INVALID_CRED": 1,
|
||||
"AUTH_INTERNAL_ERROR": 2,
|
||||
}
|
||||
|
||||
func (x AuthResult) String() string {
|
||||
return proto.EnumName(AuthResult_name, int32(x))
|
||||
}
|
||||
|
||||
func (AuthResult) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{0}
|
||||
}
|
||||
|
||||
type ConnectionType int32
|
||||
|
||||
const (
|
||||
ConnectionType_Stream ConnectionType = 0
|
||||
ConnectionType_Packet ConnectionType = 1
|
||||
)
|
||||
|
||||
var ConnectionType_name = map[int32]string{
|
||||
0: "Stream",
|
||||
1: "Packet",
|
||||
}
|
||||
|
||||
var ConnectionType_value = map[string]int32{
|
||||
"Stream": 0,
|
||||
"Packet": 1,
|
||||
}
|
||||
|
||||
func (x ConnectionType) String() string {
|
||||
return proto.EnumName(ConnectionType_name, int32(x))
|
||||
}
|
||||
|
||||
func (ConnectionType) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{1}
|
||||
}
|
||||
|
||||
type ConnectResult int32
|
||||
|
||||
const (
|
||||
ConnectResult_CONN_SUCCESS ConnectResult = 0
|
||||
ConnectResult_CONN_FAILED ConnectResult = 1
|
||||
ConnectResult_CONN_BLOCKED ConnectResult = 2
|
||||
)
|
||||
|
||||
var ConnectResult_name = map[int32]string{
|
||||
0: "CONN_SUCCESS",
|
||||
1: "CONN_FAILED",
|
||||
2: "CONN_BLOCKED",
|
||||
}
|
||||
|
||||
var ConnectResult_value = map[string]int32{
|
||||
"CONN_SUCCESS": 0,
|
||||
"CONN_FAILED": 1,
|
||||
"CONN_BLOCKED": 2,
|
||||
}
|
||||
|
||||
func (x ConnectResult) String() string {
|
||||
return proto.EnumName(ConnectResult_name, int32(x))
|
||||
}
|
||||
|
||||
func (ConnectResult) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{2}
|
||||
}
|
||||
|
||||
type Speed struct {
|
||||
SendBps uint64 `protobuf:"varint,1,opt,name=send_bps,json=sendBps,proto3" json:"send_bps,omitempty"`
|
||||
ReceiveBps uint64 `protobuf:"varint,2,opt,name=receive_bps,json=receiveBps,proto3" json:"receive_bps,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Speed) Reset() { *m = Speed{} }
|
||||
func (m *Speed) String() string { return proto.CompactTextString(m) }
|
||||
func (*Speed) ProtoMessage() {}
|
||||
func (*Speed) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{0}
|
||||
}
|
||||
|
||||
func (m *Speed) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Speed.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Speed) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Speed.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Speed) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Speed.Merge(m, src)
|
||||
}
|
||||
func (m *Speed) XXX_Size() int {
|
||||
return xxx_messageInfo_Speed.Size(m)
|
||||
}
|
||||
func (m *Speed) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Speed.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Speed proto.InternalMessageInfo
|
||||
|
||||
func (m *Speed) GetSendBps() uint64 {
|
||||
if m != nil {
|
||||
return m.SendBps
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Speed) GetReceiveBps() uint64 {
|
||||
if m != nil {
|
||||
return m.ReceiveBps
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Credential struct {
|
||||
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Credential) Reset() { *m = Credential{} }
|
||||
func (m *Credential) String() string { return proto.CompactTextString(m) }
|
||||
func (*Credential) ProtoMessage() {}
|
||||
func (*Credential) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{1}
|
||||
}
|
||||
|
||||
func (m *Credential) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Credential.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Credential) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Credential.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Credential) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Credential.Merge(m, src)
|
||||
}
|
||||
func (m *Credential) XXX_Size() int {
|
||||
return xxx_messageInfo_Credential.Size(m)
|
||||
}
|
||||
func (m *Credential) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Credential.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Credential proto.InternalMessageInfo
|
||||
|
||||
func (m *Credential) GetUsername() string {
|
||||
if m != nil {
|
||||
return m.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Credential) GetPassword() string {
|
||||
if m != nil {
|
||||
return m.Password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ClientAuthRequest struct {
|
||||
Credential *Credential `protobuf:"bytes,1,opt,name=credential,proto3" json:"credential,omitempty"`
|
||||
Speed *Speed `protobuf:"bytes,2,opt,name=speed,proto3" json:"speed,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ClientAuthRequest) Reset() { *m = ClientAuthRequest{} }
|
||||
func (m *ClientAuthRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*ClientAuthRequest) ProtoMessage() {}
|
||||
func (*ClientAuthRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{2}
|
||||
}
|
||||
|
||||
func (m *ClientAuthRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ClientAuthRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ClientAuthRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ClientAuthRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ClientAuthRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ClientAuthRequest.Merge(m, src)
|
||||
}
|
||||
func (m *ClientAuthRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_ClientAuthRequest.Size(m)
|
||||
}
|
||||
func (m *ClientAuthRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ClientAuthRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ClientAuthRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *ClientAuthRequest) GetCredential() *Credential {
|
||||
if m != nil {
|
||||
return m.Credential
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ClientAuthRequest) GetSpeed() *Speed {
|
||||
if m != nil {
|
||||
return m.Speed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ServerAuthResponse struct {
|
||||
Result AuthResult `protobuf:"varint,1,opt,name=result,proto3,enum=core.AuthResult" json:"result,omitempty"`
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
Speed *Speed `protobuf:"bytes,3,opt,name=speed,proto3" json:"speed,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ServerAuthResponse) Reset() { *m = ServerAuthResponse{} }
|
||||
func (m *ServerAuthResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*ServerAuthResponse) ProtoMessage() {}
|
||||
func (*ServerAuthResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{3}
|
||||
}
|
||||
|
||||
func (m *ServerAuthResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ServerAuthResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ServerAuthResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ServerAuthResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ServerAuthResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ServerAuthResponse.Merge(m, src)
|
||||
}
|
||||
func (m *ServerAuthResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_ServerAuthResponse.Size(m)
|
||||
}
|
||||
func (m *ServerAuthResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ServerAuthResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ServerAuthResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *ServerAuthResponse) GetResult() AuthResult {
|
||||
if m != nil {
|
||||
return m.Result
|
||||
}
|
||||
return AuthResult_AUTH_SUCCESS
|
||||
}
|
||||
|
||||
func (m *ServerAuthResponse) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ServerAuthResponse) GetSpeed() *Speed {
|
||||
if m != nil {
|
||||
return m.Speed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ClientConnectRequest struct {
|
||||
Type ConnectionType `protobuf:"varint,1,opt,name=type,proto3,enum=core.ConnectionType" json:"type,omitempty"`
|
||||
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ClientConnectRequest) Reset() { *m = ClientConnectRequest{} }
|
||||
func (m *ClientConnectRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*ClientConnectRequest) ProtoMessage() {}
|
||||
func (*ClientConnectRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{4}
|
||||
}
|
||||
|
||||
func (m *ClientConnectRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ClientConnectRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ClientConnectRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ClientConnectRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ClientConnectRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ClientConnectRequest.Merge(m, src)
|
||||
}
|
||||
func (m *ClientConnectRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_ClientConnectRequest.Size(m)
|
||||
}
|
||||
func (m *ClientConnectRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ClientConnectRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ClientConnectRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *ClientConnectRequest) GetType() ConnectionType {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ConnectionType_Stream
|
||||
}
|
||||
|
||||
func (m *ClientConnectRequest) GetAddress() string {
|
||||
if m != nil {
|
||||
return m.Address
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ServerConnectResponse struct {
|
||||
Result ConnectResult `protobuf:"varint,1,opt,name=result,proto3,enum=core.ConnectResult" json:"result,omitempty"`
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ServerConnectResponse) Reset() { *m = ServerConnectResponse{} }
|
||||
func (m *ServerConnectResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*ServerConnectResponse) ProtoMessage() {}
|
||||
func (*ServerConnectResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0c5120591600887d, []int{5}
|
||||
}
|
||||
|
||||
func (m *ServerConnectResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ServerConnectResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ServerConnectResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ServerConnectResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ServerConnectResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ServerConnectResponse.Merge(m, src)
|
||||
}
|
||||
func (m *ServerConnectResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_ServerConnectResponse.Size(m)
|
||||
}
|
||||
func (m *ServerConnectResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ServerConnectResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ServerConnectResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *ServerConnectResponse) GetResult() ConnectResult {
|
||||
if m != nil {
|
||||
return m.Result
|
||||
}
|
||||
return ConnectResult_CONN_SUCCESS
|
||||
}
|
||||
|
||||
func (m *ServerConnectResponse) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("core.AuthResult", AuthResult_name, AuthResult_value)
|
||||
proto.RegisterEnum("core.ConnectionType", ConnectionType_name, ConnectionType_value)
|
||||
proto.RegisterEnum("core.ConnectResult", ConnectResult_name, ConnectResult_value)
|
||||
proto.RegisterType((*Speed)(nil), "core.Speed")
|
||||
proto.RegisterType((*Credential)(nil), "core.Credential")
|
||||
proto.RegisterType((*ClientAuthRequest)(nil), "core.ClientAuthRequest")
|
||||
proto.RegisterType((*ServerAuthResponse)(nil), "core.ServerAuthResponse")
|
||||
proto.RegisterType((*ClientConnectRequest)(nil), "core.ClientConnectRequest")
|
||||
proto.RegisterType((*ServerConnectResponse)(nil), "core.ServerConnectResponse")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("control.proto", fileDescriptor_0c5120591600887d)
|
||||
}
|
||||
|
||||
var fileDescriptor_0c5120591600887d = []byte{
|
||||
// 434 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xd1, 0x6e, 0xd3, 0x30,
|
||||
0x14, 0x86, 0xd7, 0xd2, 0x75, 0xdb, 0x09, 0x1b, 0x99, 0xb7, 0x89, 0xc1, 0x0d, 0x90, 0xab, 0xaa,
|
||||
0x48, 0x15, 0x1a, 0x4f, 0x90, 0x3a, 0x41, 0x54, 0x54, 0x29, 0x72, 0x3a, 0x2e, 0xb8, 0xa0, 0xca,
|
||||
0x92, 0x23, 0x56, 0x91, 0xda, 0xc6, 0x76, 0x86, 0x26, 0x5e, 0x1e, 0xc5, 0x71, 0xd2, 0x16, 0x09,
|
||||
0x89, 0xbb, 0x9e, 0x73, 0x7e, 0xfd, 0x9f, 0xbf, 0x2a, 0x70, 0x9a, 0x0b, 0x6e, 0x94, 0x28, 0x27,
|
||||
0x52, 0x09, 0x23, 0xc8, 0x20, 0x17, 0x0a, 0x03, 0x0a, 0x87, 0xa9, 0x44, 0x2c, 0xc8, 0x0b, 0x38,
|
||||
0xd6, 0xc8, 0x8b, 0xd5, 0x9d, 0xd4, 0xd7, 0xbd, 0xd7, 0xbd, 0xd1, 0x80, 0x1d, 0xd5, 0xf3, 0x54,
|
||||
0x6a, 0xf2, 0x0a, 0x3c, 0x85, 0x39, 0xae, 0x1f, 0xd0, 0x5e, 0xfb, 0xf6, 0x0a, 0x6e, 0x35, 0x95,
|
||||
0x3a, 0x88, 0x00, 0xa8, 0xc2, 0x02, 0xb9, 0x59, 0x67, 0x25, 0x79, 0x09, 0xc7, 0x95, 0x46, 0xc5,
|
||||
0xb3, 0x0d, 0xda, 0xa6, 0x13, 0xd6, 0xcd, 0xf5, 0x4d, 0x66, 0x5a, 0xff, 0x12, 0xaa, 0xb0, 0x3d,
|
||||
0x27, 0xac, 0x9b, 0x83, 0x7b, 0x38, 0xa7, 0xe5, 0x1a, 0xb9, 0x09, 0x2b, 0x73, 0xcf, 0xf0, 0x67,
|
||||
0x85, 0xda, 0x90, 0x77, 0x00, 0x79, 0x57, 0x6d, 0xeb, 0xbc, 0x1b, 0x7f, 0x52, 0x3f, 0x7d, 0xb2,
|
||||
0x45, 0xb2, 0x9d, 0x0c, 0x79, 0x03, 0x87, 0xba, 0x36, 0xb2, 0xfd, 0xde, 0x8d, 0xd7, 0x84, 0xad,
|
||||
0x24, 0x6b, 0x2e, 0xc1, 0x6f, 0x20, 0x29, 0xaa, 0x07, 0x54, 0x0d, 0x49, 0x4b, 0xc1, 0x35, 0x92,
|
||||
0x11, 0x0c, 0x15, 0xea, 0xaa, 0x34, 0x16, 0x73, 0xd6, 0x62, 0x5c, 0xa6, 0x2a, 0x0d, 0x73, 0x77,
|
||||
0x72, 0x0d, 0x47, 0x1b, 0xd4, 0x3a, 0xfb, 0x8e, 0x4e, 0xa2, 0x1d, 0xb7, 0xf0, 0x27, 0xff, 0x84,
|
||||
0x7f, 0x85, 0xcb, 0x46, 0x93, 0x0a, 0xce, 0x31, 0x37, 0xad, 0xe9, 0x08, 0x06, 0xe6, 0x51, 0xa2,
|
||||
0x83, 0x5f, 0x3a, 0xc7, 0x26, 0xb3, 0x16, 0x7c, 0xf9, 0x28, 0x91, 0xd9, 0x44, 0x8d, 0xcf, 0x8a,
|
||||
0x42, 0xa1, 0xd6, 0x2d, 0xde, 0x8d, 0xc1, 0x37, 0xb8, 0x6a, 0xc4, 0xba, 0x6e, 0xe7, 0xf6, 0xf6,
|
||||
0x2f, 0xb7, 0x8b, 0xbd, 0xfa, 0xff, 0xd5, 0x1b, 0x27, 0x00, 0xdb, 0xbf, 0x83, 0xf8, 0xf0, 0x34,
|
||||
0xbc, 0x5d, 0x7e, 0x5c, 0xa5, 0xb7, 0x94, 0xc6, 0x69, 0xea, 0x1f, 0x90, 0x2b, 0x38, 0xb7, 0x9b,
|
||||
0x59, 0xf2, 0x25, 0x9c, 0xcf, 0xa2, 0x15, 0x65, 0x71, 0xe4, 0xf7, 0xc8, 0x73, 0xb8, 0x70, 0xeb,
|
||||
0x65, 0xcc, 0x92, 0x70, 0xbe, 0x8a, 0x19, 0x5b, 0x30, 0xbf, 0x3f, 0x1e, 0xc1, 0xd9, 0xbe, 0x21,
|
||||
0x01, 0x18, 0xa6, 0x46, 0x61, 0xb6, 0xf1, 0x0f, 0xea, 0xdf, 0x9f, 0xb3, 0xfc, 0x07, 0x1a, 0xbf,
|
||||
0x37, 0x8e, 0xe0, 0x74, 0xef, 0xb1, 0x35, 0x9c, 0x2e, 0x92, 0x64, 0x07, 0xfe, 0x0c, 0x3c, 0xbb,
|
||||
0xf9, 0x10, 0xce, 0xe6, 0x16, 0xdb, 0x46, 0xa6, 0xf3, 0x05, 0xfd, 0x14, 0x47, 0x7e, 0xff, 0x6e,
|
||||
0x68, 0x3f, 0xfd, 0xf7, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x2f, 0x8d, 0x6f, 0x0b, 0x03,
|
||||
0x00, 0x00,
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
syntax = "proto3";
|
||||
package core;
|
||||
|
||||
message Speed {
|
||||
uint64 send_bps = 1;
|
||||
uint64 receive_bps = 2;
|
||||
}
|
||||
|
||||
message Credential {
|
||||
string username = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
enum AuthResult {
|
||||
AUTH_SUCCESS = 0;
|
||||
AUTH_INVALID_CRED = 1;
|
||||
AUTH_INTERNAL_ERROR = 2;
|
||||
}
|
||||
|
||||
message ClientAuthRequest {
|
||||
Credential credential = 1;
|
||||
Speed speed = 2;
|
||||
}
|
||||
|
||||
message ServerAuthResponse {
|
||||
AuthResult result = 1;
|
||||
string message = 2;
|
||||
Speed speed = 3;
|
||||
}
|
||||
|
||||
enum ConnectionType {
|
||||
Stream = 0;
|
||||
Packet = 1;
|
||||
}
|
||||
|
||||
enum ConnectResult {
|
||||
CONN_SUCCESS = 0;
|
||||
CONN_FAILED = 1;
|
||||
CONN_BLOCKED = 2;
|
||||
}
|
||||
|
||||
message ClientConnectRequest {
|
||||
ConnectionType type = 1;
|
||||
string address = 2;
|
||||
}
|
||||
|
||||
message ServerConnectResponse {
|
||||
ConnectResult result = 1;
|
||||
string message = 2;
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
package pb
|
||||
|
||||
//go:generate protoc --go_out=. control.proto
|
53
pkg/core/protocol.go
Normal file
53
pkg/core/protocol.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
protocolVersion = uint8(1)
|
||||
protocolTimeout = 10 * time.Second
|
||||
|
||||
closeErrorCodeGeneric = 0
|
||||
closeErrorCodeProtocol = 1
|
||||
closeErrorCodeAuth = 2
|
||||
)
|
||||
|
||||
type transmissionRate struct {
|
||||
SendBPS uint64
|
||||
RecvBPS uint64
|
||||
}
|
||||
|
||||
type clientHello struct {
|
||||
Rate transmissionRate
|
||||
AuthLen uint16 `struc:"sizeof=Auth"`
|
||||
Auth []byte
|
||||
}
|
||||
|
||||
type serverHello struct {
|
||||
OK bool
|
||||
Rate transmissionRate
|
||||
MessageLen uint16 `struc:"sizeof=Message"`
|
||||
Message string
|
||||
}
|
||||
|
||||
type clientRequest struct {
|
||||
UDP bool
|
||||
AddressLen uint16 `struc:"sizeof=Address"`
|
||||
Address string
|
||||
}
|
||||
|
||||
type serverResponse struct {
|
||||
OK bool
|
||||
UDPSessionID uint32
|
||||
MessageLen uint16 `struc:"sizeof=Message"`
|
||||
Message string
|
||||
}
|
||||
|
||||
type udpMessage struct {
|
||||
SessionID uint32
|
||||
AddressLen uint16 `struc:"sizeof=Address"`
|
||||
Address string
|
||||
DataLen uint16 `struc:"sizeof=Data"`
|
||||
Data []byte
|
||||
}
|
@@ -4,61 +4,40 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/tobyxdd/hysteria/pkg/core/pb"
|
||||
"github.com/tobyxdd/hysteria/pkg/utils"
|
||||
"io"
|
||||
"github.com/lunixbochs/struc"
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AuthResult int32
|
||||
type ConnectionType int32
|
||||
type ConnectResult int32
|
||||
const dialTimeout = 10 * time.Second
|
||||
|
||||
const (
|
||||
AuthResultSuccess AuthResult = iota
|
||||
AuthResultInvalidCred
|
||||
AuthResultInternalError
|
||||
)
|
||||
|
||||
const (
|
||||
ConnectionTypeStream ConnectionType = iota
|
||||
ConnectionTypePacket
|
||||
)
|
||||
|
||||
const (
|
||||
ConnectResultSuccess ConnectResult = iota
|
||||
ConnectResultFailed
|
||||
ConnectResultBlocked
|
||||
)
|
||||
|
||||
type ClientAuthFunc func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (AuthResult, string)
|
||||
type ClientDisconnectedFunc func(addr net.Addr, username string, err error)
|
||||
type HandleRequestFunc func(addr net.Addr, username string, id int, reqType ConnectionType, reqAddr string) (ConnectResult, string, io.ReadWriteCloser)
|
||||
type RequestClosedFunc func(addr net.Addr, username string, id int, reqType ConnectionType, reqAddr string, err error)
|
||||
type AuthFunc func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string)
|
||||
type TCPRequestFunc func(addr net.Addr, auth []byte, reqAddr string, action acl.Action, arg string)
|
||||
type TCPErrorFunc func(addr net.Addr, auth []byte, reqAddr string, err error)
|
||||
type UDPRequestFunc func(addr net.Addr, auth []byte, sessionID uint32)
|
||||
type UDPErrorFunc func(addr net.Addr, auth []byte, sessionID uint32, err error)
|
||||
|
||||
type Server struct {
|
||||
inboundBytes, outboundBytes uint64 // atomic
|
||||
sendBPS, recvBPS uint64
|
||||
congestionFactory CongestionFactory
|
||||
disableUDP bool
|
||||
aclEngine *acl.Engine
|
||||
|
||||
listener quic.Listener
|
||||
sendBPS, recvBPS uint64
|
||||
authFunc AuthFunc
|
||||
tcpRequestFunc TCPRequestFunc
|
||||
tcpErrorFunc TCPErrorFunc
|
||||
udpRequestFunc UDPRequestFunc
|
||||
udpErrorFunc UDPErrorFunc
|
||||
|
||||
congestionFactory CongestionFactory
|
||||
clientAuthFunc ClientAuthFunc
|
||||
clientDisconnectedFunc ClientDisconnectedFunc
|
||||
handleRequestFunc HandleRequestFunc
|
||||
requestClosedFunc RequestClosedFunc
|
||||
listener quic.Listener
|
||||
}
|
||||
|
||||
func NewServer(addr string, tlsConfig *tls.Config, quicConfig *quic.Config,
|
||||
sendBPS uint64, recvBPS uint64, congestionFactory CongestionFactory,
|
||||
obfuscator Obfuscator,
|
||||
clientAuthFunc ClientAuthFunc,
|
||||
clientDisconnectedFunc ClientDisconnectedFunc,
|
||||
handleRequestFunc HandleRequestFunc,
|
||||
requestClosedFunc RequestClosedFunc) (*Server, error) {
|
||||
sendBPS uint64, recvBPS uint64, congestionFactory CongestionFactory, disableUDP bool, aclEngine *acl.Engine,
|
||||
obfuscator Obfuscator, authFunc AuthFunc, tcpRequestFunc TCPRequestFunc, tcpErrorFunc TCPErrorFunc,
|
||||
udpRequestFunc UDPRequestFunc, udpErrorFunc UDPErrorFunc) (*Server, error) {
|
||||
packetConn, err := net.ListenPacket("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -75,14 +54,17 @@ func NewServer(addr string, tlsConfig *tls.Config, quicConfig *quic.Config,
|
||||
return nil, err
|
||||
}
|
||||
s := &Server{
|
||||
listener: listener,
|
||||
sendBPS: sendBPS,
|
||||
recvBPS: recvBPS,
|
||||
congestionFactory: congestionFactory,
|
||||
clientAuthFunc: clientAuthFunc,
|
||||
clientDisconnectedFunc: clientDisconnectedFunc,
|
||||
handleRequestFunc: handleRequestFunc,
|
||||
requestClosedFunc: requestClosedFunc,
|
||||
listener: listener,
|
||||
sendBPS: sendBPS,
|
||||
recvBPS: recvBPS,
|
||||
congestionFactory: congestionFactory,
|
||||
disableUDP: disableUDP,
|
||||
aclEngine: aclEngine,
|
||||
authFunc: authFunc,
|
||||
tcpRequestFunc: tcpRequestFunc,
|
||||
tcpErrorFunc: tcpErrorFunc,
|
||||
udpRequestFunc: udpRequestFunc,
|
||||
udpErrorFunc: udpErrorFunc,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -97,128 +79,71 @@ func (s *Server) Serve() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Stats() (uint64, uint64) {
|
||||
return atomic.LoadUint64(&s.inboundBytes), atomic.LoadUint64(&s.outboundBytes)
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
return s.listener.Close()
|
||||
}
|
||||
|
||||
func (s *Server) handleClient(cs quic.Session) {
|
||||
// Expect the client to create a control stream to send its own information
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), controlStreamTimeout)
|
||||
ctlStream, err := cs.AcceptStream(ctx)
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), protocolTimeout)
|
||||
stream, err := cs.AcceptStream(ctx)
|
||||
ctxCancel()
|
||||
if err != nil {
|
||||
_ = cs.CloseWithError(closeErrorCodeProtocolFailure, "control stream error")
|
||||
_ = cs.CloseWithError(closeErrorCodeProtocol, "protocol error")
|
||||
return
|
||||
}
|
||||
// Handle the control stream
|
||||
username, ok, err := s.handleControlStream(cs, ctlStream)
|
||||
auth, ok, err := s.handleControlStream(cs, stream)
|
||||
if err != nil {
|
||||
_ = cs.CloseWithError(closeErrorCodeProtocolFailure, "control stream handling error")
|
||||
_ = cs.CloseWithError(closeErrorCodeProtocol, "protocol error")
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
_ = cs.CloseWithError(closeErrorCodeGeneric, "authentication failure")
|
||||
_ = cs.CloseWithError(closeErrorCodeAuth, "auth error")
|
||||
return
|
||||
}
|
||||
// Start accepting streams
|
||||
var closeErr error
|
||||
for {
|
||||
stream, err := cs.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
closeErr = err
|
||||
break
|
||||
}
|
||||
go s.handleStream(cs.LocalAddr(), cs.RemoteAddr(), username, stream)
|
||||
}
|
||||
s.clientDisconnectedFunc(cs.RemoteAddr(), username, closeErr)
|
||||
_ = cs.CloseWithError(closeErrorCodeGeneric, "generic")
|
||||
// Start accepting streams and messages
|
||||
sc := newServerClient(cs, auth, s.disableUDP, s.aclEngine,
|
||||
s.tcpRequestFunc, s.tcpErrorFunc, s.udpRequestFunc, s.udpErrorFunc)
|
||||
sc.Run()
|
||||
_ = cs.CloseWithError(closeErrorCodeGeneric, "")
|
||||
}
|
||||
|
||||
// Auth & negotiate speed
|
||||
func (s *Server) handleControlStream(cs quic.Session, stream quic.Stream) (string, bool, error) {
|
||||
req, err := readClientAuthRequest(stream)
|
||||
func (s *Server) handleControlStream(cs quic.Session, stream quic.Stream) ([]byte, bool, error) {
|
||||
var ch clientHello
|
||||
err := struc.Unpack(stream, &ch)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
return nil, false, err
|
||||
}
|
||||
// Speed
|
||||
if req.Speed == nil || req.Speed.SendBps == 0 || req.Speed.ReceiveBps == 0 {
|
||||
return "", false, errors.New("incorrect speed provided by the client")
|
||||
if ch.Rate.SendBPS == 0 || ch.Rate.RecvBPS == 0 {
|
||||
return nil, false, errors.New("invalid rate from client")
|
||||
}
|
||||
serverSendBPS, serverReceiveBPS := req.Speed.ReceiveBps, req.Speed.SendBps
|
||||
serverSendBPS, serverRecvBPS := ch.Rate.RecvBPS, ch.Rate.SendBPS
|
||||
if s.sendBPS > 0 && serverSendBPS > s.sendBPS {
|
||||
serverSendBPS = s.sendBPS
|
||||
}
|
||||
if s.recvBPS > 0 && serverReceiveBPS > s.recvBPS {
|
||||
serverReceiveBPS = s.recvBPS
|
||||
if s.recvBPS > 0 && serverRecvBPS > s.recvBPS {
|
||||
serverRecvBPS = s.recvBPS
|
||||
}
|
||||
// Auth
|
||||
if req.Credential == nil {
|
||||
return "", false, errors.New("incorrect credential provided by the client")
|
||||
}
|
||||
authResult, msg := s.clientAuthFunc(cs.RemoteAddr(), req.Credential.Username, req.Credential.Password,
|
||||
serverSendBPS, serverReceiveBPS)
|
||||
ok, msg := s.authFunc(cs.RemoteAddr(), ch.Auth, serverSendBPS, serverRecvBPS)
|
||||
// Response
|
||||
err = writeServerAuthResponse(stream, &pb.ServerAuthResponse{
|
||||
Result: pb.AuthResult(authResult),
|
||||
Message: msg,
|
||||
Speed: &pb.Speed{
|
||||
SendBps: serverSendBPS,
|
||||
ReceiveBps: serverReceiveBPS,
|
||||
err = struc.Pack(stream, &serverHello{
|
||||
OK: ok,
|
||||
Rate: transmissionRate{
|
||||
SendBPS: serverSendBPS,
|
||||
RecvBPS: serverRecvBPS,
|
||||
},
|
||||
Message: msg,
|
||||
})
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
return nil, false, err
|
||||
}
|
||||
// Set the congestion accordingly
|
||||
if authResult == AuthResultSuccess && s.congestionFactory != nil {
|
||||
cs.SetCongestion(s.congestionFactory(serverSendBPS))
|
||||
if ok && s.congestionFactory != nil {
|
||||
cs.SetCongestionControl(s.congestionFactory(serverSendBPS))
|
||||
}
|
||||
return req.Credential.Username, authResult == AuthResultSuccess, nil
|
||||
}
|
||||
|
||||
func (s *Server) handleStream(localAddr net.Addr, remoteAddr net.Addr, username string, stream quic.Stream) {
|
||||
defer stream.Close()
|
||||
// Read request
|
||||
req, err := readClientConnectRequest(stream)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Create connection with the handler
|
||||
result, msg, conn := s.handleRequestFunc(remoteAddr, username, int(stream.StreamID()), ConnectionType(req.Type), req.Address)
|
||||
defer func() {
|
||||
if conn != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}()
|
||||
// Send response
|
||||
err = writeServerConnectResponse(stream, &pb.ServerConnectResponse{
|
||||
Result: pb.ConnectResult(result),
|
||||
Message: msg,
|
||||
})
|
||||
if err != nil {
|
||||
s.requestClosedFunc(remoteAddr, username, int(stream.StreamID()), ConnectionType(req.Type), req.Address, err)
|
||||
return
|
||||
}
|
||||
if result != ConnectResultSuccess {
|
||||
s.requestClosedFunc(remoteAddr, username, int(stream.StreamID()), ConnectionType(req.Type), req.Address,
|
||||
fmt.Errorf("handler returned an unsuccessful state %d (msg: %s)", result, msg))
|
||||
return
|
||||
}
|
||||
switch req.Type {
|
||||
case pb.ConnectionType_Stream:
|
||||
err = utils.PipePair(stream, conn, &s.outboundBytes, &s.inboundBytes)
|
||||
case pb.ConnectionType_Packet:
|
||||
err = utils.PipePair(&utils.PacketWrapperConn{Orig: &utils.QUICStreamWrapperConn{
|
||||
Orig: stream,
|
||||
PseudoLocalAddr: localAddr,
|
||||
PseudoRemoteAddr: remoteAddr,
|
||||
}}, conn, &s.outboundBytes, &s.inboundBytes)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported connection type %s", req.Type.String())
|
||||
}
|
||||
s.requestClosedFunc(remoteAddr, username, int(stream.StreamID()), ConnectionType(req.Type), req.Address, err)
|
||||
return ch.Auth, ok, nil
|
||||
}
|
||||
|
269
pkg/core/server_client.go
Normal file
269
pkg/core/server_client.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lunixbochs/struc"
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
"github.com/tobyxdd/hysteria/pkg/utils"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const udpBufferSize = 65535
|
||||
|
||||
type serverClient struct {
|
||||
CS quic.Session
|
||||
Auth []byte
|
||||
ClientAddr net.Addr
|
||||
DisableUDP bool
|
||||
ACLEngine *acl.Engine
|
||||
CTCPRequestFunc TCPRequestFunc
|
||||
CTCPErrorFunc TCPErrorFunc
|
||||
CUDPRequestFunc UDPRequestFunc
|
||||
CUDPErrorFunc UDPErrorFunc
|
||||
|
||||
udpSessionMutex sync.RWMutex
|
||||
udpSessionMap map[uint32]*net.UDPConn
|
||||
nextUDPSessionID uint32
|
||||
}
|
||||
|
||||
func newServerClient(cs quic.Session, auth []byte, disableUDP bool, ACLEngine *acl.Engine,
|
||||
CTCPRequestFunc TCPRequestFunc, CTCPErrorFunc TCPErrorFunc,
|
||||
CUDPRequestFunc UDPRequestFunc, CUDPErrorFunc UDPErrorFunc) *serverClient {
|
||||
return &serverClient{
|
||||
CS: cs,
|
||||
Auth: auth,
|
||||
ClientAddr: cs.RemoteAddr(),
|
||||
DisableUDP: disableUDP,
|
||||
ACLEngine: ACLEngine,
|
||||
CTCPRequestFunc: CTCPRequestFunc,
|
||||
CTCPErrorFunc: CTCPErrorFunc,
|
||||
CUDPRequestFunc: CUDPRequestFunc,
|
||||
CUDPErrorFunc: CUDPErrorFunc,
|
||||
udpSessionMap: make(map[uint32]*net.UDPConn),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverClient) Run() {
|
||||
if !c.DisableUDP {
|
||||
go func() {
|
||||
for {
|
||||
msg, err := c.CS.ReceiveMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
c.handleMessage(msg)
|
||||
}
|
||||
}()
|
||||
}
|
||||
for {
|
||||
stream, err := c.CS.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
go c.handleStream(stream)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverClient) handleStream(stream quic.Stream) {
|
||||
defer stream.Close()
|
||||
// Read request
|
||||
var req clientRequest
|
||||
err := struc.Unpack(stream, &req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !req.UDP {
|
||||
// TCP connection
|
||||
c.handleTCP(stream, req.Address)
|
||||
} else if !c.DisableUDP {
|
||||
// UDP connection
|
||||
c.handleUDP(stream)
|
||||
} else {
|
||||
// UDP disabled
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "UDP disabled",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverClient) handleMessage(msg []byte) {
|
||||
var udpMsg udpMessage
|
||||
err := struc.Unpack(bytes.NewBuffer(msg), &udpMsg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.udpSessionMutex.RLock()
|
||||
conn, ok := c.udpSessionMap[udpMsg.SessionID]
|
||||
c.udpSessionMutex.RUnlock()
|
||||
if ok {
|
||||
// Session found, send the message
|
||||
host, port, err := net.SplitHostPort(udpMsg.Address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
action, arg := acl.ActionDirect, ""
|
||||
if c.ACLEngine != nil {
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
// IP request, clear host for ACL engine
|
||||
host = ""
|
||||
}
|
||||
action, arg = c.ACLEngine.Lookup(host, ip)
|
||||
}
|
||||
switch action {
|
||||
case acl.ActionDirect, acl.ActionProxy: // Treat proxy as direct on server side
|
||||
addr, err := net.ResolveUDPAddr("udp", udpMsg.Address)
|
||||
if err == nil {
|
||||
_, _ = conn.WriteToUDP(udpMsg.Data, addr)
|
||||
}
|
||||
case acl.ActionBlock:
|
||||
// Do nothing
|
||||
case acl.ActionHijack:
|
||||
hijackAddr := net.JoinHostPort(arg, port)
|
||||
addr, err := net.ResolveUDPAddr("udp", hijackAddr)
|
||||
if err == nil {
|
||||
_, _ = conn.WriteToUDP(udpMsg.Data, addr)
|
||||
}
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverClient) handleTCP(stream quic.Stream, reqAddr string) {
|
||||
host, port, err := net.SplitHostPort(reqAddr)
|
||||
if err != nil {
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "invalid address",
|
||||
})
|
||||
c.CTCPErrorFunc(c.ClientAddr, c.Auth, reqAddr, err)
|
||||
return
|
||||
}
|
||||
action, arg := acl.ActionDirect, ""
|
||||
if c.ACLEngine != nil {
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
// IP request, clear host for ACL engine
|
||||
host = ""
|
||||
}
|
||||
action, arg = c.ACLEngine.Lookup(host, ip)
|
||||
}
|
||||
c.CTCPRequestFunc(c.ClientAddr, c.Auth, reqAddr, action, arg)
|
||||
|
||||
var conn net.Conn // Connection to be piped
|
||||
switch action {
|
||||
case acl.ActionDirect, acl.ActionProxy: // Treat proxy as direct on server side
|
||||
conn, err = net.DialTimeout("tcp", reqAddr, dialTimeout)
|
||||
if err != nil {
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
c.CTCPErrorFunc(c.ClientAddr, c.Auth, reqAddr, err)
|
||||
return
|
||||
}
|
||||
case acl.ActionBlock:
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "blocked by ACL",
|
||||
})
|
||||
return
|
||||
case acl.ActionHijack:
|
||||
hijackAddr := net.JoinHostPort(arg, port)
|
||||
conn, err = net.DialTimeout("tcp", hijackAddr, dialTimeout)
|
||||
if err != nil {
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
c.CTCPErrorFunc(c.ClientAddr, c.Auth, reqAddr, err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "ACL error",
|
||||
})
|
||||
return
|
||||
}
|
||||
// So far so good if we reach here
|
||||
defer conn.Close()
|
||||
err = struc.Pack(stream, &serverResponse{
|
||||
OK: true,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = utils.Pipe2Way(stream, conn)
|
||||
c.CTCPErrorFunc(c.ClientAddr, c.Auth, reqAddr, err)
|
||||
}
|
||||
|
||||
func (c *serverClient) handleUDP(stream quic.Stream) {
|
||||
// Like in SOCKS5, the stream here is only used to maintain the UDP session. No need to read anything from it
|
||||
conn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "UDP initialization failed",
|
||||
})
|
||||
c.CUDPErrorFunc(c.ClientAddr, c.Auth, 0, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var id uint32
|
||||
c.udpSessionMutex.Lock()
|
||||
id = c.nextUDPSessionID
|
||||
c.udpSessionMap[id] = conn
|
||||
c.nextUDPSessionID += 1
|
||||
c.udpSessionMutex.Unlock()
|
||||
|
||||
err = struc.Pack(stream, &serverResponse{
|
||||
OK: true,
|
||||
UDPSessionID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.CUDPRequestFunc(c.ClientAddr, c.Auth, id)
|
||||
|
||||
// Receive UDP packets, send them to the client
|
||||
go func() {
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
n, rAddr, err := conn.ReadFromUDP(buf)
|
||||
if n > 0 {
|
||||
var msgBuf bytes.Buffer
|
||||
_ = struc.Pack(&msgBuf, &udpMessage{
|
||||
SessionID: id,
|
||||
Address: rAddr.String(),
|
||||
Data: buf[:n],
|
||||
})
|
||||
_ = c.CS.SendMessage(msgBuf.Bytes())
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Hold the stream until it's closed by the client
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
_, err = stream.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
c.CUDPErrorFunc(c.ClientAddr, c.Auth, id, err)
|
||||
|
||||
// Remove the session
|
||||
c.udpSessionMutex.Lock()
|
||||
delete(c.udpSessionMap, id)
|
||||
c.udpSessionMutex.Unlock()
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"time"
|
||||
)
|
||||
|
||||
const controlStreamTimeout = 10 * time.Second
|
||||
|
||||
var controlProtocolEndian = binary.BigEndian
|
||||
|
||||
type CongestionFactory func(refBPS uint64) congestion.ExternalSendAlgorithm
|
@@ -27,13 +27,13 @@ func NewProxyHTTPServer(hyClient *core.Client, idleTimeout time.Duration, aclEng
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
host = ""
|
||||
}
|
||||
// ACL
|
||||
action, arg := acl.ActionProxy, ""
|
||||
if aclEngine != nil {
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
host = ""
|
||||
}
|
||||
action, arg = aclEngine.Lookup(host, ip)
|
||||
}
|
||||
newDialFunc(addr, action, arg)
|
||||
@@ -42,9 +42,9 @@ func NewProxyHTTPServer(hyClient *core.Client, idleTimeout time.Duration, aclEng
|
||||
case acl.ActionDirect:
|
||||
return net.Dial(network, addr)
|
||||
case acl.ActionProxy:
|
||||
return hyClient.Dial(false, addr)
|
||||
return hyClient.DialTCP(addr)
|
||||
case acl.ActionBlock:
|
||||
return nil, errors.New("blocked in ACL")
|
||||
return nil, errors.New("blocked by ACL")
|
||||
case acl.ActionHijack:
|
||||
return net.Dial(network, net.JoinHostPort(arg, port))
|
||||
default:
|
||||
@@ -52,8 +52,7 @@ func NewProxyHTTPServer(hyClient *core.Client, idleTimeout time.Duration, aclEng
|
||||
}
|
||||
},
|
||||
IdleConnTimeout: idleTimeout,
|
||||
// TODO: Disable HTTP2 support? ref: https://github.com/elazarl/goproxy/issues/361
|
||||
//TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),
|
||||
// Disable HTTP2 support? ref: https://github.com/elazarl/goproxy/issues/361
|
||||
}
|
||||
proxy.ConnectDial = nil
|
||||
if basicAuthFunc != nil {
|
||||
|
64
pkg/relay/relay.go
Normal file
64
pkg/relay/relay.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/tobyxdd/hysteria/pkg/utils"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
HyClient *core.Client
|
||||
ListenAddr *net.TCPAddr
|
||||
Remote string
|
||||
Timeout time.Duration
|
||||
|
||||
ConnFunc func(addr net.Addr)
|
||||
ErrorFunc func(addr net.Addr, err error)
|
||||
|
||||
tcpListener *net.TCPListener
|
||||
}
|
||||
|
||||
func NewRelay(hyClient *core.Client, listen, remote string, timeout time.Duration,
|
||||
connFunc func(addr net.Addr), errorFunc func(addr net.Addr, err error)) (*Relay, error) {
|
||||
tAddr, err := net.ResolveTCPAddr("tcp", listen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &Relay{
|
||||
HyClient: hyClient,
|
||||
ListenAddr: tAddr,
|
||||
Remote: remote,
|
||||
Timeout: timeout,
|
||||
ConnFunc: connFunc,
|
||||
ErrorFunc: errorFunc,
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Relay) ListenAndServe() error {
|
||||
var err error
|
||||
r.tcpListener, err = net.ListenTCP("tcp", r.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.tcpListener.Close()
|
||||
for {
|
||||
c, err := r.tcpListener.AcceptTCP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func(c *net.TCPConn) {
|
||||
defer c.Close()
|
||||
r.ConnFunc(c.RemoteAddr())
|
||||
rc, err := r.HyClient.DialTCP(r.Remote)
|
||||
if err != nil {
|
||||
r.ErrorFunc(c.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
err = utils.PipePairWithTimeout(c, rc, r.Timeout)
|
||||
r.ErrorFunc(c.RemoteAddr(), err)
|
||||
}(c)
|
||||
}
|
||||
}
|
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/tobyxdd/hysteria/pkg/utils"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -17,40 +16,36 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const udpBufferSize = 65535
|
||||
|
||||
var (
|
||||
ErrUnsupportedCmd = errors.New("unsupported command")
|
||||
ErrUserPassAuth = errors.New("invalid username or password")
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
HyClient *core.Client
|
||||
AuthFunc func(username, password string) bool
|
||||
Method byte
|
||||
TCPAddr *net.TCPAddr
|
||||
TCPDeadline int
|
||||
ACLEngine *acl.Engine
|
||||
DisableUDP bool
|
||||
HyClient *core.Client
|
||||
AuthFunc func(username, password string) bool
|
||||
Method byte
|
||||
TCPAddr *net.TCPAddr
|
||||
TCPTimeout time.Duration
|
||||
ACLEngine *acl.Engine
|
||||
DisableUDP bool
|
||||
|
||||
NewRequestFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string)
|
||||
RequestClosedFunc func(addr net.Addr, reqAddr string, err error)
|
||||
NewUDPAssociateFunc func(addr net.Addr)
|
||||
UDPAssociateClosedFunc func(addr net.Addr, err error)
|
||||
NewUDPTunnelFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string)
|
||||
UDPTunnelClosedFunc func(addr net.Addr, reqAddr string, err error)
|
||||
TCPRequestFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string)
|
||||
TCPErrorFunc func(addr net.Addr, reqAddr string, err error)
|
||||
UDPAssociateFunc func(addr net.Addr)
|
||||
UDPErrorFunc func(addr net.Addr, err error)
|
||||
|
||||
tcpListener *net.TCPListener
|
||||
}
|
||||
|
||||
func NewServer(hyClient *core.Client, addr string, authFunc func(username, password string) bool, tcpDeadline int,
|
||||
func NewServer(hyClient *core.Client, addr string, authFunc func(username, password string) bool, tcpTimeout time.Duration,
|
||||
aclEngine *acl.Engine, disableUDP bool,
|
||||
newReqFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string),
|
||||
reqClosedFunc func(addr net.Addr, reqAddr string, err error),
|
||||
newUDPAssociateFunc func(addr net.Addr),
|
||||
udpAssociateClosedFunc func(addr net.Addr, err error),
|
||||
newUDPTunnelFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string),
|
||||
udpTunnelClosedFunc func(addr net.Addr, reqAddr string, err error)) (*Server, error) {
|
||||
|
||||
taddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
tcpReqFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string),
|
||||
tcpErrorFunc func(addr net.Addr, reqAddr string, err error),
|
||||
udpAssocFunc func(addr net.Addr), udpErrorFunc func(addr net.Addr, err error)) (*Server, error) {
|
||||
tAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -59,19 +54,17 @@ func NewServer(hyClient *core.Client, addr string, authFunc func(username, passw
|
||||
m = socks5.MethodUsernamePassword
|
||||
}
|
||||
s := &Server{
|
||||
HyClient: hyClient,
|
||||
AuthFunc: authFunc,
|
||||
Method: m,
|
||||
TCPAddr: taddr,
|
||||
TCPDeadline: tcpDeadline,
|
||||
ACLEngine: aclEngine,
|
||||
DisableUDP: disableUDP,
|
||||
NewRequestFunc: newReqFunc,
|
||||
RequestClosedFunc: reqClosedFunc,
|
||||
NewUDPAssociateFunc: newUDPAssociateFunc,
|
||||
UDPAssociateClosedFunc: udpAssociateClosedFunc,
|
||||
NewUDPTunnelFunc: newUDPTunnelFunc,
|
||||
UDPTunnelClosedFunc: udpTunnelClosedFunc,
|
||||
HyClient: hyClient,
|
||||
AuthFunc: authFunc,
|
||||
Method: m,
|
||||
TCPAddr: tAddr,
|
||||
TCPTimeout: tcpTimeout,
|
||||
ACLEngine: aclEngine,
|
||||
DisableUDP: disableUDP,
|
||||
TCPRequestFunc: tcpReqFunc,
|
||||
TCPErrorFunc: tcpErrorFunc,
|
||||
UDPAssociateFunc: udpAssocFunc,
|
||||
UDPErrorFunc: udpErrorFunc,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -133,8 +126,8 @@ func (s *Server) ListenAndServe() error {
|
||||
}
|
||||
go func(c *net.TCPConn) {
|
||||
defer c.Close()
|
||||
if s.TCPDeadline != 0 {
|
||||
if err := c.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second)); err != nil {
|
||||
if s.TCPTimeout != 0 {
|
||||
if err := c.SetDeadline(time.Now().Add(s.TCPTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -174,10 +167,10 @@ func (s *Server) handleTCP(c *net.TCPConn, r *socks5.Request) error {
|
||||
if s.ACLEngine != nil {
|
||||
action, arg = s.ACLEngine.Lookup(domain, ip)
|
||||
}
|
||||
s.NewRequestFunc(c.RemoteAddr(), addr, action, arg)
|
||||
s.TCPRequestFunc(c.RemoteAddr(), addr, action, arg)
|
||||
var closeErr error
|
||||
defer func() {
|
||||
s.RequestClosedFunc(c.RemoteAddr(), addr, closeErr)
|
||||
s.TCPErrorFunc(c.RemoteAddr(), addr, closeErr)
|
||||
}()
|
||||
// Handle according to the action
|
||||
switch action {
|
||||
@@ -190,10 +183,10 @@ func (s *Server) handleTCP(c *net.TCPConn, r *socks5.Request) error {
|
||||
}
|
||||
defer rc.Close()
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = pipePair(c, rc, s.TCPDeadline)
|
||||
closeErr = utils.PipePairWithTimeout(c, rc, s.TCPTimeout)
|
||||
return nil
|
||||
case acl.ActionProxy:
|
||||
rc, err := s.HyClient.Dial(false, addr)
|
||||
rc, err := s.HyClient.DialTCP(addr)
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
@@ -201,7 +194,7 @@ func (s *Server) handleTCP(c *net.TCPConn, r *socks5.Request) error {
|
||||
}
|
||||
defer rc.Close()
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = pipePair(c, rc, s.TCPDeadline)
|
||||
closeErr = utils.PipePairWithTimeout(c, rc, s.TCPTimeout)
|
||||
return nil
|
||||
case acl.ActionBlock:
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
@@ -216,7 +209,7 @@ func (s *Server) handleTCP(c *net.TCPConn, r *socks5.Request) error {
|
||||
}
|
||||
defer rc.Close()
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = pipePair(c, rc, s.TCPDeadline)
|
||||
closeErr = utils.PipePairWithTimeout(c, rc, s.TCPTimeout)
|
||||
return nil
|
||||
default:
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
@@ -226,11 +219,12 @@ func (s *Server) handleTCP(c *net.TCPConn, r *socks5.Request) error {
|
||||
}
|
||||
|
||||
func (s *Server) handleUDP(c *net.TCPConn, r *socks5.Request) error {
|
||||
s.NewUDPAssociateFunc(c.RemoteAddr())
|
||||
s.UDPAssociateFunc(c.RemoteAddr())
|
||||
var closeErr error
|
||||
defer func() {
|
||||
s.UDPAssociateClosedFunc(c.RemoteAddr(), closeErr)
|
||||
s.UDPErrorFunc(c.RemoteAddr(), closeErr)
|
||||
}()
|
||||
// Start local UDP server
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||
IP: s.TCPAddr.IP,
|
||||
Zone: s.TCPAddr.Zone,
|
||||
@@ -241,6 +235,25 @@ func (s *Server) handleUDP(c *net.TCPConn, r *socks5.Request) error {
|
||||
return err
|
||||
}
|
||||
defer udpConn.Close()
|
||||
// Local UDP relay conn for ACL Direct
|
||||
var localRelayConn *net.UDPConn
|
||||
if s.ACLEngine != nil {
|
||||
localRelayConn, err = net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer localRelayConn.Close()
|
||||
}
|
||||
// HyClient UDP session
|
||||
hyUDP, err := s.HyClient.DialUDP()
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer hyUDP.Close()
|
||||
// Send UDP server addr to the client
|
||||
atyp, addr, port, err := socks5.ParseAddress(udpConn.LocalAddr().String())
|
||||
if err != nil {
|
||||
@@ -250,11 +263,11 @@ func (s *Server) handleUDP(c *net.TCPConn, r *socks5.Request) error {
|
||||
}
|
||||
_, _ = socks5.NewReply(socks5.RepSuccess, atyp, addr, port).WriteTo(c)
|
||||
// Let UDP server do its job, we hold the TCP connection here
|
||||
go s.udpServer(udpConn)
|
||||
go s.udpServer(udpConn, localRelayConn, hyUDP)
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
if s.TCPDeadline != 0 {
|
||||
_ = c.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second))
|
||||
if s.TCPTimeout != 0 {
|
||||
_ = c.SetDeadline(time.Now().Add(s.TCPTimeout))
|
||||
}
|
||||
_, err := c.Read(buf)
|
||||
if err != nil {
|
||||
@@ -262,20 +275,17 @@ func (s *Server) handleUDP(c *net.TCPConn, r *socks5.Request) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
// As the TCP connection closes, so does the UDP listener
|
||||
// As the TCP connection closes, so does the UDP server & HyClient session
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) udpServer(c *net.UDPConn) {
|
||||
func (s *Server) udpServer(clientConn *net.UDPConn, localRelayConn *net.UDPConn, hyUDP core.UDPConn) {
|
||||
var clientAddr *net.UDPAddr
|
||||
remoteMap := make(map[string]io.ReadWriteCloser) // Remote addr <-> Remote conn
|
||||
buf := make([]byte, utils.PipeBufferSize)
|
||||
var closeErr error
|
||||
|
||||
buf := make([]byte, udpBufferSize)
|
||||
// Local to remote
|
||||
for {
|
||||
n, caddr, err := c.ReadFromUDP(buf)
|
||||
n, cAddr, err := clientConn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
closeErr = err
|
||||
break
|
||||
}
|
||||
d, err := socks5.NewDatagramFromBytes(buf[:n])
|
||||
@@ -284,71 +294,67 @@ func (s *Server) udpServer(c *net.UDPConn) {
|
||||
continue
|
||||
}
|
||||
if clientAddr == nil {
|
||||
// Whoever sends the first valid packet is our client :P
|
||||
clientAddr = caddr
|
||||
} else if caddr.String() != clientAddr.String() {
|
||||
// We already have a client and you're not it!
|
||||
// Whoever sends the first valid packet is our client
|
||||
clientAddr = cAddr
|
||||
// Start remote to local
|
||||
go func() {
|
||||
for {
|
||||
bs, _, err := hyUDP.ReadFrom()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// RFC 1928 is very ambiguous on how to properly use DST.ADDR and DST.PORT in reply packets
|
||||
// So we just fill in zeros for now. Works fine for all the SOCKS5 clients I tested
|
||||
d := socks5.NewDatagram(socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}, bs)
|
||||
_, _ = clientConn.WriteToUDP(d.Bytes(), clientAddr)
|
||||
}
|
||||
}()
|
||||
if localRelayConn != nil {
|
||||
go func() {
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
n, _, err := localRelayConn.ReadFrom(buf)
|
||||
if n > 0 {
|
||||
d := socks5.NewDatagram(socks5.ATYPIPv4,
|
||||
[]byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}, buf[:n])
|
||||
_, _ = clientConn.WriteToUDP(d.Bytes(), clientAddr)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
} else if cAddr.String() != clientAddr.String() {
|
||||
// Not our client, bye
|
||||
continue
|
||||
}
|
||||
domain, ip, port, addr := parseDatagramRequestAddress(d)
|
||||
rc := remoteMap[addr]
|
||||
if rc == nil {
|
||||
// Need a new entry
|
||||
action, arg := acl.ActionProxy, ""
|
||||
if s.ACLEngine != nil {
|
||||
action, arg = s.ACLEngine.Lookup(domain, ip)
|
||||
}
|
||||
s.NewUDPTunnelFunc(clientAddr, addr, action, arg)
|
||||
// Handle according to the action
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
rc, err = net.Dial("udp", addr)
|
||||
if err != nil {
|
||||
s.UDPTunnelClosedFunc(clientAddr, addr, err)
|
||||
continue
|
||||
}
|
||||
// The other direction
|
||||
go udpReversePipe(clientAddr, c, rc)
|
||||
remoteMap[addr] = rc
|
||||
case acl.ActionProxy:
|
||||
rc, err = s.HyClient.Dial(true, addr)
|
||||
if err != nil {
|
||||
s.UDPTunnelClosedFunc(clientAddr, addr, err)
|
||||
continue
|
||||
}
|
||||
// The other direction
|
||||
go udpReversePipe(clientAddr, c, rc)
|
||||
remoteMap[addr] = rc
|
||||
case acl.ActionBlock:
|
||||
s.UDPTunnelClosedFunc(clientAddr, addr, errors.New("blocked in ACL"))
|
||||
continue
|
||||
case acl.ActionHijack:
|
||||
rc, err = net.Dial("udp", net.JoinHostPort(arg, port))
|
||||
if err != nil {
|
||||
s.UDPTunnelClosedFunc(clientAddr, addr, err)
|
||||
continue
|
||||
}
|
||||
// The other direction
|
||||
go udpReversePipe(clientAddr, c, rc)
|
||||
remoteMap[addr] = rc
|
||||
default:
|
||||
s.UDPTunnelClosedFunc(clientAddr, addr, fmt.Errorf("unknown action %d", action))
|
||||
continue
|
||||
}
|
||||
action, arg := acl.ActionProxy, ""
|
||||
if s.ACLEngine != nil && localRelayConn != nil {
|
||||
action, arg = s.ACLEngine.Lookup(domain, ip)
|
||||
}
|
||||
_, err = rc.Write(d.Data)
|
||||
if err != nil {
|
||||
// The connection is no longer valid, close & remove from map
|
||||
_ = rc.Close()
|
||||
delete(remoteMap, addr)
|
||||
s.UDPTunnelClosedFunc(clientAddr, addr, err)
|
||||
// Handle according to the action
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
rAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err == nil {
|
||||
_, _ = localRelayConn.WriteToUDP(d.Data, rAddr)
|
||||
}
|
||||
case acl.ActionProxy:
|
||||
_ = hyUDP.WriteTo(d.Data, addr)
|
||||
case acl.ActionBlock:
|
||||
// Do nothing
|
||||
case acl.ActionHijack:
|
||||
hijackAddr := net.JoinHostPort(arg, port)
|
||||
rAddr, err := net.ResolveUDPAddr("udp", hijackAddr)
|
||||
if err == nil {
|
||||
_, _ = localRelayConn.WriteToUDP(d.Data, rAddr)
|
||||
}
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
// Close all remote connections
|
||||
for raddr, rc := range remoteMap {
|
||||
_ = rc.Close()
|
||||
s.UDPTunnelClosedFunc(clientAddr, raddr, closeErr)
|
||||
}
|
||||
}
|
||||
|
||||
func sendReply(conn *net.TCPConn, rep byte) error {
|
||||
@@ -376,48 +382,3 @@ func parseDatagramRequestAddress(r *socks5.Datagram) (domain string, ip net.IP,
|
||||
return "", r.DstAddr, p, net.JoinHostPort(net.IP(r.DstAddr).String(), p)
|
||||
}
|
||||
}
|
||||
|
||||
func pipePair(conn *net.TCPConn, stream io.ReadWriteCloser, deadline int) error {
|
||||
errChan := make(chan error, 2)
|
||||
// TCP to stream
|
||||
go func() {
|
||||
buf := make([]byte, utils.PipeBufferSize)
|
||||
for {
|
||||
if deadline != 0 {
|
||||
_ = conn.SetDeadline(time.Now().Add(time.Duration(deadline) * time.Second))
|
||||
}
|
||||
rn, err := conn.Read(buf)
|
||||
if rn > 0 {
|
||||
_, err := stream.Write(buf[:rn])
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Stream to TCP
|
||||
go func() {
|
||||
errChan <- utils.Pipe(stream, conn, nil)
|
||||
}()
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
func udpReversePipe(clientAddr *net.UDPAddr, c *net.UDPConn, rc io.ReadWriteCloser) {
|
||||
buf := make([]byte, utils.PipeBufferSize)
|
||||
for {
|
||||
n, err := rc.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
d := socks5.NewDatagram(socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}, buf[:n])
|
||||
_, err = c.WriteTo(d.Bytes(), clientAddr)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,96 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PacketWrapperConn struct {
|
||||
Orig net.Conn
|
||||
}
|
||||
|
||||
func (w *PacketWrapperConn) Read(b []byte) (n int, err error) {
|
||||
var sz uint32
|
||||
if err := binary.Read(w.Orig, binary.BigEndian, &sz); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if int(sz) <= len(b) {
|
||||
return io.ReadFull(w.Orig, b[:sz])
|
||||
} else {
|
||||
return 0, fmt.Errorf("the buffer is too small to hold %d bytes of packet data", sz)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *PacketWrapperConn) Write(b []byte) (n int, err error) {
|
||||
sz := uint32(len(b))
|
||||
if err := binary.Write(w.Orig, binary.BigEndian, &sz); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return w.Orig.Write(b)
|
||||
}
|
||||
|
||||
func (w *PacketWrapperConn) Close() error {
|
||||
return w.Orig.Close()
|
||||
}
|
||||
|
||||
func (w *PacketWrapperConn) LocalAddr() net.Addr {
|
||||
return w.Orig.LocalAddr()
|
||||
}
|
||||
|
||||
func (w *PacketWrapperConn) RemoteAddr() net.Addr {
|
||||
return w.Orig.RemoteAddr()
|
||||
}
|
||||
|
||||
func (w *PacketWrapperConn) SetDeadline(t time.Time) error {
|
||||
return w.Orig.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (w *PacketWrapperConn) SetReadDeadline(t time.Time) error {
|
||||
return w.Orig.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (w *PacketWrapperConn) SetWriteDeadline(t time.Time) error {
|
||||
return w.Orig.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type QUICStreamWrapperConn struct {
|
||||
Orig quic.Stream
|
||||
PseudoLocalAddr net.Addr
|
||||
PseudoRemoteAddr net.Addr
|
||||
}
|
||||
|
||||
func (w *QUICStreamWrapperConn) Read(b []byte) (n int, err error) {
|
||||
return w.Orig.Read(b)
|
||||
}
|
||||
|
||||
func (w *QUICStreamWrapperConn) Write(b []byte) (n int, err error) {
|
||||
return w.Orig.Write(b)
|
||||
}
|
||||
|
||||
func (w *QUICStreamWrapperConn) Close() error {
|
||||
return w.Orig.Close()
|
||||
}
|
||||
|
||||
func (w *QUICStreamWrapperConn) LocalAddr() net.Addr {
|
||||
return w.PseudoLocalAddr
|
||||
}
|
||||
|
||||
func (w *QUICStreamWrapperConn) RemoteAddr() net.Addr {
|
||||
return w.PseudoRemoteAddr
|
||||
}
|
||||
|
||||
func (w *QUICStreamWrapperConn) SetDeadline(t time.Time) error {
|
||||
return w.Orig.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (w *QUICStreamWrapperConn) SetReadDeadline(t time.Time) error {
|
||||
return w.Orig.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (w *QUICStreamWrapperConn) SetWriteDeadline(t time.Time) error {
|
||||
return w.Orig.SetWriteDeadline(t)
|
||||
}
|
@@ -2,20 +2,18 @@ package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const PipeBufferSize = 65536
|
||||
|
||||
func Pipe(src, dst io.ReadWriter, atomicCounter *uint64) error {
|
||||
func Pipe(src, dst io.ReadWriter) error {
|
||||
buf := make([]byte, PipeBufferSize)
|
||||
for {
|
||||
rn, err := src.Read(buf)
|
||||
if rn > 0 {
|
||||
wn, err := dst.Write(buf[:rn])
|
||||
if atomicCounter != nil {
|
||||
atomic.AddUint64(atomicCounter, uint64(wn))
|
||||
}
|
||||
_, err := dst.Write(buf[:rn])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -26,14 +24,61 @@ func Pipe(src, dst io.ReadWriter, atomicCounter *uint64) error {
|
||||
}
|
||||
}
|
||||
|
||||
func PipePair(rw1, rw2 io.ReadWriter, rw1WriteCounter, rw2WriteCounter *uint64) error {
|
||||
func Pipe2Way(rw1, rw2 io.ReadWriter) error {
|
||||
errChan := make(chan error, 2)
|
||||
go func() {
|
||||
errChan <- Pipe(rw2, rw1, rw1WriteCounter)
|
||||
errChan <- Pipe(rw2, rw1)
|
||||
}()
|
||||
go func() {
|
||||
errChan <- Pipe(rw1, rw2, rw2WriteCounter)
|
||||
errChan <- Pipe(rw1, rw2)
|
||||
}()
|
||||
// We only need the first error
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
func PipePairWithTimeout(conn *net.TCPConn, stream io.ReadWriteCloser, timeout time.Duration) error {
|
||||
errChan := make(chan error, 2)
|
||||
// TCP to stream
|
||||
go func() {
|
||||
buf := make([]byte, PipeBufferSize)
|
||||
for {
|
||||
if timeout != 0 {
|
||||
_ = conn.SetDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
rn, err := conn.Read(buf)
|
||||
if rn > 0 {
|
||||
_, err := stream.Write(buf[:rn])
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Stream to TCP
|
||||
go func() {
|
||||
buf := make([]byte, PipeBufferSize)
|
||||
for {
|
||||
rn, err := stream.Read(buf)
|
||||
if rn > 0 {
|
||||
_, err := conn.Write(buf[:rn])
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
if timeout != 0 {
|
||||
_ = conn.SetDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return <-errChan
|
||||
}
|
||||
|
Reference in New Issue
Block a user