update file structure
This commit is contained in:
parent
6613df9960
commit
74eb52dc2a
2
.gitignore
vendored
2
.gitignore
vendored
@ -25,3 +25,5 @@ _testmain.go
|
||||
*.test
|
||||
|
||||
*.bak
|
||||
|
||||
cmd/gost
|
@ -1,5 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
1.6
|
||||
1.7
|
21
LICENSE
21
LICENSE
@ -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.
|
358
README.md
358
README.md
@ -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:123456@192.168.1.1: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:123456@192.168.1.3: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类型代理。
|
||||
|
||||
|
||||
|
362
README_en.md
362
README_en.md
@ -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:123456@192.168.1.1: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:123456@192.168.1.3: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.
|
||||
|
||||
|
||||
|
580
chain.go
580
chain.go
@ -1,558 +1,110 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/pht"
|
||||
"github.com/golang/glog"
|
||||
"github.com/lucas-clemente/quic-go/h2quic"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// Proxy chain holds a list of proxy nodes
|
||||
type ProxyChain struct {
|
||||
nodes []ProxyNode
|
||||
lastNode *ProxyNode
|
||||
http2NodeIndex int
|
||||
http2Enabled bool
|
||||
http2Client *http.Client
|
||||
kcpEnabled bool
|
||||
kcpConfig *KCPConfig
|
||||
kcpSession *KCPSession
|
||||
kcpMutex sync.Mutex
|
||||
phtClient *pht.Client
|
||||
quicClient *http.Client
|
||||
var (
|
||||
// ErrEmptyChain is an error that implies the chain is empty.
|
||||
ErrEmptyChain = errors.New("empty chain")
|
||||
)
|
||||
|
||||
// Chain is a proxy chain that holds a list of proxy nodes.
|
||||
type Chain struct {
|
||||
nodes []Node
|
||||
}
|
||||
|
||||
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)
|
||||
// NewChain creates a proxy chain with proxy nodes nodes.
|
||||
func NewChain(nodes ...Node) *Chain {
|
||||
return &Chain{
|
||||
nodes: nodes,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ProxyChain) Nodes() []ProxyNode {
|
||||
// Nodes returns the proxy nodes that the chain holds.
|
||||
func (c *Chain) Nodes() []Node {
|
||||
return c.nodes
|
||||
}
|
||||
|
||||
func (c *ProxyChain) GetNode(index int) *ProxyNode {
|
||||
if index < len(c.nodes) {
|
||||
return &c.nodes[index]
|
||||
// LastNode returns the last node of the node list.
|
||||
// If the chain is empty, an empty node is returns.
|
||||
func (c *Chain) LastNode() Node {
|
||||
if c.IsEmpty() {
|
||||
return Node{}
|
||||
}
|
||||
return nil
|
||||
return c.nodes[len(c.nodes)-1]
|
||||
}
|
||||
|
||||
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 {
|
||||
// AddNode appends the node(s) to the chain.
|
||||
func (c *Chain) AddNode(nodes ...Node) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.lastNode = &c.nodes[length-1]
|
||||
|
||||
// HTTP2 restrict: HTTP2 will be enabled when at least one HTTP2 proxy node is present.
|
||||
for i, node := range c.nodes {
|
||||
if node.Transport == "http2" {
|
||||
glog.V(LINFO).Infoln("HTTP2 is enabled")
|
||||
cfg := &tls.Config{
|
||||
InsecureSkipVerify: node.insecureSkipVerify(),
|
||||
ServerName: node.serverName,
|
||||
}
|
||||
|
||||
caFile := node.caFile()
|
||||
|
||||
if caFile != "" {
|
||||
cfg.RootCAs = x509.NewCertPool()
|
||||
|
||||
data, err := ioutil.ReadFile(caFile)
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
|
||||
if !cfg.RootCAs.AppendCertsFromPEM(data) {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
c.http2NodeIndex = i
|
||||
c.initHttp2Client(cfg, c.nodes[:i]...)
|
||||
break // shortest chain for HTTP2
|
||||
}
|
||||
}
|
||||
|
||||
for i, node := range c.nodes {
|
||||
if (node.Transport == "kcp" || node.Transport == "pht" || node.Transport == "quic") && i > 0 {
|
||||
glog.Fatal("KCP/PHT/QUIC must be the first node in the proxy chain")
|
||||
}
|
||||
}
|
||||
|
||||
if c.nodes[0].Transport == "kcp" {
|
||||
glog.V(LINFO).Infoln("KCP is enabled")
|
||||
c.kcpEnabled = true
|
||||
config, err := ParseKCPConfig(c.nodes[0].Get("c"))
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||
}
|
||||
if config == nil {
|
||||
config = DefaultKCPConfig
|
||||
}
|
||||
if c.nodes[0].Users != nil {
|
||||
config.Crypt = c.nodes[0].Users[0].Username()
|
||||
config.Key, _ = c.nodes[0].Users[0].Password()
|
||||
}
|
||||
c.kcpConfig = config
|
||||
go snmpLogger(config.SnmpLog, config.SnmpPeriod)
|
||||
go kcpSigHandler()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if c.nodes[0].Transport == "quic" {
|
||||
glog.V(LINFO).Infoln("QUIC is enabled")
|
||||
c.quicClient = &http.Client{
|
||||
Transport: &h2quic.QuicRoundTripper{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: c.nodes[0].insecureSkipVerify(),
|
||||
ServerName: c.nodes[0].serverName,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if c.nodes[0].Transport == "pht" {
|
||||
glog.V(LINFO).Infoln("Pure HTTP mode is enabled")
|
||||
c.phtClient = pht.NewClient(c.nodes[0].Addr, c.nodes[0].Get("key"))
|
||||
}
|
||||
c.nodes = append(c.nodes, nodes...)
|
||||
}
|
||||
|
||||
func (c *ProxyChain) KCPEnabled() bool {
|
||||
return c.kcpEnabled
|
||||
// IsEmpty checks if the chain is empty.
|
||||
// An empty chain means that there is no proxy node in the chain.
|
||||
func (c *Chain) IsEmpty() bool {
|
||||
return c == nil || len(c.nodes) == 0
|
||||
}
|
||||
|
||||
func (c *ProxyChain) Http2Enabled() bool {
|
||||
return c.http2Enabled
|
||||
}
|
||||
|
||||
// Wrap a net.Conn into a client tls connection, performing any
|
||||
// additional verification as needed.
|
||||
//
|
||||
// As of go 1.3, crypto/tls only supports either doing no certificate
|
||||
// verification, or doing full verification including of the peer's
|
||||
// DNS name. For consul, we want to validate that the certificate is
|
||||
// signed by a known CA, but because consul doesn't use DNS names for
|
||||
// node names, we don't verify the certificate DNS names. Since go 1.3
|
||||
// no longer supports this mode of operation, we have to do it
|
||||
// manually.
|
||||
//
|
||||
// This code is taken from consul:
|
||||
// https://github.com/hashicorp/consul/blob/master/tlsutil/config.go
|
||||
func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) {
|
||||
var err error
|
||||
var tlsConn *tls.Conn
|
||||
|
||||
tlsConn = tls.Client(conn, tlsConfig)
|
||||
|
||||
// If crypto/tls is doing verification, there's no need to do our own.
|
||||
if tlsConfig.InsecureSkipVerify == false {
|
||||
return tlsConn, nil
|
||||
// Dial connects to the target address addr through the chain.
|
||||
// If the chain is empty, it will use the net.Dial directly.
|
||||
func (c *Chain) Dial(addr string) (net.Conn, error) {
|
||||
if c.IsEmpty() {
|
||||
return net.Dial("tcp", addr)
|
||||
}
|
||||
|
||||
// Similarly if we use host's CA, we can do full handshake
|
||||
if tlsConfig.RootCAs == nil {
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
// Otherwise perform handshake, but don't verify the domain
|
||||
//
|
||||
// The following is lightly-modified from the doFullHandshake
|
||||
// method in https://golang.org/src/crypto/tls/handshake_client.go
|
||||
if err = tlsConn.Handshake(); err != nil {
|
||||
tlsConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: tlsConfig.RootCAs,
|
||||
CurrentTime: time.Now(),
|
||||
DNSName: "",
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
|
||||
certs := tlsConn.ConnectionState().PeerCertificates
|
||||
for i, cert := range certs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
|
||||
_, err = certs[0].Verify(opts)
|
||||
conn, err := c.Conn()
|
||||
if err != nil {
|
||||
tlsConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tlsConn, err
|
||||
cc, err := c.LastNode().Client.Connect(conn, addr)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (c *ProxyChain) initHttp2Client(config *tls.Config, nodes ...ProxyNode) {
|
||||
if c.http2NodeIndex < 0 || c.http2NodeIndex >= len(c.nodes) {
|
||||
return
|
||||
}
|
||||
http2Node := c.nodes[c.http2NodeIndex]
|
||||
|
||||
tr := http2.Transport{
|
||||
TLSClientConfig: config,
|
||||
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||
// replace the default dialer with our proxy chain.
|
||||
conn, err := c.dialWithNodes(false, http2Node.Addr, nodes...)
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
|
||||
conn, err = wrapTLSClient(conn, cfg)
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
|
||||
// enable HTTP2 ping-pong
|
||||
pingIntvl, _ := strconv.Atoi(http2Node.Get("ping"))
|
||||
if pingIntvl > 0 {
|
||||
enablePing(conn, time.Duration(pingIntvl)*time.Second)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
c.http2Client = &http.Client{Transport: &tr}
|
||||
c.http2Enabled = true
|
||||
|
||||
}
|
||||
|
||||
func enablePing(conn net.Conn, interval time.Duration) {
|
||||
if conn == nil || interval == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(LINFO).Infoln("[http2] ping enabled, interval:", interval)
|
||||
go func() {
|
||||
t := time.NewTicker(interval)
|
||||
var framer *http2.Framer
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
if framer == nil {
|
||||
framer = http2.NewFramer(conn, conn)
|
||||
}
|
||||
|
||||
var p [8]byte
|
||||
rand.Read(p[:])
|
||||
err := framer.WritePing(false, p)
|
||||
if err != nil {
|
||||
t.Stop()
|
||||
framer = nil
|
||||
glog.V(LWARNING).Infoln("[http2] ping:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Connect to addr through proxy chain
|
||||
func (c *ProxyChain) Dial(addr string) (net.Conn, error) {
|
||||
if !strings.Contains(addr, ":") {
|
||||
addr += ":80"
|
||||
}
|
||||
return c.dialWithNodes(true, addr, c.nodes...)
|
||||
}
|
||||
|
||||
// GetConn initializes a proxy chain connection,
|
||||
// if no proxy nodes on this chain, it will return error
|
||||
func (c *ProxyChain) GetConn() (net.Conn, error) {
|
||||
nodes := c.nodes
|
||||
if len(nodes) == 0 {
|
||||
// Conn obtains a handshaked connection to the last node of the chain.
|
||||
// If the chain is empty, it returns an ErrEmptyChain error.
|
||||
func (c *Chain) Conn() (net.Conn, error) {
|
||||
if c.IsEmpty() {
|
||||
return nil, ErrEmptyChain
|
||||
}
|
||||
|
||||
if c.Http2Enabled() {
|
||||
nodes = nodes[c.http2NodeIndex+1:]
|
||||
if len(nodes) == 0 {
|
||||
header := make(http.Header)
|
||||
header.Set("Proxy-Switch", "gost") // Flag header to indicate server to switch to HTTP2 transport mode
|
||||
conn, err := c.getHttp2Conn(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
http2Node := c.nodes[c.http2NodeIndex]
|
||||
if http2Node.Transport == "http2" {
|
||||
http2Node.Transport = "h2"
|
||||
}
|
||||
if http2Node.Protocol == "http2" {
|
||||
http2Node.Protocol = "socks5" // assume it as socks5 protocol, so we can do much more things.
|
||||
}
|
||||
pc := NewProxyConn(conn, http2Node)
|
||||
if err := pc.Handshake(); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return pc, nil
|
||||
}
|
||||
}
|
||||
return c.travelNodes(true, nodes...)
|
||||
}
|
||||
|
||||
func (c *ProxyChain) dialWithNodes(withHttp2 bool, addr string, nodes ...ProxyNode) (conn net.Conn, err error) {
|
||||
if len(nodes) == 0 {
|
||||
return net.DialTimeout("tcp", addr, DialTimeout)
|
||||
}
|
||||
|
||||
if withHttp2 && c.Http2Enabled() {
|
||||
nodes = nodes[c.http2NodeIndex+1:]
|
||||
if len(nodes) == 0 {
|
||||
return c.http2Connect(addr)
|
||||
}
|
||||
}
|
||||
|
||||
if nodes[0].Transport == "quic" {
|
||||
glog.V(LINFO).Infoln("Dial with QUIC")
|
||||
return c.quicConnect(addr)
|
||||
}
|
||||
|
||||
pc, err := c.travelNodes(withHttp2, nodes...)
|
||||
nodes := c.nodes
|
||||
conn, err := nodes[0].Client.Dial(nodes[0].Addr, nodes[0].DialOptions...)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
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, err = nodes[0].Client.Handshake(conn, nodes[0].HandshakeOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, node := range nodes {
|
||||
if i == len(nodes)-1 {
|
||||
break
|
||||
}
|
||||
|
||||
next := nodes[i+1]
|
||||
cc, err := node.Client.Connect(conn, next.Addr)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
conn = nil
|
||||
return nil, err
|
||||
}
|
||||
}()
|
||||
|
||||
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
|
||||
cc, err = next.Client.Handshake(cc, next.HandshakeOptions...)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
conn = cc
|
||||
}
|
||||
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
cmd/gost/.gitignore
vendored
1
cmd/gost/.gitignore
vendored
@ -1 +0,0 @@
|
||||
gost
|
413
cmd/gost/main.go
413
cmd/gost/main.go
@ -1,22 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gost"
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/net/http2"
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
var (
|
||||
options struct {
|
||||
ChainNodes, ServeNodes flagStringList
|
||||
chainNodes, serveNodes stringList
|
||||
debugMode bool
|
||||
}
|
||||
)
|
||||
|
||||
@ -26,55 +33,340 @@ func init() {
|
||||
printVersion bool
|
||||
)
|
||||
|
||||
flag.Var(&options.chainNodes, "F", "forward address, can make a forward chain")
|
||||
flag.Var(&options.serveNodes, "L", "listen address, can listen on multiple ports")
|
||||
flag.StringVar(&configureFile, "C", "", "configure file")
|
||||
flag.Var(&options.ChainNodes, "F", "forward address, can make a forward chain")
|
||||
flag.Var(&options.ServeNodes, "L", "listen address, can listen on multiple ports")
|
||||
flag.BoolVar(&options.debugMode, "D", false, "enable debug log")
|
||||
flag.BoolVar(&printVersion, "V", false, "print version")
|
||||
flag.Parse()
|
||||
|
||||
if err := loadConfigureFile(configureFile); err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
|
||||
if glog.V(5) {
|
||||
http2.VerboseLogs = true
|
||||
log.Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if flag.NFlag() == 0 {
|
||||
flag.PrintDefaults()
|
||||
return
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if printVersion {
|
||||
fmt.Fprintf(os.Stderr, "gost %s (%s)\n", gost.Version, runtime.Version())
|
||||
return
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
gost.Debug = options.debugMode
|
||||
}
|
||||
|
||||
func main() {
|
||||
chain := gost.NewProxyChain()
|
||||
if err := chain.AddProxyNodeString(options.ChainNodes...); err != nil {
|
||||
glog.Fatal(err)
|
||||
chain, err := initChain()
|
||||
if err != nil {
|
||||
log.Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
chain.Init()
|
||||
if err := serve(chain); err != nil {
|
||||
log.Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
select {}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, ns := range options.ServeNodes {
|
||||
serverNode, err := gost.ParseProxyNode(ns)
|
||||
func initChain() (*gost.Chain, error) {
|
||||
chain := gost.NewChain()
|
||||
for _, ns := range options.chainNodes {
|
||||
node, err := gost.ParseNode(ns)
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.Info(serverNode)
|
||||
serverName, _, _ := net.SplitHostPort(node.Addr)
|
||||
if serverName == "" {
|
||||
serverName = "localhost" // default server name
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(node gost.ProxyNode) {
|
||||
defer wg.Done()
|
||||
server := gost.NewProxyServer(node, chain)
|
||||
glog.Fatal(server.Serve())
|
||||
}(serverNode)
|
||||
tlsCfg := &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: !toBool(node.Values.Get("scure")),
|
||||
}
|
||||
var tr gost.Transporter
|
||||
switch node.Transport {
|
||||
case "tls":
|
||||
tr = gost.TLSTransporter()
|
||||
case "ws":
|
||||
wsOpts := &gost.WSOptions{}
|
||||
wsOpts.EnableCompression = toBool(node.Values.Get("compression"))
|
||||
wsOpts.ReadBufferSize, _ = strconv.Atoi(node.Values.Get("rbuf"))
|
||||
wsOpts.WriteBufferSize, _ = strconv.Atoi(node.Values.Get("wbuf"))
|
||||
node.HandshakeOptions = append(node.HandshakeOptions,
|
||||
gost.WSOptionsHandshakeOption(wsOpts),
|
||||
)
|
||||
tr = gost.WSTransporter(nil)
|
||||
case "wss":
|
||||
tr = gost.WSSTransporter(nil)
|
||||
case "kcp":
|
||||
if !chain.IsEmpty() {
|
||||
return nil, errors.New("KCP must be the first node in the proxy chain")
|
||||
}
|
||||
config, err := parseKCPConfig(node.Values.Get("c"))
|
||||
if err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
node.HandshakeOptions = append(node.HandshakeOptions,
|
||||
gost.KCPConfigHandshakeOption(config),
|
||||
)
|
||||
tr = gost.KCPTransporter(nil)
|
||||
case "ssh":
|
||||
if node.Protocol == "direct" || node.Protocol == "remote" || node.Protocol == "forward" {
|
||||
tr = gost.SSHForwardTransporter()
|
||||
} else {
|
||||
tr = gost.SSHTunnelTransporter()
|
||||
}
|
||||
|
||||
node.DialOptions = append(node.DialOptions,
|
||||
gost.ChainDialOption(chain),
|
||||
)
|
||||
chain = gost.NewChain() // cutoff the chain for multiplex
|
||||
case "quic":
|
||||
if !chain.IsEmpty() {
|
||||
return nil, errors.New("QUIC must be the first node in the proxy chain")
|
||||
}
|
||||
config := &gost.QUICConfig{
|
||||
TLSConfig: tlsCfg,
|
||||
KeepAlive: toBool(node.Values.Get("keepalive")),
|
||||
}
|
||||
node.HandshakeOptions = append(node.HandshakeOptions,
|
||||
gost.QUICConfigHandshakeOption(config),
|
||||
)
|
||||
tr = gost.QUICTransporter(nil)
|
||||
case "http2":
|
||||
tr = gost.HTTP2Transporter(nil)
|
||||
node.DialOptions = append(node.DialOptions,
|
||||
gost.ChainDialOption(chain),
|
||||
)
|
||||
chain = gost.NewChain() // cutoff the chain for multiplex
|
||||
case "h2":
|
||||
tr = gost.H2Transporter(nil)
|
||||
node.DialOptions = append(node.DialOptions,
|
||||
gost.ChainDialOption(chain),
|
||||
)
|
||||
chain = gost.NewChain() // cutoff the chain for multiplex
|
||||
case "h2c":
|
||||
tr = gost.H2CTransporter()
|
||||
node.DialOptions = append(node.DialOptions,
|
||||
gost.ChainDialOption(chain),
|
||||
)
|
||||
chain = gost.NewChain() // cutoff the chain for multiplex
|
||||
default:
|
||||
tr = gost.TCPTransporter()
|
||||
}
|
||||
|
||||
var connector gost.Connector
|
||||
switch node.Protocol {
|
||||
case "http2":
|
||||
connector = gost.HTTP2Connector(node.User)
|
||||
case "socks", "socks5":
|
||||
connector = gost.SOCKS5Connector(node.User)
|
||||
case "socks4":
|
||||
connector = gost.SOCKS4Connector()
|
||||
case "socks4a":
|
||||
connector = gost.SOCKS4AConnector()
|
||||
case "ss":
|
||||
connector = gost.ShadowConnector(node.User)
|
||||
case "direct", "forward":
|
||||
connector = gost.SSHDirectForwardConnector()
|
||||
case "remote":
|
||||
connector = gost.SSHRemoteForwardConnector()
|
||||
case "http":
|
||||
fallthrough
|
||||
default:
|
||||
node.Protocol = "http" // default protocol is HTTP
|
||||
connector = gost.HTTPConnector(node.User)
|
||||
}
|
||||
|
||||
node.DialOptions = append(node.DialOptions,
|
||||
gost.TimeoutDialOption(gost.DialTimeout),
|
||||
)
|
||||
|
||||
interval, _ := strconv.Atoi(node.Values.Get("ping"))
|
||||
node.HandshakeOptions = append(node.HandshakeOptions,
|
||||
gost.AddrHandshakeOption(node.Addr),
|
||||
gost.UserHandshakeOption(node.User),
|
||||
gost.TLSConfigHandshakeOption(tlsCfg),
|
||||
gost.IntervalHandshakeOption(time.Duration(interval)*time.Second),
|
||||
)
|
||||
node.Client = &gost.Client{
|
||||
Connector: connector,
|
||||
Transporter: tr,
|
||||
}
|
||||
chain.AddNode(node)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
func serve(chain *gost.Chain) error {
|
||||
for _, ns := range options.serveNodes {
|
||||
node, err := gost.ParseNode(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
users, err := parseUsers(node.Values.Get("secrets"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if node.User != nil {
|
||||
users = append(users, node.User)
|
||||
}
|
||||
tlsCfg, err := tlsConfig(node.Values.Get("cert"), node.Values.Get("key"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ln gost.Listener
|
||||
switch node.Transport {
|
||||
case "tls":
|
||||
ln, err = gost.TLSListener(node.Addr, tlsCfg)
|
||||
case "ws":
|
||||
wsOpts := &gost.WSOptions{}
|
||||
wsOpts.EnableCompression = toBool(node.Values.Get("compression"))
|
||||
wsOpts.ReadBufferSize, _ = strconv.Atoi(node.Values.Get("rbuf"))
|
||||
wsOpts.WriteBufferSize, _ = strconv.Atoi(node.Values.Get("wbuf"))
|
||||
ln, err = gost.WSListener(node.Addr, wsOpts)
|
||||
case "wss":
|
||||
wsOpts := &gost.WSOptions{}
|
||||
wsOpts.EnableCompression = toBool(node.Values.Get("compression"))
|
||||
wsOpts.ReadBufferSize, _ = strconv.Atoi(node.Values.Get("rbuf"))
|
||||
wsOpts.WriteBufferSize, _ = strconv.Atoi(node.Values.Get("wbuf"))
|
||||
ln, err = gost.WSSListener(node.Addr, tlsCfg, wsOpts)
|
||||
case "kcp":
|
||||
config, err := parseKCPConfig(node.Values.Get("c"))
|
||||
if err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
ln, err = gost.KCPListener(node.Addr, config)
|
||||
case "ssh":
|
||||
config := &gost.SSHConfig{
|
||||
Users: users,
|
||||
TLSConfig: tlsCfg,
|
||||
}
|
||||
if node.Protocol == "forward" {
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
} else {
|
||||
ln, err = gost.SSHTunnelListener(node.Addr, config)
|
||||
}
|
||||
case "quic":
|
||||
config := &gost.QUICConfig{
|
||||
TLSConfig: tlsCfg,
|
||||
KeepAlive: toBool(node.Values.Get("keepalive")),
|
||||
}
|
||||
timeout, _ := strconv.Atoi(node.Values.Get("timeout"))
|
||||
config.Timeout = time.Duration(timeout) * time.Second
|
||||
ln, err = gost.QUICListener(node.Addr, config)
|
||||
case "http2":
|
||||
ln, err = gost.HTTP2Listener(node.Addr, tlsCfg)
|
||||
case "h2":
|
||||
ln, err = gost.H2Listener(node.Addr, tlsCfg)
|
||||
case "h2c":
|
||||
ln, err = gost.H2CListener(node.Addr)
|
||||
case "tcp":
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
case "rtcp":
|
||||
if chain.LastNode().Protocol == "forward" && chain.LastNode().Transport == "ssh" {
|
||||
chain.Nodes()[len(chain.Nodes())-1].Client.Connector = gost.SSHRemoteForwardConnector()
|
||||
}
|
||||
ln, err = gost.TCPRemoteForwardListener(node.Addr, chain)
|
||||
case "udp":
|
||||
ttl, _ := strconv.Atoi(node.Values.Get("ttl"))
|
||||
ln, err = gost.UDPDirectForwardListener(node.Addr, time.Duration(ttl)*time.Second)
|
||||
case "rudp":
|
||||
ttl, _ := strconv.Atoi(node.Values.Get("ttl"))
|
||||
ln, err = gost.UDPRemoteForwardListener(node.Addr, chain, time.Duration(ttl)*time.Second)
|
||||
case "redirect":
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
case "ssu":
|
||||
ttl, _ := strconv.Atoi(node.Values.Get("ttl"))
|
||||
ln, err = gost.ShadowUDPListener(node.Addr, node.User, time.Duration(ttl)*time.Second)
|
||||
default:
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var whitelist, blacklist *gost.Permissions
|
||||
if node.Values.Get("whitelist") != "" {
|
||||
if whitelist, err = gost.ParsePermissions(node.Values.Get("whitelist")); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// By default allow for everyting
|
||||
whitelist, _ = gost.ParsePermissions("*:*:*")
|
||||
}
|
||||
|
||||
if node.Values.Get("blacklist") != "" {
|
||||
if blacklist, err = gost.ParsePermissions(node.Values.Get("blacklist")); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// By default block nothing
|
||||
blacklist, _ = gost.ParsePermissions("")
|
||||
}
|
||||
|
||||
var handlerOptions []gost.HandlerOption
|
||||
|
||||
handlerOptions = append(handlerOptions,
|
||||
gost.AddrHandlerOption(node.Addr),
|
||||
gost.ChainHandlerOption(chain),
|
||||
gost.UsersHandlerOption(users...),
|
||||
gost.TLSConfigHandlerOption(tlsCfg),
|
||||
gost.WhitelistHandlerOption(whitelist),
|
||||
gost.BlacklistHandlerOption(blacklist),
|
||||
)
|
||||
var handler gost.Handler
|
||||
switch node.Protocol {
|
||||
case "http2":
|
||||
handler = gost.HTTP2Handler(handlerOptions...)
|
||||
case "socks", "socks5":
|
||||
handler = gost.SOCKS5Handler(handlerOptions...)
|
||||
case "socks4", "socks4a":
|
||||
handler = gost.SOCKS4Handler(handlerOptions...)
|
||||
case "ss":
|
||||
handler = gost.ShadowHandler(handlerOptions...)
|
||||
case "http":
|
||||
handler = gost.HTTPHandler(handlerOptions...)
|
||||
case "tcp":
|
||||
handler = gost.TCPDirectForwardHandler(node.Remote, handlerOptions...)
|
||||
case "rtcp":
|
||||
handler = gost.TCPRemoteForwardHandler(node.Remote, handlerOptions...)
|
||||
case "udp":
|
||||
handler = gost.UDPDirectForwardHandler(node.Remote, handlerOptions...)
|
||||
case "rudp":
|
||||
handler = gost.UDPRemoteForwardHandler(node.Remote, handlerOptions...)
|
||||
case "forward":
|
||||
handler = gost.SSHForwardHandler(handlerOptions...)
|
||||
case "redirect":
|
||||
handler = gost.TCPRedirectHandler(handlerOptions...)
|
||||
case "ssu":
|
||||
handler = gost.ShadowUDPdHandler(handlerOptions...)
|
||||
default:
|
||||
handler = gost.AutoHandler(handlerOptions...)
|
||||
}
|
||||
go new(gost.Server).Serve(ln, handler)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load the certificate from cert and key files, will use the default certificate if the provided info are invalid.
|
||||
func tlsConfig(certFile, keyFile string) (*tls.Config, error) {
|
||||
if certFile == "" || keyFile == "" {
|
||||
return nil, nil
|
||||
}
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
|
||||
}
|
||||
|
||||
func loadConfigureFile(configureFile string) error {
|
||||
@ -91,12 +383,65 @@ func loadConfigureFile(configureFile string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type flagStringList []string
|
||||
type stringList []string
|
||||
|
||||
func (this *flagStringList) String() string {
|
||||
return fmt.Sprintf("%s", *this)
|
||||
func (l *stringList) String() string {
|
||||
return fmt.Sprintf("%s", *l)
|
||||
}
|
||||
func (this *flagStringList) Set(value string) error {
|
||||
*this = append(*this, value)
|
||||
func (l *stringList) Set(value string) error {
|
||||
*l = append(*l, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func toBool(s string) bool {
|
||||
if b, _ := strconv.ParseBool(s); b {
|
||||
return b
|
||||
}
|
||||
n, _ := strconv.Atoi(s)
|
||||
return n > 0
|
||||
}
|
||||
|
||||
func parseKCPConfig(configFile string) (*gost.KCPConfig, error) {
|
||||
if configFile == "" {
|
||||
return nil, nil
|
||||
}
|
||||
file, err := os.Open(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
config := &gost.KCPConfig{}
|
||||
if err = json.NewDecoder(file).Decode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func parseUsers(authFile string) (users []*url.Userinfo, err error) {
|
||||
if authFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(authFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
s := strings.SplitN(line, " ", 2)
|
||||
if len(s) == 1 {
|
||||
users = append(users, url.User(strings.TrimSpace(s[0])))
|
||||
} else if len(s) == 2 {
|
||||
users = append(users, url.UserPassword(strings.TrimSpace(s[0]), strings.TrimSpace(s[1])))
|
||||
}
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
return
|
||||
}
|
||||
|
313
conn.go
313
conn.go
@ -1,313 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gosocks4"
|
||||
"github.com/ginuerzh/gosocks5"
|
||||
"github.com/golang/glog"
|
||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||
)
|
||||
|
||||
type ProxyConn struct {
|
||||
conn net.Conn
|
||||
Node ProxyNode
|
||||
handshaked bool
|
||||
handshakeMutex sync.Mutex
|
||||
handshakeErr error
|
||||
}
|
||||
|
||||
func NewProxyConn(conn net.Conn, node ProxyNode) *ProxyConn {
|
||||
return &ProxyConn{
|
||||
conn: conn,
|
||||
Node: node,
|
||||
}
|
||||
}
|
||||
|
||||
// Handshake handshake with this proxy node based on the proxy node info: transport, protocol, authentication, etc.
|
||||
//
|
||||
// NOTE: any HTTP2 scheme will be treated as http (for protocol) or tls (for transport).
|
||||
func (c *ProxyConn) Handshake() error {
|
||||
c.handshakeMutex.Lock()
|
||||
defer c.handshakeMutex.Unlock()
|
||||
|
||||
if err := c.handshakeErr; err != nil {
|
||||
return err
|
||||
}
|
||||
if c.handshaked {
|
||||
return nil
|
||||
}
|
||||
c.handshakeErr = c.handshake()
|
||||
return c.handshakeErr
|
||||
}
|
||||
|
||||
func (c *ProxyConn) handshake() error {
|
||||
var tlsUsed bool
|
||||
|
||||
switch c.Node.Transport {
|
||||
case "ws": // websocket connection
|
||||
rbuf, _ := strconv.Atoi(c.Node.Get("rbuf"))
|
||||
wbuf, _ := strconv.Atoi(c.Node.Get("wbuf"))
|
||||
comp := c.Node.getBool("compression")
|
||||
opt := WSOptions{
|
||||
ReadBufferSize: rbuf,
|
||||
WriteBufferSize: wbuf,
|
||||
HandshakeTimeout: DialTimeout,
|
||||
EnableCompression: comp,
|
||||
}
|
||||
u := url.URL{Scheme: "ws", Host: c.Node.Addr, Path: "/ws"}
|
||||
conn, err := WebsocketClientConn(u.String(), c.conn, &opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn = conn
|
||||
case "wss": // websocket security
|
||||
tlsUsed = true
|
||||
|
||||
rbuf, _ := strconv.Atoi(c.Node.Get("rbuf"))
|
||||
wbuf, _ := strconv.Atoi(c.Node.Get("wbuf"))
|
||||
comp := c.Node.getBool("compression")
|
||||
opt := WSOptions{
|
||||
ReadBufferSize: rbuf,
|
||||
WriteBufferSize: wbuf,
|
||||
HandshakeTimeout: DialTimeout,
|
||||
EnableCompression: comp,
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: c.Node.insecureSkipVerify(),
|
||||
ServerName: c.Node.serverName,
|
||||
},
|
||||
}
|
||||
|
||||
u := url.URL{Scheme: "wss", Host: c.Node.Addr, Path: "/ws"}
|
||||
conn, err := WebsocketClientConn(u.String(), c.conn, &opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn = conn
|
||||
case "tls", "http2": // tls connection
|
||||
tlsUsed = true
|
||||
cfg := &tls.Config{
|
||||
InsecureSkipVerify: c.Node.insecureSkipVerify(),
|
||||
ServerName: c.Node.serverName,
|
||||
}
|
||||
c.conn = tls.Client(c.conn, cfg)
|
||||
case "h2": // same as http2, but just set a flag for later using.
|
||||
tlsUsed = true
|
||||
case "kcp": // kcp connection
|
||||
tlsUsed = true
|
||||
default:
|
||||
}
|
||||
|
||||
switch c.Node.Protocol {
|
||||
case "socks", "socks5": // socks5 handshake with auth and tls supported
|
||||
selector := &ClientSelector{
|
||||
methods: []uint8{
|
||||
gosocks5.MethodNoAuth,
|
||||
gosocks5.MethodUserPass,
|
||||
//MethodTLS,
|
||||
},
|
||||
}
|
||||
|
||||
if len(c.Node.Users) > 0 {
|
||||
selector.User = c.Node.Users[0]
|
||||
}
|
||||
|
||||
if !tlsUsed { // if transport is not security, enable security socks5
|
||||
selector.methods = append(selector.methods, MethodTLS)
|
||||
selector.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: c.Node.insecureSkipVerify(),
|
||||
ServerName: c.Node.serverName,
|
||||
}
|
||||
}
|
||||
|
||||
conn := gosocks5.ClientConn(c.conn, selector)
|
||||
if err := conn.Handleshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn = conn
|
||||
case "ss": // shadowsocks
|
||||
// nothing to do
|
||||
case "http", "http2":
|
||||
fallthrough
|
||||
default:
|
||||
}
|
||||
|
||||
c.handshaked = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect connect to addr through this proxy node
|
||||
func (c *ProxyConn) Connect(addr string) error {
|
||||
switch c.Node.Protocol {
|
||||
case "ss": // shadowsocks
|
||||
rawaddr, err := ss.RawAddr(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var method, password string
|
||||
if len(c.Node.Users) > 0 {
|
||||
method = c.Node.Users[0].Username()
|
||||
password, _ = c.Node.Users[0].Password()
|
||||
}
|
||||
if c.Node.getBool("ota") && !strings.HasSuffix(method, "-auth") {
|
||||
method += "-auth"
|
||||
}
|
||||
|
||||
cipher, err := ss.NewCipher(method, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ssc, err := ss.DialWithRawAddrConn(rawaddr, c.conn, cipher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn = &shadowConn{conn: ssc}
|
||||
return nil
|
||||
case "socks", "socks5":
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, _ := strconv.Atoi(port)
|
||||
req := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{
|
||||
Type: gosocks5.AddrDomain,
|
||||
Host: host,
|
||||
Port: uint16(p),
|
||||
})
|
||||
if err := req.Write(c); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(LDEBUG).Infoln("[socks5]", req)
|
||||
|
||||
reply, err := gosocks5.ReadReply(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(LDEBUG).Infoln("[socks5]", reply)
|
||||
if reply.Rep != gosocks5.Succeeded {
|
||||
return errors.New("Service unavailable")
|
||||
}
|
||||
case "socks4", "socks4a":
|
||||
atype := gosocks4.AddrDomain
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, _ := strconv.Atoi(port)
|
||||
|
||||
if c.Node.Protocol == "socks4" {
|
||||
taddr, err := net.ResolveTCPAddr("tcp4", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host = taddr.IP.String()
|
||||
p = taddr.Port
|
||||
atype = gosocks4.AddrIPv4
|
||||
}
|
||||
req := gosocks4.NewRequest(gosocks4.CmdConnect,
|
||||
&gosocks4.Addr{Type: atype, Host: host, Port: uint16(p)}, nil)
|
||||
if err := req.Write(c); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, req)
|
||||
|
||||
reply, err := gosocks4.ReadReply(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, reply)
|
||||
|
||||
if reply.Code != gosocks4.Granted {
|
||||
return errors.New(fmt.Sprintf("%s: code=%d", c.Node.Protocol, reply.Code))
|
||||
}
|
||||
case "http":
|
||||
fallthrough
|
||||
default:
|
||||
req := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{Host: addr},
|
||||
Host: addr,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
req.Header.Set("Proxy-Connection", "keep-alive")
|
||||
if len(c.Node.Users) > 0 {
|
||||
user := c.Node.Users[0]
|
||||
s := user.String()
|
||||
if _, set := user.Password(); !set {
|
||||
s += ":"
|
||||
}
|
||||
req.Header.Set("Proxy-Authorization",
|
||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
|
||||
}
|
||||
if err := req.Write(c); err != nil {
|
||||
return err
|
||||
}
|
||||
if glog.V(LDEBUG) {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
glog.Infoln(string(dump))
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(c), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if glog.V(LDEBUG) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
glog.Infoln(string(dump))
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ProxyConn) Read(b []byte) (n int, err error) {
|
||||
return c.conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *ProxyConn) Write(b []byte) (n int, err error) {
|
||||
return c.conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *ProxyConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *ProxyConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *ProxyConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *ProxyConn) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *ProxyConn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *ProxyConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
func main() {
|
@ -4,7 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
func main() {
|
@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
func main() {
|
@ -4,7 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
func main() {
|
@ -5,7 +5,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
var (
|
@ -5,7 +5,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
var (
|
@ -8,7 +8,7 @@ import (
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
var (
|
@ -6,7 +6,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
var (
|
@ -5,7 +5,7 @@ import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
var (
|
@ -4,8 +4,9 @@ import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -33,6 +34,9 @@ func main() {
|
||||
Protocol: "socks5",
|
||||
Transport: "ssh",
|
||||
Addr: faddr,
|
||||
HandshakeOptions: []gost.HandshakeOption{
|
||||
gost.IntervalHandshakeOption(30 * time.Second),
|
||||
},
|
||||
Client: &gost.Client{
|
||||
Connector: gost.SOCKS5Connector(nil),
|
||||
Transporter: gost.SSHTunnelTransporter(),
|
@ -5,7 +5,7 @@ import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/ginuerzh/gost"
|
||||
)
|
||||
|
||||
var (
|
1167
forward.go
1167
forward.go
File diff suppressed because it is too large
Load Diff
157
gost.go
157
gost.go
@ -6,88 +6,76 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = "2.4-dev20170711"
|
||||
)
|
||||
|
||||
// Log level for glog
|
||||
const (
|
||||
LFATAL = iota
|
||||
LERROR
|
||||
LWARNING
|
||||
LINFO
|
||||
LDEBUG
|
||||
)
|
||||
// Version is the gost version.
|
||||
const Version = "2.4-dev20170803"
|
||||
|
||||
// Debug is a flag that enables the debug log.
|
||||
var Debug bool
|
||||
|
||||
var (
|
||||
tinyBufferSize = 128
|
||||
smallBufferSize = 1 * 1024 // 1KB small buffer
|
||||
mediumBufferSize = 8 * 1024 // 8KB medium buffer
|
||||
largeBufferSize = 32 * 1024 // 32KB large buffer
|
||||
)
|
||||
|
||||
var (
|
||||
// KeepAliveTime is the keep alive time period for TCP connection.
|
||||
KeepAliveTime = 180 * time.Second
|
||||
DialTimeout = 30 * time.Second
|
||||
ReadTimeout = 90 * time.Second
|
||||
WriteTimeout = 90 * time.Second
|
||||
|
||||
DefaultTTL = 60 // default udp node TTL in second for udp port forwarding
|
||||
// DialTimeout is the timeout of dial.
|
||||
DialTimeout = 30 * time.Second
|
||||
// ReadTimeout is the timeout for reading.
|
||||
ReadTimeout = 30 * time.Second
|
||||
// WriteTimeout is the timeout for writing.
|
||||
WriteTimeout = 60 * time.Second
|
||||
// PingTimeout is the timeout for pinging.
|
||||
PingTimeout = 30 * time.Second
|
||||
// PingRetries is the reties of ping.
|
||||
PingRetries = 3
|
||||
// default udp node TTL in second for udp port forwarding.
|
||||
defaultTTL = 60 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
SmallBufferSize = 1 * 1024 // 1KB small buffer
|
||||
MediumBufferSize = 8 * 1024 // 8KB medium buffer
|
||||
LargeBufferSize = 32 * 1024 // 32KB large buffer
|
||||
DefaultTLSConfig *tls.Config
|
||||
)
|
||||
|
||||
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
|
||||
defaultRawKey []byte
|
||||
)
|
||||
|
||||
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")
|
||||
func init() {
|
||||
rawCert, rawKey, err := generateKeyPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := c.SetKeepAlive(true); err != nil {
|
||||
return err
|
||||
cert, err := tls.X509KeyPair(rawCert, rawKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := c.SetKeepAlivePeriod(d); err != nil {
|
||||
return err
|
||||
DefaultTLSConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
return nil
|
||||
|
||||
log.DefaultLogger = &LogLogger{}
|
||||
}
|
||||
|
||||
func generateKeyPair() (rawCert, rawKey []byte) {
|
||||
if defaultRawCert != nil && defaultRawKey != nil {
|
||||
return defaultRawCert, defaultRawKey
|
||||
}
|
||||
func SetLogger(logger log.Logger) {
|
||||
log.DefaultLogger = logger
|
||||
}
|
||||
|
||||
func generateKeyPair() (rawCert, rawKey []byte, err error) {
|
||||
// Create private key and self-signed certificate
|
||||
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
return
|
||||
}
|
||||
validFor := time.Hour * 24 * 365 * 10
|
||||
validFor := time.Hour * 24 * 365 * 10 // ten years
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(validFor)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
@ -106,68 +94,11 @@ func generateKeyPair() (rawCert, rawKey []byte) {
|
||||
}
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
|
||||
return rawCert, rawKey
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
rawCert, rawKey := defaultRawCert, defaultRawKey
|
||||
if defaultRawCert == nil || defaultRawKey == nil {
|
||||
rawCert, rawKey = generateKeyPair()
|
||||
}
|
||||
return tls.X509KeyPair(rawCert, rawKey)
|
||||
}
|
||||
|
||||
// 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
|
||||
return
|
||||
}
|
||||
|
110
gost/chain.go
110
gost/chain.go
@ -1,110 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmptyChain is an error that implies the chain is empty.
|
||||
ErrEmptyChain = errors.New("empty chain")
|
||||
)
|
||||
|
||||
// Chain is a proxy chain that holds a list of proxy nodes.
|
||||
type Chain struct {
|
||||
nodes []Node
|
||||
}
|
||||
|
||||
// NewChain creates a proxy chain with proxy nodes nodes.
|
||||
func NewChain(nodes ...Node) *Chain {
|
||||
return &Chain{
|
||||
nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes returns the proxy nodes that the chain holds.
|
||||
func (c *Chain) Nodes() []Node {
|
||||
return c.nodes
|
||||
}
|
||||
|
||||
// LastNode returns the last node of the node list.
|
||||
// If the chain is empty, an empty node is returns.
|
||||
func (c *Chain) LastNode() Node {
|
||||
if c.IsEmpty() {
|
||||
return Node{}
|
||||
}
|
||||
return c.nodes[len(c.nodes)-1]
|
||||
}
|
||||
|
||||
// AddNode appends the node(s) to the chain.
|
||||
func (c *Chain) AddNode(nodes ...Node) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
c.nodes = append(c.nodes, nodes...)
|
||||
}
|
||||
|
||||
// IsEmpty checks if the chain is empty.
|
||||
// An empty chain means that there is no proxy node in the chain.
|
||||
func (c *Chain) IsEmpty() bool {
|
||||
return c == nil || len(c.nodes) == 0
|
||||
}
|
||||
|
||||
// Dial connects to the target address addr through the chain.
|
||||
// If the chain is empty, it will use the net.Dial directly.
|
||||
func (c *Chain) Dial(addr string) (net.Conn, error) {
|
||||
if c.IsEmpty() {
|
||||
return net.Dial("tcp", addr)
|
||||
}
|
||||
|
||||
conn, err := c.Conn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc, err := c.LastNode().Client.Connect(conn, addr)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Conn obtains a handshaked connection to the last node of the chain.
|
||||
// If the chain is empty, it returns an ErrEmptyChain error.
|
||||
func (c *Chain) Conn() (net.Conn, error) {
|
||||
if c.IsEmpty() {
|
||||
return nil, ErrEmptyChain
|
||||
}
|
||||
|
||||
nodes := c.nodes
|
||||
conn, err := nodes[0].Client.Dial(nodes[0].Addr, nodes[0].DialOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err = nodes[0].Client.Handshake(conn, nodes[0].HandshakeOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, node := range nodes {
|
||||
if i == len(nodes)-1 {
|
||||
break
|
||||
}
|
||||
|
||||
next := nodes[i+1]
|
||||
cc, err := node.Client.Connect(conn, next.Addr)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
cc, err = next.Client.Handshake(cc, next.HandshakeOptions...)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
conn = cc
|
||||
}
|
||||
return conn, nil
|
||||
}
|
@ -1,430 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gost/gost"
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
var (
|
||||
options struct {
|
||||
chainNodes, serveNodes stringList
|
||||
debugMode bool
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
var (
|
||||
configureFile string
|
||||
printVersion bool
|
||||
)
|
||||
|
||||
flag.Var(&options.chainNodes, "F", "forward address, can make a forward chain")
|
||||
flag.Var(&options.serveNodes, "L", "listen address, can listen on multiple ports")
|
||||
flag.StringVar(&configureFile, "C", "", "configure file")
|
||||
flag.BoolVar(&options.debugMode, "D", false, "enable debug log")
|
||||
flag.BoolVar(&printVersion, "V", false, "print version")
|
||||
flag.Parse()
|
||||
|
||||
if err := loadConfigureFile(configureFile); err != nil {
|
||||
log.Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if flag.NFlag() == 0 {
|
||||
flag.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if printVersion {
|
||||
fmt.Fprintf(os.Stderr, "gost %s (%s)\n", gost.Version, runtime.Version())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
gost.Debug = options.debugMode
|
||||
}
|
||||
|
||||
func main() {
|
||||
chain, err := initChain()
|
||||
if err != nil {
|
||||
log.Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := serve(chain); err != nil {
|
||||
log.Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
select {}
|
||||
}
|
||||
|
||||
func initChain() (*gost.Chain, error) {
|
||||
chain := gost.NewChain()
|
||||
for _, ns := range options.chainNodes {
|
||||
node, err := gost.ParseNode(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverName, _, _ := net.SplitHostPort(node.Addr)
|
||||
if serverName == "" {
|
||||
serverName = "localhost" // default server name
|
||||
}
|
||||
|
||||
tlsCfg := &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: !toBool(node.Values.Get("scure")),
|
||||
}
|
||||
var tr gost.Transporter
|
||||
switch node.Transport {
|
||||
case "tls":
|
||||
tr = gost.TLSTransporter()
|
||||
case "ws":
|
||||
wsOpts := &gost.WSOptions{}
|
||||
wsOpts.EnableCompression = toBool(node.Values.Get("compression"))
|
||||
wsOpts.ReadBufferSize, _ = strconv.Atoi(node.Values.Get("rbuf"))
|
||||
wsOpts.WriteBufferSize, _ = strconv.Atoi(node.Values.Get("wbuf"))
|
||||
node.HandshakeOptions = append(node.HandshakeOptions,
|
||||
gost.WSOptionsHandshakeOption(wsOpts),
|
||||
)
|
||||
tr = gost.WSTransporter(nil)
|
||||
case "wss":
|
||||
tr = gost.WSSTransporter(nil)
|
||||
case "kcp":
|
||||
if !chain.IsEmpty() {
|
||||
return nil, errors.New("KCP must be the first node in the proxy chain")
|
||||
}
|
||||
config, err := parseKCPConfig(node.Values.Get("c"))
|
||||
if err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
node.HandshakeOptions = append(node.HandshakeOptions,
|
||||
gost.KCPConfigHandshakeOption(config),
|
||||
)
|
||||
tr = gost.KCPTransporter(nil)
|
||||
case "ssh":
|
||||
if node.Protocol == "direct" || node.Protocol == "remote" {
|
||||
tr = gost.SSHForwardTransporter()
|
||||
} else {
|
||||
tr = gost.SSHTunnelTransporter()
|
||||
}
|
||||
node.Chain = chain // cutoff the chain for multiplex
|
||||
chain = gost.NewChain()
|
||||
case "quic":
|
||||
if !chain.IsEmpty() {
|
||||
return nil, errors.New("QUIC must be the first node in the proxy chain")
|
||||
}
|
||||
config := &gost.QUICConfig{
|
||||
TLSConfig: tlsCfg,
|
||||
KeepAlive: toBool(node.Values.Get("keepalive")),
|
||||
}
|
||||
node.HandshakeOptions = append(node.HandshakeOptions,
|
||||
gost.QUICConfigHandshakeOption(config),
|
||||
)
|
||||
tr = gost.QUICTransporter(nil)
|
||||
case "http2":
|
||||
tr = gost.HTTP2Transporter(nil)
|
||||
node.Chain = chain // cutoff the chain for multiplex
|
||||
chain = gost.NewChain()
|
||||
case "h2":
|
||||
tr = gost.H2Transporter(nil)
|
||||
case "h2c":
|
||||
tr = gost.H2CTransporter()
|
||||
default:
|
||||
tr = gost.TCPTransporter()
|
||||
}
|
||||
|
||||
var connector gost.Connector
|
||||
switch node.Protocol {
|
||||
case "http2":
|
||||
connector = gost.HTTP2Connector(nil)
|
||||
case "socks", "socks5":
|
||||
connector = gost.SOCKS5Connector(nil)
|
||||
case "socks4":
|
||||
connector = gost.SOCKS4Connector()
|
||||
case "socks4a":
|
||||
connector = gost.SOCKS4AConnector()
|
||||
case "ss":
|
||||
connector = gost.ShadowConnector(nil)
|
||||
case "direct":
|
||||
connector = gost.SSHDirectForwardConnector()
|
||||
case "remote":
|
||||
connector = gost.SSHRemoteForwardConnector()
|
||||
case "http":
|
||||
fallthrough
|
||||
default:
|
||||
node.Protocol = "http" // default protocol is HTTP
|
||||
connector = gost.HTTPConnector(nil)
|
||||
}
|
||||
|
||||
node.DialOptions = append(node.DialOptions,
|
||||
gost.TimeoutDialOption(gost.DialTimeout),
|
||||
gost.ChainDialOption(node.Chain),
|
||||
)
|
||||
|
||||
interval, _ := strconv.Atoi(node.Values.Get("ping"))
|
||||
node.HandshakeOptions = append(node.HandshakeOptions,
|
||||
gost.AddrHandshakeOption(node.Addr),
|
||||
gost.UserHandshakeOption(node.User),
|
||||
gost.TLSConfigHandshakeOption(tlsCfg),
|
||||
gost.IntervalHandshakeOption(time.Duration(interval)*time.Second),
|
||||
)
|
||||
node.Client = &gost.Client{
|
||||
Connector: connector,
|
||||
Transporter: tr,
|
||||
}
|
||||
chain.AddNode(node)
|
||||
}
|
||||
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
func serve(chain *gost.Chain) error {
|
||||
for _, ns := range options.serveNodes {
|
||||
node, err := gost.ParseNode(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
users, err := parseUsers(node.Values.Get("secrets"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsCfg, err := tlsConfig(node.Values.Get("cert"), node.Values.Get("key"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ln gost.Listener
|
||||
switch node.Transport {
|
||||
case "tls":
|
||||
ln, err = gost.TLSListener(node.Addr, tlsCfg)
|
||||
case "ws":
|
||||
wsOpts := &gost.WSOptions{}
|
||||
wsOpts.EnableCompression = toBool(node.Values.Get("compression"))
|
||||
wsOpts.ReadBufferSize, _ = strconv.Atoi(node.Values.Get("rbuf"))
|
||||
wsOpts.WriteBufferSize, _ = strconv.Atoi(node.Values.Get("wbuf"))
|
||||
ln, err = gost.WSListener(node.Addr, wsOpts)
|
||||
case "wss":
|
||||
wsOpts := &gost.WSOptions{}
|
||||
wsOpts.EnableCompression = toBool(node.Values.Get("compression"))
|
||||
wsOpts.ReadBufferSize, _ = strconv.Atoi(node.Values.Get("rbuf"))
|
||||
wsOpts.WriteBufferSize, _ = strconv.Atoi(node.Values.Get("wbuf"))
|
||||
ln, err = gost.WSSListener(node.Addr, tlsCfg, wsOpts)
|
||||
case "kcp":
|
||||
config, err := parseKCPConfig(node.Values.Get("c"))
|
||||
if err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
ln, err = gost.KCPListener(node.Addr, config)
|
||||
case "ssh":
|
||||
config := &gost.SSHConfig{
|
||||
Users: users,
|
||||
TLSConfig: tlsCfg,
|
||||
}
|
||||
if node.Protocol == "forward" {
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
} else {
|
||||
ln, err = gost.SSHTunnelListener(node.Addr, config)
|
||||
}
|
||||
case "quic":
|
||||
config := &gost.QUICConfig{
|
||||
TLSConfig: tlsCfg,
|
||||
KeepAlive: toBool(node.Values.Get("keepalive")),
|
||||
}
|
||||
timeout, _ := strconv.Atoi(node.Values.Get("timeout"))
|
||||
config.Timeout = time.Duration(timeout) * time.Second
|
||||
ln, err = gost.QUICListener(node.Addr, config)
|
||||
case "http2":
|
||||
ln, err = gost.HTTP2Listener(node.Addr, tlsCfg)
|
||||
case "h2":
|
||||
ln, err = gost.H2Listener(node.Addr, tlsCfg)
|
||||
case "h2c":
|
||||
ln, err = gost.H2CListener(node.Addr)
|
||||
case "tcp":
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
case "rtcp":
|
||||
ln, err = gost.TCPRemoteForwardListener(node.Addr, chain)
|
||||
case "udp":
|
||||
ttl, _ := strconv.Atoi(node.Values.Get("ttl"))
|
||||
ln, err = gost.UDPDirectForwardListener(node.Addr, time.Duration(ttl)*time.Second)
|
||||
case "rudp":
|
||||
ttl, _ := strconv.Atoi(node.Values.Get("ttl"))
|
||||
ln, err = gost.UDPRemoteForwardListener(node.Addr, chain, time.Duration(ttl)*time.Second)
|
||||
case "redirect":
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
case "ssu":
|
||||
ttl, _ := strconv.Atoi(node.Values.Get("ttl"))
|
||||
ln, err = gost.ShadowUDPListener(node.Addr, node.User, time.Duration(ttl)*time.Second)
|
||||
default:
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var whitelist, blacklist *gost.Permissions
|
||||
if node.Values.Get("whitelist") != "" {
|
||||
if whitelist, err = gost.ParsePermissions(node.Values.Get("whitelist")); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// By default allow for everyting
|
||||
whitelist, _ = gost.ParsePermissions("*:*:*")
|
||||
}
|
||||
|
||||
if node.Values.Get("blacklist") != "" {
|
||||
if blacklist, err = gost.ParsePermissions(node.Values.Get("blacklist")); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// By default block nothing
|
||||
blacklist, _ = gost.ParsePermissions("")
|
||||
}
|
||||
|
||||
var handlerOptions []gost.HandlerOption
|
||||
|
||||
handlerOptions = append(handlerOptions,
|
||||
gost.AddrHandlerOption(node.Addr),
|
||||
gost.ChainHandlerOption(chain),
|
||||
gost.UsersHandlerOption(users...),
|
||||
gost.TLSConfigHandlerOption(tlsCfg),
|
||||
gost.WhitelistHandlerOption(whitelist),
|
||||
gost.BlacklistHandlerOption(blacklist),
|
||||
)
|
||||
var handler gost.Handler
|
||||
switch node.Protocol {
|
||||
case "http2":
|
||||
handler = gost.HTTP2Handler(handlerOptions...)
|
||||
case "socks", "socks5":
|
||||
handler = gost.SOCKS5Handler(handlerOptions...)
|
||||
case "socks4", "socks4a":
|
||||
handler = gost.SOCKS4Handler(handlerOptions...)
|
||||
case "ss":
|
||||
handler = gost.ShadowHandler(handlerOptions...)
|
||||
case "http":
|
||||
handler = gost.HTTPHandler(handlerOptions...)
|
||||
case "tcp":
|
||||
handler = gost.TCPDirectForwardHandler(node.Remote, handlerOptions...)
|
||||
case "rtcp":
|
||||
handler = gost.TCPRemoteForwardHandler(node.Remote, handlerOptions...)
|
||||
case "udp":
|
||||
handler = gost.UDPDirectForwardHandler(node.Remote, handlerOptions...)
|
||||
case "rudp":
|
||||
handler = gost.UDPRemoteForwardHandler(node.Remote, handlerOptions...)
|
||||
case "forward":
|
||||
handler = gost.SSHForwardHandler(handlerOptions...)
|
||||
case "redirect":
|
||||
handler = gost.TCPRedirectHandler(handlerOptions...)
|
||||
case "ssu":
|
||||
handler = gost.ShadowUDPdHandler(handlerOptions...)
|
||||
default:
|
||||
// TODO: auto poroxy handler
|
||||
handler = gost.HTTPHandler(handlerOptions...)
|
||||
}
|
||||
go new(gost.Server).Serve(ln, handler)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load the certificate from cert and key files, will use the default certificate if the provided info are invalid.
|
||||
func tlsConfig(certFile, keyFile string) (*tls.Config, error) {
|
||||
if certFile == "" || keyFile == "" {
|
||||
return nil, nil
|
||||
}
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
|
||||
}
|
||||
|
||||
func loadConfigureFile(configureFile string) error {
|
||||
if configureFile == "" {
|
||||
return nil
|
||||
}
|
||||
content, err := ioutil.ReadFile(configureFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(content, &options); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type stringList []string
|
||||
|
||||
func (l *stringList) String() string {
|
||||
return fmt.Sprintf("%s", *l)
|
||||
}
|
||||
func (l *stringList) Set(value string) error {
|
||||
*l = append(*l, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func toBool(s string) bool {
|
||||
if b, _ := strconv.ParseBool(s); b {
|
||||
return b
|
||||
}
|
||||
n, _ := strconv.Atoi(s)
|
||||
return n > 0
|
||||
}
|
||||
|
||||
func parseKCPConfig(configFile string) (*gost.KCPConfig, error) {
|
||||
if configFile == "" {
|
||||
return nil, nil
|
||||
}
|
||||
file, err := os.Open(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
config := &gost.KCPConfig{}
|
||||
if err = json.NewDecoder(file).Decode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func parseUsers(authFile string) (users []*url.Userinfo, err error) {
|
||||
if authFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(authFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
s := strings.SplitN(line, " ", 2)
|
||||
if len(s) == 1 {
|
||||
users = append(users, url.User(strings.TrimSpace(s[0])))
|
||||
} else if len(s) == 2 {
|
||||
users = append(users, url.UserPassword(strings.TrimSpace(s[0]), strings.TrimSpace(s[1])))
|
||||
}
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
return
|
||||
}
|
663
gost/forward.go
663
gost/forward.go
@ -1,663 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/ginuerzh/gosocks5"
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
type tcpDirectForwardHandler struct {
|
||||
raddr string
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// TCPDirectForwardHandler creates a server Handler for TCP port forwarding server.
|
||||
// The raddr is the remote address that the server will forward to.
|
||||
func TCPDirectForwardHandler(raddr string, opts ...HandlerOption) Handler {
|
||||
h := &tcpDirectForwardHandler{
|
||||
raddr: raddr,
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
log.Logf("[tcp] %s - %s", conn.RemoteAddr(), h.raddr)
|
||||
cc, err := h.options.Chain.Dial(h.raddr)
|
||||
if err != nil {
|
||||
log.Logf("[tcp] %s -> %s : %s", conn.RemoteAddr(), h.raddr, err)
|
||||
return
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
log.Logf("[tcp] %s <-> %s", conn.RemoteAddr(), h.raddr)
|
||||
transport(conn, cc)
|
||||
log.Logf("[tcp] %s >-< %s", conn.RemoteAddr(), h.raddr)
|
||||
}
|
||||
|
||||
type udpDirectForwardHandler struct {
|
||||
raddr string
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// UDPDirectForwardHandler creates a server Handler for UDP port forwarding server.
|
||||
// The raddr is the remote address that the server will forward to.
|
||||
func UDPDirectForwardHandler(raddr string, opts ...HandlerOption) Handler {
|
||||
h := &udpDirectForwardHandler{
|
||||
raddr: raddr,
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *udpDirectForwardHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
var cc net.Conn
|
||||
if h.options.Chain.IsEmpty() {
|
||||
raddr, err := net.ResolveUDPAddr("udp", h.raddr)
|
||||
if err != nil {
|
||||
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), h.raddr, err)
|
||||
return
|
||||
}
|
||||
cc, err = net.DialUDP("udp", nil, raddr)
|
||||
if err != nil {
|
||||
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), h.raddr, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
|
||||
if err != nil {
|
||||
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), h.raddr, err)
|
||||
return
|
||||
}
|
||||
cc = &udpTunnelConn{Conn: cc, raddr: h.raddr}
|
||||
}
|
||||
|
||||
defer cc.Close()
|
||||
|
||||
log.Logf("[udp] %s <-> %s", conn.RemoteAddr(), h.raddr)
|
||||
transport(conn, cc)
|
||||
log.Logf("[udp] %s >-< %s", conn.RemoteAddr(), h.raddr)
|
||||
}
|
||||
|
||||
type tcpRemoteForwardHandler struct {
|
||||
raddr string
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// TCPRemoteForwardHandler creates a server Handler for TCP remote port forwarding server.
|
||||
// The raddr is the remote address that the server will forward to.
|
||||
func TCPRemoteForwardHandler(raddr string, opts ...HandlerOption) Handler {
|
||||
h := &tcpRemoteForwardHandler{
|
||||
raddr: raddr,
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *tcpRemoteForwardHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
cc, err := net.DialTimeout("tcp", h.raddr, DialTimeout)
|
||||
if err != nil {
|
||||
log.Logf("[rtcp] %s -> %s : %s", conn.LocalAddr(), h.raddr, err)
|
||||
return
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
log.Logf("[rtcp] %s <-> %s", conn.LocalAddr(), h.raddr)
|
||||
transport(cc, conn)
|
||||
log.Logf("[rtcp] %s >-< %s", conn.LocalAddr(), h.raddr)
|
||||
}
|
||||
|
||||
type udpRemoteForwardHandler struct {
|
||||
raddr string
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// UDPRemoteForwardHandler creates a server Handler for UDP remote port forwarding server.
|
||||
// The raddr is the remote address that the server will forward to.
|
||||
func UDPRemoteForwardHandler(raddr string, opts ...HandlerOption) Handler {
|
||||
h := &udpRemoteForwardHandler{
|
||||
raddr: raddr,
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *udpRemoteForwardHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
raddr, err := net.ResolveUDPAddr("udp", h.raddr)
|
||||
if err != nil {
|
||||
log.Logf("[rudp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err)
|
||||
return
|
||||
}
|
||||
cc, err := net.DialUDP("udp", nil, raddr)
|
||||
if err != nil {
|
||||
log.Logf("[rudp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Logf("[rudp] %s <-> %s", conn.RemoteAddr(), h.raddr)
|
||||
transport(conn, cc)
|
||||
log.Logf("[rudp] %s >-< %s", conn.RemoteAddr(), h.raddr)
|
||||
}
|
||||
|
||||
type udpDirectForwardListener struct {
|
||||
ln net.PacketConn
|
||||
conns map[string]*udpServerConn
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
// UDPDirectForwardListener creates a Listener for UDP port forwarding server.
|
||||
func UDPDirectForwardListener(addr string, ttl time.Duration) (Listener, error) {
|
||||
laddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ln, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := &udpDirectForwardListener{
|
||||
ln: ln,
|
||||
conns: make(map[string]*udpServerConn),
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
ttl: ttl,
|
||||
}
|
||||
go l.listenLoop()
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *udpDirectForwardListener) listenLoop() {
|
||||
for {
|
||||
b := make([]byte, mediumBufferSize)
|
||||
n, raddr, err := l.ln.ReadFrom(b)
|
||||
if err != nil {
|
||||
log.Logf("[udp] peer -> %s : %s", l.Addr(), err)
|
||||
l.ln.Close()
|
||||
l.errChan <- err
|
||||
close(l.errChan)
|
||||
return
|
||||
}
|
||||
if Debug {
|
||||
log.Logf("[udp] %s >>> %s : length %d", raddr, l.Addr(), n)
|
||||
}
|
||||
conn, ok := l.conns[raddr.String()]
|
||||
if !ok || conn.Closed() {
|
||||
conn = newUDPServerConn(l.ln, raddr, l.ttl)
|
||||
l.conns[raddr.String()] = conn
|
||||
|
||||
select {
|
||||
case l.connChan <- conn:
|
||||
default:
|
||||
conn.Close()
|
||||
log.Logf("[udp] %s - %s: connection queue is full", raddr, l.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case conn.rChan <- b[:n]:
|
||||
default:
|
||||
log.Logf("[udp] %s -> %s : read queue is full", raddr, l.Addr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *udpDirectForwardListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *udpDirectForwardListener) Addr() net.Addr {
|
||||
return l.ln.LocalAddr()
|
||||
}
|
||||
|
||||
func (l *udpDirectForwardListener) Close() error {
|
||||
return l.ln.Close()
|
||||
}
|
||||
|
||||
type udpServerConn struct {
|
||||
conn net.PacketConn
|
||||
raddr net.Addr
|
||||
rChan, wChan chan []byte
|
||||
closed chan struct{}
|
||||
brokenChan chan struct{}
|
||||
closeMutex sync.Mutex
|
||||
ttl time.Duration
|
||||
nopChan chan int
|
||||
}
|
||||
|
||||
func newUDPServerConn(conn net.PacketConn, raddr net.Addr, ttl time.Duration) *udpServerConn {
|
||||
c := &udpServerConn{
|
||||
conn: conn,
|
||||
raddr: raddr,
|
||||
rChan: make(chan []byte, 128),
|
||||
wChan: make(chan []byte, 128),
|
||||
closed: make(chan struct{}),
|
||||
brokenChan: make(chan struct{}),
|
||||
nopChan: make(chan int),
|
||||
ttl: ttl,
|
||||
}
|
||||
go c.writeLoop()
|
||||
go c.ttlWait()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *udpServerConn) Read(b []byte) (n int, err error) {
|
||||
select {
|
||||
case bb := <-c.rChan:
|
||||
n = copy(b, bb)
|
||||
if n != len(bb) {
|
||||
err = errors.New("read partial data")
|
||||
return
|
||||
}
|
||||
case <-c.brokenChan:
|
||||
err = errors.New("Broken pipe")
|
||||
case <-c.closed:
|
||||
err = errors.New("read from closed connection")
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case c.nopChan <- n:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpServerConn) Write(b []byte) (n int, err error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
select {
|
||||
case c.wChan <- b:
|
||||
n = len(b)
|
||||
case <-c.brokenChan:
|
||||
err = errors.New("Broken pipe")
|
||||
case <-c.closed:
|
||||
err = errors.New("write to closed connection")
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case c.nopChan <- n:
|
||||
default:
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpServerConn) Close() error {
|
||||
c.closeMutex.Lock()
|
||||
defer c.closeMutex.Unlock()
|
||||
|
||||
select {
|
||||
case <-c.closed:
|
||||
return errors.New("connection is closed")
|
||||
default:
|
||||
close(c.closed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *udpServerConn) Closed() bool {
|
||||
select {
|
||||
case <-c.closed:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *udpServerConn) writeLoop() {
|
||||
for {
|
||||
select {
|
||||
case b, ok := <-c.wChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
n, err := c.conn.WriteTo(b, c.raddr)
|
||||
if err != nil {
|
||||
log.Logf("[udp] %s - %s : %s", c.RemoteAddr(), c.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
if Debug {
|
||||
log.Logf("[udp] %s <<< %s : length %d", c.RemoteAddr(), c.LocalAddr(), n)
|
||||
}
|
||||
case <-c.brokenChan:
|
||||
return
|
||||
case <-c.closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *udpServerConn) ttlWait() {
|
||||
ttl := c.ttl
|
||||
if ttl == 0 {
|
||||
ttl = defaultTTL
|
||||
}
|
||||
timer := time.NewTimer(ttl)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.nopChan:
|
||||
timer.Reset(ttl)
|
||||
case <-timer.C:
|
||||
close(c.brokenChan)
|
||||
return
|
||||
case <-c.closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *udpServerConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *udpServerConn) RemoteAddr() net.Addr {
|
||||
return c.raddr
|
||||
}
|
||||
|
||||
func (c *udpServerConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *udpServerConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *udpServerConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type tcpRemoteForwardListener struct {
|
||||
addr net.Addr
|
||||
chain *Chain
|
||||
ln net.Listener
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
// TCPRemoteForwardListener creates a Listener for TCP remote port forwarding server.
|
||||
func TCPRemoteForwardListener(addr string, chain *Chain) (Listener, error) {
|
||||
laddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tcpRemoteForwardListener{
|
||||
addr: laddr,
|
||||
chain: chain,
|
||||
closed: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *tcpRemoteForwardListener) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case <-l.closed:
|
||||
return nil, errors.New("closed")
|
||||
default:
|
||||
}
|
||||
|
||||
var tempDelay time.Duration
|
||||
for {
|
||||
conn, err := l.accept()
|
||||
if err != nil {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 1000 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 6 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Logf("[rtcp] Accept error: %v; retrying in %v", err, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *tcpRemoteForwardListener) accept() (conn net.Conn, err error) {
|
||||
lastNode := l.chain.LastNode()
|
||||
if lastNode.Protocol == "remote" && lastNode.Transport == "ssh" {
|
||||
conn, err = l.chain.Dial(l.addr.String())
|
||||
} else if lastNode.Protocol == "socks5" {
|
||||
cc, er := l.chain.Conn()
|
||||
if er != nil {
|
||||
return nil, er
|
||||
}
|
||||
conn, err = l.waitConnectSOCKS5(cc)
|
||||
if err != nil {
|
||||
cc.Close()
|
||||
}
|
||||
} else {
|
||||
if l.ln == nil {
|
||||
l.ln, err = net.Listen("tcp", l.addr.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
conn, err = l.ln.Accept()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *tcpRemoteForwardListener) waitConnectSOCKS5(conn net.Conn) (net.Conn, error) {
|
||||
conn, err := socks5Handshake(conn, l.chain.LastNode().User)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := gosocks5.NewRequest(gosocks5.CmdBind, toSocksAddr(l.addr))
|
||||
if err := req.Write(conn); err != nil {
|
||||
log.Log("[rtcp] SOCKS5 BIND request: ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// first reply, bind status
|
||||
conn.SetReadDeadline(time.Now().Add(ReadTimeout))
|
||||
rep, err := gosocks5.ReadReply(conn)
|
||||
if err != nil {
|
||||
log.Log("[rtcp] SOCKS5 BIND reply: ", err)
|
||||
return nil, err
|
||||
}
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
if rep.Rep != gosocks5.Succeeded {
|
||||
log.Logf("[rtcp] bind on %s failure", l.addr)
|
||||
return nil, fmt.Errorf("Bind on %s failure", l.addr.String())
|
||||
}
|
||||
log.Logf("[rtcp] BIND ON %s OK", rep.Addr)
|
||||
|
||||
// second reply, peer connected
|
||||
rep, err = gosocks5.ReadReply(conn)
|
||||
if err != nil {
|
||||
log.Log("[rtcp]", err)
|
||||
return nil, err
|
||||
}
|
||||
if rep.Rep != gosocks5.Succeeded {
|
||||
log.Logf("[rtcp] peer connect failure: %d", rep.Rep)
|
||||
return nil, errors.New("peer connect failure")
|
||||
}
|
||||
|
||||
log.Logf("[rtcp] PEER %s CONNECTED", rep.Addr)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *tcpRemoteForwardListener) Addr() net.Addr {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
func (l *tcpRemoteForwardListener) Close() error {
|
||||
close(l.closed)
|
||||
return nil
|
||||
}
|
||||
|
||||
type udpRemoteForwardListener struct {
|
||||
addr *net.UDPAddr
|
||||
chain *Chain
|
||||
conns map[string]*udpServerConn
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
ttl time.Duration
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
// UDPRemoteForwardListener creates a Listener for UDP remote port forwarding server.
|
||||
func UDPRemoteForwardListener(addr string, chain *Chain, ttl time.Duration) (Listener, error) {
|
||||
laddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ln := &udpRemoteForwardListener{
|
||||
addr: laddr,
|
||||
chain: chain,
|
||||
conns: make(map[string]*udpServerConn),
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
ttl: ttl,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
go ln.listenLoop()
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (l *udpRemoteForwardListener) listenLoop() {
|
||||
for {
|
||||
conn, err := l.connect()
|
||||
if err != nil {
|
||||
log.Logf("[rudp] %s : %s", l.Addr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
b := make([]byte, mediumBufferSize)
|
||||
n, raddr, err := conn.ReadFrom(b)
|
||||
if err != nil {
|
||||
log.Logf("[rudp] %s : %s", l.Addr(), err)
|
||||
break
|
||||
}
|
||||
if Debug {
|
||||
log.Logf("[udp] %s >>> %s : length %d", raddr, l.Addr(), n)
|
||||
}
|
||||
uc, ok := l.conns[raddr.String()]
|
||||
if !ok || uc.Closed() {
|
||||
uc = newUDPServerConn(conn, raddr, l.ttl)
|
||||
l.conns[raddr.String()] = uc
|
||||
|
||||
select {
|
||||
case l.connChan <- uc:
|
||||
default:
|
||||
uc.Close()
|
||||
log.Logf("[rudp] %s - %s: connection queue is full", raddr, l.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case uc.rChan <- b[:n]:
|
||||
default:
|
||||
log.Logf("[rudp] %s -> %s : write queue is full", raddr, l.Addr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (l *udpRemoteForwardListener) connect() (conn net.PacketConn, err error) {
|
||||
var tempDelay time.Duration
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-l.closed:
|
||||
return nil, errors.New("closed")
|
||||
default:
|
||||
}
|
||||
|
||||
lastNode := l.chain.LastNode()
|
||||
if lastNode.Protocol == "socks5" {
|
||||
var cc net.Conn
|
||||
cc, err = getSOCKS5UDPTunnel(l.chain, l.addr)
|
||||
if err != nil {
|
||||
log.Logf("[rudp] %s : %s", l.Addr(), err)
|
||||
} else {
|
||||
conn = &udpTunnelConn{Conn: cc}
|
||||
}
|
||||
} else {
|
||||
conn, err = net.ListenUDP("udp", l.addr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 1000 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 6 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Logf("[rudp] Accept error: %v; retrying in %v", err, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (l *udpRemoteForwardListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *udpRemoteForwardListener) Addr() net.Addr {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
func (l *udpRemoteForwardListener) Close() error {
|
||||
close(l.closed)
|
||||
return nil
|
||||
}
|
108
gost/gost.go
108
gost/gost.go
@ -1,108 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
// Version is the gost version.
|
||||
const Version = "2.4-dev20170803"
|
||||
|
||||
// Debug is a flag that enables the debug log.
|
||||
var Debug bool
|
||||
|
||||
var (
|
||||
tinyBufferSize = 128
|
||||
smallBufferSize = 1 * 1024 // 1KB small buffer
|
||||
mediumBufferSize = 8 * 1024 // 8KB medium buffer
|
||||
largeBufferSize = 32 * 1024 // 32KB large buffer
|
||||
)
|
||||
|
||||
var (
|
||||
// KeepAliveTime is the keep alive time period for TCP connection.
|
||||
KeepAliveTime = 180 * time.Second
|
||||
// DialTimeout is the timeout of dial.
|
||||
DialTimeout = 30 * time.Second
|
||||
// ReadTimeout is the timeout for reading.
|
||||
ReadTimeout = 30 * time.Second
|
||||
// WriteTimeout is the timeout for writing.
|
||||
WriteTimeout = 60 * time.Second
|
||||
// PingTimeout is the timeout for pinging.
|
||||
PingTimeout = 30 * time.Second
|
||||
// PingRetries is the reties of ping.
|
||||
PingRetries = 3
|
||||
// default udp node TTL in second for udp port forwarding.
|
||||
defaultTTL = 60 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
defaultRawCert []byte
|
||||
defaultRawKey []byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
rawCert, rawKey, err := generateKeyPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defaultRawCert, defaultRawKey = rawCert, rawKey
|
||||
|
||||
log.DefaultLogger = &LogLogger{}
|
||||
}
|
||||
|
||||
func SetLogger(logger log.Logger) {
|
||||
log.DefaultLogger = logger
|
||||
}
|
||||
|
||||
func generateKeyPair() (rawCert, rawKey []byte, err error) {
|
||||
if defaultRawCert != nil && defaultRawKey != nil {
|
||||
return defaultRawCert, defaultRawKey, nil
|
||||
}
|
||||
|
||||
// Create private key and self-signed certificate
|
||||
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
validFor := time.Hour * 24 * 365 * 10 // ten years
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(validFor)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"gost"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetDefaultCertificate replaces the default certificate by your own
|
||||
func SetDefaultCertificate(rawCert, rawKey []byte) {
|
||||
defaultRawCert = rawCert
|
||||
defaultRawKey = rawKey
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Handler is a proxy server handler
|
||||
type Handler interface {
|
||||
Handle(net.Conn)
|
||||
}
|
||||
|
||||
// HandlerOptions describes the options for Handler.
|
||||
type HandlerOptions struct {
|
||||
Addr string
|
||||
Chain *Chain
|
||||
Users []*url.Userinfo
|
||||
TLSConfig *tls.Config
|
||||
Whitelist *Permissions
|
||||
Blacklist *Permissions
|
||||
}
|
||||
|
||||
// HandlerOption allows a common way to set handler options.
|
||||
type HandlerOption func(opts *HandlerOptions)
|
||||
|
||||
// AddrHandlerOption sets the Addr option of HandlerOptions.
|
||||
func AddrHandlerOption(addr string) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Addr = addr
|
||||
}
|
||||
}
|
||||
|
||||
// ChainHandlerOption sets the Chain option of HandlerOptions.
|
||||
func ChainHandlerOption(chain *Chain) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Chain = chain
|
||||
}
|
||||
}
|
||||
|
||||
// UsersHandlerOption sets the Users option of HandlerOptions.
|
||||
func UsersHandlerOption(users ...*url.Userinfo) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Users = users
|
||||
}
|
||||
}
|
||||
|
||||
// TLSConfigHandlerOption sets the TLSConfig option of HandlerOptions.
|
||||
func TLSConfigHandlerOption(config *tls.Config) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.TLSConfig = config
|
||||
}
|
||||
}
|
||||
|
||||
// WhitelistHandlerOption sets the Whitelist option of HandlerOptions.
|
||||
func WhitelistHandlerOption(whitelist *Permissions) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Whitelist = whitelist
|
||||
}
|
||||
}
|
||||
|
||||
// BlacklistHandlerOption sets the Blacklist option of HandlerOptions.
|
||||
func BlacklistHandlerOption(blacklist *Permissions) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Blacklist = blacklist
|
||||
}
|
||||
}
|
259
gost/http.go
259
gost/http.go
@ -1,259 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
type httpConnector struct {
|
||||
User *url.Userinfo
|
||||
}
|
||||
|
||||
// HTTPConnector creates a Connector for HTTP proxy client.
|
||||
// It accepts an optional auth info for HTTP Basic Authentication.
|
||||
func HTTPConnector(user *url.Userinfo) Connector {
|
||||
return &httpConnector{User: user}
|
||||
}
|
||||
|
||||
func (c *httpConnector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
||||
req := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{Host: addr},
|
||||
Host: addr,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
req.Header.Set("Proxy-Connection", "keep-alive")
|
||||
|
||||
if c.User != nil {
|
||||
s := c.User.String()
|
||||
if _, set := c.User.Password(); !set {
|
||||
s += ":"
|
||||
}
|
||||
req.Header.Set("Proxy-Authorization",
|
||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
|
||||
}
|
||||
|
||||
if err := req.Write(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if Debug {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Log(string(dump))
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if Debug {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
log.Log(string(dump))
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s", resp.Status)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
type httpHandler struct {
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// HTTPHandler creates a server Handler for HTTP proxy server.
|
||||
func HTTPHandler(opts ...HandlerOption) Handler {
|
||||
h := &httpHandler{
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *httpHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(conn))
|
||||
if err != nil {
|
||||
log.Logf("[http] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
if Debug {
|
||||
log.Logf("[http] %s %s - %s %s", req.Method, conn.RemoteAddr(), req.Host, req.Proto)
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Logf(string(dump))
|
||||
}
|
||||
|
||||
if req.Method == "PRI" && req.ProtoMajor == 2 {
|
||||
log.Logf("[http] %s <- %s : Not an HTTP2 server", conn.RemoteAddr(), req.Host)
|
||||
resp := "HTTP/1.1 400 Bad Request\r\n" +
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n"
|
||||
conn.Write([]byte(resp))
|
||||
return
|
||||
}
|
||||
|
||||
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
|
||||
if !authenticate(u, p, h.options.Users...) {
|
||||
log.Logf("[http] %s <- %s : proxy authentication required", conn.RemoteAddr(), req.Host)
|
||||
resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" +
|
||||
"Proxy-Authenticate: Basic realm=\"gost\"\r\n" +
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n"
|
||||
conn.Write([]byte(resp))
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Del("Proxy-Authorization")
|
||||
req.Header.Del("Proxy-Connection")
|
||||
|
||||
if !Can("tcp", req.Host, h.options.Whitelist, h.options.Blacklist) {
|
||||
log.Logf("[http] Unauthorized to tcp connect to %s", req.Host)
|
||||
b := []byte("HTTP/1.1 403 Forbidden\r\n" +
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
||||
conn.Write(b)
|
||||
if Debug {
|
||||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// forward http request
|
||||
lastNode := h.options.Chain.LastNode()
|
||||
if req.Method != http.MethodConnect && lastNode.Protocol == "http" {
|
||||
h.forwardRequest(conn, req)
|
||||
return
|
||||
}
|
||||
|
||||
host := req.Host
|
||||
if !strings.Contains(req.Host, ":") {
|
||||
host += ":80"
|
||||
}
|
||||
cc, err := h.options.Chain.Dial(host)
|
||||
if err != nil {
|
||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
||||
|
||||
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
||||
if Debug {
|
||||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
|
||||
}
|
||||
conn.Write(b)
|
||||
return
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
if req.Method == http.MethodConnect {
|
||||
b := []byte("HTTP/1.1 200 Connection established\r\n" +
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
||||
if Debug {
|
||||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
|
||||
}
|
||||
conn.Write(b)
|
||||
} else {
|
||||
req.Header.Del("Proxy-Connection")
|
||||
|
||||
if err = req.Write(cc); err != nil {
|
||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Logf("[http] %s <-> %s", conn.RemoteAddr(), req.Host)
|
||||
transport(conn, cc)
|
||||
log.Logf("[http] %s >-< %s", conn.RemoteAddr(), req.Host)
|
||||
}
|
||||
|
||||
func (h *httpHandler) forwardRequest(conn net.Conn, req *http.Request) {
|
||||
if h.options.Chain.IsEmpty() {
|
||||
return
|
||||
}
|
||||
lastNode := h.options.Chain.LastNode()
|
||||
|
||||
cc, err := h.options.Chain.Conn()
|
||||
if err != nil {
|
||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), lastNode.Addr, err)
|
||||
|
||||
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
||||
if Debug {
|
||||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), lastNode.Addr, string(b))
|
||||
}
|
||||
conn.Write(b)
|
||||
return
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
if lastNode.User != nil {
|
||||
s := lastNode.User.String()
|
||||
if _, set := lastNode.User.Password(); !set {
|
||||
s += ":"
|
||||
}
|
||||
req.Header.Set("Proxy-Authorization",
|
||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
|
||||
}
|
||||
|
||||
cc.SetWriteDeadline(time.Now().Add(WriteTimeout))
|
||||
if err = req.WriteProxy(cc); err != nil {
|
||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
||||
return
|
||||
}
|
||||
cc.SetWriteDeadline(time.Time{})
|
||||
|
||||
log.Logf("[http] %s <-> %s", conn.RemoteAddr(), req.Host)
|
||||
transport(conn, cc)
|
||||
log.Logf("[http] %s >-< %s", conn.RemoteAddr(), req.Host)
|
||||
return
|
||||
}
|
||||
|
||||
func basicProxyAuth(proxyAuth string) (username, password string, ok bool) {
|
||||
if proxyAuth == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(proxyAuth, "Basic ") {
|
||||
return
|
||||
}
|
||||
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(proxyAuth, "Basic "))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cs := string(c)
|
||||
s := strings.IndexByte(cs, ':')
|
||||
if s < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return cs[:s], cs[s+1:], true
|
||||
}
|
||||
|
||||
func authenticate(username, password string, users ...*url.Userinfo) bool {
|
||||
if len(users) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
u := user.Username()
|
||||
p, _ := user.Password()
|
||||
if (u == username && p == password) ||
|
||||
(u == username && p == "") ||
|
||||
(u == "" && p == password) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
517
gost/kcp.go
517
gost/kcp.go
@ -1,517 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/go-log/log"
|
||||
"github.com/klauspost/compress/snappy"
|
||||
"gopkg.in/xtaci/kcp-go.v2"
|
||||
"gopkg.in/xtaci/smux.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
// KCPSalt is the default salt for KCP cipher.
|
||||
KCPSalt = "kcp-go"
|
||||
)
|
||||
|
||||
// KCPConfig describes the config for KCP.
|
||||
type KCPConfig struct {
|
||||
Key string `json:"key"`
|
||||
Crypt string `json:"crypt"`
|
||||
Mode string `json:"mode"`
|
||||
MTU int `json:"mtu"`
|
||||
SndWnd int `json:"sndwnd"`
|
||||
RcvWnd int `json:"rcvwnd"`
|
||||
DataShard int `json:"datashard"`
|
||||
ParityShard int `json:"parityshard"`
|
||||
DSCP int `json:"dscp"`
|
||||
NoComp bool `json:"nocomp"`
|
||||
AckNodelay bool `json:"acknodelay"`
|
||||
NoDelay int `json:"nodelay"`
|
||||
Interval int `json:"interval"`
|
||||
Resend int `json:"resend"`
|
||||
NoCongestion int `json:"nc"`
|
||||
SockBuf int `json:"sockbuf"`
|
||||
KeepAlive int `json:"keepalive"`
|
||||
SnmpLog string `json:"snmplog"`
|
||||
SnmpPeriod int `json:"snmpperiod"`
|
||||
Signal bool `json:"signal"` // Signal enables the signal SIGUSR1 feature.
|
||||
}
|
||||
|
||||
// Init initializes the KCP config.
|
||||
func (c *KCPConfig) Init() {
|
||||
switch c.Mode {
|
||||
case "normal":
|
||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 50, 2, 1
|
||||
case "fast2":
|
||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 30, 2, 1
|
||||
case "fast3":
|
||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1
|
||||
case "fast":
|
||||
fallthrough
|
||||
default:
|
||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 40, 2, 1
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultKCPConfig is the default KCP config.
|
||||
DefaultKCPConfig = &KCPConfig{
|
||||
Key: "it's a secrect",
|
||||
Crypt: "aes",
|
||||
Mode: "fast",
|
||||
MTU: 1350,
|
||||
SndWnd: 1024,
|
||||
RcvWnd: 1024,
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
NoComp: false,
|
||||
AckNodelay: false,
|
||||
NoDelay: 0,
|
||||
Interval: 50,
|
||||
Resend: 0,
|
||||
NoCongestion: 0,
|
||||
SockBuf: 4194304,
|
||||
KeepAlive: 10,
|
||||
SnmpLog: "",
|
||||
SnmpPeriod: 60,
|
||||
Signal: false,
|
||||
}
|
||||
)
|
||||
|
||||
type kcpConn struct {
|
||||
conn net.Conn
|
||||
stream *smux.Stream
|
||||
}
|
||||
|
||||
func (c *kcpConn) Read(b []byte) (n int, err error) {
|
||||
return c.stream.Read(b)
|
||||
}
|
||||
|
||||
func (c *kcpConn) Write(b []byte) (n int, err error) {
|
||||
return c.stream.Write(b)
|
||||
}
|
||||
|
||||
func (c *kcpConn) Close() error {
|
||||
return c.stream.Close()
|
||||
}
|
||||
|
||||
func (c *kcpConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *kcpConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *kcpConn) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *kcpConn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *kcpConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type kcpSession struct {
|
||||
conn net.Conn
|
||||
session *smux.Session
|
||||
}
|
||||
|
||||
func (session *kcpSession) GetConn() (*kcpConn, error) {
|
||||
stream, err := session.session.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &kcpConn{conn: session.conn, stream: stream}, nil
|
||||
}
|
||||
|
||||
func (session *kcpSession) Close() error {
|
||||
return session.session.Close()
|
||||
}
|
||||
|
||||
func (session *kcpSession) IsClosed() bool {
|
||||
return session.session.IsClosed()
|
||||
}
|
||||
|
||||
func (session *kcpSession) NumStreams() int {
|
||||
return session.session.NumStreams()
|
||||
}
|
||||
|
||||
type kcpTransporter struct {
|
||||
sessions map[string]*kcpSession
|
||||
sessionMutex sync.Mutex
|
||||
config *KCPConfig
|
||||
}
|
||||
|
||||
// KCPTransporter creates a Transporter that is used by KCP proxy client.
|
||||
func KCPTransporter(config *KCPConfig) Transporter {
|
||||
if config == nil {
|
||||
config = DefaultKCPConfig
|
||||
}
|
||||
config.Init()
|
||||
|
||||
go snmpLogger(config.SnmpLog, config.SnmpPeriod)
|
||||
if config.Signal {
|
||||
go kcpSigHandler()
|
||||
}
|
||||
|
||||
return &kcpTransporter{
|
||||
config: config,
|
||||
sessions: make(map[string]*kcpSession),
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *kcpTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) {
|
||||
uaddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[addr]
|
||||
if !ok {
|
||||
conn, err = net.DialUDP("udp", nil, uaddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session = &kcpSession{conn: conn}
|
||||
tr.sessions[addr] = session
|
||||
}
|
||||
return session.conn, nil
|
||||
}
|
||||
|
||||
func (tr *kcpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
config := tr.config
|
||||
if opts.KCPConfig != nil {
|
||||
config = opts.KCPConfig
|
||||
}
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[opts.Addr]
|
||||
if session != nil && session.conn != conn {
|
||||
conn.Close()
|
||||
return nil, errors.New("kcp: unrecognized connection")
|
||||
}
|
||||
if !ok || session.session == nil {
|
||||
s, err := tr.initSession(opts.Addr, conn, config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
session = s
|
||||
tr.sessions[opts.Addr] = session
|
||||
}
|
||||
cc, err := session.GetConn()
|
||||
if err != nil {
|
||||
session.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPConfig) (*kcpSession, error) {
|
||||
udpConn, ok := conn.(*net.UDPConn)
|
||||
if !ok {
|
||||
return nil, errors.New("kcp: wrong connection type")
|
||||
}
|
||||
|
||||
kcpconn, err := kcp.NewConn(addr,
|
||||
blockCrypt(config.Key, config.Crypt, KCPSalt),
|
||||
config.DataShard, config.ParityShard,
|
||||
&kcp.ConnectedUDPConn{UDPConn: udpConn, Conn: udpConn})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kcpconn.SetStreamMode(true)
|
||||
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion)
|
||||
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd)
|
||||
kcpconn.SetMtu(config.MTU)
|
||||
kcpconn.SetACKNoDelay(config.AckNodelay)
|
||||
kcpconn.SetKeepAlive(config.KeepAlive)
|
||||
|
||||
if err := kcpconn.SetDSCP(config.DSCP); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
|
||||
// stream multiplex
|
||||
smuxConfig := smux.DefaultConfig()
|
||||
smuxConfig.MaxReceiveBuffer = config.SockBuf
|
||||
var cc net.Conn = kcpconn
|
||||
if !config.NoComp {
|
||||
cc = newCompStreamConn(kcpconn)
|
||||
}
|
||||
session, err := smux.Client(cc, smuxConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &kcpSession{conn: conn, session: session}, nil
|
||||
}
|
||||
|
||||
func (tr *kcpTransporter) Multiplex() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type kcpListener struct {
|
||||
config *KCPConfig
|
||||
ln *kcp.Listener
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// KCPListener creates a Listener for KCP proxy server.
|
||||
func KCPListener(addr string, config *KCPConfig) (Listener, error) {
|
||||
if config == nil {
|
||||
config = DefaultKCPConfig
|
||||
}
|
||||
config.Init()
|
||||
|
||||
ln, err := kcp.ListenWithOptions(addr,
|
||||
blockCrypt(config.Key, config.Crypt, KCPSalt), config.DataShard, config.ParityShard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = ln.SetDSCP(config.DSCP); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
if err = ln.SetReadBuffer(config.SockBuf); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
if err = ln.SetWriteBuffer(config.SockBuf); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
|
||||
go snmpLogger(config.SnmpLog, config.SnmpPeriod)
|
||||
if config.Signal {
|
||||
go kcpSigHandler()
|
||||
}
|
||||
|
||||
l := &kcpListener{
|
||||
config: config,
|
||||
ln: ln,
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
}
|
||||
go l.listenLoop()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *kcpListener) listenLoop() {
|
||||
for {
|
||||
conn, err := l.ln.AcceptKCP()
|
||||
if err != nil {
|
||||
log.Log("[kcp] accept:", err)
|
||||
l.errChan <- err
|
||||
close(l.errChan)
|
||||
return
|
||||
}
|
||||
conn.SetStreamMode(true)
|
||||
conn.SetNoDelay(l.config.NoDelay, l.config.Interval, l.config.Resend, l.config.NoCongestion)
|
||||
conn.SetMtu(l.config.MTU)
|
||||
conn.SetWindowSize(l.config.SndWnd, l.config.RcvWnd)
|
||||
conn.SetACKNoDelay(l.config.AckNodelay)
|
||||
conn.SetKeepAlive(l.config.KeepAlive)
|
||||
go l.mux(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *kcpListener) mux(conn net.Conn) {
|
||||
smuxConfig := smux.DefaultConfig()
|
||||
smuxConfig.MaxReceiveBuffer = l.config.SockBuf
|
||||
|
||||
log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr())
|
||||
|
||||
if !l.config.NoComp {
|
||||
conn = newCompStreamConn(conn)
|
||||
}
|
||||
|
||||
mux, err := smux.Server(conn, smuxConfig)
|
||||
if err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
return
|
||||
}
|
||||
defer mux.Close()
|
||||
|
||||
log.Logf("[kcp] %s <-> %s", conn.RemoteAddr(), l.Addr())
|
||||
defer log.Logf("[kcp] %s >-< %s", conn.RemoteAddr(), l.Addr())
|
||||
|
||||
for {
|
||||
stream, err := mux.AcceptStream()
|
||||
if err != nil {
|
||||
log.Log("[kcp] accept stream:", err)
|
||||
return
|
||||
}
|
||||
|
||||
cc := &kcpConn{conn: conn, stream: stream}
|
||||
select {
|
||||
case l.connChan <- cc:
|
||||
default:
|
||||
cc.Close()
|
||||
log.Logf("[kcp] %s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *kcpListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (l *kcpListener) Addr() net.Addr {
|
||||
return l.ln.Addr()
|
||||
}
|
||||
|
||||
func (l *kcpListener) Close() error {
|
||||
return l.ln.Close()
|
||||
}
|
||||
|
||||
func blockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
|
||||
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New)
|
||||
|
||||
switch crypt {
|
||||
case "tea":
|
||||
block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
||||
case "xor":
|
||||
block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
||||
case "none":
|
||||
block, _ = kcp.NewNoneBlockCrypt(pass)
|
||||
case "aes-128":
|
||||
block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
||||
case "aes-192":
|
||||
block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
||||
case "blowfish":
|
||||
block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
||||
case "twofish":
|
||||
block, _ = kcp.NewTwofishBlockCrypt(pass)
|
||||
case "cast5":
|
||||
block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
||||
case "3des":
|
||||
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
||||
case "xtea":
|
||||
block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
||||
case "salsa20":
|
||||
block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||
case "aes":
|
||||
fallthrough
|
||||
default: // aes
|
||||
block, _ = kcp.NewAESBlockCrypt(pass)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func snmpLogger(format string, interval int) {
|
||||
if format == "" || interval == 0 {
|
||||
return
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
f, err := os.OpenFile(time.Now().Format(format), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
return
|
||||
}
|
||||
w := csv.NewWriter(f)
|
||||
// write header in empty file
|
||||
if stat, err := f.Stat(); err == nil && stat.Size() == 0 {
|
||||
if err := w.Write(append([]string{"Unix"}, kcp.DefaultSnmp.Header()...)); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
}
|
||||
if err := w.Write(append([]string{fmt.Sprint(time.Now().Unix())}, kcp.DefaultSnmp.ToSlice()...)); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
kcp.DefaultSnmp.Reset()
|
||||
w.Flush()
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type compStreamConn struct {
|
||||
conn net.Conn
|
||||
w *snappy.Writer
|
||||
r *snappy.Reader
|
||||
}
|
||||
|
||||
func newCompStreamConn(conn net.Conn) *compStreamConn {
|
||||
c := new(compStreamConn)
|
||||
c.conn = conn
|
||||
c.w = snappy.NewBufferedWriter(conn)
|
||||
c.r = snappy.NewReader(conn)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *compStreamConn) Read(b []byte) (n int, err error) {
|
||||
return c.r.Read(b)
|
||||
}
|
||||
|
||||
func (c *compStreamConn) Write(b []byte) (n int, err error) {
|
||||
n, err = c.w.Write(b)
|
||||
err = c.w.Flush()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *compStreamConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *compStreamConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *compStreamConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *compStreamConn) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *compStreamConn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *compStreamConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
95
gost/node.go
95
gost/node.go
@ -1,95 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
// Node is a proxy node, mainly used to construct a proxy chain.
|
||||
type Node struct {
|
||||
Addr string
|
||||
Protocol string
|
||||
Transport string
|
||||
Remote string // remote address, used by tcp/udp port forwarding
|
||||
User *url.Userinfo
|
||||
Chain *Chain
|
||||
Values url.Values
|
||||
Client *Client
|
||||
DialOptions []DialOption
|
||||
HandshakeOptions []HandshakeOption
|
||||
}
|
||||
|
||||
func ParseNode(s string) (node Node, err error) {
|
||||
if !strings.Contains(s, "://") {
|
||||
s = "auto://" + s
|
||||
}
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
node = Node{
|
||||
Addr: u.Host,
|
||||
Values: u.Query(),
|
||||
User: u.User,
|
||||
}
|
||||
|
||||
schemes := strings.Split(u.Scheme, "+")
|
||||
if len(schemes) == 1 {
|
||||
node.Protocol = schemes[0]
|
||||
node.Transport = schemes[0]
|
||||
}
|
||||
if len(schemes) == 2 {
|
||||
node.Protocol = schemes[0]
|
||||
node.Transport = schemes[1]
|
||||
}
|
||||
|
||||
switch node.Transport {
|
||||
case "tls", "ws", "wss", "kcp", "ssh", "quic", "ssu", "http2", "h2", "h2c", "redirect":
|
||||
case "https":
|
||||
node.Protocol = "http"
|
||||
node.Transport = "tls"
|
||||
case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding
|
||||
node.Remote = strings.Trim(u.EscapedPath(), "/")
|
||||
case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding
|
||||
node.Remote = strings.Trim(u.EscapedPath(), "/")
|
||||
default:
|
||||
node.Transport = ""
|
||||
}
|
||||
|
||||
switch node.Protocol {
|
||||
case "http", "http2", "socks4", "socks4a", "socks", "socks5", "ss", "ssu":
|
||||
case "tcp", "udp", "rtcp", "rudp": // port forwarding
|
||||
case "direct", "remote", "forward": // SSH port forwarding
|
||||
default:
|
||||
node.Protocol = ""
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Can(action string, addr string, whitelist, blacklist *Permissions) bool {
|
||||
if !strings.Contains(addr, ":") {
|
||||
addr = addr + ":80"
|
||||
}
|
||||
host, strport, err := net.SplitHostPort(addr)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(strport)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if Debug {
|
||||
log.Logf("Can action: %s, host: %s, port %d", action, host, port)
|
||||
}
|
||||
return whitelist.Can(action, host, port) && !blacklist.Can(action, host, port)
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
glob "github.com/ryanuber/go-glob"
|
||||
)
|
||||
|
||||
type Permission struct {
|
||||
Actions StringSet
|
||||
Hosts StringSet
|
||||
Ports PortSet
|
||||
}
|
||||
|
||||
type Permissions []Permission
|
||||
|
||||
func minint(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func maxint(x, y int) int {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
type PortRange struct {
|
||||
Min, Max int
|
||||
}
|
||||
|
||||
func (ir *PortRange) Contains(value int) bool {
|
||||
return value >= ir.Min && value <= ir.Max
|
||||
}
|
||||
|
||||
func ParsePortRange(s string) (*PortRange, error) {
|
||||
if s == "*" {
|
||||
return &PortRange{Min: 0, Max: 65535}, nil
|
||||
}
|
||||
|
||||
minmax := strings.Split(s, "-")
|
||||
switch len(minmax) {
|
||||
case 1:
|
||||
port, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if port < 0 || port > 65535 {
|
||||
return nil, fmt.Errorf("invalid port: %s", s)
|
||||
}
|
||||
return &PortRange{Min: port, Max: port}, nil
|
||||
case 2:
|
||||
min, err := strconv.Atoi(minmax[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
max, err := strconv.Atoi(minmax[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
realmin := maxint(0, minint(min, max))
|
||||
realmax := minint(65535, maxint(min, max))
|
||||
|
||||
return &PortRange{Min: realmin, Max: realmax}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid range: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *PortSet) Contains(value int) bool {
|
||||
for _, portRange := range *ps {
|
||||
if portRange.Contains(value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type PortSet []PortRange
|
||||
|
||||
func ParsePortSet(s string) (*PortSet, error) {
|
||||
ps := &PortSet{}
|
||||
|
||||
if s == "" {
|
||||
return nil, errors.New("must specify at least one port")
|
||||
}
|
||||
|
||||
ranges := strings.Split(s, ",")
|
||||
|
||||
for _, r := range ranges {
|
||||
portRange, err := ParsePortRange(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
*ps = append(*ps, *portRange)
|
||||
}
|
||||
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func (ss *StringSet) Contains(subj string) bool {
|
||||
for _, s := range *ss {
|
||||
if glob.Glob(s, subj) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type StringSet []string
|
||||
|
||||
func ParseStringSet(s string) (*StringSet, error) {
|
||||
ss := &StringSet{}
|
||||
if s == "" {
|
||||
return nil, errors.New("cannot be empty")
|
||||
}
|
||||
|
||||
*ss = strings.Split(s, ",")
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
func (ps *Permissions) Can(action string, host string, port int) bool {
|
||||
for _, p := range *ps {
|
||||
if p.Actions.Contains(action) && p.Hosts.Contains(host) && p.Ports.Contains(port) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func ParsePermissions(s string) (*Permissions, error) {
|
||||
ps := &Permissions{}
|
||||
|
||||
if s == "" {
|
||||
return &Permissions{}, nil
|
||||
}
|
||||
|
||||
perms := strings.Split(s, " ")
|
||||
|
||||
for _, perm := range perms {
|
||||
parts := strings.Split(perm, ":")
|
||||
|
||||
switch len(parts) {
|
||||
case 3:
|
||||
actions, err := ParseStringSet(parts[0])
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("action list must look like connect,bind given: %s", parts[0])
|
||||
}
|
||||
|
||||
hosts, err := ParseStringSet(parts[1])
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hosts list must look like google.pl,*.google.com given: %s", parts[1])
|
||||
}
|
||||
|
||||
ports, err := ParsePortSet(parts[2])
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ports list must look like 80,8000-9000, given: %s", parts[2])
|
||||
}
|
||||
|
||||
permission := Permission{Actions: *actions, Hosts: *hosts, Ports: *ports}
|
||||
|
||||
*ps = append(*ps, permission)
|
||||
default:
|
||||
return nil, fmt.Errorf("permission must have format [actions]:[hosts]:[ports] given: %s", perm)
|
||||
}
|
||||
}
|
||||
|
||||
return ps, nil
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var portRangeTests = []struct {
|
||||
in string
|
||||
out *PortRange
|
||||
}{
|
||||
{"1", &PortRange{Min: 1, Max: 1}},
|
||||
{"1-3", &PortRange{Min: 1, Max: 3}},
|
||||
{"3-1", &PortRange{Min: 1, Max: 3}},
|
||||
{"0-100000", &PortRange{Min: 0, Max: 65535}},
|
||||
{"*", &PortRange{Min: 0, Max: 65535}},
|
||||
}
|
||||
|
||||
var stringSetTests = []struct {
|
||||
in string
|
||||
out *StringSet
|
||||
}{
|
||||
{"*", &StringSet{"*"}},
|
||||
{"google.pl,google.com", &StringSet{"google.pl", "google.com"}},
|
||||
}
|
||||
|
||||
var portSetTests = []struct {
|
||||
in string
|
||||
out *PortSet
|
||||
}{
|
||||
{"1,3", &PortSet{PortRange{Min: 1, Max: 1}, PortRange{Min: 3, Max: 3}}},
|
||||
{"1-3,7-5", &PortSet{PortRange{Min: 1, Max: 3}, PortRange{Min: 5, Max: 7}}},
|
||||
{"0-100000", &PortSet{PortRange{Min: 0, Max: 65535}}},
|
||||
{"*", &PortSet{PortRange{Min: 0, Max: 65535}}},
|
||||
}
|
||||
|
||||
var permissionsTests = []struct {
|
||||
in string
|
||||
out *Permissions
|
||||
}{
|
||||
{"", &Permissions{}},
|
||||
{"*:*:*", &Permissions{
|
||||
Permission{
|
||||
Actions: StringSet{"*"},
|
||||
Hosts: StringSet{"*"},
|
||||
Ports: PortSet{PortRange{Min: 0, Max: 65535}},
|
||||
},
|
||||
}},
|
||||
{"bind:127.0.0.1,localhost:80,443,8000-8100 connect:*.google.pl:80,443", &Permissions{
|
||||
Permission{
|
||||
Actions: StringSet{"bind"},
|
||||
Hosts: StringSet{"127.0.0.1", "localhost"},
|
||||
Ports: PortSet{
|
||||
PortRange{Min: 80, Max: 80},
|
||||
PortRange{Min: 443, Max: 443},
|
||||
PortRange{Min: 8000, Max: 8100},
|
||||
},
|
||||
},
|
||||
Permission{
|
||||
Actions: StringSet{"connect"},
|
||||
Hosts: StringSet{"*.google.pl"},
|
||||
Ports: PortSet{
|
||||
PortRange{Min: 80, Max: 80},
|
||||
PortRange{Min: 443, Max: 443},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
func TestPortRangeParse(t *testing.T) {
|
||||
for _, test := range portRangeTests {
|
||||
actual, err := ParsePortRange(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("ParsePortRange(%q) returned error: %v", test.in, err)
|
||||
} else if *actual != *test.out {
|
||||
t.Errorf("ParsePortRange(%q): got %v, want %v", test.in, actual, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortRangeContains(t *testing.T) {
|
||||
actual, _ := ParsePortRange("5-10")
|
||||
|
||||
if !actual.Contains(5) || !actual.Contains(7) || !actual.Contains(10) {
|
||||
t.Errorf("5-10 should contain 5, 7 and 10")
|
||||
}
|
||||
|
||||
if actual.Contains(4) || actual.Contains(11) {
|
||||
t.Errorf("5-10 should not contain 4, 11")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSetParse(t *testing.T) {
|
||||
for _, test := range stringSetTests {
|
||||
actual, err := ParseStringSet(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("ParseStringSet(%q) returned error: %v", test.in, err)
|
||||
} else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) {
|
||||
t.Errorf("ParseStringSet(%q): got %v, want %v", test.in, actual, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSetContains(t *testing.T) {
|
||||
ss, _ := ParseStringSet("google.pl,*.google.com")
|
||||
|
||||
if !ss.Contains("google.pl") || !ss.Contains("www.google.com") {
|
||||
t.Errorf("google.pl,*.google.com should contain google.pl and www.google.com")
|
||||
}
|
||||
|
||||
if ss.Contains("www.google.pl") || ss.Contains("foobar.com") {
|
||||
t.Errorf("google.pl,*.google.com shound not contain www.google.pl and foobar.com")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortSetParse(t *testing.T) {
|
||||
for _, test := range portSetTests {
|
||||
actual, err := ParsePortSet(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("ParsePortRange(%q) returned error: %v", test.in, err)
|
||||
} else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) {
|
||||
t.Errorf("ParsePortRange(%q): got %v, want %v", test.in, actual, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortSetContains(t *testing.T) {
|
||||
actual, _ := ParsePortSet("5-10,20-30")
|
||||
|
||||
if !actual.Contains(5) || !actual.Contains(7) || !actual.Contains(10) {
|
||||
t.Errorf("5-10,20-30 should contain 5, 7 and 10")
|
||||
}
|
||||
|
||||
if !actual.Contains(20) || !actual.Contains(27) || !actual.Contains(30) {
|
||||
t.Errorf("5-10,20-30 should contain 20, 27 and 30")
|
||||
}
|
||||
|
||||
if actual.Contains(4) || actual.Contains(11) || actual.Contains(31) {
|
||||
t.Errorf("5-10,20-30 should not contain 4, 11, 31")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermissionsParse(t *testing.T) {
|
||||
for _, test := range permissionsTests {
|
||||
actual, err := ParsePermissions(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("ParsePermissions(%q) returned error: %v", test.in, err)
|
||||
} else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) {
|
||||
t.Errorf("ParsePermissions(%q): got %v, want %v", test.in, actual, test.out)
|
||||
}
|
||||
}
|
||||
}
|
237
gost/quic.go
237
gost/quic.go
@ -1,237 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-log/log"
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
)
|
||||
|
||||
type quicSession struct {
|
||||
conn net.Conn
|
||||
session quic.Session
|
||||
}
|
||||
|
||||
func (session *quicSession) GetConn() (*quicConn, error) {
|
||||
stream, err := session.session.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &quicConn{
|
||||
Stream: stream,
|
||||
laddr: session.session.LocalAddr(),
|
||||
raddr: session.session.RemoteAddr(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (session *quicSession) Close() error {
|
||||
return session.session.Close(nil)
|
||||
}
|
||||
|
||||
type quicTransporter struct {
|
||||
config *QUICConfig
|
||||
sessionMutex sync.Mutex
|
||||
sessions map[string]*quicSession
|
||||
}
|
||||
|
||||
// QUICTransporter creates a Transporter that is used by QUIC proxy client.
|
||||
func QUICTransporter(config *QUICConfig) Transporter {
|
||||
if config == nil {
|
||||
config = &QUICConfig{}
|
||||
}
|
||||
return &quicTransporter{
|
||||
config: config,
|
||||
sessions: make(map[string]*quicSession),
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *quicTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) {
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[addr]
|
||||
if !ok {
|
||||
conn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session = &quicSession{conn: conn}
|
||||
tr.sessions[addr] = session
|
||||
}
|
||||
return session.conn, nil
|
||||
}
|
||||
|
||||
func (tr *quicTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
config := tr.config
|
||||
if opts.QUICConfig != nil {
|
||||
config = opts.QUICConfig
|
||||
}
|
||||
if config.TLSConfig == nil {
|
||||
config.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[opts.Addr]
|
||||
if session != nil && session.conn != conn {
|
||||
conn.Close()
|
||||
return nil, errors.New("quic: unrecognized connection")
|
||||
}
|
||||
if !ok || session.session == nil {
|
||||
s, err := tr.initSession(opts.Addr, conn, config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
session = s
|
||||
tr.sessions[opts.Addr] = session
|
||||
}
|
||||
cc, err := session.GetConn()
|
||||
if err != nil {
|
||||
session.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (tr *quicTransporter) initSession(addr string, conn net.Conn, config *QUICConfig) (*quicSession, error) {
|
||||
udpConn, ok := conn.(*net.UDPConn)
|
||||
if !ok {
|
||||
return nil, errors.New("quic: wrong connection type")
|
||||
}
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quicConfig := &quic.Config{
|
||||
HandshakeTimeout: config.Timeout,
|
||||
KeepAlive: config.KeepAlive,
|
||||
}
|
||||
session, err := quic.Dial(udpConn, udpAddr, addr, config.TLSConfig, quicConfig)
|
||||
if err != nil {
|
||||
log.Log("quic dial", err)
|
||||
return nil, err
|
||||
}
|
||||
return &quicSession{conn: conn, session: session}, nil
|
||||
}
|
||||
|
||||
func (tr *quicTransporter) Multiplex() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type QUICConfig struct {
|
||||
TLSConfig *tls.Config
|
||||
Timeout time.Duration
|
||||
KeepAlive bool
|
||||
}
|
||||
|
||||
type quicListener struct {
|
||||
ln quic.Listener
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// QUICListener creates a Listener for QUIC proxy server.
|
||||
func QUICListener(addr string, config *QUICConfig) (Listener, error) {
|
||||
if config == nil {
|
||||
config = &QUICConfig{}
|
||||
}
|
||||
quicConfig := &quic.Config{
|
||||
HandshakeTimeout: config.Timeout,
|
||||
KeepAlive: config.KeepAlive,
|
||||
}
|
||||
|
||||
ln, err := quic.ListenAddr(addr, config.TLSConfig, quicConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := &quicListener{
|
||||
ln: ln,
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
}
|
||||
go l.listenLoop()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *quicListener) listenLoop() {
|
||||
for {
|
||||
session, err := l.ln.Accept()
|
||||
if err != nil {
|
||||
log.Log("[quic] accept:", err)
|
||||
l.errChan <- err
|
||||
close(l.errChan)
|
||||
return
|
||||
}
|
||||
go l.sessionLoop(session)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *quicListener) sessionLoop(session quic.Session) {
|
||||
log.Logf("[quic] %s <-> %s", session.RemoteAddr(), session.LocalAddr())
|
||||
defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr())
|
||||
|
||||
for {
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
log.Log("[quic] accept stream:", err)
|
||||
return
|
||||
}
|
||||
|
||||
cc := &quicConn{Stream: stream, laddr: session.LocalAddr(), raddr: session.RemoteAddr()}
|
||||
select {
|
||||
case l.connChan <- cc:
|
||||
default:
|
||||
cc.Close()
|
||||
log.Logf("[quic] %s - %s: connection queue is full", session.RemoteAddr(), session.LocalAddr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *quicListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *quicListener) Addr() net.Addr {
|
||||
return l.ln.Addr()
|
||||
}
|
||||
|
||||
func (l *quicListener) Close() error {
|
||||
return l.ln.Close()
|
||||
}
|
||||
|
||||
type quicConn struct {
|
||||
quic.Stream
|
||||
laddr net.Addr
|
||||
raddr net.Addr
|
||||
}
|
||||
|
||||
func (c *quicConn) LocalAddr() net.Addr {
|
||||
return c.laddr
|
||||
}
|
||||
|
||||
func (c *quicConn) RemoteAddr() net.Addr {
|
||||
return c.raddr
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
type tcpRedirectHandler struct {
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// TCPRedirectHandler creates a server Handler for TCP redirect server.
|
||||
func TCPRedirectHandler(opts ...HandlerOption) Handler {
|
||||
h := &tcpRedirectHandler{
|
||||
options: &HandlerOptions{
|
||||
Chain: new(Chain),
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *tcpRedirectHandler) Handle(c net.Conn) {
|
||||
conn, ok := c.(*net.TCPConn)
|
||||
if !ok {
|
||||
log.Log("[red-tcp] not a TCP connection")
|
||||
}
|
||||
|
||||
srcAddr := conn.RemoteAddr()
|
||||
dstAddr, conn, err := h.getOriginalDstAddr(conn)
|
||||
if err != nil {
|
||||
log.Logf("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
log.Logf("[red-tcp] %s -> %s", srcAddr, dstAddr)
|
||||
|
||||
cc, err := h.options.Chain.Dial(dstAddr.String())
|
||||
if err != nil {
|
||||
log.Logf("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err)
|
||||
return
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
log.Logf("[red-tcp] %s <-> %s", srcAddr, dstAddr)
|
||||
transport(conn, cc)
|
||||
log.Logf("[red-tcp] %s >-< %s", srcAddr, dstAddr)
|
||||
}
|
||||
|
||||
func (h *tcpRedirectHandler) getOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err error) {
|
||||
defer conn.Close()
|
||||
|
||||
fc, err := conn.File()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer fc.Close()
|
||||
|
||||
mreq, err := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, 80)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// only ipv4 support
|
||||
ip := net.IPv4(mreq.Multiaddr[4], mreq.Multiaddr[5], mreq.Multiaddr[6], mreq.Multiaddr[7])
|
||||
port := uint16(mreq.Multiaddr[2])<<8 + uint16(mreq.Multiaddr[3])
|
||||
addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cc, err := net.FileConn(fc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c, ok := cc.(*net.TCPConn)
|
||||
if !ok {
|
||||
err = errors.New("not a TCP connection")
|
||||
}
|
||||
return
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
type tcpRedirectHandler struct {
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// TCPRedirectHandler creates a server Handler for TCP redirect server.
|
||||
func TCPRedirectHandler(opts ...HandlerOption) Handler {
|
||||
h := &tcpRedirectHandler{
|
||||
options: &HandlerOptions{
|
||||
Chain: new(Chain),
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *tcpRedirectHandler) Handle(c net.Conn) {
|
||||
log.Log("[red-tcp] TCP redirect is not available on the Windows platform")
|
||||
c.Close()
|
||||
}
|
104
gost/server.go
104
gost/server.go
@ -1,104 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
// Server is a proxy server.
|
||||
type Server struct {
|
||||
}
|
||||
|
||||
// Serve serves as a proxy server.
|
||||
func (s *Server) Serve(l net.Listener, h Handler) error {
|
||||
defer l.Close()
|
||||
|
||||
if l == nil {
|
||||
ln, err := TCPListener(":8080")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l = ln
|
||||
}
|
||||
if h == nil {
|
||||
h = HTTPHandler()
|
||||
}
|
||||
|
||||
var tempDelay time.Duration
|
||||
for {
|
||||
conn, e := l.Accept()
|
||||
if e != nil {
|
||||
if ne, ok := e.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Logf("server: Accept error: %v; retrying in %v", e, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
return e
|
||||
}
|
||||
tempDelay = 0
|
||||
go h.Handle(conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Listener is a proxy server listener, just like a net.Listener.
|
||||
type Listener interface {
|
||||
net.Listener
|
||||
}
|
||||
|
||||
type tcpListener struct {
|
||||
net.Listener
|
||||
}
|
||||
|
||||
// TCPListener creates a Listener for TCP proxy server.
|
||||
func TCPListener(addr string) (Listener, error) {
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tcpListener{Listener: &tcpKeepAliveListener{ln.(*net.TCPListener)}}, nil
|
||||
}
|
||||
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(KeepAliveTime)
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func transport(rw1, rw2 io.ReadWriter) error {
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := io.Copy(rw1, rw2)
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(rw2, rw1)
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
err := <-errc
|
||||
if err != nil && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package gost
|
||||
|
||||
func kcpSigHandler() {}
|
@ -1,24 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-log/log"
|
||||
"gopkg.in/xtaci/kcp-go.v2"
|
||||
)
|
||||
|
||||
func kcpSigHandler() {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGUSR1)
|
||||
|
||||
for {
|
||||
switch <-ch {
|
||||
case syscall.SIGUSR1:
|
||||
log.Logf("[kcp] SNMP: %+v", kcp.DefaultSnmp.Copy())
|
||||
}
|
||||
}
|
||||
}
|
1166
gost/socks.go
1166
gost/socks.go
File diff suppressed because it is too large
Load Diff
419
gost/ss.go
419
gost/ss.go
@ -1,419 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gosocks5"
|
||||
"github.com/go-log/log"
|
||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||
)
|
||||
|
||||
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
|
||||
// we wrap around it to make io.Copy happy.
|
||||
type shadowConn struct {
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (c *shadowConn) Read(b []byte) (n int, err error) {
|
||||
return c.conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *shadowConn) Write(b []byte) (n int, err error) {
|
||||
n = len(b) // force byte length consistent
|
||||
_, err = c.conn.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *shadowConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *shadowConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *shadowConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *shadowConn) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *shadowConn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *shadowConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type shadowConnector struct {
|
||||
Cipher *url.Userinfo
|
||||
}
|
||||
|
||||
// ShadowConnector creates a Connector for shadowsocks proxy client.
|
||||
// It accepts a cipher info for shadowsocks data encryption/decryption.
|
||||
// The cipher must not be nil.
|
||||
func ShadowConnector(cipher *url.Userinfo) Connector {
|
||||
return &shadowConnector{Cipher: cipher}
|
||||
}
|
||||
|
||||
func (c *shadowConnector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
||||
rawaddr, err := ss.RawAddr(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var method, password string
|
||||
if c.Cipher != nil {
|
||||
method = c.Cipher.Username()
|
||||
password, _ = c.Cipher.Password()
|
||||
}
|
||||
|
||||
cipher, err := ss.NewCipher(method, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sc, err := ss.DialWithRawAddrConn(rawaddr, conn, cipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &shadowConn{conn: sc}, nil
|
||||
}
|
||||
|
||||
type shadowHandler struct {
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// ShadowHandler creates a server Handler for shadowsocks proxy server.
|
||||
func ShadowHandler(opts ...HandlerOption) Handler {
|
||||
h := &shadowHandler{
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *shadowHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
var method, password string
|
||||
users := h.options.Users
|
||||
if len(users) > 0 {
|
||||
method = users[0].Username()
|
||||
password, _ = users[0].Password()
|
||||
}
|
||||
cipher, err := ss.NewCipher(method, password)
|
||||
if err != nil {
|
||||
log.Log("[ss]", err)
|
||||
return
|
||||
}
|
||||
conn = &shadowConn{conn: ss.NewConn(conn, cipher)}
|
||||
|
||||
log.Logf("[ss] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
|
||||
addr, err := h.getRequest(conn)
|
||||
if err != nil {
|
||||
log.Logf("[ss] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
log.Logf("[ss] %s -> %s", conn.RemoteAddr(), addr)
|
||||
|
||||
if !Can("tcp", addr, h.options.Whitelist, h.options.Blacklist) {
|
||||
log.Logf("[ss] Unauthorized to tcp connect to %s", addr)
|
||||
return
|
||||
}
|
||||
|
||||
cc, err := h.options.Chain.Dial(addr)
|
||||
if err != nil {
|
||||
log.Logf("[ss] %s -> %s : %s", conn.RemoteAddr(), addr, err)
|
||||
return
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
log.Logf("[ss] %s <-> %s", conn.RemoteAddr(), addr)
|
||||
transport(conn, cc)
|
||||
log.Logf("[ss] %s >-< %s", conn.RemoteAddr(), addr)
|
||||
}
|
||||
|
||||
const (
|
||||
idType = 0 // address type index
|
||||
idIP0 = 1 // ip addres start index
|
||||
idDmLen = 1 // domain address length index
|
||||
idDm0 = 2 // domain address start index
|
||||
|
||||
typeIPv4 = 1 // type is ipv4 address
|
||||
typeDm = 3 // type is domain address
|
||||
typeIPv6 = 4 // type is ipv6 address
|
||||
|
||||
lenIPv4 = net.IPv4len + 2 // ipv4 + 2port
|
||||
lenIPv6 = net.IPv6len + 2 // ipv6 + 2port
|
||||
lenDmBase = 2 // 1addrLen + 2port, plus addrLen
|
||||
lenHmacSha1 = 10
|
||||
)
|
||||
|
||||
// This function is copied from shadowsocks library with some modification.
|
||||
func (h *shadowHandler) getRequest(conn net.Conn) (host string, err error) {
|
||||
// buf size should at least have the same size with the largest possible
|
||||
// request size (when addrType is 3, domain name has at most 256 bytes)
|
||||
// 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
|
||||
buf := make([]byte, smallBufferSize)
|
||||
|
||||
// read till we get possible domain length field
|
||||
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||
if _, err = io.ReadFull(conn, buf[:idType+1]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var reqStart, reqEnd int
|
||||
addrType := buf[idType]
|
||||
switch addrType & ss.AddrMask {
|
||||
case typeIPv4:
|
||||
reqStart, reqEnd = idIP0, idIP0+lenIPv4
|
||||
case typeIPv6:
|
||||
reqStart, reqEnd = idIP0, idIP0+lenIPv6
|
||||
case typeDm:
|
||||
if _, err = io.ReadFull(conn, buf[idType+1:idDmLen+1]); err != nil {
|
||||
return
|
||||
}
|
||||
reqStart, reqEnd = idDm0, int(idDm0+buf[idDmLen]+lenDmBase)
|
||||
default:
|
||||
err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return string for typeIP is not most efficient, but browsers (Chrome,
|
||||
// Safari, Firefox) all seems using typeDm exclusively. So this is not a
|
||||
// big problem.
|
||||
switch addrType & ss.AddrMask {
|
||||
case typeIPv4:
|
||||
host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
|
||||
case typeIPv6:
|
||||
host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
|
||||
case typeDm:
|
||||
host = string(buf[idDm0 : idDm0+buf[idDmLen]])
|
||||
}
|
||||
// parse port
|
||||
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
|
||||
host = net.JoinHostPort(host, strconv.Itoa(int(port)))
|
||||
return
|
||||
}
|
||||
|
||||
type shadowUDPListener struct {
|
||||
ln net.PacketConn
|
||||
conns map[string]*udpServerConn
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
// ShadowUDPListener creates a Listener for shadowsocks UDP relay server.
|
||||
func ShadowUDPListener(addr string, cipher *url.Userinfo, ttl time.Duration) (Listener, error) {
|
||||
laddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ln, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var method, password string
|
||||
if cipher != nil {
|
||||
method = cipher.Username()
|
||||
password, _ = cipher.Password()
|
||||
}
|
||||
cp, err := ss.NewCipher(method, password)
|
||||
if err != nil {
|
||||
ln.Close()
|
||||
return nil, err
|
||||
}
|
||||
l := &shadowUDPListener{
|
||||
ln: ss.NewSecurePacketConn(ln, cp, false),
|
||||
conns: make(map[string]*udpServerConn),
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
ttl: ttl,
|
||||
}
|
||||
go l.listenLoop()
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *shadowUDPListener) listenLoop() {
|
||||
for {
|
||||
b := make([]byte, mediumBufferSize)
|
||||
n, raddr, err := l.ln.ReadFrom(b)
|
||||
if err != nil {
|
||||
log.Logf("[ssu] peer -> %s : %s", l.Addr(), err)
|
||||
l.ln.Close()
|
||||
l.errChan <- err
|
||||
close(l.errChan)
|
||||
return
|
||||
}
|
||||
if Debug {
|
||||
log.Logf("[ssu] %s >>> %s : length %d", raddr, l.Addr(), n)
|
||||
}
|
||||
|
||||
conn, ok := l.conns[raddr.String()]
|
||||
if !ok || conn.Closed() {
|
||||
conn = newUDPServerConn(l.ln, raddr, l.ttl)
|
||||
l.conns[raddr.String()] = conn
|
||||
|
||||
select {
|
||||
case l.connChan <- conn:
|
||||
default:
|
||||
conn.Close()
|
||||
log.Logf("[ssu] %s - %s: connection queue is full", raddr, l.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case conn.rChan <- b[:n]: // we keep the addr info so that the handler can identify the destination.
|
||||
default:
|
||||
log.Logf("[ssu] %s -> %s : read queue is full", raddr, l.Addr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *shadowUDPListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *shadowUDPListener) Addr() net.Addr {
|
||||
return l.ln.LocalAddr()
|
||||
}
|
||||
|
||||
func (l *shadowUDPListener) Close() error {
|
||||
return l.ln.Close()
|
||||
}
|
||||
|
||||
type shadowUDPdHandler struct {
|
||||
ttl time.Duration
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// ShadowUDPdHandler creates a server Handler for shadowsocks UDP relay server.
|
||||
func ShadowUDPdHandler(opts ...HandlerOption) Handler {
|
||||
h := &shadowUDPdHandler{
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *shadowUDPdHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
var err error
|
||||
var cc net.PacketConn
|
||||
if h.options.Chain.IsEmpty() {
|
||||
cc, err = net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var c net.Conn
|
||||
c, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
|
||||
if err != nil {
|
||||
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
cc = &udpTunnelConn{Conn: c}
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
transportUDP(conn, cc)
|
||||
log.Logf("[ssu] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
|
||||
func transportUDP(sc net.Conn, cc net.PacketConn) error {
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
for {
|
||||
b := make([]byte, mediumBufferSize)
|
||||
n, err := sc.Read(b[3:]) // add rsv and frag fields to make it the standard SOCKS5 UDP datagram
|
||||
if err != nil {
|
||||
// log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err)
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n+3]))
|
||||
if err != nil {
|
||||
log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err)
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
//if Debug {
|
||||
// log.Logf("[ssu] %s >>> %s length: %d", sc.RemoteAddr(), dgram.Header.Addr.String(), len(dgram.Data))
|
||||
//}
|
||||
addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String())
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
if _, err := cc.WriteTo(dgram.Data, addr); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
b := make([]byte, mediumBufferSize)
|
||||
n, addr, err := cc.ReadFrom(b)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
//if Debug {
|
||||
// log.Logf("[ssu] %s <<< %s length: %d", sc.RemoteAddr(), addr, n)
|
||||
//}
|
||||
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, toSocksAddr(addr)), b[:n])
|
||||
buf := bytes.Buffer{}
|
||||
dgram.Write(&buf)
|
||||
if buf.Len() < 10 {
|
||||
log.Logf("[ssu] %s <- %s : invalid udp datagram", sc.RemoteAddr(), addr)
|
||||
continue
|
||||
}
|
||||
if _, err := sc.Write(buf.Bytes()[3:]); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err := <-errc
|
||||
if err != nil && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
834
gost/ssh.go
834
gost/ssh.go
@ -1,834 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-log/log"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Applicaple SSH Request types for Port Forwarding - RFC 4254 7.X
|
||||
const (
|
||||
DirectForwardRequest = "direct-tcpip" // RFC 4254 7.2
|
||||
RemoteForwardRequest = "tcpip-forward" // RFC 4254 7.1
|
||||
ForwardedTCPReturnRequest = "forwarded-tcpip" // RFC 4254 7.2
|
||||
CancelRemoteForwardRequest = "cancel-tcpip-forward" // RFC 4254 7.1
|
||||
|
||||
GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel
|
||||
)
|
||||
|
||||
var (
|
||||
errSessionDead = errors.New("session is dead")
|
||||
)
|
||||
|
||||
type sshDirectForwardConnector struct {
|
||||
}
|
||||
|
||||
func SSHDirectForwardConnector() Connector {
|
||||
return &sshDirectForwardConnector{}
|
||||
}
|
||||
|
||||
func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string) (net.Conn, error) {
|
||||
cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution.
|
||||
if !ok {
|
||||
return nil, errors.New("ssh: wrong connection type")
|
||||
}
|
||||
conn, err := cc.session.client.Dial("tcp", raddr)
|
||||
if err != nil {
|
||||
log.Logf("[ssh-tcp] %s -> %s : %s", cc.session.addr, raddr, err)
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
type sshRemoteForwardConnector struct {
|
||||
}
|
||||
|
||||
func SSHRemoteForwardConnector() Connector {
|
||||
return &sshRemoteForwardConnector{}
|
||||
}
|
||||
|
||||
func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
||||
cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution.
|
||||
if !ok {
|
||||
return nil, errors.New("ssh: wrong connection type")
|
||||
}
|
||||
|
||||
cc.session.once.Do(func() {
|
||||
go func() {
|
||||
defer log.Log("ssh-rtcp: session is closed")
|
||||
defer close(cc.session.connChan)
|
||||
|
||||
if cc.session == nil || cc.session.client == nil {
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(addr, ":") {
|
||||
addr = "0.0.0.0" + addr
|
||||
}
|
||||
ln, err := cc.session.client.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
rc, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Logf("[ssh-rtcp] %s <-> %s accpet : %s", ln.Addr(), addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case cc.session.connChan <- rc:
|
||||
default:
|
||||
rc.Close()
|
||||
log.Logf("[ssh-rtcp] %s - %s: connection queue is full", ln.Addr(), addr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
sc, ok := <-cc.session.connChan
|
||||
if !ok {
|
||||
return nil, errors.New("ssh-rtcp: connection is closed")
|
||||
}
|
||||
return sc, nil
|
||||
}
|
||||
|
||||
type sshForwardTransporter struct {
|
||||
sessions map[string]*sshSession
|
||||
sessionMutex sync.Mutex
|
||||
}
|
||||
|
||||
func SSHForwardTransporter() Transporter {
|
||||
return &sshForwardTransporter{
|
||||
sessions: make(map[string]*sshSession),
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *sshForwardTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) {
|
||||
opts := &DialOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[addr]
|
||||
if !ok || session.Closed() {
|
||||
if opts.Chain == nil {
|
||||
conn, err = net.DialTimeout("tcp", addr, opts.Timeout)
|
||||
} else {
|
||||
conn, err = opts.Chain.Dial(addr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session = &sshSession{
|
||||
addr: addr,
|
||||
conn: conn,
|
||||
}
|
||||
tr.sessions[addr] = session
|
||||
}
|
||||
|
||||
return session.conn, nil
|
||||
}
|
||||
|
||||
func (tr *sshForwardTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
Timeout: opts.Timeout,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
if opts.User != nil {
|
||||
config.User = opts.User.Username()
|
||||
password, _ := opts.User.Password()
|
||||
config.Auth = []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
}
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[opts.Addr]
|
||||
if session != nil && session.conn != conn {
|
||||
conn.Close()
|
||||
return nil, errors.New("ssh: unrecognized connection")
|
||||
}
|
||||
if !ok || session.client == nil {
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session = &sshSession{
|
||||
addr: opts.Addr,
|
||||
conn: conn,
|
||||
client: ssh.NewClient(sshConn, chans, reqs),
|
||||
closed: make(chan struct{}),
|
||||
deaded: make(chan struct{}),
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
}
|
||||
tr.sessions[opts.Addr] = session
|
||||
go session.Ping(opts.Interval, 1)
|
||||
go session.waitServer()
|
||||
go session.waitClose()
|
||||
}
|
||||
if session.Closed() {
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, errSessionDead
|
||||
}
|
||||
|
||||
return &sshNopConn{session: session}, nil
|
||||
}
|
||||
|
||||
func (tr *sshForwardTransporter) Multiplex() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type sshTunnelTransporter struct {
|
||||
sessions map[string]*sshSession
|
||||
sessionMutex sync.Mutex
|
||||
}
|
||||
|
||||
// SSHTunnelTransporter creates a Transporter that is used by SSH tunnel client.
|
||||
func SSHTunnelTransporter() Transporter {
|
||||
return &sshTunnelTransporter{
|
||||
sessions: make(map[string]*sshSession),
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *sshTunnelTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) {
|
||||
opts := &DialOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[addr]
|
||||
if !ok || session.Closed() {
|
||||
if opts.Chain == nil {
|
||||
conn, err = net.DialTimeout("tcp", addr, opts.Timeout)
|
||||
} else {
|
||||
conn, err = opts.Chain.Dial(addr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session = &sshSession{
|
||||
addr: addr,
|
||||
conn: conn,
|
||||
}
|
||||
tr.sessions[addr] = session
|
||||
}
|
||||
|
||||
return session.conn, nil
|
||||
}
|
||||
|
||||
func (tr *sshTunnelTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
Timeout: opts.Timeout,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
if opts.User != nil {
|
||||
config.User = opts.User.Username()
|
||||
password, _ := opts.User.Password()
|
||||
config.Auth = []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
}
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[opts.Addr]
|
||||
if session != nil && session.conn != conn {
|
||||
conn.Close()
|
||||
return nil, errors.New("ssh: unrecognized connection")
|
||||
}
|
||||
if !ok || session.client == nil {
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session = &sshSession{
|
||||
addr: opts.Addr,
|
||||
conn: conn,
|
||||
client: ssh.NewClient(sshConn, chans, reqs),
|
||||
closed: make(chan struct{}),
|
||||
deaded: make(chan struct{}),
|
||||
}
|
||||
tr.sessions[opts.Addr] = session
|
||||
go session.Ping(opts.Interval, 1)
|
||||
go session.waitServer()
|
||||
go session.waitClose()
|
||||
}
|
||||
|
||||
if session.Closed() {
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, errSessionDead
|
||||
}
|
||||
|
||||
channel, reqs, err := session.client.OpenChannel(GostSSHTunnelRequest, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
return &sshConn{channel: channel, conn: conn}, nil
|
||||
}
|
||||
|
||||
func (tr *sshTunnelTransporter) Multiplex() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type sshSession struct {
|
||||
addr string
|
||||
conn net.Conn
|
||||
client *ssh.Client
|
||||
closed chan struct{}
|
||||
deaded chan struct{}
|
||||
once sync.Once
|
||||
connChan chan net.Conn
|
||||
}
|
||||
|
||||
func (s *sshSession) Ping(interval time.Duration, retries int) {
|
||||
if interval <= 0 {
|
||||
return
|
||||
}
|
||||
defer close(s.deaded)
|
||||
|
||||
log.Log("[ssh] ping is enabled, interval:", interval)
|
||||
baseCtx := context.Background()
|
||||
t := time.NewTicker(interval)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
start := time.Now()
|
||||
//if Debug {
|
||||
log.Log("[ssh] sending ping")
|
||||
//}
|
||||
ctx, cancel := context.WithTimeout(baseCtx, time.Second*30)
|
||||
var err error
|
||||
select {
|
||||
case err = <-s.sendPing():
|
||||
case <-ctx.Done():
|
||||
err = errors.New("Timeout")
|
||||
}
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Log("[ssh] ping:", err)
|
||||
return
|
||||
}
|
||||
//if Debug {
|
||||
log.Log("[ssh] ping OK, RTT:", time.Since(start))
|
||||
//}
|
||||
|
||||
case <-s.closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sshSession) sendPing() <-chan error {
|
||||
ch := make(chan error, 1)
|
||||
go func() {
|
||||
if _, _, err := s.client.SendRequest("ping", true, nil); err != nil {
|
||||
ch <- err
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (s *sshSession) waitServer() error {
|
||||
defer close(s.closed)
|
||||
return s.client.Wait()
|
||||
}
|
||||
|
||||
func (s *sshSession) waitClose() {
|
||||
defer s.client.Close()
|
||||
|
||||
select {
|
||||
case <-s.deaded:
|
||||
case <-s.closed:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sshSession) Closed() bool {
|
||||
select {
|
||||
case <-s.deaded:
|
||||
return true
|
||||
case <-s.closed:
|
||||
return true
|
||||
default:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type sshForwardHandler struct {
|
||||
options *HandlerOptions
|
||||
config *ssh.ServerConfig
|
||||
}
|
||||
|
||||
func SSHForwardHandler(opts ...HandlerOption) Handler {
|
||||
h := &sshForwardHandler{
|
||||
options: new(HandlerOptions),
|
||||
config: new(ssh.ServerConfig),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
h.config.PasswordCallback = defaultSSHPasswordCallback(h.options.Users...)
|
||||
if len(h.options.Users) == 0 {
|
||||
h.config.NoClientAuth = true
|
||||
}
|
||||
if h.options.TLSConfig != nil && len(h.options.TLSConfig.Certificates) > 0 {
|
||||
signer, err := ssh.NewSignerFromKey(h.options.TLSConfig.Certificates[0].PrivateKey)
|
||||
if err != nil {
|
||||
log.Log("[ssh-forward]", err)
|
||||
}
|
||||
h.config.AddHostKey(signer)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *sshForwardHandler) Handle(conn net.Conn) {
|
||||
sshConn, chans, reqs, err := ssh.NewServerConn(conn, h.config)
|
||||
if err != nil {
|
||||
log.Logf("[ssh-forward] %s -> %s : %s", conn.RemoteAddr(), h.options.Addr, err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
defer sshConn.Close()
|
||||
|
||||
log.Logf("[ssh-forward] %s <-> %s", conn.RemoteAddr(), h.options.Addr)
|
||||
h.handleForward(sshConn, chans, reqs)
|
||||
log.Logf("[ssh-forward] %s >-< %s", conn.RemoteAddr(), h.options.Addr)
|
||||
}
|
||||
|
||||
func (h *sshForwardHandler) handleForward(conn ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) {
|
||||
quit := make(chan struct{})
|
||||
defer close(quit) // quit signal
|
||||
|
||||
go func() {
|
||||
for req := range reqs {
|
||||
switch req.Type {
|
||||
case RemoteForwardRequest:
|
||||
go h.tcpipForwardRequest(conn, req, quit)
|
||||
default:
|
||||
// log.Log("[ssh] unknown channel type:", req.Type)
|
||||
if req.WantReply {
|
||||
req.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for newChannel := range chans {
|
||||
// Check the type of channel
|
||||
t := newChannel.ChannelType()
|
||||
switch t {
|
||||
case DirectForwardRequest:
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Log("[ssh] Could not accept channel:", err)
|
||||
continue
|
||||
}
|
||||
p := directForward{}
|
||||
ssh.Unmarshal(newChannel.ExtraData(), &p)
|
||||
|
||||
if p.Host1 == "<nil>" {
|
||||
p.Host1 = ""
|
||||
}
|
||||
|
||||
go ssh.DiscardRequests(requests)
|
||||
go h.directPortForwardChannel(channel, fmt.Sprintf("%s:%d", p.Host1, p.Port1))
|
||||
default:
|
||||
log.Log("[ssh] Unknown channel type:", t)
|
||||
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
conn.Wait()
|
||||
}
|
||||
|
||||
func (h *sshForwardHandler) directPortForwardChannel(channel ssh.Channel, raddr string) {
|
||||
defer channel.Close()
|
||||
|
||||
log.Logf("[ssh-tcp] %s - %s", h.options.Addr, raddr)
|
||||
|
||||
if !Can("tcp", raddr, h.options.Whitelist, h.options.Blacklist) {
|
||||
log.Logf("[ssh-tcp] Unauthorized to tcp connect to %s", raddr)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := h.options.Chain.Dial(raddr)
|
||||
if err != nil {
|
||||
log.Logf("[ssh-tcp] %s - %s : %s", h.options.Addr, raddr, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
log.Logf("[ssh-tcp] %s <-> %s", h.options.Addr, raddr)
|
||||
transport(conn, channel)
|
||||
log.Logf("[ssh-tcp] %s >-< %s", h.options.Addr, raddr)
|
||||
}
|
||||
|
||||
// tcpipForward is structure for RFC 4254 7.1 "tcpip-forward" request
|
||||
type tcpipForward struct {
|
||||
Host string
|
||||
Port uint32
|
||||
}
|
||||
|
||||
func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit <-chan struct{}) {
|
||||
t := tcpipForward{}
|
||||
ssh.Unmarshal(req.Payload, &t)
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", t.Host, t.Port)
|
||||
|
||||
if !Can("rtcp", addr, h.options.Whitelist, h.options.Blacklist) {
|
||||
log.Logf("[ssh-rtcp] Unauthorized to tcp bind to %s", addr)
|
||||
req.Reply(false, nil)
|
||||
return
|
||||
}
|
||||
|
||||
log.Log("[ssh-rtcp] listening on tcp", addr)
|
||||
ln, err := net.Listen("tcp", addr) //tie to the client connection
|
||||
if err != nil {
|
||||
log.Log("[ssh-rtcp]", err)
|
||||
req.Reply(false, nil)
|
||||
return
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
replyFunc := func() error {
|
||||
if t.Port == 0 && req.WantReply { // Client sent port 0. let them know which port is actually being used
|
||||
_, port, err := getHostPortFromAddr(ln.Addr())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var b [4]byte
|
||||
binary.BigEndian.PutUint32(b[:], uint32(port))
|
||||
t.Port = uint32(port)
|
||||
return req.Reply(true, b[:])
|
||||
}
|
||||
return req.Reply(true, nil)
|
||||
}
|
||||
if err := replyFunc(); err != nil {
|
||||
log.Log("[ssh-rtcp]", err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil { // Unable to accept new connection - listener is likely closed
|
||||
return
|
||||
}
|
||||
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
p := directForward{}
|
||||
var err error
|
||||
|
||||
var portnum int
|
||||
p.Host1 = t.Host
|
||||
p.Port1 = t.Port
|
||||
p.Host2, portnum, err = getHostPortFromAddr(conn.RemoteAddr())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.Port2 = uint32(portnum)
|
||||
ch, reqs, err := sshConn.OpenChannel(ForwardedTCPReturnRequest, ssh.Marshal(p))
|
||||
if err != nil {
|
||||
log.Log("[ssh-rtcp] open forwarded channel:", err)
|
||||
return
|
||||
}
|
||||
defer ch.Close()
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
log.Logf("[ssh-rtcp] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
transport(ch, conn)
|
||||
log.Logf("[ssh-rtcp] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
<-quit
|
||||
}
|
||||
|
||||
// SSHConfig holds the SSH tunnel server config
|
||||
type SSHConfig struct {
|
||||
Users []*url.Userinfo
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
type sshTunnelListener struct {
|
||||
net.Listener
|
||||
config *ssh.ServerConfig
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// SSHTunnelListener creates a Listener for SSH tunnel server.
|
||||
func SSHTunnelListener(addr string, config *SSHConfig) (Listener, error) {
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
config = &SSHConfig{}
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ServerConfig{}
|
||||
sshConfig.PasswordCallback = defaultSSHPasswordCallback(config.Users...)
|
||||
if len(config.Users) == 0 {
|
||||
sshConfig.NoClientAuth = true
|
||||
}
|
||||
if config.TLSConfig == nil {
|
||||
cert, err := tls.X509KeyPair(defaultRawCert, defaultRawKey)
|
||||
if err != nil {
|
||||
ln.Close()
|
||||
return nil, err
|
||||
}
|
||||
config.TLSConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
}
|
||||
|
||||
signer, err := ssh.NewSignerFromKey(config.TLSConfig.Certificates[0].PrivateKey)
|
||||
if err != nil {
|
||||
ln.Close()
|
||||
return nil, err
|
||||
|
||||
}
|
||||
sshConfig.AddHostKey(signer)
|
||||
|
||||
l := &sshTunnelListener{
|
||||
Listener: ln,
|
||||
config: sshConfig,
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
}
|
||||
|
||||
go l.listenLoop()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *sshTunnelListener) listenLoop() {
|
||||
for {
|
||||
conn, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
log.Log("[ssh] accept:", err)
|
||||
l.errChan <- err
|
||||
close(l.errChan)
|
||||
return
|
||||
}
|
||||
go l.serveConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *sshTunnelListener) serveConn(conn net.Conn) {
|
||||
sc, chans, reqs, err := ssh.NewServerConn(conn, l.config)
|
||||
if err != nil {
|
||||
log.Logf("[ssh] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
defer sc.Close()
|
||||
|
||||
go ssh.DiscardRequests(reqs)
|
||||
go func() {
|
||||
for newChannel := range chans {
|
||||
// Check the type of channel
|
||||
t := newChannel.ChannelType()
|
||||
switch t {
|
||||
case GostSSHTunnelRequest:
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Log("[ssh] Could not accept channel:", err)
|
||||
continue
|
||||
}
|
||||
go ssh.DiscardRequests(requests)
|
||||
cc := &sshConn{conn: conn, channel: channel}
|
||||
select {
|
||||
case l.connChan <- cc:
|
||||
default:
|
||||
cc.Close()
|
||||
log.Logf("[ssh] %s - %s: connection queue is full", conn.RemoteAddr(), l.Addr())
|
||||
}
|
||||
|
||||
default:
|
||||
log.Log("[ssh] Unknown channel type:", t)
|
||||
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Logf("[ssh] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
sc.Wait()
|
||||
log.Logf("[ssh] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
|
||||
func (l *sshTunnelListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// directForward is structure for RFC 4254 7.2 - can be used for "forwarded-tcpip" and "direct-tcpip"
|
||||
type directForward struct {
|
||||
Host1 string
|
||||
Port1 uint32
|
||||
Host2 string
|
||||
Port2 uint32
|
||||
}
|
||||
|
||||
func (p directForward) String() string {
|
||||
return fmt.Sprintf("%s:%d -> %s:%d", p.Host2, p.Port2, p.Host1, p.Port1)
|
||||
}
|
||||
|
||||
func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
|
||||
host, portString, err := net.SplitHostPort(addr.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
port, err = strconv.Atoi(portString)
|
||||
return
|
||||
}
|
||||
|
||||
type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
|
||||
|
||||
func defaultSSHPasswordCallback(users ...*url.Userinfo) PasswordCallbackFunc {
|
||||
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||
for _, user := range users {
|
||||
u := user.Username()
|
||||
p, _ := user.Password()
|
||||
if u == conn.User() && p == string(password) {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
log.Logf("[ssh] %s -> %s : password rejected for %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User())
|
||||
return nil, fmt.Errorf("password rejected for %s", conn.User())
|
||||
}
|
||||
}
|
||||
|
||||
type sshNopConn struct {
|
||||
session *sshSession
|
||||
}
|
||||
|
||||
func (c *sshNopConn) Read(b []byte) (n int, err error) {
|
||||
return 0, &net.OpError{Op: "read", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("read not supported")}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) Write(b []byte) (n int, err error) {
|
||||
return 0, &net.OpError{Op: "write", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("write not supported")}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *sshNopConn) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
type sshConn struct {
|
||||
channel ssh.Channel
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (c *sshConn) Read(b []byte) (n int, err error) {
|
||||
return c.channel.Read(b)
|
||||
}
|
||||
|
||||
func (c *sshConn) Write(b []byte) (n int, err error) {
|
||||
return c.channel.Write(b)
|
||||
}
|
||||
|
||||
func (c *sshConn) Close() error {
|
||||
return c.channel.Close()
|
||||
}
|
||||
|
||||
func (c *sshConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *sshConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *sshConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *sshConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *sshConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
295
gost/ws.go
295
gost/ws.go
@ -1,295 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"net/url"
|
||||
|
||||
"github.com/go-log/log"
|
||||
"gopkg.in/gorilla/websocket.v1"
|
||||
)
|
||||
|
||||
// WSOptions describes the options for websocket.
|
||||
type WSOptions struct {
|
||||
ReadBufferSize int
|
||||
WriteBufferSize int
|
||||
HandshakeTimeout time.Duration
|
||||
EnableCompression bool
|
||||
}
|
||||
|
||||
type websocketConn struct {
|
||||
conn *websocket.Conn
|
||||
rb []byte
|
||||
}
|
||||
|
||||
func websocketClientConn(url string, conn net.Conn, tlsConfig *tls.Config, options *WSOptions) (net.Conn, error) {
|
||||
if options == nil {
|
||||
options = &WSOptions{}
|
||||
}
|
||||
dialer := websocket.Dialer{
|
||||
ReadBufferSize: options.ReadBufferSize,
|
||||
WriteBufferSize: options.WriteBufferSize,
|
||||
TLSClientConfig: tlsConfig,
|
||||
HandshakeTimeout: options.HandshakeTimeout,
|
||||
EnableCompression: options.EnableCompression,
|
||||
NetDial: func(net, addr string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
c, resp, err := dialer.Dial(url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return &websocketConn{conn: c}, nil
|
||||
}
|
||||
|
||||
func websocketServerConn(conn *websocket.Conn) net.Conn {
|
||||
// conn.EnableWriteCompression(true)
|
||||
return &websocketConn{
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *websocketConn) Read(b []byte) (n int, err error) {
|
||||
if len(c.rb) == 0 {
|
||||
_, c.rb, err = c.conn.ReadMessage()
|
||||
}
|
||||
n = copy(b, c.rb)
|
||||
c.rb = c.rb[n:]
|
||||
return
|
||||
}
|
||||
|
||||
func (c *websocketConn) Write(b []byte) (n int, err error) {
|
||||
err = c.conn.WriteMessage(websocket.BinaryMessage, b)
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *websocketConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *websocketConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *websocketConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *websocketConn) SetDeadline(t time.Time) error {
|
||||
if err := c.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SetWriteDeadline(t)
|
||||
}
|
||||
func (c *websocketConn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *websocketConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type wsTransporter struct {
|
||||
*tcpTransporter
|
||||
options *WSOptions
|
||||
}
|
||||
|
||||
// WSTransporter creates a Transporter that is used by websocket proxy client.
|
||||
func WSTransporter(opts *WSOptions) Transporter {
|
||||
return &wsTransporter{
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *wsTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
wsOptions := tr.options
|
||||
if opts.WSOptions != nil {
|
||||
wsOptions = opts.WSOptions
|
||||
}
|
||||
url := url.URL{Scheme: "ws", Host: opts.Addr, Path: "/ws"}
|
||||
return websocketClientConn(url.String(), conn, nil, wsOptions)
|
||||
}
|
||||
|
||||
type wssTransporter struct {
|
||||
*tcpTransporter
|
||||
options *WSOptions
|
||||
}
|
||||
|
||||
// WSSTransporter creates a Transporter that is used by websocket secure proxy client.
|
||||
func WSSTransporter(opts *WSOptions) Transporter {
|
||||
return &wssTransporter{
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *wssTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
wsOptions := tr.options
|
||||
if opts.WSOptions != nil {
|
||||
wsOptions = opts.WSOptions
|
||||
}
|
||||
if opts.TLSConfig == nil {
|
||||
opts.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
url := url.URL{Scheme: "wss", Host: opts.Addr, Path: "/ws"}
|
||||
return websocketClientConn(url.String(), conn, opts.TLSConfig, wsOptions)
|
||||
}
|
||||
|
||||
type wsListener struct {
|
||||
addr net.Addr
|
||||
upgrader *websocket.Upgrader
|
||||
srv *http.Server
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// WSListener creates a Listener for websocket proxy server.
|
||||
func WSListener(addr string, options *WSOptions) (Listener, error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options == nil {
|
||||
options = &WSOptions{}
|
||||
}
|
||||
l := &wsListener{
|
||||
addr: tcpAddr,
|
||||
upgrader: &websocket.Upgrader{
|
||||
ReadBufferSize: options.ReadBufferSize,
|
||||
WriteBufferSize: options.WriteBufferSize,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
EnableCompression: options.EnableCompression,
|
||||
},
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", http.HandlerFunc(l.upgrade))
|
||||
l.srv = &http.Server{Addr: addr, Handler: mux}
|
||||
|
||||
ln, err := net.ListenTCP("tcp", tcpAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := l.srv.Serve(tcpKeepAliveListener{ln})
|
||||
if err != nil {
|
||||
l.errChan <- err
|
||||
}
|
||||
close(l.errChan)
|
||||
}()
|
||||
select {
|
||||
case err := <-l.errChan:
|
||||
return nil, err
|
||||
default:
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *wsListener) upgrade(w http.ResponseWriter, r *http.Request) {
|
||||
log.Logf("[ws] %s -> %s", r.RemoteAddr, l.addr)
|
||||
if Debug {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
log.Log(string(dump))
|
||||
}
|
||||
conn, err := l.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Logf("[ws] %s - %s : %s", r.RemoteAddr, l.addr, err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case l.connChan <- websocketServerConn(conn):
|
||||
default:
|
||||
conn.Close()
|
||||
log.Logf("[ws] %s - %s: connection queue is full", r.RemoteAddr, l.addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *wsListener) Accept() (conn net.Conn, err error) {
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err = <-l.errChan:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *wsListener) Close() error {
|
||||
return l.srv.Close()
|
||||
}
|
||||
|
||||
func (l *wsListener) Addr() net.Addr {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
type wssListener struct {
|
||||
*wsListener
|
||||
}
|
||||
|
||||
// WSSListener creates a Listener for websocket secure proxy server.
|
||||
func WSSListener(addr string, tlsConfig *tls.Config, options *WSOptions) (Listener, error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options == nil {
|
||||
options = &WSOptions{}
|
||||
}
|
||||
l := &wssListener{
|
||||
wsListener: &wsListener{
|
||||
addr: tcpAddr,
|
||||
upgrader: &websocket.Upgrader{
|
||||
ReadBufferSize: options.ReadBufferSize,
|
||||
WriteBufferSize: options.WriteBufferSize,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
EnableCompression: options.EnableCompression,
|
||||
},
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
},
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", http.HandlerFunc(l.upgrade))
|
||||
l.srv = &http.Server{
|
||||
Addr: addr,
|
||||
TLSConfig: tlsConfig,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
ln, err := net.ListenTCP("tcp", tcpAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := l.srv.Serve(tls.NewListener(tcpKeepAliveListener{ln}, tlsConfig))
|
||||
if err != nil {
|
||||
l.errChan <- err
|
||||
}
|
||||
close(l.errChan)
|
||||
}()
|
||||
select {
|
||||
case err := <-l.errChan:
|
||||
return nil, err
|
||||
default:
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
108
handler.go
108
handler.go
@ -1,32 +1,114 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/ginuerzh/gosocks4"
|
||||
"github.com/ginuerzh/gosocks5"
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
// Handler is a proxy server handler
|
||||
type Handler interface {
|
||||
Handle(net.Conn)
|
||||
}
|
||||
|
||||
type defaultHandler struct {
|
||||
server Server
|
||||
// HandlerOptions describes the options for Handler.
|
||||
type HandlerOptions struct {
|
||||
Addr string
|
||||
Chain *Chain
|
||||
Users []*url.Userinfo
|
||||
TLSConfig *tls.Config
|
||||
Whitelist *Permissions
|
||||
Blacklist *Permissions
|
||||
}
|
||||
|
||||
func DefaultHandler(server Server) Handler {
|
||||
return &defaultHandler{server: server}
|
||||
// HandlerOption allows a common way to set handler options.
|
||||
type HandlerOption func(opts *HandlerOptions)
|
||||
|
||||
// AddrHandlerOption sets the Addr option of HandlerOptions.
|
||||
func AddrHandlerOption(addr string) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Addr = addr
|
||||
}
|
||||
}
|
||||
|
||||
func (h *defaultHandler) Handle(conn net.Conn) {
|
||||
var handler Handler
|
||||
// ChainHandlerOption sets the Chain option of HandlerOptions.
|
||||
func ChainHandlerOption(chain *Chain) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Chain = chain
|
||||
}
|
||||
}
|
||||
|
||||
switch h.server.Options().BaseOptions().Protocol {
|
||||
case "http":
|
||||
handler = HTTPHandler(h.server)
|
||||
case "socks", "socks5":
|
||||
case "ss": // shadowsocks
|
||||
handler = ShadowHandler(h.server)
|
||||
// UsersHandlerOption sets the Users option of HandlerOptions.
|
||||
func UsersHandlerOption(users ...*url.Userinfo) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Users = users
|
||||
}
|
||||
}
|
||||
|
||||
// TLSConfigHandlerOption sets the TLSConfig option of HandlerOptions.
|
||||
func TLSConfigHandlerOption(config *tls.Config) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.TLSConfig = config
|
||||
}
|
||||
}
|
||||
|
||||
// WhitelistHandlerOption sets the Whitelist option of HandlerOptions.
|
||||
func WhitelistHandlerOption(whitelist *Permissions) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Whitelist = whitelist
|
||||
}
|
||||
}
|
||||
|
||||
// BlacklistHandlerOption sets the Blacklist option of HandlerOptions.
|
||||
func BlacklistHandlerOption(blacklist *Permissions) HandlerOption {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Blacklist = blacklist
|
||||
}
|
||||
}
|
||||
|
||||
type autoHandler struct {
|
||||
options []HandlerOption
|
||||
}
|
||||
|
||||
// AutoHandler creates a server Handler for auto proxy server.
|
||||
func AutoHandler(opts ...HandlerOption) Handler {
|
||||
h := &autoHandler{
|
||||
options: opts,
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *autoHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
b, err := br.Peek(1)
|
||||
if err != nil {
|
||||
log.Log(err)
|
||||
return
|
||||
}
|
||||
|
||||
handler.Handle(conn)
|
||||
cc := &bufferdConn{Conn: conn, br: br}
|
||||
switch b[0] {
|
||||
case gosocks4.Ver4:
|
||||
SOCKS4Handler(h.options...).Handle(cc)
|
||||
case gosocks5.Ver5:
|
||||
SOCKS5Handler(h.options...).Handle(cc)
|
||||
default: // http
|
||||
HTTPHandler(h.options...).Handle(cc)
|
||||
}
|
||||
}
|
||||
|
||||
type bufferdConn struct {
|
||||
net.Conn
|
||||
br *bufio.Reader
|
||||
}
|
||||
|
||||
func (c *bufferdConn) Read(b []byte) (int, error) {
|
||||
return c.br.Read(b)
|
||||
}
|
||||
|
486
http.go
486
http.go
@ -2,96 +2,100 @@ package gost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/pht"
|
||||
"github.com/go-log/log"
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type HttpServer struct {
|
||||
conn net.Conn
|
||||
Base *ProxyServer
|
||||
type httpConnector struct {
|
||||
User *url.Userinfo
|
||||
}
|
||||
|
||||
func NewHttpServer(conn net.Conn, base *ProxyServer) *HttpServer {
|
||||
return &HttpServer{
|
||||
conn: conn,
|
||||
Base: base,
|
||||
}
|
||||
// HTTPConnector creates a Connector for HTTP proxy client.
|
||||
// It accepts an optional auth info for HTTP Basic Authentication.
|
||||
func HTTPConnector(user *url.Userinfo) Connector {
|
||||
return &httpConnector{User: user}
|
||||
}
|
||||
|
||||
// Default HTTP server handler
|
||||
func (s *HttpServer) HandleRequest(req *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func (s *HttpServer) forwardRequest(req *http.Request) {
|
||||
last := s.Base.Chain.lastNode
|
||||
if last == nil {
|
||||
return
|
||||
func (c *httpConnector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
||||
req := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{Host: addr},
|
||||
Host: addr,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
cc, err := s.Base.Chain.GetConn()
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), last.Addr, err)
|
||||
req.Header.Set("Proxy-Connection", "keep-alive")
|
||||
|
||||
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 {
|
||||
if c.User != nil {
|
||||
s := c.User.String()
|
||||
if _, set := c.User.Password(); !set {
|
||||
s += ":"
|
||||
}
|
||||
req.Header.Set("Proxy-Authorization",
|
||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
|
||||
}
|
||||
|
||||
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
|
||||
if err := req.Write(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
if Debug {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Log(string(dump))
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if Debug {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
log.Log(string(dump))
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s", resp.Status)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
type httpHandler struct {
|
||||
server Server
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
func HTTPHandler(server Server) Handler {
|
||||
return &httpHandler{server: server}
|
||||
// HTTPHandler creates a server Handler for HTTP proxy server.
|
||||
func HTTPHandler(opts ...HandlerOption) Handler {
|
||||
h := &httpHandler{
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *httpHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(conn))
|
||||
if err != nil {
|
||||
log.Log("[http]", err)
|
||||
log.Logf("[http] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Logf("[http] %s %s - %s %s", req.Method, conn.RemoteAddr(), req.Host, req.Proto)
|
||||
|
||||
if Debug {
|
||||
log.Logf("[http] %s %s - %s %s", req.Method, conn.RemoteAddr(), req.Host, req.Proto)
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Logf(string(dump))
|
||||
}
|
||||
@ -104,21 +108,8 @@ func (h *httpHandler) Handle(conn net.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
valid := false
|
||||
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
|
||||
users := h.server.Options().BaseOptions().Users
|
||||
for _, user := range users {
|
||||
username := user.Username()
|
||||
password, _ := user.Password()
|
||||
if (u == username && p == password) ||
|
||||
(u == username && password == "") ||
|
||||
(username == "" && p == password) {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(users) > 0 && !valid {
|
||||
if !authenticate(u, p, h.options.Users...) {
|
||||
log.Logf("[http] %s <- %s : proxy authentication required", conn.RemoteAddr(), req.Host)
|
||||
resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" +
|
||||
"Proxy-Authenticate: Basic realm=\"gost\"\r\n" +
|
||||
@ -128,20 +119,31 @@ func (h *httpHandler) Handle(conn net.Conn) {
|
||||
}
|
||||
|
||||
req.Header.Del("Proxy-Authorization")
|
||||
req.Header.Del("Proxy-Connection")
|
||||
|
||||
if !Can("tcp", req.Host, h.options.Whitelist, h.options.Blacklist) {
|
||||
log.Logf("[http] Unauthorized to tcp connect to %s", req.Host)
|
||||
b := []byte("HTTP/1.1 403 Forbidden\r\n" +
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
||||
conn.Write(b)
|
||||
if Debug {
|
||||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// forward http request
|
||||
//lastNode := s.Base.Chain.lastNode
|
||||
//if lastNode != nil && lastNode.Transport == "" && (lastNode.Protocol == "http" || lastNode.Protocol == "") {
|
||||
// s.forwardRequest(req)
|
||||
// return
|
||||
//}
|
||||
lastNode := h.options.Chain.LastNode()
|
||||
if req.Method != http.MethodConnect && lastNode.Protocol == "http" {
|
||||
h.forwardRequest(conn, req)
|
||||
return
|
||||
}
|
||||
|
||||
// if !s.Base.Node.Can("tcp", req.Host) {
|
||||
// glog.Errorf("Unauthorized to tcp connect to %s", req.Host)
|
||||
// return
|
||||
// }
|
||||
|
||||
cc, err := h.server.Chain().Dial(req.Host)
|
||||
host := req.Host
|
||||
if !strings.Contains(req.Host, ":") {
|
||||
host += ":80"
|
||||
}
|
||||
cc, err := h.options.Chain.Dial(host)
|
||||
if err != nil {
|
||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
||||
|
||||
@ -164,7 +166,6 @@ func (h *httpHandler) Handle(conn net.Conn) {
|
||||
conn.Write(b)
|
||||
} else {
|
||||
req.Header.Del("Proxy-Connection")
|
||||
// req.Header.Set("Connection", "Keep-Alive")
|
||||
|
||||
if err = req.Write(cc); err != nil {
|
||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
||||
@ -172,274 +173,87 @@ func (h *httpHandler) Handle(conn net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Logf("[http] %s <-> %s", cc.LocalAddr(), req.Host)
|
||||
transport(conn, cc)
|
||||
log.Logf("[http] %s >-< %s", cc.LocalAddr(), req.Host)
|
||||
}
|
||||
|
||||
func (h *httpHandler) forwardRequest(conn net.Conn, req *http.Request) {
|
||||
if h.options.Chain.IsEmpty() {
|
||||
return
|
||||
}
|
||||
lastNode := h.options.Chain.LastNode()
|
||||
|
||||
cc, err := h.options.Chain.Conn()
|
||||
if err != nil {
|
||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), lastNode.Addr, err)
|
||||
|
||||
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
|
||||
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
||||
if Debug {
|
||||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), lastNode.Addr, string(b))
|
||||
}
|
||||
conn.Write(b)
|
||||
return
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
if lastNode.User != nil {
|
||||
s := lastNode.User.String()
|
||||
if _, set := lastNode.User.Password(); !set {
|
||||
s += ":"
|
||||
}
|
||||
req.Header.Set("Proxy-Authorization",
|
||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
|
||||
}
|
||||
|
||||
cc.SetWriteDeadline(time.Now().Add(WriteTimeout))
|
||||
if err = req.WriteProxy(cc); err != nil {
|
||||
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err)
|
||||
return
|
||||
}
|
||||
cc.SetWriteDeadline(time.Time{})
|
||||
|
||||
log.Logf("[http] %s <-> %s", conn.RemoteAddr(), req.Host)
|
||||
Transport(conn, cc)
|
||||
transport(conn, cc)
|
||||
log.Logf("[http] %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)
|
||||
|
||||
if !s.Base.Node.Can("tcp", target) {
|
||||
glog.Errorf("Unauthorized to tcp connect to %s", target)
|
||||
func basicProxyAuth(proxyAuth string) (username, password string, ok bool) {
|
||||
if proxyAuth == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
|
||||
valid := false
|
||||
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
|
||||
for _, user := range s.Base.Node.Users {
|
||||
username := user.Username()
|
||||
password, _ := user.Password()
|
||||
return cs[:s], cs[s+1:], true
|
||||
}
|
||||
|
||||
func authenticate(username, password string, users ...*url.Userinfo) bool {
|
||||
if len(users) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
u := user.Username()
|
||||
p, _ := user.Password()
|
||||
if (u == username && p == password) ||
|
||||
(u == username && password == "") ||
|
||||
(username == "" && p == password) {
|
||||
valid = true
|
||||
break
|
||||
(u == username && p == "") ||
|
||||
(u == "" && p == password) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
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)
|
||||
return false
|
||||
}
|
||||
|
@ -392,13 +392,7 @@ func HTTP2Listener(addr string, config *tls.Config) (Listener, error) {
|
||||
errChan: make(chan error, 1),
|
||||
}
|
||||
if config == nil {
|
||||
cert, err := tls.X509KeyPair(defaultRawCert, defaultRawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
config = DefaultTLSConfig
|
||||
}
|
||||
server := &http.Server{
|
||||
Addr: addr,
|
||||
@ -410,7 +404,12 @@ func HTTP2Listener(addr string, config *tls.Config) (Listener, error) {
|
||||
}
|
||||
l.server = server
|
||||
|
||||
go server.ListenAndServeTLS("", "")
|
||||
go func() {
|
||||
err := server.ListenAndServeTLS("", "")
|
||||
if err != nil {
|
||||
log.Log("[http2]", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
@ -473,17 +472,11 @@ func H2Listener(addr string, config *tls.Config) (Listener, error) {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
cert, err := tls.X509KeyPair(defaultRawCert, defaultRawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
config = DefaultTLSConfig
|
||||
}
|
||||
|
||||
l := &h2Listener{
|
||||
Listener: ln,
|
||||
Listener: tcpKeepAliveListener{ln.(*net.TCPListener)},
|
||||
server: &http2.Server{
|
||||
// MaxConcurrentStreams: 1000,
|
||||
PermitProhibitedCipherSuites: true,
|
||||
@ -505,7 +498,7 @@ func H2CListener(addr string) (Listener, error) {
|
||||
return nil, err
|
||||
}
|
||||
l := &h2Listener{
|
||||
Listener: ln,
|
||||
Listener: tcpKeepAliveListener{ln.(*net.TCPListener)},
|
||||
server: &http2.Server{
|
||||
// MaxConcurrentStreams: 1000,
|
||||
},
|
493
kcp.go
493
kcp.go
@ -1,30 +1,30 @@
|
||||
// KCP feature is based on https://github.com/xtaci/kcptun
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/go-log/log"
|
||||
"github.com/klauspost/compress/snappy"
|
||||
"gopkg.in/xtaci/kcp-go.v2"
|
||||
"gopkg.in/xtaci/smux.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
SALT = "kcp-go"
|
||||
// KCPSalt is the default salt for KCP cipher.
|
||||
KCPSalt = "kcp-go"
|
||||
)
|
||||
|
||||
// KCPConfig describes the config for KCP.
|
||||
type KCPConfig struct {
|
||||
Key string `json:"key"`
|
||||
Crypt string `json:"crypt"`
|
||||
@ -45,25 +45,10 @@ type KCPConfig struct {
|
||||
KeepAlive int `json:"keepalive"`
|
||||
SnmpLog string `json:"snmplog"`
|
||||
SnmpPeriod int `json:"snmpperiod"`
|
||||
Signal bool `json:"signal"` // Signal enables the signal SIGUSR1 feature.
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Init initializes the KCP config.
|
||||
func (c *KCPConfig) Init() {
|
||||
switch c.Mode {
|
||||
case "normal":
|
||||
@ -80,6 +65,7 @@ func (c *KCPConfig) Init() {
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultKCPConfig is the default KCP config.
|
||||
DefaultKCPConfig = &KCPConfig{
|
||||
Key: "it's a secrect",
|
||||
Crypt: "aes",
|
||||
@ -100,89 +86,323 @@ var (
|
||||
KeepAlive: 10,
|
||||
SnmpLog: "",
|
||||
SnmpPeriod: 60,
|
||||
Signal: false,
|
||||
}
|
||||
)
|
||||
|
||||
type KCPServer struct {
|
||||
Base *ProxyServer
|
||||
Config *KCPConfig
|
||||
type kcpConn struct {
|
||||
conn net.Conn
|
||||
stream *smux.Stream
|
||||
}
|
||||
|
||||
func NewKCPServer(base *ProxyServer, config *KCPConfig) *KCPServer {
|
||||
return &KCPServer{Base: base, Config: config}
|
||||
func (c *kcpConn) Read(b []byte) (n int, err error) {
|
||||
return c.stream.Read(b)
|
||||
}
|
||||
|
||||
func (s *KCPServer) ListenAndServe() (err error) {
|
||||
if s.Config == nil {
|
||||
s.Config = DefaultKCPConfig
|
||||
}
|
||||
s.Config.Init()
|
||||
func (c *kcpConn) Write(b []byte) (n int, err error) {
|
||||
return c.stream.Write(b)
|
||||
}
|
||||
|
||||
ln, err := kcp.ListenWithOptions(s.Base.Node.Addr,
|
||||
blockCrypt(s.Config.Key, s.Config.Crypt, SALT), s.Config.DataShard, s.Config.ParityShard)
|
||||
func (c *kcpConn) Close() error {
|
||||
return c.stream.Close()
|
||||
}
|
||||
|
||||
func (c *kcpConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *kcpConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *kcpConn) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *kcpConn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *kcpConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type kcpSession struct {
|
||||
conn net.Conn
|
||||
session *smux.Session
|
||||
}
|
||||
|
||||
func (session *kcpSession) GetConn() (*kcpConn, error) {
|
||||
stream, err := session.session.OpenStream()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err = ln.SetDSCP(s.Config.DSCP); err != nil {
|
||||
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||
return &kcpConn{conn: session.conn, stream: stream}, nil
|
||||
}
|
||||
|
||||
func (session *kcpSession) Close() error {
|
||||
return session.session.Close()
|
||||
}
|
||||
|
||||
func (session *kcpSession) IsClosed() bool {
|
||||
return session.session.IsClosed()
|
||||
}
|
||||
|
||||
func (session *kcpSession) NumStreams() int {
|
||||
return session.session.NumStreams()
|
||||
}
|
||||
|
||||
type kcpTransporter struct {
|
||||
sessions map[string]*kcpSession
|
||||
sessionMutex sync.Mutex
|
||||
config *KCPConfig
|
||||
}
|
||||
|
||||
// KCPTransporter creates a Transporter that is used by KCP proxy client.
|
||||
func KCPTransporter(config *KCPConfig) Transporter {
|
||||
if config == nil {
|
||||
config = DefaultKCPConfig
|
||||
}
|
||||
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)
|
||||
config.Init()
|
||||
|
||||
go snmpLogger(config.SnmpLog, config.SnmpPeriod)
|
||||
if config.Signal {
|
||||
go kcpSigHandler()
|
||||
}
|
||||
|
||||
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)
|
||||
return &kcpTransporter{
|
||||
config: config,
|
||||
sessions: make(map[string]*kcpSession),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *KCPServer) handleMux(conn net.Conn) {
|
||||
func (tr *kcpTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) {
|
||||
uaddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[addr]
|
||||
if !ok {
|
||||
conn, err = net.DialUDP("udp", nil, uaddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session = &kcpSession{conn: conn}
|
||||
tr.sessions[addr] = session
|
||||
}
|
||||
return session.conn, nil
|
||||
}
|
||||
|
||||
func (tr *kcpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
config := tr.config
|
||||
if opts.KCPConfig != nil {
|
||||
config = opts.KCPConfig
|
||||
}
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[opts.Addr]
|
||||
if session != nil && session.conn != conn {
|
||||
conn.Close()
|
||||
return nil, errors.New("kcp: unrecognized connection")
|
||||
}
|
||||
if !ok || session.session == nil {
|
||||
s, err := tr.initSession(opts.Addr, conn, config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
session = s
|
||||
tr.sessions[opts.Addr] = session
|
||||
}
|
||||
cc, err := session.GetConn()
|
||||
if err != nil {
|
||||
session.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPConfig) (*kcpSession, error) {
|
||||
udpConn, ok := conn.(*net.UDPConn)
|
||||
if !ok {
|
||||
return nil, errors.New("kcp: wrong connection type")
|
||||
}
|
||||
|
||||
kcpconn, err := kcp.NewConn(addr,
|
||||
blockCrypt(config.Key, config.Crypt, KCPSalt),
|
||||
config.DataShard, config.ParityShard,
|
||||
&kcp.ConnectedUDPConn{UDPConn: udpConn, Conn: udpConn})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kcpconn.SetStreamMode(true)
|
||||
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion)
|
||||
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd)
|
||||
kcpconn.SetMtu(config.MTU)
|
||||
kcpconn.SetACKNoDelay(config.AckNodelay)
|
||||
kcpconn.SetKeepAlive(config.KeepAlive)
|
||||
|
||||
if err := kcpconn.SetDSCP(config.DSCP); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
|
||||
// stream multiplex
|
||||
smuxConfig := smux.DefaultConfig()
|
||||
smuxConfig.MaxReceiveBuffer = s.Config.SockBuf
|
||||
smuxConfig.MaxReceiveBuffer = config.SockBuf
|
||||
var cc net.Conn = kcpconn
|
||||
if !config.NoComp {
|
||||
cc = newCompStreamConn(kcpconn)
|
||||
}
|
||||
session, err := smux.Client(cc, smuxConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &kcpSession{conn: conn, session: session}, nil
|
||||
}
|
||||
|
||||
glog.V(LINFO).Infof("[kcp] %s - %s", conn.RemoteAddr(), s.Base.Node.Addr)
|
||||
func (tr *kcpTransporter) Multiplex() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if !s.Config.NoComp {
|
||||
type kcpListener struct {
|
||||
config *KCPConfig
|
||||
ln *kcp.Listener
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// KCPListener creates a Listener for KCP proxy server.
|
||||
func KCPListener(addr string, config *KCPConfig) (Listener, error) {
|
||||
if config == nil {
|
||||
config = DefaultKCPConfig
|
||||
}
|
||||
config.Init()
|
||||
|
||||
ln, err := kcp.ListenWithOptions(addr,
|
||||
blockCrypt(config.Key, config.Crypt, KCPSalt), config.DataShard, config.ParityShard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = ln.SetDSCP(config.DSCP); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
if err = ln.SetReadBuffer(config.SockBuf); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
if err = ln.SetWriteBuffer(config.SockBuf); err != nil {
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
|
||||
go snmpLogger(config.SnmpLog, config.SnmpPeriod)
|
||||
if config.Signal {
|
||||
go kcpSigHandler()
|
||||
}
|
||||
|
||||
l := &kcpListener{
|
||||
config: config,
|
||||
ln: ln,
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
}
|
||||
go l.listenLoop()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *kcpListener) listenLoop() {
|
||||
for {
|
||||
conn, err := l.ln.AcceptKCP()
|
||||
if err != nil {
|
||||
log.Log("[kcp] accept:", err)
|
||||
l.errChan <- err
|
||||
close(l.errChan)
|
||||
return
|
||||
}
|
||||
conn.SetStreamMode(true)
|
||||
conn.SetNoDelay(l.config.NoDelay, l.config.Interval, l.config.Resend, l.config.NoCongestion)
|
||||
conn.SetMtu(l.config.MTU)
|
||||
conn.SetWindowSize(l.config.SndWnd, l.config.RcvWnd)
|
||||
conn.SetACKNoDelay(l.config.AckNodelay)
|
||||
conn.SetKeepAlive(l.config.KeepAlive)
|
||||
go l.mux(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *kcpListener) mux(conn net.Conn) {
|
||||
smuxConfig := smux.DefaultConfig()
|
||||
smuxConfig.MaxReceiveBuffer = l.config.SockBuf
|
||||
|
||||
log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr())
|
||||
|
||||
if !l.config.NoComp {
|
||||
conn = newCompStreamConn(conn)
|
||||
}
|
||||
|
||||
mux, err := smux.Server(conn, smuxConfig)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||
log.Log("[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)
|
||||
log.Logf("[kcp] %s <-> %s", conn.RemoteAddr(), l.Addr())
|
||||
defer log.Logf("[kcp] %s >-< %s", conn.RemoteAddr(), l.Addr())
|
||||
|
||||
for {
|
||||
stream, err := mux.AcceptStream()
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||
log.Log("[kcp] accept stream:", err)
|
||||
return
|
||||
}
|
||||
go s.Base.handleConn(NewKCPConn(conn, stream))
|
||||
|
||||
cc := &kcpConn{conn: conn, stream: stream}
|
||||
select {
|
||||
case l.connChan <- cc:
|
||||
default:
|
||||
cc.Close()
|
||||
log.Logf("[kcp] %s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *kcpListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (l *kcpListener) Addr() net.Addr {
|
||||
return l.ln.Addr()
|
||||
}
|
||||
|
||||
func (l *kcpListener) Close() error {
|
||||
return l.ln.Close()
|
||||
}
|
||||
|
||||
func blockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
|
||||
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New)
|
||||
|
||||
@ -217,8 +437,8 @@ func blockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
|
||||
return
|
||||
}
|
||||
|
||||
func snmpLogger(path string, interval int) {
|
||||
if path == "" || interval == 0 {
|
||||
func snmpLogger(format string, interval int) {
|
||||
if format == "" || interval == 0 {
|
||||
return
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
@ -226,20 +446,20 @@ func snmpLogger(path string, interval int) {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
f, err := os.OpenFile(time.Now().Format(path), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
f, err := os.OpenFile(time.Now().Format(format), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||
log.Log("[kcp]", err)
|
||||
return
|
||||
}
|
||||
w := csv.NewWriter(f)
|
||||
// write header in empty file
|
||||
if stat, err := f.Stat(); err == nil && stat.Size() == 0 {
|
||||
if err := w.Write(append([]string{"Unix"}, kcp.DefaultSnmp.Header()...)); err != nil {
|
||||
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
}
|
||||
if err := w.Write(append([]string{fmt.Sprint(time.Now().Unix())}, kcp.DefaultSnmp.ToSlice()...)); err != nil {
|
||||
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||
log.Log("[kcp]", err)
|
||||
}
|
||||
kcp.DefaultSnmp.Reset()
|
||||
w.Flush()
|
||||
@ -248,117 +468,6 @@ func snmpLogger(path string, interval int) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
175
node.go
175
node.go
@ -1,90 +1,40 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
// 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
|
||||
Whitelist *Permissions
|
||||
Blacklist *Permissions
|
||||
values url.Values
|
||||
serverName string
|
||||
conn net.Conn
|
||||
// Node is a proxy node, mainly used to construct a proxy chain.
|
||||
type Node struct {
|
||||
Addr string
|
||||
Protocol string
|
||||
Transport string
|
||||
Remote string // remote address, used by tcp/udp port forwarding
|
||||
User *url.Userinfo
|
||||
Values url.Values
|
||||
Client *Client
|
||||
DialOptions []DialOption
|
||||
HandshakeOptions []HandshakeOption
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func ParseNode(s string) (node Node, err error) {
|
||||
if !strings.Contains(s, "://") {
|
||||
s = "gost://" + s
|
||||
s = "auto://" + s
|
||||
}
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
|
||||
node = ProxyNode{
|
||||
Addr: u.Host,
|
||||
values: query,
|
||||
serverName: u.Host,
|
||||
}
|
||||
|
||||
if query.Get("whitelist") != "" {
|
||||
node.Whitelist, err = ParsePermissions(query.Get("whitelist"))
|
||||
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
// By default allow for everyting
|
||||
node.Whitelist, _ = ParsePermissions("*:*:*")
|
||||
}
|
||||
|
||||
if query.Get("blacklist") != "" {
|
||||
node.Blacklist, err = ParsePermissions(query.Get("blacklist"))
|
||||
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
// By default block nothing
|
||||
node.Blacklist, _ = ParsePermissions("")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
node = Node{
|
||||
Addr: u.Host,
|
||||
Values: u.Query(),
|
||||
User: u.User,
|
||||
}
|
||||
|
||||
schemes := strings.Split(u.Scheme, "+")
|
||||
@ -98,20 +48,24 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
|
||||
}
|
||||
|
||||
switch node.Transport {
|
||||
case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu", "pht", "ssh":
|
||||
case "tls", "ws", "wss", "kcp", "ssh", "quic", "ssu", "http2", "h2", "h2c", "redirect":
|
||||
case "https":
|
||||
node.Protocol = "http"
|
||||
node.Transport = "tls"
|
||||
case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding
|
||||
node.Remote = strings.Trim(u.EscapedPath(), "/")
|
||||
case "rtcp", "rudp": // started from v2.1, rtcp and rudp are for remote port forwarding
|
||||
case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding
|
||||
node.Remote = strings.Trim(u.EscapedPath(), "/")
|
||||
default:
|
||||
node.Transport = ""
|
||||
}
|
||||
|
||||
switch node.Protocol {
|
||||
case "http", "http2", "socks", "socks4", "socks4a", "socks5", "ss":
|
||||
case "http", "http2", "socks4", "socks4a", "ss", "ssu":
|
||||
case "socks", "socks5":
|
||||
node.Protocol = "socks5"
|
||||
case "tcp", "udp", "rtcp", "rudp": // port forwarding
|
||||
case "direct", "remote", "forward": // SSH port forwarding
|
||||
default:
|
||||
node.Protocol = ""
|
||||
}
|
||||
@ -119,40 +73,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
|
||||
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) Can(action string, addr string) bool {
|
||||
func Can(action string, addr string, whitelist, blacklist *Permissions) bool {
|
||||
if !strings.Contains(addr, ":") {
|
||||
addr = addr + ":80"
|
||||
}
|
||||
@ -168,46 +89,8 @@ func (node *ProxyNode) Can(action string, addr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
glog.V(LDEBUG).Infof("Can action: %s, host: %s, port %d", action, host, port)
|
||||
|
||||
return node.Whitelist.Can(action, host, port) && !node.Blacklist.Can(action, host, port)
|
||||
}
|
||||
|
||||
func (node *ProxyNode) getBool(key string) bool {
|
||||
s := node.Get(key)
|
||||
if b, _ := strconv.ParseBool(s); b {
|
||||
return b
|
||||
if Debug {
|
||||
log.Logf("Can action: %s, host: %s, port %d", action, host, port)
|
||||
}
|
||||
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) caFile() string {
|
||||
return node.Get("ca")
|
||||
}
|
||||
|
||||
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, whitelist: %v, blacklist: %v", node.Transport, node.Protocol, node.Addr, node.Whitelist, node.Blacklist)
|
||||
return whitelist.Can(action, host, port) && !blacklist.Can(action, host, port)
|
||||
}
|
||||
|
43
node_test.go
43
node_test.go
@ -1,43 +0,0 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNodeDefaultWhitelist(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
node, _ := ParseProxyNode("http2://localhost:8000")
|
||||
|
||||
assert.True(node.Can("connect", "google.pl:80"))
|
||||
assert.True(node.Can("connect", "google.pl:443"))
|
||||
assert.True(node.Can("connect", "google.pl:22"))
|
||||
assert.True(node.Can("bind", "google.pl:80"))
|
||||
assert.True(node.Can("bind", "google.com:80"))
|
||||
}
|
||||
|
||||
func TestNodeWhitelist(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
node, _ := ParseProxyNode("http2://localhost:8000?whitelist=connect:google.pl:80,443")
|
||||
|
||||
assert.True(node.Can("connect", "google.pl:80"))
|
||||
assert.True(node.Can("connect", "google.pl:443"))
|
||||
assert.False(node.Can("connect", "google.pl:22"))
|
||||
assert.False(node.Can("bind", "google.pl:80"))
|
||||
assert.False(node.Can("bind", "google.com:80"))
|
||||
}
|
||||
|
||||
func TestNodeBlacklist(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
node, _ := ParseProxyNode("http2://localhost:8000?blacklist=connect:google.pl:80,443")
|
||||
|
||||
assert.False(node.Can("connect", "google.pl:80"))
|
||||
assert.False(node.Can("connect", "google.pl:443"))
|
||||
assert.True(node.Can("connect", "google.pl:22"))
|
||||
assert.True(node.Can("bind", "google.pl:80"))
|
||||
assert.True(node.Can("bind", "google.com:80"))
|
||||
}
|
@ -9,14 +9,6 @@ import (
|
||||
glob "github.com/ryanuber/go-glob"
|
||||
)
|
||||
|
||||
type PortRange struct {
|
||||
Min, Max int
|
||||
}
|
||||
|
||||
type PortSet []PortRange
|
||||
|
||||
type StringSet []string
|
||||
|
||||
type Permission struct {
|
||||
Actions StringSet
|
||||
Hosts StringSet
|
||||
@ -39,6 +31,10 @@ func maxint(x, y int) int {
|
||||
return y
|
||||
}
|
||||
|
||||
type PortRange struct {
|
||||
Min, Max int
|
||||
}
|
||||
|
||||
func (ir *PortRange) Contains(value int) bool {
|
||||
return value >= ir.Min && value <= ir.Max
|
||||
}
|
||||
@ -88,6 +84,8 @@ func (ps *PortSet) Contains(value int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type PortSet []PortRange
|
||||
|
||||
func ParsePortSet(s string) (*PortSet, error) {
|
||||
ps := &PortSet{}
|
||||
|
||||
@ -120,6 +118,8 @@ func (ss *StringSet) Contains(subj string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type StringSet []string
|
||||
|
||||
func ParseStringSet(s string) (*StringSet, error) {
|
||||
ss := &StringSet{}
|
||||
if s == "" {
|
||||
|
278
quic.go
278
quic.go
@ -1,81 +1,241 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"github.com/golang/glog"
|
||||
"github.com/lucas-clemente/quic-go/h2quic"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-log/log"
|
||||
quic "github.com/lucas-clemente/quic-go"
|
||||
)
|
||||
|
||||
type QuicServer struct {
|
||||
Base *ProxyServer
|
||||
Handler http.Handler
|
||||
type quicSession struct {
|
||||
conn net.Conn
|
||||
session quic.Session
|
||||
}
|
||||
|
||||
func (session *quicSession) GetConn() (*quicConn, error) {
|
||||
stream, err := session.session.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &quicConn{
|
||||
Stream: stream,
|
||||
laddr: session.session.LocalAddr(),
|
||||
raddr: session.session.RemoteAddr(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (session *quicSession) Close() error {
|
||||
return session.session.Close(nil)
|
||||
}
|
||||
|
||||
type quicTransporter struct {
|
||||
config *QUICConfig
|
||||
sessionMutex sync.Mutex
|
||||
sessions map[string]*quicSession
|
||||
}
|
||||
|
||||
// QUICTransporter creates a Transporter that is used by QUIC proxy client.
|
||||
func QUICTransporter(config *QUICConfig) Transporter {
|
||||
if config == nil {
|
||||
config = &QUICConfig{}
|
||||
}
|
||||
return &quicTransporter{
|
||||
config: config,
|
||||
sessions: make(map[string]*quicSession),
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *quicTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) {
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[addr]
|
||||
if !ok {
|
||||
conn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session = &quicSession{conn: conn}
|
||||
tr.sessions[addr] = session
|
||||
}
|
||||
return session.conn, nil
|
||||
}
|
||||
|
||||
func (tr *quicTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
config := tr.config
|
||||
if opts.QUICConfig != nil {
|
||||
config = opts.QUICConfig
|
||||
}
|
||||
if config.TLSConfig == nil {
|
||||
config.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[opts.Addr]
|
||||
if session != nil && session.conn != conn {
|
||||
conn.Close()
|
||||
return nil, errors.New("quic: unrecognized connection")
|
||||
}
|
||||
if !ok || session.session == nil {
|
||||
s, err := tr.initSession(opts.Addr, conn, config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
session = s
|
||||
tr.sessions[opts.Addr] = session
|
||||
}
|
||||
cc, err := session.GetConn()
|
||||
if err != nil {
|
||||
session.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (tr *quicTransporter) initSession(addr string, conn net.Conn, config *QUICConfig) (*quicSession, error) {
|
||||
udpConn, ok := conn.(*net.UDPConn)
|
||||
if !ok {
|
||||
return nil, errors.New("quic: wrong connection type")
|
||||
}
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quicConfig := &quic.Config{
|
||||
HandshakeTimeout: config.Timeout,
|
||||
KeepAlive: config.KeepAlive,
|
||||
}
|
||||
session, err := quic.Dial(udpConn, udpAddr, addr, config.TLSConfig, quicConfig)
|
||||
if err != nil {
|
||||
log.Log("quic dial", err)
|
||||
return nil, err
|
||||
}
|
||||
return &quicSession{conn: conn, session: session}, nil
|
||||
}
|
||||
|
||||
func (tr *quicTransporter) Multiplex() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type QUICConfig struct {
|
||||
TLSConfig *tls.Config
|
||||
Timeout time.Duration
|
||||
KeepAlive bool
|
||||
}
|
||||
|
||||
func NewQuicServer(base *ProxyServer) *QuicServer {
|
||||
return &QuicServer{Base: base}
|
||||
type quicListener struct {
|
||||
ln quic.Listener
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
func (s *QuicServer) ListenAndServeTLS(config *tls.Config) error {
|
||||
server := &h2quic.Server{
|
||||
Server: &http.Server{
|
||||
Addr: s.Base.Node.Addr,
|
||||
Handler: s.Handler,
|
||||
TLSConfig: config,
|
||||
},
|
||||
// QUICListener creates a Listener for QUIC proxy server.
|
||||
func QUICListener(addr string, config *QUICConfig) (Listener, error) {
|
||||
if config == nil {
|
||||
config = &QUICConfig{}
|
||||
}
|
||||
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))
|
||||
quicConfig := &quic.Config{
|
||||
HandshakeTimeout: config.Timeout,
|
||||
KeepAlive: config.KeepAlive,
|
||||
}
|
||||
|
||||
c, err := s.Base.Chain.Dial(target)
|
||||
tlsConfig := config.TLSConfig
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = DefaultTLSConfig
|
||||
}
|
||||
ln, err := quic.ListenAddr(addr, tlsConfig, quicConfig)
|
||||
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
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(c), req)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln(err)
|
||||
return
|
||||
l := &quicListener{
|
||||
ln: ln,
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
go l.listenLoop()
|
||||
|
||||
for k, v := range resp.Header {
|
||||
for _, vv := range v {
|
||||
w.Header().Add(k, vv)
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *quicListener) listenLoop() {
|
||||
for {
|
||||
session, err := l.ln.Accept()
|
||||
if err != nil {
|
||||
log.Log("[quic] accept:", err)
|
||||
l.errChan <- err
|
||||
close(l.errChan)
|
||||
return
|
||||
}
|
||||
go l.sessionLoop(session)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *quicListener) sessionLoop(session quic.Session) {
|
||||
log.Logf("[quic] %s <-> %s", session.RemoteAddr(), session.LocalAddr())
|
||||
defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr())
|
||||
|
||||
for {
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
log.Log("[quic] accept stream:", err)
|
||||
return
|
||||
}
|
||||
|
||||
cc := &quicConn{Stream: stream, laddr: session.LocalAddr(), raddr: session.RemoteAddr()}
|
||||
select {
|
||||
case l.connChan <- cc:
|
||||
default:
|
||||
cc.Close()
|
||||
log.Logf("[quic] %s - %s: connection queue is full", session.RemoteAddr(), session.LocalAddr())
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func (l *quicListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *quicListener) Addr() net.Addr {
|
||||
return l.ln.Addr()
|
||||
}
|
||||
|
||||
func (l *quicListener) Close() error {
|
||||
return l.ln.Close()
|
||||
}
|
||||
|
||||
type quicConn struct {
|
||||
quic.Stream
|
||||
laddr net.Addr
|
||||
raddr net.Addr
|
||||
}
|
||||
|
||||
func (c *quicConn) LocalAddr() net.Addr {
|
||||
return c.laddr
|
||||
}
|
||||
|
||||
func (c *quicConn) RemoteAddr() net.Addr {
|
||||
return c.raddr
|
||||
}
|
||||
|
68
redirect.go
68
redirect.go
@ -5,70 +5,58 @@ package gost
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
const (
|
||||
SO_ORIGINAL_DST = 80
|
||||
)
|
||||
|
||||
type RedsocksTCPServer struct {
|
||||
Base *ProxyServer
|
||||
type tcpRedirectHandler struct {
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
func NewRedsocksTCPServer(base *ProxyServer) *RedsocksTCPServer {
|
||||
return &RedsocksTCPServer{
|
||||
Base: base,
|
||||
// TCPRedirectHandler creates a server Handler for TCP redirect server.
|
||||
func TCPRedirectHandler(opts ...HandlerOption) Handler {
|
||||
h := &tcpRedirectHandler{
|
||||
options: &HandlerOptions{
|
||||
Chain: new(Chain),
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (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
|
||||
func (h *tcpRedirectHandler) Handle(c net.Conn) {
|
||||
conn, ok := c.(*net.TCPConn)
|
||||
if !ok {
|
||||
log.Log("[red-tcp] not a TCP connection")
|
||||
}
|
||||
|
||||
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)
|
||||
dstAddr, conn, err := h.getOriginalDstAddr(conn)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infof("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err)
|
||||
log.Logf("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
glog.V(LINFO).Infof("[red-tcp] %s -> %s", srcAddr, dstAddr)
|
||||
log.Logf("[red-tcp] %s -> %s", srcAddr, dstAddr)
|
||||
|
||||
cc, err := s.Base.Chain.Dial(dstAddr.String())
|
||||
cc, err := h.options.Chain.Dial(dstAddr.String())
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infof("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err)
|
||||
log.Logf("[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)
|
||||
log.Logf("[red-tcp] %s <-> %s", srcAddr, dstAddr)
|
||||
transport(conn, cc)
|
||||
log.Logf("[red-tcp] %s >-< %s", srcAddr, dstAddr)
|
||||
}
|
||||
|
||||
func getOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err error) {
|
||||
func (h *tcpRedirectHandler) getOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err error) {
|
||||
defer conn.Close()
|
||||
|
||||
fc, err := conn.File()
|
||||
@ -77,7 +65,7 @@ func getOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err e
|
||||
}
|
||||
defer fc.Close()
|
||||
|
||||
mreq, err := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
|
||||
mreq, err := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, 80)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -3,15 +3,29 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
type RedsocksTCPServer struct{}
|
||||
|
||||
func NewRedsocksTCPServer(base *ProxyServer) *RedsocksTCPServer {
|
||||
return &RedsocksTCPServer{}
|
||||
type tcpRedirectHandler struct {
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
func (s *RedsocksTCPServer) ListenAndServe() error {
|
||||
return errors.New("Not supported")
|
||||
// TCPRedirectHandler creates a server Handler for TCP redirect server.
|
||||
func TCPRedirectHandler(opts ...HandlerOption) Handler {
|
||||
h := &tcpRedirectHandler{
|
||||
options: &HandlerOptions{
|
||||
Chain: new(Chain),
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *tcpRedirectHandler) Handle(c net.Conn) {
|
||||
log.Log("[red-tcp] TCP redirect is not available on the Windows platform")
|
||||
c.Close()
|
||||
}
|
||||
|
325
server.go
325
server.go
@ -1,297 +1,104 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gosocks4"
|
||||
"github.com/ginuerzh/gosocks5"
|
||||
"github.com/golang/glog"
|
||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
type ProxyServer struct {
|
||||
Node ProxyNode
|
||||
Chain *ProxyChain
|
||||
TLSConfig *tls.Config
|
||||
selector *ServerSelector
|
||||
cipher *ss.Cipher
|
||||
ota bool
|
||||
// Server is a proxy server.
|
||||
type Server struct {
|
||||
}
|
||||
|
||||
func NewProxyServer(node ProxyNode, chain *ProxyChain) *ProxyServer {
|
||||
certFile, keyFile := node.certFile(), node.keyFile()
|
||||
// Serve serves as a proxy server.
|
||||
func (s *Server) Serve(l net.Listener, h Handler) error {
|
||||
defer l.Close()
|
||||
|
||||
cert, err := LoadCertificate(certFile, keyFile)
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
|
||||
if chain == nil {
|
||||
chain = NewProxyChain()
|
||||
}
|
||||
|
||||
var cipher *ss.Cipher
|
||||
var ota bool
|
||||
if node.Protocol == "ss" || node.Transport == "ssu" {
|
||||
var err error
|
||||
var method, password string
|
||||
|
||||
if len(node.Users) > 0 {
|
||||
method = node.Users[0].Username()
|
||||
password, _ = node.Users[0].Password()
|
||||
}
|
||||
ota = node.getBool("ota")
|
||||
if strings.HasSuffix(method, "-auth") {
|
||||
ota = true
|
||||
method = strings.TrimSuffix(method, "-auth")
|
||||
}
|
||||
cipher, err = ss.NewCipher(method, password)
|
||||
if 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
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(s.TLSConfig.Certificates[0].PrivateKey)
|
||||
if l == nil {
|
||||
ln, err := TCPListener(":8080")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.AddHostKey(signer)
|
||||
s := &SSHServer{
|
||||
Addr: node.Addr,
|
||||
Base: s,
|
||||
Config: &config,
|
||||
}
|
||||
return s.ListenAndServe()
|
||||
default:
|
||||
ln, err = net.Listen("tcp", node.Addr)
|
||||
l = ln
|
||||
}
|
||||
if h == nil {
|
||||
h = HTTPHandler()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer ln.Close()
|
||||
|
||||
var tempDelay time.Duration
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln(err)
|
||||
continue
|
||||
conn, e := l.Accept()
|
||||
if e != nil {
|
||||
if ne, ok := e.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Logf("server: Accept error: %v; retrying in %v", e, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
setKeepAlive(conn, KeepAliveTime)
|
||||
|
||||
go s.handleConn(conn)
|
||||
tempDelay = 0
|
||||
go h.Handle(conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *ProxyServer) handleConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
// Listener is a proxy server listener, just like a net.Listener.
|
||||
type Listener interface {
|
||||
net.Listener
|
||||
}
|
||||
|
||||
switch s.Node.Protocol {
|
||||
case "ss": // shadowsocks
|
||||
//server := NewShadowServer(ss.NewConn(conn, s.cipher.Copy()), s)
|
||||
//server.OTA = s.ota
|
||||
//server.Serve()
|
||||
return
|
||||
case "http":
|
||||
req, err := http.ReadRequest(bufio.NewReader(conn))
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[http]", err)
|
||||
return
|
||||
}
|
||||
NewHttpServer(conn, s).HandleRequest(req)
|
||||
return
|
||||
case "socks", "socks5":
|
||||
conn = gosocks5.ServerConn(conn, s.selector)
|
||||
req, err := gosocks5.ReadRequest(conn)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[socks5]", err)
|
||||
return
|
||||
}
|
||||
NewSocks5Server(conn, s).HandleRequest(req)
|
||||
return
|
||||
case "socks4", "socks4a":
|
||||
req, err := gosocks4.ReadRequest(conn)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[socks4]", err)
|
||||
return
|
||||
}
|
||||
NewSocks4Server(conn, s).HandleRequest(req)
|
||||
return
|
||||
}
|
||||
type tcpListener struct {
|
||||
net.Listener
|
||||
}
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
b, err := br.Peek(1)
|
||||
// TCPListener creates a Listener for TCP proxy server.
|
||||
func TCPListener(addr string) (Listener, error) {
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tcpListener{Listener: tcpKeepAliveListener{ln.(*net.TCPListener)}}, nil
|
||||
}
|
||||
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln(err)
|
||||
return
|
||||
}
|
||||
|
||||
switch b[0] {
|
||||
case gosocks4.Ver4:
|
||||
req, err := gosocks4.ReadRequest(br)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[socks4]", err)
|
||||
return
|
||||
}
|
||||
NewSocks4Server(conn, s).HandleRequest(req)
|
||||
|
||||
case gosocks5.Ver5:
|
||||
methods, err := gosocks5.ReadMethods(br)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[socks5]", err)
|
||||
return
|
||||
}
|
||||
method := s.selector.Select(methods...)
|
||||
if _, err := conn.Write([]byte{gosocks5.Ver5, method}); err != nil {
|
||||
glog.V(LWARNING).Infoln("[socks5] select:", err)
|
||||
return
|
||||
}
|
||||
c, err := s.selector.OnSelected(method, conn)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[socks5] onselected:", err)
|
||||
return
|
||||
}
|
||||
conn = c
|
||||
|
||||
req, err := gosocks5.ReadRequest(conn)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[socks5] request:", err)
|
||||
return
|
||||
}
|
||||
NewSocks5Server(conn, s).HandleRequest(req)
|
||||
|
||||
default: // http
|
||||
req, err := http.ReadRequest(br)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[http]", err)
|
||||
return
|
||||
}
|
||||
NewHttpServer(conn, s).HandleRequest(req)
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(KeepAliveTime)
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func (_ *ProxyServer) transport(conn1, conn2 net.Conn) (err error) {
|
||||
errc := make(chan error, 2)
|
||||
|
||||
func transport(rw1, rw2 io.ReadWriter) error {
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := io.Copy(conn1, conn2)
|
||||
_, err := io.Copy(rw1, rw2)
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(conn2, conn1)
|
||||
_, err := io.Copy(rw2, rw1)
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case err = <-errc:
|
||||
// glog.V(LWARNING).Infoln("transport exit", err)
|
||||
err := <-errc
|
||||
if err != nil && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
@ -3,11 +3,12 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"gopkg.in/xtaci/kcp-go.v2"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-log/log"
|
||||
"gopkg.in/xtaci/kcp-go.v2"
|
||||
)
|
||||
|
||||
func kcpSigHandler() {
|
||||
@ -17,7 +18,7 @@ func kcpSigHandler() {
|
||||
for {
|
||||
switch <-ch {
|
||||
case syscall.SIGUSR1:
|
||||
glog.V(LINFO).Infof("[kcp] SNMP: %+v", kcp.DefaultSnmp.Copy())
|
||||
log.Logf("[kcp] SNMP: %+v", kcp.DefaultSnmp.Copy())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
365
ss.go
365
ss.go
@ -3,28 +3,25 @@ package gost
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ginuerzh/gosocks5"
|
||||
"github.com/go-log/log"
|
||||
"github.com/golang/glog"
|
||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||
)
|
||||
|
||||
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
|
||||
// we wrap around it to make io.Copy happy
|
||||
// we wrap around it to make io.Copy happy.
|
||||
type shadowConn struct {
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func ShadowConn(conn net.Conn) net.Conn {
|
||||
return &shadowConn{conn: conn}
|
||||
}
|
||||
|
||||
func (c *shadowConn) Read(b []byte) (n int, err error) {
|
||||
return c.conn.Read(b)
|
||||
}
|
||||
@ -59,18 +56,61 @@ func (c *shadowConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type shadowHandler struct {
|
||||
server Server
|
||||
type shadowConnector struct {
|
||||
Cipher *url.Userinfo
|
||||
}
|
||||
|
||||
func ShadowHandler(server Server) Handler {
|
||||
return &shadowHandler{server: server}
|
||||
// ShadowConnector creates a Connector for shadowsocks proxy client.
|
||||
// It accepts a cipher info for shadowsocks data encryption/decryption.
|
||||
// The cipher must not be nil.
|
||||
func ShadowConnector(cipher *url.Userinfo) Connector {
|
||||
return &shadowConnector{Cipher: cipher}
|
||||
}
|
||||
|
||||
func (c *shadowConnector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
||||
rawaddr, err := ss.RawAddr(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var method, password string
|
||||
if c.Cipher != nil {
|
||||
method = c.Cipher.Username()
|
||||
password, _ = c.Cipher.Password()
|
||||
}
|
||||
|
||||
cipher, err := ss.NewCipher(method, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sc, err := ss.DialWithRawAddrConn(rawaddr, conn, cipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &shadowConn{conn: sc}, nil
|
||||
}
|
||||
|
||||
type shadowHandler struct {
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// ShadowHandler creates a server Handler for shadowsocks proxy server.
|
||||
func ShadowHandler(opts ...HandlerOption) Handler {
|
||||
h := &shadowHandler{
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *shadowHandler) Handle(conn net.Conn) {
|
||||
var method, password string
|
||||
defer conn.Close()
|
||||
|
||||
users := h.server.Options().BaseOptions().Users
|
||||
var method, password string
|
||||
users := h.options.Users
|
||||
if len(users) > 0 {
|
||||
method = users[0].Username()
|
||||
password, _ = users[0].Password()
|
||||
@ -80,7 +120,7 @@ func (h *shadowHandler) Handle(conn net.Conn) {
|
||||
log.Log("[ss]", err)
|
||||
return
|
||||
}
|
||||
conn = ShadowConn(ss.NewConn(conn, cipher))
|
||||
conn = &shadowConn{conn: ss.NewConn(conn, cipher)}
|
||||
|
||||
log.Logf("[ss] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
|
||||
@ -91,7 +131,12 @@ func (h *shadowHandler) Handle(conn net.Conn) {
|
||||
}
|
||||
log.Logf("[ss] %s -> %s", conn.RemoteAddr(), addr)
|
||||
|
||||
cc, err := h.server.Chain().Dial(addr)
|
||||
if !Can("tcp", addr, h.options.Whitelist, h.options.Blacklist) {
|
||||
log.Logf("[ss] Unauthorized to tcp connect to %s", addr)
|
||||
return
|
||||
}
|
||||
|
||||
cc, err := h.options.Chain.Dial(addr)
|
||||
if err != nil {
|
||||
log.Logf("[ss] %s -> %s : %s", conn.RemoteAddr(), addr, err)
|
||||
return
|
||||
@ -99,7 +144,7 @@ func (h *shadowHandler) Handle(conn net.Conn) {
|
||||
defer cc.Close()
|
||||
|
||||
log.Logf("[ss] %s <-> %s", conn.RemoteAddr(), addr)
|
||||
Transport(conn, cc)
|
||||
transport(conn, cc)
|
||||
log.Logf("[ss] %s >-< %s", conn.RemoteAddr(), addr)
|
||||
}
|
||||
|
||||
@ -124,7 +169,7 @@ func (h *shadowHandler) getRequest(conn net.Conn) (host string, err error) {
|
||||
// buf size should at least have the same size with the largest possible
|
||||
// request size (when addrType is 3, domain name has at most 256 bytes)
|
||||
// 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
|
||||
buf := make([]byte, SmallBufferSize)
|
||||
buf := make([]byte, smallBufferSize)
|
||||
|
||||
// read till we get possible domain length field
|
||||
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||
@ -170,117 +215,205 @@ func (h *shadowHandler) getRequest(conn net.Conn) (host string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type ShadowUdpServer struct {
|
||||
Base *ProxyServer
|
||||
TTL int
|
||||
type shadowUDPListener struct {
|
||||
ln net.PacketConn
|
||||
conns map[string]*udpServerConn
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
func NewShadowUdpServer(base *ProxyServer, ttl int) *ShadowUdpServer {
|
||||
return &ShadowUdpServer{Base: base, TTL: ttl}
|
||||
// ShadowUDPListener creates a Listener for shadowsocks UDP relay server.
|
||||
func ShadowUDPListener(addr string, cipher *url.Userinfo, ttl time.Duration) (Listener, error) {
|
||||
laddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ln, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var method, password string
|
||||
if cipher != nil {
|
||||
method = cipher.Username()
|
||||
password, _ = cipher.Password()
|
||||
}
|
||||
cp, err := ss.NewCipher(method, password)
|
||||
if err != nil {
|
||||
ln.Close()
|
||||
return nil, err
|
||||
}
|
||||
l := &shadowUDPListener{
|
||||
ln: ss.NewSecurePacketConn(ln, cp, false),
|
||||
conns: make(map[string]*udpServerConn),
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
ttl: ttl,
|
||||
}
|
||||
go l.listenLoop()
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (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()
|
||||
func (l *shadowUDPListener) listenLoop() {
|
||||
for {
|
||||
b := make([]byte, mediumBufferSize)
|
||||
n, raddr, err := l.ln.ReadFrom(b)
|
||||
if err != nil {
|
||||
log.Logf("[ssu] peer -> %s : %s", l.Addr(), err)
|
||||
l.ln.Close()
|
||||
l.errChan <- err
|
||||
close(l.errChan)
|
||||
return
|
||||
}
|
||||
if Debug {
|
||||
log.Logf("[ssu] %s >>> %s : length %d", raddr, l.Addr(), n)
|
||||
}
|
||||
|
||||
conn := 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
|
||||
}
|
||||
conn, ok := l.conns[raddr.String()]
|
||||
if !ok || conn.Closed() {
|
||||
conn = newUDPServerConn(l.ln, raddr, l.ttl)
|
||||
l.conns[raddr.String()] = conn
|
||||
|
||||
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")
|
||||
case l.connChan <- conn:
|
||||
default:
|
||||
conn.Close()
|
||||
log.Logf("[ssu] %s - %s: connection queue is full", raddr, l.Addr())
|
||||
}
|
||||
}
|
||||
}(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")
|
||||
case conn.rChan <- b[:n]: // we keep the addr info so that the handler can identify the destination.
|
||||
default:
|
||||
log.Logf("[ssu] %s -> %s : read queue is full", raddr, l.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *shadowUDPListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *shadowUDPListener) Addr() net.Addr {
|
||||
return l.ln.LocalAddr()
|
||||
}
|
||||
|
||||
func (l *shadowUDPListener) Close() error {
|
||||
return l.ln.Close()
|
||||
}
|
||||
|
||||
type shadowUDPdHandler struct {
|
||||
ttl time.Duration
|
||||
options *HandlerOptions
|
||||
}
|
||||
|
||||
// ShadowUDPdHandler creates a server Handler for shadowsocks UDP relay server.
|
||||
func ShadowUDPdHandler(opts ...HandlerOption) Handler {
|
||||
h := &shadowUDPdHandler{
|
||||
options: &HandlerOptions{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *shadowUDPdHandler) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
var err error
|
||||
var cc net.PacketConn
|
||||
if h.options.Chain.IsEmpty() {
|
||||
cc, err = net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var c net.Conn
|
||||
c, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
|
||||
if err != nil {
|
||||
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
cc = &udpTunnelConn{Conn: c}
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
transportUDP(conn, cc)
|
||||
log.Logf("[ssu] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
|
||||
func transportUDP(sc net.Conn, cc net.PacketConn) error {
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
for {
|
||||
b := make([]byte, mediumBufferSize)
|
||||
n, err := sc.Read(b[3:]) // add rsv and frag fields to make it the standard SOCKS5 UDP datagram
|
||||
if err != nil {
|
||||
// log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err)
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n+3]))
|
||||
if err != nil {
|
||||
log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err)
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
//if Debug {
|
||||
// log.Logf("[ssu] %s >>> %s length: %d", sc.RemoteAddr(), dgram.Header.Addr.String(), len(dgram.Data))
|
||||
//}
|
||||
addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String())
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
if _, err := cc.WriteTo(dgram.Data, addr); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
b := make([]byte, mediumBufferSize)
|
||||
n, addr, err := cc.ReadFrom(b)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
//if Debug {
|
||||
// log.Logf("[ssu] %s <<< %s length: %d", sc.RemoteAddr(), addr, n)
|
||||
//}
|
||||
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, toSocksAddr(addr)), b[:n])
|
||||
buf := bytes.Buffer{}
|
||||
dgram.Write(&buf)
|
||||
if buf.Len() < 10 {
|
||||
log.Logf("[ssu] %s <- %s : invalid udp datagram", sc.RemoteAddr(), addr)
|
||||
continue
|
||||
}
|
||||
if _, err := sc.Write(buf.Bytes()[3:]); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err := <-errc
|
||||
if err != nil && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
730
ssh.go
730
ssh.go
@ -1,15 +1,19 @@
|
||||
// The ssh tunnel is inspired by easyssh(https://dev.justinjudd.org/justin/easyssh)
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/go-log/log"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@ -19,58 +23,436 @@ const (
|
||||
RemoteForwardRequest = "tcpip-forward" // RFC 4254 7.1
|
||||
ForwardedTCPReturnRequest = "forwarded-tcpip" // RFC 4254 7.2
|
||||
CancelRemoteForwardRequest = "cancel-tcpip-forward" // RFC 4254 7.1
|
||||
|
||||
GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel
|
||||
)
|
||||
|
||||
type SSHServer struct {
|
||||
Addr string
|
||||
Base *ProxyServer
|
||||
Config *ssh.ServerConfig
|
||||
Handler func(ssh.Conn, <-chan ssh.NewChannel, <-chan *ssh.Request)
|
||||
var (
|
||||
errSessionDead = errors.New("session is dead")
|
||||
)
|
||||
|
||||
type sshDirectForwardConnector struct {
|
||||
}
|
||||
|
||||
func (s *SSHServer) ListenAndServe() error {
|
||||
ln, err := net.Listen("tcp", s.Addr)
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[ssh] Listen:", err)
|
||||
return err
|
||||
func SSHDirectForwardConnector() Connector {
|
||||
return &sshDirectForwardConnector{}
|
||||
}
|
||||
|
||||
func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string) (net.Conn, error) {
|
||||
cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution.
|
||||
if !ok {
|
||||
return nil, errors.New("ssh: wrong connection type")
|
||||
}
|
||||
defer ln.Close()
|
||||
conn, err := cc.session.client.Dial("tcp", raddr)
|
||||
if err != nil {
|
||||
log.Logf("[ssh-tcp] %s -> %s : %s", cc.session.addr, raddr, err)
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[ssh] Accept:", err)
|
||||
return err
|
||||
}
|
||||
type sshRemoteForwardConnector struct {
|
||||
}
|
||||
|
||||
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)
|
||||
func SSHRemoteForwardConnector() Connector {
|
||||
return &sshRemoteForwardConnector{}
|
||||
}
|
||||
|
||||
func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string) (net.Conn, error) {
|
||||
cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution.
|
||||
if !ok {
|
||||
return nil, errors.New("ssh: wrong connection type")
|
||||
}
|
||||
|
||||
cc.session.once.Do(func() {
|
||||
go func() {
|
||||
defer log.Log("ssh-rtcp: session is closed")
|
||||
defer close(cc.session.connChan)
|
||||
|
||||
if cc.session == nil || cc.session.client == nil {
|
||||
return
|
||||
}
|
||||
defer sshConn.Close()
|
||||
|
||||
if s.Handler == nil {
|
||||
s.Handler = s.handleSSHConn
|
||||
if strings.HasPrefix(addr, ":") {
|
||||
addr = "0.0.0.0" + addr
|
||||
}
|
||||
ln, err := cc.session.client.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
rc, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Logf("[ssh-rtcp] %s <-> %s accpet : %s", ln.Addr(), addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
select {
|
||||
case cc.session.connChan <- rc:
|
||||
default:
|
||||
rc.Close()
|
||||
log.Logf("[ssh-rtcp] %s - %s: connection queue is full", ln.Addr(), addr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
sc, ok := <-cc.session.connChan
|
||||
if !ok {
|
||||
return nil, errors.New("ssh-rtcp: connection is closed")
|
||||
}
|
||||
return sc, nil
|
||||
}
|
||||
|
||||
type sshForwardTransporter struct {
|
||||
sessions map[string]*sshSession
|
||||
sessionMutex sync.Mutex
|
||||
}
|
||||
|
||||
func SSHForwardTransporter() Transporter {
|
||||
return &sshForwardTransporter{
|
||||
sessions: make(map[string]*sshSession),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SSHServer) handleSSHConn(conn ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) {
|
||||
quit := make(chan interface{})
|
||||
func (tr *sshForwardTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) {
|
||||
opts := &DialOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[addr]
|
||||
if !ok || session.Closed() {
|
||||
if opts.Chain == nil {
|
||||
conn, err = net.DialTimeout("tcp", addr, opts.Timeout)
|
||||
} else {
|
||||
conn, err = opts.Chain.Dial(addr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session = &sshSession{
|
||||
addr: addr,
|
||||
conn: conn,
|
||||
}
|
||||
tr.sessions[addr] = session
|
||||
}
|
||||
|
||||
return session.conn, nil
|
||||
}
|
||||
|
||||
func (tr *sshForwardTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
Timeout: opts.Timeout,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
if opts.User != nil {
|
||||
config.User = opts.User.Username()
|
||||
password, _ := opts.User.Password()
|
||||
config.Auth = []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
}
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[opts.Addr]
|
||||
if session != nil && session.conn != conn {
|
||||
conn.Close()
|
||||
return nil, errors.New("ssh: unrecognized connection")
|
||||
}
|
||||
if !ok || session.client == nil {
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session = &sshSession{
|
||||
addr: opts.Addr,
|
||||
conn: conn,
|
||||
client: ssh.NewClient(sshConn, chans, reqs),
|
||||
closed: make(chan struct{}),
|
||||
deaded: make(chan struct{}),
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
}
|
||||
tr.sessions[opts.Addr] = session
|
||||
go session.Ping(opts.Interval, opts.Timeout, 1)
|
||||
go session.waitServer()
|
||||
go session.waitClose()
|
||||
}
|
||||
if session.Closed() {
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, errSessionDead
|
||||
}
|
||||
|
||||
return &sshNopConn{session: session}, nil
|
||||
}
|
||||
|
||||
func (tr *sshForwardTransporter) Multiplex() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type sshTunnelTransporter struct {
|
||||
sessions map[string]*sshSession
|
||||
sessionMutex sync.Mutex
|
||||
}
|
||||
|
||||
// SSHTunnelTransporter creates a Transporter that is used by SSH tunnel client.
|
||||
func SSHTunnelTransporter() Transporter {
|
||||
return &sshTunnelTransporter{
|
||||
sessions: make(map[string]*sshSession),
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *sshTunnelTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) {
|
||||
opts := &DialOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[addr]
|
||||
if !ok || session.Closed() {
|
||||
if opts.Chain == nil {
|
||||
conn, err = net.DialTimeout("tcp", addr, opts.Timeout)
|
||||
} else {
|
||||
conn, err = opts.Chain.Dial(addr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session = &sshSession{
|
||||
addr: addr,
|
||||
conn: conn,
|
||||
}
|
||||
tr.sessions[addr] = session
|
||||
}
|
||||
|
||||
return session.conn, nil
|
||||
}
|
||||
|
||||
func (tr *sshTunnelTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
Timeout: opts.Timeout,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
if opts.User != nil {
|
||||
config.User = opts.User.Username()
|
||||
password, _ := opts.User.Password()
|
||||
config.Auth = []ssh.AuthMethod{
|
||||
ssh.Password(password),
|
||||
}
|
||||
}
|
||||
|
||||
tr.sessionMutex.Lock()
|
||||
defer tr.sessionMutex.Unlock()
|
||||
|
||||
session, ok := tr.sessions[opts.Addr]
|
||||
if session != nil && session.conn != conn {
|
||||
conn.Close()
|
||||
return nil, errors.New("ssh: unrecognized connection")
|
||||
}
|
||||
if !ok || session.client == nil {
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session = &sshSession{
|
||||
addr: opts.Addr,
|
||||
conn: conn,
|
||||
client: ssh.NewClient(sshConn, chans, reqs),
|
||||
closed: make(chan struct{}),
|
||||
deaded: make(chan struct{}),
|
||||
}
|
||||
tr.sessions[opts.Addr] = session
|
||||
go session.Ping(opts.Interval, opts.Timeout, 1)
|
||||
go session.waitServer()
|
||||
go session.waitClose()
|
||||
}
|
||||
|
||||
if session.Closed() {
|
||||
delete(tr.sessions, opts.Addr)
|
||||
return nil, errSessionDead
|
||||
}
|
||||
|
||||
channel, reqs, err := session.client.OpenChannel(GostSSHTunnelRequest, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
return &sshConn{channel: channel, conn: conn}, nil
|
||||
}
|
||||
|
||||
func (tr *sshTunnelTransporter) Multiplex() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type sshSession struct {
|
||||
addr string
|
||||
conn net.Conn
|
||||
client *ssh.Client
|
||||
closed chan struct{}
|
||||
deaded chan struct{}
|
||||
once sync.Once
|
||||
connChan chan net.Conn
|
||||
}
|
||||
|
||||
func (s *sshSession) Ping(interval, timeout time.Duration, retries int) {
|
||||
if interval <= 0 {
|
||||
return
|
||||
}
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
defer close(s.deaded)
|
||||
|
||||
log.Log("[ssh] ping is enabled, interval:", interval)
|
||||
baseCtx := context.Background()
|
||||
t := time.NewTicker(interval)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
start := time.Now()
|
||||
//if Debug {
|
||||
log.Log("[ssh] sending ping")
|
||||
//}
|
||||
ctx, cancel := context.WithTimeout(baseCtx, timeout)
|
||||
var err error
|
||||
select {
|
||||
case err = <-s.sendPing():
|
||||
case <-ctx.Done():
|
||||
err = errors.New("Timeout")
|
||||
}
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Log("[ssh] ping:", err)
|
||||
return
|
||||
}
|
||||
//if Debug {
|
||||
log.Log("[ssh] ping OK, RTT:", time.Since(start))
|
||||
//}
|
||||
|
||||
case <-s.closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sshSession) sendPing() <-chan error {
|
||||
ch := make(chan error, 1)
|
||||
go func() {
|
||||
if _, _, err := s.client.SendRequest("ping", true, nil); err != nil {
|
||||
ch <- err
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (s *sshSession) waitServer() error {
|
||||
defer close(s.closed)
|
||||
return s.client.Wait()
|
||||
}
|
||||
|
||||
func (s *sshSession) waitClose() {
|
||||
defer s.client.Close()
|
||||
|
||||
select {
|
||||
case <-s.deaded:
|
||||
case <-s.closed:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sshSession) Closed() bool {
|
||||
select {
|
||||
case <-s.deaded:
|
||||
return true
|
||||
case <-s.closed:
|
||||
return true
|
||||
default:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type sshForwardHandler struct {
|
||||
options *HandlerOptions
|
||||
config *ssh.ServerConfig
|
||||
}
|
||||
|
||||
func SSHForwardHandler(opts ...HandlerOption) Handler {
|
||||
h := &sshForwardHandler{
|
||||
options: new(HandlerOptions),
|
||||
config: new(ssh.ServerConfig),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(h.options)
|
||||
}
|
||||
h.config.PasswordCallback = defaultSSHPasswordCallback(h.options.Users...)
|
||||
if len(h.options.Users) == 0 {
|
||||
h.config.NoClientAuth = true
|
||||
}
|
||||
tlsConfig := h.options.TLSConfig
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = DefaultTLSConfig
|
||||
}
|
||||
if tlsConfig != nil && len(tlsConfig.Certificates) > 0 {
|
||||
signer, err := ssh.NewSignerFromKey(tlsConfig.Certificates[0].PrivateKey)
|
||||
if err != nil {
|
||||
log.Log("[ssh-forward]", err)
|
||||
}
|
||||
h.config.AddHostKey(signer)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *sshForwardHandler) Handle(conn net.Conn) {
|
||||
sshConn, chans, reqs, err := ssh.NewServerConn(conn, h.config)
|
||||
if err != nil {
|
||||
log.Logf("[ssh-forward] %s -> %s : %s", conn.RemoteAddr(), h.options.Addr, err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
defer sshConn.Close()
|
||||
|
||||
log.Logf("[ssh-forward] %s <-> %s", conn.RemoteAddr(), h.options.Addr)
|
||||
h.handleForward(sshConn, chans, reqs)
|
||||
log.Logf("[ssh-forward] %s >-< %s", conn.RemoteAddr(), h.options.Addr)
|
||||
}
|
||||
|
||||
func (h *sshForwardHandler) handleForward(conn ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) {
|
||||
quit := make(chan struct{})
|
||||
defer close(quit) // quit signal
|
||||
|
||||
go func() {
|
||||
for req := range reqs {
|
||||
switch req.Type {
|
||||
case RemoteForwardRequest:
|
||||
go s.tcpipForwardRequest(conn, req, quit)
|
||||
go h.tcpipForwardRequest(conn, req, quit)
|
||||
default:
|
||||
// glog.V(LWARNING).Infoln("unknown channel type:", req.Type)
|
||||
// log.Log("[ssh] unknown channel type:", req.Type)
|
||||
if req.WantReply {
|
||||
req.Reply(false, nil)
|
||||
}
|
||||
@ -86,7 +468,7 @@ func (s *SSHServer) handleSSHConn(conn ssh.Conn, chans <-chan ssh.NewChannel, re
|
||||
case DirectForwardRequest:
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
glog.V(LINFO).Infoln("[ssh] Could not accept channel:", err)
|
||||
log.Log("[ssh] Could not accept channel:", err)
|
||||
continue
|
||||
}
|
||||
p := directForward{}
|
||||
@ -97,50 +479,37 @@ func (s *SSHServer) handleSSHConn(conn ssh.Conn, chans <-chan ssh.NewChannel, re
|
||||
}
|
||||
|
||||
go ssh.DiscardRequests(requests)
|
||||
go s.directPortForwardChannel(channel, fmt.Sprintf("%s:%d", p.Host1, p.Port1))
|
||||
go h.directPortForwardChannel(channel, fmt.Sprintf("%s:%d", p.Host1, p.Port1))
|
||||
default:
|
||||
glog.V(LWARNING).Infoln("[ssh] Unknown channel type:", t)
|
||||
log.Log("[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) {
|
||||
func (h *sshForwardHandler) directPortForwardChannel(channel ssh.Channel, raddr string) {
|
||||
defer channel.Close()
|
||||
|
||||
glog.V(LINFO).Infof("[ssh-tcp] %s - %s", s.Addr, raddr)
|
||||
log.Logf("[ssh-tcp] %s - %s", h.options.Addr, raddr)
|
||||
|
||||
if !s.Base.Node.Can("tcp", raddr) {
|
||||
glog.Errorf("Unauthorized to tcp connect to %s", raddr)
|
||||
if !Can("tcp", raddr, h.options.Whitelist, h.options.Blacklist) {
|
||||
log.Logf("[ssh-tcp] Unauthorized to tcp connect to %s", raddr)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := s.Base.Chain.Dial(raddr)
|
||||
conn, err := h.options.Chain.Dial(raddr)
|
||||
if err != nil {
|
||||
glog.V(LINFO).Infof("[ssh-tcp] %s - %s : %s", s.Addr, raddr, err)
|
||||
log.Logf("[ssh-tcp] %s - %s : %s", h.options.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)
|
||||
log.Logf("[ssh-tcp] %s <-> %s", h.options.Addr, raddr)
|
||||
transport(conn, channel)
|
||||
log.Logf("[ssh-tcp] %s >-< %s", h.options.Addr, raddr)
|
||||
}
|
||||
|
||||
// tcpipForward is structure for RFC 4254 7.1 "tcpip-forward" request
|
||||
@ -149,22 +518,22 @@ type tcpipForward struct {
|
||||
Port uint32
|
||||
}
|
||||
|
||||
func (s *SSHServer) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit <-chan interface{}) {
|
||||
func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit <-chan struct{}) {
|
||||
t := tcpipForward{}
|
||||
ssh.Unmarshal(req.Payload, &t)
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", t.Host, t.Port)
|
||||
|
||||
if !s.Base.Node.Can("rtcp", addr) {
|
||||
glog.Errorf("Unauthorized to tcp bind to %s", addr)
|
||||
if !Can("rtcp", addr, h.options.Whitelist, h.options.Blacklist) {
|
||||
log.Logf("[ssh-rtcp] Unauthorized to tcp bind to %s", addr)
|
||||
req.Reply(false, nil)
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(LINFO).Infoln("[ssh-rtcp] listening tcp", addr)
|
||||
log.Log("[ssh-rtcp] listening on tcp", addr)
|
||||
ln, err := net.Listen("tcp", addr) //tie to the client connection
|
||||
if err != nil {
|
||||
glog.V(LWARNING).Infoln("[ssh-rtcp]", err)
|
||||
log.Log("[ssh-rtcp]", err)
|
||||
req.Reply(false, nil)
|
||||
return
|
||||
}
|
||||
@ -184,14 +553,14 @@ func (s *SSHServer) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit
|
||||
return req.Reply(true, nil)
|
||||
}
|
||||
if err := replyFunc(); err != nil {
|
||||
glog.V(LWARNING).Infoln("[ssh-rtcp]", err)
|
||||
log.Log("[ssh-rtcp]", err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil { // Unable to accept new connection - listener likely closed
|
||||
if err != nil { // Unable to accept new connection - listener is likely closed
|
||||
return
|
||||
}
|
||||
|
||||
@ -210,18 +579,17 @@ func (s *SSHServer) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit
|
||||
}
|
||||
|
||||
p.Port2 = uint32(portnum)
|
||||
glog.V(3).Info(p)
|
||||
ch, reqs, err := sshConn.OpenChannel(ForwardedTCPReturnRequest, ssh.Marshal(p))
|
||||
if err != nil {
|
||||
glog.V(1).Infoln("[ssh-rtcp] open forwarded channel:", err)
|
||||
log.Log("[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())
|
||||
log.Logf("[ssh-rtcp] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
transport(ch, conn)
|
||||
log.Logf("[ssh-rtcp] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}(conn)
|
||||
}
|
||||
}()
|
||||
@ -229,6 +597,139 @@ func (s *SSHServer) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit
|
||||
<-quit
|
||||
}
|
||||
|
||||
// SSHConfig holds the SSH tunnel server config
|
||||
type SSHConfig struct {
|
||||
Users []*url.Userinfo
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
type sshTunnelListener struct {
|
||||
net.Listener
|
||||
config *ssh.ServerConfig
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// SSHTunnelListener creates a Listener for SSH tunnel server.
|
||||
func SSHTunnelListener(addr string, config *SSHConfig) (Listener, error) {
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
config = &SSHConfig{}
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ServerConfig{}
|
||||
sshConfig.PasswordCallback = defaultSSHPasswordCallback(config.Users...)
|
||||
if len(config.Users) == 0 {
|
||||
sshConfig.NoClientAuth = true
|
||||
}
|
||||
tlsConfig := config.TLSConfig
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = DefaultTLSConfig
|
||||
}
|
||||
|
||||
signer, err := ssh.NewSignerFromKey(tlsConfig.Certificates[0].PrivateKey)
|
||||
if err != nil {
|
||||
ln.Close()
|
||||
return nil, err
|
||||
|
||||
}
|
||||
sshConfig.AddHostKey(signer)
|
||||
|
||||
l := &sshTunnelListener{
|
||||
Listener: tcpKeepAliveListener{ln.(*net.TCPListener)},
|
||||
config: sshConfig,
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
}
|
||||
|
||||
go l.listenLoop()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *sshTunnelListener) listenLoop() {
|
||||
for {
|
||||
conn, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
log.Log("[ssh] accept:", err)
|
||||
l.errChan <- err
|
||||
close(l.errChan)
|
||||
return
|
||||
}
|
||||
go l.serveConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *sshTunnelListener) serveConn(conn net.Conn) {
|
||||
sc, chans, reqs, err := ssh.NewServerConn(conn, l.config)
|
||||
if err != nil {
|
||||
log.Logf("[ssh] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
defer sc.Close()
|
||||
|
||||
go ssh.DiscardRequests(reqs)
|
||||
go func() {
|
||||
for newChannel := range chans {
|
||||
// Check the type of channel
|
||||
t := newChannel.ChannelType()
|
||||
switch t {
|
||||
case GostSSHTunnelRequest:
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Log("[ssh] Could not accept channel:", err)
|
||||
continue
|
||||
}
|
||||
go ssh.DiscardRequests(requests)
|
||||
cc := &sshConn{conn: conn, channel: channel}
|
||||
select {
|
||||
case l.connChan <- cc:
|
||||
default:
|
||||
cc.Close()
|
||||
log.Logf("[ssh] %s - %s: connection queue is full", conn.RemoteAddr(), l.Addr())
|
||||
}
|
||||
|
||||
default:
|
||||
log.Log("[ssh] Unknown channel type:", t)
|
||||
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Logf("[ssh] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
sc.Wait()
|
||||
log.Logf("[ssh] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
|
||||
func (l *sshTunnelListener) Accept() (conn net.Conn, err error) {
|
||||
var ok bool
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err, ok = <-l.errChan:
|
||||
if !ok {
|
||||
err = errors.New("accpet on closed listener")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// directForward is structure for RFC 4254 7.2 - can be used for "forwarded-tcpip" and "direct-tcpip"
|
||||
type directForward struct {
|
||||
Host1 string
|
||||
Port1 uint32
|
||||
Host2 string
|
||||
Port2 uint32
|
||||
}
|
||||
|
||||
func (p directForward) String() string {
|
||||
return fmt.Sprintf("%s:%d -> %s:%d", p.Host2, p.Port2, p.Host1, p.Port1)
|
||||
}
|
||||
|
||||
func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
|
||||
host, portString, err := net.SplitHostPort(addr.String())
|
||||
if err != nil {
|
||||
@ -240,7 +741,7 @@ func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
|
||||
|
||||
type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
|
||||
|
||||
func DefaultPasswordCallback(users []*url.Userinfo) PasswordCallbackFunc {
|
||||
func defaultSSHPasswordCallback(users ...*url.Userinfo) PasswordCallbackFunc {
|
||||
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||
for _, user := range users {
|
||||
u := user.Username()
|
||||
@ -249,7 +750,86 @@ func DefaultPasswordCallback(users []*url.Userinfo) PasswordCallbackFunc {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
glog.V(LINFO).Infof("[ssh] %s -> %s : password rejected for %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User())
|
||||
log.Logf("[ssh] %s -> %s : password rejected for %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User())
|
||||
return nil, fmt.Errorf("password rejected for %s", conn.User())
|
||||
}
|
||||
}
|
||||
|
||||
type sshNopConn struct {
|
||||
session *sshSession
|
||||
}
|
||||
|
||||
func (c *sshNopConn) Read(b []byte) (n int, err error) {
|
||||
return 0, &net.OpError{Op: "read", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("read not supported")}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) Write(b []byte) (n int, err error) {
|
||||
return 0, &net.OpError{Op: "write", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("write not supported")}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *sshNopConn) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *sshNopConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
type sshConn struct {
|
||||
channel ssh.Channel
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (c *sshConn) Read(b []byte) (n int, err error) {
|
||||
return c.channel.Read(b)
|
||||
}
|
||||
|
||||
func (c *sshConn) Write(b []byte) (n int, err error) {
|
||||
return c.channel.Write(b)
|
||||
}
|
||||
|
||||
func (c *sshConn) Close() error {
|
||||
return c.channel.Close()
|
||||
}
|
||||
|
||||
func (c *sshConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *sshConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *sshConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *sshConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *sshConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
@ -34,11 +34,14 @@ type tlsListener struct {
|
||||
|
||||
// TLSListener creates a Listener for TLS proxy server.
|
||||
func TLSListener(addr string, config *tls.Config) (Listener, error) {
|
||||
if config == nil {
|
||||
config = DefaultTLSConfig
|
||||
}
|
||||
ln, err := tls.Listen("tcp", addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tlsListener{ln}, nil
|
||||
return &tlsListener{tcpKeepAliveListener{ln.(*net.TCPListener)}}, nil
|
||||
}
|
||||
|
||||
// Wrap a net.Conn into a client tls connection, performing any
|
306
ws.go
306
ws.go
@ -5,119 +5,57 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-log/log"
|
||||
"gopkg.in/gorilla/websocket.v1"
|
||||
)
|
||||
|
||||
type WebsocketServer struct {
|
||||
Addr string
|
||||
Base *ProxyServer
|
||||
Handler http.Handler
|
||||
upgrader websocket.Upgrader
|
||||
}
|
||||
|
||||
func NewWebsocketServer(base *ProxyServer) *WebsocketServer {
|
||||
rbuf, _ := strconv.Atoi(base.Node.Get("rbuf"))
|
||||
wbuf, _ := strconv.Atoi(base.Node.Get("wbuf"))
|
||||
comp := base.Node.getBool("compression")
|
||||
|
||||
return &WebsocketServer{
|
||||
Addr: base.Node.Addr,
|
||||
Base: base,
|
||||
upgrader: websocket.Upgrader{
|
||||
ReadBufferSize: rbuf,
|
||||
WriteBufferSize: wbuf,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
EnableCompression: comp,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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("", "")
|
||||
}
|
||||
|
||||
// WSOptions describes the options for websocket.
|
||||
type WSOptions struct {
|
||||
ReadBufferSize int
|
||||
WriteBufferSize int
|
||||
HandshakeTimeout time.Duration
|
||||
EnableCompression bool
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
type WebsocketConn struct {
|
||||
type websocketConn struct {
|
||||
conn *websocket.Conn
|
||||
rb []byte
|
||||
}
|
||||
|
||||
func WebsocketClientConn(url string, conn net.Conn, options *WSOptions) (*WebsocketConn, error) {
|
||||
func websocketClientConn(url string, conn net.Conn, tlsConfig *tls.Config, options *WSOptions) (net.Conn, error) {
|
||||
if options == nil {
|
||||
options = &WSOptions{}
|
||||
}
|
||||
dialer := websocket.Dialer{
|
||||
ReadBufferSize: options.ReadBufferSize,
|
||||
WriteBufferSize: options.WriteBufferSize,
|
||||
TLSClientConfig: options.TLSConfig,
|
||||
TLSClientConfig: tlsConfig,
|
||||
HandshakeTimeout: options.HandshakeTimeout,
|
||||
EnableCompression: options.EnableCompression,
|
||||
NetDial: func(net, addr string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
|
||||
c, resp, err := dialer.Dial(url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return &WebsocketConn{conn: c}, nil
|
||||
return &websocketConn{conn: c}, nil
|
||||
}
|
||||
|
||||
func WebsocketServerConn(conn *websocket.Conn) *WebsocketConn {
|
||||
conn.EnableWriteCompression(true)
|
||||
return &WebsocketConn{
|
||||
func websocketServerConn(conn *websocket.Conn) net.Conn {
|
||||
// conn.EnableWriteCompression(true)
|
||||
return &websocketConn{
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
||||
func (c *websocketConn) Read(b []byte) (n int, err error) {
|
||||
if len(c.rb) == 0 {
|
||||
_, c.rb, err = c.conn.ReadMessage()
|
||||
}
|
||||
@ -126,34 +64,236 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) Write(b []byte) (n int, err error) {
|
||||
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 {
|
||||
func (c *websocketConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) LocalAddr() net.Addr {
|
||||
func (c *websocketConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) RemoteAddr() net.Addr {
|
||||
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 {
|
||||
func (c *websocketConn) SetDeadline(t time.Time) error {
|
||||
if err := c.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return conn.SetWriteDeadline(t)
|
||||
return c.SetWriteDeadline(t)
|
||||
}
|
||||
func (c *WebsocketConn) SetReadDeadline(t time.Time) error {
|
||||
func (c *websocketConn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) SetWriteDeadline(t time.Time) error {
|
||||
func (c *websocketConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type wsTransporter struct {
|
||||
*tcpTransporter
|
||||
options *WSOptions
|
||||
}
|
||||
|
||||
// WSTransporter creates a Transporter that is used by websocket proxy client.
|
||||
func WSTransporter(opts *WSOptions) Transporter {
|
||||
return &wsTransporter{
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *wsTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
wsOptions := tr.options
|
||||
if opts.WSOptions != nil {
|
||||
wsOptions = opts.WSOptions
|
||||
}
|
||||
url := url.URL{Scheme: "ws", Host: opts.Addr, Path: "/ws"}
|
||||
return websocketClientConn(url.String(), conn, nil, wsOptions)
|
||||
}
|
||||
|
||||
type wssTransporter struct {
|
||||
*tcpTransporter
|
||||
options *WSOptions
|
||||
}
|
||||
|
||||
// WSSTransporter creates a Transporter that is used by websocket secure proxy client.
|
||||
func WSSTransporter(opts *WSOptions) Transporter {
|
||||
return &wssTransporter{
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *wssTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||
opts := &HandshakeOptions{}
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
wsOptions := tr.options
|
||||
if opts.WSOptions != nil {
|
||||
wsOptions = opts.WSOptions
|
||||
}
|
||||
if opts.TLSConfig == nil {
|
||||
opts.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
url := url.URL{Scheme: "wss", Host: opts.Addr, Path: "/ws"}
|
||||
return websocketClientConn(url.String(), conn, opts.TLSConfig, wsOptions)
|
||||
}
|
||||
|
||||
type wsListener struct {
|
||||
addr net.Addr
|
||||
upgrader *websocket.Upgrader
|
||||
srv *http.Server
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// WSListener creates a Listener for websocket proxy server.
|
||||
func WSListener(addr string, options *WSOptions) (Listener, error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options == nil {
|
||||
options = &WSOptions{}
|
||||
}
|
||||
l := &wsListener{
|
||||
addr: tcpAddr,
|
||||
upgrader: &websocket.Upgrader{
|
||||
ReadBufferSize: options.ReadBufferSize,
|
||||
WriteBufferSize: options.WriteBufferSize,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
EnableCompression: options.EnableCompression,
|
||||
},
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", http.HandlerFunc(l.upgrade))
|
||||
l.srv = &http.Server{Addr: addr, Handler: mux}
|
||||
|
||||
ln, err := net.ListenTCP("tcp", tcpAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := l.srv.Serve(tcpKeepAliveListener{ln})
|
||||
if err != nil {
|
||||
l.errChan <- err
|
||||
}
|
||||
close(l.errChan)
|
||||
}()
|
||||
select {
|
||||
case err := <-l.errChan:
|
||||
return nil, err
|
||||
default:
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *wsListener) upgrade(w http.ResponseWriter, r *http.Request) {
|
||||
log.Logf("[ws] %s -> %s", r.RemoteAddr, l.addr)
|
||||
if Debug {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
log.Log(string(dump))
|
||||
}
|
||||
conn, err := l.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Logf("[ws] %s - %s : %s", r.RemoteAddr, l.addr, err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case l.connChan <- websocketServerConn(conn):
|
||||
default:
|
||||
conn.Close()
|
||||
log.Logf("[ws] %s - %s: connection queue is full", r.RemoteAddr, l.addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *wsListener) Accept() (conn net.Conn, err error) {
|
||||
select {
|
||||
case conn = <-l.connChan:
|
||||
case err = <-l.errChan:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *wsListener) Close() error {
|
||||
return l.srv.Close()
|
||||
}
|
||||
|
||||
func (l *wsListener) Addr() net.Addr {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
type wssListener struct {
|
||||
*wsListener
|
||||
}
|
||||
|
||||
// WSSListener creates a Listener for websocket secure proxy server.
|
||||
func WSSListener(addr string, tlsConfig *tls.Config, options *WSOptions) (Listener, error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options == nil {
|
||||
options = &WSOptions{}
|
||||
}
|
||||
l := &wssListener{
|
||||
wsListener: &wsListener{
|
||||
addr: tcpAddr,
|
||||
upgrader: &websocket.Upgrader{
|
||||
ReadBufferSize: options.ReadBufferSize,
|
||||
WriteBufferSize: options.WriteBufferSize,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
EnableCompression: options.EnableCompression,
|
||||
},
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
errChan: make(chan error, 1),
|
||||
},
|
||||
}
|
||||
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = DefaultTLSConfig
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", http.HandlerFunc(l.upgrade))
|
||||
l.srv = &http.Server{
|
||||
Addr: addr,
|
||||
TLSConfig: tlsConfig,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
ln, err := net.ListenTCP("tcp", tcpAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := l.srv.Serve(tls.NewListener(tcpKeepAliveListener{ln}, tlsConfig))
|
||||
if err != nil {
|
||||
l.errChan <- err
|
||||
}
|
||||
close(l.errChan)
|
||||
}()
|
||||
select {
|
||||
case err := <-l.errChan:
|
||||
return nil, err
|
||||
default:
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user