mirror of https://github.com/ginuerzh/gost
24 changed files with 1159 additions and 406 deletions
@ -1,5 +1,4 @@ |
|||||
GO Simple Tunnel |
# GO Simple Tunnel |
||||
====== |
|
||||
|
|
||||
### GO语言实现的安全隧道 |
### GO语言实现的安全隧道 |
||||
|
|
||||
@ -9,30 +8,29 @@ GO Simple Tunnel |
|||||
[](https://github.com/ginuerzh/gost/releases/latest) |
[](https://github.com/ginuerzh/gost/releases/latest) |
||||
[](https://hub.docker.com/r/ginuerzh/gost/) |
[](https://hub.docker.com/r/ginuerzh/gost/) |
||||
[](https://snapcraft.io/gost) |
[](https://snapcraft.io/gost) |
||||
|
|
||||
[English README](README_en.md) |
[English README](README_en.md) |
||||
|
|
||||
### !!!!! |
### !!!!! |
||||
|
|
||||
特性 |
## 特性 |
||||
------ |
|
||||
|
- 多端口监听 |
||||
* 多端口监听 |
- 可设置转发代理,支持多级转发(代理链) |
||||
* 可设置转发代理,支持多级转发(代理链) |
- 支持标准HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5代理协议 |
||||
* 支持标准HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5代理协议 |
- Web代理支持[探测防御](https://v2.gost.run/probe_resist/) |
||||
* Web代理支持[探测防御](https://v2.gost.run/probe_resist/) |
- [支持多种隧道类型](https://v2.gost.run/configuration/) |
||||
* [支持多种隧道类型](https://v2.gost.run/configuration/) |
- [SOCKS5代理支持TLS协商加密](https://v2.gost.run/socks/) |
||||
* [SOCKS5代理支持TLS协商加密](https://v2.gost.run/socks/) |
- [Tunnel UDP over TCP](https://v2.gost.run/socks/) |
||||
* [Tunnel UDP over TCP](https://v2.gost.run/socks/) |
- [TCP/UDP透明代理](https://v2.gost.run/redirect/) |
||||
* [TCP/UDP透明代理](https://v2.gost.run/redirect/) |
- [本地/远程TCP/UDP端口转发](https://v2.gost.run/port-forwarding/) |
||||
* [本地/远程TCP/UDP端口转发](https://v2.gost.run/port-forwarding/) |
- [支持Shadowsocks(TCP/UDP)协议](https://v2.gost.run/ss/) |
||||
* [支持Shadowsocks(TCP/UDP)协议](https://v2.gost.run/ss/) |
- [支持SNI代理](https://v2.gost.run/sni/) |
||||
* [支持SNI代理](https://v2.gost.run/sni/) |
- [权限控制](https://v2.gost.run/permission/) |
||||
* [权限控制](https://v2.gost.run/permission/) |
- [负载均衡](https://v2.gost.run/load-balancing/) |
||||
* [负载均衡](https://v2.gost.run/load-balancing/) |
- [路由控制](https://v2.gost.run/bypass/) |
||||
* [路由控制](https://v2.gost.run/bypass/) |
- DNS[解析](https://v2.gost.run/resolver/)和[代理](https://v2.gost.run/dns/) |
||||
* DNS[解析](https://v2.gost.run/resolver/)和[代理](https://v2.gost.run/dns/) |
- [TUN/TAP设备](https://v2.gost.run/tuntap/) |
||||
* [TUN/TAP设备](https://v2.gost.run/tuntap/) |
|
||||
|
|
||||
Wiki站点: [v2.gost.run](https://v2.gost.run) |
Wiki站点: [v2.gost.run](https://v2.gost.run) |
||||
|
|
||||
@ -40,14 +38,13 @@ Telegram讨论群: <https://t.me/gogost> |
|||||
|
|
||||
Google讨论组: <https://groups.google.com/d/forum/go-gost> |
Google讨论组: <https://groups.google.com/d/forum/go-gost> |
||||
|
|
||||
安装 |
## 安装 |
||||
------ |
|
||||
|
|
||||
#### 二进制文件 |
### 二进制文件 |
||||
|
|
||||
<https://github.com/ginuerzh/gost/releases> |
<https://github.com/ginuerzh/gost/releases> |
||||
|
|
||||
#### 源码编译 |
### 源码编译 |
||||
|
|
||||
```bash |
```bash |
||||
git clone https://github.com/ginuerzh/gost.git |
git clone https://github.com/ginuerzh/gost.git |
||||
@ -55,52 +52,50 @@ cd gost/cmd/gost |
|||||
go build |
go build |
||||
``` |
``` |
||||
|
|
||||
#### Docker |
### Docker |
||||
|
|
||||
```bash |
```bash |
||||
docker run --rm ginuerzh/gost -V |
docker run --rm ginuerzh/gost -V |
||||
``` |
``` |
||||
|
|
||||
#### Homebrew |
### Homebrew |
||||
|
|
||||
```bash |
```bash |
||||
brew install gost |
brew install gost |
||||
``` |
``` |
||||
|
|
||||
#### Ubuntu商店 |
### Ubuntu商店 |
||||
|
|
||||
|
|
||||
```bash |
```bash |
||||
sudo snap install core |
sudo snap install core |
||||
sudo snap install gost |
sudo snap install gost |
||||
``` |
``` |
||||
|
|
||||
快速上手 |
## 快速上手 |
||||
------ |
|
||||
|
|
||||
#### 不设置转发代理 |
### 不设置转发代理 |
||||
|
|
||||
<img src="https://ginuerzh.github.io/images/gost_01.png" /> |
<img src="https://ginuerzh.github.io/images/gost_01.png" /> |
||||
|
|
||||
* 作为标准HTTP/SOCKS5代理 |
- 作为标准HTTP/SOCKS5代理 |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 |
gost -L=:8080 |
||||
``` |
``` |
||||
|
|
||||
* 设置代理认证信息 |
- 设置代理认证信息 |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=admin:123456@localhost:8080 |
gost -L=admin:123456@localhost:8080 |
||||
``` |
``` |
||||
|
|
||||
* 多端口监听 |
- 多端口监听 |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=http2://:443 -L=socks5://:1080 -L=ss://aes-128-cfb:123456@:8338 |
gost -L=http2://:443 -L=socks5://:1080 -L=ss://aes-128-cfb:123456@:8338 |
||||
``` |
``` |
||||
|
|
||||
#### 设置转发代理 |
### 设置转发代理 |
||||
|
|
||||
<img src="https://ginuerzh.github.io/images/gost_02.png" /> |
<img src="https://ginuerzh.github.io/images/gost_02.png" /> |
||||
|
|
||||
@ -108,13 +103,13 @@ gost -L=http2://:443 -L=socks5://:1080 -L=ss://aes-128-cfb:123456@:8338 |
|||||
gost -L=:8080 -F=192.168.1.1:8081 |
gost -L=:8080 -F=192.168.1.1:8081 |
||||
``` |
``` |
||||
|
|
||||
* 转发代理认证 |
- 转发代理认证 |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F=http://admin:[email protected]:8081 |
gost -L=:8080 -F=http://admin:[email protected]:8081 |
||||
``` |
``` |
||||
|
|
||||
#### 设置多级转发代理(代理链) |
### 设置多级转发代理(代理链) |
||||
|
|
||||
<img src="https://ginuerzh.github.io/images/gost_03.png" /> |
<img src="https://ginuerzh.github.io/images/gost_03.png" /> |
||||
|
|
||||
@ -124,7 +119,7 @@ gost -L=:8080 -F=quic://192.168.1.1:6121 -F=socks5+wss://192.168.1.2:1080 -F=htt |
|||||
|
|
||||
gost按照-F设置的顺序通过代理链将请求最终转发给a.b.c.d:NNNN处理,每一个转发代理可以是任意HTTP/HTTPS/HTTP2/SOCKS4/SOCKS5/Shadowsocks类型代理。 |
gost按照-F设置的顺序通过代理链将请求最终转发给a.b.c.d:NNNN处理,每一个转发代理可以是任意HTTP/HTTPS/HTTP2/SOCKS4/SOCKS5/Shadowsocks类型代理。 |
||||
|
|
||||
#### 本地端口转发(TCP) |
### 本地端口转发(TCP) |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=tcp://:2222/192.168.1.1:22 [-F=...] |
gost -L=tcp://:2222/192.168.1.1:22 [-F=...] |
||||
@ -136,7 +131,7 @@ gost -L=tcp://:2222/192.168.1.1:22 [-F=...] |
|||||
gost -L=tcp://:2222/192.168.1.1:22 -F forward+ssh://:2222 |
gost -L=tcp://:2222/192.168.1.1:22 -F forward+ssh://:2222 |
||||
``` |
``` |
||||
|
|
||||
#### 本地端口转发(UDP) |
### 本地端口转发(UDP) |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=udp://:5353/192.168.1.1:53?ttl=60 [-F=...] |
gost -L=udp://:5353/192.168.1.1:53?ttl=60 [-F=...] |
||||
@ -147,146 +142,176 @@ gost -L=udp://:5353/192.168.1.1:53?ttl=60 [-F=...] |
|||||
|
|
||||
**注:** 转发UDP数据时,如果有代理链,则代理链的末端(最后一个-F参数)必须是gost SOCKS5类型代理,gost会使用UDP over TCP方式进行转发。 |
**注:** 转发UDP数据时,如果有代理链,则代理链的末端(最后一个-F参数)必须是gost SOCKS5类型代理,gost会使用UDP over TCP方式进行转发。 |
||||
|
|
||||
#### 远程端口转发(TCP) |
### 远程端口转发(TCP) |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=rtcp://:2222/192.168.1.1:22 [-F=... -F=socks5://172.24.10.1:1080] |
gost -L=rtcp://:2222/192.168.1.1:22 [-F=... -F=socks5://172.24.10.1:1080] |
||||
``` |
``` |
||||
|
|
||||
将172.24.10.1:2222上的数据(通过代理链)转发到192.168.1.1:22上。当代理链末端(最后一个-F参数)为SSH转发通道类型时,gost会直接使用SSH的远程端口转发功能: |
将172.24.10.1:2222上的数据(通过代理链)转发到192.168.1.1:22上。当代理链末端(最后一个-F参数)为SSH转发通道类型时,gost会直接使用SSH的远程端口转发功能: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=rtcp://:2222/192.168.1.1:22 -F forward+ssh://:2222 |
gost -L=rtcp://:2222/192.168.1.1:22 -F forward+ssh://:2222 |
||||
``` |
``` |
||||
|
|
||||
#### 远程端口转发(UDP) |
### 远程端口转发(UDP) |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=rudp://:5353/192.168.1.1:53?ttl=60 [-F=... -F=socks5://172.24.10.1:1080] |
gost -L=rudp://:5353/192.168.1.1:53?ttl=60 [-F=... -F=socks5://172.24.10.1:1080] |
||||
``` |
``` |
||||
|
|
||||
将172.24.10.1:5353上的数据(通过代理链)转发到192.168.1.1:53上。 |
将172.24.10.1:5353上的数据(通过代理链)转发到192.168.1.1:53上。 |
||||
每条转发通道都有超时时间,当超过此时间,且在此时间段内无任何数据交互,则此通道将关闭。可以通过`ttl`参数来设置超时时间,默认值为60秒。 |
每条转发通道都有超时时间,当超过此时间,且在此时间段内无任何数据交互,则此通道将关闭。可以通过`ttl`参数来设置超时时间,默认值为60秒。 |
||||
|
|
||||
**注:** 转发UDP数据时,如果有代理链,则代理链的末端(最后一个-F参数)必须是GOST SOCKS5类型代理,gost会使用UDP-over-TCP方式进行转发。 |
**注:** 转发UDP数据时,如果有代理链,则代理链的末端(最后一个-F参数)必须是GOST SOCKS5类型代理,gost会使用UDP-over-TCP方式进行转发。 |
||||
|
|
||||
#### HTTP2 |
### HTTP2 |
||||
|
|
||||
gost的HTTP2支持两种模式: |
gost的HTTP2支持两种模式: |
||||
* 作为标准的HTTP2代理,并向下兼容HTTPS代理。 |
|
||||
* 作为通道传输其他协议。 |
|
||||
|
|
||||
##### 代理模式 |
- 作为标准的HTTP2代理,并向下兼容HTTPS代理。 |
||||
|
- 作为通道传输其他协议。 |
||||
|
|
||||
|
#### 代理模式 |
||||
|
|
||||
服务端: |
服务端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=http2://:443 |
gost -L=http2://:443 |
||||
``` |
``` |
||||
|
|
||||
客户端: |
客户端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F=http2://server_ip:443 |
gost -L=:8080 -F=http2://server_ip:443 |
||||
``` |
``` |
||||
|
|
||||
##### 通道模式 |
#### 通道模式 |
||||
|
|
||||
服务端: |
服务端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=h2://:443 |
gost -L=h2://:443 |
||||
``` |
``` |
||||
|
|
||||
客户端: |
客户端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F=h2://server_ip:443 |
gost -L=:8080 -F=h2://server_ip:443 |
||||
``` |
``` |
||||
|
|
||||
#### QUIC |
### QUIC |
||||
gost对QUIC的支持是基于[quic-go](https://github.com/lucas-clemente/quic-go)库。 |
|
||||
|
gost对QUIC的支持是基于[quic-go](https://github.com/quic-go/quic-go)库。 |
||||
|
|
||||
服务端: |
服务端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=quic://:6121 |
gost -L=quic://:6121 |
||||
``` |
``` |
||||
|
|
||||
客户端: |
客户端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F=quic://server_ip:6121 |
gost -L=:8080 -F=quic://server_ip:6121 |
||||
``` |
``` |
||||
|
|
||||
**注:** QUIC模式只能作为代理链的第一个节点。 |
**注:** QUIC模式只能作为代理链的第一个节点。 |
||||
|
|
||||
#### KCP |
### KCP |
||||
|
|
||||
gost对KCP的支持是基于[kcp-go](https://github.com/xtaci/kcp-go)和[kcptun](https://github.com/xtaci/kcptun)库。 |
gost对KCP的支持是基于[kcp-go](https://github.com/xtaci/kcp-go)和[kcptun](https://github.com/xtaci/kcptun)库。 |
||||
|
|
||||
服务端: |
服务端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=kcp://:8388 |
gost -L=kcp://:8388 |
||||
``` |
``` |
||||
|
|
||||
客户端: |
客户端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F=kcp://server_ip:8388 |
gost -L=:8080 -F=kcp://server_ip:8388 |
||||
``` |
``` |
||||
|
|
||||
gost会自动加载当前工作目录中的kcp.json(如果存在)配置文件,或者可以手动通过参数指定配置文件路径: |
gost会自动加载当前工作目录中的kcp.json(如果存在)配置文件,或者可以手动通过参数指定配置文件路径: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=kcp://:8388?c=/path/to/conf/file |
gost -L=kcp://:8388?c=/path/to/conf/file |
||||
``` |
``` |
||||
|
|
||||
**注:** KCP模式只能作为代理链的第一个节点。 |
**注:** KCP模式只能作为代理链的第一个节点。 |
||||
|
|
||||
#### SSH |
### SSH |
||||
|
|
||||
gost的SSH支持两种模式: |
gost的SSH支持两种模式: |
||||
* 作为转发通道,配合本地/远程TCP端口转发使用。 |
|
||||
* 作为通道传输其他协议。 |
|
||||
|
|
||||
##### 转发模式 |
- 作为转发通道,配合本地/远程TCP端口转发使用。 |
||||
|
- 作为通道传输其他协议。 |
||||
|
|
||||
|
#### 转发模式 |
||||
|
|
||||
服务端: |
服务端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=forward+ssh://:2222 |
gost -L=forward+ssh://:2222 |
||||
``` |
``` |
||||
|
|
||||
客户端: |
客户端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=rtcp://:1222/:22 -F=forward+ssh://server_ip:2222 |
gost -L=rtcp://:1222/:22 -F=forward+ssh://server_ip:2222 |
||||
``` |
``` |
||||
|
|
||||
##### 通道模式 |
#### 通道模式 |
||||
|
|
||||
服务端: |
服务端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=ssh://:2222 |
gost -L=ssh://:2222 |
||||
``` |
``` |
||||
|
|
||||
客户端: |
客户端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F=ssh://server_ip:2222?ping=60 |
gost -L=:8080 -F=ssh://server_ip:2222?ping=60 |
||||
``` |
``` |
||||
|
|
||||
可以通过`ping`参数设置心跳包发送周期,单位为秒。默认不发送心跳包。 |
可以通过`ping`参数设置心跳包发送周期,单位为秒。默认不发送心跳包。 |
||||
|
|
||||
|
### 透明代理 |
||||
|
|
||||
#### 透明代理 |
|
||||
基于iptables的透明代理。 |
基于iptables的透明代理。 |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=redirect://:12345 -F=http2://server_ip:443 |
gost -L=redirect://:12345 -F=http2://server_ip:443 |
||||
``` |
``` |
||||
|
|
||||
#### obfs4 |
### obfs4 |
||||
|
|
||||
此功能由[@isofew](https://github.com/isofew)贡献。 |
此功能由[@isofew](https://github.com/isofew)贡献。 |
||||
|
|
||||
服务端: |
服务端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=obfs4://:443 |
gost -L=obfs4://:443 |
||||
``` |
``` |
||||
|
|
||||
当服务端运行后会在控制台打印出连接地址供客户端使用: |
当服务端运行后会在控制台打印出连接地址供客户端使用: |
||||
|
|
||||
``` |
``` |
||||
obfs4://:443/?cert=4UbQjIfjJEQHPOs8vs5sagrSXx1gfrDCGdVh2hpIPSKH0nklv1e4f29r7jb91VIrq4q5Jw&iat-mode=0 |
obfs4://:443/?cert=4UbQjIfjJEQHPOs8vs5sagrSXx1gfrDCGdVh2hpIPSKH0nklv1e4f29r7jb91VIrq4q5Jw&iat-mode=0 |
||||
``` |
``` |
||||
|
|
||||
客户端: |
客户端: |
||||
|
|
||||
``` |
``` |
||||
gost -L=:8888 -F='obfs4://server_ip:443?cert=4UbQjIfjJEQHPOs8vs5sagrSXx1gfrDCGdVh2hpIPSKH0nklv1e4f29r7jb91VIrq4q5Jw&iat-mode=0' |
gost -L=:8888 -F='obfs4://server_ip:443?cert=4UbQjIfjJEQHPOs8vs5sagrSXx1gfrDCGdVh2hpIPSKH0nklv1e4f29r7jb91VIrq4q5Jw&iat-mode=0' |
||||
``` |
``` |
||||
|
|
||||
加密机制 |
## 加密机制 |
||||
------ |
|
||||
|
|
||||
#### HTTP |
### HTTP |
||||
|
|
||||
对于HTTP可以使用TLS加密整个通讯过程,即HTTPS代理: |
对于HTTP可以使用TLS加密整个通讯过程,即HTTPS代理: |
||||
|
|
||||
@ -295,19 +320,20 @@ gost -L=:8888 -F='obfs4://server_ip:443?cert=4UbQjIfjJEQHPOs8vs5sagrSXx1gfrDCGdV |
|||||
```bash |
```bash |
||||
gost -L=https://:443 |
gost -L=https://:443 |
||||
``` |
``` |
||||
|
|
||||
客户端: |
客户端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F=http+tls://server_ip:443 |
gost -L=:8080 -F=http+tls://server_ip:443 |
||||
``` |
``` |
||||
|
|
||||
#### HTTP2 |
### HTTP2 |
||||
|
|
||||
gost的HTTP2代理模式仅支持使用TLS加密的HTTP2协议,不支持明文HTTP2传输。 |
gost的HTTP2代理模式仅支持使用TLS加密的HTTP2协议,不支持明文HTTP2传输。 |
||||
|
|
||||
gost的HTTP2通道模式支持加密(h2)和明文(h2c)两种模式。 |
gost的HTTP2通道模式支持加密(h2)和明文(h2c)两种模式。 |
||||
|
|
||||
#### SOCKS5 |
### SOCKS5 |
||||
|
|
||||
gost支持标准SOCKS5协议的no-auth(0x00)和user/pass(0x02)方法,并在此基础上扩展了两个:tls(0x80)和tls-auth(0x82),用于数据加密。 |
gost支持标准SOCKS5协议的no-auth(0x00)和user/pass(0x02)方法,并在此基础上扩展了两个:tls(0x80)和tls-auth(0x82),用于数据加密。 |
||||
|
|
||||
@ -325,7 +351,8 @@ gost -L=:8080 -F=socks5://server_ip:1080 |
|||||
|
|
||||
如果两端都是gost(如上)则数据传输会被加密(协商使用tls或tls-auth方法),否则使用标准SOCKS5进行通讯(no-auth或user/pass方法)。 |
如果两端都是gost(如上)则数据传输会被加密(协商使用tls或tls-auth方法),否则使用标准SOCKS5进行通讯(no-auth或user/pass方法)。 |
||||
|
|
||||
#### Shadowsocks |
### Shadowsocks |
||||
|
|
||||
gost对shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 |
gost对shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 |
||||
|
|
||||
服务端: |
服务端: |
||||
@ -333,13 +360,14 @@ gost对shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowso |
|||||
```bash |
```bash |
||||
gost -L=ss://chacha20:123456@:8338 |
gost -L=ss://chacha20:123456@:8338 |
||||
``` |
``` |
||||
|
|
||||
客户端: |
客户端: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F=ss://chacha20:123456@server_ip:8338 |
gost -L=:8080 -F=ss://chacha20:123456@server_ip:8338 |
||||
``` |
``` |
||||
|
|
||||
##### Shadowsocks UDP relay |
#### Shadowsocks UDP relay |
||||
|
|
||||
目前仅服务端支持UDP Relay。 |
目前仅服务端支持UDP Relay。 |
||||
|
|
||||
@ -349,21 +377,27 @@ gost -L=:8080 -F=ss://chacha20:123456@server_ip:8338 |
|||||
gost -L=ssu://chacha20:123456@:8338 |
gost -L=ssu://chacha20:123456@:8338 |
||||
``` |
``` |
||||
|
|
||||
#### TLS |
### TLS |
||||
|
|
||||
gost内置了TLS证书,如果需要使用其他TLS证书,有两种方法: |
gost内置了TLS证书,如果需要使用其他TLS证书,有两种方法: |
||||
* 在gost运行目录放置cert.pem(公钥)和key.pem(私钥)两个文件即可,gost会自动加载运行目录下的cert.pem和key.pem文件。 |
|
||||
* 使用参数指定证书文件路径: |
- 在gost运行目录放置cert.pem(公钥)和key.pem(私钥)两个文件即可,gost会自动加载运行目录下的cert.pem和key.pem文件。 |
||||
|
- 使用参数指定证书文件路径: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L="http2://:443?cert=/path/to/my/cert/file&key=/path/to/my/key/file" |
gost -L="http2://:443?cert=/path/to/my/cert/file&key=/path/to/my/key/file" |
||||
``` |
``` |
||||
|
|
||||
对于客户端可以通过`secure`参数开启服务器证书和域名校验: |
对于客户端可以通过`secure`参数开启服务器证书和域名校验: |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F="http2://server_domain_name:443?secure=true" |
gost -L=:8080 -F="http2://server_domain_name:443?secure=true" |
||||
``` |
``` |
||||
|
|
||||
对于客户端可以指定CA证书进行[证书锁定](https://en.wikipedia.org/wiki/Transport_Layer_Security#Certificate_pinning)(Certificate Pinning): |
对于客户端可以指定CA证书进行[证书锁定](https://en.wikipedia.org/wiki/Transport_Layer_Security#Certificate_pinning)(Certificate Pinning): |
||||
|
|
||||
```bash |
```bash |
||||
gost -L=:8080 -F="http2://:443?ca=ca.pem" |
gost -L=:8080 -F="http2://:443?ca=ca.pem" |
||||
``` |
``` |
||||
|
|
||||
证书锁定功能由[@sheerun](https://github.com/sheerun)贡献 |
证书锁定功能由[@sheerun](https://github.com/sheerun)贡献 |
||||
|
|||||
@ -0,0 +1,347 @@ |
|||||
|
package gost |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"crypto/aes" |
||||
|
"crypto/cipher" |
||||
|
"crypto/rand" |
||||
|
"crypto/tls" |
||||
|
"errors" |
||||
|
"io" |
||||
|
"net" |
||||
|
"sync" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/go-log/log" |
||||
|
quic "github.com/quic-go/quic-go" |
||||
|
) |
||||
|
|
||||
|
type quicSession struct { |
||||
|
session quic.EarlyConnection |
||||
|
} |
||||
|
|
||||
|
func (session *quicSession) GetConn() (*quicConn, error) { |
||||
|
stream, err := session.session.OpenStreamSync(context.Background()) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
return &quicConn{ |
||||
|
Stream: stream, |
||||
|
laddr: session.session.LocalAddr(), |
||||
|
raddr: session.session.RemoteAddr(), |
||||
|
}, nil |
||||
|
} |
||||
|
|
||||
|
func (session *quicSession) Close() error { |
||||
|
return session.session.CloseWithError(quic.ApplicationErrorCode(0), "closed") |
||||
|
} |
||||
|
|
||||
|
type quicTransporter struct { |
||||
|
config *QUICConfig |
||||
|
sessionMutex sync.Mutex |
||||
|
sessions map[string]*quicSession |
||||
|
} |
||||
|
|
||||
|
// QUICTransporter creates a Transporter that is used by QUIC proxy client.
|
||||
|
func QUICTransporter(config *QUICConfig) Transporter { |
||||
|
if config == nil { |
||||
|
config = &QUICConfig{} |
||||
|
} |
||||
|
return &quicTransporter{ |
||||
|
config: config, |
||||
|
sessions: make(map[string]*quicSession), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (tr *quicTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) { |
||||
|
opts := &DialOptions{} |
||||
|
for _, option := range options { |
||||
|
option(opts) |
||||
|
} |
||||
|
|
||||
|
udpAddr, err := net.ResolveUDPAddr("udp", addr) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
tr.sessionMutex.Lock() |
||||
|
defer tr.sessionMutex.Unlock() |
||||
|
|
||||
|
session, ok := tr.sessions[addr] |
||||
|
if !ok { |
||||
|
var pc net.PacketConn |
||||
|
pc, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if tr.config != nil && tr.config.Key != nil { |
||||
|
pc = &quicCipherConn{PacketConn: pc, key: tr.config.Key} |
||||
|
} |
||||
|
|
||||
|
session, err = tr.initSession(udpAddr, pc) |
||||
|
if err != nil { |
||||
|
pc.Close() |
||||
|
return nil, err |
||||
|
} |
||||
|
tr.sessions[addr] = session |
||||
|
} |
||||
|
|
||||
|
conn, err = session.GetConn() |
||||
|
if err != nil { |
||||
|
session.Close() |
||||
|
delete(tr.sessions, addr) |
||||
|
return nil, err |
||||
|
} |
||||
|
return conn, nil |
||||
|
} |
||||
|
|
||||
|
func (tr *quicTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { |
||||
|
return conn, nil |
||||
|
} |
||||
|
|
||||
|
func (tr *quicTransporter) initSession(addr net.Addr, conn net.PacketConn) (*quicSession, error) { |
||||
|
config := tr.config |
||||
|
if config == nil { |
||||
|
config = &QUICConfig{} |
||||
|
} |
||||
|
if config.TLSConfig == nil { |
||||
|
config.TLSConfig = &tls.Config{InsecureSkipVerify: true} |
||||
|
} |
||||
|
|
||||
|
quicConfig := &quic.Config{ |
||||
|
HandshakeIdleTimeout: config.Timeout, |
||||
|
MaxIdleTimeout: config.IdleTimeout, |
||||
|
KeepAlivePeriod: config.KeepAlivePeriod, |
||||
|
Versions: []quic.VersionNumber{ |
||||
|
quic.Version1, |
||||
|
quic.Version2, |
||||
|
}, |
||||
|
} |
||||
|
session, err := quic.DialEarly(context.Background(), conn, addr, tlsConfigQUICALPN(config.TLSConfig), quicConfig) |
||||
|
if err != nil { |
||||
|
log.Logf("quic dial %s: %v", addr, err) |
||||
|
return nil, err |
||||
|
} |
||||
|
return &quicSession{session: session}, nil |
||||
|
} |
||||
|
|
||||
|
func (tr *quicTransporter) Multiplex() bool { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
// QUICConfig is the config for QUIC client and server
|
||||
|
type QUICConfig struct { |
||||
|
TLSConfig *tls.Config |
||||
|
Timeout time.Duration |
||||
|
KeepAlive bool |
||||
|
KeepAlivePeriod time.Duration |
||||
|
IdleTimeout time.Duration |
||||
|
Key []byte |
||||
|
} |
||||
|
|
||||
|
type quicListener struct { |
||||
|
ln quic.EarlyListener |
||||
|
connChan chan net.Conn |
||||
|
errChan chan error |
||||
|
} |
||||
|
|
||||
|
// QUICListener creates a Listener for QUIC proxy server.
|
||||
|
func QUICListener(addr string, config *QUICConfig) (Listener, error) { |
||||
|
if config == nil { |
||||
|
config = &QUICConfig{} |
||||
|
} |
||||
|
quicConfig := &quic.Config{ |
||||
|
HandshakeIdleTimeout: config.Timeout, |
||||
|
KeepAlivePeriod: config.KeepAlivePeriod, |
||||
|
MaxIdleTimeout: config.IdleTimeout, |
||||
|
Versions: []quic.VersionNumber{ |
||||
|
quic.Version1, |
||||
|
quic.Version2, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
tlsConfig := config.TLSConfig |
||||
|
if tlsConfig == nil { |
||||
|
tlsConfig = DefaultTLSConfig |
||||
|
} |
||||
|
var conn net.PacketConn |
||||
|
|
||||
|
udpAddr, err := net.ResolveUDPAddr("udp", addr) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
conn, err = net.ListenUDP("udp", udpAddr) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
if config.Key != nil { |
||||
|
conn = &quicCipherConn{PacketConn: conn, key: config.Key} |
||||
|
} |
||||
|
|
||||
|
ln, err := quic.ListenEarly(conn, tlsConfigQUICALPN(tlsConfig), quicConfig) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
l := &quicListener{ |
||||
|
ln: *ln, |
||||
|
connChan: make(chan net.Conn, 1024), |
||||
|
errChan: make(chan error, 1), |
||||
|
} |
||||
|
go l.listenLoop() |
||||
|
|
||||
|
return l, nil |
||||
|
} |
||||
|
|
||||
|
func (l *quicListener) listenLoop() { |
||||
|
for { |
||||
|
session, err := l.ln.Accept(context.Background()) |
||||
|
if err != nil { |
||||
|
log.Log("[quic] accept:", err) |
||||
|
l.errChan <- err |
||||
|
close(l.errChan) |
||||
|
return |
||||
|
} |
||||
|
go l.sessionLoop(session) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (l *quicListener) sessionLoop(session quic.Connection) { |
||||
|
log.Logf("[quic] %s <-> %s", session.RemoteAddr(), session.LocalAddr()) |
||||
|
defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr()) |
||||
|
|
||||
|
for { |
||||
|
stream, err := session.AcceptStream(context.Background()) |
||||
|
if err != nil { |
||||
|
log.Log("[quic] accept stream:", err) |
||||
|
session.CloseWithError(quic.ApplicationErrorCode(0), "closed") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
cc := &quicConn{Stream: stream, laddr: session.LocalAddr(), raddr: session.RemoteAddr()} |
||||
|
select { |
||||
|
case l.connChan <- cc: |
||||
|
default: |
||||
|
cc.Close() |
||||
|
log.Logf("[quic] %s - %s: connection queue is full", session.RemoteAddr(), session.LocalAddr()) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (l *quicListener) Accept() (conn net.Conn, err error) { |
||||
|
var ok bool |
||||
|
select { |
||||
|
case conn = <-l.connChan: |
||||
|
case err, ok = <-l.errChan: |
||||
|
if !ok { |
||||
|
err = errors.New("accpet on closed listener") |
||||
|
} |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (l *quicListener) Addr() net.Addr { |
||||
|
return l.ln.Addr() |
||||
|
} |
||||
|
|
||||
|
func (l *quicListener) Close() error { |
||||
|
return l.ln.Close() |
||||
|
} |
||||
|
|
||||
|
type quicConn struct { |
||||
|
quic.Stream |
||||
|
laddr net.Addr |
||||
|
raddr net.Addr |
||||
|
} |
||||
|
|
||||
|
func (c *quicConn) LocalAddr() net.Addr { |
||||
|
return c.laddr |
||||
|
} |
||||
|
|
||||
|
func (c *quicConn) RemoteAddr() net.Addr { |
||||
|
return c.raddr |
||||
|
} |
||||
|
|
||||
|
type quicCipherConn struct { |
||||
|
net.PacketConn |
||||
|
key []byte |
||||
|
} |
||||
|
|
||||
|
func (conn *quicCipherConn) ReadFrom(data []byte) (n int, addr net.Addr, err error) { |
||||
|
n, addr, err = conn.PacketConn.ReadFrom(data) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
b, err := conn.decrypt(data[:n]) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
copy(data, b) |
||||
|
|
||||
|
return len(b), addr, nil |
||||
|
} |
||||
|
|
||||
|
func (conn *quicCipherConn) WriteTo(data []byte, addr net.Addr) (n int, err error) { |
||||
|
b, err := conn.encrypt(data) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
_, err = conn.PacketConn.WriteTo(b, addr) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
return len(b), nil |
||||
|
} |
||||
|
|
||||
|
func (conn *quicCipherConn) encrypt(data []byte) ([]byte, error) { |
||||
|
c, err := aes.NewCipher(conn.key) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
gcm, err := cipher.NewGCM(c) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
nonce := make([]byte, gcm.NonceSize()) |
||||
|
if _, err = io.ReadFull(rand.Reader, nonce); err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return gcm.Seal(nonce, nonce, data, nil), nil |
||||
|
} |
||||
|
|
||||
|
func (conn *quicCipherConn) decrypt(data []byte) ([]byte, error) { |
||||
|
c, err := aes.NewCipher(conn.key) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
gcm, err := cipher.NewGCM(c) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
nonceSize := gcm.NonceSize() |
||||
|
if len(data) < nonceSize { |
||||
|
return nil, errors.New("ciphertext too short") |
||||
|
} |
||||
|
|
||||
|
nonce, ciphertext := data[:nonceSize], data[nonceSize:] |
||||
|
return gcm.Open(nil, nonce, ciphertext, nil) |
||||
|
} |
||||
|
|
||||
|
func tlsConfigQUICALPN(tlsConfig *tls.Config) *tls.Config { |
||||
|
if tlsConfig == nil { |
||||
|
panic("quic: tlsconfig is nil") |
||||
|
} |
||||
|
tlsConfigQUIC := tlsConfig.Clone() |
||||
|
tlsConfigQUIC.NextProtos = []string{"http/3", "quic/v1"} |
||||
|
return tlsConfigQUIC |
||||
|
} |
||||
@ -0,0 +1,463 @@ |
|||||
|
package gost |
||||
|
|
||||
|
import ( |
||||
|
"crypto/rand" |
||||
|
"crypto/sha256" |
||||
|
"fmt" |
||||
|
"net/http/httptest" |
||||
|
"net/url" |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
func httpOverQUICRoundtrip(targetURL string, data []byte, |
||||
|
clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { |
||||
|
|
||||
|
ln, err := QUICListener("localhost:0", nil) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: HTTPConnector(clientInfo), |
||||
|
Transporter: QUICTransporter(nil), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: HTTPHandler( |
||||
|
UsersHandlerOption(serverInfo...), |
||||
|
), |
||||
|
} |
||||
|
|
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
return proxyRoundtrip(client, server, targetURL, data) |
||||
|
} |
||||
|
|
||||
|
func TestHTTPOverQUIC(t *testing.T) { |
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
for i, tc := range httpProxyTests { |
||||
|
err := httpOverQUICRoundtrip(httpSrv.URL, sendData, tc.cliUser, tc.srvUsers) |
||||
|
if err == nil { |
||||
|
if tc.errStr != "" { |
||||
|
t.Errorf("#%d should failed with error %s", i, tc.errStr) |
||||
|
} |
||||
|
} else { |
||||
|
if tc.errStr == "" { |
||||
|
t.Errorf("#%d got error %v", i, err) |
||||
|
} |
||||
|
if err.Error() != tc.errStr { |
||||
|
t.Errorf("#%d got error %v, want %v", i, err, tc.errStr) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func BenchmarkHTTPOverQUIC(b *testing.B) { |
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
ln, err := QUICListener("localhost:0", nil) |
||||
|
if err != nil { |
||||
|
b.Error(err) |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: HTTPConnector(url.UserPassword("admin", "123456")), |
||||
|
Transporter: QUICTransporter(&QUICConfig{KeepAlive: true}), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: HTTPHandler( |
||||
|
UsersHandlerOption(url.UserPassword("admin", "123456")), |
||||
|
), |
||||
|
} |
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
for i := 0; i < b.N; i++ { |
||||
|
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { |
||||
|
b.Error(err) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func BenchmarkHTTPOverQUICParallel(b *testing.B) { |
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
ln, err := QUICListener("localhost:0", nil) |
||||
|
if err != nil { |
||||
|
b.Error(err) |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: HTTPConnector(url.UserPassword("admin", "123456")), |
||||
|
Transporter: QUICTransporter(nil), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: HTTPHandler( |
||||
|
UsersHandlerOption(url.UserPassword("admin", "123456")), |
||||
|
), |
||||
|
} |
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
b.RunParallel(func(pb *testing.PB) { |
||||
|
for pb.Next() { |
||||
|
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { |
||||
|
b.Error(err) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
func socks5OverQUICRoundtrip(targetURL string, data []byte, |
||||
|
clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { |
||||
|
|
||||
|
ln, err := QUICListener("localhost:0", nil) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: SOCKS5Connector(clientInfo), |
||||
|
Transporter: QUICTransporter(nil), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: SOCKS5Handler( |
||||
|
UsersHandlerOption(serverInfo...), |
||||
|
), |
||||
|
} |
||||
|
|
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
return proxyRoundtrip(client, server, targetURL, data) |
||||
|
} |
||||
|
|
||||
|
func TestSOCKS5OverQUIC(t *testing.T) { |
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
for i, tc := range socks5ProxyTests { |
||||
|
err := socks5OverQUICRoundtrip(httpSrv.URL, sendData, |
||||
|
tc.cliUser, |
||||
|
tc.srvUsers, |
||||
|
) |
||||
|
if err == nil { |
||||
|
if !tc.pass { |
||||
|
t.Errorf("#%d should failed", i) |
||||
|
} |
||||
|
} else { |
||||
|
// t.Logf("#%d %v", i, err)
|
||||
|
if tc.pass { |
||||
|
t.Errorf("#%d got error: %v", i, err) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func socks4OverQUICRoundtrip(targetURL string, data []byte) error { |
||||
|
ln, err := QUICListener("localhost:0", nil) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: SOCKS4Connector(), |
||||
|
Transporter: QUICTransporter(nil), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: SOCKS4Handler(), |
||||
|
} |
||||
|
|
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
return proxyRoundtrip(client, server, targetURL, data) |
||||
|
} |
||||
|
|
||||
|
func TestSOCKS4OverQUIC(t *testing.T) { |
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
err := socks4OverQUICRoundtrip(httpSrv.URL, sendData) |
||||
|
// t.Logf("#%d %v", i, err)
|
||||
|
if err != nil { |
||||
|
t.Errorf("got error: %v", err) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func socks4aOverQUICRoundtrip(targetURL string, data []byte) error { |
||||
|
ln, err := QUICListener("localhost:0", nil) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: SOCKS4AConnector(), |
||||
|
Transporter: QUICTransporter(nil), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: SOCKS4Handler(), |
||||
|
} |
||||
|
|
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
return proxyRoundtrip(client, server, targetURL, data) |
||||
|
} |
||||
|
|
||||
|
func TestSOCKS4AOverQUIC(t *testing.T) { |
||||
|
|
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
err := socks4aOverQUICRoundtrip(httpSrv.URL, sendData) |
||||
|
// t.Logf("#%d %v", i, err)
|
||||
|
if err != nil { |
||||
|
t.Errorf("got error: %v", err) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func ssOverQUICRoundtrip(targetURL string, data []byte, |
||||
|
clientInfo, serverInfo *url.Userinfo) error { |
||||
|
|
||||
|
ln, err := QUICListener("localhost:0", nil) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: ShadowConnector(clientInfo), |
||||
|
Transporter: QUICTransporter(nil), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: ShadowHandler( |
||||
|
UsersHandlerOption(serverInfo), |
||||
|
), |
||||
|
} |
||||
|
|
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
return proxyRoundtrip(client, server, targetURL, data) |
||||
|
} |
||||
|
|
||||
|
func TestSSOverQUIC(t *testing.T) { |
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
for i, tc := range ssProxyTests { |
||||
|
err := ssOverQUICRoundtrip(httpSrv.URL, sendData, |
||||
|
tc.clientCipher, |
||||
|
tc.serverCipher, |
||||
|
) |
||||
|
if err == nil { |
||||
|
if !tc.pass { |
||||
|
t.Errorf("#%d should failed", i) |
||||
|
} |
||||
|
} else { |
||||
|
// t.Logf("#%d %v", i, err)
|
||||
|
if tc.pass { |
||||
|
t.Errorf("#%d got error: %v", i, err) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func sniOverQUICRoundtrip(targetURL string, data []byte, host string) error { |
||||
|
ln, err := QUICListener("localhost:0", nil) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
u, err := url.Parse(targetURL) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: SNIConnector(host), |
||||
|
Transporter: QUICTransporter(nil), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: SNIHandler(HostHandlerOption(u.Host)), |
||||
|
} |
||||
|
|
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
return sniRoundtrip(client, server, targetURL, data) |
||||
|
} |
||||
|
|
||||
|
func TestSNIOverQUIC(t *testing.T) { |
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
httpsSrv := httptest.NewTLSServer(httpTestHandler) |
||||
|
defer httpsSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
var sniProxyTests = []struct { |
||||
|
targetURL string |
||||
|
host string |
||||
|
pass bool |
||||
|
}{ |
||||
|
{httpSrv.URL, "", true}, |
||||
|
{httpSrv.URL, "example.com", true}, |
||||
|
{httpsSrv.URL, "", true}, |
||||
|
{httpsSrv.URL, "example.com", true}, |
||||
|
} |
||||
|
|
||||
|
for i, tc := range sniProxyTests { |
||||
|
tc := tc |
||||
|
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { |
||||
|
err := sniOverQUICRoundtrip(tc.targetURL, sendData, tc.host) |
||||
|
if err == nil { |
||||
|
if !tc.pass { |
||||
|
t.Errorf("#%d should failed", i) |
||||
|
} |
||||
|
} else { |
||||
|
// t.Logf("#%d %v", i, err)
|
||||
|
if tc.pass { |
||||
|
t.Errorf("#%d got error: %v", i, err) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func quicForwardTunnelRoundtrip(targetURL string, data []byte) error { |
||||
|
ln, err := QUICListener("localhost:0", nil) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
u, err := url.Parse(targetURL) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: ForwardConnector(), |
||||
|
Transporter: QUICTransporter(nil), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: TCPDirectForwardHandler(u.Host), |
||||
|
} |
||||
|
server.Handler.Init() |
||||
|
|
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
return proxyRoundtrip(client, server, targetURL, data) |
||||
|
} |
||||
|
|
||||
|
func TestQUICForwardTunnel(t *testing.T) { |
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
err := quicForwardTunnelRoundtrip(httpSrv.URL, sendData) |
||||
|
if err != nil { |
||||
|
t.Error(err) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func httpOverCipherQUICRoundtrip(targetURL string, data []byte, |
||||
|
clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { |
||||
|
|
||||
|
sum := sha256.Sum256([]byte("12345678")) |
||||
|
cfg := &QUICConfig{ |
||||
|
Key: sum[:], |
||||
|
} |
||||
|
ln, err := QUICListener("localhost:0", cfg) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
client := &Client{ |
||||
|
Connector: HTTPConnector(clientInfo), |
||||
|
Transporter: QUICTransporter(cfg), |
||||
|
} |
||||
|
|
||||
|
server := &Server{ |
||||
|
Listener: ln, |
||||
|
Handler: HTTPHandler( |
||||
|
UsersHandlerOption(serverInfo...), |
||||
|
), |
||||
|
} |
||||
|
|
||||
|
go server.Run() |
||||
|
defer server.Close() |
||||
|
|
||||
|
return proxyRoundtrip(client, server, targetURL, data) |
||||
|
} |
||||
|
|
||||
|
func TestHTTPOverCipherQUIC(t *testing.T) { |
||||
|
httpSrv := httptest.NewServer(httpTestHandler) |
||||
|
defer httpSrv.Close() |
||||
|
|
||||
|
sendData := make([]byte, 128) |
||||
|
rand.Read(sendData) |
||||
|
|
||||
|
for i, tc := range httpProxyTests { |
||||
|
err := httpOverCipherQUICRoundtrip(httpSrv.URL, sendData, tc.cliUser, tc.srvUsers) |
||||
|
if err == nil { |
||||
|
if tc.errStr != "" { |
||||
|
t.Errorf("#%d should failed with error %s", i, tc.errStr) |
||||
|
} |
||||
|
} else { |
||||
|
if tc.errStr == "" { |
||||
|
t.Errorf("#%d got error %v", i, err) |
||||
|
} |
||||
|
if err.Error() != tc.errStr { |
||||
|
t.Errorf("#%d got error %v, want %v", i, err, tc.errStr) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue