mirror of https://github.com/ginuerzh/gost
66 changed files with 3623 additions and 9702 deletions
@ -1,5 +0,0 @@ |
|||
language: go |
|||
|
|||
go: |
|||
1.6 |
|||
1.7 |
|||
@ -1,21 +0,0 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2016 ginuerzh |
|||
|
|||
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 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. |
|||
@ -1,358 +0,0 @@ |
|||
gost - GO Simple Tunnel |
|||
====== |
|||
|
|||
### GO语言实现的安全隧道 |
|||
|
|||
[English README](README_en.md) |
|||
|
|||
特性 |
|||
------ |
|||
* 可同时监听多端口 |
|||
* 可设置转发代理,支持多级转发(代理链) |
|||
* 支持标准HTTP/HTTPS/SOCKS4(A)/SOCKS5代理协议 |
|||
* SOCKS5代理支持TLS协商加密 |
|||
* Tunnel UDP over TCP |
|||
* 支持Shadowsocks协议 (OTA: 2.2+,UDP: 2.4+) |
|||
* 支持本地/远程端口转发 (2.1+) |
|||
* 支持HTTP 2.0 (2.2+) |
|||
* 实验性支持QUIC (2.3+) |
|||
* 支持KCP协议 (2.3+) |
|||
* 透明代理 (2.3+) |
|||
* SSH隧道 (2.4+) |
|||
|
|||
二进制文件下载:https://github.com/ginuerzh/gost/releases |
|||
|
|||
Google讨论组: https://groups.google.com/d/forum/go-gost |
|||
|
|||
在gost中,gost与其他代理服务都被看作是代理节点,gost可以自己处理请求,或者将请求转发给任意一个或多个代理节点。 |
|||
|
|||
参数说明 |
|||
------ |
|||
#### 代理及代理链 |
|||
|
|||
适用于-L和-F参数 |
|||
|
|||
```bash |
|||
[scheme://][user:pass@host]:port |
|||
``` |
|||
scheme分为两部分: protocol+transport |
|||
|
|||
protocol: 代理协议类型(http, socks4(a), socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic, kcp, pht), 二者可以任意组合,或单独使用: |
|||
|
|||
> http - HTTP代理: http://:8080 |
|||
|
|||
> http+tls - HTTPS代理(可能需要提供受信任的证书): http+tls://:443或https://:443 |
|||
|
|||
> http2 - HTTP2代理并向下兼容HTTPS代理: http2://:443 |
|||
|
|||
> socks4(a) - 标准SOCKS4(A)代理: socks4://:1080或socks4a://:1080 |
|||
|
|||
> socks - 标准SOCKS5代理(支持TLS协商加密): socks://:1080 |
|||
|
|||
> socks+wss - SOCKS5代理,使用websocket传输数据: socks+wss://:1080 |
|||
|
|||
> tls - HTTPS/SOCKS5代理,使用TLS传输数据: tls://:443 |
|||
|
|||
> ss - Shadowsocks代理,ss://chacha20:123456@:8338 |
|||
|
|||
> ssu - Shadowsocks UDP relay,ssu://chacha20:123456@:8338 |
|||
|
|||
> quic - QUIC代理,quic://:6121 |
|||
|
|||
> kcp - KCP通道,kcp://:8388或kcp://aes:123456@:8388 |
|||
|
|||
> pht - 普通HTTP通道,pht://:8080 |
|||
|
|||
> redirect - 透明代理,redirect://:12345 |
|||
|
|||
> ssh - SSH转发隧道,ssh://admin:123456@:2222 |
|||
|
|||
#### 端口转发 |
|||
|
|||
适用于-L参数 |
|||
|
|||
```bash |
|||
scheme://[bind_address]:port/[host]:hostport |
|||
``` |
|||
> scheme - 端口转发模式, 本地端口转发: tcp, udp; 远程端口转发: rtcp, rudp |
|||
|
|||
> bind_address:port - 本地/远程绑定地址 |
|||
|
|||
> host:hostport - 目标访问地址 |
|||
|
|||
#### 配置文件 |
|||
|
|||
> -C : 指定配置文件路径 |
|||
|
|||
配置文件为标准json格式: |
|||
```json |
|||
{ |
|||
"ServeNodes": [ |
|||
":8080", |
|||
"ss://chacha20:12345678@:8338" |
|||
], |
|||
"ChainNodes": [ |
|||
"http://192.168.1.1:8080", |
|||
"https://10.0.2.1:443" |
|||
] |
|||
} |
|||
``` |
|||
|
|||
ServeNodes等同于-L参数,ChainNodes等同于-F参数 |
|||
|
|||
#### 开启日志 |
|||
|
|||
> -logtostderr : 输出到控制台 |
|||
|
|||
> -v=3 : 日志级别(1-5),级别越高,日志越详细(级别5将开启http2 debug) |
|||
|
|||
> -log_dir=/log/dir/path : 输出到目录/log/dir/path |
|||
|
|||
|
|||
使用方法 |
|||
------ |
|||
#### 不设置转发代理 |
|||
|
|||
<img src="https://ginuerzh.github.io/images/gost_01.png" /> |
|||
|
|||
* 作为标准HTTP/SOCKS5代理 |
|||
```bash |
|||
gost -L=:8080 |
|||
``` |
|||
|
|||
* 设置代理认证信息 |
|||
```bash |
|||
gost -L=admin:123456@localhost:8080 |
|||
``` |
|||
|
|||
* 多组认证信息 |
|||
```bash |
|||
gost -L=localhost:8080?secrets=secrets.txt |
|||
``` |
|||
|
|||
通过secrets参数可以为HTTP/SOCKS5代理设置多组认证信息,格式为: |
|||
```plain |
|||
# username password |
|||
|
|||
test001 123456 |
|||
test002 12345678 |
|||
``` |
|||
|
|||
* 多端口监听 |
|||
```bash |
|||
gost -L=http2://:443 -L=socks://:1080 -L=ss://aes-128-cfb:123456@:8338 |
|||
``` |
|||
|
|||
#### 设置转发代理 |
|||
|
|||
<img src="https://ginuerzh.github.io/images/gost_02.png" /> |
|||
```bash |
|||
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" /> |
|||
```bash |
|||
gost -L=:8080 -F=http+tls://192.168.1.1:443 -F=socks+ws://192.168.1.2:1080 -F=ss://aes-128-cfb:[email protected]:8338 -F=a.b.c.d:NNNN |
|||
``` |
|||
gost按照-F设置的顺序通过代理链将请求最终转发给a.b.c.d:NNNN处理,每一个转发代理可以是任意HTTP/HTTPS/HTTP2/SOCKS5/Shadowsocks类型代理。 |
|||
|
|||
#### 本地端口转发(TCP) |
|||
|
|||
```bash |
|||
gost -L=tcp://:2222/192.168.1.1:22 -F=... |
|||
``` |
|||
将本地TCP端口2222上的数据(通过代理链)转发到192.168.1.1:22上。当代理链末端(最后一个-F参数)为SSH类型时,gost会直接使用SSH的本地端口转发功能。 |
|||
#### 本地端口转发(UDP) |
|||
|
|||
```bash |
|||
gost -L=udp://:5353/192.168.1.1:53?ttl=60 -F=... |
|||
``` |
|||
将本地UDP端口5353上的数据(通过代理链)转发到192.168.1.1:53上。 |
|||
每条转发通道都有超时时间,当超过此时间,且在此时间段内无任何数据交互,则此通道将关闭。可以通过`ttl`参数来设置超时时间,默认值为60秒。 |
|||
|
|||
**注:** 转发UDP数据时,如果有代理链,则代理链的末端(最后一个-F参数)必须是gost SOCKS5类型代理。 |
|||
|
|||
#### 远程端口转发(TCP) |
|||
|
|||
```bash |
|||
gost -L=rtcp://:2222/192.168.1.1:22 -F=... -F=socks://172.24.10.1:1080 |
|||
``` |
|||
将172.24.10.1:2222上的数据(通过代理链)转发到192.168.1.1:22上。当代理链末端(最后一个-F参数)为SSH类型时,gost会直接使用SSH的远程端口转发功能。 |
|||
|
|||
#### 远程端口转发(UDP) |
|||
|
|||
```bash |
|||
gost -L=rudp://:5353/192.168.1.1:53 -F=... -F=socks://172.24.10.1:1080 |
|||
``` |
|||
将172.24.10.1:5353上的数据(通过代理链)转发到192.168.1.1:53上。 |
|||
|
|||
**注:** 若要使用远程端口转发功能,代理链不能为空(至少要设置一个-F参数),且代理链的末端(最后一个-F参数)必须是gost SOCKS5类型代理。 |
|||
|
|||
#### HTTP2 |
|||
gost的HTTP2支持两种模式并自适应: |
|||
* 作为标准的HTTP2代理,并向下兼容HTTPS代理。 |
|||
* 作为transport(类似于wss),传输其他协议。 |
|||
|
|||
服务端: |
|||
```bash |
|||
gost -L=http2://:443 |
|||
``` |
|||
客户端: |
|||
```bash |
|||
gost -L=:8080 -F=http2://server_ip:443?ping=30 |
|||
``` |
|||
|
|||
客户端支持`ping`参数开启心跳检测(默认不开启),参数值代表心跳间隔秒数。 |
|||
|
|||
**注:** gost的代理链仅支持一个HTTP2代理节点,采用就近原则,会将第一个遇到的HTTP2代理节点视为HTTP2代理,其他HTTP2代理节点则被视为HTTPS代理。 |
|||
|
|||
#### QUIC |
|||
gost对QUIC的支持是基于[quic-go](https://github.com/lucas-clemente/quic-go)库。 |
|||
|
|||
服务端: |
|||
```bash |
|||
gost -L=quic://:6121 |
|||
``` |
|||
|
|||
客户端(Chrome): |
|||
```bash |
|||
chrome --enable-quic --proxy-server=quic://server_ip:6121 |
|||
``` |
|||
|
|||
**注:** 由于Chrome自身的限制,目前只能通过QUIC访问HTTP网站,无法访问HTTPS网站。 |
|||
|
|||
#### 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 |
|||
``` |
|||
|
|||
或者手动指定加密方法和密码(手动指定的加密方法和密码会覆盖配置文件中的相应值) |
|||
|
|||
服务端: |
|||
```bash |
|||
gost -L=kcp://aes:123456@:8388 |
|||
``` |
|||
|
|||
客户端: |
|||
```bash |
|||
gost -L=:8080 -F=kcp://aes:123456@server_ip:8388 |
|||
``` |
|||
|
|||
gost会自动加载当前工作目录中的kcp.json(如果存在)配置文件,或者可以手动通过参数指定配置文件路径: |
|||
```bash |
|||
gost -L=kcp://:8388?c=/path/to/conf/file |
|||
``` |
|||
|
|||
**注:** 客户端若要开启KCP转发,当且仅当代理链不为空且首个代理节点(第一个-F参数)为kcp类型。 |
|||
|
|||
#### 透明代理 |
|||
基于iptables的透明代理。 |
|||
|
|||
```bash |
|||
gost -L=redirect://:12345 -F=http2://server_ip:443 |
|||
``` |
|||
|
|||
加密机制 |
|||
------ |
|||
#### HTTP |
|||
对于HTTP可以使用TLS加密整个通讯过程,即HTTPS代理: |
|||
|
|||
服务端: |
|||
```bash |
|||
gost -L=http+tls://:443 |
|||
``` |
|||
客户端: |
|||
```bash |
|||
gost -L=:8080 -F=http+tls://server_ip:443 |
|||
``` |
|||
|
|||
#### HTTP2 |
|||
gost仅支持使用TLS加密的HTTP2协议,不支持明文HTTP2传输。 |
|||
|
|||
|
|||
#### SOCKS5 |
|||
gost支持标准SOCKS5协议的no-auth(0x00)和user/pass(0x02)方法,并在此基础上扩展了两个:tls(0x80)和tls-auth(0x82),用于数据加密。 |
|||
|
|||
服务端: |
|||
```bash |
|||
gost -L=socks://:1080 |
|||
``` |
|||
客户端: |
|||
```bash |
|||
gost -L=:8080 -F=socks://server_ip:1080 |
|||
``` |
|||
|
|||
如果两端都是gost(如上)则数据传输会被加密(协商使用tls或tls-auth方法),否则使用标准SOCKS5进行通讯(no-auth或user/pass方法)。 |
|||
|
|||
**注:** 如果transport已经支持加密(wss, tls, http2, kcp),则SOCKS5不会再使用加密方法,防止不必要的双重加密。 |
|||
|
|||
#### Shadowsocks |
|||
gost对shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 |
|||
|
|||
服务端(可以通过ota参数开启OTA强制模式,开启后客户端必须使用OTA模式): |
|||
```bash |
|||
gost -L=ss://aes-128-cfb:123456@:8338?ota=1 |
|||
``` |
|||
客户端(可以通过ota参数开启OTA模式): |
|||
```bash |
|||
gost -L=:8080 -F=ss://aes-128-cfb:123456@server_ip:8338?ota=1 |
|||
``` |
|||
|
|||
##### Shadowsocks UDP relay |
|||
|
|||
目前仅服务端支持UDP,且仅支持OTA模式。 |
|||
|
|||
服务端: |
|||
```bash |
|||
gost -L=ssu://aes-128-cfb:123456@:8338 |
|||
``` |
|||
|
|||
#### TLS |
|||
gost内置了TLS证书,如果需要使用其他TLS证书,有两种方法: |
|||
* 在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" |
|||
``` |
|||
|
|||
SOCKS5 UDP数据处理 |
|||
------ |
|||
#### 不设置转发代理 |
|||
|
|||
<img src="https://ginuerzh.github.io/images/udp01.png" height=100 /> |
|||
|
|||
gost作为标准SOCKS5代理处理UDP数据 |
|||
|
|||
#### 设置转发代理 |
|||
|
|||
<img src="https://ginuerzh.github.io/images/udp02.png" height=100 /> |
|||
|
|||
#### 设置多个转发代理(代理链) |
|||
|
|||
<img src="https://ginuerzh.github.io/images/udp03.png" height=200 /> |
|||
|
|||
当设置转发代理时,gost会使用UDP-over-TCP方式转发UDP数据。proxy1 - proxyN可以为任意HTTP/HTTPS/HTTP2/SOCKS5/Shadowsocks类型代理。 |
|||
|
|||
限制条件 |
|||
------ |
|||
代理链中的HTTP代理节点必须支持CONNECT方法。 |
|||
|
|||
如果要转发SOCKS5的BIND和UDP请求,代理链的末端(最后一个-F参数)必须支持gost SOCKS5类型代理。 |
|||
|
|||
|
|||
|
|||
@ -1,362 +0,0 @@ |
|||
gost - GO Simple Tunnel |
|||
====== |
|||
|
|||
### A simple security tunnel written in Golang |
|||
|
|||
Features |
|||
------ |
|||
* Listening on multiple ports |
|||
* Multi-level forward proxy - proxy chain |
|||
* Standard HTTP/HTTPS/SOCKS4(A)/SOCKS5 proxy protocols support |
|||
* TLS encryption via negotiation support for SOCKS5 proxy |
|||
* Tunnel UDP over TCP |
|||
* Shadowsocks protocol support (OTA: 2.2+, UDP: 2.4+) |
|||
* Local/remote port forwarding (2.1+) |
|||
* HTTP 2.0 support (2.2+) |
|||
* Experimental QUIC support (2.3+) |
|||
* KCP protocol support (2.3+) |
|||
* Transparent proxy (2.3+) |
|||
* SSH tunnel (2.4+) |
|||
|
|||
Binary file download:https://github.com/ginuerzh/gost/releases |
|||
|
|||
Google group: https://groups.google.com/d/forum/go-gost |
|||
|
|||
Gost and other proxy services are considered to be proxy nodes, |
|||
gost can handle the request itself, or forward the request to any one or more proxy nodes. |
|||
|
|||
Parameter Description |
|||
------ |
|||
#### Proxy and proxy chain |
|||
|
|||
Effective for the -L and -F parameters |
|||
|
|||
```bash |
|||
[scheme://][user:pass@host]:port |
|||
``` |
|||
scheme can be divided into two parts: protocol+transport |
|||
|
|||
protocol: proxy protocol types (http, socks4(a), socks5, shadowsocks), |
|||
transport: data transmission mode (ws, wss, tls, http2, quic, kcp, pht), may be used in any combination or individually: |
|||
|
|||
> http - standard HTTP proxy: http://:8080 |
|||
|
|||
> http+tls - standard HTTPS proxy (may need to provide a trusted certificate): http+tls://:443 or https://:443 |
|||
|
|||
> http2 - HTTP2 proxy and backwards-compatible with HTTPS proxy: http2://:443 |
|||
|
|||
> socks4(a) - standard SOCKS4(A) proxy: socks4://:1080 or socks4a://:1080 |
|||
|
|||
> socks - standard SOCKS5 proxy: socks://:1080 |
|||
|
|||
> socks+wss - SOCKS5 over websocket: socks+wss://:1080 |
|||
|
|||
> tls - HTTPS/SOCKS5 over TLS: tls://:443 |
|||
|
|||
> ss - standard shadowsocks proxy, ss://chacha20:123456@:8338 |
|||
|
|||
> ssu - shadowsocks UDP relay,ssu://chacha20:123456@:8338 |
|||
|
|||
> quic - standard QUIC proxy, quic://:6121 |
|||
|
|||
> kcp - standard KCP tunnel,kcp://:8388 or kcp://aes:123456@:8388 |
|||
|
|||
> pht - plain HTTP tunnel, pht://:8080 |
|||
|
|||
> redirect - transparent proxy,redirect://:12345 |
|||
|
|||
> ssh - SSH tunnel, ssh://admin:123456@:2222 |
|||
|
|||
#### Port forwarding |
|||
|
|||
Effective for the -L parameter |
|||
|
|||
```bash |
|||
scheme://[bind_address]:port/[host]:hostport |
|||
``` |
|||
> scheme - forward mode, local: tcp, udp; remote: rtcp, rudp |
|||
|
|||
> bind_address:port - local/remote binding address |
|||
|
|||
> host:hostport - target address |
|||
|
|||
#### Configuration file |
|||
|
|||
> -C : specifies the configuration file path |
|||
|
|||
The configuration file is in standard JSON format: |
|||
```json |
|||
{ |
|||
"ServeNodes": [ |
|||
":8080", |
|||
"ss://chacha20:12345678@:8338" |
|||
], |
|||
"ChainNodes": [ |
|||
"http://192.168.1.1:8080", |
|||
"https://10.0.2.1:443" |
|||
] |
|||
} |
|||
``` |
|||
|
|||
ServeNodes is equivalent to the -L parameter, ChainNodes is equivalent to the -F parameter. |
|||
|
|||
#### Logging |
|||
|
|||
> -logtostderr : log to console |
|||
|
|||
> -v=3 : log level (1-5),The higher the level, the more detailed the log (level 5 will enable HTTP2 debug) |
|||
|
|||
> -log_dir=/log/dir/path : log to directory /log/dir/path |
|||
|
|||
Usage |
|||
------ |
|||
#### No forward proxy |
|||
|
|||
<img src="https://ginuerzh.github.io/images/gost_01.png" /> |
|||
|
|||
* Standard HTTP/SOCKS5 proxy |
|||
```bash |
|||
gost -L=:8080 |
|||
``` |
|||
|
|||
* Proxy authentication |
|||
```bash |
|||
gost -L=admin:123456@localhost:8080 |
|||
``` |
|||
|
|||
* Multiple sets of authentication information |
|||
```bash |
|||
gost -L=localhost:8080?secrets=secrets.txt |
|||
``` |
|||
|
|||
The secrets parameter allows you to set multiple authentication information for HTTP/SOCKS5 proxies, the format is: |
|||
```plain |
|||
# username password |
|||
|
|||
test001 123456 |
|||
test002 12345678 |
|||
``` |
|||
|
|||
* Listen on multiple ports |
|||
```bash |
|||
gost -L=http2://:443 -L=socks://:1080 -L=ss://aes-128-cfb:123456@:8338 |
|||
``` |
|||
|
|||
#### Forward proxy |
|||
|
|||
<img src="https://ginuerzh.github.io/images/gost_02.png" /> |
|||
```bash |
|||
gost -L=:8080 -F=192.168.1.1:8081 |
|||
``` |
|||
|
|||
* Forward proxy authentication |
|||
```bash |
|||
gost -L=:8080 -F=http://admin:[email protected]:8081 |
|||
``` |
|||
|
|||
#### Multi-level forward proxy |
|||
|
|||
<img src="https://ginuerzh.github.io/images/gost_03.png" /> |
|||
```bash |
|||
gost -L=:8080 -F=http+tls://192.168.1.1:443 -F=socks+ws://192.168.1.2:1080 -F=ss://aes-128-cfb:[email protected]:8338 -F=a.b.c.d:NNNN |
|||
``` |
|||
Gost forwards the request to a.b.c.d:NNNN through the proxy chain in the order set by -F, |
|||
each forward proxy can be any HTTP/HTTPS/HTTP2/SOCKS5/Shadowsocks type. |
|||
|
|||
#### Local TCP port forwarding |
|||
|
|||
```bash |
|||
gost -L=tcp://:2222/192.168.1.1:22 -F=... |
|||
``` |
|||
The data on the local TCP port 2222 is forwarded to 192.168.1.1:22 (through the proxy chain). If the last node of the chain (the last -F parameter) is a SSH tunnel, then gost will use the local port forwarding function of SSH directly. |
|||
|
|||
#### Local UDP port forwarding |
|||
|
|||
```bash |
|||
gost -L=udp://:5353/192.168.1.1:53?ttl=60 -F=... |
|||
``` |
|||
The data on the local UDP port 5353 is forwarded to 192.168.1.1:53 (through the proxy chain). |
|||
Each forwarding channel has a timeout period. When this time is exceeded and there is no data interaction during this time period, the channel will be closed. The timeout value can be set by the `ttl` parameter. The default value is 60 seconds. |
|||
|
|||
**NOTE:** When forwarding UDP data, if there is a proxy chain, the end of the chain (the last -F parameter) must be gost SOCKS5 proxy. |
|||
|
|||
#### Remote TCP port forwarding |
|||
|
|||
```bash |
|||
gost -L=rtcp://:2222/192.168.1.1:22 -F=... -F=socks://172.24.10.1:1080 |
|||
``` |
|||
The data on 172.24.10.1:2222 is forwarded to 192.168.1.1:22 (through the proxy chain). If the last node of the chain (the last -F parameter) is a SSH tunnel, then gost will use the remote port forwarding function of SSH directly. |
|||
|
|||
#### Remote UDP port forwarding |
|||
|
|||
```bash |
|||
gost -L=rudp://:5353/192.168.1.1:53 -F=... -F=socks://172.24.10.1:1080 |
|||
``` |
|||
The data on 172.24.10.1:5353 is forwarded to 192.168.1.1:53 (through the proxy chain). |
|||
|
|||
**NOTE:** To use the remote port forwarding feature, the proxy chain can not be empty (at least one -F parameter is set) |
|||
and the end of the chain (last -F parameter) must be gost SOCKS5 proxy. |
|||
|
|||
#### HTTP2 |
|||
Gost HTTP2 supports two modes and self-adapting: |
|||
* As a standard HTTP2 proxy, and backwards-compatible with the HTTPS proxy. |
|||
* As transport (similar to wss), tunnel other protocol. |
|||
|
|||
Server: |
|||
```bash |
|||
gost -L=http2://:443 |
|||
``` |
|||
Client: |
|||
```bash |
|||
gost -L=:8080 -F=http2://server_ip:443?ping=30 |
|||
``` |
|||
|
|||
The client supports the `ping` parameter to enable heartbeat detection (which is disabled by default). |
|||
Parameter value represents heartbeat interval seconds. |
|||
|
|||
**NOTE:** The proxy chain of gost supports only one HTTP2 proxy node and the nearest rule applies, |
|||
the first HTTP2 proxy node is treated as an HTTP2 proxy, and the other HTTP2 proxy nodes are treated as HTTPS proxies. |
|||
|
|||
#### QUIC |
|||
Support for QUIC is based on library [quic-go](https://github.com/lucas-clemente/quic-go). |
|||
|
|||
Server: |
|||
```bash |
|||
gost -L=quic://:6121 |
|||
``` |
|||
Client(Chrome): |
|||
```bash |
|||
chrome --enable-quic --proxy-server=quic://server_ip:6121 |
|||
``` |
|||
|
|||
**NOTE:** Due to Chrome's limitations, it is currently only possible to access the HTTP (but not HTTPS) site through QUIC. |
|||
|
|||
#### KCP |
|||
Support for KCP is based on libraries [kcp-go](https://github.com/xtaci/kcp-go) and [kcptun](https://github.com/xtaci/kcptun). |
|||
|
|||
Server: |
|||
```bash |
|||
gost -L=kcp://:8388 |
|||
``` |
|||
Client: |
|||
```bash |
|||
gost -L=:8080 -F=kcp://server_ip:8388 |
|||
``` |
|||
|
|||
Or manually specify the encryption method and password (Manually specifying the encryption method and password overwrites the corresponding value in the configuration file) |
|||
|
|||
Server: |
|||
```bash |
|||
gost -L=kcp://aes:123456@:8388 |
|||
``` |
|||
|
|||
Client: |
|||
```bash |
|||
gost -L=:8080 -F=kcp://aes:123456@server_ip:8388 |
|||
``` |
|||
|
|||
Gost will automatically load kcp.json configuration file from current working directory if exists, |
|||
or you can use the parameter to specify the path to the file. |
|||
```bash |
|||
gost -L=kcp://:8388?c=/path/to/conf/file |
|||
``` |
|||
|
|||
**NOTE:** KCP will be enabled if and only if the proxy chain is not empty and the first proxy node (the first -F parameter) is of type KCP. |
|||
|
|||
#### Transparent proxy |
|||
Iptables-based transparent proxy |
|||
|
|||
```bash |
|||
gost -L=redirect://:12345 -F=http2://server_ip:443 |
|||
``` |
|||
|
|||
Encryption Mechanism |
|||
------ |
|||
#### HTTP |
|||
For HTTP, you can use TLS to encrypt the entire communication process, the HTTPS proxy: |
|||
|
|||
Server: |
|||
```bash |
|||
gost -L=http+tls://:443 |
|||
``` |
|||
Client: |
|||
```bash |
|||
gost -L=:8080 -F=http+tls://server_ip:443 |
|||
``` |
|||
|
|||
#### HTTP2 |
|||
Gost supports only the HTTP2 protocol that uses TLS encryption (h2) and does not support plaintext HTTP2 (h2c) transport. |
|||
|
|||
|
|||
#### SOCKS5 |
|||
Gost supports the standard SOCKS5 protocol methods: no-auth (0x00) and user/pass (0x02), |
|||
and extends two methods for data encryption: tls(0x80) and tls-auth(0x82). |
|||
|
|||
Server: |
|||
```bash |
|||
gost -L=socks://:1080 |
|||
``` |
|||
Client: |
|||
```bash |
|||
gost -L=:8080 -F=socks://server_ip:1080 |
|||
``` |
|||
|
|||
If both ends are gosts (as example above), the data transfer will be encrypted (using tls or tls-auth). |
|||
Otherwise, use standard SOCKS5 for communication (no-auth or user/pass). |
|||
|
|||
**NOTE:** If transport already supports encryption (wss, tls, http2, kcp), SOCKS5 will no longer use the encryption method to prevent unnecessary double encryption. |
|||
|
|||
#### Shadowsocks |
|||
Support for shadowsocks is based on library [shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go). |
|||
|
|||
Server (The OTA mode can be enabled by the ota parameter. When enabled, the client must use OTA mode): |
|||
```bash |
|||
gost -L=ss://aes-128-cfb:123456@:8338?ota=1 |
|||
``` |
|||
Client (The OTA mode can be enabled by the ota parameter): |
|||
```bash |
|||
gost -L=:8080 -F=ss://aes-128-cfb:123456@server_ip:8338?ota=1 |
|||
``` |
|||
|
|||
##### Shadowsocks UDP relay |
|||
Currently, only the server supports UDP, and only OTA mode is supported. |
|||
|
|||
Server: |
|||
```bash |
|||
gost -L=ssu://aes-128-cfb:123456@:8338 |
|||
``` |
|||
|
|||
#### TLS |
|||
There is built-in TLS certificate in gost, if you need to use other TLS certificate, there are two ways: |
|||
* Place two files cert.pem (public key) and key.pem (private key) in the current working directory, gost will automatically load them. |
|||
* Use the parameter to specify the path to the certificate file: |
|||
```bash |
|||
gost -L="http2://:443?cert=/path/to/my/cert/file&key=/path/to/my/key/file" |
|||
``` |
|||
|
|||
SOCKS5 UDP Data Processing |
|||
------ |
|||
#### No forward proxy |
|||
|
|||
<img src="https://ginuerzh.github.io/images/udp01.png" height=100 /> |
|||
|
|||
Gost acts as the standard SOCKS5 proxy for UDP relay. |
|||
|
|||
#### Forward proxy |
|||
|
|||
<img src="https://ginuerzh.github.io/images/udp02.png" height=100 /> |
|||
|
|||
#### Multi-level forward proxy |
|||
|
|||
<img src="https://ginuerzh.github.io/images/udp03.png" height=200 /> |
|||
|
|||
When forward proxies are set, gost uses UDP-over-TCP to forward UDP data, proxy1 to proxyN can be any HTTP/HTTPS/HTTP2/SOCKS5/Shadowsocks type. |
|||
|
|||
Limitation |
|||
------ |
|||
The HTTP proxy node in the proxy chain must support the CONNECT method. |
|||
|
|||
If the BIND and UDP requests for SOCKS5 are to be forwarded, the end of the chain (the last -F parameter) must be the gost SOCKS5 proxy. |
|||
|
|||
|
|||
|
|||
@ -1,558 +1,110 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"crypto/rand" |
|||
"crypto/tls" |
|||
"crypto/x509" |
|||
"encoding/base64" |
|||
"errors" |
|||
"io" |
|||
"io/ioutil" |
|||
"net" |
|||
"net/http" |
|||
"net/http/httputil" |
|||
"net/url" |
|||
"strconv" |
|||
"strings" |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/ginuerzh/pht" |
|||
"github.com/golang/glog" |
|||
"github.com/lucas-clemente/quic-go/h2quic" |
|||
"golang.org/x/net/http2" |
|||
) |
|||
|
|||
// Proxy chain holds a list of proxy nodes
|
|||
type ProxyChain struct { |
|||
nodes []ProxyNode |
|||
lastNode *ProxyNode |
|||
http2NodeIndex int |
|||
http2Enabled bool |
|||
http2Client *http.Client |
|||
kcpEnabled bool |
|||
kcpConfig *KCPConfig |
|||
kcpSession *KCPSession |
|||
kcpMutex sync.Mutex |
|||
phtClient *pht.Client |
|||
quicClient *http.Client |
|||
} |
|||
|
|||
func NewProxyChain(nodes ...ProxyNode) *ProxyChain { |
|||
chain := &ProxyChain{nodes: nodes, http2NodeIndex: -1} |
|||
return chain |
|||
} |
|||
var ( |
|||
// ErrEmptyChain is an error that implies the chain is empty.
|
|||
ErrEmptyChain = errors.New("empty chain") |
|||
) |
|||
|
|||
func (c *ProxyChain) AddProxyNode(node ...ProxyNode) { |
|||
c.nodes = append(c.nodes, node...) |
|||
// Chain is a proxy chain that holds a list of proxy nodes.
|
|||
type Chain struct { |
|||
nodes []Node |
|||
} |
|||
|
|||
func (c *ProxyChain) AddProxyNodeString(snode ...string) error { |
|||
for _, sn := range snode { |
|||
node, err := ParseProxyNode(sn) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
c.AddProxyNode(node) |
|||
// NewChain creates a proxy chain with proxy nodes nodes.
|
|||
func NewChain(nodes ...Node) *Chain { |
|||
return &Chain{ |
|||
nodes: nodes, |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
func (c *ProxyChain) Nodes() []ProxyNode { |
|||
// Nodes returns the proxy nodes that the chain holds.
|
|||
func (c *Chain) Nodes() []Node { |
|||
return c.nodes |
|||
} |
|||
|
|||
func (c *ProxyChain) GetNode(index int) *ProxyNode { |
|||
if index < len(c.nodes) { |
|||
return &c.nodes[index] |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
func (c *ProxyChain) SetNode(index int, node ProxyNode) { |
|||
if index < len(c.nodes) { |
|||
c.nodes[index] = node |
|||
// LastNode returns the last node of the node list.
|
|||
// If the chain is empty, an empty node is returns.
|
|||
func (c *Chain) LastNode() Node { |
|||
if c.IsEmpty() { |
|||
return Node{} |
|||
} |
|||
return c.nodes[len(c.nodes)-1] |
|||
} |
|||
|
|||
// Init initialize the proxy chain.
|
|||
// KCP will be enabled if the first proxy node is KCP proxy (transport == kcp).
|
|||
// HTTP2 will be enabled when at least one HTTP2 proxy node (scheme == http2) is present.
|
|||
//
|
|||
// NOTE: Should be called immediately when proxy nodes are ready.
|
|||
func (c *ProxyChain) Init() { |
|||
length := len(c.nodes) |
|||
if length == 0 { |
|||
// AddNode appends the node(s) to the chain.
|
|||
func (c *Chain) AddNode(nodes ...Node) { |
|||
if c == nil { |
|||
return |
|||
} |
|||
|
|||
c.lastNode = &c.nodes[length-1] |
|||
|
|||
// HTTP2 restrict: HTTP2 will be enabled when at least one HTTP2 proxy node is present.
|
|||
for i, node := range c.nodes { |
|||
if node.Transport == "http2" { |
|||
glog.V(LINFO).Infoln("HTTP2 is enabled") |
|||
cfg := &tls.Config{ |
|||
InsecureSkipVerify: node.insecureSkipVerify(), |
|||
ServerName: node.serverName, |
|||
} |
|||
|
|||
caFile := node.caFile() |
|||
|
|||
if caFile != "" { |
|||
cfg.RootCAs = x509.NewCertPool() |
|||
|
|||
data, err := ioutil.ReadFile(caFile) |
|||
if err != nil { |
|||
glog.Fatal(err) |
|||
} |
|||
|
|||
if !cfg.RootCAs.AppendCertsFromPEM(data) { |
|||
glog.Fatal(err) |
|||
} |
|||
} |
|||
|
|||
c.http2NodeIndex = i |
|||
c.initHttp2Client(cfg, c.nodes[:i]...) |
|||
break // shortest chain for HTTP2
|
|||
} |
|||
} |
|||
|
|||
for i, node := range c.nodes { |
|||
if (node.Transport == "kcp" || node.Transport == "pht" || node.Transport == "quic") && i > 0 { |
|||
glog.Fatal("KCP/PHT/QUIC must be the first node in the proxy chain") |
|||
} |
|||
} |
|||
|
|||
if c.nodes[0].Transport == "kcp" { |
|||
glog.V(LINFO).Infoln("KCP is enabled") |
|||
c.kcpEnabled = true |
|||
config, err := ParseKCPConfig(c.nodes[0].Get("c")) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[kcp]", err) |
|||
} |
|||
if config == nil { |
|||
config = DefaultKCPConfig |
|||
} |
|||
if c.nodes[0].Users != nil { |
|||
config.Crypt = c.nodes[0].Users[0].Username() |
|||
config.Key, _ = c.nodes[0].Users[0].Password() |
|||
} |
|||
c.kcpConfig = config |
|||
go snmpLogger(config.SnmpLog, config.SnmpPeriod) |
|||
go kcpSigHandler() |
|||
|
|||
return |
|||
} |
|||
|
|||
if c.nodes[0].Transport == "quic" { |
|||
glog.V(LINFO).Infoln("QUIC is enabled") |
|||
c.quicClient = &http.Client{ |
|||
Transport: &h2quic.QuicRoundTripper{ |
|||
TLSClientConfig: &tls.Config{ |
|||
InsecureSkipVerify: c.nodes[0].insecureSkipVerify(), |
|||
ServerName: c.nodes[0].serverName, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
|
|||
if c.nodes[0].Transport == "pht" { |
|||
glog.V(LINFO).Infoln("Pure HTTP mode is enabled") |
|||
c.phtClient = pht.NewClient(c.nodes[0].Addr, c.nodes[0].Get("key")) |
|||
} |
|||
} |
|||
|
|||
func (c *ProxyChain) KCPEnabled() bool { |
|||
return c.kcpEnabled |
|||
c.nodes = append(c.nodes, nodes...) |
|||
} |
|||
|
|||
func (c *ProxyChain) Http2Enabled() bool { |
|||
return c.http2Enabled |
|||
// IsEmpty checks if the chain is empty.
|
|||
// An empty chain means that there is no proxy node in the chain.
|
|||
func (c *Chain) IsEmpty() bool { |
|||
return c == nil || len(c.nodes) == 0 |
|||
} |
|||
|
|||
// Wrap a net.Conn into a client tls connection, performing any
|
|||
// additional verification as needed.
|
|||
//
|
|||
// As of go 1.3, crypto/tls only supports either doing no certificate
|
|||
// verification, or doing full verification including of the peer's
|
|||
// DNS name. For consul, we want to validate that the certificate is
|
|||
// signed by a known CA, but because consul doesn't use DNS names for
|
|||
// node names, we don't verify the certificate DNS names. Since go 1.3
|
|||
// no longer supports this mode of operation, we have to do it
|
|||
// manually.
|
|||
//
|
|||
// This code is taken from consul:
|
|||
// https://github.com/hashicorp/consul/blob/master/tlsutil/config.go
|
|||
func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { |
|||
var err error |
|||
var tlsConn *tls.Conn |
|||
|
|||
tlsConn = tls.Client(conn, tlsConfig) |
|||
|
|||
// If crypto/tls is doing verification, there's no need to do our own.
|
|||
if tlsConfig.InsecureSkipVerify == false { |
|||
return tlsConn, nil |
|||
// Dial connects to the target address addr through the chain.
|
|||
// If the chain is empty, it will use the net.Dial directly.
|
|||
func (c *Chain) Dial(addr string) (net.Conn, error) { |
|||
if c.IsEmpty() { |
|||
return net.Dial("tcp", addr) |
|||
} |
|||
|
|||
// Similarly if we use host's CA, we can do full handshake
|
|||
if tlsConfig.RootCAs == nil { |
|||
return tlsConn, nil |
|||
} |
|||
|
|||
// Otherwise perform handshake, but don't verify the domain
|
|||
//
|
|||
// The following is lightly-modified from the doFullHandshake
|
|||
// method in https://golang.org/src/crypto/tls/handshake_client.go
|
|||
if err = tlsConn.Handshake(); err != nil { |
|||
tlsConn.Close() |
|||
conn, err := c.Conn() |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
opts := x509.VerifyOptions{ |
|||
Roots: tlsConfig.RootCAs, |
|||
CurrentTime: time.Now(), |
|||
DNSName: "", |
|||
Intermediates: x509.NewCertPool(), |
|||
} |
|||
|
|||
certs := tlsConn.ConnectionState().PeerCertificates |
|||
for i, cert := range certs { |
|||
if i == 0 { |
|||
continue |
|||
} |
|||
opts.Intermediates.AddCert(cert) |
|||
} |
|||
|
|||
_, err = certs[0].Verify(opts) |
|||
cc, err := c.LastNode().Client.Connect(conn, addr) |
|||
if err != nil { |
|||
tlsConn.Close() |
|||
conn.Close() |
|||
return nil, err |
|||
} |
|||
|
|||
return tlsConn, err |
|||
} |
|||
|
|||
func (c *ProxyChain) initHttp2Client(config *tls.Config, nodes ...ProxyNode) { |
|||
if c.http2NodeIndex < 0 || c.http2NodeIndex >= len(c.nodes) { |
|||
return |
|||
} |
|||
http2Node := c.nodes[c.http2NodeIndex] |
|||
|
|||
tr := http2.Transport{ |
|||
TLSClientConfig: config, |
|||
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { |
|||
// replace the default dialer with our proxy chain.
|
|||
conn, err := c.dialWithNodes(false, http2Node.Addr, nodes...) |
|||
if err != nil { |
|||
return conn, err |
|||
} |
|||
|
|||
conn, err = wrapTLSClient(conn, cfg) |
|||
if err != nil { |
|||
return conn, err |
|||
} |
|||
|
|||
// enable HTTP2 ping-pong
|
|||
pingIntvl, _ := strconv.Atoi(http2Node.Get("ping")) |
|||
if pingIntvl > 0 { |
|||
enablePing(conn, time.Duration(pingIntvl)*time.Second) |
|||
} |
|||
|
|||
return conn, nil |
|||
}, |
|||
} |
|||
c.http2Client = &http.Client{Transport: &tr} |
|||
c.http2Enabled = true |
|||
|
|||
} |
|||
|
|||
func enablePing(conn net.Conn, interval time.Duration) { |
|||
if conn == nil || interval == 0 { |
|||
return |
|||
} |
|||
|
|||
glog.V(LINFO).Infoln("[http2] ping enabled, interval:", interval) |
|||
go func() { |
|||
t := time.NewTicker(interval) |
|||
var framer *http2.Framer |
|||
for { |
|||
select { |
|||
case <-t.C: |
|||
if framer == nil { |
|||
framer = http2.NewFramer(conn, conn) |
|||
} |
|||
|
|||
var p [8]byte |
|||
rand.Read(p[:]) |
|||
err := framer.WritePing(false, p) |
|||
if err != nil { |
|||
t.Stop() |
|||
framer = nil |
|||
glog.V(LWARNING).Infoln("[http2] ping:", err) |
|||
return |
|||
} |
|||
} |
|||
} |
|||
}() |
|||
} |
|||
|
|||
// Connect to addr through proxy chain
|
|||
func (c *ProxyChain) Dial(addr string) (net.Conn, error) { |
|||
if !strings.Contains(addr, ":") { |
|||
addr += ":80" |
|||
} |
|||
return c.dialWithNodes(true, addr, c.nodes...) |
|||
return cc, nil |
|||
} |
|||
|
|||
// GetConn initializes a proxy chain connection,
|
|||
// if no proxy nodes on this chain, it will return error
|
|||
func (c *ProxyChain) GetConn() (net.Conn, error) { |
|||
nodes := c.nodes |
|||
if len(nodes) == 0 { |
|||
// Conn obtains a handshaked connection to the last node of the chain.
|
|||
// If the chain is empty, it returns an ErrEmptyChain error.
|
|||
func (c *Chain) Conn() (net.Conn, error) { |
|||
if c.IsEmpty() { |
|||
return nil, ErrEmptyChain |
|||
} |
|||
|
|||
if c.Http2Enabled() { |
|||
nodes = nodes[c.http2NodeIndex+1:] |
|||
if len(nodes) == 0 { |
|||
header := make(http.Header) |
|||
header.Set("Proxy-Switch", "gost") // Flag header to indicate server to switch to HTTP2 transport mode
|
|||
conn, err := c.getHttp2Conn(header) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
http2Node := c.nodes[c.http2NodeIndex] |
|||
if http2Node.Transport == "http2" { |
|||
http2Node.Transport = "h2" |
|||
} |
|||
if http2Node.Protocol == "http2" { |
|||
http2Node.Protocol = "socks5" // assume it as socks5 protocol, so we can do much more things.
|
|||
} |
|||
pc := NewProxyConn(conn, http2Node) |
|||
if err := pc.Handshake(); err != nil { |
|||
conn.Close() |
|||
return nil, err |
|||
} |
|||
return pc, nil |
|||
} |
|||
} |
|||
return c.travelNodes(true, nodes...) |
|||
} |
|||
|
|||
func (c *ProxyChain) dialWithNodes(withHttp2 bool, addr string, nodes ...ProxyNode) (conn net.Conn, err error) { |
|||
if len(nodes) == 0 { |
|||
return net.DialTimeout("tcp", addr, DialTimeout) |
|||
} |
|||
|
|||
if withHttp2 && c.Http2Enabled() { |
|||
nodes = nodes[c.http2NodeIndex+1:] |
|||
if len(nodes) == 0 { |
|||
return c.http2Connect(addr) |
|||
} |
|||
} |
|||
|
|||
if nodes[0].Transport == "quic" { |
|||
glog.V(LINFO).Infoln("Dial with QUIC") |
|||
return c.quicConnect(addr) |
|||
} |
|||
|
|||
pc, err := c.travelNodes(withHttp2, nodes...) |
|||
if err != nil { |
|||
return |
|||
} |
|||
if err = pc.Connect(addr); err != nil { |
|||
pc.Close() |
|||
return |
|||
} |
|||
conn = pc |
|||
return |
|||
} |
|||
|
|||
func (c *ProxyChain) travelNodes(withHttp2 bool, nodes ...ProxyNode) (conn *ProxyConn, err error) { |
|||
defer func() { |
|||
if err != nil && conn != nil { |
|||
conn.Close() |
|||
conn = nil |
|||
} |
|||
}() |
|||
|
|||
var cc net.Conn |
|||
node := nodes[0] |
|||
|
|||
if withHttp2 && c.Http2Enabled() { |
|||
cc, err = c.http2Connect(node.Addr) |
|||
} else if node.Transport == "kcp" { |
|||
cc, err = c.getKCPConn() |
|||
} else if node.Transport == "pht" { |
|||
cc, err = c.phtClient.Dial() |
|||
} else { |
|||
cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout) |
|||
} |
|||
nodes := c.nodes |
|||
conn, err := nodes[0].Client.Dial(nodes[0].Addr, nodes[0].DialOptions...) |
|||
if err != nil { |
|||
return |
|||
} |
|||
setKeepAlive(cc, KeepAliveTime) |
|||
|
|||
pc := NewProxyConn(cc, node) |
|||
conn = pc |
|||
if err = pc.Handshake(); err != nil { |
|||
return |
|||
} |
|||
|
|||
for _, node := range nodes[1:] { |
|||
if err = conn.Connect(node.Addr); err != nil { |
|||
return |
|||
} |
|||
pc := NewProxyConn(conn, node) |
|||
conn = pc |
|||
if err = pc.Handshake(); err != nil { |
|||
return |
|||
} |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (c *ProxyChain) initKCPSession() (err error) { |
|||
c.kcpMutex.Lock() |
|||
defer c.kcpMutex.Unlock() |
|||
|
|||
if c.kcpSession == nil || c.kcpSession.IsClosed() { |
|||
glog.V(LINFO).Infoln("[kcp] new kcp session") |
|||
c.kcpSession, err = DialKCP(c.nodes[0].Addr, c.kcpConfig) |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (c *ProxyChain) getKCPConn() (conn net.Conn, err error) { |
|||
if !c.KCPEnabled() { |
|||
return nil, errors.New("KCP is not enabled") |
|||
} |
|||
|
|||
if err = c.initKCPSession(); err != nil { |
|||
return nil, err |
|||
} |
|||
return c.kcpSession.GetConn() |
|||
} |
|||
|
|||
// Initialize an HTTP2 transport if HTTP2 is enabled.
|
|||
func (c *ProxyChain) getHttp2Conn(header http.Header) (net.Conn, error) { |
|||
if !c.Http2Enabled() { |
|||
return nil, errors.New("HTTP2 is not enabled") |
|||
} |
|||
http2Node := c.nodes[c.http2NodeIndex] |
|||
pr, pw := io.Pipe() |
|||
|
|||
if header == nil { |
|||
header = make(http.Header) |
|||
} |
|||
|
|||
req := http.Request{ |
|||
Method: http.MethodConnect, |
|||
URL: &url.URL{Scheme: "https", Host: http2Node.Addr}, |
|||
Header: header, |
|||
Proto: "HTTP/2.0", |
|||
ProtoMajor: 2, |
|||
ProtoMinor: 0, |
|||
Body: pr, |
|||
Host: http2Node.Addr, |
|||
ContentLength: -1, |
|||
} |
|||
if glog.V(LDEBUG) { |
|||
dump, _ := httputil.DumpRequest(&req, false) |
|||
glog.Infoln(string(dump)) |
|||
} |
|||
resp, err := c.http2Client.Do(&req) |
|||
conn, err = nodes[0].Client.Handshake(conn, nodes[0].HandshakeOptions...) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if glog.V(LDEBUG) { |
|||
dump, _ := httputil.DumpResponse(resp, false) |
|||
glog.Infoln(string(dump)) |
|||
} |
|||
if resp.StatusCode != http.StatusOK { |
|||
resp.Body.Close() |
|||
return nil, errors.New(resp.Status) |
|||
} |
|||
conn := &http2Conn{r: resp.Body, w: pw} |
|||
conn.remoteAddr, _ = net.ResolveTCPAddr("tcp", http2Node.Addr) |
|||
return conn, nil |
|||
} |
|||
|
|||
// Use HTTP2 as transport to connect target addr.
|
|||
//
|
|||
// BUG: SOCKS5 is ignored, only HTTP supported
|
|||
func (c *ProxyChain) http2Connect(addr string) (net.Conn, error) { |
|||
if !c.Http2Enabled() { |
|||
return nil, errors.New("HTTP2 is not enabled") |
|||
} |
|||
http2Node := c.nodes[c.http2NodeIndex] |
|||
|
|||
header := make(http.Header) |
|||
header.Set("Gost-Target", addr) // Flag header to indicate the address that server connected to
|
|||
if http2Node.Users != nil { |
|||
header.Set("Proxy-Authorization", |
|||
"Basic "+base64.StdEncoding.EncodeToString([]byte(http2Node.Users[0].String()))) |
|||
} |
|||
return c.getHttp2Conn(header) |
|||
} |
|||
|
|||
func (c *ProxyChain) quicConnect(addr string) (net.Conn, error) { |
|||
quicNode := c.nodes[0] |
|||
header := make(http.Header) |
|||
header.Set("Gost-Target", addr) // Flag header to indicate the address that server connected to
|
|||
if quicNode.Users != nil { |
|||
header.Set("Proxy-Authorization", |
|||
"Basic "+base64.StdEncoding.EncodeToString([]byte(quicNode.Users[0].String()))) |
|||
} |
|||
return c.getQuicConn(header) |
|||
} |
|||
|
|||
func (c *ProxyChain) getQuicConn(header http.Header) (net.Conn, error) { |
|||
quicNode := c.nodes[0] |
|||
pr, pw := io.Pipe() |
|||
|
|||
if header == nil { |
|||
header = make(http.Header) |
|||
} |
|||
|
|||
/* |
|||
req := http.Request{ |
|||
Method: http.MethodGet, |
|||
URL: &url.URL{Scheme: "https", Host: quicNode.Addr}, |
|||
Header: header, |
|||
Proto: "HTTP/2.0", |
|||
ProtoMajor: 2, |
|||
ProtoMinor: 0, |
|||
Body: pr, |
|||
Host: quicNode.Addr, |
|||
ContentLength: -1, |
|||
for i, node := range nodes { |
|||
if i == len(nodes)-1 { |
|||
break |
|||
} |
|||
*/ |
|||
req, err := http.NewRequest(http.MethodPost, "https://"+quicNode.Addr, pr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
req.ContentLength = -1 |
|||
req.Header = header |
|||
|
|||
if glog.V(LDEBUG) { |
|||
dump, _ := httputil.DumpRequest(req, false) |
|||
glog.Infoln(string(dump)) |
|||
} |
|||
resp, err := c.quicClient.Do(req) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if glog.V(LDEBUG) { |
|||
dump, _ := httputil.DumpResponse(resp, false) |
|||
glog.Infoln(string(dump)) |
|||
} |
|||
if resp.StatusCode != http.StatusOK { |
|||
resp.Body.Close() |
|||
return nil, errors.New(resp.Status) |
|||
next := nodes[i+1] |
|||
cc, err := node.Client.Connect(conn, next.Addr) |
|||
if err != nil { |
|||
conn.Close() |
|||
return nil, err |
|||
} |
|||
cc, err = next.Client.Handshake(cc, next.HandshakeOptions...) |
|||
if err != nil { |
|||
conn.Close() |
|||
return nil, err |
|||
} |
|||
conn = cc |
|||
} |
|||
conn := &http2Conn{r: resp.Body, w: pw} |
|||
conn.remoteAddr, _ = net.ResolveUDPAddr("udp", quicNode.Addr) |
|||
return conn, nil |
|||
} |
|||
|
|||
@ -1 +0,0 @@ |
|||
gost |
|||
@ -1,313 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"bufio" |
|||
"crypto/tls" |
|||
"encoding/base64" |
|||
"errors" |
|||
"fmt" |
|||
"net" |
|||
"net/http" |
|||
"net/http/httputil" |
|||
"net/url" |
|||
"strconv" |
|||
"strings" |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/ginuerzh/gosocks4" |
|||
"github.com/ginuerzh/gosocks5" |
|||
"github.com/golang/glog" |
|||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" |
|||
) |
|||
|
|||
type ProxyConn struct { |
|||
conn net.Conn |
|||
Node ProxyNode |
|||
handshaked bool |
|||
handshakeMutex sync.Mutex |
|||
handshakeErr error |
|||
} |
|||
|
|||
func NewProxyConn(conn net.Conn, node ProxyNode) *ProxyConn { |
|||
return &ProxyConn{ |
|||
conn: conn, |
|||
Node: node, |
|||
} |
|||
} |
|||
|
|||
// Handshake handshake with this proxy node based on the proxy node info: transport, protocol, authentication, etc.
|
|||
//
|
|||
// NOTE: any HTTP2 scheme will be treated as http (for protocol) or tls (for transport).
|
|||
func (c *ProxyConn) Handshake() error { |
|||
c.handshakeMutex.Lock() |
|||
defer c.handshakeMutex.Unlock() |
|||
|
|||
if err := c.handshakeErr; err != nil { |
|||
return err |
|||
} |
|||
if c.handshaked { |
|||
return nil |
|||
} |
|||
c.handshakeErr = c.handshake() |
|||
return c.handshakeErr |
|||
} |
|||
|
|||
func (c *ProxyConn) handshake() error { |
|||
var tlsUsed bool |
|||
|
|||
switch c.Node.Transport { |
|||
case "ws": // websocket connection
|
|||
rbuf, _ := strconv.Atoi(c.Node.Get("rbuf")) |
|||
wbuf, _ := strconv.Atoi(c.Node.Get("wbuf")) |
|||
comp := c.Node.getBool("compression") |
|||
opt := WSOptions{ |
|||
ReadBufferSize: rbuf, |
|||
WriteBufferSize: wbuf, |
|||
HandshakeTimeout: DialTimeout, |
|||
EnableCompression: comp, |
|||
} |
|||
u := url.URL{Scheme: "ws", Host: c.Node.Addr, Path: "/ws"} |
|||
conn, err := WebsocketClientConn(u.String(), c.conn, &opt) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
c.conn = conn |
|||
case "wss": // websocket security
|
|||
tlsUsed = true |
|||
|
|||
rbuf, _ := strconv.Atoi(c.Node.Get("rbuf")) |
|||
wbuf, _ := strconv.Atoi(c.Node.Get("wbuf")) |
|||
comp := c.Node.getBool("compression") |
|||
opt := WSOptions{ |
|||
ReadBufferSize: rbuf, |
|||
WriteBufferSize: wbuf, |
|||
HandshakeTimeout: DialTimeout, |
|||
EnableCompression: comp, |
|||
TLSConfig: &tls.Config{ |
|||
InsecureSkipVerify: c.Node.insecureSkipVerify(), |
|||
ServerName: c.Node.serverName, |
|||
}, |
|||
} |
|||
|
|||
u := url.URL{Scheme: "wss", Host: c.Node.Addr, Path: "/ws"} |
|||
conn, err := WebsocketClientConn(u.String(), c.conn, &opt) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
c.conn = conn |
|||
case "tls", "http2": // tls connection
|
|||
tlsUsed = true |
|||
cfg := &tls.Config{ |
|||
InsecureSkipVerify: c.Node.insecureSkipVerify(), |
|||
ServerName: c.Node.serverName, |
|||
} |
|||
c.conn = tls.Client(c.conn, cfg) |
|||
case "h2": // same as http2, but just set a flag for later using.
|
|||
tlsUsed = true |
|||
case "kcp": // kcp connection
|
|||
tlsUsed = true |
|||
default: |
|||
} |
|||
|
|||
switch c.Node.Protocol { |
|||
case "socks", "socks5": // socks5 handshake with auth and tls supported
|
|||
selector := &ClientSelector{ |
|||
methods: []uint8{ |
|||
gosocks5.MethodNoAuth, |
|||
gosocks5.MethodUserPass, |
|||
//MethodTLS,
|
|||
}, |
|||
} |
|||
|
|||
if len(c.Node.Users) > 0 { |
|||
selector.User = c.Node.Users[0] |
|||
} |
|||
|
|||
if !tlsUsed { // if transport is not security, enable security socks5
|
|||
selector.methods = append(selector.methods, MethodTLS) |
|||
selector.TLSConfig = &tls.Config{ |
|||
InsecureSkipVerify: c.Node.insecureSkipVerify(), |
|||
ServerName: c.Node.serverName, |
|||
} |
|||
} |
|||
|
|||
conn := gosocks5.ClientConn(c.conn, selector) |
|||
if err := conn.Handleshake(); err != nil { |
|||
return err |
|||
} |
|||
c.conn = conn |
|||
case "ss": // shadowsocks
|
|||
// nothing to do
|
|||
case "http", "http2": |
|||
fallthrough |
|||
default: |
|||
} |
|||
|
|||
c.handshaked = true |
|||
|
|||
return nil |
|||
} |
|||
|
|||
// Connect connect to addr through this proxy node
|
|||
func (c *ProxyConn) Connect(addr string) error { |
|||
switch c.Node.Protocol { |
|||
case "ss": // shadowsocks
|
|||
rawaddr, err := ss.RawAddr(addr) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
var method, password string |
|||
if len(c.Node.Users) > 0 { |
|||
method = c.Node.Users[0].Username() |
|||
password, _ = c.Node.Users[0].Password() |
|||
} |
|||
if c.Node.getBool("ota") && !strings.HasSuffix(method, "-auth") { |
|||
method += "-auth" |
|||
} |
|||
|
|||
cipher, err := ss.NewCipher(method, password) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
ssc, err := ss.DialWithRawAddrConn(rawaddr, c.conn, cipher) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
c.conn = &shadowConn{conn: ssc} |
|||
return nil |
|||
case "socks", "socks5": |
|||
host, port, err := net.SplitHostPort(addr) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
p, _ := strconv.Atoi(port) |
|||
req := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{ |
|||
Type: gosocks5.AddrDomain, |
|||
Host: host, |
|||
Port: uint16(p), |
|||
}) |
|||
if err := req.Write(c); err != nil { |
|||
return err |
|||
} |
|||
glog.V(LDEBUG).Infoln("[socks5]", req) |
|||
|
|||
reply, err := gosocks5.ReadReply(c) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
glog.V(LDEBUG).Infoln("[socks5]", reply) |
|||
if reply.Rep != gosocks5.Succeeded { |
|||
return errors.New("Service unavailable") |
|||
} |
|||
case "socks4", "socks4a": |
|||
atype := gosocks4.AddrDomain |
|||
host, port, err := net.SplitHostPort(addr) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
p, _ := strconv.Atoi(port) |
|||
|
|||
if c.Node.Protocol == "socks4" { |
|||
taddr, err := net.ResolveTCPAddr("tcp4", addr) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
host = taddr.IP.String() |
|||
p = taddr.Port |
|||
atype = gosocks4.AddrIPv4 |
|||
} |
|||
req := gosocks4.NewRequest(gosocks4.CmdConnect, |
|||
&gosocks4.Addr{Type: atype, Host: host, Port: uint16(p)}, nil) |
|||
if err := req.Write(c); err != nil { |
|||
return err |
|||
} |
|||
glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, req) |
|||
|
|||
reply, err := gosocks4.ReadReply(c) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, reply) |
|||
|
|||
if reply.Code != gosocks4.Granted { |
|||
return errors.New(fmt.Sprintf("%s: code=%d", c.Node.Protocol, reply.Code)) |
|||
} |
|||
case "http": |
|||
fallthrough |
|||
default: |
|||
req := &http.Request{ |
|||
Method: http.MethodConnect, |
|||
URL: &url.URL{Host: addr}, |
|||
Host: addr, |
|||
ProtoMajor: 1, |
|||
ProtoMinor: 1, |
|||
Header: make(http.Header), |
|||
} |
|||
req.Header.Set("Proxy-Connection", "keep-alive") |
|||
if len(c.Node.Users) > 0 { |
|||
user := c.Node.Users[0] |
|||
s := user.String() |
|||
if _, set := user.Password(); !set { |
|||
s += ":" |
|||
} |
|||
req.Header.Set("Proxy-Authorization", |
|||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s))) |
|||
} |
|||
if err := req.Write(c); err != nil { |
|||
return err |
|||
} |
|||
if glog.V(LDEBUG) { |
|||
dump, _ := httputil.DumpRequest(req, false) |
|||
glog.Infoln(string(dump)) |
|||
} |
|||
|
|||
resp, err := http.ReadResponse(bufio.NewReader(c), req) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
if glog.V(LDEBUG) { |
|||
dump, _ := httputil.DumpResponse(resp, false) |
|||
glog.Infoln(string(dump)) |
|||
} |
|||
if resp.StatusCode != http.StatusOK { |
|||
return errors.New(resp.Status) |
|||
} |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func (c *ProxyConn) Read(b []byte) (n int, err error) { |
|||
return c.conn.Read(b) |
|||
} |
|||
|
|||
func (c *ProxyConn) Write(b []byte) (n int, err error) { |
|||
return c.conn.Write(b) |
|||
} |
|||
|
|||
func (c *ProxyConn) Close() error { |
|||
return c.conn.Close() |
|||
} |
|||
|
|||
func (c *ProxyConn) LocalAddr() net.Addr { |
|||
return c.conn.LocalAddr() |
|||
} |
|||
|
|||
func (c *ProxyConn) RemoteAddr() net.Addr { |
|||
return c.conn.RemoteAddr() |
|||
} |
|||
|
|||
func (c *ProxyConn) SetDeadline(t time.Time) error { |
|||
return c.conn.SetDeadline(t) |
|||
} |
|||
|
|||
func (c *ProxyConn) SetReadDeadline(t time.Time) error { |
|||
return c.conn.SetReadDeadline(t) |
|||
} |
|||
|
|||
func (c *ProxyConn) SetWriteDeadline(t time.Time) error { |
|||
return c.conn.SetWriteDeadline(t) |
|||
} |
|||
File diff suppressed because it is too large
@ -1,110 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"errors" |
|||
"net" |
|||
) |
|||
|
|||
var ( |
|||
// ErrEmptyChain is an error that implies the chain is empty.
|
|||
ErrEmptyChain = errors.New("empty chain") |
|||
) |
|||
|
|||
// Chain is a proxy chain that holds a list of proxy nodes.
|
|||
type Chain struct { |
|||
nodes []Node |
|||
} |
|||
|
|||
// NewChain creates a proxy chain with proxy nodes nodes.
|
|||
func NewChain(nodes ...Node) *Chain { |
|||
return &Chain{ |
|||
nodes: nodes, |
|||
} |
|||
} |
|||
|
|||
// Nodes returns the proxy nodes that the chain holds.
|
|||
func (c *Chain) Nodes() []Node { |
|||
return c.nodes |
|||
} |
|||
|
|||
// LastNode returns the last node of the node list.
|
|||
// If the chain is empty, an empty node is returns.
|
|||
func (c *Chain) LastNode() Node { |
|||
if c.IsEmpty() { |
|||
return Node{} |
|||
} |
|||
return c.nodes[len(c.nodes)-1] |
|||
} |
|||
|
|||
// AddNode appends the node(s) to the chain.
|
|||
func (c *Chain) AddNode(nodes ...Node) { |
|||
if c == nil { |
|||
return |
|||
} |
|||
c.nodes = append(c.nodes, nodes...) |
|||
} |
|||
|
|||
// IsEmpty checks if the chain is empty.
|
|||
// An empty chain means that there is no proxy node in the chain.
|
|||
func (c *Chain) IsEmpty() bool { |
|||
return c == nil || len(c.nodes) == 0 |
|||
} |
|||
|
|||
// Dial connects to the target address addr through the chain.
|
|||
// If the chain is empty, it will use the net.Dial directly.
|
|||
func (c *Chain) Dial(addr string) (net.Conn, error) { |
|||
if c.IsEmpty() { |
|||
return net.Dial("tcp", addr) |
|||
} |
|||
|
|||
conn, err := c.Conn() |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
cc, err := c.LastNode().Client.Connect(conn, addr) |
|||
if err != nil { |
|||
conn.Close() |
|||
return nil, err |
|||
} |
|||
return cc, nil |
|||
} |
|||
|
|||
// Conn obtains a handshaked connection to the last node of the chain.
|
|||
// If the chain is empty, it returns an ErrEmptyChain error.
|
|||
func (c *Chain) Conn() (net.Conn, error) { |
|||
if c.IsEmpty() { |
|||
return nil, ErrEmptyChain |
|||
} |
|||
|
|||
nodes := c.nodes |
|||
conn, err := nodes[0].Client.Dial(nodes[0].Addr, nodes[0].DialOptions...) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
conn, err = nodes[0].Client.Handshake(conn, nodes[0].HandshakeOptions...) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
for i, node := range nodes { |
|||
if i == len(nodes)-1 { |
|||
break |
|||
} |
|||
|
|||
next := nodes[i+1] |
|||
cc, err := node.Client.Connect(conn, next.Addr) |
|||
if err != nil { |
|||
conn.Close() |
|||
return nil, err |
|||
} |
|||
cc, err = next.Client.Handshake(cc, next.HandshakeOptions...) |
|||
if err != nil { |
|||
conn.Close() |
|||
return nil, err |
|||
} |
|||
conn = cc |
|||
} |
|||
return conn, nil |
|||
} |
|||
@ -1,430 +0,0 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"bufio" |
|||
"crypto/tls" |
|||
"encoding/json" |
|||
"errors" |
|||
"flag" |
|||
"fmt" |
|||
"io/ioutil" |
|||
"net" |
|||
"net/url" |
|||
"os" |
|||
"runtime" |
|||
"strconv" |
|||
"strings" |
|||
"time" |
|||
|
|||
"github.com/ginuerzh/gost/gost" |
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
var ( |
|||
options struct { |
|||
chainNodes, serveNodes stringList |
|||
debugMode bool |
|||
} |
|||
) |
|||
|
|||
func init() { |
|||
var ( |
|||
configureFile string |
|||
printVersion bool |
|||
) |
|||
|
|||
flag.Var(&options.chainNodes, "F", "forward address, can make a forward chain") |
|||
flag.Var(&options.serveNodes, "L", "listen address, can listen on multiple ports") |
|||
flag.StringVar(&configureFile, "C", "", "configure file") |
|||
flag.BoolVar(&options.debugMode, "D", false, "enable debug log") |
|||
flag.BoolVar(&printVersion, "V", false, "print version") |
|||
flag.Parse() |
|||
|
|||
if err := loadConfigureFile(configureFile); err != nil { |
|||
log.Log(err) |
|||
os.Exit(1) |
|||
} |
|||
|
|||
if flag.NFlag() == 0 { |
|||
flag.PrintDefaults() |
|||
os.Exit(0) |
|||
} |
|||
|
|||
if printVersion { |
|||
fmt.Fprintf(os.Stderr, "gost %s (%s)\n", gost.Version, runtime.Version()) |
|||
os.Exit(0) |
|||
} |
|||
|
|||
gost.Debug = options.debugMode |
|||
} |
|||
|
|||
func main() { |
|||
chain, err := initChain() |
|||
if err != nil { |
|||
log.Log(err) |
|||
os.Exit(1) |
|||
} |
|||
if err := serve(chain); err != nil { |
|||
log.Log(err) |
|||
os.Exit(1) |
|||
} |
|||
select {} |
|||
} |
|||
|
|||
func initChain() (*gost.Chain, error) { |
|||
chain := gost.NewChain() |
|||
for _, ns := range options.chainNodes { |
|||
node, err := gost.ParseNode(ns) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
serverName, _, _ := net.SplitHostPort(node.Addr) |
|||
if serverName == "" { |
|||
serverName = "localhost" // default server name
|
|||
} |
|||
|
|||
tlsCfg := &tls.Config{ |
|||
ServerName: serverName, |
|||
InsecureSkipVerify: !toBool(node.Values.Get("scure")), |
|||
} |
|||
var tr gost.Transporter |
|||
switch node.Transport { |
|||
case "tls": |
|||
tr = gost.TLSTransporter() |
|||
case "ws": |
|||
wsOpts := &gost.WSOptions{} |
|||
wsOpts.EnableCompression = toBool(node.Values.Get("compression")) |
|||
wsOpts.ReadBufferSize, _ = strconv.Atoi(node.Values.Get("rbuf")) |
|||
wsOpts.WriteBufferSize, _ = strconv.Atoi(node.Values.Get("wbuf")) |
|||
node.HandshakeOptions = append(node.HandshakeOptions, |
|||
gost.WSOptionsHandshakeOption(wsOpts), |
|||
) |
|||
tr = gost.WSTransporter(nil) |
|||
case "wss": |
|||
tr = gost.WSSTransporter(nil) |
|||
case "kcp": |
|||
if !chain.IsEmpty() { |
|||
return nil, errors.New("KCP must be the first node in the proxy chain") |
|||
} |
|||
config, err := parseKCPConfig(node.Values.Get("c")) |
|||
if err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
node.HandshakeOptions = append(node.HandshakeOptions, |
|||
gost.KCPConfigHandshakeOption(config), |
|||
) |
|||
tr = gost.KCPTransporter(nil) |
|||
case "ssh": |
|||
if node.Protocol == "direct" || node.Protocol == "remote" { |
|||
tr = gost.SSHForwardTransporter() |
|||
} else { |
|||
tr = gost.SSHTunnelTransporter() |
|||
} |
|||
node.Chain = chain // cutoff the chain for multiplex
|
|||
chain = gost.NewChain() |
|||
case "quic": |
|||
if !chain.IsEmpty() { |
|||
return nil, errors.New("QUIC must be the first node in the proxy chain") |
|||
} |
|||
config := &gost.QUICConfig{ |
|||
TLSConfig: tlsCfg, |
|||
KeepAlive: toBool(node.Values.Get("keepalive")), |
|||
} |
|||
node.HandshakeOptions = append(node.HandshakeOptions, |
|||
gost.QUICConfigHandshakeOption(config), |
|||
) |
|||
tr = gost.QUICTransporter(nil) |
|||
case "http2": |
|||
tr = gost.HTTP2Transporter(nil) |
|||
node.Chain = chain // cutoff the chain for multiplex
|
|||
chain = gost.NewChain() |
|||
case "h2": |
|||
tr = gost.H2Transporter(nil) |
|||
case "h2c": |
|||
tr = gost.H2CTransporter() |
|||
default: |
|||
tr = gost.TCPTransporter() |
|||
} |
|||
|
|||
var connector gost.Connector |
|||
switch node.Protocol { |
|||
case "http2": |
|||
connector = gost.HTTP2Connector(nil) |
|||
case "socks", "socks5": |
|||
connector = gost.SOCKS5Connector(nil) |
|||
case "socks4": |
|||
connector = gost.SOCKS4Connector() |
|||
case "socks4a": |
|||
connector = gost.SOCKS4AConnector() |
|||
case "ss": |
|||
connector = gost.ShadowConnector(nil) |
|||
case "direct": |
|||
connector = gost.SSHDirectForwardConnector() |
|||
case "remote": |
|||
connector = gost.SSHRemoteForwardConnector() |
|||
case "http": |
|||
fallthrough |
|||
default: |
|||
node.Protocol = "http" // default protocol is HTTP
|
|||
connector = gost.HTTPConnector(nil) |
|||
} |
|||
|
|||
node.DialOptions = append(node.DialOptions, |
|||
gost.TimeoutDialOption(gost.DialTimeout), |
|||
gost.ChainDialOption(node.Chain), |
|||
) |
|||
|
|||
interval, _ := strconv.Atoi(node.Values.Get("ping")) |
|||
node.HandshakeOptions = append(node.HandshakeOptions, |
|||
gost.AddrHandshakeOption(node.Addr), |
|||
gost.UserHandshakeOption(node.User), |
|||
gost.TLSConfigHandshakeOption(tlsCfg), |
|||
gost.IntervalHandshakeOption(time.Duration(interval)*time.Second), |
|||
) |
|||
node.Client = &gost.Client{ |
|||
Connector: connector, |
|||
Transporter: tr, |
|||
} |
|||
chain.AddNode(node) |
|||
} |
|||
|
|||
return chain, nil |
|||
} |
|||
|
|||
func serve(chain *gost.Chain) error { |
|||
for _, ns := range options.serveNodes { |
|||
node, err := gost.ParseNode(ns) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
users, err := parseUsers(node.Values.Get("secrets")) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
tlsCfg, err := tlsConfig(node.Values.Get("cert"), node.Values.Get("key")) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
var ln gost.Listener |
|||
switch node.Transport { |
|||
case "tls": |
|||
ln, err = gost.TLSListener(node.Addr, tlsCfg) |
|||
case "ws": |
|||
wsOpts := &gost.WSOptions{} |
|||
wsOpts.EnableCompression = toBool(node.Values.Get("compression")) |
|||
wsOpts.ReadBufferSize, _ = strconv.Atoi(node.Values.Get("rbuf")) |
|||
wsOpts.WriteBufferSize, _ = strconv.Atoi(node.Values.Get("wbuf")) |
|||
ln, err = gost.WSListener(node.Addr, wsOpts) |
|||
case "wss": |
|||
wsOpts := &gost.WSOptions{} |
|||
wsOpts.EnableCompression = toBool(node.Values.Get("compression")) |
|||
wsOpts.ReadBufferSize, _ = strconv.Atoi(node.Values.Get("rbuf")) |
|||
wsOpts.WriteBufferSize, _ = strconv.Atoi(node.Values.Get("wbuf")) |
|||
ln, err = gost.WSSListener(node.Addr, tlsCfg, wsOpts) |
|||
case "kcp": |
|||
config, err := parseKCPConfig(node.Values.Get("c")) |
|||
if err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
ln, err = gost.KCPListener(node.Addr, config) |
|||
case "ssh": |
|||
config := &gost.SSHConfig{ |
|||
Users: users, |
|||
TLSConfig: tlsCfg, |
|||
} |
|||
if node.Protocol == "forward" { |
|||
ln, err = gost.TCPListener(node.Addr) |
|||
} else { |
|||
ln, err = gost.SSHTunnelListener(node.Addr, config) |
|||
} |
|||
case "quic": |
|||
config := &gost.QUICConfig{ |
|||
TLSConfig: tlsCfg, |
|||
KeepAlive: toBool(node.Values.Get("keepalive")), |
|||
} |
|||
timeout, _ := strconv.Atoi(node.Values.Get("timeout")) |
|||
config.Timeout = time.Duration(timeout) * time.Second |
|||
ln, err = gost.QUICListener(node.Addr, config) |
|||
case "http2": |
|||
ln, err = gost.HTTP2Listener(node.Addr, tlsCfg) |
|||
case "h2": |
|||
ln, err = gost.H2Listener(node.Addr, tlsCfg) |
|||
case "h2c": |
|||
ln, err = gost.H2CListener(node.Addr) |
|||
case "tcp": |
|||
ln, err = gost.TCPListener(node.Addr) |
|||
case "rtcp": |
|||
ln, err = gost.TCPRemoteForwardListener(node.Addr, chain) |
|||
case "udp": |
|||
ttl, _ := strconv.Atoi(node.Values.Get("ttl")) |
|||
ln, err = gost.UDPDirectForwardListener(node.Addr, time.Duration(ttl)*time.Second) |
|||
case "rudp": |
|||
ttl, _ := strconv.Atoi(node.Values.Get("ttl")) |
|||
ln, err = gost.UDPRemoteForwardListener(node.Addr, chain, time.Duration(ttl)*time.Second) |
|||
case "redirect": |
|||
ln, err = gost.TCPListener(node.Addr) |
|||
case "ssu": |
|||
ttl, _ := strconv.Atoi(node.Values.Get("ttl")) |
|||
ln, err = gost.ShadowUDPListener(node.Addr, node.User, time.Duration(ttl)*time.Second) |
|||
default: |
|||
ln, err = gost.TCPListener(node.Addr) |
|||
} |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
var whitelist, blacklist *gost.Permissions |
|||
if node.Values.Get("whitelist") != "" { |
|||
if whitelist, err = gost.ParsePermissions(node.Values.Get("whitelist")); err != nil { |
|||
return err |
|||
} |
|||
} else { |
|||
// By default allow for everyting
|
|||
whitelist, _ = gost.ParsePermissions("*:*:*") |
|||
} |
|||
|
|||
if node.Values.Get("blacklist") != "" { |
|||
if blacklist, err = gost.ParsePermissions(node.Values.Get("blacklist")); err != nil { |
|||
return err |
|||
} |
|||
} else { |
|||
// By default block nothing
|
|||
blacklist, _ = gost.ParsePermissions("") |
|||
} |
|||
|
|||
var handlerOptions []gost.HandlerOption |
|||
|
|||
handlerOptions = append(handlerOptions, |
|||
gost.AddrHandlerOption(node.Addr), |
|||
gost.ChainHandlerOption(chain), |
|||
gost.UsersHandlerOption(users...), |
|||
gost.TLSConfigHandlerOption(tlsCfg), |
|||
gost.WhitelistHandlerOption(whitelist), |
|||
gost.BlacklistHandlerOption(blacklist), |
|||
) |
|||
var handler gost.Handler |
|||
switch node.Protocol { |
|||
case "http2": |
|||
handler = gost.HTTP2Handler(handlerOptions...) |
|||
case "socks", "socks5": |
|||
handler = gost.SOCKS5Handler(handlerOptions...) |
|||
case "socks4", "socks4a": |
|||
handler = gost.SOCKS4Handler(handlerOptions...) |
|||
case "ss": |
|||
handler = gost.ShadowHandler(handlerOptions...) |
|||
case "http": |
|||
handler = gost.HTTPHandler(handlerOptions...) |
|||
case "tcp": |
|||
handler = gost.TCPDirectForwardHandler(node.Remote, handlerOptions...) |
|||
case "rtcp": |
|||
handler = gost.TCPRemoteForwardHandler(node.Remote, handlerOptions...) |
|||
case "udp": |
|||
handler = gost.UDPDirectForwardHandler(node.Remote, handlerOptions...) |
|||
case "rudp": |
|||
handler = gost.UDPRemoteForwardHandler(node.Remote, handlerOptions...) |
|||
case "forward": |
|||
handler = gost.SSHForwardHandler(handlerOptions...) |
|||
case "redirect": |
|||
handler = gost.TCPRedirectHandler(handlerOptions...) |
|||
case "ssu": |
|||
handler = gost.ShadowUDPdHandler(handlerOptions...) |
|||
default: |
|||
// TODO: auto poroxy handler
|
|||
handler = gost.HTTPHandler(handlerOptions...) |
|||
} |
|||
go new(gost.Server).Serve(ln, handler) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
// Load the certificate from cert and key files, will use the default certificate if the provided info are invalid.
|
|||
func tlsConfig(certFile, keyFile string) (*tls.Config, error) { |
|||
if certFile == "" || keyFile == "" { |
|||
return nil, nil |
|||
} |
|||
cert, err := tls.LoadX509KeyPair(certFile, keyFile) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil |
|||
} |
|||
|
|||
func loadConfigureFile(configureFile string) error { |
|||
if configureFile == "" { |
|||
return nil |
|||
} |
|||
content, err := ioutil.ReadFile(configureFile) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
if err := json.Unmarshal(content, &options); err != nil { |
|||
return err |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
type stringList []string |
|||
|
|||
func (l *stringList) String() string { |
|||
return fmt.Sprintf("%s", *l) |
|||
} |
|||
func (l *stringList) Set(value string) error { |
|||
*l = append(*l, value) |
|||
return nil |
|||
} |
|||
|
|||
func toBool(s string) bool { |
|||
if b, _ := strconv.ParseBool(s); b { |
|||
return b |
|||
} |
|||
n, _ := strconv.Atoi(s) |
|||
return n > 0 |
|||
} |
|||
|
|||
func parseKCPConfig(configFile string) (*gost.KCPConfig, error) { |
|||
if configFile == "" { |
|||
return nil, nil |
|||
} |
|||
file, err := os.Open(configFile) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
defer file.Close() |
|||
|
|||
config := &gost.KCPConfig{} |
|||
if err = json.NewDecoder(file).Decode(config); err != nil { |
|||
return nil, err |
|||
} |
|||
return config, nil |
|||
} |
|||
|
|||
func parseUsers(authFile string) (users []*url.Userinfo, err error) { |
|||
if authFile == "" { |
|||
return |
|||
} |
|||
|
|||
file, err := os.Open(authFile) |
|||
if err != nil { |
|||
return |
|||
} |
|||
scanner := bufio.NewScanner(file) |
|||
for scanner.Scan() { |
|||
line := strings.TrimSpace(scanner.Text()) |
|||
if line == "" || strings.HasPrefix(line, "#") { |
|||
continue |
|||
} |
|||
|
|||
s := strings.SplitN(line, " ", 2) |
|||
if len(s) == 1 { |
|||
users = append(users, url.User(strings.TrimSpace(s[0]))) |
|||
} else if len(s) == 2 { |
|||
users = append(users, url.UserPassword(strings.TrimSpace(s[0]), strings.TrimSpace(s[1]))) |
|||
} |
|||
} |
|||
|
|||
err = scanner.Err() |
|||
return |
|||
} |
|||
@ -1,663 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"errors" |
|||
"net" |
|||
"sync" |
|||
"time" |
|||
|
|||
"fmt" |
|||
|
|||
"github.com/ginuerzh/gosocks5" |
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
type tcpDirectForwardHandler struct { |
|||
raddr string |
|||
options *HandlerOptions |
|||
} |
|||
|
|||
// TCPDirectForwardHandler creates a server Handler for TCP port forwarding server.
|
|||
// The raddr is the remote address that the server will forward to.
|
|||
func TCPDirectForwardHandler(raddr string, opts ...HandlerOption) Handler { |
|||
h := &tcpDirectForwardHandler{ |
|||
raddr: raddr, |
|||
options: &HandlerOptions{}, |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *tcpDirectForwardHandler) Handle(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
log.Logf("[tcp] %s - %s", conn.RemoteAddr(), h.raddr) |
|||
cc, err := h.options.Chain.Dial(h.raddr) |
|||
if err != nil { |
|||
log.Logf("[tcp] %s -> %s : %s", conn.RemoteAddr(), h.raddr, err) |
|||
return |
|||
} |
|||
defer cc.Close() |
|||
|
|||
log.Logf("[tcp] %s <-> %s", conn.RemoteAddr(), h.raddr) |
|||
transport(conn, cc) |
|||
log.Logf("[tcp] %s >-< %s", conn.RemoteAddr(), h.raddr) |
|||
} |
|||
|
|||
type udpDirectForwardHandler struct { |
|||
raddr string |
|||
options *HandlerOptions |
|||
} |
|||
|
|||
// UDPDirectForwardHandler creates a server Handler for UDP port forwarding server.
|
|||
// The raddr is the remote address that the server will forward to.
|
|||
func UDPDirectForwardHandler(raddr string, opts ...HandlerOption) Handler { |
|||
h := &udpDirectForwardHandler{ |
|||
raddr: raddr, |
|||
options: &HandlerOptions{}, |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *udpDirectForwardHandler) Handle(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
var cc net.Conn |
|||
if h.options.Chain.IsEmpty() { |
|||
raddr, err := net.ResolveUDPAddr("udp", h.raddr) |
|||
if err != nil { |
|||
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), h.raddr, err) |
|||
return |
|||
} |
|||
cc, err = net.DialUDP("udp", nil, raddr) |
|||
if err != nil { |
|||
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), h.raddr, err) |
|||
return |
|||
} |
|||
} else { |
|||
var err error |
|||
cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil) |
|||
if err != nil { |
|||
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), h.raddr, err) |
|||
return |
|||
} |
|||
cc = &udpTunnelConn{Conn: cc, raddr: h.raddr} |
|||
} |
|||
|
|||
defer cc.Close() |
|||
|
|||
log.Logf("[udp] %s <-> %s", conn.RemoteAddr(), h.raddr) |
|||
transport(conn, cc) |
|||
log.Logf("[udp] %s >-< %s", conn.RemoteAddr(), h.raddr) |
|||
} |
|||
|
|||
type tcpRemoteForwardHandler struct { |
|||
raddr string |
|||
options *HandlerOptions |
|||
} |
|||
|
|||
// TCPRemoteForwardHandler creates a server Handler for TCP remote port forwarding server.
|
|||
// The raddr is the remote address that the server will forward to.
|
|||
func TCPRemoteForwardHandler(raddr string, opts ...HandlerOption) Handler { |
|||
h := &tcpRemoteForwardHandler{ |
|||
raddr: raddr, |
|||
options: &HandlerOptions{}, |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *tcpRemoteForwardHandler) Handle(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
cc, err := net.DialTimeout("tcp", h.raddr, DialTimeout) |
|||
if err != nil { |
|||
log.Logf("[rtcp] %s -> %s : %s", conn.LocalAddr(), h.raddr, err) |
|||
return |
|||
} |
|||
defer cc.Close() |
|||
|
|||
log.Logf("[rtcp] %s <-> %s", conn.LocalAddr(), h.raddr) |
|||
transport(cc, conn) |
|||
log.Logf("[rtcp] %s >-< %s", conn.LocalAddr(), h.raddr) |
|||
} |
|||
|
|||
type udpRemoteForwardHandler struct { |
|||
raddr string |
|||
options *HandlerOptions |
|||
} |
|||
|
|||
// UDPRemoteForwardHandler creates a server Handler for UDP remote port forwarding server.
|
|||
// The raddr is the remote address that the server will forward to.
|
|||
func UDPRemoteForwardHandler(raddr string, opts ...HandlerOption) Handler { |
|||
h := &udpRemoteForwardHandler{ |
|||
raddr: raddr, |
|||
options: &HandlerOptions{}, |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *udpRemoteForwardHandler) Handle(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
raddr, err := net.ResolveUDPAddr("udp", h.raddr) |
|||
if err != nil { |
|||
log.Logf("[rudp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err) |
|||
return |
|||
} |
|||
cc, err := net.DialUDP("udp", nil, raddr) |
|||
if err != nil { |
|||
log.Logf("[rudp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err) |
|||
return |
|||
} |
|||
|
|||
log.Logf("[rudp] %s <-> %s", conn.RemoteAddr(), h.raddr) |
|||
transport(conn, cc) |
|||
log.Logf("[rudp] %s >-< %s", conn.RemoteAddr(), h.raddr) |
|||
} |
|||
|
|||
type udpDirectForwardListener struct { |
|||
ln net.PacketConn |
|||
conns map[string]*udpServerConn |
|||
connChan chan net.Conn |
|||
errChan chan error |
|||
ttl time.Duration |
|||
} |
|||
|
|||
// UDPDirectForwardListener creates a Listener for UDP port forwarding server.
|
|||
func UDPDirectForwardListener(addr string, ttl time.Duration) (Listener, error) { |
|||
laddr, err := net.ResolveUDPAddr("udp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
ln, err := net.ListenUDP("udp", laddr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
l := &udpDirectForwardListener{ |
|||
ln: ln, |
|||
conns: make(map[string]*udpServerConn), |
|||
connChan: make(chan net.Conn, 1024), |
|||
errChan: make(chan error, 1), |
|||
ttl: ttl, |
|||
} |
|||
go l.listenLoop() |
|||
return l, nil |
|||
} |
|||
|
|||
func (l *udpDirectForwardListener) listenLoop() { |
|||
for { |
|||
b := make([]byte, mediumBufferSize) |
|||
n, raddr, err := l.ln.ReadFrom(b) |
|||
if err != nil { |
|||
log.Logf("[udp] peer -> %s : %s", l.Addr(), err) |
|||
l.ln.Close() |
|||
l.errChan <- err |
|||
close(l.errChan) |
|||
return |
|||
} |
|||
if Debug { |
|||
log.Logf("[udp] %s >>> %s : length %d", raddr, l.Addr(), n) |
|||
} |
|||
conn, ok := l.conns[raddr.String()] |
|||
if !ok || conn.Closed() { |
|||
conn = newUDPServerConn(l.ln, raddr, l.ttl) |
|||
l.conns[raddr.String()] = conn |
|||
|
|||
select { |
|||
case l.connChan <- conn: |
|||
default: |
|||
conn.Close() |
|||
log.Logf("[udp] %s - %s: connection queue is full", raddr, l.Addr()) |
|||
} |
|||
} |
|||
|
|||
select { |
|||
case conn.rChan <- b[:n]: |
|||
default: |
|||
log.Logf("[udp] %s -> %s : read queue is full", raddr, l.Addr()) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (l *udpDirectForwardListener) 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 *udpDirectForwardListener) Addr() net.Addr { |
|||
return l.ln.LocalAddr() |
|||
} |
|||
|
|||
func (l *udpDirectForwardListener) Close() error { |
|||
return l.ln.Close() |
|||
} |
|||
|
|||
type udpServerConn struct { |
|||
conn net.PacketConn |
|||
raddr net.Addr |
|||
rChan, wChan chan []byte |
|||
closed chan struct{} |
|||
brokenChan chan struct{} |
|||
closeMutex sync.Mutex |
|||
ttl time.Duration |
|||
nopChan chan int |
|||
} |
|||
|
|||
func newUDPServerConn(conn net.PacketConn, raddr net.Addr, ttl time.Duration) *udpServerConn { |
|||
c := &udpServerConn{ |
|||
conn: conn, |
|||
raddr: raddr, |
|||
rChan: make(chan []byte, 128), |
|||
wChan: make(chan []byte, 128), |
|||
closed: make(chan struct{}), |
|||
brokenChan: make(chan struct{}), |
|||
nopChan: make(chan int), |
|||
ttl: ttl, |
|||
} |
|||
go c.writeLoop() |
|||
go c.ttlWait() |
|||
return c |
|||
} |
|||
|
|||
func (c *udpServerConn) Read(b []byte) (n int, err error) { |
|||
select { |
|||
case bb := <-c.rChan: |
|||
n = copy(b, bb) |
|||
if n != len(bb) { |
|||
err = errors.New("read partial data") |
|||
return |
|||
} |
|||
case <-c.brokenChan: |
|||
err = errors.New("Broken pipe") |
|||
case <-c.closed: |
|||
err = errors.New("read from closed connection") |
|||
return |
|||
} |
|||
|
|||
select { |
|||
case c.nopChan <- n: |
|||
default: |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (c *udpServerConn) Write(b []byte) (n int, err error) { |
|||
if len(b) == 0 { |
|||
return 0, nil |
|||
} |
|||
select { |
|||
case c.wChan <- b: |
|||
n = len(b) |
|||
case <-c.brokenChan: |
|||
err = errors.New("Broken pipe") |
|||
case <-c.closed: |
|||
err = errors.New("write to closed connection") |
|||
return |
|||
} |
|||
|
|||
select { |
|||
case c.nopChan <- n: |
|||
default: |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
func (c *udpServerConn) Close() error { |
|||
c.closeMutex.Lock() |
|||
defer c.closeMutex.Unlock() |
|||
|
|||
select { |
|||
case <-c.closed: |
|||
return errors.New("connection is closed") |
|||
default: |
|||
close(c.closed) |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
func (c *udpServerConn) Closed() bool { |
|||
select { |
|||
case <-c.closed: |
|||
return true |
|||
default: |
|||
return false |
|||
} |
|||
} |
|||
|
|||
func (c *udpServerConn) writeLoop() { |
|||
for { |
|||
select { |
|||
case b, ok := <-c.wChan: |
|||
if !ok { |
|||
return |
|||
} |
|||
n, err := c.conn.WriteTo(b, c.raddr) |
|||
if err != nil { |
|||
log.Logf("[udp] %s - %s : %s", c.RemoteAddr(), c.LocalAddr(), err) |
|||
return |
|||
} |
|||
if Debug { |
|||
log.Logf("[udp] %s <<< %s : length %d", c.RemoteAddr(), c.LocalAddr(), n) |
|||
} |
|||
case <-c.brokenChan: |
|||
return |
|||
case <-c.closed: |
|||
return |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (c *udpServerConn) ttlWait() { |
|||
ttl := c.ttl |
|||
if ttl == 0 { |
|||
ttl = defaultTTL |
|||
} |
|||
timer := time.NewTimer(ttl) |
|||
|
|||
for { |
|||
select { |
|||
case <-c.nopChan: |
|||
timer.Reset(ttl) |
|||
case <-timer.C: |
|||
close(c.brokenChan) |
|||
return |
|||
case <-c.closed: |
|||
return |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (c *udpServerConn) LocalAddr() net.Addr { |
|||
return c.conn.LocalAddr() |
|||
} |
|||
|
|||
func (c *udpServerConn) RemoteAddr() net.Addr { |
|||
return c.raddr |
|||
} |
|||
|
|||
func (c *udpServerConn) SetDeadline(t time.Time) error { |
|||
return nil |
|||
} |
|||
|
|||
func (c *udpServerConn) SetReadDeadline(t time.Time) error { |
|||
return nil |
|||
} |
|||
|
|||
func (c *udpServerConn) SetWriteDeadline(t time.Time) error { |
|||
return nil |
|||
} |
|||
|
|||
type tcpRemoteForwardListener struct { |
|||
addr net.Addr |
|||
chain *Chain |
|||
ln net.Listener |
|||
closed chan struct{} |
|||
} |
|||
|
|||
// TCPRemoteForwardListener creates a Listener for TCP remote port forwarding server.
|
|||
func TCPRemoteForwardListener(addr string, chain *Chain) (Listener, error) { |
|||
laddr, err := net.ResolveTCPAddr("tcp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return &tcpRemoteForwardListener{ |
|||
addr: laddr, |
|||
chain: chain, |
|||
closed: make(chan struct{}), |
|||
}, nil |
|||
} |
|||
|
|||
func (l *tcpRemoteForwardListener) Accept() (net.Conn, error) { |
|||
select { |
|||
case <-l.closed: |
|||
return nil, errors.New("closed") |
|||
default: |
|||
} |
|||
|
|||
var tempDelay time.Duration |
|||
for { |
|||
conn, err := l.accept() |
|||
if err != nil { |
|||
if tempDelay == 0 { |
|||
tempDelay = 1000 * time.Millisecond |
|||
} else { |
|||
tempDelay *= 2 |
|||
} |
|||
if max := 6 * time.Second; tempDelay > max { |
|||
tempDelay = max |
|||
} |
|||
log.Logf("[rtcp] Accept error: %v; retrying in %v", err, tempDelay) |
|||
time.Sleep(tempDelay) |
|||
continue |
|||
} |
|||
return conn, nil |
|||
} |
|||
} |
|||
|
|||
func (l *tcpRemoteForwardListener) accept() (conn net.Conn, err error) { |
|||
lastNode := l.chain.LastNode() |
|||
if lastNode.Protocol == "remote" && lastNode.Transport == "ssh" { |
|||
conn, err = l.chain.Dial(l.addr.String()) |
|||
} else if lastNode.Protocol == "socks5" { |
|||
cc, er := l.chain.Conn() |
|||
if er != nil { |
|||
return nil, er |
|||
} |
|||
conn, err = l.waitConnectSOCKS5(cc) |
|||
if err != nil { |
|||
cc.Close() |
|||
} |
|||
} else { |
|||
if l.ln == nil { |
|||
l.ln, err = net.Listen("tcp", l.addr.String()) |
|||
if err != nil { |
|||
return |
|||
} |
|||
} |
|||
conn, err = l.ln.Accept() |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (l *tcpRemoteForwardListener) waitConnectSOCKS5(conn net.Conn) (net.Conn, error) { |
|||
conn, err := socks5Handshake(conn, l.chain.LastNode().User) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
req := gosocks5.NewRequest(gosocks5.CmdBind, toSocksAddr(l.addr)) |
|||
if err := req.Write(conn); err != nil { |
|||
log.Log("[rtcp] SOCKS5 BIND request: ", err) |
|||
return nil, err |
|||
} |
|||
|
|||
// first reply, bind status
|
|||
conn.SetReadDeadline(time.Now().Add(ReadTimeout)) |
|||
rep, err := gosocks5.ReadReply(conn) |
|||
if err != nil { |
|||
log.Log("[rtcp] SOCKS5 BIND reply: ", err) |
|||
return nil, err |
|||
} |
|||
conn.SetReadDeadline(time.Time{}) |
|||
if rep.Rep != gosocks5.Succeeded { |
|||
log.Logf("[rtcp] bind on %s failure", l.addr) |
|||
return nil, fmt.Errorf("Bind on %s failure", l.addr.String()) |
|||
} |
|||
log.Logf("[rtcp] BIND ON %s OK", rep.Addr) |
|||
|
|||
// second reply, peer connected
|
|||
rep, err = gosocks5.ReadReply(conn) |
|||
if err != nil { |
|||
log.Log("[rtcp]", err) |
|||
return nil, err |
|||
} |
|||
if rep.Rep != gosocks5.Succeeded { |
|||
log.Logf("[rtcp] peer connect failure: %d", rep.Rep) |
|||
return nil, errors.New("peer connect failure") |
|||
} |
|||
|
|||
log.Logf("[rtcp] PEER %s CONNECTED", rep.Addr) |
|||
return conn, nil |
|||
} |
|||
|
|||
func (l *tcpRemoteForwardListener) Addr() net.Addr { |
|||
return l.addr |
|||
} |
|||
|
|||
func (l *tcpRemoteForwardListener) Close() error { |
|||
close(l.closed) |
|||
return nil |
|||
} |
|||
|
|||
type udpRemoteForwardListener struct { |
|||
addr *net.UDPAddr |
|||
chain *Chain |
|||
conns map[string]*udpServerConn |
|||
connChan chan net.Conn |
|||
errChan chan error |
|||
ttl time.Duration |
|||
closed chan struct{} |
|||
} |
|||
|
|||
// UDPRemoteForwardListener creates a Listener for UDP remote port forwarding server.
|
|||
func UDPRemoteForwardListener(addr string, chain *Chain, ttl time.Duration) (Listener, error) { |
|||
laddr, err := net.ResolveUDPAddr("udp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
ln := &udpRemoteForwardListener{ |
|||
addr: laddr, |
|||
chain: chain, |
|||
conns: make(map[string]*udpServerConn), |
|||
connChan: make(chan net.Conn, 1024), |
|||
errChan: make(chan error, 1), |
|||
ttl: ttl, |
|||
closed: make(chan struct{}), |
|||
} |
|||
go ln.listenLoop() |
|||
return ln, nil |
|||
} |
|||
|
|||
func (l *udpRemoteForwardListener) listenLoop() { |
|||
for { |
|||
conn, err := l.connect() |
|||
if err != nil { |
|||
log.Logf("[rudp] %s : %s", l.Addr(), err) |
|||
return |
|||
} |
|||
|
|||
defer conn.Close() |
|||
|
|||
for { |
|||
b := make([]byte, mediumBufferSize) |
|||
n, raddr, err := conn.ReadFrom(b) |
|||
if err != nil { |
|||
log.Logf("[rudp] %s : %s", l.Addr(), err) |
|||
break |
|||
} |
|||
if Debug { |
|||
log.Logf("[udp] %s >>> %s : length %d", raddr, l.Addr(), n) |
|||
} |
|||
uc, ok := l.conns[raddr.String()] |
|||
if !ok || uc.Closed() { |
|||
uc = newUDPServerConn(conn, raddr, l.ttl) |
|||
l.conns[raddr.String()] = uc |
|||
|
|||
select { |
|||
case l.connChan <- uc: |
|||
default: |
|||
uc.Close() |
|||
log.Logf("[rudp] %s - %s: connection queue is full", raddr, l.Addr()) |
|||
} |
|||
} |
|||
|
|||
select { |
|||
case uc.rChan <- b[:n]: |
|||
default: |
|||
log.Logf("[rudp] %s -> %s : write queue is full", raddr, l.Addr()) |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
func (l *udpRemoteForwardListener) connect() (conn net.PacketConn, err error) { |
|||
var tempDelay time.Duration |
|||
|
|||
for { |
|||
select { |
|||
case <-l.closed: |
|||
return nil, errors.New("closed") |
|||
default: |
|||
} |
|||
|
|||
lastNode := l.chain.LastNode() |
|||
if lastNode.Protocol == "socks5" { |
|||
var cc net.Conn |
|||
cc, err = getSOCKS5UDPTunnel(l.chain, l.addr) |
|||
if err != nil { |
|||
log.Logf("[rudp] %s : %s", l.Addr(), err) |
|||
} else { |
|||
conn = &udpTunnelConn{Conn: cc} |
|||
} |
|||
} else { |
|||
conn, err = net.ListenUDP("udp", l.addr) |
|||
} |
|||
|
|||
if err != nil { |
|||
if tempDelay == 0 { |
|||
tempDelay = 1000 * time.Millisecond |
|||
} else { |
|||
tempDelay *= 2 |
|||
} |
|||
if max := 6 * time.Second; tempDelay > max { |
|||
tempDelay = max |
|||
} |
|||
log.Logf("[rudp] Accept error: %v; retrying in %v", err, tempDelay) |
|||
time.Sleep(tempDelay) |
|||
continue |
|||
} |
|||
return |
|||
} |
|||
} |
|||
|
|||
func (l *udpRemoteForwardListener) 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 *udpRemoteForwardListener) Addr() net.Addr { |
|||
return l.addr |
|||
} |
|||
|
|||
func (l *udpRemoteForwardListener) Close() error { |
|||
close(l.closed) |
|||
return nil |
|||
} |
|||
@ -1,108 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"crypto/rand" |
|||
"crypto/rsa" |
|||
"crypto/x509" |
|||
"crypto/x509/pkix" |
|||
"encoding/pem" |
|||
"math/big" |
|||
"time" |
|||
|
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
// Version is the gost version.
|
|||
const Version = "2.4-dev20170803" |
|||
|
|||
// Debug is a flag that enables the debug log.
|
|||
var Debug bool |
|||
|
|||
var ( |
|||
tinyBufferSize = 128 |
|||
smallBufferSize = 1 * 1024 // 1KB small buffer
|
|||
mediumBufferSize = 8 * 1024 // 8KB medium buffer
|
|||
largeBufferSize = 32 * 1024 // 32KB large buffer
|
|||
) |
|||
|
|||
var ( |
|||
// KeepAliveTime is the keep alive time period for TCP connection.
|
|||
KeepAliveTime = 180 * time.Second |
|||
// DialTimeout is the timeout of dial.
|
|||
DialTimeout = 30 * time.Second |
|||
// ReadTimeout is the timeout for reading.
|
|||
ReadTimeout = 30 * time.Second |
|||
// WriteTimeout is the timeout for writing.
|
|||
WriteTimeout = 60 * time.Second |
|||
// PingTimeout is the timeout for pinging.
|
|||
PingTimeout = 30 * time.Second |
|||
// PingRetries is the reties of ping.
|
|||
PingRetries = 3 |
|||
// default udp node TTL in second for udp port forwarding.
|
|||
defaultTTL = 60 * time.Second |
|||
) |
|||
|
|||
var ( |
|||
defaultRawCert []byte |
|||
defaultRawKey []byte |
|||
) |
|||
|
|||
func init() { |
|||
rawCert, rawKey, err := generateKeyPair() |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
defaultRawCert, defaultRawKey = rawCert, rawKey |
|||
|
|||
log.DefaultLogger = &LogLogger{} |
|||
} |
|||
|
|||
func SetLogger(logger log.Logger) { |
|||
log.DefaultLogger = logger |
|||
} |
|||
|
|||
func generateKeyPair() (rawCert, rawKey []byte, err error) { |
|||
if defaultRawCert != nil && defaultRawKey != nil { |
|||
return defaultRawCert, defaultRawKey, nil |
|||
} |
|||
|
|||
// Create private key and self-signed certificate
|
|||
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go
|
|||
|
|||
priv, err := rsa.GenerateKey(rand.Reader, 2048) |
|||
if err != nil { |
|||
return |
|||
} |
|||
validFor := time.Hour * 24 * 365 * 10 // ten years
|
|||
notBefore := time.Now() |
|||
notAfter := notBefore.Add(validFor) |
|||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) |
|||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) |
|||
template := x509.Certificate{ |
|||
SerialNumber: serialNumber, |
|||
Subject: pkix.Name{ |
|||
Organization: []string{"gost"}, |
|||
}, |
|||
NotBefore: notBefore, |
|||
NotAfter: notAfter, |
|||
|
|||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, |
|||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, |
|||
BasicConstraintsValid: true, |
|||
} |
|||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) |
|||
rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) |
|||
|
|||
return |
|||
} |
|||
|
|||
// SetDefaultCertificate replaces the default certificate by your own
|
|||
func SetDefaultCertificate(rawCert, rawKey []byte) { |
|||
defaultRawCert = rawCert |
|||
defaultRawKey = rawKey |
|||
} |
|||
@ -1,67 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"crypto/tls" |
|||
"net" |
|||
"net/url" |
|||
) |
|||
|
|||
// Handler is a proxy server handler
|
|||
type Handler interface { |
|||
Handle(net.Conn) |
|||
} |
|||
|
|||
// HandlerOptions describes the options for Handler.
|
|||
type HandlerOptions struct { |
|||
Addr string |
|||
Chain *Chain |
|||
Users []*url.Userinfo |
|||
TLSConfig *tls.Config |
|||
Whitelist *Permissions |
|||
Blacklist *Permissions |
|||
} |
|||
|
|||
// HandlerOption allows a common way to set handler options.
|
|||
type HandlerOption func(opts *HandlerOptions) |
|||
|
|||
// AddrHandlerOption sets the Addr option of HandlerOptions.
|
|||
func AddrHandlerOption(addr string) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Addr = addr |
|||
} |
|||
} |
|||
|
|||
// ChainHandlerOption sets the Chain option of HandlerOptions.
|
|||
func ChainHandlerOption(chain *Chain) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Chain = chain |
|||
} |
|||
} |
|||
|
|||
// UsersHandlerOption sets the Users option of HandlerOptions.
|
|||
func UsersHandlerOption(users ...*url.Userinfo) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Users = users |
|||
} |
|||
} |
|||
|
|||
// TLSConfigHandlerOption sets the TLSConfig option of HandlerOptions.
|
|||
func TLSConfigHandlerOption(config *tls.Config) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.TLSConfig = config |
|||
} |
|||
} |
|||
|
|||
// WhitelistHandlerOption sets the Whitelist option of HandlerOptions.
|
|||
func WhitelistHandlerOption(whitelist *Permissions) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Whitelist = whitelist |
|||
} |
|||
} |
|||
|
|||
// BlacklistHandlerOption sets the Blacklist option of HandlerOptions.
|
|||
func BlacklistHandlerOption(blacklist *Permissions) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Blacklist = blacklist |
|||
} |
|||
} |
|||
@ -1,259 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"bufio" |
|||
"encoding/base64" |
|||
"fmt" |
|||
"net" |
|||
"net/http" |
|||
"net/http/httputil" |
|||
"net/url" |
|||
"strings" |
|||
"time" |
|||
|
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
type httpConnector struct { |
|||
User *url.Userinfo |
|||
} |
|||
|
|||
// HTTPConnector creates a Connector for HTTP proxy client.
|
|||
// It accepts an optional auth info for HTTP Basic Authentication.
|
|||
func HTTPConnector(user *url.Userinfo) Connector { |
|||
return &httpConnector{User: user} |
|||
} |
|||
|
|||
func (c *httpConnector) Connect(conn net.Conn, addr string) (net.Conn, error) { |
|||
req := &http.Request{ |
|||
Method: http.MethodConnect, |
|||
URL: &url.URL{Host: addr}, |
|||
Host: addr, |
|||
ProtoMajor: 1, |
|||
ProtoMinor: 1, |
|||
Header: make(http.Header), |
|||
} |
|||
req.Header.Set("Proxy-Connection", "keep-alive") |
|||
|
|||
if c.User != nil { |
|||
s := c.User.String() |
|||
if _, set := c.User.Password(); !set { |
|||
s += ":" |
|||
} |
|||
req.Header.Set("Proxy-Authorization", |
|||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s))) |
|||
} |
|||
|
|||
if err := req.Write(conn); err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
if Debug { |
|||
dump, _ := httputil.DumpRequest(req, false) |
|||
log.Log(string(dump)) |
|||
} |
|||
|
|||
resp, err := http.ReadResponse(bufio.NewReader(conn), req) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
if Debug { |
|||
dump, _ := httputil.DumpResponse(resp, false) |
|||
log.Log(string(dump)) |
|||
} |
|||
|
|||
if resp.StatusCode != http.StatusOK { |
|||
return nil, fmt.Errorf("%s", resp.Status) |
|||
} |
|||
|
|||
return conn, nil |
|||
} |
|||
|
|||
type httpHandler struct { |
|||
options *HandlerOptions |
|||
} |
|||
|
|||
// HTTPHandler creates a server Handler for HTTP proxy server.
|
|||
func HTTPHandler(opts ...HandlerOption) Handler { |
|||
h := &httpHandler{ |
|||
options: &HandlerOptions{}, |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *httpHandler) Handle(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
req, err := http.ReadRequest(bufio.NewReader(conn)) |
|||
if err != nil { |
|||
log.Logf("[http] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) |
|||
return |
|||
} |
|||
|
|||
if Debug { |
|||
log.Logf("[http] %s %s - %s %s", req.Method, conn.RemoteAddr(), req.Host, req.Proto) |
|||
dump, _ := httputil.DumpRequest(req, false) |
|||
log.Logf(string(dump)) |
|||
} |
|||
|
|||
if req.Method == "PRI" && req.ProtoMajor == 2 { |
|||
log.Logf("[http] %s <- %s : Not an HTTP2 server", conn.RemoteAddr(), req.Host) |
|||
resp := "HTTP/1.1 400 Bad Request\r\n" + |
|||
"Proxy-Agent: gost/" + Version + "\r\n\r\n" |
|||
conn.Write([]byte(resp)) |
|||
return |
|||
} |
|||
|
|||
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) |
|||
if !authenticate(u, p, h.options.Users...) { |
|||
log.Logf("[http] %s <- %s : proxy authentication required", conn.RemoteAddr(), req.Host) |
|||
resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" + |
|||
"Proxy-Authenticate: Basic realm=\"gost\"\r\n" + |
|||
"Proxy-Agent: gost/" + Version + "\r\n\r\n" |
|||
conn.Write([]byte(resp)) |
|||
return |
|||
} |
|||
|
|||
req.Header.Del("Proxy-Authorization") |
|||
req.Header.Del("Proxy-Connection") |
|||
|
|||
if !Can("tcp", req.Host, h.options.Whitelist, h.options.Blacklist) { |
|||
log.Logf("[http] Unauthorized to tcp connect to %s", req.Host) |
|||
b := []byte("HTTP/1.1 403 Forbidden\r\n" + |
|||
"Proxy-Agent: gost/" + Version + "\r\n\r\n") |
|||
conn.Write(b) |
|||
if Debug { |
|||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b)) |
|||
} |
|||
return |
|||
} |
|||
|
|||
// forward http request
|
|||
lastNode := h.options.Chain.LastNode() |
|||
if req.Method != http.MethodConnect && lastNode.Protocol == "http" { |
|||
h.forwardRequest(conn, req) |
|||
return |
|||
} |
|||
|
|||
host := req.Host |
|||
if !strings.Contains(req.Host, ":") { |
|||
host += ":80" |
|||
} |
|||
cc, err := h.options.Chain.Dial(host) |
|||
if err != nil { |
|||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err) |
|||
|
|||
b := []byte("HTTP/1.1 503 Service unavailable\r\n" + |
|||
"Proxy-Agent: gost/" + Version + "\r\n\r\n") |
|||
if Debug { |
|||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b)) |
|||
} |
|||
conn.Write(b) |
|||
return |
|||
} |
|||
defer cc.Close() |
|||
|
|||
if req.Method == http.MethodConnect { |
|||
b := []byte("HTTP/1.1 200 Connection established\r\n" + |
|||
"Proxy-Agent: gost/" + Version + "\r\n\r\n") |
|||
if Debug { |
|||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b)) |
|||
} |
|||
conn.Write(b) |
|||
} else { |
|||
req.Header.Del("Proxy-Connection") |
|||
|
|||
if err = req.Write(cc); err != nil { |
|||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err) |
|||
return |
|||
} |
|||
} |
|||
|
|||
log.Logf("[http] %s <-> %s", conn.RemoteAddr(), req.Host) |
|||
transport(conn, cc) |
|||
log.Logf("[http] %s >-< %s", conn.RemoteAddr(), req.Host) |
|||
} |
|||
|
|||
func (h *httpHandler) forwardRequest(conn net.Conn, req *http.Request) { |
|||
if h.options.Chain.IsEmpty() { |
|||
return |
|||
} |
|||
lastNode := h.options.Chain.LastNode() |
|||
|
|||
cc, err := h.options.Chain.Conn() |
|||
if err != nil { |
|||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), lastNode.Addr, err) |
|||
|
|||
b := []byte("HTTP/1.1 503 Service unavailable\r\n" + |
|||
"Proxy-Agent: gost/" + Version + "\r\n\r\n") |
|||
if Debug { |
|||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), lastNode.Addr, string(b)) |
|||
} |
|||
conn.Write(b) |
|||
return |
|||
} |
|||
defer cc.Close() |
|||
|
|||
if lastNode.User != nil { |
|||
s := lastNode.User.String() |
|||
if _, set := lastNode.User.Password(); !set { |
|||
s += ":" |
|||
} |
|||
req.Header.Set("Proxy-Authorization", |
|||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s))) |
|||
} |
|||
|
|||
cc.SetWriteDeadline(time.Now().Add(WriteTimeout)) |
|||
if err = req.WriteProxy(cc); err != nil { |
|||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err) |
|||
return |
|||
} |
|||
cc.SetWriteDeadline(time.Time{}) |
|||
|
|||
log.Logf("[http] %s <-> %s", conn.RemoteAddr(), req.Host) |
|||
transport(conn, cc) |
|||
log.Logf("[http] %s >-< %s", conn.RemoteAddr(), req.Host) |
|||
return |
|||
} |
|||
|
|||
func basicProxyAuth(proxyAuth string) (username, password string, ok bool) { |
|||
if proxyAuth == "" { |
|||
return |
|||
} |
|||
|
|||
if !strings.HasPrefix(proxyAuth, "Basic ") { |
|||
return |
|||
} |
|||
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(proxyAuth, "Basic ")) |
|||
if err != nil { |
|||
return |
|||
} |
|||
cs := string(c) |
|||
s := strings.IndexByte(cs, ':') |
|||
if s < 0 { |
|||
return |
|||
} |
|||
|
|||
return cs[:s], cs[s+1:], true |
|||
} |
|||
|
|||
func authenticate(username, password string, users ...*url.Userinfo) bool { |
|||
if len(users) == 0 { |
|||
return true |
|||
} |
|||
|
|||
for _, user := range users { |
|||
u := user.Username() |
|||
p, _ := user.Password() |
|||
if (u == username && p == password) || |
|||
(u == username && p == "") || |
|||
(u == "" && p == password) { |
|||
return true |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
@ -1,517 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"crypto/sha1" |
|||
"encoding/csv" |
|||
"errors" |
|||
"fmt" |
|||
"net" |
|||
"os" |
|||
"time" |
|||
|
|||
"golang.org/x/crypto/pbkdf2" |
|||
|
|||
"sync" |
|||
|
|||
"github.com/go-log/log" |
|||
"github.com/klauspost/compress/snappy" |
|||
"gopkg.in/xtaci/kcp-go.v2" |
|||
"gopkg.in/xtaci/smux.v1" |
|||
) |
|||
|
|||
var ( |
|||
// KCPSalt is the default salt for KCP cipher.
|
|||
KCPSalt = "kcp-go" |
|||
) |
|||
|
|||
// KCPConfig describes the config for KCP.
|
|||
type KCPConfig struct { |
|||
Key string `json:"key"` |
|||
Crypt string `json:"crypt"` |
|||
Mode string `json:"mode"` |
|||
MTU int `json:"mtu"` |
|||
SndWnd int `json:"sndwnd"` |
|||
RcvWnd int `json:"rcvwnd"` |
|||
DataShard int `json:"datashard"` |
|||
ParityShard int `json:"parityshard"` |
|||
DSCP int `json:"dscp"` |
|||
NoComp bool `json:"nocomp"` |
|||
AckNodelay bool `json:"acknodelay"` |
|||
NoDelay int `json:"nodelay"` |
|||
Interval int `json:"interval"` |
|||
Resend int `json:"resend"` |
|||
NoCongestion int `json:"nc"` |
|||
SockBuf int `json:"sockbuf"` |
|||
KeepAlive int `json:"keepalive"` |
|||
SnmpLog string `json:"snmplog"` |
|||
SnmpPeriod int `json:"snmpperiod"` |
|||
Signal bool `json:"signal"` // Signal enables the signal SIGUSR1 feature.
|
|||
} |
|||
|
|||
// Init initializes the KCP config.
|
|||
func (c *KCPConfig) Init() { |
|||
switch c.Mode { |
|||
case "normal": |
|||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 50, 2, 1 |
|||
case "fast2": |
|||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 30, 2, 1 |
|||
case "fast3": |
|||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1 |
|||
case "fast": |
|||
fallthrough |
|||
default: |
|||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 40, 2, 1 |
|||
} |
|||
} |
|||
|
|||
var ( |
|||
// DefaultKCPConfig is the default KCP config.
|
|||
DefaultKCPConfig = &KCPConfig{ |
|||
Key: "it's a secrect", |
|||
Crypt: "aes", |
|||
Mode: "fast", |
|||
MTU: 1350, |
|||
SndWnd: 1024, |
|||
RcvWnd: 1024, |
|||
DataShard: 10, |
|||
ParityShard: 3, |
|||
DSCP: 0, |
|||
NoComp: false, |
|||
AckNodelay: false, |
|||
NoDelay: 0, |
|||
Interval: 50, |
|||
Resend: 0, |
|||
NoCongestion: 0, |
|||
SockBuf: 4194304, |
|||
KeepAlive: 10, |
|||
SnmpLog: "", |
|||
SnmpPeriod: 60, |
|||
Signal: false, |
|||
} |
|||
) |
|||
|
|||
type kcpConn struct { |
|||
conn net.Conn |
|||
stream *smux.Stream |
|||
} |
|||
|
|||
func (c *kcpConn) Read(b []byte) (n int, err error) { |
|||
return c.stream.Read(b) |
|||
} |
|||
|
|||
func (c *kcpConn) Write(b []byte) (n int, err error) { |
|||
return c.stream.Write(b) |
|||
} |
|||
|
|||
func (c *kcpConn) Close() error { |
|||
return c.stream.Close() |
|||
} |
|||
|
|||
func (c *kcpConn) LocalAddr() net.Addr { |
|||
return c.conn.LocalAddr() |
|||
} |
|||
|
|||
func (c *kcpConn) RemoteAddr() net.Addr { |
|||
return c.conn.RemoteAddr() |
|||
} |
|||
|
|||
func (c *kcpConn) SetDeadline(t time.Time) error { |
|||
return c.conn.SetDeadline(t) |
|||
} |
|||
|
|||
func (c *kcpConn) SetReadDeadline(t time.Time) error { |
|||
return c.conn.SetReadDeadline(t) |
|||
} |
|||
|
|||
func (c *kcpConn) SetWriteDeadline(t time.Time) error { |
|||
return c.conn.SetWriteDeadline(t) |
|||
} |
|||
|
|||
type kcpSession struct { |
|||
conn net.Conn |
|||
session *smux.Session |
|||
} |
|||
|
|||
func (session *kcpSession) GetConn() (*kcpConn, error) { |
|||
stream, err := session.session.OpenStream() |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
return &kcpConn{conn: session.conn, stream: stream}, nil |
|||
} |
|||
|
|||
func (session *kcpSession) Close() error { |
|||
return session.session.Close() |
|||
} |
|||
|
|||
func (session *kcpSession) IsClosed() bool { |
|||
return session.session.IsClosed() |
|||
} |
|||
|
|||
func (session *kcpSession) NumStreams() int { |
|||
return session.session.NumStreams() |
|||
} |
|||
|
|||
type kcpTransporter struct { |
|||
sessions map[string]*kcpSession |
|||
sessionMutex sync.Mutex |
|||
config *KCPConfig |
|||
} |
|||
|
|||
// KCPTransporter creates a Transporter that is used by KCP proxy client.
|
|||
func KCPTransporter(config *KCPConfig) Transporter { |
|||
if config == nil { |
|||
config = DefaultKCPConfig |
|||
} |
|||
config.Init() |
|||
|
|||
go snmpLogger(config.SnmpLog, config.SnmpPeriod) |
|||
if config.Signal { |
|||
go kcpSigHandler() |
|||
} |
|||
|
|||
return &kcpTransporter{ |
|||
config: config, |
|||
sessions: make(map[string]*kcpSession), |
|||
} |
|||
} |
|||
|
|||
func (tr *kcpTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) { |
|||
uaddr, err := net.ResolveUDPAddr("udp", addr) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
session, ok := tr.sessions[addr] |
|||
if !ok { |
|||
conn, err = net.DialUDP("udp", nil, uaddr) |
|||
if err != nil { |
|||
return |
|||
} |
|||
session = &kcpSession{conn: conn} |
|||
tr.sessions[addr] = session |
|||
} |
|||
return session.conn, nil |
|||
} |
|||
|
|||
func (tr *kcpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { |
|||
opts := &HandshakeOptions{} |
|||
for _, option := range options { |
|||
option(opts) |
|||
} |
|||
config := tr.config |
|||
if opts.KCPConfig != nil { |
|||
config = opts.KCPConfig |
|||
} |
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
session, ok := tr.sessions[opts.Addr] |
|||
if session != nil && session.conn != conn { |
|||
conn.Close() |
|||
return nil, errors.New("kcp: unrecognized connection") |
|||
} |
|||
if !ok || session.session == nil { |
|||
s, err := tr.initSession(opts.Addr, conn, config) |
|||
if err != nil { |
|||
conn.Close() |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, err |
|||
} |
|||
session = s |
|||
tr.sessions[opts.Addr] = session |
|||
} |
|||
cc, err := session.GetConn() |
|||
if err != nil { |
|||
session.Close() |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, err |
|||
} |
|||
|
|||
return cc, nil |
|||
} |
|||
|
|||
func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPConfig) (*kcpSession, error) { |
|||
udpConn, ok := conn.(*net.UDPConn) |
|||
if !ok { |
|||
return nil, errors.New("kcp: wrong connection type") |
|||
} |
|||
|
|||
kcpconn, err := kcp.NewConn(addr, |
|||
blockCrypt(config.Key, config.Crypt, KCPSalt), |
|||
config.DataShard, config.ParityShard, |
|||
&kcp.ConnectedUDPConn{UDPConn: udpConn, Conn: udpConn}) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
kcpconn.SetStreamMode(true) |
|||
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion) |
|||
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd) |
|||
kcpconn.SetMtu(config.MTU) |
|||
kcpconn.SetACKNoDelay(config.AckNodelay) |
|||
kcpconn.SetKeepAlive(config.KeepAlive) |
|||
|
|||
if err := kcpconn.SetDSCP(config.DSCP); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
|
|||
// stream multiplex
|
|||
smuxConfig := smux.DefaultConfig() |
|||
smuxConfig.MaxReceiveBuffer = config.SockBuf |
|||
var cc net.Conn = kcpconn |
|||
if !config.NoComp { |
|||
cc = newCompStreamConn(kcpconn) |
|||
} |
|||
session, err := smux.Client(cc, smuxConfig) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
return &kcpSession{conn: conn, session: session}, nil |
|||
} |
|||
|
|||
func (tr *kcpTransporter) Multiplex() bool { |
|||
return true |
|||
} |
|||
|
|||
type kcpListener struct { |
|||
config *KCPConfig |
|||
ln *kcp.Listener |
|||
connChan chan net.Conn |
|||
errChan chan error |
|||
} |
|||
|
|||
// KCPListener creates a Listener for KCP proxy server.
|
|||
func KCPListener(addr string, config *KCPConfig) (Listener, error) { |
|||
if config == nil { |
|||
config = DefaultKCPConfig |
|||
} |
|||
config.Init() |
|||
|
|||
ln, err := kcp.ListenWithOptions(addr, |
|||
blockCrypt(config.Key, config.Crypt, KCPSalt), config.DataShard, config.ParityShard) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if err = ln.SetDSCP(config.DSCP); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
if err = ln.SetReadBuffer(config.SockBuf); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
if err = ln.SetWriteBuffer(config.SockBuf); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
|
|||
go snmpLogger(config.SnmpLog, config.SnmpPeriod) |
|||
if config.Signal { |
|||
go kcpSigHandler() |
|||
} |
|||
|
|||
l := &kcpListener{ |
|||
config: config, |
|||
ln: ln, |
|||
connChan: make(chan net.Conn, 1024), |
|||
errChan: make(chan error, 1), |
|||
} |
|||
go l.listenLoop() |
|||
|
|||
return l, nil |
|||
} |
|||
|
|||
func (l *kcpListener) listenLoop() { |
|||
for { |
|||
conn, err := l.ln.AcceptKCP() |
|||
if err != nil { |
|||
log.Log("[kcp] accept:", err) |
|||
l.errChan <- err |
|||
close(l.errChan) |
|||
return |
|||
} |
|||
conn.SetStreamMode(true) |
|||
conn.SetNoDelay(l.config.NoDelay, l.config.Interval, l.config.Resend, l.config.NoCongestion) |
|||
conn.SetMtu(l.config.MTU) |
|||
conn.SetWindowSize(l.config.SndWnd, l.config.RcvWnd) |
|||
conn.SetACKNoDelay(l.config.AckNodelay) |
|||
conn.SetKeepAlive(l.config.KeepAlive) |
|||
go l.mux(conn) |
|||
} |
|||
} |
|||
|
|||
func (l *kcpListener) mux(conn net.Conn) { |
|||
smuxConfig := smux.DefaultConfig() |
|||
smuxConfig.MaxReceiveBuffer = l.config.SockBuf |
|||
|
|||
log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr()) |
|||
|
|||
if !l.config.NoComp { |
|||
conn = newCompStreamConn(conn) |
|||
} |
|||
|
|||
mux, err := smux.Server(conn, smuxConfig) |
|||
if err != nil { |
|||
log.Log("[kcp]", err) |
|||
return |
|||
} |
|||
defer mux.Close() |
|||
|
|||
log.Logf("[kcp] %s <-> %s", conn.RemoteAddr(), l.Addr()) |
|||
defer log.Logf("[kcp] %s >-< %s", conn.RemoteAddr(), l.Addr()) |
|||
|
|||
for { |
|||
stream, err := mux.AcceptStream() |
|||
if err != nil { |
|||
log.Log("[kcp] accept stream:", err) |
|||
return |
|||
} |
|||
|
|||
cc := &kcpConn{conn: conn, stream: stream} |
|||
select { |
|||
case l.connChan <- cc: |
|||
default: |
|||
cc.Close() |
|||
log.Logf("[kcp] %s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr()) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (l *kcpListener) 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 *kcpListener) Addr() net.Addr { |
|||
return l.ln.Addr() |
|||
} |
|||
|
|||
func (l *kcpListener) Close() error { |
|||
return l.ln.Close() |
|||
} |
|||
|
|||
func blockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) { |
|||
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New) |
|||
|
|||
switch crypt { |
|||
case "tea": |
|||
block, _ = kcp.NewTEABlockCrypt(pass[:16]) |
|||
case "xor": |
|||
block, _ = kcp.NewSimpleXORBlockCrypt(pass) |
|||
case "none": |
|||
block, _ = kcp.NewNoneBlockCrypt(pass) |
|||
case "aes-128": |
|||
block, _ = kcp.NewAESBlockCrypt(pass[:16]) |
|||
case "aes-192": |
|||
block, _ = kcp.NewAESBlockCrypt(pass[:24]) |
|||
case "blowfish": |
|||
block, _ = kcp.NewBlowfishBlockCrypt(pass) |
|||
case "twofish": |
|||
block, _ = kcp.NewTwofishBlockCrypt(pass) |
|||
case "cast5": |
|||
block, _ = kcp.NewCast5BlockCrypt(pass[:16]) |
|||
case "3des": |
|||
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24]) |
|||
case "xtea": |
|||
block, _ = kcp.NewXTEABlockCrypt(pass[:16]) |
|||
case "salsa20": |
|||
block, _ = kcp.NewSalsa20BlockCrypt(pass) |
|||
case "aes": |
|||
fallthrough |
|||
default: // aes
|
|||
block, _ = kcp.NewAESBlockCrypt(pass) |
|||
} |
|||
return |
|||
} |
|||
|
|||
func snmpLogger(format string, interval int) { |
|||
if format == "" || interval == 0 { |
|||
return |
|||
} |
|||
ticker := time.NewTicker(time.Duration(interval) * time.Second) |
|||
defer ticker.Stop() |
|||
for { |
|||
select { |
|||
case <-ticker.C: |
|||
f, err := os.OpenFile(time.Now().Format(format), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
|||
if err != nil { |
|||
log.Log("[kcp]", err) |
|||
return |
|||
} |
|||
w := csv.NewWriter(f) |
|||
// write header in empty file
|
|||
if stat, err := f.Stat(); err == nil && stat.Size() == 0 { |
|||
if err := w.Write(append([]string{"Unix"}, kcp.DefaultSnmp.Header()...)); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
} |
|||
if err := w.Write(append([]string{fmt.Sprint(time.Now().Unix())}, kcp.DefaultSnmp.ToSlice()...)); err != nil { |
|||
log.Log("[kcp]", err) |
|||
} |
|||
kcp.DefaultSnmp.Reset() |
|||
w.Flush() |
|||
f.Close() |
|||
} |
|||
} |
|||
} |
|||
|
|||
type compStreamConn struct { |
|||
conn net.Conn |
|||
w *snappy.Writer |
|||
r *snappy.Reader |
|||
} |
|||
|
|||
func newCompStreamConn(conn net.Conn) *compStreamConn { |
|||
c := new(compStreamConn) |
|||
c.conn = conn |
|||
c.w = snappy.NewBufferedWriter(conn) |
|||
c.r = snappy.NewReader(conn) |
|||
return c |
|||
} |
|||
|
|||
func (c *compStreamConn) Read(b []byte) (n int, err error) { |
|||
return c.r.Read(b) |
|||
} |
|||
|
|||
func (c *compStreamConn) Write(b []byte) (n int, err error) { |
|||
n, err = c.w.Write(b) |
|||
err = c.w.Flush() |
|||
return n, err |
|||
} |
|||
|
|||
func (c *compStreamConn) Close() error { |
|||
return c.conn.Close() |
|||
} |
|||
|
|||
func (c *compStreamConn) LocalAddr() net.Addr { |
|||
return c.conn.LocalAddr() |
|||
} |
|||
|
|||
func (c *compStreamConn) RemoteAddr() net.Addr { |
|||
return c.conn.RemoteAddr() |
|||
} |
|||
|
|||
func (c *compStreamConn) SetDeadline(t time.Time) error { |
|||
return c.conn.SetDeadline(t) |
|||
} |
|||
|
|||
func (c *compStreamConn) SetReadDeadline(t time.Time) error { |
|||
return c.conn.SetReadDeadline(t) |
|||
} |
|||
|
|||
func (c *compStreamConn) SetWriteDeadline(t time.Time) error { |
|||
return c.conn.SetWriteDeadline(t) |
|||
} |
|||
@ -1,95 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"net" |
|||
"net/url" |
|||
"strconv" |
|||
"strings" |
|||
|
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
// Node is a proxy node, mainly used to construct a proxy chain.
|
|||
type Node struct { |
|||
Addr string |
|||
Protocol string |
|||
Transport string |
|||
Remote string // remote address, used by tcp/udp port forwarding
|
|||
User *url.Userinfo |
|||
Chain *Chain |
|||
Values url.Values |
|||
Client *Client |
|||
DialOptions []DialOption |
|||
HandshakeOptions []HandshakeOption |
|||
} |
|||
|
|||
func ParseNode(s string) (node Node, err error) { |
|||
if !strings.Contains(s, "://") { |
|||
s = "auto://" + s |
|||
} |
|||
u, err := url.Parse(s) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
node = Node{ |
|||
Addr: u.Host, |
|||
Values: u.Query(), |
|||
User: u.User, |
|||
} |
|||
|
|||
schemes := strings.Split(u.Scheme, "+") |
|||
if len(schemes) == 1 { |
|||
node.Protocol = schemes[0] |
|||
node.Transport = schemes[0] |
|||
} |
|||
if len(schemes) == 2 { |
|||
node.Protocol = schemes[0] |
|||
node.Transport = schemes[1] |
|||
} |
|||
|
|||
switch node.Transport { |
|||
case "tls", "ws", "wss", "kcp", "ssh", "quic", "ssu", "http2", "h2", "h2c", "redirect": |
|||
case "https": |
|||
node.Protocol = "http" |
|||
node.Transport = "tls" |
|||
case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding
|
|||
node.Remote = strings.Trim(u.EscapedPath(), "/") |
|||
case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding
|
|||
node.Remote = strings.Trim(u.EscapedPath(), "/") |
|||
default: |
|||
node.Transport = "" |
|||
} |
|||
|
|||
switch node.Protocol { |
|||
case "http", "http2", "socks4", "socks4a", "socks", "socks5", "ss", "ssu": |
|||
case "tcp", "udp", "rtcp", "rudp": // port forwarding
|
|||
case "direct", "remote", "forward": // SSH port forwarding
|
|||
default: |
|||
node.Protocol = "" |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
func Can(action string, addr string, whitelist, blacklist *Permissions) bool { |
|||
if !strings.Contains(addr, ":") { |
|||
addr = addr + ":80" |
|||
} |
|||
host, strport, err := net.SplitHostPort(addr) |
|||
|
|||
if err != nil { |
|||
return false |
|||
} |
|||
|
|||
port, err := strconv.Atoi(strport) |
|||
|
|||
if err != nil { |
|||
return false |
|||
} |
|||
|
|||
if Debug { |
|||
log.Logf("Can action: %s, host: %s, port %d", action, host, port) |
|||
} |
|||
return whitelist.Can(action, host, port) && !blacklist.Can(action, host, port) |
|||
} |
|||
@ -1,185 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
"strconv" |
|||
"strings" |
|||
|
|||
glob "github.com/ryanuber/go-glob" |
|||
) |
|||
|
|||
type Permission struct { |
|||
Actions StringSet |
|||
Hosts StringSet |
|||
Ports PortSet |
|||
} |
|||
|
|||
type Permissions []Permission |
|||
|
|||
func minint(x, y int) int { |
|||
if x < y { |
|||
return x |
|||
} |
|||
return y |
|||
} |
|||
|
|||
func maxint(x, y int) int { |
|||
if x > y { |
|||
return x |
|||
} |
|||
return y |
|||
} |
|||
|
|||
type PortRange struct { |
|||
Min, Max int |
|||
} |
|||
|
|||
func (ir *PortRange) Contains(value int) bool { |
|||
return value >= ir.Min && value <= ir.Max |
|||
} |
|||
|
|||
func ParsePortRange(s string) (*PortRange, error) { |
|||
if s == "*" { |
|||
return &PortRange{Min: 0, Max: 65535}, nil |
|||
} |
|||
|
|||
minmax := strings.Split(s, "-") |
|||
switch len(minmax) { |
|||
case 1: |
|||
port, err := strconv.Atoi(s) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if port < 0 || port > 65535 { |
|||
return nil, fmt.Errorf("invalid port: %s", s) |
|||
} |
|||
return &PortRange{Min: port, Max: port}, nil |
|||
case 2: |
|||
min, err := strconv.Atoi(minmax[0]) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
max, err := strconv.Atoi(minmax[1]) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
realmin := maxint(0, minint(min, max)) |
|||
realmax := minint(65535, maxint(min, max)) |
|||
|
|||
return &PortRange{Min: realmin, Max: realmax}, nil |
|||
default: |
|||
return nil, fmt.Errorf("invalid range: %s", s) |
|||
} |
|||
} |
|||
|
|||
func (ps *PortSet) Contains(value int) bool { |
|||
for _, portRange := range *ps { |
|||
if portRange.Contains(value) { |
|||
return true |
|||
} |
|||
} |
|||
|
|||
return false |
|||
} |
|||
|
|||
type PortSet []PortRange |
|||
|
|||
func ParsePortSet(s string) (*PortSet, error) { |
|||
ps := &PortSet{} |
|||
|
|||
if s == "" { |
|||
return nil, errors.New("must specify at least one port") |
|||
} |
|||
|
|||
ranges := strings.Split(s, ",") |
|||
|
|||
for _, r := range ranges { |
|||
portRange, err := ParsePortRange(r) |
|||
|
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
*ps = append(*ps, *portRange) |
|||
} |
|||
|
|||
return ps, nil |
|||
} |
|||
|
|||
func (ss *StringSet) Contains(subj string) bool { |
|||
for _, s := range *ss { |
|||
if glob.Glob(s, subj) { |
|||
return true |
|||
} |
|||
} |
|||
|
|||
return false |
|||
} |
|||
|
|||
type StringSet []string |
|||
|
|||
func ParseStringSet(s string) (*StringSet, error) { |
|||
ss := &StringSet{} |
|||
if s == "" { |
|||
return nil, errors.New("cannot be empty") |
|||
} |
|||
|
|||
*ss = strings.Split(s, ",") |
|||
|
|||
return ss, nil |
|||
} |
|||
|
|||
func (ps *Permissions) Can(action string, host string, port int) bool { |
|||
for _, p := range *ps { |
|||
if p.Actions.Contains(action) && p.Hosts.Contains(host) && p.Ports.Contains(port) { |
|||
return true |
|||
} |
|||
} |
|||
|
|||
return false |
|||
} |
|||
|
|||
func ParsePermissions(s string) (*Permissions, error) { |
|||
ps := &Permissions{} |
|||
|
|||
if s == "" { |
|||
return &Permissions{}, nil |
|||
} |
|||
|
|||
perms := strings.Split(s, " ") |
|||
|
|||
for _, perm := range perms { |
|||
parts := strings.Split(perm, ":") |
|||
|
|||
switch len(parts) { |
|||
case 3: |
|||
actions, err := ParseStringSet(parts[0]) |
|||
|
|||
if err != nil { |
|||
return nil, fmt.Errorf("action list must look like connect,bind given: %s", parts[0]) |
|||
} |
|||
|
|||
hosts, err := ParseStringSet(parts[1]) |
|||
|
|||
if err != nil { |
|||
return nil, fmt.Errorf("hosts list must look like google.pl,*.google.com given: %s", parts[1]) |
|||
} |
|||
|
|||
ports, err := ParsePortSet(parts[2]) |
|||
|
|||
if err != nil { |
|||
return nil, fmt.Errorf("ports list must look like 80,8000-9000, given: %s", parts[2]) |
|||
} |
|||
|
|||
permission := Permission{Actions: *actions, Hosts: *hosts, Ports: *ports} |
|||
|
|||
*ps = append(*ps, permission) |
|||
default: |
|||
return nil, fmt.Errorf("permission must have format [actions]:[hosts]:[ports] given: %s", perm) |
|||
} |
|||
} |
|||
|
|||
return ps, nil |
|||
} |
|||
@ -1,152 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"fmt" |
|||
"testing" |
|||
) |
|||
|
|||
var portRangeTests = []struct { |
|||
in string |
|||
out *PortRange |
|||
}{ |
|||
{"1", &PortRange{Min: 1, Max: 1}}, |
|||
{"1-3", &PortRange{Min: 1, Max: 3}}, |
|||
{"3-1", &PortRange{Min: 1, Max: 3}}, |
|||
{"0-100000", &PortRange{Min: 0, Max: 65535}}, |
|||
{"*", &PortRange{Min: 0, Max: 65535}}, |
|||
} |
|||
|
|||
var stringSetTests = []struct { |
|||
in string |
|||
out *StringSet |
|||
}{ |
|||
{"*", &StringSet{"*"}}, |
|||
{"google.pl,google.com", &StringSet{"google.pl", "google.com"}}, |
|||
} |
|||
|
|||
var portSetTests = []struct { |
|||
in string |
|||
out *PortSet |
|||
}{ |
|||
{"1,3", &PortSet{PortRange{Min: 1, Max: 1}, PortRange{Min: 3, Max: 3}}}, |
|||
{"1-3,7-5", &PortSet{PortRange{Min: 1, Max: 3}, PortRange{Min: 5, Max: 7}}}, |
|||
{"0-100000", &PortSet{PortRange{Min: 0, Max: 65535}}}, |
|||
{"*", &PortSet{PortRange{Min: 0, Max: 65535}}}, |
|||
} |
|||
|
|||
var permissionsTests = []struct { |
|||
in string |
|||
out *Permissions |
|||
}{ |
|||
{"", &Permissions{}}, |
|||
{"*:*:*", &Permissions{ |
|||
Permission{ |
|||
Actions: StringSet{"*"}, |
|||
Hosts: StringSet{"*"}, |
|||
Ports: PortSet{PortRange{Min: 0, Max: 65535}}, |
|||
}, |
|||
}}, |
|||
{"bind:127.0.0.1,localhost:80,443,8000-8100 connect:*.google.pl:80,443", &Permissions{ |
|||
Permission{ |
|||
Actions: StringSet{"bind"}, |
|||
Hosts: StringSet{"127.0.0.1", "localhost"}, |
|||
Ports: PortSet{ |
|||
PortRange{Min: 80, Max: 80}, |
|||
PortRange{Min: 443, Max: 443}, |
|||
PortRange{Min: 8000, Max: 8100}, |
|||
}, |
|||
}, |
|||
Permission{ |
|||
Actions: StringSet{"connect"}, |
|||
Hosts: StringSet{"*.google.pl"}, |
|||
Ports: PortSet{ |
|||
PortRange{Min: 80, Max: 80}, |
|||
PortRange{Min: 443, Max: 443}, |
|||
}, |
|||
}, |
|||
}}, |
|||
} |
|||
|
|||
func TestPortRangeParse(t *testing.T) { |
|||
for _, test := range portRangeTests { |
|||
actual, err := ParsePortRange(test.in) |
|||
if err != nil { |
|||
t.Errorf("ParsePortRange(%q) returned error: %v", test.in, err) |
|||
} else if *actual != *test.out { |
|||
t.Errorf("ParsePortRange(%q): got %v, want %v", test.in, actual, test.out) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func TestPortRangeContains(t *testing.T) { |
|||
actual, _ := ParsePortRange("5-10") |
|||
|
|||
if !actual.Contains(5) || !actual.Contains(7) || !actual.Contains(10) { |
|||
t.Errorf("5-10 should contain 5, 7 and 10") |
|||
} |
|||
|
|||
if actual.Contains(4) || actual.Contains(11) { |
|||
t.Errorf("5-10 should not contain 4, 11") |
|||
} |
|||
} |
|||
|
|||
func TestStringSetParse(t *testing.T) { |
|||
for _, test := range stringSetTests { |
|||
actual, err := ParseStringSet(test.in) |
|||
if err != nil { |
|||
t.Errorf("ParseStringSet(%q) returned error: %v", test.in, err) |
|||
} else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) { |
|||
t.Errorf("ParseStringSet(%q): got %v, want %v", test.in, actual, test.out) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func TestStringSetContains(t *testing.T) { |
|||
ss, _ := ParseStringSet("google.pl,*.google.com") |
|||
|
|||
if !ss.Contains("google.pl") || !ss.Contains("www.google.com") { |
|||
t.Errorf("google.pl,*.google.com should contain google.pl and www.google.com") |
|||
} |
|||
|
|||
if ss.Contains("www.google.pl") || ss.Contains("foobar.com") { |
|||
t.Errorf("google.pl,*.google.com shound not contain www.google.pl and foobar.com") |
|||
} |
|||
} |
|||
|
|||
func TestPortSetParse(t *testing.T) { |
|||
for _, test := range portSetTests { |
|||
actual, err := ParsePortSet(test.in) |
|||
if err != nil { |
|||
t.Errorf("ParsePortRange(%q) returned error: %v", test.in, err) |
|||
} else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) { |
|||
t.Errorf("ParsePortRange(%q): got %v, want %v", test.in, actual, test.out) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func TestPortSetContains(t *testing.T) { |
|||
actual, _ := ParsePortSet("5-10,20-30") |
|||
|
|||
if !actual.Contains(5) || !actual.Contains(7) || !actual.Contains(10) { |
|||
t.Errorf("5-10,20-30 should contain 5, 7 and 10") |
|||
} |
|||
|
|||
if !actual.Contains(20) || !actual.Contains(27) || !actual.Contains(30) { |
|||
t.Errorf("5-10,20-30 should contain 20, 27 and 30") |
|||
} |
|||
|
|||
if actual.Contains(4) || actual.Contains(11) || actual.Contains(31) { |
|||
t.Errorf("5-10,20-30 should not contain 4, 11, 31") |
|||
} |
|||
} |
|||
|
|||
func TestPermissionsParse(t *testing.T) { |
|||
for _, test := range permissionsTests { |
|||
actual, err := ParsePermissions(test.in) |
|||
if err != nil { |
|||
t.Errorf("ParsePermissions(%q) returned error: %v", test.in, err) |
|||
} else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) { |
|||
t.Errorf("ParsePermissions(%q): got %v, want %v", test.in, actual, test.out) |
|||
} |
|||
} |
|||
} |
|||
@ -1,237 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"crypto/tls" |
|||
"errors" |
|||
"net" |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/go-log/log" |
|||
quic "github.com/lucas-clemente/quic-go" |
|||
) |
|||
|
|||
type quicSession struct { |
|||
conn net.Conn |
|||
session quic.Session |
|||
} |
|||
|
|||
func (session *quicSession) GetConn() (*quicConn, error) { |
|||
stream, err := session.session.OpenStream() |
|||
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.Close(nil) |
|||
} |
|||
|
|||
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) { |
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
session, ok := tr.sessions[addr] |
|||
if !ok { |
|||
conn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) |
|||
if err != nil { |
|||
return |
|||
} |
|||
session = &quicSession{conn: conn} |
|||
tr.sessions[addr] = session |
|||
} |
|||
return session.conn, nil |
|||
} |
|||
|
|||
func (tr *quicTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { |
|||
opts := &HandshakeOptions{} |
|||
for _, option := range options { |
|||
option(opts) |
|||
} |
|||
config := tr.config |
|||
if opts.QUICConfig != nil { |
|||
config = opts.QUICConfig |
|||
} |
|||
if config.TLSConfig == nil { |
|||
config.TLSConfig = &tls.Config{InsecureSkipVerify: true} |
|||
} |
|||
|
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
session, ok := tr.sessions[opts.Addr] |
|||
if session != nil && session.conn != conn { |
|||
conn.Close() |
|||
return nil, errors.New("quic: unrecognized connection") |
|||
} |
|||
if !ok || session.session == nil { |
|||
s, err := tr.initSession(opts.Addr, conn, config) |
|||
if err != nil { |
|||
conn.Close() |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, err |
|||
} |
|||
session = s |
|||
tr.sessions[opts.Addr] = session |
|||
} |
|||
cc, err := session.GetConn() |
|||
if err != nil { |
|||
session.Close() |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, err |
|||
} |
|||
|
|||
return cc, nil |
|||
} |
|||
|
|||
func (tr *quicTransporter) initSession(addr string, conn net.Conn, config *QUICConfig) (*quicSession, error) { |
|||
udpConn, ok := conn.(*net.UDPConn) |
|||
if !ok { |
|||
return nil, errors.New("quic: wrong connection type") |
|||
} |
|||
udpAddr, err := net.ResolveUDPAddr("udp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
quicConfig := &quic.Config{ |
|||
HandshakeTimeout: config.Timeout, |
|||
KeepAlive: config.KeepAlive, |
|||
} |
|||
session, err := quic.Dial(udpConn, udpAddr, addr, config.TLSConfig, quicConfig) |
|||
if err != nil { |
|||
log.Log("quic dial", err) |
|||
return nil, err |
|||
} |
|||
return &quicSession{conn: conn, session: session}, nil |
|||
} |
|||
|
|||
func (tr *quicTransporter) Multiplex() bool { |
|||
return true |
|||
} |
|||
|
|||
type QUICConfig struct { |
|||
TLSConfig *tls.Config |
|||
Timeout time.Duration |
|||
KeepAlive bool |
|||
} |
|||
|
|||
type quicListener struct { |
|||
ln quic.Listener |
|||
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{ |
|||
HandshakeTimeout: config.Timeout, |
|||
KeepAlive: config.KeepAlive, |
|||
} |
|||
|
|||
ln, err := quic.ListenAddr(addr, config.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() |
|||
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.Session) { |
|||
log.Logf("[quic] %s <-> %s", session.RemoteAddr(), session.LocalAddr()) |
|||
defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr()) |
|||
|
|||
for { |
|||
stream, err := session.AcceptStream() |
|||
if err != nil { |
|||
log.Log("[quic] accept stream:", err) |
|||
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 |
|||
} |
|||
@ -1,91 +0,0 @@ |
|||
// +build !windows
|
|||
|
|||
package gost |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
"net" |
|||
"syscall" |
|||
|
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
type tcpRedirectHandler struct { |
|||
options *HandlerOptions |
|||
} |
|||
|
|||
// TCPRedirectHandler creates a server Handler for TCP redirect server.
|
|||
func TCPRedirectHandler(opts ...HandlerOption) Handler { |
|||
h := &tcpRedirectHandler{ |
|||
options: &HandlerOptions{ |
|||
Chain: new(Chain), |
|||
}, |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *tcpRedirectHandler) Handle(c net.Conn) { |
|||
conn, ok := c.(*net.TCPConn) |
|||
if !ok { |
|||
log.Log("[red-tcp] not a TCP connection") |
|||
} |
|||
|
|||
srcAddr := conn.RemoteAddr() |
|||
dstAddr, conn, err := h.getOriginalDstAddr(conn) |
|||
if err != nil { |
|||
log.Logf("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err) |
|||
return |
|||
} |
|||
defer conn.Close() |
|||
|
|||
log.Logf("[red-tcp] %s -> %s", srcAddr, dstAddr) |
|||
|
|||
cc, err := h.options.Chain.Dial(dstAddr.String()) |
|||
if err != nil { |
|||
log.Logf("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err) |
|||
return |
|||
} |
|||
defer cc.Close() |
|||
|
|||
log.Logf("[red-tcp] %s <-> %s", srcAddr, dstAddr) |
|||
transport(conn, cc) |
|||
log.Logf("[red-tcp] %s >-< %s", srcAddr, dstAddr) |
|||
} |
|||
|
|||
func (h *tcpRedirectHandler) getOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err error) { |
|||
defer conn.Close() |
|||
|
|||
fc, err := conn.File() |
|||
if err != nil { |
|||
return |
|||
} |
|||
defer fc.Close() |
|||
|
|||
mreq, err := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, 80) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
// only ipv4 support
|
|||
ip := net.IPv4(mreq.Multiaddr[4], mreq.Multiaddr[5], mreq.Multiaddr[6], mreq.Multiaddr[7]) |
|||
port := uint16(mreq.Multiaddr[2])<<8 + uint16(mreq.Multiaddr[3]) |
|||
addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port)) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
cc, err := net.FileConn(fc) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
c, ok := cc.(*net.TCPConn) |
|||
if !ok { |
|||
err = errors.New("not a TCP connection") |
|||
} |
|||
return |
|||
} |
|||
@ -1,31 +0,0 @@ |
|||
// +build windows
|
|||
|
|||
package gost |
|||
|
|||
import ( |
|||
"net" |
|||
|
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
type tcpRedirectHandler struct { |
|||
options *HandlerOptions |
|||
} |
|||
|
|||
// TCPRedirectHandler creates a server Handler for TCP redirect server.
|
|||
func TCPRedirectHandler(opts ...HandlerOption) Handler { |
|||
h := &tcpRedirectHandler{ |
|||
options: &HandlerOptions{ |
|||
Chain: new(Chain), |
|||
}, |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *tcpRedirectHandler) Handle(c net.Conn) { |
|||
log.Log("[red-tcp] TCP redirect is not available on the Windows platform") |
|||
c.Close() |
|||
} |
|||
@ -1,104 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"io" |
|||
"net" |
|||
"time" |
|||
|
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
// Server is a proxy server.
|
|||
type Server struct { |
|||
} |
|||
|
|||
// Serve serves as a proxy server.
|
|||
func (s *Server) Serve(l net.Listener, h Handler) error { |
|||
defer l.Close() |
|||
|
|||
if l == nil { |
|||
ln, err := TCPListener(":8080") |
|||
if err != nil { |
|||
return err |
|||
} |
|||
l = ln |
|||
} |
|||
if h == nil { |
|||
h = HTTPHandler() |
|||
} |
|||
|
|||
var tempDelay time.Duration |
|||
for { |
|||
conn, e := l.Accept() |
|||
if e != nil { |
|||
if ne, ok := e.(net.Error); ok && ne.Temporary() { |
|||
if tempDelay == 0 { |
|||
tempDelay = 5 * time.Millisecond |
|||
} else { |
|||
tempDelay *= 2 |
|||
} |
|||
if max := 1 * time.Second; tempDelay > max { |
|||
tempDelay = max |
|||
} |
|||
log.Logf("server: Accept error: %v; retrying in %v", e, tempDelay) |
|||
time.Sleep(tempDelay) |
|||
continue |
|||
} |
|||
return e |
|||
} |
|||
tempDelay = 0 |
|||
go h.Handle(conn) |
|||
} |
|||
|
|||
} |
|||
|
|||
// Listener is a proxy server listener, just like a net.Listener.
|
|||
type Listener interface { |
|||
net.Listener |
|||
} |
|||
|
|||
type tcpListener struct { |
|||
net.Listener |
|||
} |
|||
|
|||
// TCPListener creates a Listener for TCP proxy server.
|
|||
func TCPListener(addr string) (Listener, error) { |
|||
ln, err := net.Listen("tcp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
return &tcpListener{Listener: &tcpKeepAliveListener{ln.(*net.TCPListener)}}, nil |
|||
} |
|||
|
|||
type tcpKeepAliveListener struct { |
|||
*net.TCPListener |
|||
} |
|||
|
|||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { |
|||
tc, err := ln.AcceptTCP() |
|||
if err != nil { |
|||
return |
|||
} |
|||
tc.SetKeepAlive(true) |
|||
tc.SetKeepAlivePeriod(KeepAliveTime) |
|||
return tc, nil |
|||
} |
|||
|
|||
func transport(rw1, rw2 io.ReadWriter) error { |
|||
errc := make(chan error, 1) |
|||
go func() { |
|||
_, err := io.Copy(rw1, rw2) |
|||
errc <- err |
|||
}() |
|||
|
|||
go func() { |
|||
_, err := io.Copy(rw2, rw1) |
|||
errc <- err |
|||
}() |
|||
|
|||
err := <-errc |
|||
if err != nil && err == io.EOF { |
|||
err = nil |
|||
} |
|||
return err |
|||
} |
|||
@ -1,5 +0,0 @@ |
|||
// +build windows
|
|||
|
|||
package gost |
|||
|
|||
func kcpSigHandler() {} |
|||
@ -1,24 +0,0 @@ |
|||
// +build !windows
|
|||
|
|||
package gost |
|||
|
|||
import ( |
|||
"os" |
|||
"os/signal" |
|||
"syscall" |
|||
|
|||
"github.com/go-log/log" |
|||
"gopkg.in/xtaci/kcp-go.v2" |
|||
) |
|||
|
|||
func kcpSigHandler() { |
|||
ch := make(chan os.Signal, 1) |
|||
signal.Notify(ch, syscall.SIGUSR1) |
|||
|
|||
for { |
|||
switch <-ch { |
|||
case syscall.SIGUSR1: |
|||
log.Logf("[kcp] SNMP: %+v", kcp.DefaultSnmp.Copy()) |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -1,419 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"bytes" |
|||
"encoding/binary" |
|||
"errors" |
|||
"fmt" |
|||
"io" |
|||
"net" |
|||
"net/url" |
|||
"strconv" |
|||
"time" |
|||
|
|||
"github.com/ginuerzh/gosocks5" |
|||
"github.com/go-log/log" |
|||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" |
|||
) |
|||
|
|||
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
|
|||
// we wrap around it to make io.Copy happy.
|
|||
type shadowConn struct { |
|||
conn net.Conn |
|||
} |
|||
|
|||
func (c *shadowConn) Read(b []byte) (n int, err error) { |
|||
return c.conn.Read(b) |
|||
} |
|||
|
|||
func (c *shadowConn) Write(b []byte) (n int, err error) { |
|||
n = len(b) // force byte length consistent
|
|||
_, err = c.conn.Write(b) |
|||
return |
|||
} |
|||
|
|||
func (c *shadowConn) Close() error { |
|||
return c.conn.Close() |
|||
} |
|||
|
|||
func (c *shadowConn) LocalAddr() net.Addr { |
|||
return c.conn.LocalAddr() |
|||
} |
|||
|
|||
func (c *shadowConn) RemoteAddr() net.Addr { |
|||
return c.conn.RemoteAddr() |
|||
} |
|||
|
|||
func (c *shadowConn) SetDeadline(t time.Time) error { |
|||
return c.conn.SetDeadline(t) |
|||
} |
|||
|
|||
func (c *shadowConn) SetReadDeadline(t time.Time) error { |
|||
return c.conn.SetReadDeadline(t) |
|||
} |
|||
|
|||
func (c *shadowConn) SetWriteDeadline(t time.Time) error { |
|||
return c.conn.SetWriteDeadline(t) |
|||
} |
|||
|
|||
type shadowConnector struct { |
|||
Cipher *url.Userinfo |
|||
} |
|||
|
|||
// ShadowConnector creates a Connector for shadowsocks proxy client.
|
|||
// It accepts a cipher info for shadowsocks data encryption/decryption.
|
|||
// The cipher must not be nil.
|
|||
func ShadowConnector(cipher *url.Userinfo) Connector { |
|||
return &shadowConnector{Cipher: cipher} |
|||
} |
|||
|
|||
func (c *shadowConnector) Connect(conn net.Conn, addr string) (net.Conn, error) { |
|||
rawaddr, err := ss.RawAddr(addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
var method, password string |
|||
if c.Cipher != nil { |
|||
method = c.Cipher.Username() |
|||
password, _ = c.Cipher.Password() |
|||
} |
|||
|
|||
cipher, err := ss.NewCipher(method, password) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
sc, err := ss.DialWithRawAddrConn(rawaddr, conn, cipher) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
return &shadowConn{conn: sc}, nil |
|||
} |
|||
|
|||
type shadowHandler struct { |
|||
options *HandlerOptions |
|||
} |
|||
|
|||
// ShadowHandler creates a server Handler for shadowsocks proxy server.
|
|||
func ShadowHandler(opts ...HandlerOption) Handler { |
|||
h := &shadowHandler{ |
|||
options: &HandlerOptions{}, |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *shadowHandler) Handle(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
var method, password string |
|||
users := h.options.Users |
|||
if len(users) > 0 { |
|||
method = users[0].Username() |
|||
password, _ = users[0].Password() |
|||
} |
|||
cipher, err := ss.NewCipher(method, password) |
|||
if err != nil { |
|||
log.Log("[ss]", err) |
|||
return |
|||
} |
|||
conn = &shadowConn{conn: ss.NewConn(conn, cipher)} |
|||
|
|||
log.Logf("[ss] %s - %s", conn.RemoteAddr(), conn.LocalAddr()) |
|||
|
|||
addr, err := h.getRequest(conn) |
|||
if err != nil { |
|||
log.Logf("[ss] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) |
|||
return |
|||
} |
|||
log.Logf("[ss] %s -> %s", conn.RemoteAddr(), addr) |
|||
|
|||
if !Can("tcp", addr, h.options.Whitelist, h.options.Blacklist) { |
|||
log.Logf("[ss] Unauthorized to tcp connect to %s", addr) |
|||
return |
|||
} |
|||
|
|||
cc, err := h.options.Chain.Dial(addr) |
|||
if err != nil { |
|||
log.Logf("[ss] %s -> %s : %s", conn.RemoteAddr(), addr, err) |
|||
return |
|||
} |
|||
defer cc.Close() |
|||
|
|||
log.Logf("[ss] %s <-> %s", conn.RemoteAddr(), addr) |
|||
transport(conn, cc) |
|||
log.Logf("[ss] %s >-< %s", conn.RemoteAddr(), addr) |
|||
} |
|||
|
|||
const ( |
|||
idType = 0 // address type index
|
|||
idIP0 = 1 // ip addres start index
|
|||
idDmLen = 1 // domain address length index
|
|||
idDm0 = 2 // domain address start index
|
|||
|
|||
typeIPv4 = 1 // type is ipv4 address
|
|||
typeDm = 3 // type is domain address
|
|||
typeIPv6 = 4 // type is ipv6 address
|
|||
|
|||
lenIPv4 = net.IPv4len + 2 // ipv4 + 2port
|
|||
lenIPv6 = net.IPv6len + 2 // ipv6 + 2port
|
|||
lenDmBase = 2 // 1addrLen + 2port, plus addrLen
|
|||
lenHmacSha1 = 10 |
|||
) |
|||
|
|||
// This function is copied from shadowsocks library with some modification.
|
|||
func (h *shadowHandler) getRequest(conn net.Conn) (host string, err error) { |
|||
// buf size should at least have the same size with the largest possible
|
|||
// request size (when addrType is 3, domain name has at most 256 bytes)
|
|||
// 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
|
|||
buf := make([]byte, smallBufferSize) |
|||
|
|||
// read till we get possible domain length field
|
|||
conn.SetReadDeadline(time.Now().Add(30 * time.Second)) |
|||
if _, err = io.ReadFull(conn, buf[:idType+1]); err != nil { |
|||
return |
|||
} |
|||
|
|||
var reqStart, reqEnd int |
|||
addrType := buf[idType] |
|||
switch addrType & ss.AddrMask { |
|||
case typeIPv4: |
|||
reqStart, reqEnd = idIP0, idIP0+lenIPv4 |
|||
case typeIPv6: |
|||
reqStart, reqEnd = idIP0, idIP0+lenIPv6 |
|||
case typeDm: |
|||
if _, err = io.ReadFull(conn, buf[idType+1:idDmLen+1]); err != nil { |
|||
return |
|||
} |
|||
reqStart, reqEnd = idDm0, int(idDm0+buf[idDmLen]+lenDmBase) |
|||
default: |
|||
err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask) |
|||
return |
|||
} |
|||
|
|||
if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil { |
|||
return |
|||
} |
|||
|
|||
// Return string for typeIP is not most efficient, but browsers (Chrome,
|
|||
// Safari, Firefox) all seems using typeDm exclusively. So this is not a
|
|||
// big problem.
|
|||
switch addrType & ss.AddrMask { |
|||
case typeIPv4: |
|||
host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String() |
|||
case typeIPv6: |
|||
host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String() |
|||
case typeDm: |
|||
host = string(buf[idDm0 : idDm0+buf[idDmLen]]) |
|||
} |
|||
// parse port
|
|||
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd]) |
|||
host = net.JoinHostPort(host, strconv.Itoa(int(port))) |
|||
return |
|||
} |
|||
|
|||
type shadowUDPListener struct { |
|||
ln net.PacketConn |
|||
conns map[string]*udpServerConn |
|||
connChan chan net.Conn |
|||
errChan chan error |
|||
ttl time.Duration |
|||
} |
|||
|
|||
// ShadowUDPListener creates a Listener for shadowsocks UDP relay server.
|
|||
func ShadowUDPListener(addr string, cipher *url.Userinfo, ttl time.Duration) (Listener, error) { |
|||
laddr, err := net.ResolveUDPAddr("udp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
ln, err := net.ListenUDP("udp", laddr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
var method, password string |
|||
if cipher != nil { |
|||
method = cipher.Username() |
|||
password, _ = cipher.Password() |
|||
} |
|||
cp, err := ss.NewCipher(method, password) |
|||
if err != nil { |
|||
ln.Close() |
|||
return nil, err |
|||
} |
|||
l := &shadowUDPListener{ |
|||
ln: ss.NewSecurePacketConn(ln, cp, false), |
|||
conns: make(map[string]*udpServerConn), |
|||
connChan: make(chan net.Conn, 1024), |
|||
errChan: make(chan error, 1), |
|||
ttl: ttl, |
|||
} |
|||
go l.listenLoop() |
|||
return l, nil |
|||
} |
|||
|
|||
func (l *shadowUDPListener) listenLoop() { |
|||
for { |
|||
b := make([]byte, mediumBufferSize) |
|||
n, raddr, err := l.ln.ReadFrom(b) |
|||
if err != nil { |
|||
log.Logf("[ssu] peer -> %s : %s", l.Addr(), err) |
|||
l.ln.Close() |
|||
l.errChan <- err |
|||
close(l.errChan) |
|||
return |
|||
} |
|||
if Debug { |
|||
log.Logf("[ssu] %s >>> %s : length %d", raddr, l.Addr(), n) |
|||
} |
|||
|
|||
conn, ok := l.conns[raddr.String()] |
|||
if !ok || conn.Closed() { |
|||
conn = newUDPServerConn(l.ln, raddr, l.ttl) |
|||
l.conns[raddr.String()] = conn |
|||
|
|||
select { |
|||
case l.connChan <- conn: |
|||
default: |
|||
conn.Close() |
|||
log.Logf("[ssu] %s - %s: connection queue is full", raddr, l.Addr()) |
|||
} |
|||
} |
|||
|
|||
select { |
|||
case conn.rChan <- b[:n]: // we keep the addr info so that the handler can identify the destination.
|
|||
default: |
|||
log.Logf("[ssu] %s -> %s : read queue is full", raddr, l.Addr()) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (l *shadowUDPListener) 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 *shadowUDPListener) Addr() net.Addr { |
|||
return l.ln.LocalAddr() |
|||
} |
|||
|
|||
func (l *shadowUDPListener) Close() error { |
|||
return l.ln.Close() |
|||
} |
|||
|
|||
type shadowUDPdHandler struct { |
|||
ttl time.Duration |
|||
options *HandlerOptions |
|||
} |
|||
|
|||
// ShadowUDPdHandler creates a server Handler for shadowsocks UDP relay server.
|
|||
func ShadowUDPdHandler(opts ...HandlerOption) Handler { |
|||
h := &shadowUDPdHandler{ |
|||
options: &HandlerOptions{}, |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *shadowUDPdHandler) Handle(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
var err error |
|||
var cc net.PacketConn |
|||
if h.options.Chain.IsEmpty() { |
|||
cc, err = net.ListenUDP("udp", nil) |
|||
if err != nil { |
|||
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err) |
|||
return |
|||
} |
|||
} else { |
|||
var c net.Conn |
|||
c, err = getSOCKS5UDPTunnel(h.options.Chain, nil) |
|||
if err != nil { |
|||
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err) |
|||
return |
|||
} |
|||
cc = &udpTunnelConn{Conn: c} |
|||
} |
|||
defer cc.Close() |
|||
|
|||
log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr()) |
|||
transportUDP(conn, cc) |
|||
log.Logf("[ssu] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr()) |
|||
} |
|||
|
|||
func transportUDP(sc net.Conn, cc net.PacketConn) error { |
|||
errc := make(chan error, 1) |
|||
go func() { |
|||
for { |
|||
b := make([]byte, mediumBufferSize) |
|||
n, err := sc.Read(b[3:]) // add rsv and frag fields to make it the standard SOCKS5 UDP datagram
|
|||
if err != nil { |
|||
// log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err)
|
|||
errc <- err |
|||
return |
|||
} |
|||
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n+3])) |
|||
if err != nil { |
|||
log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err) |
|||
errc <- err |
|||
return |
|||
} |
|||
//if Debug {
|
|||
// log.Logf("[ssu] %s >>> %s length: %d", sc.RemoteAddr(), dgram.Header.Addr.String(), len(dgram.Data))
|
|||
//}
|
|||
addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) |
|||
if err != nil { |
|||
errc <- err |
|||
return |
|||
} |
|||
if _, err := cc.WriteTo(dgram.Data, addr); err != nil { |
|||
errc <- err |
|||
return |
|||
} |
|||
} |
|||
}() |
|||
|
|||
go func() { |
|||
for { |
|||
b := make([]byte, mediumBufferSize) |
|||
n, addr, err := cc.ReadFrom(b) |
|||
if err != nil { |
|||
errc <- err |
|||
return |
|||
} |
|||
//if Debug {
|
|||
// log.Logf("[ssu] %s <<< %s length: %d", sc.RemoteAddr(), addr, n)
|
|||
//}
|
|||
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, toSocksAddr(addr)), b[:n]) |
|||
buf := bytes.Buffer{} |
|||
dgram.Write(&buf) |
|||
if buf.Len() < 10 { |
|||
log.Logf("[ssu] %s <- %s : invalid udp datagram", sc.RemoteAddr(), addr) |
|||
continue |
|||
} |
|||
if _, err := sc.Write(buf.Bytes()[3:]); err != nil { |
|||
errc <- err |
|||
return |
|||
} |
|||
} |
|||
}() |
|||
|
|||
err := <-errc |
|||
if err != nil && err == io.EOF { |
|||
err = nil |
|||
} |
|||
return err |
|||
} |
|||
@ -1,834 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"context" |
|||
"crypto/tls" |
|||
"encoding/binary" |
|||
"errors" |
|||
"fmt" |
|||
"net" |
|||
"net/url" |
|||
"strconv" |
|||
"strings" |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/go-log/log" |
|||
"golang.org/x/crypto/ssh" |
|||
) |
|||
|
|||
// Applicaple SSH Request types for Port Forwarding - RFC 4254 7.X
|
|||
const ( |
|||
DirectForwardRequest = "direct-tcpip" // RFC 4254 7.2
|
|||
RemoteForwardRequest = "tcpip-forward" // RFC 4254 7.1
|
|||
ForwardedTCPReturnRequest = "forwarded-tcpip" // RFC 4254 7.2
|
|||
CancelRemoteForwardRequest = "cancel-tcpip-forward" // RFC 4254 7.1
|
|||
|
|||
GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel
|
|||
) |
|||
|
|||
var ( |
|||
errSessionDead = errors.New("session is dead") |
|||
) |
|||
|
|||
type sshDirectForwardConnector struct { |
|||
} |
|||
|
|||
func SSHDirectForwardConnector() Connector { |
|||
return &sshDirectForwardConnector{} |
|||
} |
|||
|
|||
func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string) (net.Conn, error) { |
|||
cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution.
|
|||
if !ok { |
|||
return nil, errors.New("ssh: wrong connection type") |
|||
} |
|||
conn, err := cc.session.client.Dial("tcp", raddr) |
|||
if err != nil { |
|||
log.Logf("[ssh-tcp] %s -> %s : %s", cc.session.addr, raddr, err) |
|||
return nil, err |
|||
} |
|||
return conn, nil |
|||
} |
|||
|
|||
type sshRemoteForwardConnector struct { |
|||
} |
|||
|
|||
func SSHRemoteForwardConnector() Connector { |
|||
return &sshRemoteForwardConnector{} |
|||
} |
|||
|
|||
func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string) (net.Conn, error) { |
|||
cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution.
|
|||
if !ok { |
|||
return nil, errors.New("ssh: wrong connection type") |
|||
} |
|||
|
|||
cc.session.once.Do(func() { |
|||
go func() { |
|||
defer log.Log("ssh-rtcp: session is closed") |
|||
defer close(cc.session.connChan) |
|||
|
|||
if cc.session == nil || cc.session.client == nil { |
|||
return |
|||
} |
|||
if strings.HasPrefix(addr, ":") { |
|||
addr = "0.0.0.0" + addr |
|||
} |
|||
ln, err := cc.session.client.Listen("tcp", addr) |
|||
if err != nil { |
|||
return |
|||
} |
|||
for { |
|||
rc, err := ln.Accept() |
|||
if err != nil { |
|||
log.Logf("[ssh-rtcp] %s <-> %s accpet : %s", ln.Addr(), addr, err) |
|||
return |
|||
} |
|||
|
|||
select { |
|||
case cc.session.connChan <- rc: |
|||
default: |
|||
rc.Close() |
|||
log.Logf("[ssh-rtcp] %s - %s: connection queue is full", ln.Addr(), addr) |
|||
} |
|||
} |
|||
}() |
|||
}) |
|||
|
|||
sc, ok := <-cc.session.connChan |
|||
if !ok { |
|||
return nil, errors.New("ssh-rtcp: connection is closed") |
|||
} |
|||
return sc, nil |
|||
} |
|||
|
|||
type sshForwardTransporter struct { |
|||
sessions map[string]*sshSession |
|||
sessionMutex sync.Mutex |
|||
} |
|||
|
|||
func SSHForwardTransporter() Transporter { |
|||
return &sshForwardTransporter{ |
|||
sessions: make(map[string]*sshSession), |
|||
} |
|||
} |
|||
|
|||
func (tr *sshForwardTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) { |
|||
opts := &DialOptions{} |
|||
for _, option := range options { |
|||
option(opts) |
|||
} |
|||
|
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
session, ok := tr.sessions[addr] |
|||
if !ok || session.Closed() { |
|||
if opts.Chain == nil { |
|||
conn, err = net.DialTimeout("tcp", addr, opts.Timeout) |
|||
} else { |
|||
conn, err = opts.Chain.Dial(addr) |
|||
} |
|||
if err != nil { |
|||
return |
|||
} |
|||
session = &sshSession{ |
|||
addr: addr, |
|||
conn: conn, |
|||
} |
|||
tr.sessions[addr] = session |
|||
} |
|||
|
|||
return session.conn, nil |
|||
} |
|||
|
|||
func (tr *sshForwardTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { |
|||
opts := &HandshakeOptions{} |
|||
for _, option := range options { |
|||
option(opts) |
|||
} |
|||
|
|||
config := ssh.ClientConfig{ |
|||
Timeout: opts.Timeout, |
|||
HostKeyCallback: ssh.InsecureIgnoreHostKey(), |
|||
} |
|||
if opts.User != nil { |
|||
config.User = opts.User.Username() |
|||
password, _ := opts.User.Password() |
|||
config.Auth = []ssh.AuthMethod{ |
|||
ssh.Password(password), |
|||
} |
|||
} |
|||
|
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
session, ok := tr.sessions[opts.Addr] |
|||
if session != nil && session.conn != conn { |
|||
conn.Close() |
|||
return nil, errors.New("ssh: unrecognized connection") |
|||
} |
|||
if !ok || session.client == nil { |
|||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config) |
|||
if err != nil { |
|||
conn.Close() |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, err |
|||
} |
|||
|
|||
session = &sshSession{ |
|||
addr: opts.Addr, |
|||
conn: conn, |
|||
client: ssh.NewClient(sshConn, chans, reqs), |
|||
closed: make(chan struct{}), |
|||
deaded: make(chan struct{}), |
|||
connChan: make(chan net.Conn, 1024), |
|||
} |
|||
tr.sessions[opts.Addr] = session |
|||
go session.Ping(opts.Interval, 1) |
|||
go session.waitServer() |
|||
go session.waitClose() |
|||
} |
|||
if session.Closed() { |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, errSessionDead |
|||
} |
|||
|
|||
return &sshNopConn{session: session}, nil |
|||
} |
|||
|
|||
func (tr *sshForwardTransporter) Multiplex() bool { |
|||
return true |
|||
} |
|||
|
|||
type sshTunnelTransporter struct { |
|||
sessions map[string]*sshSession |
|||
sessionMutex sync.Mutex |
|||
} |
|||
|
|||
// SSHTunnelTransporter creates a Transporter that is used by SSH tunnel client.
|
|||
func SSHTunnelTransporter() Transporter { |
|||
return &sshTunnelTransporter{ |
|||
sessions: make(map[string]*sshSession), |
|||
} |
|||
} |
|||
|
|||
func (tr *sshTunnelTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) { |
|||
opts := &DialOptions{} |
|||
for _, option := range options { |
|||
option(opts) |
|||
} |
|||
|
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
session, ok := tr.sessions[addr] |
|||
if !ok || session.Closed() { |
|||
if opts.Chain == nil { |
|||
conn, err = net.DialTimeout("tcp", addr, opts.Timeout) |
|||
} else { |
|||
conn, err = opts.Chain.Dial(addr) |
|||
} |
|||
if err != nil { |
|||
return |
|||
} |
|||
session = &sshSession{ |
|||
addr: addr, |
|||
conn: conn, |
|||
} |
|||
tr.sessions[addr] = session |
|||
} |
|||
|
|||
return session.conn, nil |
|||
} |
|||
|
|||
func (tr *sshTunnelTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { |
|||
opts := &HandshakeOptions{} |
|||
for _, option := range options { |
|||
option(opts) |
|||
} |
|||
|
|||
config := ssh.ClientConfig{ |
|||
Timeout: opts.Timeout, |
|||
HostKeyCallback: ssh.InsecureIgnoreHostKey(), |
|||
} |
|||
if opts.User != nil { |
|||
config.User = opts.User.Username() |
|||
password, _ := opts.User.Password() |
|||
config.Auth = []ssh.AuthMethod{ |
|||
ssh.Password(password), |
|||
} |
|||
} |
|||
|
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
session, ok := tr.sessions[opts.Addr] |
|||
if session != nil && session.conn != conn { |
|||
conn.Close() |
|||
return nil, errors.New("ssh: unrecognized connection") |
|||
} |
|||
if !ok || session.client == nil { |
|||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config) |
|||
if err != nil { |
|||
conn.Close() |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, err |
|||
} |
|||
|
|||
session = &sshSession{ |
|||
addr: opts.Addr, |
|||
conn: conn, |
|||
client: ssh.NewClient(sshConn, chans, reqs), |
|||
closed: make(chan struct{}), |
|||
deaded: make(chan struct{}), |
|||
} |
|||
tr.sessions[opts.Addr] = session |
|||
go session.Ping(opts.Interval, 1) |
|||
go session.waitServer() |
|||
go session.waitClose() |
|||
} |
|||
|
|||
if session.Closed() { |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, errSessionDead |
|||
} |
|||
|
|||
channel, reqs, err := session.client.OpenChannel(GostSSHTunnelRequest, nil) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
go ssh.DiscardRequests(reqs) |
|||
return &sshConn{channel: channel, conn: conn}, nil |
|||
} |
|||
|
|||
func (tr *sshTunnelTransporter) Multiplex() bool { |
|||
return true |
|||
} |
|||
|
|||
type sshSession struct { |
|||
addr string |
|||
conn net.Conn |
|||
client *ssh.Client |
|||
closed chan struct{} |
|||
deaded chan struct{} |
|||
once sync.Once |
|||
connChan chan net.Conn |
|||
} |
|||
|
|||
func (s *sshSession) Ping(interval time.Duration, retries int) { |
|||
if interval <= 0 { |
|||
return |
|||
} |
|||
defer close(s.deaded) |
|||
|
|||
log.Log("[ssh] ping is enabled, interval:", interval) |
|||
baseCtx := context.Background() |
|||
t := time.NewTicker(interval) |
|||
defer t.Stop() |
|||
|
|||
for { |
|||
select { |
|||
case <-t.C: |
|||
start := time.Now() |
|||
//if Debug {
|
|||
log.Log("[ssh] sending ping") |
|||
//}
|
|||
ctx, cancel := context.WithTimeout(baseCtx, time.Second*30) |
|||
var err error |
|||
select { |
|||
case err = <-s.sendPing(): |
|||
case <-ctx.Done(): |
|||
err = errors.New("Timeout") |
|||
} |
|||
cancel() |
|||
if err != nil { |
|||
log.Log("[ssh] ping:", err) |
|||
return |
|||
} |
|||
//if Debug {
|
|||
log.Log("[ssh] ping OK, RTT:", time.Since(start)) |
|||
//}
|
|||
|
|||
case <-s.closed: |
|||
return |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (s *sshSession) sendPing() <-chan error { |
|||
ch := make(chan error, 1) |
|||
go func() { |
|||
if _, _, err := s.client.SendRequest("ping", true, nil); err != nil { |
|||
ch <- err |
|||
} |
|||
close(ch) |
|||
}() |
|||
return ch |
|||
} |
|||
|
|||
func (s *sshSession) waitServer() error { |
|||
defer close(s.closed) |
|||
return s.client.Wait() |
|||
} |
|||
|
|||
func (s *sshSession) waitClose() { |
|||
defer s.client.Close() |
|||
|
|||
select { |
|||
case <-s.deaded: |
|||
case <-s.closed: |
|||
} |
|||
} |
|||
|
|||
func (s *sshSession) Closed() bool { |
|||
select { |
|||
case <-s.deaded: |
|||
return true |
|||
case <-s.closed: |
|||
return true |
|||
default: |
|||
} |
|||
return false |
|||
} |
|||
|
|||
type sshForwardHandler struct { |
|||
options *HandlerOptions |
|||
config *ssh.ServerConfig |
|||
} |
|||
|
|||
func SSHForwardHandler(opts ...HandlerOption) Handler { |
|||
h := &sshForwardHandler{ |
|||
options: new(HandlerOptions), |
|||
config: new(ssh.ServerConfig), |
|||
} |
|||
for _, opt := range opts { |
|||
opt(h.options) |
|||
} |
|||
h.config.PasswordCallback = defaultSSHPasswordCallback(h.options.Users...) |
|||
if len(h.options.Users) == 0 { |
|||
h.config.NoClientAuth = true |
|||
} |
|||
if h.options.TLSConfig != nil && len(h.options.TLSConfig.Certificates) > 0 { |
|||
signer, err := ssh.NewSignerFromKey(h.options.TLSConfig.Certificates[0].PrivateKey) |
|||
if err != nil { |
|||
log.Log("[ssh-forward]", err) |
|||
} |
|||
h.config.AddHostKey(signer) |
|||
} |
|||
|
|||
return h |
|||
} |
|||
|
|||
func (h *sshForwardHandler) Handle(conn net.Conn) { |
|||
sshConn, chans, reqs, err := ssh.NewServerConn(conn, h.config) |
|||
if err != nil { |
|||
log.Logf("[ssh-forward] %s -> %s : %s", conn.RemoteAddr(), h.options.Addr, err) |
|||
conn.Close() |
|||
return |
|||
} |
|||
defer sshConn.Close() |
|||
|
|||
log.Logf("[ssh-forward] %s <-> %s", conn.RemoteAddr(), h.options.Addr) |
|||
h.handleForward(sshConn, chans, reqs) |
|||
log.Logf("[ssh-forward] %s >-< %s", conn.RemoteAddr(), h.options.Addr) |
|||
} |
|||
|
|||
func (h *sshForwardHandler) handleForward(conn ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) { |
|||
quit := make(chan struct{}) |
|||
defer close(quit) // quit signal
|
|||
|
|||
go func() { |
|||
for req := range reqs { |
|||
switch req.Type { |
|||
case RemoteForwardRequest: |
|||
go h.tcpipForwardRequest(conn, req, quit) |
|||
default: |
|||
// log.Log("[ssh] unknown channel type:", req.Type)
|
|||
if req.WantReply { |
|||
req.Reply(false, nil) |
|||
} |
|||
} |
|||
} |
|||
}() |
|||
|
|||
go func() { |
|||
for newChannel := range chans { |
|||
// Check the type of channel
|
|||
t := newChannel.ChannelType() |
|||
switch t { |
|||
case DirectForwardRequest: |
|||
channel, requests, err := newChannel.Accept() |
|||
if err != nil { |
|||
log.Log("[ssh] Could not accept channel:", err) |
|||
continue |
|||
} |
|||
p := directForward{} |
|||
ssh.Unmarshal(newChannel.ExtraData(), &p) |
|||
|
|||
if p.Host1 == "<nil>" { |
|||
p.Host1 = "" |
|||
} |
|||
|
|||
go ssh.DiscardRequests(requests) |
|||
go h.directPortForwardChannel(channel, fmt.Sprintf("%s:%d", p.Host1, p.Port1)) |
|||
default: |
|||
log.Log("[ssh] Unknown channel type:", t) |
|||
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t)) |
|||
} |
|||
} |
|||
}() |
|||
|
|||
conn.Wait() |
|||
} |
|||
|
|||
func (h *sshForwardHandler) directPortForwardChannel(channel ssh.Channel, raddr string) { |
|||
defer channel.Close() |
|||
|
|||
log.Logf("[ssh-tcp] %s - %s", h.options.Addr, raddr) |
|||
|
|||
if !Can("tcp", raddr, h.options.Whitelist, h.options.Blacklist) { |
|||
log.Logf("[ssh-tcp] Unauthorized to tcp connect to %s", raddr) |
|||
return |
|||
} |
|||
|
|||
conn, err := h.options.Chain.Dial(raddr) |
|||
if err != nil { |
|||
log.Logf("[ssh-tcp] %s - %s : %s", h.options.Addr, raddr, err) |
|||
return |
|||
} |
|||
defer conn.Close() |
|||
|
|||
log.Logf("[ssh-tcp] %s <-> %s", h.options.Addr, raddr) |
|||
transport(conn, channel) |
|||
log.Logf("[ssh-tcp] %s >-< %s", h.options.Addr, raddr) |
|||
} |
|||
|
|||
// tcpipForward is structure for RFC 4254 7.1 "tcpip-forward" request
|
|||
type tcpipForward struct { |
|||
Host string |
|||
Port uint32 |
|||
} |
|||
|
|||
func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit <-chan struct{}) { |
|||
t := tcpipForward{} |
|||
ssh.Unmarshal(req.Payload, &t) |
|||
|
|||
addr := fmt.Sprintf("%s:%d", t.Host, t.Port) |
|||
|
|||
if !Can("rtcp", addr, h.options.Whitelist, h.options.Blacklist) { |
|||
log.Logf("[ssh-rtcp] Unauthorized to tcp bind to %s", addr) |
|||
req.Reply(false, nil) |
|||
return |
|||
} |
|||
|
|||
log.Log("[ssh-rtcp] listening on tcp", addr) |
|||
ln, err := net.Listen("tcp", addr) //tie to the client connection
|
|||
if err != nil { |
|||
log.Log("[ssh-rtcp]", err) |
|||
req.Reply(false, nil) |
|||
return |
|||
} |
|||
defer ln.Close() |
|||
|
|||
replyFunc := func() error { |
|||
if t.Port == 0 && req.WantReply { // Client sent port 0. let them know which port is actually being used
|
|||
_, port, err := getHostPortFromAddr(ln.Addr()) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
var b [4]byte |
|||
binary.BigEndian.PutUint32(b[:], uint32(port)) |
|||
t.Port = uint32(port) |
|||
return req.Reply(true, b[:]) |
|||
} |
|||
return req.Reply(true, nil) |
|||
} |
|||
if err := replyFunc(); err != nil { |
|||
log.Log("[ssh-rtcp]", err) |
|||
return |
|||
} |
|||
|
|||
go func() { |
|||
for { |
|||
conn, err := ln.Accept() |
|||
if err != nil { // Unable to accept new connection - listener is likely closed
|
|||
return |
|||
} |
|||
|
|||
go func(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
p := directForward{} |
|||
var err error |
|||
|
|||
var portnum int |
|||
p.Host1 = t.Host |
|||
p.Port1 = t.Port |
|||
p.Host2, portnum, err = getHostPortFromAddr(conn.RemoteAddr()) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
p.Port2 = uint32(portnum) |
|||
ch, reqs, err := sshConn.OpenChannel(ForwardedTCPReturnRequest, ssh.Marshal(p)) |
|||
if err != nil { |
|||
log.Log("[ssh-rtcp] open forwarded channel:", err) |
|||
return |
|||
} |
|||
defer ch.Close() |
|||
go ssh.DiscardRequests(reqs) |
|||
|
|||
log.Logf("[ssh-rtcp] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr()) |
|||
transport(ch, conn) |
|||
log.Logf("[ssh-rtcp] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr()) |
|||
}(conn) |
|||
} |
|||
}() |
|||
|
|||
<-quit |
|||
} |
|||
|
|||
// SSHConfig holds the SSH tunnel server config
|
|||
type SSHConfig struct { |
|||
Users []*url.Userinfo |
|||
TLSConfig *tls.Config |
|||
} |
|||
|
|||
type sshTunnelListener struct { |
|||
net.Listener |
|||
config *ssh.ServerConfig |
|||
connChan chan net.Conn |
|||
errChan chan error |
|||
} |
|||
|
|||
// SSHTunnelListener creates a Listener for SSH tunnel server.
|
|||
func SSHTunnelListener(addr string, config *SSHConfig) (Listener, error) { |
|||
ln, err := net.Listen("tcp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
if config == nil { |
|||
config = &SSHConfig{} |
|||
} |
|||
|
|||
sshConfig := &ssh.ServerConfig{} |
|||
sshConfig.PasswordCallback = defaultSSHPasswordCallback(config.Users...) |
|||
if len(config.Users) == 0 { |
|||
sshConfig.NoClientAuth = true |
|||
} |
|||
if config.TLSConfig == nil { |
|||
cert, err := tls.X509KeyPair(defaultRawCert, defaultRawKey) |
|||
if err != nil { |
|||
ln.Close() |
|||
return nil, err |
|||
} |
|||
config.TLSConfig = &tls.Config{ |
|||
Certificates: []tls.Certificate{cert}, |
|||
} |
|||
} |
|||
|
|||
signer, err := ssh.NewSignerFromKey(config.TLSConfig.Certificates[0].PrivateKey) |
|||
if err != nil { |
|||
ln.Close() |
|||
return nil, err |
|||
|
|||
} |
|||
sshConfig.AddHostKey(signer) |
|||
|
|||
l := &sshTunnelListener{ |
|||
Listener: ln, |
|||
config: sshConfig, |
|||
connChan: make(chan net.Conn, 1024), |
|||
errChan: make(chan error, 1), |
|||
} |
|||
|
|||
go l.listenLoop() |
|||
|
|||
return l, nil |
|||
} |
|||
|
|||
func (l *sshTunnelListener) listenLoop() { |
|||
for { |
|||
conn, err := l.Listener.Accept() |
|||
if err != nil { |
|||
log.Log("[ssh] accept:", err) |
|||
l.errChan <- err |
|||
close(l.errChan) |
|||
return |
|||
} |
|||
go l.serveConn(conn) |
|||
} |
|||
} |
|||
|
|||
func (l *sshTunnelListener) serveConn(conn net.Conn) { |
|||
sc, chans, reqs, err := ssh.NewServerConn(conn, l.config) |
|||
if err != nil { |
|||
log.Logf("[ssh] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) |
|||
conn.Close() |
|||
return |
|||
} |
|||
defer sc.Close() |
|||
|
|||
go ssh.DiscardRequests(reqs) |
|||
go func() { |
|||
for newChannel := range chans { |
|||
// Check the type of channel
|
|||
t := newChannel.ChannelType() |
|||
switch t { |
|||
case GostSSHTunnelRequest: |
|||
channel, requests, err := newChannel.Accept() |
|||
if err != nil { |
|||
log.Log("[ssh] Could not accept channel:", err) |
|||
continue |
|||
} |
|||
go ssh.DiscardRequests(requests) |
|||
cc := &sshConn{conn: conn, channel: channel} |
|||
select { |
|||
case l.connChan <- cc: |
|||
default: |
|||
cc.Close() |
|||
log.Logf("[ssh] %s - %s: connection queue is full", conn.RemoteAddr(), l.Addr()) |
|||
} |
|||
|
|||
default: |
|||
log.Log("[ssh] Unknown channel type:", t) |
|||
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t)) |
|||
} |
|||
} |
|||
}() |
|||
|
|||
log.Logf("[ssh] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr()) |
|||
sc.Wait() |
|||
log.Logf("[ssh] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr()) |
|||
} |
|||
|
|||
func (l *sshTunnelListener) 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 |
|||
} |
|||
|
|||
// directForward is structure for RFC 4254 7.2 - can be used for "forwarded-tcpip" and "direct-tcpip"
|
|||
type directForward struct { |
|||
Host1 string |
|||
Port1 uint32 |
|||
Host2 string |
|||
Port2 uint32 |
|||
} |
|||
|
|||
func (p directForward) String() string { |
|||
return fmt.Sprintf("%s:%d -> %s:%d", p.Host2, p.Port2, p.Host1, p.Port1) |
|||
} |
|||
|
|||
func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) { |
|||
host, portString, err := net.SplitHostPort(addr.String()) |
|||
if err != nil { |
|||
return |
|||
} |
|||
port, err = strconv.Atoi(portString) |
|||
return |
|||
} |
|||
|
|||
type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) |
|||
|
|||
func defaultSSHPasswordCallback(users ...*url.Userinfo) PasswordCallbackFunc { |
|||
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { |
|||
for _, user := range users { |
|||
u := user.Username() |
|||
p, _ := user.Password() |
|||
if u == conn.User() && p == string(password) { |
|||
return nil, nil |
|||
} |
|||
} |
|||
log.Logf("[ssh] %s -> %s : password rejected for %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User()) |
|||
return nil, fmt.Errorf("password rejected for %s", conn.User()) |
|||
} |
|||
} |
|||
|
|||
type sshNopConn struct { |
|||
session *sshSession |
|||
} |
|||
|
|||
func (c *sshNopConn) Read(b []byte) (n int, err error) { |
|||
return 0, &net.OpError{Op: "read", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("read not supported")} |
|||
} |
|||
|
|||
func (c *sshNopConn) Write(b []byte) (n int, err error) { |
|||
return 0, &net.OpError{Op: "write", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("write not supported")} |
|||
} |
|||
|
|||
func (c *sshNopConn) Close() error { |
|||
return nil |
|||
} |
|||
|
|||
func (c *sshNopConn) LocalAddr() net.Addr { |
|||
return &net.TCPAddr{ |
|||
IP: net.IPv4zero, |
|||
Port: 0, |
|||
} |
|||
} |
|||
|
|||
func (c *sshNopConn) RemoteAddr() net.Addr { |
|||
return &net.TCPAddr{ |
|||
IP: net.IPv4zero, |
|||
Port: 0, |
|||
} |
|||
} |
|||
|
|||
func (c *sshNopConn) SetDeadline(t time.Time) error { |
|||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
|||
} |
|||
|
|||
func (c *sshNopConn) SetReadDeadline(t time.Time) error { |
|||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
|||
} |
|||
|
|||
func (c *sshNopConn) SetWriteDeadline(t time.Time) error { |
|||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
|||
} |
|||
|
|||
type sshConn struct { |
|||
channel ssh.Channel |
|||
conn net.Conn |
|||
} |
|||
|
|||
func (c *sshConn) Read(b []byte) (n int, err error) { |
|||
return c.channel.Read(b) |
|||
} |
|||
|
|||
func (c *sshConn) Write(b []byte) (n int, err error) { |
|||
return c.channel.Write(b) |
|||
} |
|||
|
|||
func (c *sshConn) Close() error { |
|||
return c.channel.Close() |
|||
} |
|||
|
|||
func (c *sshConn) LocalAddr() net.Addr { |
|||
return c.conn.LocalAddr() |
|||
} |
|||
|
|||
func (c *sshConn) RemoteAddr() net.Addr { |
|||
return c.conn.RemoteAddr() |
|||
} |
|||
|
|||
func (c *sshConn) SetDeadline(t time.Time) error { |
|||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
|||
} |
|||
|
|||
func (c *sshConn) SetReadDeadline(t time.Time) error { |
|||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
|||
} |
|||
|
|||
func (c *sshConn) SetWriteDeadline(t time.Time) error { |
|||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
|||
} |
|||
@ -1,295 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"crypto/tls" |
|||
"net" |
|||
"net/http" |
|||
"net/http/httputil" |
|||
"time" |
|||
|
|||
"net/url" |
|||
|
|||
"github.com/go-log/log" |
|||
"gopkg.in/gorilla/websocket.v1" |
|||
) |
|||
|
|||
// WSOptions describes the options for websocket.
|
|||
type WSOptions struct { |
|||
ReadBufferSize int |
|||
WriteBufferSize int |
|||
HandshakeTimeout time.Duration |
|||
EnableCompression bool |
|||
} |
|||
|
|||
type websocketConn struct { |
|||
conn *websocket.Conn |
|||
rb []byte |
|||
} |
|||
|
|||
func websocketClientConn(url string, conn net.Conn, tlsConfig *tls.Config, options *WSOptions) (net.Conn, error) { |
|||
if options == nil { |
|||
options = &WSOptions{} |
|||
} |
|||
dialer := websocket.Dialer{ |
|||
ReadBufferSize: options.ReadBufferSize, |
|||
WriteBufferSize: options.WriteBufferSize, |
|||
TLSClientConfig: tlsConfig, |
|||
HandshakeTimeout: options.HandshakeTimeout, |
|||
EnableCompression: options.EnableCompression, |
|||
NetDial: func(net, addr string) (net.Conn, error) { |
|||
return conn, nil |
|||
}, |
|||
} |
|||
c, resp, err := dialer.Dial(url, nil) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
resp.Body.Close() |
|||
return &websocketConn{conn: c}, nil |
|||
} |
|||
|
|||
func websocketServerConn(conn *websocket.Conn) net.Conn { |
|||
// conn.EnableWriteCompression(true)
|
|||
return &websocketConn{ |
|||
conn: conn, |
|||
} |
|||
} |
|||
|
|||
func (c *websocketConn) Read(b []byte) (n int, err error) { |
|||
if len(c.rb) == 0 { |
|||
_, c.rb, err = c.conn.ReadMessage() |
|||
} |
|||
n = copy(b, c.rb) |
|||
c.rb = c.rb[n:] |
|||
return |
|||
} |
|||
|
|||
func (c *websocketConn) Write(b []byte) (n int, err error) { |
|||
err = c.conn.WriteMessage(websocket.BinaryMessage, b) |
|||
n = len(b) |
|||
return |
|||
} |
|||
|
|||
func (c *websocketConn) Close() error { |
|||
return c.conn.Close() |
|||
} |
|||
|
|||
func (c *websocketConn) LocalAddr() net.Addr { |
|||
return c.conn.LocalAddr() |
|||
} |
|||
|
|||
func (c *websocketConn) RemoteAddr() net.Addr { |
|||
return c.conn.RemoteAddr() |
|||
} |
|||
|
|||
func (c *websocketConn) SetDeadline(t time.Time) error { |
|||
if err := c.SetReadDeadline(t); err != nil { |
|||
return err |
|||
} |
|||
return c.SetWriteDeadline(t) |
|||
} |
|||
func (c *websocketConn) SetReadDeadline(t time.Time) error { |
|||
return c.conn.SetReadDeadline(t) |
|||
} |
|||
|
|||
func (c *websocketConn) SetWriteDeadline(t time.Time) error { |
|||
return c.conn.SetWriteDeadline(t) |
|||
} |
|||
|
|||
type wsTransporter struct { |
|||
*tcpTransporter |
|||
options *WSOptions |
|||
} |
|||
|
|||
// WSTransporter creates a Transporter that is used by websocket proxy client.
|
|||
func WSTransporter(opts *WSOptions) Transporter { |
|||
return &wsTransporter{ |
|||
options: opts, |
|||
} |
|||
} |
|||
|
|||
func (tr *wsTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { |
|||
opts := &HandshakeOptions{} |
|||
for _, option := range options { |
|||
option(opts) |
|||
} |
|||
wsOptions := tr.options |
|||
if opts.WSOptions != nil { |
|||
wsOptions = opts.WSOptions |
|||
} |
|||
url := url.URL{Scheme: "ws", Host: opts.Addr, Path: "/ws"} |
|||
return websocketClientConn(url.String(), conn, nil, wsOptions) |
|||
} |
|||
|
|||
type wssTransporter struct { |
|||
*tcpTransporter |
|||
options *WSOptions |
|||
} |
|||
|
|||
// WSSTransporter creates a Transporter that is used by websocket secure proxy client.
|
|||
func WSSTransporter(opts *WSOptions) Transporter { |
|||
return &wssTransporter{ |
|||
options: opts, |
|||
} |
|||
} |
|||
|
|||
func (tr *wssTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { |
|||
opts := &HandshakeOptions{} |
|||
for _, option := range options { |
|||
option(opts) |
|||
} |
|||
wsOptions := tr.options |
|||
if opts.WSOptions != nil { |
|||
wsOptions = opts.WSOptions |
|||
} |
|||
if opts.TLSConfig == nil { |
|||
opts.TLSConfig = &tls.Config{InsecureSkipVerify: true} |
|||
} |
|||
url := url.URL{Scheme: "wss", Host: opts.Addr, Path: "/ws"} |
|||
return websocketClientConn(url.String(), conn, opts.TLSConfig, wsOptions) |
|||
} |
|||
|
|||
type wsListener struct { |
|||
addr net.Addr |
|||
upgrader *websocket.Upgrader |
|||
srv *http.Server |
|||
connChan chan net.Conn |
|||
errChan chan error |
|||
} |
|||
|
|||
// WSListener creates a Listener for websocket proxy server.
|
|||
func WSListener(addr string, options *WSOptions) (Listener, error) { |
|||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if options == nil { |
|||
options = &WSOptions{} |
|||
} |
|||
l := &wsListener{ |
|||
addr: tcpAddr, |
|||
upgrader: &websocket.Upgrader{ |
|||
ReadBufferSize: options.ReadBufferSize, |
|||
WriteBufferSize: options.WriteBufferSize, |
|||
CheckOrigin: func(r *http.Request) bool { return true }, |
|||
EnableCompression: options.EnableCompression, |
|||
}, |
|||
connChan: make(chan net.Conn, 1024), |
|||
errChan: make(chan error, 1), |
|||
} |
|||
|
|||
mux := http.NewServeMux() |
|||
mux.Handle("/ws", http.HandlerFunc(l.upgrade)) |
|||
l.srv = &http.Server{Addr: addr, Handler: mux} |
|||
|
|||
ln, err := net.ListenTCP("tcp", tcpAddr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
go func() { |
|||
err := l.srv.Serve(tcpKeepAliveListener{ln}) |
|||
if err != nil { |
|||
l.errChan <- err |
|||
} |
|||
close(l.errChan) |
|||
}() |
|||
select { |
|||
case err := <-l.errChan: |
|||
return nil, err |
|||
default: |
|||
} |
|||
|
|||
return l, nil |
|||
} |
|||
|
|||
func (l *wsListener) upgrade(w http.ResponseWriter, r *http.Request) { |
|||
log.Logf("[ws] %s -> %s", r.RemoteAddr, l.addr) |
|||
if Debug { |
|||
dump, _ := httputil.DumpRequest(r, false) |
|||
log.Log(string(dump)) |
|||
} |
|||
conn, err := l.upgrader.Upgrade(w, r, nil) |
|||
if err != nil { |
|||
log.Logf("[ws] %s - %s : %s", r.RemoteAddr, l.addr, err) |
|||
return |
|||
} |
|||
select { |
|||
case l.connChan <- websocketServerConn(conn): |
|||
default: |
|||
conn.Close() |
|||
log.Logf("[ws] %s - %s: connection queue is full", r.RemoteAddr, l.addr) |
|||
} |
|||
} |
|||
|
|||
func (l *wsListener) Accept() (conn net.Conn, err error) { |
|||
select { |
|||
case conn = <-l.connChan: |
|||
case err = <-l.errChan: |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (l *wsListener) Close() error { |
|||
return l.srv.Close() |
|||
} |
|||
|
|||
func (l *wsListener) Addr() net.Addr { |
|||
return l.addr |
|||
} |
|||
|
|||
type wssListener struct { |
|||
*wsListener |
|||
} |
|||
|
|||
// WSSListener creates a Listener for websocket secure proxy server.
|
|||
func WSSListener(addr string, tlsConfig *tls.Config, options *WSOptions) (Listener, error) { |
|||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if options == nil { |
|||
options = &WSOptions{} |
|||
} |
|||
l := &wssListener{ |
|||
wsListener: &wsListener{ |
|||
addr: tcpAddr, |
|||
upgrader: &websocket.Upgrader{ |
|||
ReadBufferSize: options.ReadBufferSize, |
|||
WriteBufferSize: options.WriteBufferSize, |
|||
CheckOrigin: func(r *http.Request) bool { return true }, |
|||
EnableCompression: options.EnableCompression, |
|||
}, |
|||
connChan: make(chan net.Conn, 1024), |
|||
errChan: make(chan error, 1), |
|||
}, |
|||
} |
|||
|
|||
mux := http.NewServeMux() |
|||
mux.Handle("/ws", http.HandlerFunc(l.upgrade)) |
|||
l.srv = &http.Server{ |
|||
Addr: addr, |
|||
TLSConfig: tlsConfig, |
|||
Handler: mux, |
|||
} |
|||
|
|||
ln, err := net.ListenTCP("tcp", tcpAddr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
go func() { |
|||
err := l.srv.Serve(tls.NewListener(tcpKeepAliveListener{ln}, tlsConfig)) |
|||
if err != nil { |
|||
l.errChan <- err |
|||
} |
|||
close(l.errChan) |
|||
}() |
|||
select { |
|||
case err := <-l.errChan: |
|||
return nil, err |
|||
default: |
|||
} |
|||
|
|||
return l, nil |
|||
} |
|||
@ -1,32 +1,114 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"bufio" |
|||
"crypto/tls" |
|||
"net" |
|||
"net/url" |
|||
|
|||
"github.com/ginuerzh/gosocks4" |
|||
"github.com/ginuerzh/gosocks5" |
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
// Handler is a proxy server handler
|
|||
type Handler interface { |
|||
Handle(net.Conn) |
|||
} |
|||
|
|||
type defaultHandler struct { |
|||
server Server |
|||
// HandlerOptions describes the options for Handler.
|
|||
type HandlerOptions struct { |
|||
Addr string |
|||
Chain *Chain |
|||
Users []*url.Userinfo |
|||
TLSConfig *tls.Config |
|||
Whitelist *Permissions |
|||
Blacklist *Permissions |
|||
} |
|||
|
|||
// HandlerOption allows a common way to set handler options.
|
|||
type HandlerOption func(opts *HandlerOptions) |
|||
|
|||
// AddrHandlerOption sets the Addr option of HandlerOptions.
|
|||
func AddrHandlerOption(addr string) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Addr = addr |
|||
} |
|||
} |
|||
|
|||
// ChainHandlerOption sets the Chain option of HandlerOptions.
|
|||
func ChainHandlerOption(chain *Chain) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Chain = chain |
|||
} |
|||
} |
|||
|
|||
func DefaultHandler(server Server) Handler { |
|||
return &defaultHandler{server: server} |
|||
// UsersHandlerOption sets the Users option of HandlerOptions.
|
|||
func UsersHandlerOption(users ...*url.Userinfo) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Users = users |
|||
} |
|||
} |
|||
|
|||
func (h *defaultHandler) Handle(conn net.Conn) { |
|||
var handler Handler |
|||
// TLSConfigHandlerOption sets the TLSConfig option of HandlerOptions.
|
|||
func TLSConfigHandlerOption(config *tls.Config) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.TLSConfig = config |
|||
} |
|||
} |
|||
|
|||
switch h.server.Options().BaseOptions().Protocol { |
|||
case "http": |
|||
handler = HTTPHandler(h.server) |
|||
case "socks", "socks5": |
|||
case "ss": // shadowsocks
|
|||
handler = ShadowHandler(h.server) |
|||
// WhitelistHandlerOption sets the Whitelist option of HandlerOptions.
|
|||
func WhitelistHandlerOption(whitelist *Permissions) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Whitelist = whitelist |
|||
} |
|||
} |
|||
|
|||
// BlacklistHandlerOption sets the Blacklist option of HandlerOptions.
|
|||
func BlacklistHandlerOption(blacklist *Permissions) HandlerOption { |
|||
return func(opts *HandlerOptions) { |
|||
opts.Blacklist = blacklist |
|||
} |
|||
} |
|||
|
|||
type autoHandler struct { |
|||
options []HandlerOption |
|||
} |
|||
|
|||
// AutoHandler creates a server Handler for auto proxy server.
|
|||
func AutoHandler(opts ...HandlerOption) Handler { |
|||
h := &autoHandler{ |
|||
options: opts, |
|||
} |
|||
return h |
|||
} |
|||
|
|||
func (h *autoHandler) Handle(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
br := bufio.NewReader(conn) |
|||
b, err := br.Peek(1) |
|||
if err != nil { |
|||
log.Log(err) |
|||
return |
|||
} |
|||
|
|||
cc := &bufferdConn{Conn: conn, br: br} |
|||
switch b[0] { |
|||
case gosocks4.Ver4: |
|||
SOCKS4Handler(h.options...).Handle(cc) |
|||
case gosocks5.Ver5: |
|||
SOCKS5Handler(h.options...).Handle(cc) |
|||
default: // http
|
|||
HTTPHandler(h.options...).Handle(cc) |
|||
} |
|||
} |
|||
|
|||
type bufferdConn struct { |
|||
net.Conn |
|||
br *bufio.Reader |
|||
} |
|||
|
|||
handler.Handle(conn) |
|||
func (c *bufferdConn) Read(b []byte) (int, error) { |
|||
return c.br.Read(b) |
|||
} |
|||
|
|||
@ -1,43 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"github.com/stretchr/testify/assert" |
|||
) |
|||
|
|||
func TestNodeDefaultWhitelist(t *testing.T) { |
|||
assert := assert.New(t) |
|||
|
|||
node, _ := ParseProxyNode("http2://localhost:8000") |
|||
|
|||
assert.True(node.Can("connect", "google.pl:80")) |
|||
assert.True(node.Can("connect", "google.pl:443")) |
|||
assert.True(node.Can("connect", "google.pl:22")) |
|||
assert.True(node.Can("bind", "google.pl:80")) |
|||
assert.True(node.Can("bind", "google.com:80")) |
|||
} |
|||
|
|||
func TestNodeWhitelist(t *testing.T) { |
|||
assert := assert.New(t) |
|||
|
|||
node, _ := ParseProxyNode("http2://localhost:8000?whitelist=connect:google.pl:80,443") |
|||
|
|||
assert.True(node.Can("connect", "google.pl:80")) |
|||
assert.True(node.Can("connect", "google.pl:443")) |
|||
assert.False(node.Can("connect", "google.pl:22")) |
|||
assert.False(node.Can("bind", "google.pl:80")) |
|||
assert.False(node.Can("bind", "google.com:80")) |
|||
} |
|||
|
|||
func TestNodeBlacklist(t *testing.T) { |
|||
assert := assert.New(t) |
|||
|
|||
node, _ := ParseProxyNode("http2://localhost:8000?blacklist=connect:google.pl:80,443") |
|||
|
|||
assert.False(node.Can("connect", "google.pl:80")) |
|||
assert.False(node.Can("connect", "google.pl:443")) |
|||
assert.True(node.Can("connect", "google.pl:22")) |
|||
assert.True(node.Can("bind", "google.pl:80")) |
|||
assert.True(node.Can("bind", "google.com:80")) |
|||
} |
|||
@ -1,81 +1,241 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"bufio" |
|||
"crypto/tls" |
|||
"github.com/golang/glog" |
|||
"github.com/lucas-clemente/quic-go/h2quic" |
|||
"io" |
|||
"net/http" |
|||
"net/http/httputil" |
|||
"errors" |
|||
"net" |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/go-log/log" |
|||
quic "github.com/lucas-clemente/quic-go" |
|||
) |
|||
|
|||
type QuicServer struct { |
|||
Base *ProxyServer |
|||
Handler http.Handler |
|||
TLSConfig *tls.Config |
|||
type quicSession struct { |
|||
conn net.Conn |
|||
session quic.Session |
|||
} |
|||
|
|||
func (session *quicSession) GetConn() (*quicConn, error) { |
|||
stream, err := session.session.OpenStream() |
|||
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.Close(nil) |
|||
} |
|||
|
|||
func NewQuicServer(base *ProxyServer) *QuicServer { |
|||
return &QuicServer{Base: base} |
|||
type quicTransporter struct { |
|||
config *QUICConfig |
|||
sessionMutex sync.Mutex |
|||
sessions map[string]*quicSession |
|||
} |
|||
|
|||
func (s *QuicServer) ListenAndServeTLS(config *tls.Config) error { |
|||
server := &h2quic.Server{ |
|||
Server: &http.Server{ |
|||
Addr: s.Base.Node.Addr, |
|||
Handler: s.Handler, |
|||
TLSConfig: config, |
|||
}, |
|||
// QUICTransporter creates a Transporter that is used by QUIC proxy client.
|
|||
func QUICTransporter(config *QUICConfig) Transporter { |
|||
if config == nil { |
|||
config = &QUICConfig{} |
|||
} |
|||
if server.Handler == nil { |
|||
// server.Handler = http.HandlerFunc(s.HandleRequest)
|
|||
server.Handler = http.HandlerFunc(NewHttp2Server(s.Base).HandleRequest) |
|||
return &quicTransporter{ |
|||
config: config, |
|||
sessions: make(map[string]*quicSession), |
|||
} |
|||
return server.ListenAndServe() |
|||
} |
|||
|
|||
func (s *QuicServer) HandleRequest(w http.ResponseWriter, req *http.Request) { |
|||
target := req.Host |
|||
glog.V(LINFO).Infof("[quic] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto) |
|||
func (tr *quicTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) { |
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
if glog.V(LDEBUG) { |
|||
dump, _ := httputil.DumpRequest(req, false) |
|||
glog.Infoln(string(dump)) |
|||
session, ok := tr.sessions[addr] |
|||
if !ok { |
|||
conn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) |
|||
if err != nil { |
|||
return |
|||
} |
|||
session = &quicSession{conn: conn} |
|||
tr.sessions[addr] = session |
|||
} |
|||
return session.conn, nil |
|||
} |
|||
|
|||
func (tr *quicTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { |
|||
opts := &HandshakeOptions{} |
|||
for _, option := range options { |
|||
option(opts) |
|||
} |
|||
config := tr.config |
|||
if opts.QUICConfig != nil { |
|||
config = opts.QUICConfig |
|||
} |
|||
if config.TLSConfig == nil { |
|||
config.TLSConfig = &tls.Config{InsecureSkipVerify: true} |
|||
} |
|||
|
|||
tr.sessionMutex.Lock() |
|||
defer tr.sessionMutex.Unlock() |
|||
|
|||
c, err := s.Base.Chain.Dial(target) |
|||
session, ok := tr.sessions[opts.Addr] |
|||
if session != nil && session.conn != conn { |
|||
conn.Close() |
|||
return nil, errors.New("quic: unrecognized connection") |
|||
} |
|||
if !ok || session.session == nil { |
|||
s, err := tr.initSession(opts.Addr, conn, config) |
|||
if err != nil { |
|||
conn.Close() |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, err |
|||
} |
|||
session = s |
|||
tr.sessions[opts.Addr] = session |
|||
} |
|||
cc, err := session.GetConn() |
|||
if err != nil { |
|||
glog.V(LWARNING).Infof("[quic] %s -> %s : %s", req.RemoteAddr, target, err) |
|||
w.WriteHeader(http.StatusServiceUnavailable) |
|||
return |
|||
session.Close() |
|||
delete(tr.sessions, opts.Addr) |
|||
return nil, err |
|||
} |
|||
defer c.Close() |
|||
|
|||
glog.V(LINFO).Infof("[quic] %s <-> %s", req.RemoteAddr, target) |
|||
return cc, nil |
|||
} |
|||
|
|||
req.Header.Set("Connection", "Keep-Alive") |
|||
if err = req.Write(c); err != nil { |
|||
glog.V(LWARNING).Infof("[quic] %s -> %s : %s", req.RemoteAddr, target, err) |
|||
return |
|||
func (tr *quicTransporter) initSession(addr string, conn net.Conn, config *QUICConfig) (*quicSession, error) { |
|||
udpConn, ok := conn.(*net.UDPConn) |
|||
if !ok { |
|||
return nil, errors.New("quic: wrong connection type") |
|||
} |
|||
udpAddr, err := net.ResolveUDPAddr("udp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
quicConfig := &quic.Config{ |
|||
HandshakeTimeout: config.Timeout, |
|||
KeepAlive: config.KeepAlive, |
|||
} |
|||
session, err := quic.Dial(udpConn, udpAddr, addr, config.TLSConfig, quicConfig) |
|||
if err != nil { |
|||
log.Log("quic dial", err) |
|||
return nil, err |
|||
} |
|||
return &quicSession{conn: conn, session: session}, nil |
|||
} |
|||
|
|||
func (tr *quicTransporter) Multiplex() bool { |
|||
return true |
|||
} |
|||
|
|||
type QUICConfig struct { |
|||
TLSConfig *tls.Config |
|||
Timeout time.Duration |
|||
KeepAlive bool |
|||
} |
|||
|
|||
resp, err := http.ReadResponse(bufio.NewReader(c), req) |
|||
type quicListener struct { |
|||
ln quic.Listener |
|||
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{ |
|||
HandshakeTimeout: config.Timeout, |
|||
KeepAlive: config.KeepAlive, |
|||
} |
|||
|
|||
tlsConfig := config.TLSConfig |
|||
if tlsConfig == nil { |
|||
tlsConfig = DefaultTLSConfig |
|||
} |
|||
ln, err := quic.ListenAddr(addr, tlsConfig, quicConfig) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln(err) |
|||
return |
|||
return nil, err |
|||
} |
|||
|
|||
l := &quicListener{ |
|||
ln: ln, |
|||
connChan: make(chan net.Conn, 1024), |
|||
errChan: make(chan error, 1), |
|||
} |
|||
defer resp.Body.Close() |
|||
go l.listenLoop() |
|||
|
|||
for k, v := range resp.Header { |
|||
for _, vv := range v { |
|||
w.Header().Add(k, vv) |
|||
return l, nil |
|||
} |
|||
|
|||
func (l *quicListener) listenLoop() { |
|||
for { |
|||
session, err := l.ln.Accept() |
|||
if err != nil { |
|||
log.Log("[quic] accept:", err) |
|||
l.errChan <- err |
|||
close(l.errChan) |
|||
return |
|||
} |
|||
go l.sessionLoop(session) |
|||
} |
|||
w.WriteHeader(resp.StatusCode) |
|||
if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil { |
|||
glog.V(LWARNING).Infof("[quic] %s <- %s : %s", req.RemoteAddr, target, err) |
|||
} |
|||
|
|||
func (l *quicListener) sessionLoop(session quic.Session) { |
|||
log.Logf("[quic] %s <-> %s", session.RemoteAddr(), session.LocalAddr()) |
|||
defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr()) |
|||
|
|||
for { |
|||
stream, err := session.AcceptStream() |
|||
if err != nil { |
|||
log.Log("[quic] accept stream:", err) |
|||
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 |
|||
} |
|||
|
|||
glog.V(LINFO).Infof("[quic] %s >-< %s", req.RemoteAddr, target) |
|||
func (c *quicConn) RemoteAddr() net.Addr { |
|||
return c.raddr |
|||
} |
|||
|
|||
@ -1,297 +1,104 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"bufio" |
|||
"crypto/tls" |
|||
"io" |
|||
"net" |
|||
"net/http" |
|||
"strconv" |
|||
"strings" |
|||
"time" |
|||
|
|||
"github.com/ginuerzh/gosocks4" |
|||
"github.com/ginuerzh/gosocks5" |
|||
"github.com/golang/glog" |
|||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" |
|||
"golang.org/x/crypto/ssh" |
|||
"github.com/go-log/log" |
|||
) |
|||
|
|||
type ProxyServer struct { |
|||
Node ProxyNode |
|||
Chain *ProxyChain |
|||
TLSConfig *tls.Config |
|||
selector *ServerSelector |
|||
cipher *ss.Cipher |
|||
ota bool |
|||
// Server is a proxy server.
|
|||
type Server struct { |
|||
} |
|||
|
|||
func NewProxyServer(node ProxyNode, chain *ProxyChain) *ProxyServer { |
|||
certFile, keyFile := node.certFile(), node.keyFile() |
|||
// Serve serves as a proxy server.
|
|||
func (s *Server) Serve(l net.Listener, h Handler) error { |
|||
defer l.Close() |
|||
|
|||
cert, err := LoadCertificate(certFile, keyFile) |
|||
if err != nil { |
|||
glog.Fatal(err) |
|||
} |
|||
|
|||
config := &tls.Config{ |
|||
Certificates: []tls.Certificate{cert}, |
|||
} |
|||
|
|||
if chain == nil { |
|||
chain = NewProxyChain() |
|||
} |
|||
|
|||
var cipher *ss.Cipher |
|||
var ota bool |
|||
if node.Protocol == "ss" || node.Transport == "ssu" { |
|||
var err error |
|||
var method, password string |
|||
|
|||
if len(node.Users) > 0 { |
|||
method = node.Users[0].Username() |
|||
password, _ = node.Users[0].Password() |
|||
} |
|||
ota = node.getBool("ota") |
|||
if strings.HasSuffix(method, "-auth") { |
|||
ota = true |
|||
method = strings.TrimSuffix(method, "-auth") |
|||
} |
|||
cipher, err = ss.NewCipher(method, password) |
|||
if l == nil { |
|||
ln, err := TCPListener(":8080") |
|||
if err != nil { |
|||
glog.Fatal(err) |
|||
return err |
|||
} |
|||
l = ln |
|||
} |
|||
return &ProxyServer{ |
|||
Node: node, |
|||
Chain: chain, |
|||
TLSConfig: config, |
|||
selector: &ServerSelector{ // socks5 server selector
|
|||
// methods that socks5 server supported
|
|||
methods: []uint8{ |
|||
gosocks5.MethodNoAuth, |
|||
gosocks5.MethodUserPass, |
|||
MethodTLS, |
|||
MethodTLSAuth, |
|||
}, |
|||
// Users: node.Users,
|
|||
TLSConfig: config, |
|||
}, |
|||
cipher: cipher, |
|||
ota: ota, |
|||
if h == nil { |
|||
h = HTTPHandler() |
|||
} |
|||
} |
|||
|
|||
func (s *ProxyServer) Serve() error { |
|||
var ln net.Listener |
|||
var err error |
|||
node := s.Node |
|||
|
|||
switch node.Transport { |
|||
case "ws": // websocket connection
|
|||
return NewWebsocketServer(s).ListenAndServe() |
|||
case "wss": // websocket security connection
|
|||
return NewWebsocketServer(s).ListenAndServeTLS(s.TLSConfig) |
|||
case "tls": // tls connection
|
|||
ln, err = tls.Listen("tcp", node.Addr, s.TLSConfig) |
|||
case "http2": // Standard HTTP2 proxy server, compatible with HTTP1.x.
|
|||
server := NewHttp2Server(s) |
|||
server.Handler = http.HandlerFunc(server.HandleRequest) |
|||
return server.ListenAndServeTLS(s.TLSConfig) |
|||
case "tcp": // Local TCP port forwarding
|
|||
return NewTcpForwardServer(s).ListenAndServe() |
|||
case "udp": // Local UDP port forwarding
|
|||
ttl, _ := strconv.Atoi(s.Node.Get("ttl")) |
|||
if ttl <= 0 { |
|||
ttl = DefaultTTL |
|||
} |
|||
return NewUdpForwardServer(s, ttl).ListenAndServe() |
|||
case "rtcp": // Remote TCP port forwarding
|
|||
return NewRTcpForwardServer(s).Serve() |
|||
case "rudp": // Remote UDP port forwarding
|
|||
return NewRUdpForwardServer(s).Serve() |
|||
case "quic": |
|||
return NewQuicServer(s).ListenAndServeTLS(s.TLSConfig) |
|||
case "kcp": |
|||
config, err := ParseKCPConfig(s.Node.Get("c")) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[kcp]", err) |
|||
} |
|||
if config == nil { |
|||
config = DefaultKCPConfig |
|||
} |
|||
// override crypt and key if specified explicitly
|
|||
if s.Node.Users != nil { |
|||
config.Crypt = s.Node.Users[0].Username() |
|||
config.Key, _ = s.Node.Users[0].Password() |
|||
} |
|||
return NewKCPServer(s, config).ListenAndServe() |
|||
case "redirect": |
|||
return NewRedsocksTCPServer(s).ListenAndServe() |
|||
case "ssu": // shadowsocks udp relay
|
|||
ttl, _ := strconv.Atoi(s.Node.Get("ttl")) |
|||
if ttl <= 0 { |
|||
ttl = DefaultTTL |
|||
} |
|||
return NewShadowUdpServer(s, ttl).ListenAndServe() |
|||
case "pht": // pure http tunnel
|
|||
return NewPureHttpServer(s).ListenAndServe() |
|||
case "ssh": // SSH tunnel
|
|||
/* |
|||
key := s.Node.Get("key") |
|||
privateBytes, err := ioutil.ReadFile(key) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[ssh]", err) |
|||
privateBytes = defaultRawKey |
|||
} |
|||
private, err := ssh.ParsePrivateKey(privateBytes) |
|||
if err != nil { |
|||
return err |
|||
var tempDelay time.Duration |
|||
for { |
|||
conn, e := l.Accept() |
|||
if e != nil { |
|||
if ne, ok := e.(net.Error); ok && ne.Temporary() { |
|||
if tempDelay == 0 { |
|||
tempDelay = 5 * time.Millisecond |
|||
} else { |
|||
tempDelay *= 2 |
|||
} |
|||
if max := 1 * time.Second; tempDelay > max { |
|||
tempDelay = max |
|||
} |
|||
log.Logf("server: Accept error: %v; retrying in %v", e, tempDelay) |
|||
time.Sleep(tempDelay) |
|||
continue |
|||
} |
|||
*/ |
|||
config := ssh.ServerConfig{ |
|||
PasswordCallback: DefaultPasswordCallback(s.Node.Users), |
|||
} |
|||
if len(s.Node.Users) == 0 { |
|||
config.NoClientAuth = true |
|||
} |
|||
signer, err := ssh.NewSignerFromKey(s.TLSConfig.Certificates[0].PrivateKey) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
config.AddHostKey(signer) |
|||
s := &SSHServer{ |
|||
Addr: node.Addr, |
|||
Base: s, |
|||
Config: &config, |
|||
return e |
|||
} |
|||
return s.ListenAndServe() |
|||
default: |
|||
ln, err = net.Listen("tcp", node.Addr) |
|||
tempDelay = 0 |
|||
go h.Handle(conn) |
|||
} |
|||
|
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
defer ln.Close() |
|||
} |
|||
|
|||
for { |
|||
conn, err := ln.Accept() |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln(err) |
|||
continue |
|||
} |
|||
// Listener is a proxy server listener, just like a net.Listener.
|
|||
type Listener interface { |
|||
net.Listener |
|||
} |
|||
|
|||
setKeepAlive(conn, KeepAliveTime) |
|||
type tcpListener struct { |
|||
net.Listener |
|||
} |
|||
|
|||
go s.handleConn(conn) |
|||
// TCPListener creates a Listener for TCP proxy server.
|
|||
func TCPListener(addr string) (Listener, error) { |
|||
ln, err := net.Listen("tcp", addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
return &tcpListener{Listener: tcpKeepAliveListener{ln.(*net.TCPListener)}}, nil |
|||
} |
|||
|
|||
func (s *ProxyServer) handleConn(conn net.Conn) { |
|||
defer conn.Close() |
|||
|
|||
switch s.Node.Protocol { |
|||
case "ss": // shadowsocks
|
|||
//server := NewShadowServer(ss.NewConn(conn, s.cipher.Copy()), s)
|
|||
//server.OTA = s.ota
|
|||
//server.Serve()
|
|||
return |
|||
case "http": |
|||
req, err := http.ReadRequest(bufio.NewReader(conn)) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[http]", err) |
|||
return |
|||
} |
|||
NewHttpServer(conn, s).HandleRequest(req) |
|||
return |
|||
case "socks", "socks5": |
|||
conn = gosocks5.ServerConn(conn, s.selector) |
|||
req, err := gosocks5.ReadRequest(conn) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[socks5]", err) |
|||
return |
|||
} |
|||
NewSocks5Server(conn, s).HandleRequest(req) |
|||
return |
|||
case "socks4", "socks4a": |
|||
req, err := gosocks4.ReadRequest(conn) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[socks4]", err) |
|||
return |
|||
} |
|||
NewSocks4Server(conn, s).HandleRequest(req) |
|||
return |
|||
} |
|||
type tcpKeepAliveListener struct { |
|||
*net.TCPListener |
|||
} |
|||
|
|||
br := bufio.NewReader(conn) |
|||
b, err := br.Peek(1) |
|||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { |
|||
tc, err := ln.AcceptTCP() |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln(err) |
|||
return |
|||
} |
|||
|
|||
switch b[0] { |
|||
case gosocks4.Ver4: |
|||
req, err := gosocks4.ReadRequest(br) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[socks4]", err) |
|||
return |
|||
} |
|||
NewSocks4Server(conn, s).HandleRequest(req) |
|||
|
|||
case gosocks5.Ver5: |
|||
methods, err := gosocks5.ReadMethods(br) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[socks5]", err) |
|||
return |
|||
} |
|||
method := s.selector.Select(methods...) |
|||
if _, err := conn.Write([]byte{gosocks5.Ver5, method}); err != nil { |
|||
glog.V(LWARNING).Infoln("[socks5] select:", err) |
|||
return |
|||
} |
|||
c, err := s.selector.OnSelected(method, conn) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[socks5] onselected:", err) |
|||
return |
|||
} |
|||
conn = c |
|||
|
|||
req, err := gosocks5.ReadRequest(conn) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[socks5] request:", err) |
|||
return |
|||
} |
|||
NewSocks5Server(conn, s).HandleRequest(req) |
|||
|
|||
default: // http
|
|||
req, err := http.ReadRequest(br) |
|||
if err != nil { |
|||
glog.V(LWARNING).Infoln("[http]", err) |
|||
return |
|||
} |
|||
NewHttpServer(conn, s).HandleRequest(req) |
|||
} |
|||
tc.SetKeepAlive(true) |
|||
tc.SetKeepAlivePeriod(KeepAliveTime) |
|||
return tc, nil |
|||
} |
|||
|
|||
func (_ *ProxyServer) transport(conn1, conn2 net.Conn) (err error) { |
|||
errc := make(chan error, 2) |
|||
|
|||
func transport(rw1, rw2 io.ReadWriter) error { |
|||
errc := make(chan error, 1) |
|||
go func() { |
|||
_, err := io.Copy(conn1, conn2) |
|||
_, err := io.Copy(rw1, rw2) |
|||
errc <- err |
|||
}() |
|||
|
|||
go func() { |
|||
_, err := io.Copy(conn2, conn1) |
|||
_, err := io.Copy(rw2, rw1) |
|||
errc <- err |
|||
}() |
|||
|
|||
select { |
|||
case err = <-errc: |
|||
// glog.V(LWARNING).Infoln("transport exit", err)
|
|||
err := <-errc |
|||
if err != nil && err == io.EOF { |
|||
err = nil |
|||
} |
|||
|
|||
return |
|||
return err |
|||
} |
|||
|
|||
File diff suppressed because it is too large
Loading…
Reference in new issue