mirror of https://github.com/ginuerzh/gost
committed by
ginuerzh
21 changed files with 0 additions and 5332 deletions
@ -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,473 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"crypto/rand" |
|
||||
"crypto/tls" |
|
||||
"encoding/base64" |
|
||||
"errors" |
|
||||
"github.com/ginuerzh/pht" |
|
||||
"github.com/golang/glog" |
|
||||
"github.com/lucas-clemente/quic-go/h2quic" |
|
||||
"golang.org/x/net/http2" |
|
||||
"io" |
|
||||
"net" |
|
||||
"net/http" |
|
||||
"net/http/httputil" |
|
||||
"net/url" |
|
||||
"strconv" |
|
||||
"strings" |
|
||||
"sync" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
// 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 |
|
||||
} |
|
||||
|
|
||||
func (c *ProxyChain) AddProxyNode(node ...ProxyNode) { |
|
||||
c.nodes = append(c.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) |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func (c *ProxyChain) Nodes() []ProxyNode { |
|
||||
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 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 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 { |
|
||||
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, |
|
||||
} |
|
||||
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 |
|
||||
} |
|
||||
|
|
||||
func (c *ProxyChain) Http2Enabled() bool { |
|
||||
return c.http2Enabled |
|
||||
} |
|
||||
|
|
||||
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 = tls.Client(conn, cfg) |
|
||||
|
|
||||
// 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...) |
|
||||
} |
|
||||
|
|
||||
// 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 { |
|
||||
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) |
|
||||
} |
|
||||
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) |
|
||||
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, |
|
||||
} |
|
||||
*/ |
|
||||
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) |
|
||||
} |
|
||||
conn := &http2Conn{r: resp.Body, w: pw} |
|
||||
conn.remoteAddr, _ = net.ResolveUDPAddr("udp", quicNode.Addr) |
|
||||
return conn, nil |
|
||||
} |
|
||||
@ -1,292 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"bufio" |
|
||||
"crypto/tls" |
|
||||
"encoding/base64" |
|
||||
"errors" |
|
||||
"fmt" |
|
||||
"github.com/ginuerzh/gosocks4" |
|
||||
"github.com/ginuerzh/gosocks5" |
|
||||
"github.com/golang/glog" |
|
||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" |
|
||||
"net" |
|
||||
"net/http" |
|
||||
"net/http/httputil" |
|
||||
"net/url" |
|
||||
"strconv" |
|
||||
"strings" |
|
||||
"sync" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
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
|
|
||||
u := url.URL{Scheme: "ws", Host: c.Node.Addr, Path: "/ws"} |
|
||||
conn, err := WebsocketClientConn(u.String(), c.conn, nil) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
c.conn = conn |
|
||||
case "wss": // websocket security
|
|
||||
tlsUsed = true |
|
||||
u := url.URL{Scheme: "wss", Host: c.Node.Addr, Path: "/ws"} |
|
||||
config := &tls.Config{ |
|
||||
InsecureSkipVerify: c.Node.insecureSkipVerify(), |
|
||||
ServerName: c.Node.serverName, |
|
||||
} |
|
||||
conn, err := WebsocketClientConn(u.String(), c.conn, config) |
|
||||
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) |
|
||||
} |
|
||||
@ -1,689 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
"fmt" |
|
||||
"github.com/ginuerzh/gosocks5" |
|
||||
"github.com/golang/glog" |
|
||||
"golang.org/x/crypto/ssh" |
|
||||
"net" |
|
||||
"strconv" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
type TcpForwardServer struct { |
|
||||
Base *ProxyServer |
|
||||
sshClient *ssh.Client |
|
||||
Handler func(conn net.Conn, raddr *net.TCPAddr) |
|
||||
} |
|
||||
|
|
||||
func NewTcpForwardServer(base *ProxyServer) *TcpForwardServer { |
|
||||
return &TcpForwardServer{Base: base} |
|
||||
} |
|
||||
|
|
||||
func (s *TcpForwardServer) ListenAndServe() error { |
|
||||
raddr, err := net.ResolveTCPAddr("tcp", s.Base.Node.Remote) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
ln, err := net.Listen("tcp", s.Base.Node.Addr) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
defer ln.Close() |
|
||||
|
|
||||
if s.Handler == nil { |
|
||||
s.Handler = s.handleTcpForward |
|
||||
} |
|
||||
|
|
||||
quit := make(chan interface{}) |
|
||||
close(quit) |
|
||||
|
|
||||
for { |
|
||||
start: |
|
||||
conn, err := ln.Accept() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[tcp]", err) |
|
||||
continue |
|
||||
} |
|
||||
setKeepAlive(conn, KeepAliveTime) |
|
||||
|
|
||||
select { |
|
||||
case <-quit: |
|
||||
if s.Base.Chain.lastNode == nil || s.Base.Chain.lastNode.Transport != "ssh" { |
|
||||
break |
|
||||
} |
|
||||
if err := s.initSSHClient(); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[tcp]", err) |
|
||||
conn.Close() |
|
||||
goto start |
|
||||
} |
|
||||
quit = make(chan interface{}) |
|
||||
go func(ch chan interface{}) { |
|
||||
s.sshClient.Wait() |
|
||||
glog.V(LINFO).Infoln("[tcp] connection closed") |
|
||||
close(ch) |
|
||||
}(quit) |
|
||||
|
|
||||
default: |
|
||||
} |
|
||||
|
|
||||
go s.Handler(conn, raddr) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *TcpForwardServer) initSSHClient() error { |
|
||||
if s.sshClient != nil { |
|
||||
s.sshClient.Close() |
|
||||
s.sshClient = nil |
|
||||
} |
|
||||
|
|
||||
sshNode := s.Base.Chain.lastNode |
|
||||
c, err := s.Base.Chain.GetConn() |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
var user, password string |
|
||||
if len(sshNode.Users) > 0 { |
|
||||
user = sshNode.Users[0].Username() |
|
||||
password, _ = sshNode.Users[0].Password() |
|
||||
} |
|
||||
config := ssh.ClientConfig{ |
|
||||
User: user, |
|
||||
Auth: []ssh.AuthMethod{ |
|
||||
ssh.Password(password), |
|
||||
}, |
|
||||
} |
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(c, sshNode.Addr, &config) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
s.sshClient = ssh.NewClient(sshConn, chans, reqs) |
|
||||
s.Handler = s.handleTcpForwardSSH |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func (s *TcpForwardServer) handleTcpForward(conn net.Conn, raddr *net.TCPAddr) { |
|
||||
defer conn.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[tcp] %s - %s", conn.RemoteAddr(), raddr) |
|
||||
cc, err := s.Base.Chain.Dial(raddr.String()) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[tcp] %s -> %s : %s", conn.RemoteAddr(), raddr, err) |
|
||||
return |
|
||||
} |
|
||||
defer cc.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[tcp] %s <-> %s", conn.RemoteAddr(), raddr) |
|
||||
s.Base.transport(conn, cc) |
|
||||
glog.V(LINFO).Infof("[tcp] %s >-< %s", conn.RemoteAddr(), raddr) |
|
||||
} |
|
||||
|
|
||||
func (s *TcpForwardServer) handleTcpForwardSSH(conn net.Conn, raddr *net.TCPAddr) { |
|
||||
defer conn.Close() |
|
||||
|
|
||||
if s.sshClient == nil { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
rc, err := s.sshClient.DialTCP("tcp", nil, raddr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[tcp] %s -> %s : %s", conn.RemoteAddr(), raddr, err) |
|
||||
return |
|
||||
} |
|
||||
defer rc.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[tcp] %s <-> %s", conn.RemoteAddr(), raddr) |
|
||||
Transport(conn, rc) |
|
||||
glog.V(LINFO).Infof("[tcp] %s >-< %s", conn.RemoteAddr(), raddr) |
|
||||
} |
|
||||
|
|
||||
type packet struct { |
|
||||
srcAddr string // src address
|
|
||||
dstAddr string // dest address
|
|
||||
data []byte |
|
||||
} |
|
||||
|
|
||||
type cnode struct { |
|
||||
chain *ProxyChain |
|
||||
conn net.Conn |
|
||||
srcAddr, dstAddr string |
|
||||
rChan, wChan chan *packet |
|
||||
err error |
|
||||
ttl time.Duration |
|
||||
} |
|
||||
|
|
||||
func (node *cnode) getUDPTunnel() (net.Conn, error) { |
|
||||
conn, err := node.chain.GetConn() |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
conn.SetWriteDeadline(time.Now().Add(WriteTimeout)) |
|
||||
if err = gosocks5.NewRequest(CmdUdpTun, nil).Write(conn); err != nil { |
|
||||
conn.Close() |
|
||||
return nil, err |
|
||||
} |
|
||||
conn.SetWriteDeadline(time.Time{}) |
|
||||
|
|
||||
conn.SetReadDeadline(time.Now().Add(ReadTimeout)) |
|
||||
reply, err := gosocks5.ReadReply(conn) |
|
||||
if err != nil { |
|
||||
conn.Close() |
|
||||
return nil, err |
|
||||
} |
|
||||
conn.SetReadDeadline(time.Time{}) |
|
||||
|
|
||||
if reply.Rep != gosocks5.Succeeded { |
|
||||
conn.Close() |
|
||||
return nil, errors.New("UDP tunnel failure") |
|
||||
} |
|
||||
|
|
||||
return conn, nil |
|
||||
} |
|
||||
|
|
||||
func (node *cnode) run() { |
|
||||
if len(node.chain.Nodes()) == 0 { |
|
||||
lconn, err := net.ListenUDP("udp", nil) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", node.srcAddr, node.dstAddr, err) |
|
||||
node.err = err |
|
||||
return |
|
||||
} |
|
||||
node.conn = lconn |
|
||||
} else { |
|
||||
tc, err := node.getUDPTunnel() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s -> %s : %s", node.srcAddr, node.dstAddr, err) |
|
||||
node.err = err |
|
||||
return |
|
||||
} |
|
||||
node.conn = tc |
|
||||
} |
|
||||
|
|
||||
defer node.conn.Close() |
|
||||
|
|
||||
timer := time.NewTimer(node.ttl) |
|
||||
errChan := make(chan error, 2) |
|
||||
|
|
||||
go func() { |
|
||||
for { |
|
||||
switch c := node.conn.(type) { |
|
||||
case *net.UDPConn: |
|
||||
b := make([]byte, MediumBufferSize) |
|
||||
n, addr, err := c.ReadFromUDP(b) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", node.srcAddr, node.dstAddr, err) |
|
||||
node.err = err |
|
||||
errChan <- err |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
timer.Reset(node.ttl) |
|
||||
glog.V(LDEBUG).Infof("[udp] %s <<< %s : length %d", node.srcAddr, addr, n) |
|
||||
|
|
||||
select { |
|
||||
// swap srcAddr with dstAddr
|
|
||||
case node.rChan <- &packet{srcAddr: addr.String(), dstAddr: node.srcAddr, data: b[:n]}: |
|
||||
case <-time.After(time.Second * 3): |
|
||||
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard") |
|
||||
} |
|
||||
|
|
||||
default: |
|
||||
dgram, err := gosocks5.ReadUDPDatagram(c) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s <- %s : %s", node.srcAddr, node.dstAddr, err) |
|
||||
node.err = err |
|
||||
errChan <- err |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
timer.Reset(node.ttl) |
|
||||
glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s : length %d", node.srcAddr, dgram.Header.Addr.String(), len(dgram.Data)) |
|
||||
|
|
||||
select { |
|
||||
// swap srcAddr with dstAddr
|
|
||||
case node.rChan <- &packet{srcAddr: dgram.Header.Addr.String(), dstAddr: node.srcAddr, data: dgram.Data}: |
|
||||
case <-time.After(time.Second * 3): |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard") |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
go func() { |
|
||||
for pkt := range node.wChan { |
|
||||
timer.Reset(node.ttl) |
|
||||
|
|
||||
dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
switch c := node.conn.(type) { |
|
||||
case *net.UDPConn: |
|
||||
if _, err := c.WriteToUDP(pkt.data, dstAddr); err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err) |
|
||||
node.err = err |
|
||||
errChan <- err |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[udp] %s >>> %s : length %d", pkt.srcAddr, pkt.dstAddr, len(pkt.data)) |
|
||||
|
|
||||
default: |
|
||||
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(pkt.data)), 0, ToSocksAddr(dstAddr)), pkt.data) |
|
||||
if err := dgram.Write(c); err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err) |
|
||||
node.err = err |
|
||||
errChan <- err |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[udp-tun] %s >>> %s : length %d", pkt.srcAddr, pkt.dstAddr, len(pkt.data)) |
|
||||
} |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
select { |
|
||||
case <-errChan: |
|
||||
case <-timer.C: |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
type UdpForwardServer struct { |
|
||||
Base *ProxyServer |
|
||||
TTL int |
|
||||
} |
|
||||
|
|
||||
func NewUdpForwardServer(base *ProxyServer, ttl int) *UdpForwardServer { |
|
||||
return &UdpForwardServer{Base: base, TTL: ttl} |
|
||||
} |
|
||||
|
|
||||
func (s *UdpForwardServer) ListenAndServe() error { |
|
||||
laddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Addr) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
raddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Remote) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
conn, err := net.ListenUDP("udp", laddr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return err |
|
||||
} |
|
||||
defer conn.Close() |
|
||||
|
|
||||
rChan, wChan := make(chan *packet, 128), make(chan *packet, 128) |
|
||||
// start send queue
|
|
||||
go func(ch chan<- *packet) { |
|
||||
for { |
|
||||
b := make([]byte, MediumBufferSize) |
|
||||
n, addr, err := conn.ReadFromUDP(b) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", laddr, raddr, err) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
select { |
|
||||
case ch <- &packet{srcAddr: addr.String(), dstAddr: raddr.String(), data: b[:n]}: |
|
||||
case <-time.After(time.Second * 3): |
|
||||
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", addr, raddr, "send queue is full, discard") |
|
||||
} |
|
||||
} |
|
||||
}(wChan) |
|
||||
// start recv queue
|
|
||||
go func(ch <-chan *packet) { |
|
||||
for pkt := range ch { |
|
||||
dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err) |
|
||||
continue |
|
||||
} |
|
||||
if _, err := conn.WriteToUDP(pkt.data, dstAddr); err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err) |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
}(rChan) |
|
||||
|
|
||||
// mapping client to node
|
|
||||
m := make(map[string]*cnode) |
|
||||
|
|
||||
// start dispatcher
|
|
||||
for pkt := range wChan { |
|
||||
// clear obsolete nodes
|
|
||||
for k, node := range m { |
|
||||
if node != nil && node.err != nil { |
|
||||
close(node.wChan) |
|
||||
delete(m, k) |
|
||||
glog.V(LINFO).Infof("[udp] clear node %s", k) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
node, ok := m[pkt.srcAddr] |
|
||||
if !ok { |
|
||||
node = &cnode{ |
|
||||
chain: s.Base.Chain, |
|
||||
srcAddr: pkt.srcAddr, |
|
||||
dstAddr: pkt.dstAddr, |
|
||||
rChan: rChan, |
|
||||
wChan: make(chan *packet, 32), |
|
||||
ttl: time.Duration(s.TTL) * time.Second, |
|
||||
} |
|
||||
m[pkt.srcAddr] = node |
|
||||
go node.run() |
|
||||
glog.V(LINFO).Infof("[udp] %s -> %s : new client (%d)", pkt.srcAddr, pkt.dstAddr, len(m)) |
|
||||
} |
|
||||
|
|
||||
select { |
|
||||
case node.wChan <- pkt: |
|
||||
case <-time.After(time.Second * 3): |
|
||||
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, "node send queue is full, discard") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
type RTcpForwardServer struct { |
|
||||
Base *ProxyServer |
|
||||
} |
|
||||
|
|
||||
func NewRTcpForwardServer(base *ProxyServer) *RTcpForwardServer { |
|
||||
return &RTcpForwardServer{Base: base} |
|
||||
} |
|
||||
|
|
||||
func (s *RTcpForwardServer) Serve() error { |
|
||||
if len(s.Base.Chain.nodes) == 0 { |
|
||||
return errors.New("rtcp: at least one -F must be assigned") |
|
||||
} |
|
||||
|
|
||||
laddr, err := net.ResolveTCPAddr("tcp", s.Base.Node.Addr) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
raddr, err := net.ResolveTCPAddr("tcp", s.Base.Node.Remote) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
retry := 0 |
|
||||
for { |
|
||||
conn, err := s.Base.Chain.GetConn() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s - %s : %s", laddr, raddr, err) |
|
||||
time.Sleep((1 << uint(retry)) * time.Second) |
|
||||
if retry < 5 { |
|
||||
retry++ |
|
||||
} |
|
||||
continue |
|
||||
} |
|
||||
retry = 0 |
|
||||
|
|
||||
glog.V(LINFO).Infof("[rtcp] %s - %s", laddr, raddr) |
|
||||
|
|
||||
lastNode := s.Base.Chain.lastNode |
|
||||
if lastNode != nil && lastNode.Transport == "ssh" { |
|
||||
s.connectRTcpForwardSSH(conn, lastNode, laddr, raddr) |
|
||||
} else { |
|
||||
if err := s.connectRTcpForward(conn, laddr, raddr); err != nil { |
|
||||
conn.Close() |
|
||||
} |
|
||||
} |
|
||||
time.Sleep(3 * time.Second) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *RTcpForwardServer) connectRTcpForwardSSH(conn net.Conn, sshNode *ProxyNode, laddr, raddr net.Addr) error { |
|
||||
defer conn.Close() |
|
||||
|
|
||||
var user, password string |
|
||||
if len(sshNode.Users) > 0 { |
|
||||
user = sshNode.Users[0].Username() |
|
||||
password, _ = sshNode.Users[0].Password() |
|
||||
} |
|
||||
config := ssh.ClientConfig{ |
|
||||
User: user, |
|
||||
Auth: []ssh.AuthMethod{ |
|
||||
ssh.Password(password), |
|
||||
}, |
|
||||
} |
|
||||
c, chans, reqs, err := ssh.NewClientConn(conn, sshNode.Addr, &config) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return err |
|
||||
} |
|
||||
client := ssh.NewClient(c, chans, reqs) |
|
||||
|
|
||||
quit := make(chan interface{}) |
|
||||
defer close(quit) |
|
||||
|
|
||||
go func() { |
|
||||
defer client.Close() |
|
||||
|
|
||||
var c <-chan time.Time |
|
||||
|
|
||||
ping, _ := strconv.Atoi(sshNode.Get("ping")) |
|
||||
if ping > 0 { |
|
||||
d := time.Second * time.Duration(ping) |
|
||||
glog.V(LINFO).Infoln("[rtcp] ping is enabled:", d) |
|
||||
t := time.NewTicker(d) |
|
||||
defer t.Stop() |
|
||||
c = t.C |
|
||||
} |
|
||||
|
|
||||
for { |
|
||||
select { |
|
||||
case <-c: |
|
||||
_, _, err := client.SendRequest("ping", true, nil) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[rtcp] ping", err) |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infoln("[rtcp] heartbeat OK") |
|
||||
|
|
||||
case <-quit: |
|
||||
glog.V(LWARNING).Infoln("[rtcp] ssh connection closed") |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
ln, err := client.Listen("tcp", laddr.String()) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return err |
|
||||
} |
|
||||
defer ln.Close() |
|
||||
|
|
||||
for { |
|
||||
rc, err := ln.Accept() |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
go func(c net.Conn) { |
|
||||
defer c.Close() |
|
||||
|
|
||||
tc, err := net.DialTimeout("tcp", raddr.String(), time.Second*30) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return |
|
||||
} |
|
||||
defer tc.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[rtcp] %s <-> %s", c.RemoteAddr(), c.LocalAddr()) |
|
||||
Transport(c, tc) |
|
||||
glog.V(LINFO).Infof("[rtcp] %s >-< %s", c.RemoteAddr(), c.LocalAddr()) |
|
||||
}(rc) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *RTcpForwardServer) connectRTcpForward(conn net.Conn, laddr, raddr net.Addr) error { |
|
||||
req := gosocks5.NewRequest(gosocks5.CmdBind, ToSocksAddr(laddr)) |
|
||||
if err := req.Write(conn); err != nil { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
// first reply, bind status
|
|
||||
conn.SetReadDeadline(time.Now().Add(ReadTimeout)) |
|
||||
rep, err := gosocks5.ReadReply(conn) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return err |
|
||||
} |
|
||||
conn.SetReadDeadline(time.Time{}) |
|
||||
if rep.Rep != gosocks5.Succeeded { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s -> %s : bind on %s failure", laddr, raddr, laddr) |
|
||||
return errors.New("Bind on " + laddr.String() + " failure") |
|
||||
} |
|
||||
glog.V(LINFO).Infof("[rtcp] %s - %s BIND ON %s OK", laddr, raddr, rep.Addr) |
|
||||
|
|
||||
// second reply, peer connection
|
|
||||
rep, err = gosocks5.ReadReply(conn) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return err |
|
||||
} |
|
||||
if rep.Rep != gosocks5.Succeeded { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s -> %s : peer connect failure", laddr, raddr) |
|
||||
return errors.New("peer connect failure") |
|
||||
} |
|
||||
|
|
||||
glog.V(LINFO).Infof("[rtcp] %s -> %s PEER %s CONNECTED", laddr, raddr, rep.Addr) |
|
||||
|
|
||||
go func() { |
|
||||
defer conn.Close() |
|
||||
|
|
||||
lconn, err := net.DialTimeout("tcp", raddr.String(), time.Second*30) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rtcp] %s -> %s : %s", rep.Addr, raddr, err) |
|
||||
return |
|
||||
} |
|
||||
defer lconn.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[rtcp] %s <-> %s", rep.Addr, lconn.RemoteAddr()) |
|
||||
s.Base.transport(lconn, conn) |
|
||||
glog.V(LINFO).Infof("[rtcp] %s >-< %s", rep.Addr, lconn.RemoteAddr()) |
|
||||
}() |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
type RUdpForwardServer struct { |
|
||||
Base *ProxyServer |
|
||||
} |
|
||||
|
|
||||
func NewRUdpForwardServer(base *ProxyServer) *RUdpForwardServer { |
|
||||
return &RUdpForwardServer{Base: base} |
|
||||
} |
|
||||
|
|
||||
func (s *RUdpForwardServer) Serve() error { |
|
||||
if len(s.Base.Chain.nodes) == 0 { |
|
||||
return errors.New("rudp: at least one -F must be assigned") |
|
||||
} |
|
||||
|
|
||||
laddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Addr) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
raddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Remote) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
retry := 0 |
|
||||
for { |
|
||||
conn, err := s.Base.Chain.GetConn() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rudp] %s - %s : %s", laddr, raddr, err) |
|
||||
time.Sleep((1 << uint(retry)) * time.Second) |
|
||||
if retry < 5 { |
|
||||
retry++ |
|
||||
} |
|
||||
continue |
|
||||
} |
|
||||
retry = 0 |
|
||||
|
|
||||
if err := s.connectRUdpForward(conn, laddr, raddr); err != nil { |
|
||||
conn.Close() |
|
||||
time.Sleep(6 * time.Second) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *RUdpForwardServer) connectRUdpForward(conn net.Conn, laddr, raddr *net.UDPAddr) error { |
|
||||
glog.V(LINFO).Infof("[rudp] %s - %s", laddr, raddr) |
|
||||
|
|
||||
req := gosocks5.NewRequest(CmdUdpTun, ToSocksAddr(laddr)) |
|
||||
conn.SetWriteDeadline(time.Now().Add(WriteTimeout)) |
|
||||
if err := req.Write(conn); err != nil { |
|
||||
glog.V(LWARNING).Infof("[rudp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return err |
|
||||
} |
|
||||
conn.SetWriteDeadline(time.Time{}) |
|
||||
|
|
||||
conn.SetReadDeadline(time.Now().Add(ReadTimeout)) |
|
||||
rep, err := gosocks5.ReadReply(conn) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rudp] %s <- %s : %s", laddr, raddr, err) |
|
||||
return err |
|
||||
} |
|
||||
conn.SetReadDeadline(time.Time{}) |
|
||||
|
|
||||
if rep.Rep != gosocks5.Succeeded { |
|
||||
glog.V(LWARNING).Infof("[rudp] %s <- %s : bind on %s failure", laddr, raddr, laddr) |
|
||||
return errors.New(fmt.Sprintf("bind on %s failure", laddr)) |
|
||||
} |
|
||||
|
|
||||
glog.V(LINFO).Infof("[rudp] %s - %s BIND ON %s OK", laddr, raddr, rep.Addr) |
|
||||
|
|
||||
for { |
|
||||
dgram, err := gosocks5.ReadUDPDatagram(conn) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rudp] %s <- %s : %s", laddr, raddr, err) |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
go func() { |
|
||||
b := make([]byte, MediumBufferSize) |
|
||||
|
|
||||
relay, err := net.DialUDP("udp", nil, raddr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rudp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return |
|
||||
} |
|
||||
defer relay.Close() |
|
||||
|
|
||||
if _, err := relay.Write(dgram.Data); err != nil { |
|
||||
glog.V(LWARNING).Infof("[rudp] %s -> %s : %s", laddr, raddr, err) |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[rudp] %s >>> %s length: %d", laddr, raddr, len(dgram.Data)) |
|
||||
|
|
||||
relay.SetReadDeadline(time.Now().Add(ReadTimeout)) |
|
||||
n, err := relay.Read(b) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[rudp] %s <- %s : %s", laddr, raddr, err) |
|
||||
return |
|
||||
} |
|
||||
relay.SetReadDeadline(time.Time{}) |
|
||||
|
|
||||
glog.V(LDEBUG).Infof("[rudp] %s <<< %s length: %d", laddr, raddr, n) |
|
||||
|
|
||||
conn.SetWriteDeadline(time.Now().Add(WriteTimeout)) |
|
||||
if err := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(n), 0, dgram.Header.Addr), b[:n]).Write(conn); err != nil { |
|
||||
glog.V(LWARNING).Infof("[rudp] %s <- %s : %s", laddr, raddr, err) |
|
||||
return |
|
||||
} |
|
||||
conn.SetWriteDeadline(time.Time{}) |
|
||||
}() |
|
||||
} |
|
||||
} |
|
||||
@ -1,162 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"crypto/tls" |
|
||||
"encoding/base64" |
|
||||
"errors" |
|
||||
"github.com/golang/glog" |
|
||||
"io" |
|
||||
"net" |
|
||||
"strings" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
const ( |
|
||||
Version = "2.4-dev20170303" |
|
||||
) |
|
||||
|
|
||||
// Log level for glog
|
|
||||
const ( |
|
||||
LFATAL = iota |
|
||||
LERROR |
|
||||
LWARNING |
|
||||
LINFO |
|
||||
LDEBUG |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
KeepAliveTime = 180 * time.Second |
|
||||
DialTimeout = 30 * time.Second |
|
||||
ReadTimeout = 90 * time.Second |
|
||||
WriteTimeout = 90 * time.Second |
|
||||
|
|
||||
DefaultTTL = 60 // default udp node TTL in second for udp port forwarding
|
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
SmallBufferSize = 1 * 1024 // 1KB small buffer
|
|
||||
MediumBufferSize = 8 * 1024 // 8KB medium buffer
|
|
||||
LargeBufferSize = 32 * 1024 // 32KB large buffer
|
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
DefaultCertFile = "cert.pem" |
|
||||
DefaultKeyFile = "key.pem" |
|
||||
|
|
||||
// This is the default cert and key data for convenience, providing your own cert is recommended.
|
|
||||
defaultRawCert = []byte(`-----BEGIN CERTIFICATE----- |
|
||||
MIIC5jCCAdCgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD |
|
||||
bzAeFw0xNDAzMTcwNjIwNTFaFw0xNTAzMTcwNjIwNTFaMBIxEDAOBgNVBAoTB0Fj |
|
||||
bWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccNO1xmd4lWSf |
|
||||
d/0/QS3E93cYIWHw831i/IKxigdRD/XMZonLdEHywW6lOiXazaP8e6CqPGSmnl0x |
|
||||
5k/3dvGCMj2JCVxM6+z7NpL+AiwvXmvkj/TOciCgwqssCwYS2CiVwjfazRjx1ZUJ |
|
||||
VDC5qiyRsfktQ2fVHrpnJGVSRagmiQgwGWBilVG9B8QvRtpQKN/GQGq17oIQm8aK |
|
||||
kOdPt93g93ojMIg7YJpgDgOirvVz/hDn7YD4ryrtPos9CMafFkJprymKpRHyvz7P |
|
||||
8a3+OkuPjFjPnwOHQ5u1U3+8vC44vfb1ExWzDLoT8Xp8Gndx39k0f7MVOol3GnYu |
|
||||
MN/dvNUdAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIAoDATBgNVHSUEDDAKBggrBgEF |
|
||||
BQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDALBgkqhkiG |
|
||||
9w0BAQUDggEBAIG8CJqvTIgJnNOK+i5/IUc/3yF/mSCWuG8qP+Fmo2t6T0PVOtc0 |
|
||||
8wiWH5iWtCAhjn0MRY9l/hIjWm6gUZGHCGuEgsOPpJDYGoNLjH9Xwokm4y3LFNRK |
|
||||
UBrrrDbKRNibApBHCapPf6gC5sXcjOwx7P2/kiHDgY7YH47jfcRhtAPNsM4gjsEO |
|
||||
RmwENY+hRUFHIRfQTyalqND+x6PWhRo3K6hpHs4DQEYPq4P2kFPqUqSBymH+Ny5/ |
|
||||
BcQ3wdMNmC6Bm/oiL1QV0M+/InOsAgQk/EDd0kmoU1ZT2lYHQduGmP099bOlHNpS |
|
||||
uqO3vXF3q8SPPr/A9TqSs7BKkBQbe0+cdsA= |
|
||||
-----END CERTIFICATE-----`) |
|
||||
defaultRawKey = []byte(`-----BEGIN RSA PRIVATE KEY----- |
|
||||
MIIEowIBAAKCAQEA3HDTtcZneJVkn3f9P0EtxPd3GCFh8PN9YvyCsYoHUQ/1zGaJ |
|
||||
y3RB8sFupTol2s2j/Hugqjxkpp5dMeZP93bxgjI9iQlcTOvs+zaS/gIsL15r5I/0 |
|
||||
znIgoMKrLAsGEtgolcI32s0Y8dWVCVQwuaoskbH5LUNn1R66ZyRlUkWoJokIMBlg |
|
||||
YpVRvQfEL0baUCjfxkBqte6CEJvGipDnT7fd4Pd6IzCIO2CaYA4Doq71c/4Q5+2A |
|
||||
+K8q7T6LPQjGnxZCaa8piqUR8r8+z/Gt/jpLj4xYz58Dh0ObtVN/vLwuOL329RMV |
|
||||
swy6E/F6fBp3cd/ZNH+zFTqJdxp2LjDf3bzVHQIDAQABAoIBAHal26147nQ+pHwY |
|
||||
jxwers3XDCjWvup7g79lfcqlKi79UiUEA6KYHm7UogMYewt7p4nb2KwH+XycvDiB |
|
||||
aAUf5flXpTs+6IkWauUDiLZi4PlV7uiEexUq5FjirlL0U/6MjbudX4bK4WQ4uxDc |
|
||||
WaV07Kw2iJFOOHLDKT0en9JaX5jtJNc4ZnE9efFoQ5jfypPWtRw65G1rULEg6nvc |
|
||||
GDh+1ce+4foCkpLRC9c24xAwJONZG6x3UqrSS9qfAsb73nWRQrTfUcO3nhoN8VvL |
|
||||
kL9skn1+S06NyUN0KoEtyRBp+RcpXSsBWAo6qZmo/WqhB/gjzWrxVwn20+yJSm35 |
|
||||
ZsMc6QECgYEA8GS+Mp9xfB2szWHz6YTOO1Uu4lHM1ccZMwS1G+dL0KO3uGAiPdvp |
|
||||
woVot6v6w88t7onXsLo5pgz7SYug0CpkF3K/MRd1Ar4lH7PK7IBQ6rFr9ppVxDbx |
|
||||
AEWRswUoPbKCr7W6HU8LbQHDavsDlEIwc6+DiwnL4BzlKjb7RpgQEz0CgYEA6sB5 |
|
||||
uHvx3Y5FDcGk1n73leQSAcq14l3ZLNpjrs8msoREDil/j5WmuSN58/7PGMiMgHEi |
|
||||
1vLm3H796JmvGr9OBvspOjHyk07ui2/We/j9Hoxm1VWhyi8HkLNDj70HKalTTFMz |
|
||||
RHO4O+0xCva+h9mKZrRMVktXr2jjdFn/0MYIZ2ECgYAIIsC1IeRLWQ3CHbCNlKsO |
|
||||
IwHlMvOFwKk/qsceXKOaOhA7szU1dr3gkXdL0Aw6mEZrrkqYdpUA46uVf54/rU+Z |
|
||||
445I8QxKvXiwK/uQKX+TkdGflPWWIG3jnnch4ejMvb/ihnn4B/bRB6A/fKNQXzUY |
|
||||
lTYUfI5j1VaEKTwz1W2l2QKBgByFCcSp+jZqhGUpc3dDsZyaOr3Q/Mvlju7uEVI5 |
|
||||
hIAHpaT60a6GBd1UPAqymEJwivFHzW3D0NxU6VAK68UaHMaoWNfjHY9b9YsnKS2i |
|
||||
kE3XzN56Ks+/avHfdYPO+UHMenw5V28nh+hv5pdoZrlmanQTz3pkaOC8o3WNQZEB |
|
||||
nh/BAoGBAMY5z2f1pmMhrvtPDSlEVjgjELbaInxFaxPLR4Pdyzn83gtIIU14+R8X |
|
||||
2LPs6PPwrNjWnIgrUSVXncIFL3pa45B+Mx1pYCpOAB1+nCZjIBQmpeo4Y0dwA/XH |
|
||||
85EthKPvoszm+OPbyI16OcePV5ocX7lupRYuAo0pek7bomhmHWHz |
|
||||
-----END RSA PRIVATE KEY-----`) |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
ErrEmptyChain = errors.New("empty chain") |
|
||||
) |
|
||||
|
|
||||
func setKeepAlive(conn net.Conn, d time.Duration) error { |
|
||||
c, ok := conn.(*net.TCPConn) |
|
||||
if !ok { |
|
||||
return errors.New("Not a TCP connection") |
|
||||
} |
|
||||
if err := c.SetKeepAlive(true); err != nil { |
|
||||
return err |
|
||||
} |
|
||||
if err := c.SetKeepAlivePeriod(d); err != nil { |
|
||||
return err |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Load the certificate from cert and key files, will use the default certificate if the provided info are invalid.
|
|
||||
func LoadCertificate(certFile, keyFile string) (tls.Certificate, error) { |
|
||||
tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile) |
|
||||
if err == nil { |
|
||||
return tlsCert, nil |
|
||||
} |
|
||||
glog.V(LWARNING).Infoln(err) |
|
||||
return tls.X509KeyPair(defaultRawCert, defaultRawKey) |
|
||||
} |
|
||||
|
|
||||
// Replace the default certificate by your own
|
|
||||
func SetDefaultCertificate(rawCert, rawKey []byte) { |
|
||||
defaultRawCert = rawCert |
|
||||
defaultRawKey = rawKey |
|
||||
} |
|
||||
|
|
||||
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 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 |
|
||||
}() |
|
||||
|
|
||||
return <-errc |
|
||||
} |
|
||||
@ -1,410 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"bufio" |
|
||||
"crypto/tls" |
|
||||
"encoding/base64" |
|
||||
"errors" |
|
||||
"github.com/ginuerzh/pht" |
|
||||
"github.com/golang/glog" |
|
||||
"golang.org/x/net/http2" |
|
||||
"io" |
|
||||
"net" |
|
||||
"net/http" |
|
||||
"net/http/httputil" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
type HttpServer struct { |
|
||||
conn net.Conn |
|
||||
Base *ProxyServer |
|
||||
} |
|
||||
|
|
||||
func NewHttpServer(conn net.Conn, base *ProxyServer) *HttpServer { |
|
||||
return &HttpServer{ |
|
||||
conn: conn, |
|
||||
Base: base, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Default HTTP server handler
|
|
||||
func (s *HttpServer) HandleRequest(req *http.Request) { |
|
||||
glog.V(LINFO).Infof("[http] %s %s - %s %s", req.Method, s.conn.RemoteAddr(), req.Host, req.Proto) |
|
||||
|
|
||||
if glog.V(LDEBUG) { |
|
||||
dump, _ := httputil.DumpRequest(req, false) |
|
||||
glog.Infoln(string(dump)) |
|
||||
} |
|
||||
|
|
||||
if req.Method == "PRI" && req.ProtoMajor == 2 { |
|
||||
glog.V(LWARNING).Infof("[http] %s <- %s : Not an HTTP2 server", s.conn.RemoteAddr(), req.Host) |
|
||||
resp := "HTTP/1.1 400 Bad Request\r\n" + |
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n" |
|
||||
s.conn.Write([]byte(resp)) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
valid := false |
|
||||
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) |
|
||||
for _, user := range s.Base.Node.Users { |
|
||||
username := user.Username() |
|
||||
password, _ := user.Password() |
|
||||
if (u == username && p == password) || |
|
||||
(u == username && password == "") || |
|
||||
(username == "" && p == password) { |
|
||||
valid = true |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if len(s.Base.Node.Users) > 0 && !valid { |
|
||||
glog.V(LWARNING).Infof("[http] %s <- %s : proxy authentication required", s.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" |
|
||||
s.conn.Write([]byte(resp)) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
req.Header.Del("Proxy-Authorization") |
|
||||
|
|
||||
// forward http request
|
|
||||
lastNode := s.Base.Chain.lastNode |
|
||||
if lastNode != nil && lastNode.Transport == "" && (lastNode.Protocol == "http" || lastNode.Protocol == "") { |
|
||||
s.forwardRequest(req) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
c, err := s.Base.Chain.Dial(req.Host) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[http] %s -> %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") |
|
||||
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", s.conn.RemoteAddr(), req.Host, string(b)) |
|
||||
s.conn.Write(b) |
|
||||
return |
|
||||
} |
|
||||
defer c.Close() |
|
||||
|
|
||||
if req.Method == http.MethodConnect { |
|
||||
b := []byte("HTTP/1.1 200 Connection established\r\n" + |
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n") |
|
||||
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", s.conn.RemoteAddr(), req.Host, string(b)) |
|
||||
s.conn.Write(b) |
|
||||
} else { |
|
||||
req.Header.Del("Proxy-Connection") |
|
||||
req.Header.Set("Connection", "Keep-Alive") |
|
||||
|
|
||||
if err = req.Write(c); err != nil { |
|
||||
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), req.Host, err) |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
glog.V(LINFO).Infof("[http] %s <-> %s", s.conn.RemoteAddr(), req.Host) |
|
||||
s.Base.transport(s.conn, c) |
|
||||
glog.V(LINFO).Infof("[http] %s >-< %s", s.conn.RemoteAddr(), req.Host) |
|
||||
} |
|
||||
|
|
||||
func (s *HttpServer) forwardRequest(req *http.Request) { |
|
||||
last := s.Base.Chain.lastNode |
|
||||
if last == nil { |
|
||||
return |
|
||||
} |
|
||||
cc, err := s.Base.Chain.GetConn() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), last.Addr, err) |
|
||||
|
|
||||
b := []byte("HTTP/1.1 503 Service unavailable\r\n" + |
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n") |
|
||||
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", s.conn.RemoteAddr(), last.Addr, string(b)) |
|
||||
s.conn.Write(b) |
|
||||
return |
|
||||
} |
|
||||
defer cc.Close() |
|
||||
|
|
||||
if len(last.Users) > 0 { |
|
||||
user := last.Users[0] |
|
||||
s := user.String() |
|
||||
if _, set := 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 { |
|
||||
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), req.Host, err) |
|
||||
return |
|
||||
} |
|
||||
cc.SetWriteDeadline(time.Time{}) |
|
||||
|
|
||||
glog.V(LINFO).Infof("[http] %s <-> %s", s.conn.RemoteAddr(), req.Host) |
|
||||
s.Base.transport(s.conn, cc) |
|
||||
glog.V(LINFO).Infof("[http] %s >-< %s", s.conn.RemoteAddr(), req.Host) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
type Http2Server struct { |
|
||||
Base *ProxyServer |
|
||||
Handler http.Handler |
|
||||
TLSConfig *tls.Config |
|
||||
} |
|
||||
|
|
||||
func NewHttp2Server(base *ProxyServer) *Http2Server { |
|
||||
return &Http2Server{Base: base} |
|
||||
} |
|
||||
|
|
||||
func (s *Http2Server) ListenAndServeTLS(config *tls.Config) error { |
|
||||
srv := http.Server{ |
|
||||
Addr: s.Base.Node.Addr, |
|
||||
Handler: s.Handler, |
|
||||
TLSConfig: config, |
|
||||
} |
|
||||
if srv.Handler == nil { |
|
||||
srv.Handler = http.HandlerFunc(s.HandleRequest) |
|
||||
} |
|
||||
http2.ConfigureServer(&srv, nil) |
|
||||
return srv.ListenAndServeTLS("", "") |
|
||||
} |
|
||||
|
|
||||
// Default HTTP2 server handler
|
|
||||
func (s *Http2Server) HandleRequest(w http.ResponseWriter, req *http.Request) { |
|
||||
target := req.Header.Get("Gost-Target") |
|
||||
if target == "" { |
|
||||
target = req.Host |
|
||||
} |
|
||||
glog.V(LINFO).Infof("[http2] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto) |
|
||||
if glog.V(LDEBUG) { |
|
||||
dump, _ := httputil.DumpRequest(req, false) |
|
||||
glog.Infoln(string(dump)) |
|
||||
} |
|
||||
|
|
||||
w.Header().Set("Proxy-Agent", "gost/"+Version) |
|
||||
|
|
||||
// HTTP2 as transport
|
|
||||
if req.Header.Get("Proxy-Switch") == "gost" { |
|
||||
conn, err := s.Upgrade(w, req) |
|
||||
if err != nil { |
|
||||
glog.V(LINFO).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) |
|
||||
return |
|
||||
} |
|
||||
glog.V(LINFO).Infof("[http2] %s - %s : switch to HTTP2 transport mode OK", req.RemoteAddr, target) |
|
||||
s.Base.handleConn(conn) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
valid := false |
|
||||
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) |
|
||||
for _, user := range s.Base.Node.Users { |
|
||||
username := user.Username() |
|
||||
password, _ := user.Password() |
|
||||
if (u == username && p == password) || |
|
||||
(u == username && password == "") || |
|
||||
(username == "" && p == password) { |
|
||||
valid = true |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
if len(s.Base.Node.Users) > 0 && !valid { |
|
||||
glog.V(LWARNING).Infof("[http2] %s <- %s : proxy authentication required", req.RemoteAddr, target) |
|
||||
w.WriteHeader(http.StatusProxyAuthRequired) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
req.Header.Del("Proxy-Authorization") |
|
||||
req.Header.Del("Proxy-Connection") |
|
||||
|
|
||||
c, err := s.Base.Chain.Dial(target) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) |
|
||||
w.WriteHeader(http.StatusServiceUnavailable) |
|
||||
return |
|
||||
} |
|
||||
defer c.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[http2] %s <-> %s", req.RemoteAddr, target) |
|
||||
|
|
||||
if req.Method == http.MethodConnect { |
|
||||
w.WriteHeader(http.StatusOK) |
|
||||
if fw, ok := w.(http.Flusher); ok { |
|
||||
fw.Flush() |
|
||||
} |
|
||||
|
|
||||
// compatible with HTTP1.x
|
|
||||
if hj, ok := w.(http.Hijacker); ok && req.ProtoMajor == 1 { |
|
||||
// we take over the underly connection
|
|
||||
conn, _, err := hj.Hijack() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) |
|
||||
w.WriteHeader(http.StatusInternalServerError) |
|
||||
return |
|
||||
} |
|
||||
defer conn.Close() |
|
||||
glog.V(LINFO).Infof("[http2] %s -> %s : downgrade to HTTP/1.1", req.RemoteAddr, target) |
|
||||
s.Base.transport(conn, c) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
errc := make(chan error, 2) |
|
||||
go func() { |
|
||||
_, err := io.Copy(c, req.Body) |
|
||||
errc <- err |
|
||||
}() |
|
||||
go func() { |
|
||||
_, err := io.Copy(flushWriter{w}, c) |
|
||||
errc <- err |
|
||||
}() |
|
||||
|
|
||||
select { |
|
||||
case <-errc: |
|
||||
// glog.V(LWARNING).Infoln("exit", err)
|
|
||||
} |
|
||||
glog.V(LINFO).Infof("[http2] %s >-< %s", req.RemoteAddr, target) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
req.Header.Set("Connection", "Keep-Alive") |
|
||||
if err = req.Write(c); err != nil { |
|
||||
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
resp, err := http.ReadResponse(bufio.NewReader(c), req) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[http2] %s -> %s : %s", req.RemoteAddr, target, err) |
|
||||
return |
|
||||
} |
|
||||
defer resp.Body.Close() |
|
||||
|
|
||||
for k, v := range resp.Header { |
|
||||
for _, vv := range v { |
|
||||
w.Header().Add(k, vv) |
|
||||
} |
|
||||
} |
|
||||
w.WriteHeader(resp.StatusCode) |
|
||||
if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil { |
|
||||
glog.V(LWARNING).Infof("[http2] %s <- %s : %s", req.RemoteAddr, target, err) |
|
||||
} |
|
||||
glog.V(LINFO).Infof("[http2] %s >-< %s", req.RemoteAddr, target) |
|
||||
} |
|
||||
|
|
||||
// Upgrade upgrade an HTTP2 request to a bidirectional connection that preparing for tunneling other protocol, just like a websocket connection.
|
|
||||
func (s *Http2Server) Upgrade(w http.ResponseWriter, r *http.Request) (net.Conn, error) { |
|
||||
if r.Method != http.MethodConnect { |
|
||||
w.WriteHeader(http.StatusMethodNotAllowed) |
|
||||
return nil, errors.New("Method not allowed") |
|
||||
} |
|
||||
|
|
||||
w.WriteHeader(http.StatusOK) |
|
||||
|
|
||||
if fw, ok := w.(http.Flusher); ok { |
|
||||
fw.Flush() |
|
||||
} |
|
||||
|
|
||||
conn := &http2Conn{r: r.Body, w: flushWriter{w}} |
|
||||
conn.remoteAddr, _ = net.ResolveTCPAddr("tcp", r.RemoteAddr) |
|
||||
conn.localAddr, _ = net.ResolveTCPAddr("tcp", r.Host) |
|
||||
return conn, nil |
|
||||
} |
|
||||
|
|
||||
// HTTP2 client connection, wrapped up just like a net.Conn
|
|
||||
type http2Conn struct { |
|
||||
r io.Reader |
|
||||
w io.Writer |
|
||||
remoteAddr net.Addr |
|
||||
localAddr net.Addr |
|
||||
} |
|
||||
|
|
||||
func (c *http2Conn) Read(b []byte) (n int, err error) { |
|
||||
return c.r.Read(b) |
|
||||
} |
|
||||
|
|
||||
func (c *http2Conn) Write(b []byte) (n int, err error) { |
|
||||
return c.w.Write(b) |
|
||||
} |
|
||||
|
|
||||
func (c *http2Conn) Close() (err error) { |
|
||||
if rc, ok := c.r.(io.Closer); ok { |
|
||||
err = rc.Close() |
|
||||
} |
|
||||
if w, ok := c.w.(io.Closer); ok { |
|
||||
err = w.Close() |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
func (c *http2Conn) LocalAddr() net.Addr { |
|
||||
return c.localAddr |
|
||||
} |
|
||||
|
|
||||
func (c *http2Conn) RemoteAddr() net.Addr { |
|
||||
return c.remoteAddr |
|
||||
} |
|
||||
|
|
||||
func (c *http2Conn) SetDeadline(t time.Time) error { |
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
|
||||
} |
|
||||
|
|
||||
func (c *http2Conn) SetReadDeadline(t time.Time) error { |
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
|
||||
} |
|
||||
|
|
||||
func (c *http2Conn) SetWriteDeadline(t time.Time) error { |
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
|
||||
} |
|
||||
|
|
||||
type flushWriter struct { |
|
||||
w io.Writer |
|
||||
} |
|
||||
|
|
||||
func (fw flushWriter) Write(p []byte) (n int, err error) { |
|
||||
defer func() { |
|
||||
if r := recover(); r != nil { |
|
||||
if s, ok := r.(string); ok { |
|
||||
err = errors.New(s) |
|
||||
return |
|
||||
} |
|
||||
err = r.(error) |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
n, err = fw.w.Write(p) |
|
||||
if err != nil { |
|
||||
// glog.V(LWARNING).Infoln("flush writer:", err)
|
|
||||
return |
|
||||
} |
|
||||
if f, ok := fw.w.(http.Flusher); ok { |
|
||||
f.Flush() |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
type PureHttpServer struct { |
|
||||
Base *ProxyServer |
|
||||
Handler func(net.Conn) |
|
||||
} |
|
||||
|
|
||||
func NewPureHttpServer(base *ProxyServer) *PureHttpServer { |
|
||||
return &PureHttpServer{ |
|
||||
Base: base, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *PureHttpServer) ListenAndServe() error { |
|
||||
server := pht.Server{ |
|
||||
Addr: s.Base.Node.Addr, |
|
||||
Key: s.Base.Node.Get("key"), |
|
||||
} |
|
||||
if server.Handler == nil { |
|
||||
server.Handler = s.handleConn |
|
||||
} |
|
||||
return server.ListenAndServe() |
|
||||
} |
|
||||
|
|
||||
func (s *PureHttpServer) handleConn(conn net.Conn) { |
|
||||
glog.V(LINFO).Infof("[pht] %s - %s", conn.RemoteAddr(), conn.LocalAddr()) |
|
||||
s.Base.handleConn(conn) |
|
||||
} |
|
||||
@ -1,408 +0,0 @@ |
|||||
// KCP feature is based on https://github.com/xtaci/kcptun
|
|
||||
|
|
||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"crypto/sha1" |
|
||||
"encoding/csv" |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"github.com/golang/glog" |
|
||||
"github.com/klauspost/compress/snappy" |
|
||||
"golang.org/x/crypto/pbkdf2" |
|
||||
"gopkg.in/xtaci/kcp-go.v2" |
|
||||
"gopkg.in/xtaci/smux.v1" |
|
||||
"net" |
|
||||
"os" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
const ( |
|
||||
DefaultKCPConfigFile = "kcp.json" |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
SALT = "kcp-go" |
|
||||
) |
|
||||
|
|
||||
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"` |
|
||||
} |
|
||||
|
|
||||
func ParseKCPConfig(configFile string) (*KCPConfig, error) { |
|
||||
if configFile == "" { |
|
||||
configFile = DefaultKCPConfigFile |
|
||||
} |
|
||||
file, err := os.Open(configFile) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
defer file.Close() |
|
||||
|
|
||||
config := &KCPConfig{} |
|
||||
if err = json.NewDecoder(file).Decode(config); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
return config, nil |
|
||||
} |
|
||||
|
|
||||
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 = &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, |
|
||||
} |
|
||||
) |
|
||||
|
|
||||
type KCPServer struct { |
|
||||
Base *ProxyServer |
|
||||
Config *KCPConfig |
|
||||
} |
|
||||
|
|
||||
func NewKCPServer(base *ProxyServer, config *KCPConfig) *KCPServer { |
|
||||
return &KCPServer{Base: base, Config: config} |
|
||||
} |
|
||||
|
|
||||
func (s *KCPServer) ListenAndServe() (err error) { |
|
||||
if s.Config == nil { |
|
||||
s.Config = DefaultKCPConfig |
|
||||
} |
|
||||
s.Config.Init() |
|
||||
|
|
||||
ln, err := kcp.ListenWithOptions(s.Base.Node.Addr, |
|
||||
blockCrypt(s.Config.Key, s.Config.Crypt, SALT), s.Config.DataShard, s.Config.ParityShard) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
if err = ln.SetDSCP(s.Config.DSCP); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
} |
|
||||
if err = ln.SetReadBuffer(s.Config.SockBuf); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
} |
|
||||
if err = ln.SetWriteBuffer(s.Config.SockBuf); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
} |
|
||||
|
|
||||
go snmpLogger(s.Config.SnmpLog, s.Config.SnmpPeriod) |
|
||||
go kcpSigHandler() |
|
||||
for { |
|
||||
conn, err := ln.AcceptKCP() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
conn.SetStreamMode(true) |
|
||||
conn.SetNoDelay(s.Config.NoDelay, s.Config.Interval, s.Config.Resend, s.Config.NoCongestion) |
|
||||
conn.SetMtu(s.Config.MTU) |
|
||||
conn.SetWindowSize(s.Config.SndWnd, s.Config.RcvWnd) |
|
||||
conn.SetACKNoDelay(s.Config.AckNodelay) |
|
||||
conn.SetKeepAlive(s.Config.KeepAlive) |
|
||||
|
|
||||
go s.handleMux(conn) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *KCPServer) handleMux(conn net.Conn) { |
|
||||
smuxConfig := smux.DefaultConfig() |
|
||||
smuxConfig.MaxReceiveBuffer = s.Config.SockBuf |
|
||||
|
|
||||
glog.V(LINFO).Infof("[kcp] %s - %s", conn.RemoteAddr(), s.Base.Node.Addr) |
|
||||
|
|
||||
if !s.Config.NoComp { |
|
||||
conn = newCompStreamConn(conn) |
|
||||
} |
|
||||
|
|
||||
mux, err := smux.Server(conn, smuxConfig) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
return |
|
||||
} |
|
||||
defer mux.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[kcp] %s <-> %s", conn.RemoteAddr(), s.Base.Node.Addr) |
|
||||
defer glog.V(LINFO).Infof("[kcp] %s >-< %s", conn.RemoteAddr(), s.Base.Node.Addr) |
|
||||
|
|
||||
for { |
|
||||
stream, err := mux.AcceptStream() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
return |
|
||||
} |
|
||||
go s.Base.handleConn(NewKCPConn(conn, stream)) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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(path string, interval int) { |
|
||||
if path == "" || 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(path), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[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 { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
} |
|
||||
} |
|
||||
if err := w.Write(append([]string{fmt.Sprint(time.Now().Unix())}, kcp.DefaultSnmp.ToSlice()...)); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
} |
|
||||
kcp.DefaultSnmp.Reset() |
|
||||
w.Flush() |
|
||||
f.Close() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
type KCPSession struct { |
|
||||
conn net.Conn |
|
||||
session *smux.Session |
|
||||
} |
|
||||
|
|
||||
func DialKCP(addr string, config *KCPConfig) (*KCPSession, error) { |
|
||||
if config == nil { |
|
||||
config = DefaultKCPConfig |
|
||||
} |
|
||||
config.Init() |
|
||||
|
|
||||
kcpconn, err := kcp.DialWithOptions(addr, |
|
||||
blockCrypt(config.Key, config.Crypt, SALT), config.DataShard, config.ParityShard) |
|
||||
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 { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
} |
|
||||
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
} |
|
||||
if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[kcp]", err) |
|
||||
} |
|
||||
|
|
||||
// stream multiplex
|
|
||||
smuxConfig := smux.DefaultConfig() |
|
||||
smuxConfig.MaxReceiveBuffer = config.SockBuf |
|
||||
var conn net.Conn = kcpconn |
|
||||
if !config.NoComp { |
|
||||
conn = newCompStreamConn(kcpconn) |
|
||||
} |
|
||||
session, err := smux.Client(conn, smuxConfig) |
|
||||
if err != nil { |
|
||||
conn.Close() |
|
||||
return nil, err |
|
||||
} |
|
||||
return &KCPSession{conn: conn, session: session}, nil |
|
||||
} |
|
||||
|
|
||||
func (session *KCPSession) GetConn() (*KCPConn, error) { |
|
||||
stream, err := session.session.OpenStream() |
|
||||
if err != nil { |
|
||||
session.Close() |
|
||||
return nil, err |
|
||||
} |
|
||||
return NewKCPConn(session.conn, 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 KCPConn struct { |
|
||||
conn net.Conn |
|
||||
stream *smux.Stream |
|
||||
} |
|
||||
|
|
||||
func NewKCPConn(conn net.Conn, stream *smux.Stream) *KCPConn { |
|
||||
return &KCPConn{conn: conn, stream: 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 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,161 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"bufio" |
|
||||
"fmt" |
|
||||
"github.com/golang/glog" |
|
||||
"net" |
|
||||
"net/url" |
|
||||
"os" |
|
||||
"strconv" |
|
||||
"strings" |
|
||||
) |
|
||||
|
|
||||
// Proxy node represent a proxy
|
|
||||
type ProxyNode struct { |
|
||||
Addr string // [host]:port
|
|
||||
Protocol string // protocol: http/socks5/ss
|
|
||||
Transport string // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp
|
|
||||
Remote string // remote address, used by tcp/udp port forwarding
|
|
||||
Users []*url.Userinfo // authentication for proxy
|
|
||||
values url.Values |
|
||||
serverName string |
|
||||
conn net.Conn |
|
||||
} |
|
||||
|
|
||||
// The proxy node string pattern is [scheme://][user:pass@host]:port.
|
|
||||
//
|
|
||||
// Scheme can be devided into two parts by character '+', such as: http+tls.
|
|
||||
func ParseProxyNode(s string) (node ProxyNode, err error) { |
|
||||
if !strings.Contains(s, "://") { |
|
||||
s = "gost://" + s |
|
||||
} |
|
||||
u, err := url.Parse(s) |
|
||||
if err != nil { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
node = ProxyNode{ |
|
||||
Addr: u.Host, |
|
||||
values: u.Query(), |
|
||||
serverName: u.Host, |
|
||||
} |
|
||||
|
|
||||
if u.User != nil { |
|
||||
node.Users = append(node.Users, u.User) |
|
||||
} |
|
||||
|
|
||||
users, er := parseUsers(node.Get("secrets")) |
|
||||
if users != nil { |
|
||||
node.Users = append(node.Users, users...) |
|
||||
} |
|
||||
if er != nil { |
|
||||
glog.V(LWARNING).Infoln("secrets:", er) |
|
||||
} |
|
||||
|
|
||||
if strings.Contains(u.Host, ":") { |
|
||||
node.serverName, _, _ = net.SplitHostPort(u.Host) |
|
||||
if node.serverName == "" { |
|
||||
node.serverName = "localhost" // default server name
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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 "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu", "pht", "ssh": |
|
||||
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": // started from v2.1, rtcp and rudp are for remote port forwarding
|
|
||||
node.Remote = strings.Trim(u.EscapedPath(), "/") |
|
||||
default: |
|
||||
node.Transport = "" |
|
||||
} |
|
||||
|
|
||||
switch node.Protocol { |
|
||||
case "http", "http2", "socks", "socks4", "socks4a", "socks5", "ss": |
|
||||
default: |
|
||||
node.Protocol = "" |
|
||||
} |
|
||||
|
|
||||
return |
|
||||
} |
|
||||
|
|
||||
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 |
|
||||
} |
|
||||
|
|
||||
// Get get node parameter by key
|
|
||||
func (node *ProxyNode) Get(key string) string { |
|
||||
return node.values.Get(key) |
|
||||
} |
|
||||
|
|
||||
func (node *ProxyNode) getBool(key string) bool { |
|
||||
s := node.Get(key) |
|
||||
if b, _ := strconv.ParseBool(s); b { |
|
||||
return b |
|
||||
} |
|
||||
n, _ := strconv.Atoi(s) |
|
||||
return n > 0 |
|
||||
} |
|
||||
|
|
||||
func (node *ProxyNode) Set(key, value string) { |
|
||||
node.values.Set(key, value) |
|
||||
} |
|
||||
|
|
||||
func (node *ProxyNode) insecureSkipVerify() bool { |
|
||||
return !node.getBool("secure") |
|
||||
} |
|
||||
|
|
||||
func (node *ProxyNode) certFile() string { |
|
||||
if cert := node.Get("cert"); cert != "" { |
|
||||
return cert |
|
||||
} |
|
||||
return DefaultCertFile |
|
||||
} |
|
||||
|
|
||||
func (node *ProxyNode) keyFile() string { |
|
||||
if key := node.Get("key"); key != "" { |
|
||||
return key |
|
||||
} |
|
||||
return DefaultKeyFile |
|
||||
} |
|
||||
|
|
||||
func (node ProxyNode) String() string { |
|
||||
return fmt.Sprintf("transport: %s, protocol: %s, addr: %s", node.Transport, node.Protocol, node.Addr) |
|
||||
} |
|
||||
@ -1,81 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"bufio" |
|
||||
"crypto/tls" |
|
||||
"github.com/golang/glog" |
|
||||
"github.com/lucas-clemente/quic-go/h2quic" |
|
||||
"io" |
|
||||
"net/http" |
|
||||
"net/http/httputil" |
|
||||
) |
|
||||
|
|
||||
type QuicServer struct { |
|
||||
Base *ProxyServer |
|
||||
Handler http.Handler |
|
||||
TLSConfig *tls.Config |
|
||||
} |
|
||||
|
|
||||
func NewQuicServer(base *ProxyServer) *QuicServer { |
|
||||
return &QuicServer{Base: base} |
|
||||
} |
|
||||
|
|
||||
func (s *QuicServer) ListenAndServeTLS(config *tls.Config) error { |
|
||||
server := &h2quic.Server{ |
|
||||
Server: &http.Server{ |
|
||||
Addr: s.Base.Node.Addr, |
|
||||
Handler: s.Handler, |
|
||||
TLSConfig: config, |
|
||||
}, |
|
||||
} |
|
||||
if server.Handler == nil { |
|
||||
// server.Handler = http.HandlerFunc(s.HandleRequest)
|
|
||||
server.Handler = http.HandlerFunc(NewHttp2Server(s.Base).HandleRequest) |
|
||||
} |
|
||||
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) |
|
||||
|
|
||||
if glog.V(LDEBUG) { |
|
||||
dump, _ := httputil.DumpRequest(req, false) |
|
||||
glog.Infoln(string(dump)) |
|
||||
} |
|
||||
|
|
||||
c, err := s.Base.Chain.Dial(target) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[quic] %s -> %s : %s", req.RemoteAddr, target, err) |
|
||||
w.WriteHeader(http.StatusServiceUnavailable) |
|
||||
return |
|
||||
} |
|
||||
defer c.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[quic] %s <-> %s", req.RemoteAddr, target) |
|
||||
|
|
||||
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 |
|
||||
} |
|
||||
|
|
||||
resp, err := http.ReadResponse(bufio.NewReader(c), req) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln(err) |
|
||||
return |
|
||||
} |
|
||||
defer resp.Body.Close() |
|
||||
|
|
||||
for k, v := range resp.Header { |
|
||||
for _, vv := range v { |
|
||||
w.Header().Add(k, vv) |
|
||||
} |
|
||||
} |
|
||||
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) |
|
||||
} |
|
||||
|
|
||||
glog.V(LINFO).Infof("[quic] %s >-< %s", req.RemoteAddr, target) |
|
||||
} |
|
||||
@ -1,103 +0,0 @@ |
|||||
// +build !windows
|
|
||||
|
|
||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
"fmt" |
|
||||
"github.com/golang/glog" |
|
||||
"net" |
|
||||
"syscall" |
|
||||
) |
|
||||
|
|
||||
const ( |
|
||||
SO_ORIGINAL_DST = 80 |
|
||||
) |
|
||||
|
|
||||
type RedsocksTCPServer struct { |
|
||||
Base *ProxyServer |
|
||||
} |
|
||||
|
|
||||
func NewRedsocksTCPServer(base *ProxyServer) *RedsocksTCPServer { |
|
||||
return &RedsocksTCPServer{ |
|
||||
Base: base, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *RedsocksTCPServer) ListenAndServe() error { |
|
||||
laddr, err := net.ResolveTCPAddr("tcp", s.Base.Node.Addr) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
ln, err := net.ListenTCP("tcp", laddr) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
defer ln.Close() |
|
||||
for { |
|
||||
conn, err := ln.AcceptTCP() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln(err) |
|
||||
continue |
|
||||
} |
|
||||
go s.handleRedirectTCP(conn) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *RedsocksTCPServer) handleRedirectTCP(conn *net.TCPConn) { |
|
||||
srcAddr := conn.RemoteAddr() |
|
||||
dstAddr, conn, err := getOriginalDstAddr(conn) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err) |
|
||||
return |
|
||||
} |
|
||||
defer conn.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[red-tcp] %s -> %s", srcAddr, dstAddr) |
|
||||
|
|
||||
cc, err := s.Base.Chain.Dial(dstAddr.String()) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err) |
|
||||
return |
|
||||
} |
|
||||
defer cc.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[red-tcp] %s <-> %s", srcAddr, dstAddr) |
|
||||
s.Base.transport(conn, cc) |
|
||||
glog.V(LINFO).Infof("[red-tcp] %s >-< %s", srcAddr, dstAddr) |
|
||||
} |
|
||||
|
|
||||
func 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, SO_ORIGINAL_DST) |
|
||||
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,17 +0,0 @@ |
|||||
// +build windows
|
|
||||
|
|
||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
) |
|
||||
|
|
||||
type RedsocksTCPServer struct{} |
|
||||
|
|
||||
func NewRedsocksTCPServer(base *ProxyServer) *RedsocksTCPServer { |
|
||||
return &RedsocksTCPServer{} |
|
||||
} |
|
||||
|
|
||||
func (s *RedsocksTCPServer) ListenAndServe() error { |
|
||||
return errors.New("Not supported") |
|
||||
} |
|
||||
@ -1,284 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"bufio" |
|
||||
"crypto/tls" |
|
||||
"github.com/ginuerzh/gosocks4" |
|
||||
"github.com/ginuerzh/gosocks5" |
|
||||
"github.com/golang/glog" |
|
||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" |
|
||||
"golang.org/x/crypto/ssh" |
|
||||
"io" |
|
||||
"io/ioutil" |
|
||||
"net" |
|
||||
"net/http" |
|
||||
"strconv" |
|
||||
"strings" |
|
||||
) |
|
||||
|
|
||||
type ProxyServer struct { |
|
||||
Node ProxyNode |
|
||||
Chain *ProxyChain |
|
||||
TLSConfig *tls.Config |
|
||||
selector *serverSelector |
|
||||
cipher *ss.Cipher |
|
||||
ota bool |
|
||||
} |
|
||||
|
|
||||
func NewProxyServer(node ProxyNode, chain *ProxyChain, config *tls.Config) *ProxyServer { |
|
||||
if chain == nil { |
|
||||
chain = NewProxyChain() |
|
||||
} |
|
||||
if config == nil { |
|
||||
config = &tls.Config{} |
|
||||
} |
|
||||
|
|
||||
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 err != nil { |
|
||||
glog.Fatal(err) |
|
||||
} |
|
||||
} |
|
||||
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, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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 |
|
||||
} |
|
||||
config := ssh.ServerConfig{ |
|
||||
PasswordCallback: DefaultPasswordCallback(s.Node.Users), |
|
||||
} |
|
||||
if len(s.Node.Users) == 0 { |
|
||||
config.NoClientAuth = true |
|
||||
} |
|
||||
|
|
||||
config.AddHostKey(private) |
|
||||
s := &SSHServer{ |
|
||||
Addr: node.Addr, |
|
||||
Base: s, |
|
||||
Config: &config, |
|
||||
} |
|
||||
return s.ListenAndServe() |
|
||||
default: |
|
||||
ln, err = net.Listen("tcp", node.Addr) |
|
||||
} |
|
||||
|
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
defer ln.Close() |
|
||||
|
|
||||
for { |
|
||||
conn, err := ln.Accept() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln(err) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
setKeepAlive(conn, KeepAliveTime) |
|
||||
|
|
||||
go s.handleConn(conn) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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 |
|
||||
} |
|
||||
|
|
||||
br := bufio.NewReader(conn) |
|
||||
b, err := br.Peek(1) |
|
||||
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) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (_ *ProxyServer) transport(conn1, conn2 net.Conn) (err error) { |
|
||||
errc := make(chan error, 2) |
|
||||
|
|
||||
go func() { |
|
||||
_, err := io.Copy(conn1, conn2) |
|
||||
errc <- err |
|
||||
}() |
|
||||
|
|
||||
go func() { |
|
||||
_, err := io.Copy(conn2, conn1) |
|
||||
errc <- err |
|
||||
}() |
|
||||
|
|
||||
select { |
|
||||
case err = <-errc: |
|
||||
// glog.V(LWARNING).Infoln("transport exit", err)
|
|
||||
} |
|
||||
|
|
||||
return |
|
||||
} |
|
||||
@ -1,5 +0,0 @@ |
|||||
// +build windows
|
|
||||
|
|
||||
package gost |
|
||||
|
|
||||
func kcpSigHandler() {} |
|
||||
@ -1,23 +0,0 @@ |
|||||
// +build !windows
|
|
||||
|
|
||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"github.com/golang/glog" |
|
||||
"gopkg.in/xtaci/kcp-go.v2" |
|
||||
"os" |
|
||||
"os/signal" |
|
||||
"syscall" |
|
||||
) |
|
||||
|
|
||||
func kcpSigHandler() { |
|
||||
ch := make(chan os.Signal, 1) |
|
||||
signal.Notify(ch, syscall.SIGUSR1) |
|
||||
|
|
||||
for { |
|
||||
switch <-ch { |
|
||||
case syscall.SIGUSR1: |
|
||||
glog.V(LINFO).Infof("[kcp] SNMP: %+v", kcp.DefaultSnmp.Copy()) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,746 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"bytes" |
|
||||
"crypto/tls" |
|
||||
"github.com/ginuerzh/gosocks4" |
|
||||
"github.com/ginuerzh/gosocks5" |
|
||||
"github.com/golang/glog" |
|
||||
"net" |
|
||||
"net/url" |
|
||||
"strconv" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
const ( |
|
||||
MethodTLS uint8 = 0x80 // extended method for tls
|
|
||||
MethodTLSAuth uint8 = 0x82 // extended method for tls+auth
|
|
||||
) |
|
||||
|
|
||||
const ( |
|
||||
CmdUdpTun uint8 = 0xF3 // extended method for udp over tcp
|
|
||||
) |
|
||||
|
|
||||
type clientSelector struct { |
|
||||
methods []uint8 |
|
||||
user *url.Userinfo |
|
||||
tlsConfig *tls.Config |
|
||||
} |
|
||||
|
|
||||
func (selector *clientSelector) Methods() []uint8 { |
|
||||
return selector.methods |
|
||||
} |
|
||||
|
|
||||
func (selector *clientSelector) Select(methods ...uint8) (method uint8) { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
func (selector *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) { |
|
||||
switch method { |
|
||||
case MethodTLS: |
|
||||
conn = tls.Client(conn, selector.tlsConfig) |
|
||||
|
|
||||
case gosocks5.MethodUserPass, MethodTLSAuth: |
|
||||
if method == MethodTLSAuth { |
|
||||
conn = tls.Client(conn, selector.tlsConfig) |
|
||||
} |
|
||||
|
|
||||
var username, password string |
|
||||
if selector.user != nil { |
|
||||
username = selector.user.Username() |
|
||||
password, _ = selector.user.Password() |
|
||||
} |
|
||||
|
|
||||
req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password) |
|
||||
if err := req.Write(conn); err != nil { |
|
||||
glog.V(LWARNING).Infoln("socks5 auth:", err) |
|
||||
return nil, err |
|
||||
} |
|
||||
glog.V(LDEBUG).Infoln(req) |
|
||||
|
|
||||
resp, err := gosocks5.ReadUserPassResponse(conn) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("socks5 auth:", err) |
|
||||
return nil, err |
|
||||
} |
|
||||
glog.V(LDEBUG).Infoln(resp) |
|
||||
|
|
||||
if resp.Status != gosocks5.Succeeded { |
|
||||
return nil, gosocks5.ErrAuthFailure |
|
||||
} |
|
||||
case gosocks5.MethodNoAcceptable: |
|
||||
return nil, gosocks5.ErrBadMethod |
|
||||
} |
|
||||
|
|
||||
return conn, nil |
|
||||
} |
|
||||
|
|
||||
type serverSelector struct { |
|
||||
methods []uint8 |
|
||||
users []*url.Userinfo |
|
||||
tlsConfig *tls.Config |
|
||||
} |
|
||||
|
|
||||
func (selector *serverSelector) Methods() []uint8 { |
|
||||
return selector.methods |
|
||||
} |
|
||||
|
|
||||
func (selector *serverSelector) Select(methods ...uint8) (method uint8) { |
|
||||
glog.V(LDEBUG).Infof("%d %d %v", gosocks5.Ver5, len(methods), methods) |
|
||||
|
|
||||
method = gosocks5.MethodNoAuth |
|
||||
for _, m := range methods { |
|
||||
if m == MethodTLS { |
|
||||
method = m |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// when user/pass is set, auth is mandatory
|
|
||||
if selector.users != nil { |
|
||||
if method == gosocks5.MethodNoAuth { |
|
||||
method = gosocks5.MethodUserPass |
|
||||
} |
|
||||
if method == MethodTLS { |
|
||||
method = MethodTLSAuth |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return |
|
||||
} |
|
||||
|
|
||||
func (selector *serverSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) { |
|
||||
glog.V(LDEBUG).Infof("%d %d", gosocks5.Ver5, method) |
|
||||
|
|
||||
switch method { |
|
||||
case MethodTLS: |
|
||||
conn = tls.Server(conn, selector.tlsConfig) |
|
||||
|
|
||||
case gosocks5.MethodUserPass, MethodTLSAuth: |
|
||||
if method == MethodTLSAuth { |
|
||||
conn = tls.Server(conn, selector.tlsConfig) |
|
||||
} |
|
||||
|
|
||||
req, err := gosocks5.ReadUserPassRequest(conn) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[socks5-auth]", err) |
|
||||
return nil, err |
|
||||
} |
|
||||
glog.V(LDEBUG).Infoln("[socks5]", req.String()) |
|
||||
|
|
||||
valid := false |
|
||||
for _, user := range selector.users { |
|
||||
username := user.Username() |
|
||||
password, _ := user.Password() |
|
||||
if (req.Username == username && req.Password == password) || |
|
||||
(req.Username == username && password == "") || |
|
||||
(username == "" && req.Password == password) { |
|
||||
valid = true |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
if len(selector.users) > 0 && !valid { |
|
||||
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure) |
|
||||
if err := resp.Write(conn); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[socks5-auth]", err) |
|
||||
return nil, err |
|
||||
} |
|
||||
glog.V(LDEBUG).Infoln("[socks5]", resp) |
|
||||
glog.V(LWARNING).Infoln("[socks5-auth] proxy authentication required") |
|
||||
|
|
||||
return nil, gosocks5.ErrAuthFailure |
|
||||
} |
|
||||
|
|
||||
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Succeeded) |
|
||||
if err := resp.Write(conn); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[socks5-auth]", err) |
|
||||
return nil, err |
|
||||
} |
|
||||
glog.V(LDEBUG).Infoln(resp) |
|
||||
|
|
||||
case gosocks5.MethodNoAcceptable: |
|
||||
return nil, gosocks5.ErrBadMethod |
|
||||
} |
|
||||
|
|
||||
return conn, nil |
|
||||
} |
|
||||
|
|
||||
type Socks5Server struct { |
|
||||
conn net.Conn |
|
||||
Base *ProxyServer |
|
||||
} |
|
||||
|
|
||||
func NewSocks5Server(conn net.Conn, base *ProxyServer) *Socks5Server { |
|
||||
return &Socks5Server{conn: conn, Base: base} |
|
||||
} |
|
||||
|
|
||||
func (s *Socks5Server) HandleRequest(req *gosocks5.Request) { |
|
||||
glog.V(LDEBUG).Infof("[socks5] %s -> %s\n%s", s.conn.RemoteAddr(), req.Addr, req) |
|
||||
|
|
||||
switch req.Cmd { |
|
||||
case gosocks5.CmdConnect: |
|
||||
glog.V(LINFO).Infof("[socks5-connect] %s -> %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
s.handleConnect(req) |
|
||||
|
|
||||
case gosocks5.CmdBind: |
|
||||
glog.V(LINFO).Infof("[socks5-bind] %s - %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
s.handleBind(req) |
|
||||
|
|
||||
case gosocks5.CmdUdp: |
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s - %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
s.handleUDPRelay(req) |
|
||||
|
|
||||
case CmdUdpTun: |
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s - %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
s.handleUDPTunnel(req) |
|
||||
|
|
||||
default: |
|
||||
glog.V(LWARNING).Infoln("[socks5] Unrecognized request:", req.Cmd) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *Socks5Server) handleConnect(req *gosocks5.Request) { |
|
||||
cc, err := s.Base.Chain.Dial(req.Addr.String()) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-connect] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
rep := gosocks5.NewReply(gosocks5.HostUnreachable, nil) |
|
||||
rep.Write(s.conn) |
|
||||
glog.V(LDEBUG).Infof("[socks5-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) |
|
||||
return |
|
||||
} |
|
||||
defer cc.Close() |
|
||||
|
|
||||
rep := gosocks5.NewReply(gosocks5.Succeeded, nil) |
|
||||
if err := rep.Write(s.conn); err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-connect] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[socks5-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) |
|
||||
|
|
||||
glog.V(LINFO).Infof("[socks5-connect] %s <-> %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
//Transport(conn, cc)
|
|
||||
s.Base.transport(s.conn, cc) |
|
||||
glog.V(LINFO).Infof("[socks5-connect] %s >-< %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
} |
|
||||
|
|
||||
func (s *Socks5Server) handleBind(req *gosocks5.Request) { |
|
||||
cc, err := s.Base.Chain.GetConn() |
|
||||
|
|
||||
// connection error
|
|
||||
if err != nil && err != ErrEmptyChain { |
|
||||
glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
reply := gosocks5.NewReply(gosocks5.Failure, nil) |
|
||||
reply.Write(s.conn) |
|
||||
glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) |
|
||||
return |
|
||||
} |
|
||||
// serve socks5 bind
|
|
||||
if err == ErrEmptyChain { |
|
||||
s.bindOn(req.Addr.String()) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
defer cc.Close() |
|
||||
// forward request
|
|
||||
req.Write(cc) |
|
||||
|
|
||||
glog.V(LINFO).Infof("[socks5-bind] %s <-> %s", s.conn.RemoteAddr(), cc.RemoteAddr()) |
|
||||
s.Base.transport(s.conn, cc) |
|
||||
glog.V(LINFO).Infof("[socks5-bind] %s >-< %s", s.conn.RemoteAddr(), cc.RemoteAddr()) |
|
||||
} |
|
||||
|
|
||||
func (s *Socks5Server) handleUDPRelay(req *gosocks5.Request) { |
|
||||
bindAddr, _ := net.ResolveUDPAddr("udp", req.Addr.String()) |
|
||||
relay, err := net.ListenUDP("udp", bindAddr) // udp associate, strict mode: if the port already in use, it will return error
|
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
reply := gosocks5.NewReply(gosocks5.Failure, nil) |
|
||||
reply.Write(s.conn) |
|
||||
glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) |
|
||||
return |
|
||||
} |
|
||||
defer relay.Close() |
|
||||
|
|
||||
socksAddr := ToSocksAddr(relay.LocalAddr()) |
|
||||
socksAddr.Host, _, _ = net.SplitHostPort(s.conn.LocalAddr().String()) |
|
||||
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) |
|
||||
if err := reply.Write(s.conn); err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", s.conn.RemoteAddr(), reply.Addr, reply) |
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s - %s BIND ON %s OK", s.conn.RemoteAddr(), req.Addr, socksAddr) |
|
||||
|
|
||||
cc, err := s.Base.Chain.GetConn() |
|
||||
// connection error
|
|
||||
if err != nil && err != ErrEmptyChain { |
|
||||
glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), socksAddr, err) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// serve as standard socks5 udp relay local <-> remote
|
|
||||
if err == ErrEmptyChain { |
|
||||
peer, er := net.ListenUDP("udp", nil) |
|
||||
if er != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), socksAddr, er) |
|
||||
return |
|
||||
} |
|
||||
defer peer.Close() |
|
||||
|
|
||||
go s.transportUDP(relay, peer) |
|
||||
} |
|
||||
|
|
||||
// forward udp local <-> tunnel
|
|
||||
if err == nil { |
|
||||
defer cc.Close() |
|
||||
|
|
||||
cc.SetWriteDeadline(time.Now().Add(WriteTimeout)) |
|
||||
req := gosocks5.NewRequest(CmdUdpTun, nil) |
|
||||
if err := req.Write(cc); err != nil { |
|
||||
glog.V(LWARNING).Infoln("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), cc.RemoteAddr(), err) |
|
||||
return |
|
||||
} |
|
||||
cc.SetWriteDeadline(time.Time{}) |
|
||||
glog.V(LDEBUG).Infof("[socks5-udp] %s -> %s\n%s", s.conn.RemoteAddr(), cc.RemoteAddr(), req) |
|
||||
|
|
||||
cc.SetReadDeadline(time.Now().Add(ReadTimeout)) |
|
||||
reply, err = gosocks5.ReadReply(cc) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), cc.RemoteAddr(), err) |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", s.conn.RemoteAddr(), cc.RemoteAddr(), reply) |
|
||||
|
|
||||
if reply.Rep != gosocks5.Succeeded { |
|
||||
glog.V(LWARNING).Infoln("[socks5-udp] %s <- %s : udp associate failed", s.conn.RemoteAddr(), cc.RemoteAddr()) |
|
||||
return |
|
||||
} |
|
||||
cc.SetReadDeadline(time.Time{}) |
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s <-> %s [tun: %s]", s.conn.RemoteAddr(), socksAddr, reply.Addr) |
|
||||
|
|
||||
go s.tunnelClientUDP(relay, cc) |
|
||||
} |
|
||||
|
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", s.conn.RemoteAddr(), socksAddr) |
|
||||
b := make([]byte, SmallBufferSize) |
|
||||
for { |
|
||||
_, err := s.conn.Read(b) // discard any data from tcp connection
|
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-udp] %s - %s : %s", s.conn.RemoteAddr(), socksAddr, err) |
|
||||
break // client disconnected
|
|
||||
} |
|
||||
} |
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s >-< %s", s.conn.RemoteAddr(), socksAddr) |
|
||||
} |
|
||||
|
|
||||
func (s *Socks5Server) handleUDPTunnel(req *gosocks5.Request) { |
|
||||
cc, err := s.Base.Chain.GetConn() |
|
||||
|
|
||||
// connection error
|
|
||||
if err != nil && err != ErrEmptyChain { |
|
||||
glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
reply := gosocks5.NewReply(gosocks5.Failure, nil) |
|
||||
reply.Write(s.conn) |
|
||||
glog.V(LDEBUG).Infof("[socks5-udp] %s -> %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// serve tunnel udp, tunnel <-> remote, handle tunnel udp request
|
|
||||
if err == ErrEmptyChain { |
|
||||
bindAddr, _ := net.ResolveUDPAddr("udp", req.Addr.String()) |
|
||||
uc, err := net.ListenUDP("udp", bindAddr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
return |
|
||||
} |
|
||||
defer uc.Close() |
|
||||
|
|
||||
socksAddr := ToSocksAddr(uc.LocalAddr()) |
|
||||
socksAddr.Host, _, _ = net.SplitHostPort(s.conn.LocalAddr().String()) |
|
||||
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) |
|
||||
if err := reply.Write(s.conn); err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", s.conn.RemoteAddr(), socksAddr, err) |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", s.conn.RemoteAddr(), socksAddr, reply) |
|
||||
|
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", s.conn.RemoteAddr(), socksAddr) |
|
||||
s.tunnelServerUDP(s.conn, uc) |
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s >-< %s", s.conn.RemoteAddr(), socksAddr) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
defer cc.Close() |
|
||||
|
|
||||
// tunnel <-> tunnel, direct forwarding
|
|
||||
req.Write(cc) |
|
||||
|
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s <-> %s [tun]", s.conn.RemoteAddr(), cc.RemoteAddr()) |
|
||||
s.Base.transport(s.conn, cc) |
|
||||
glog.V(LINFO).Infof("[socks5-udp] %s >-< %s [tun]", s.conn.RemoteAddr(), cc.RemoteAddr()) |
|
||||
} |
|
||||
|
|
||||
func (s *Socks5Server) bindOn(addr string) { |
|
||||
bindAddr, _ := net.ResolveTCPAddr("tcp", addr) |
|
||||
ln, err := net.ListenTCP("tcp", bindAddr) // strict mode: if the port already in use, it will return error
|
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-bind] %s -> %s : %s", s.conn.RemoteAddr(), addr, err) |
|
||||
gosocks5.NewReply(gosocks5.Failure, nil).Write(s.conn) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
socksAddr := ToSocksAddr(ln.Addr()) |
|
||||
// Issue: may not reachable when host has multi-interface
|
|
||||
socksAddr.Host, _, _ = net.SplitHostPort(s.conn.LocalAddr().String()) |
|
||||
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) |
|
||||
if err := reply.Write(s.conn); err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", s.conn.RemoteAddr(), addr, err) |
|
||||
ln.Close() |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", s.conn.RemoteAddr(), addr, reply) |
|
||||
glog.V(LINFO).Infof("[socks5-bind] %s - %s BIND ON %s OK", s.conn.RemoteAddr(), addr, socksAddr) |
|
||||
|
|
||||
var pconn net.Conn |
|
||||
accept := func() <-chan error { |
|
||||
errc := make(chan error, 1) |
|
||||
|
|
||||
go func() { |
|
||||
defer close(errc) |
|
||||
defer ln.Close() |
|
||||
|
|
||||
c, err := ln.AcceptTCP() |
|
||||
if err != nil { |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
pconn = c |
|
||||
}() |
|
||||
|
|
||||
return errc |
|
||||
} |
|
||||
|
|
||||
pc1, pc2 := net.Pipe() |
|
||||
pipe := func() <-chan error { |
|
||||
errc := make(chan error, 1) |
|
||||
|
|
||||
go func() { |
|
||||
defer close(errc) |
|
||||
defer pc1.Close() |
|
||||
|
|
||||
errc <- s.Base.transport(s.conn, pc1) |
|
||||
}() |
|
||||
|
|
||||
return errc |
|
||||
} |
|
||||
|
|
||||
defer pc2.Close() |
|
||||
|
|
||||
for { |
|
||||
select { |
|
||||
case err := <-accept(): |
|
||||
if err != nil || pconn == nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", s.conn.RemoteAddr(), addr, err) |
|
||||
return |
|
||||
} |
|
||||
defer pconn.Close() |
|
||||
|
|
||||
reply := gosocks5.NewReply(gosocks5.Succeeded, ToSocksAddr(pconn.RemoteAddr())) |
|
||||
if err := reply.Write(pc2); err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", s.conn.RemoteAddr(), addr, err) |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", s.conn.RemoteAddr(), addr, reply) |
|
||||
glog.V(LINFO).Infof("[socks5-bind] %s <- %s PEER %s ACCEPTED", s.conn.RemoteAddr(), socksAddr, pconn.RemoteAddr()) |
|
||||
|
|
||||
glog.V(LINFO).Infof("[socks5-bind] %s <-> %s", s.conn.RemoteAddr(), pconn.RemoteAddr()) |
|
||||
if err = s.Base.transport(pc2, pconn); err != nil { |
|
||||
glog.V(LWARNING).Infoln(err) |
|
||||
} |
|
||||
glog.V(LINFO).Infof("[socks5-bind] %s >-< %s", s.conn.RemoteAddr(), pconn.RemoteAddr()) |
|
||||
return |
|
||||
case err := <-pipe(): |
|
||||
glog.V(LWARNING).Infof("[socks5-bind] %s -> %s : %v", s.conn.RemoteAddr(), addr, err) |
|
||||
ln.Close() |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *Socks5Server) transportUDP(relay, peer *net.UDPConn) (err error) { |
|
||||
errc := make(chan error, 2) |
|
||||
|
|
||||
var clientAddr *net.UDPAddr |
|
||||
|
|
||||
go func() { |
|
||||
b := make([]byte, LargeBufferSize) |
|
||||
|
|
||||
for { |
|
||||
n, laddr, err := relay.ReadFromUDP(b) |
|
||||
if err != nil { |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
if clientAddr == nil { |
|
||||
clientAddr = laddr |
|
||||
} |
|
||||
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n])) |
|
||||
if err != nil { |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
raddr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) |
|
||||
if err != nil { |
|
||||
continue // drop silently
|
|
||||
} |
|
||||
if _, err := peer.WriteToUDP(dgram.Data, raddr); err != nil { |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[socks5-udp] %s >>> %s length: %d", relay.LocalAddr(), raddr, len(dgram.Data)) |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
go func() { |
|
||||
b := make([]byte, LargeBufferSize) |
|
||||
|
|
||||
for { |
|
||||
n, raddr, err := peer.ReadFromUDP(b) |
|
||||
if err != nil { |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
if clientAddr == nil { |
|
||||
continue |
|
||||
} |
|
||||
buf := bytes.Buffer{} |
|
||||
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, ToSocksAddr(raddr)), b[:n]) |
|
||||
dgram.Write(&buf) |
|
||||
if _, err := relay.WriteToUDP(buf.Bytes(), clientAddr); err != nil { |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[socks5-udp] %s <<< %s length: %d", relay.LocalAddr(), raddr, len(dgram.Data)) |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
select { |
|
||||
case err = <-errc: |
|
||||
//log.Println("w exit", err)
|
|
||||
} |
|
||||
|
|
||||
return |
|
||||
} |
|
||||
|
|
||||
func (s *Socks5Server) tunnelClientUDP(uc *net.UDPConn, cc net.Conn) (err error) { |
|
||||
errc := make(chan error, 2) |
|
||||
|
|
||||
var clientAddr *net.UDPAddr |
|
||||
|
|
||||
go func() { |
|
||||
b := make([]byte, LargeBufferSize) |
|
||||
|
|
||||
for { |
|
||||
n, addr, err := uc.ReadFromUDP(b) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s <- %s : %s", cc.RemoteAddr(), addr, err) |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// glog.V(LDEBUG).Infof("read udp %d, % #x", n, b[:n])
|
|
||||
// pipe from relay to tunnel
|
|
||||
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n])) |
|
||||
if err != nil { |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
if clientAddr == nil { |
|
||||
clientAddr = addr |
|
||||
} |
|
||||
dgram.Header.Rsv = uint16(len(dgram.Data)) |
|
||||
if err := dgram.Write(cc); err != nil { |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[udp-tun] %s >>> %s length: %d", uc.LocalAddr(), dgram.Header.Addr, len(dgram.Data)) |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
go func() { |
|
||||
for { |
|
||||
dgram, err := gosocks5.ReadUDPDatagram(cc) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s -> 0 : %s", cc.RemoteAddr(), err) |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// pipe from tunnel to relay
|
|
||||
if clientAddr == nil { |
|
||||
continue |
|
||||
} |
|
||||
dgram.Header.Rsv = 0 |
|
||||
|
|
||||
buf := bytes.Buffer{} |
|
||||
dgram.Write(&buf) |
|
||||
if _, err := uc.WriteToUDP(buf.Bytes(), clientAddr); err != nil { |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s length: %d", uc.LocalAddr(), dgram.Header.Addr, len(dgram.Data)) |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
select { |
|
||||
case err = <-errc: |
|
||||
} |
|
||||
|
|
||||
return |
|
||||
} |
|
||||
|
|
||||
func (s *Socks5Server) tunnelServerUDP(cc net.Conn, uc *net.UDPConn) (err error) { |
|
||||
errc := make(chan error, 2) |
|
||||
|
|
||||
go func() { |
|
||||
b := make([]byte, LargeBufferSize) |
|
||||
|
|
||||
for { |
|
||||
n, addr, err := uc.ReadFromUDP(b) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s <- %s : %s", cc.RemoteAddr(), addr, err) |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// pipe from peer to tunnel
|
|
||||
dgram := gosocks5.NewUDPDatagram( |
|
||||
gosocks5.NewUDPHeader(uint16(n), 0, ToSocksAddr(addr)), b[:n]) |
|
||||
if err := dgram.Write(cc); err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s <- %s : %s", cc.RemoteAddr(), dgram.Header.Addr, err) |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s length: %d", cc.RemoteAddr(), dgram.Header.Addr, len(dgram.Data)) |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
go func() { |
|
||||
for { |
|
||||
dgram, err := gosocks5.ReadUDPDatagram(cc) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s -> 0 : %s", cc.RemoteAddr(), err) |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// pipe from tunnel to peer
|
|
||||
addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) |
|
||||
if err != nil { |
|
||||
continue // drop silently
|
|
||||
} |
|
||||
if _, err := uc.WriteToUDP(dgram.Data, addr); err != nil { |
|
||||
glog.V(LWARNING).Infof("[udp-tun] %s -> %s : %s", cc.RemoteAddr(), addr, err) |
|
||||
errc <- err |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[udp-tun] %s >>> %s length: %d", cc.RemoteAddr(), addr, len(dgram.Data)) |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
select { |
|
||||
case err = <-errc: |
|
||||
} |
|
||||
|
|
||||
return |
|
||||
} |
|
||||
|
|
||||
func ToSocksAddr(addr net.Addr) *gosocks5.Addr { |
|
||||
host := "0.0.0.0" |
|
||||
port := 0 |
|
||||
if addr != nil { |
|
||||
h, p, _ := net.SplitHostPort(addr.String()) |
|
||||
host = h |
|
||||
port, _ = strconv.Atoi(p) |
|
||||
} |
|
||||
return &gosocks5.Addr{ |
|
||||
Type: gosocks5.AddrIPv4, |
|
||||
Host: host, |
|
||||
Port: uint16(port), |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
type Socks4Server struct { |
|
||||
conn net.Conn |
|
||||
Base *ProxyServer |
|
||||
} |
|
||||
|
|
||||
func NewSocks4Server(conn net.Conn, base *ProxyServer) *Socks4Server { |
|
||||
return &Socks4Server{conn: conn, Base: base} |
|
||||
} |
|
||||
|
|
||||
func (s *Socks4Server) HandleRequest(req *gosocks4.Request) { |
|
||||
glog.V(LDEBUG).Infof("[socks4] %s -> %s\n%s", s.conn.RemoteAddr(), req.Addr, req) |
|
||||
|
|
||||
switch req.Cmd { |
|
||||
case gosocks4.CmdConnect: |
|
||||
glog.V(LINFO).Infof("[socks4-connect] %s -> %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
s.handleConnect(req) |
|
||||
|
|
||||
case gosocks4.CmdBind: |
|
||||
glog.V(LINFO).Infof("[socks4-bind] %s - %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
s.handleBind(req) |
|
||||
|
|
||||
default: |
|
||||
glog.V(LWARNING).Infoln("[socks4] Unrecognized request:", req.Cmd) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *Socks4Server) handleConnect(req *gosocks4.Request) { |
|
||||
cc, err := s.Base.Chain.Dial(req.Addr.String()) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks4-connect] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
rep := gosocks4.NewReply(gosocks4.Failed, nil) |
|
||||
rep.Write(s.conn) |
|
||||
glog.V(LDEBUG).Infof("[socks4-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) |
|
||||
return |
|
||||
} |
|
||||
defer cc.Close() |
|
||||
|
|
||||
rep := gosocks4.NewReply(gosocks4.Granted, nil) |
|
||||
if err := rep.Write(s.conn); err != nil { |
|
||||
glog.V(LWARNING).Infof("[socks4-connect] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
return |
|
||||
} |
|
||||
glog.V(LDEBUG).Infof("[socks4-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) |
|
||||
|
|
||||
glog.V(LINFO).Infof("[socks4-connect] %s <-> %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
s.Base.transport(s.conn, cc) |
|
||||
glog.V(LINFO).Infof("[socks4-connect] %s >-< %s", s.conn.RemoteAddr(), req.Addr) |
|
||||
} |
|
||||
|
|
||||
func (s *Socks4Server) handleBind(req *gosocks4.Request) { |
|
||||
cc, err := s.Base.Chain.GetConn() |
|
||||
|
|
||||
// connection error
|
|
||||
if err != nil && err != ErrEmptyChain { |
|
||||
glog.V(LWARNING).Infof("[socks4-bind] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) |
|
||||
reply := gosocks4.NewReply(gosocks4.Failed, nil) |
|
||||
reply.Write(s.conn) |
|
||||
glog.V(LDEBUG).Infof("[socks4-bind] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) |
|
||||
return |
|
||||
} |
|
||||
// TODO: serve socks4 bind
|
|
||||
if err == ErrEmptyChain { |
|
||||
//s.bindOn(req.Addr.String())
|
|
||||
return |
|
||||
} |
|
||||
|
|
||||
defer cc.Close() |
|
||||
// forward request
|
|
||||
req.Write(cc) |
|
||||
|
|
||||
glog.V(LINFO).Infof("[socks4-bind] %s <-> %s", s.conn.RemoteAddr(), cc.RemoteAddr()) |
|
||||
s.Base.transport(s.conn, cc) |
|
||||
glog.V(LINFO).Infof("[socks4-bind] %s >-< %s", s.conn.RemoteAddr(), cc.RemoteAddr()) |
|
||||
} |
|
||||
@ -1,353 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"bytes" |
|
||||
"encoding/binary" |
|
||||
"errors" |
|
||||
"fmt" |
|
||||
"github.com/ginuerzh/gosocks5" |
|
||||
"github.com/golang/glog" |
|
||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" |
|
||||
"io" |
|
||||
"net" |
|
||||
"strconv" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
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 |
|
||||
) |
|
||||
|
|
||||
type ShadowServer struct { |
|
||||
conn *ss.Conn |
|
||||
Base *ProxyServer |
|
||||
OTA bool // one time auth
|
|
||||
} |
|
||||
|
|
||||
func NewShadowServer(conn *ss.Conn, base *ProxyServer) *ShadowServer { |
|
||||
return &ShadowServer{conn: conn, Base: base} |
|
||||
} |
|
||||
|
|
||||
func (s *ShadowServer) Serve() { |
|
||||
glog.V(LINFO).Infof("[ss] %s - %s", s.conn.RemoteAddr(), s.conn.LocalAddr()) |
|
||||
|
|
||||
addr, ota, err := s.getRequest() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[ss] %s - %s : %s", s.conn.RemoteAddr(), s.conn.LocalAddr(), err) |
|
||||
return |
|
||||
} |
|
||||
glog.V(LINFO).Infof("[ss] %s -> %s, ota: %v", s.conn.RemoteAddr(), addr, ota) |
|
||||
|
|
||||
cc, err := s.Base.Chain.Dial(addr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[ss] %s -> %s : %s", s.conn.RemoteAddr(), addr, err) |
|
||||
return |
|
||||
} |
|
||||
defer cc.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[ss] %s <-> %s", s.conn.RemoteAddr(), addr) |
|
||||
if ota { |
|
||||
s.transportOTA(s.conn, cc) |
|
||||
} else { |
|
||||
s.Base.transport(&shadowConn{conn: s.conn}, cc) |
|
||||
} |
|
||||
glog.V(LINFO).Infof("[ss] %s >-< %s", s.conn.RemoteAddr(), addr) |
|
||||
} |
|
||||
|
|
||||
// This function is copied from shadowsocks library with some modification.
|
|
||||
func (s *ShadowServer) getRequest() (host string, ota bool, 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
|
|
||||
s.conn.SetReadDeadline(time.Now().Add(ReadTimeout)) |
|
||||
if _, err = io.ReadFull(s.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(s.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(s.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))) |
|
||||
// if specified one time auth enabled, we should verify this
|
|
||||
if s.OTA || addrType&ss.OneTimeAuthMask > 0 { |
|
||||
ota = true |
|
||||
if _, err = io.ReadFull(s.conn, buf[reqEnd:reqEnd+lenHmacSha1]); err != nil { |
|
||||
return |
|
||||
} |
|
||||
iv := s.conn.GetIv() |
|
||||
key := s.conn.GetKey() |
|
||||
actualHmacSha1Buf := ss.HmacSha1(append(iv, key...), buf[:reqEnd]) |
|
||||
if !bytes.Equal(buf[reqEnd:reqEnd+lenHmacSha1], actualHmacSha1Buf) { |
|
||||
err = fmt.Errorf("verify one time auth failed, iv=%v key=%v data=%v", iv, key, buf[:reqEnd]) |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
const ( |
|
||||
dataLenLen = 2 |
|
||||
hmacSha1Len = 10 |
|
||||
idxData0 = dataLenLen + hmacSha1Len |
|
||||
) |
|
||||
|
|
||||
// copyOta copies data from src to dst with ota verification.
|
|
||||
//
|
|
||||
// This function is copied from shadowsocks library with some modification.
|
|
||||
func (s *ShadowServer) copyOta(dst net.Conn, src *ss.Conn) (int64, error) { |
|
||||
// sometimes it have to fill large block
|
|
||||
buf := make([]byte, LargeBufferSize) |
|
||||
for { |
|
||||
src.SetReadDeadline(time.Now().Add(ReadTimeout)) |
|
||||
if n, err := io.ReadFull(src, buf[:dataLenLen+hmacSha1Len]); err != nil { |
|
||||
return int64(n), err |
|
||||
} |
|
||||
src.SetReadDeadline(time.Time{}) |
|
||||
|
|
||||
dataLen := binary.BigEndian.Uint16(buf[:dataLenLen]) |
|
||||
expectedHmacSha1 := buf[dataLenLen:idxData0] |
|
||||
|
|
||||
var dataBuf []byte |
|
||||
if len(buf) < int(idxData0+dataLen) { |
|
||||
dataBuf = make([]byte, dataLen) |
|
||||
} else { |
|
||||
dataBuf = buf[idxData0 : idxData0+dataLen] |
|
||||
} |
|
||||
if n, err := io.ReadFull(src, dataBuf); err != nil { |
|
||||
return int64(n), err |
|
||||
} |
|
||||
chunkIdBytes := make([]byte, 4) |
|
||||
chunkId := src.GetAndIncrChunkId() |
|
||||
binary.BigEndian.PutUint32(chunkIdBytes, chunkId) |
|
||||
actualHmacSha1 := ss.HmacSha1(append(src.GetIv(), chunkIdBytes...), dataBuf) |
|
||||
if !bytes.Equal(expectedHmacSha1, actualHmacSha1) { |
|
||||
return 0, errors.New("ota error: mismatch") |
|
||||
} |
|
||||
|
|
||||
if n, err := dst.Write(dataBuf); err != nil { |
|
||||
return int64(n), err |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *ShadowServer) transportOTA(sc *ss.Conn, cc net.Conn) (err error) { |
|
||||
errc := make(chan error, 2) |
|
||||
|
|
||||
go func() { |
|
||||
_, err := io.Copy(&shadowConn{conn: sc}, cc) |
|
||||
errc <- err |
|
||||
}() |
|
||||
|
|
||||
go func() { |
|
||||
_, err := s.copyOta(cc, sc) |
|
||||
errc <- err |
|
||||
}() |
|
||||
|
|
||||
select { |
|
||||
case err = <-errc: |
|
||||
//glog.V(LWARNING).Infoln("transport exit", err)
|
|
||||
} |
|
||||
|
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// 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 *ss.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 ShadowUdpServer struct { |
|
||||
Base *ProxyServer |
|
||||
TTL int |
|
||||
} |
|
||||
|
|
||||
func NewShadowUdpServer(base *ProxyServer, ttl int) *ShadowUdpServer { |
|
||||
return &ShadowUdpServer{Base: base, TTL: ttl} |
|
||||
} |
|
||||
|
|
||||
func (s *ShadowUdpServer) ListenAndServe() error { |
|
||||
laddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Addr) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
lconn, err := net.ListenUDP("udp", laddr) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
defer lconn.Close() |
|
||||
|
|
||||
conn := ss.NewSecurePacketConn(lconn, s.Base.cipher.Copy(), true) // force OTA on
|
|
||||
|
|
||||
rChan, wChan := make(chan *packet, 128), make(chan *packet, 128) |
|
||||
// start send queue
|
|
||||
go func(ch chan<- *packet) { |
|
||||
for { |
|
||||
b := make([]byte, MediumBufferSize) |
|
||||
n, addr, err := conn.ReadFrom(b[3:]) // add rsv and frag fields to make it the standard SOCKS5 UDP datagram
|
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, laddr, err) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
b[3] &= ss.AddrMask // remove OTA flag
|
|
||||
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n+3])) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, laddr, err) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
select { |
|
||||
case ch <- &packet{srcAddr: addr.String(), dstAddr: dgram.Header.Addr.String(), data: dgram.Data}: |
|
||||
case <-time.After(time.Second * 3): |
|
||||
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, dgram.Header.Addr.String(), "send queue is full, discard") |
|
||||
} |
|
||||
} |
|
||||
}(wChan) |
|
||||
// start recv queue
|
|
||||
go func(ch <-chan *packet) { |
|
||||
for pkt := range ch { |
|
||||
srcAddr, err := net.ResolveUDPAddr("udp", pkt.srcAddr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err) |
|
||||
continue |
|
||||
} |
|
||||
dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, ToSocksAddr(srcAddr)), pkt.data) |
|
||||
b := bytes.Buffer{} |
|
||||
dgram.Write(&b) |
|
||||
if b.Len() < 10 { |
|
||||
glog.V(LWARNING).Infof("[ssu] %s <- %s : invalid udp datagram", pkt.dstAddr, pkt.srcAddr) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
if _, err := conn.WriteTo(b.Bytes()[3:], dstAddr); err != nil { // remove rsv and frag fields to make it standard shadowsocks UDP datagram
|
|
||||
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err) |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
}(rChan) |
|
||||
|
|
||||
// mapping client to node
|
|
||||
m := make(map[string]*cnode) |
|
||||
|
|
||||
// start dispatcher
|
|
||||
for pkt := range wChan { |
|
||||
// clear obsolete nodes
|
|
||||
for k, node := range m { |
|
||||
if node != nil && node.err != nil { |
|
||||
close(node.wChan) |
|
||||
delete(m, k) |
|
||||
glog.V(LINFO).Infof("[ssu] clear node %s", k) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
node, ok := m[pkt.srcAddr] |
|
||||
if !ok { |
|
||||
node = &cnode{ |
|
||||
chain: s.Base.Chain, |
|
||||
srcAddr: pkt.srcAddr, |
|
||||
dstAddr: pkt.dstAddr, |
|
||||
rChan: rChan, |
|
||||
wChan: make(chan *packet, 32), |
|
||||
ttl: time.Duration(s.TTL) * time.Second, |
|
||||
} |
|
||||
m[pkt.srcAddr] = node |
|
||||
go node.run() |
|
||||
glog.V(LINFO).Infof("[ssu] %s -> %s : new client (%d)", pkt.srcAddr, pkt.dstAddr, len(m)) |
|
||||
} |
|
||||
|
|
||||
select { |
|
||||
case node.wChan <- pkt: |
|
||||
case <-time.After(time.Second * 3): |
|
||||
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, "node send queue is full, discard") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
@ -1,236 +0,0 @@ |
|||||
// The ssh tunnel is inspired by easyssh(https://dev.justinjudd.org/justin/easyssh)
|
|
||||
|
|
||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"encoding/binary" |
|
||||
"fmt" |
|
||||
"github.com/golang/glog" |
|
||||
"golang.org/x/crypto/ssh" |
|
||||
"net" |
|
||||
"net/url" |
|
||||
"strconv" |
|
||||
) |
|
||||
|
|
||||
// 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
|
|
||||
) |
|
||||
|
|
||||
type SSHServer struct { |
|
||||
Addr string |
|
||||
Base *ProxyServer |
|
||||
Config *ssh.ServerConfig |
|
||||
Handler func(ssh.Conn, <-chan ssh.NewChannel, <-chan *ssh.Request) |
|
||||
} |
|
||||
|
|
||||
func (s *SSHServer) ListenAndServe() error { |
|
||||
ln, err := net.Listen("tcp", s.Addr) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[ssh] Listen:", err) |
|
||||
return err |
|
||||
} |
|
||||
defer ln.Close() |
|
||||
|
|
||||
for { |
|
||||
conn, err := ln.Accept() |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[ssh] Accept:", err) |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
go func(conn net.Conn) { |
|
||||
sshConn, chans, reqs, err := ssh.NewServerConn(conn, s.Config) |
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infof("[ssh] %s -> %s : %s", conn.RemoteAddr(), s.Addr, err) |
|
||||
return |
|
||||
} |
|
||||
defer sshConn.Close() |
|
||||
|
|
||||
if s.Handler == nil { |
|
||||
s.Handler = s.handleSSHConn |
|
||||
} |
|
||||
|
|
||||
glog.V(LINFO).Infof("[ssh] %s <-> %s", conn.RemoteAddr(), s.Addr) |
|
||||
s.Handler(sshConn, chans, reqs) |
|
||||
glog.V(LINFO).Infof("[ssh] %s >-< %s", conn.RemoteAddr(), s.Addr) |
|
||||
}(conn) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (s *SSHServer) handleSSHConn(conn ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) { |
|
||||
quit := make(chan interface{}) |
|
||||
go func() { |
|
||||
for req := range reqs { |
|
||||
switch req.Type { |
|
||||
case RemoteForwardRequest: |
|
||||
go s.tcpipForwardRequest(conn, req, quit) |
|
||||
default: |
|
||||
// glog.V(LWARNING).Infoln("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 { |
|
||||
glog.V(LINFO).Infoln("[ssh] Could not accept channel:", err) |
|
||||
continue |
|
||||
} |
|
||||
p := directForward{} |
|
||||
ssh.Unmarshal(newChannel.ExtraData(), &p) |
|
||||
|
|
||||
go ssh.DiscardRequests(requests) |
|
||||
go s.directPortForwardChannel(channel, fmt.Sprintf("%s:%d", p.Host1, p.Port1)) |
|
||||
default: |
|
||||
glog.V(LWARNING).Infoln("[ssh] Unknown channel type:", t) |
|
||||
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t)) |
|
||||
} |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
conn.Wait() |
|
||||
close(quit) |
|
||||
} |
|
||||
|
|
||||
// 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 (s *SSHServer) directPortForwardChannel(channel ssh.Channel, raddr string) { |
|
||||
defer channel.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[ssh-tcp] %s - %s", s.Addr, raddr) |
|
||||
|
|
||||
conn, err := s.Base.Chain.Dial(raddr) |
|
||||
if err != nil { |
|
||||
glog.V(LINFO).Infof("[ssh-tcp] %s - %s : %s", s.Addr, raddr, err) |
|
||||
return |
|
||||
} |
|
||||
defer conn.Close() |
|
||||
|
|
||||
glog.V(LINFO).Infof("[ssh-tcp] %s <-> %s", s.Addr, raddr) |
|
||||
Transport(conn, channel) |
|
||||
glog.V(LINFO).Infof("[ssh-tcp] %s >-< %s", s.Addr, raddr) |
|
||||
} |
|
||||
|
|
||||
// tcpipForward is structure for RFC 4254 7.1 "tcpip-forward" request
|
|
||||
type tcpipForward struct { |
|
||||
Host string |
|
||||
Port uint32 |
|
||||
} |
|
||||
|
|
||||
func (s *SSHServer) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit <-chan interface{}) { |
|
||||
t := tcpipForward{} |
|
||||
ssh.Unmarshal(req.Payload, &t) |
|
||||
addr := fmt.Sprintf("%s:%d", t.Host, t.Port) |
|
||||
glog.V(LINFO).Infoln("[ssh-rtcp] listening tcp", addr) |
|
||||
ln, err := net.Listen("tcp", addr) //tie to the client connection
|
|
||||
if err != nil { |
|
||||
glog.V(LWARNING).Infoln("[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 { |
|
||||
glog.V(LWARNING).Infoln("[ssh-rtcp]", err) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
go func() { |
|
||||
for { |
|
||||
conn, err := ln.Accept() |
|
||||
if err != nil { // Unable to accept new connection - listener 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 { |
|
||||
glog.V(1).Infoln("[ssh-rtcp] open forwarded channel:", err) |
|
||||
return |
|
||||
} |
|
||||
defer ch.Close() |
|
||||
go ssh.DiscardRequests(reqs) |
|
||||
|
|
||||
glog.V(LINFO).Infof("[ssh-rtcp] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr()) |
|
||||
Transport(ch, conn) |
|
||||
glog.V(LINFO).Infof("[ssh-rtcp] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr()) |
|
||||
}(conn) |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
<-quit |
|
||||
} |
|
||||
|
|
||||
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 DefaultPasswordCallback(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 |
|
||||
} |
|
||||
} |
|
||||
glog.V(LINFO).Infof("[ssh] %s -> %s : password rejected for %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User()) |
|
||||
return nil, fmt.Errorf("password rejected for %s", conn.User()) |
|
||||
} |
|
||||
} |
|
||||
@ -1,142 +0,0 @@ |
|||||
package gost |
|
||||
|
|
||||
import ( |
|
||||
"crypto/tls" |
|
||||
"github.com/golang/glog" |
|
||||
"gopkg.in/gorilla/websocket.v1" |
|
||||
"net" |
|
||||
"net/http" |
|
||||
"net/http/httputil" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
type WebsocketServer struct { |
|
||||
Addr string |
|
||||
Base *ProxyServer |
|
||||
Handler http.Handler |
|
||||
upgrader websocket.Upgrader |
|
||||
} |
|
||||
|
|
||||
func NewWebsocketServer(base *ProxyServer) *WebsocketServer { |
|
||||
return &WebsocketServer{ |
|
||||
Addr: base.Node.Addr, |
|
||||
Base: base, |
|
||||
upgrader: websocket.Upgrader{ |
|
||||
ReadBufferSize: 1024, |
|
||||
WriteBufferSize: 1024, |
|
||||
CheckOrigin: func(r *http.Request) bool { return true }, |
|
||||
EnableCompression: true, |
|
||||
}, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Default websocket server handler
|
|
||||
func (s *WebsocketServer) HandleRequest(w http.ResponseWriter, r *http.Request) { |
|
||||
glog.V(LINFO).Infof("[ws] %s - %s", r.RemoteAddr, s.Addr) |
|
||||
if glog.V(LDEBUG) { |
|
||||
dump, _ := httputil.DumpRequest(r, false) |
|
||||
glog.V(LDEBUG).Infof("[ws] %s - %s\n%s", r.RemoteAddr, s.Addr, string(dump)) |
|
||||
} |
|
||||
conn, err := s.upgrader.Upgrade(w, r, nil) |
|
||||
if err != nil { |
|
||||
glog.V(LERROR).Infof("[ws] %s - %s : %s", r.RemoteAddr, s.Addr, err) |
|
||||
return |
|
||||
} |
|
||||
s.Base.handleConn(WebsocketServerConn(conn)) |
|
||||
} |
|
||||
|
|
||||
func (s *WebsocketServer) ListenAndServe() error { |
|
||||
mux := http.NewServeMux() |
|
||||
if s.Handler == nil { |
|
||||
s.Handler = http.HandlerFunc(s.HandleRequest) |
|
||||
} |
|
||||
mux.Handle("/ws", s.Handler) |
|
||||
return http.ListenAndServe(s.Addr, mux) |
|
||||
} |
|
||||
|
|
||||
func (s *WebsocketServer) ListenAndServeTLS(config *tls.Config) error { |
|
||||
mux := http.NewServeMux() |
|
||||
if s.Handler == nil { |
|
||||
s.Handler = http.HandlerFunc(s.HandleRequest) |
|
||||
} |
|
||||
mux.Handle("/ws", s.Handler) |
|
||||
server := &http.Server{ |
|
||||
Addr: s.Addr, |
|
||||
Handler: mux, |
|
||||
TLSConfig: config, |
|
||||
} |
|
||||
return server.ListenAndServeTLS("", "") |
|
||||
} |
|
||||
|
|
||||
type WebsocketConn struct { |
|
||||
conn *websocket.Conn |
|
||||
rb []byte |
|
||||
} |
|
||||
|
|
||||
func WebsocketClientConn(url string, conn net.Conn, config *tls.Config) (*WebsocketConn, error) { |
|
||||
dialer := websocket.Dialer{ |
|
||||
ReadBufferSize: 1024, |
|
||||
WriteBufferSize: 1024, |
|
||||
TLSClientConfig: config, |
|
||||
HandshakeTimeout: DialTimeout, |
|
||||
EnableCompression: true, |
|
||||
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) *WebsocketConn { |
|
||||
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 (conn *WebsocketConn) SetDeadline(t time.Time) error { |
|
||||
if err := conn.SetReadDeadline(t); err != nil { |
|
||||
return err |
|
||||
} |
|
||||
return conn.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) |
|
||||
} |
|
||||
Loading…
Reference in new issue