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语言实现的安全隧道 |
|||
|
|||
@ -9,30 +8,29 @@ GO Simple Tunnel |
|||
[](https://github.com/ginuerzh/gost/releases/latest) |
|||
[](https://hub.docker.com/r/ginuerzh/gost/) |
|||
[](https://snapcraft.io/gost) |
|||
|
|||
|
|||
[English README](README_en.md) |
|||
|
|||
### !!!!! |
|||
|
|||
特性 |
|||
------ |
|||
|
|||
* 多端口监听 |
|||
* 可设置转发代理,支持多级转发(代理链) |
|||
* 支持标准HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5代理协议 |
|||
* Web代理支持[探测防御](https://v2.gost.run/probe_resist/) |
|||
* [支持多种隧道类型](https://v2.gost.run/configuration/) |
|||
* [SOCKS5代理支持TLS协商加密](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/port-forwarding/) |
|||
* [支持Shadowsocks(TCP/UDP)协议](https://v2.gost.run/ss/) |
|||
* [支持SNI代理](https://v2.gost.run/sni/) |
|||
* [权限控制](https://v2.gost.run/permission/) |
|||
* [负载均衡](https://v2.gost.run/load-balancing/) |
|||
* [路由控制](https://v2.gost.run/bypass/) |
|||
* DNS[解析](https://v2.gost.run/resolver/)和[代理](https://v2.gost.run/dns/) |
|||
* [TUN/TAP设备](https://v2.gost.run/tuntap/) |
|||
## 特性 |
|||
|
|||
- 多端口监听 |
|||
- 可设置转发代理,支持多级转发(代理链) |
|||
- 支持标准HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5代理协议 |
|||
- Web代理支持[探测防御](https://v2.gost.run/probe_resist/) |
|||
- [支持多种隧道类型](https://v2.gost.run/configuration/) |
|||
- [SOCKS5代理支持TLS协商加密](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/port-forwarding/) |
|||
- [支持Shadowsocks(TCP/UDP)协议](https://v2.gost.run/ss/) |
|||
- [支持SNI代理](https://v2.gost.run/sni/) |
|||
- [权限控制](https://v2.gost.run/permission/) |
|||
- [负载均衡](https://v2.gost.run/load-balancing/) |
|||
- [路由控制](https://v2.gost.run/bypass/) |
|||
- DNS[解析](https://v2.gost.run/resolver/)和[代理](https://v2.gost.run/dns/) |
|||
- [TUN/TAP设备](https://v2.gost.run/tuntap/) |
|||
|
|||
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> |
|||
|
|||
安装 |
|||
------ |
|||
## 安装 |
|||
|
|||
#### 二进制文件 |
|||
### 二进制文件 |
|||
|
|||
<https://github.com/ginuerzh/gost/releases> |
|||
|
|||
#### 源码编译 |
|||
### 源码编译 |
|||
|
|||
```bash |
|||
git clone https://github.com/ginuerzh/gost.git |
|||
@ -55,52 +52,50 @@ cd gost/cmd/gost |
|||
go build |
|||
``` |
|||
|
|||
#### Docker |
|||
### Docker |
|||
|
|||
```bash |
|||
docker run --rm ginuerzh/gost -V |
|||
``` |
|||
|
|||
#### Homebrew |
|||
### Homebrew |
|||
|
|||
```bash |
|||
brew install gost |
|||
``` |
|||
|
|||
#### Ubuntu商店 |
|||
|
|||
### Ubuntu商店 |
|||
|
|||
```bash |
|||
sudo snap install core |
|||
sudo snap install gost |
|||
``` |
|||
|
|||
快速上手 |
|||
------ |
|||
## 快速上手 |
|||
|
|||
#### 不设置转发代理 |
|||
### 不设置转发代理 |
|||
|
|||
<img src="https://ginuerzh.github.io/images/gost_01.png" /> |
|||
|
|||
* 作为标准HTTP/SOCKS5代理 |
|||
- 作为标准HTTP/SOCKS5代理 |
|||
|
|||
```bash |
|||
gost -L=:8080 |
|||
``` |
|||
|
|||
* 设置代理认证信息 |
|||
- 设置代理认证信息 |
|||
|
|||
```bash |
|||
gost -L=admin:123456@localhost:8080 |
|||
``` |
|||
|
|||
* 多端口监听 |
|||
- 多端口监听 |
|||
|
|||
```bash |
|||
gost -L=http2://:443 -L=socks5://:1080 -L=ss://aes-128-cfb:123456@:8338 |
|||
``` |
|||
|
|||
#### 设置转发代理 |
|||
### 设置转发代理 |
|||
|
|||
<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 |
|||
``` |
|||
|
|||
* 转发代理认证 |
|||
- 转发代理认证 |
|||
|
|||
```bash |
|||
gost -L=:8080 -F=http://admin:[email protected]:8081 |
|||
``` |
|||
|
|||
#### 设置多级转发代理(代理链) |
|||
### 设置多级转发代理(代理链) |
|||
|
|||
<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类型代理。 |
|||
|
|||
#### 本地端口转发(TCP) |
|||
### 本地端口转发(TCP) |
|||
|
|||
```bash |
|||
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 |
|||
``` |
|||
|
|||
#### 本地端口转发(UDP) |
|||
### 本地端口转发(UDP) |
|||
|
|||
```bash |
|||
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方式进行转发。 |
|||
|
|||
#### 远程端口转发(TCP) |
|||
### 远程端口转发(TCP) |
|||
|
|||
```bash |
|||
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的远程端口转发功能: |
|||
|
|||
```bash |
|||
gost -L=rtcp://:2222/192.168.1.1:22 -F forward+ssh://:2222 |
|||
``` |
|||
|
|||
#### 远程端口转发(UDP) |
|||
### 远程端口转发(UDP) |
|||
|
|||
```bash |
|||
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上。 |
|||
每条转发通道都有超时时间,当超过此时间,且在此时间段内无任何数据交互,则此通道将关闭。可以通过`ttl`参数来设置超时时间,默认值为60秒。 |
|||
|
|||
**注:** 转发UDP数据时,如果有代理链,则代理链的末端(最后一个-F参数)必须是GOST SOCKS5类型代理,gost会使用UDP-over-TCP方式进行转发。 |
|||
|
|||
#### HTTP2 |
|||
### HTTP2 |
|||
|
|||
gost的HTTP2支持两种模式: |
|||
* 作为标准的HTTP2代理,并向下兼容HTTPS代理。 |
|||
* 作为通道传输其他协议。 |
|||
|
|||
##### 代理模式 |
|||
- 作为标准的HTTP2代理,并向下兼容HTTPS代理。 |
|||
- 作为通道传输其他协议。 |
|||
|
|||
#### 代理模式 |
|||
|
|||
服务端: |
|||
|
|||
```bash |
|||
gost -L=http2://:443 |
|||
``` |
|||
|
|||
客户端: |
|||
|
|||
```bash |
|||
gost -L=:8080 -F=http2://server_ip:443 |
|||
``` |
|||
|
|||
##### 通道模式 |
|||
#### 通道模式 |
|||
|
|||
服务端: |
|||
|
|||
```bash |
|||
gost -L=h2://:443 |
|||
``` |
|||
|
|||
客户端: |
|||
|
|||
```bash |
|||
gost -L=:8080 -F=h2://server_ip:443 |
|||
``` |
|||
|
|||
#### QUIC |
|||
gost对QUIC的支持是基于[quic-go](https://github.com/lucas-clemente/quic-go)库。 |
|||
### QUIC |
|||
|
|||
gost对QUIC的支持是基于[quic-go](https://github.com/quic-go/quic-go)库。 |
|||
|
|||
服务端: |
|||
|
|||
```bash |
|||
gost -L=quic://:6121 |
|||
``` |
|||
|
|||
客户端: |
|||
|
|||
```bash |
|||
gost -L=:8080 -F=quic://server_ip:6121 |
|||
``` |
|||
|
|||
**注:** QUIC模式只能作为代理链的第一个节点。 |
|||
|
|||
#### KCP |
|||
### KCP |
|||
|
|||
gost对KCP的支持是基于[kcp-go](https://github.com/xtaci/kcp-go)和[kcptun](https://github.com/xtaci/kcptun)库。 |
|||
|
|||
服务端: |
|||
|
|||
```bash |
|||
gost -L=kcp://:8388 |
|||
``` |
|||
|
|||
客户端: |
|||
|
|||
```bash |
|||
gost -L=:8080 -F=kcp://server_ip:8388 |
|||
``` |
|||
|
|||
gost会自动加载当前工作目录中的kcp.json(如果存在)配置文件,或者可以手动通过参数指定配置文件路径: |
|||
|
|||
```bash |
|||
gost -L=kcp://:8388?c=/path/to/conf/file |
|||
``` |
|||
|
|||
**注:** KCP模式只能作为代理链的第一个节点。 |
|||
|
|||
#### SSH |
|||
### SSH |
|||
|
|||
gost的SSH支持两种模式: |
|||
* 作为转发通道,配合本地/远程TCP端口转发使用。 |
|||
* 作为通道传输其他协议。 |
|||
|
|||
##### 转发模式 |
|||
- 作为转发通道,配合本地/远程TCP端口转发使用。 |
|||
- 作为通道传输其他协议。 |
|||
|
|||
#### 转发模式 |
|||
|
|||
服务端: |
|||
|
|||
```bash |
|||
gost -L=forward+ssh://:2222 |
|||
``` |
|||
|
|||
客户端: |
|||
|
|||
```bash |
|||
gost -L=rtcp://:1222/:22 -F=forward+ssh://server_ip:2222 |
|||
``` |
|||
|
|||
##### 通道模式 |
|||
#### 通道模式 |
|||
|
|||
服务端: |
|||
|
|||
```bash |
|||
gost -L=ssh://:2222 |
|||
``` |
|||
|
|||
客户端: |
|||
|
|||
```bash |
|||
gost -L=:8080 -F=ssh://server_ip:2222?ping=60 |
|||
``` |
|||
|
|||
可以通过`ping`参数设置心跳包发送周期,单位为秒。默认不发送心跳包。 |
|||
|
|||
### 透明代理 |
|||
|
|||
#### 透明代理 |
|||
基于iptables的透明代理。 |
|||
|
|||
```bash |
|||
gost -L=redirect://:12345 -F=http2://server_ip:443 |
|||
``` |
|||
|
|||
#### obfs4 |
|||
### obfs4 |
|||
|
|||
此功能由[@isofew](https://github.com/isofew)贡献。 |
|||
|
|||
服务端: |
|||
|
|||
```bash |
|||
gost -L=obfs4://:443 |
|||
``` |
|||
|
|||
当服务端运行后会在控制台打印出连接地址供客户端使用: |
|||
|
|||
``` |
|||
obfs4://:443/?cert=4UbQjIfjJEQHPOs8vs5sagrSXx1gfrDCGdVh2hpIPSKH0nklv1e4f29r7jb91VIrq4q5Jw&iat-mode=0 |
|||
``` |
|||
|
|||
客户端: |
|||
|
|||
``` |
|||
gost -L=:8888 -F='obfs4://server_ip:443?cert=4UbQjIfjJEQHPOs8vs5sagrSXx1gfrDCGdVh2hpIPSKH0nklv1e4f29r7jb91VIrq4q5Jw&iat-mode=0' |
|||
``` |
|||
|
|||
加密机制 |
|||
------ |
|||
## 加密机制 |
|||
|
|||
#### HTTP |
|||
### HTTP |
|||
|
|||
对于HTTP可以使用TLS加密整个通讯过程,即HTTPS代理: |
|||
|
|||
@ -295,19 +320,20 @@ gost -L=:8888 -F='obfs4://server_ip:443?cert=4UbQjIfjJEQHPOs8vs5sagrSXx1gfrDCGdV |
|||
```bash |
|||
gost -L=https://:443 |
|||
``` |
|||
|
|||
客户端: |
|||
|
|||
```bash |
|||
gost -L=:8080 -F=http+tls://server_ip:443 |
|||
``` |
|||
|
|||
#### HTTP2 |
|||
### HTTP2 |
|||
|
|||
gost的HTTP2代理模式仅支持使用TLS加密的HTTP2协议,不支持明文HTTP2传输。 |
|||
|
|||
gost的HTTP2通道模式支持加密(h2)和明文(h2c)两种模式。 |
|||
|
|||
#### SOCKS5 |
|||
### SOCKS5 |
|||
|
|||
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方法)。 |
|||
|
|||
#### Shadowsocks |
|||
### Shadowsocks |
|||
|
|||
gost对shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 |
|||
|
|||
服务端: |
|||
@ -333,13 +360,14 @@ gost对shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowso |
|||
```bash |
|||
gost -L=ss://chacha20:123456@:8338 |
|||
``` |
|||
|
|||
客户端: |
|||
|
|||
```bash |
|||
gost -L=:8080 -F=ss://chacha20:123456@server_ip:8338 |
|||
``` |
|||
|
|||
##### Shadowsocks UDP relay |
|||
#### Shadowsocks UDP relay |
|||
|
|||
目前仅服务端支持UDP Relay。 |
|||
|
|||
@ -349,21 +377,27 @@ gost -L=:8080 -F=ss://chacha20:123456@server_ip:8338 |
|||
gost -L=ssu://chacha20:123456@:8338 |
|||
``` |
|||
|
|||
#### 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 |
|||
gost -L="http2://:443?cert=/path/to/my/cert/file&key=/path/to/my/key/file" |
|||
``` |
|||
|
|||
对于客户端可以通过`secure`参数开启服务器证书和域名校验: |
|||
|
|||
```bash |
|||
gost -L=:8080 -F="http2://server_domain_name:443?secure=true" |
|||
``` |
|||
|
|||
对于客户端可以指定CA证书进行[证书锁定](https://en.wikipedia.org/wiki/Transport_Layer_Security#Certificate_pinning)(Certificate Pinning): |
|||
|
|||
```bash |
|||
gost -L=:8080 -F="http2://:443?ca=ca.pem" |
|||
``` |
|||
|
|||
证书锁定功能由[@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