Commit e6adca2a authored by Adam Stankiewicz's avatar Adam Stankiewicz Committed by ginuerzh

Remove gost itself from vendored packages

parent 1ed6f0ba
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.
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类型代理。
This diff is collapsed.
This diff is collapsed.
package gost
import (
"bufio"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"github.com/ginuerzh/gosocks4"
"github.com/ginuerzh/gosocks5"
"github.com/golang/glog"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"sync"
"time"
)
type ProxyConn struct {
conn net.Conn
Node ProxyNode
handshaked bool
handshakeMutex sync.Mutex
handshakeErr error
}
func NewProxyConn(conn net.Conn, node ProxyNode) *ProxyConn {
return &ProxyConn{
conn: conn,
Node: node,
}
}
// Handshake handshake with this proxy node based on the proxy node info: transport, protocol, authentication, etc.
//
// NOTE: any HTTP2 scheme will be treated as http (for protocol) or tls (for transport).
func (c *ProxyConn) Handshake() error {
c.handshakeMutex.Lock()
defer c.handshakeMutex.Unlock()
if err := c.handshakeErr; err != nil {
return err
}
if c.handshaked {
return nil
}
c.handshakeErr = c.handshake()
return c.handshakeErr
}
func (c *ProxyConn) handshake() error {
var tlsUsed bool
switch c.Node.Transport {
case "ws": // websocket connection
u := url.URL{Scheme: "ws", Host: c.Node.Addr, Path: "/ws"}
conn, err := WebsocketClientConn(u.String(), c.conn, nil)
if err != nil {
return err
}
c.conn = conn
case "wss": // websocket security
tlsUsed = true
u := url.URL{Scheme: "wss", Host: c.Node.Addr, Path: "/ws"}
config := &tls.Config{
InsecureSkipVerify: c.Node.insecureSkipVerify(),
ServerName: c.Node.serverName,
}
conn, err := WebsocketClientConn(u.String(), c.conn, config)
if err != nil {
return err
}
c.conn = conn
case "tls", "http2": // tls connection
tlsUsed = true
cfg := &tls.Config{
InsecureSkipVerify: c.Node.insecureSkipVerify(),
ServerName: c.Node.serverName,
}
c.conn = tls.Client(c.conn, cfg)
case "h2": // same as http2, but just set a flag for later using.
tlsUsed = true
case "kcp": // kcp connection
tlsUsed = true
default:
}
switch c.Node.Protocol {
case "socks", "socks5": // socks5 handshake with auth and tls supported
selector := &clientSelector{
methods: []uint8{
gosocks5.MethodNoAuth,
gosocks5.MethodUserPass,
//MethodTLS,
},
}
if len(c.Node.Users) > 0 {
selector.user = c.Node.Users[0]
}
if !tlsUsed { // if transport is not security, enable security socks5
selector.methods = append(selector.methods, MethodTLS)
selector.tlsConfig = &tls.Config{
InsecureSkipVerify: c.Node.insecureSkipVerify(),
ServerName: c.Node.serverName,
}
}
conn := gosocks5.ClientConn(c.conn, selector)
if err := conn.Handleshake(); err != nil {
return err
}
c.conn = conn
case "ss": // shadowsocks
// nothing to do
case "http", "http2":
fallthrough
default:
}
c.handshaked = true
return nil
}
// Connect connect to addr through this proxy node
func (c *ProxyConn) Connect(addr string) error {
switch c.Node.Protocol {
case "ss": // shadowsocks
rawaddr, err := ss.RawAddr(addr)
if err != nil {
return err
}
var method, password string
if len(c.Node.Users) > 0 {
method = c.Node.Users[0].Username()
password, _ = c.Node.Users[0].Password()
}
if c.Node.getBool("ota") && !strings.HasSuffix(method, "-auth") {
method += "-auth"
}
cipher, err := ss.NewCipher(method, password)
if err != nil {
return err
}
ssc, err := ss.DialWithRawAddrConn(rawaddr, c.conn, cipher)
if err != nil {
return err
}
c.conn = &shadowConn{conn: ssc}
return nil
case "socks", "socks5":
host, port, err := net.SplitHostPort(addr)
if err != nil {
return err
}
p, _ := strconv.Atoi(port)
req := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{
Type: gosocks5.AddrDomain,
Host: host,
Port: uint16(p),
})
if err := req.Write(c); err != nil {
return err
}
glog.V(LDEBUG).Infoln("[socks5]", req)
reply, err := gosocks5.ReadReply(c)
if err != nil {
return err
}
glog.V(LDEBUG).Infoln("[socks5]", reply)
if reply.Rep != gosocks5.Succeeded {
return errors.New("Service unavailable")
}
case "socks4", "socks4a":
atype := gosocks4.AddrDomain
host, port, err := net.SplitHostPort(addr)
if err != nil {
return err
}
p, _ := strconv.Atoi(port)
if c.Node.Protocol == "socks4" {
taddr, err := net.ResolveTCPAddr("tcp4", addr)
if err != nil {
return err
}
host = taddr.IP.String()
p = taddr.Port
atype = gosocks4.AddrIPv4
}
req := gosocks4.NewRequest(gosocks4.CmdConnect,
&gosocks4.Addr{Type: atype, Host: host, Port: uint16(p)}, nil)
if err := req.Write(c); err != nil {
return err
}
glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, req)
reply, err := gosocks4.ReadReply(c)
if err != nil {
return err
}
glog.V(LDEBUG).Infof("[%s] %s", c.Node.Protocol, reply)
if reply.Code != gosocks4.Granted {
return errors.New(fmt.Sprintf("%s: code=%d", c.Node.Protocol, reply.Code))
}
case "http":
fallthrough
default:
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Host: addr},
Host: addr,
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
}
req.Header.Set("Proxy-Connection", "keep-alive")
if len(c.Node.Users) > 0 {
user := c.Node.Users[0]
s := user.String()
if _, set := user.Password(); !set {
s += ":"
}
req.Header.Set("Proxy-Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
}
if err := req.Write(c); err != nil {
return err
}
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
resp, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
return err
}
if glog.V(LDEBUG) {
dump, _ := httputil.DumpResponse(resp, false)
glog.Infoln(string(dump))
}
if resp.StatusCode != http.StatusOK {
return errors.New(resp.Status)
}
}
return nil
}
func (c *ProxyConn) Read(b []byte) (n int, err error) {
return c.conn.Read(b)
}
func (c *ProxyConn) Write(b []byte) (n int, err error) {
return c.conn.Write(b)
}
func (c *ProxyConn) Close() error {
return c.conn.Close()
}
func (c *ProxyConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *ProxyConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *ProxyConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *ProxyConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *ProxyConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
This diff is collapsed.
package gost
import (
"crypto/tls"
"encoding/base64"
"errors"
"github.com/golang/glog"
"io"
"net"
"strings"
"time"
)
const (
Version = "2.4-dev20170303"
)
// Log level for glog
const (
LFATAL = iota
LERROR
LWARNING
LINFO
LDEBUG
)
var (
KeepAliveTime = 180 * time.Second
DialTimeout = 30 * time.Second
ReadTimeout = 90 * time.Second
WriteTimeout = 90 * time.Second
DefaultTTL = 60 // default udp node TTL in second for udp port forwarding
)
var (
SmallBufferSize = 1 * 1024 // 1KB small buffer
MediumBufferSize = 8 * 1024 // 8KB medium buffer
LargeBufferSize = 32 * 1024 // 32KB large buffer
)
var (
DefaultCertFile = "cert.pem"
DefaultKeyFile = "key.pem"
// This is the default cert and key data for convenience, providing your own cert is recommended.
defaultRawCert = []byte(`-----BEGIN CERTIFICATE-----
MIIC5jCCAdCgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
bzAeFw0xNDAzMTcwNjIwNTFaFw0xNTAzMTcwNjIwNTFaMBIxEDAOBgNVBAoTB0Fj
bWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccNO1xmd4lWSf
d/0/QS3E93cYIWHw831i/IKxigdRD/XMZonLdEHywW6lOiXazaP8e6CqPGSmnl0x
5k/3dvGCMj2JCVxM6+z7NpL+AiwvXmvkj/TOciCgwqssCwYS2CiVwjfazRjx1ZUJ
VDC5qiyRsfktQ2fVHrpnJGVSRagmiQgwGWBilVG9B8QvRtpQKN/GQGq17oIQm8aK
kOdPt93g93ojMIg7YJpgDgOirvVz/hDn7YD4ryrtPos9CMafFkJprymKpRHyvz7P
8a3+OkuPjFjPnwOHQ5u1U3+8vC44vfb1ExWzDLoT8Xp8Gndx39k0f7MVOol3GnYu
MN/dvNUdAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIAoDATBgNVHSUEDDAKBggrBgEF
BQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDALBgkqhkiG
9w0BAQUDggEBAIG8CJqvTIgJnNOK+i5/IUc/3yF/mSCWuG8qP+Fmo2t6T0PVOtc0
8wiWH5iWtCAhjn0MRY9l/hIjWm6gUZGHCGuEgsOPpJDYGoNLjH9Xwokm4y3LFNRK
UBrrrDbKRNibApBHCapPf6gC5sXcjOwx7P2/kiHDgY7YH47jfcRhtAPNsM4gjsEO
RmwENY+hRUFHIRfQTyalqND+x6PWhRo3K6hpHs4DQEYPq4P2kFPqUqSBymH+Ny5/
BcQ3wdMNmC6Bm/oiL1QV0M+/InOsAgQk/EDd0kmoU1ZT2lYHQduGmP099bOlHNpS
uqO3vXF3q8SPPr/A9TqSs7BKkBQbe0+cdsA=
-----END CERTIFICATE-----`)
defaultRawKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA3HDTtcZneJVkn3f9P0EtxPd3GCFh8PN9YvyCsYoHUQ/1zGaJ
y3RB8sFupTol2s2j/Hugqjxkpp5dMeZP93bxgjI9iQlcTOvs+zaS/gIsL15r5I/0
znIgoMKrLAsGEtgolcI32s0Y8dWVCVQwuaoskbH5LUNn1R66ZyRlUkWoJokIMBlg
YpVRvQfEL0baUCjfxkBqte6CEJvGipDnT7fd4Pd6IzCIO2CaYA4Doq71c/4Q5+2A
+K8q7T6LPQjGnxZCaa8piqUR8r8+z/Gt/jpLj4xYz58Dh0ObtVN/vLwuOL329RMV
swy6E/F6fBp3cd/ZNH+zFTqJdxp2LjDf3bzVHQIDAQABAoIBAHal26147nQ+pHwY
jxwers3XDCjWvup7g79lfcqlKi79UiUEA6KYHm7UogMYewt7p4nb2KwH+XycvDiB
aAUf5flXpTs+6IkWauUDiLZi4PlV7uiEexUq5FjirlL0U/6MjbudX4bK4WQ4uxDc
WaV07Kw2iJFOOHLDKT0en9JaX5jtJNc4ZnE9efFoQ5jfypPWtRw65G1rULEg6nvc
GDh+1ce+4foCkpLRC9c24xAwJONZG6x3UqrSS9qfAsb73nWRQrTfUcO3nhoN8VvL
kL9skn1+S06NyUN0KoEtyRBp+RcpXSsBWAo6qZmo/WqhB/gjzWrxVwn20+yJSm35
ZsMc6QECgYEA8GS+Mp9xfB2szWHz6YTOO1Uu4lHM1ccZMwS1G+dL0KO3uGAiPdvp
woVot6v6w88t7onXsLo5pgz7SYug0CpkF3K/MRd1Ar4lH7PK7IBQ6rFr9ppVxDbx
AEWRswUoPbKCr7W6HU8LbQHDavsDlEIwc6+DiwnL4BzlKjb7RpgQEz0CgYEA6sB5
uHvx3Y5FDcGk1n73leQSAcq14l3ZLNpjrs8msoREDil/j5WmuSN58/7PGMiMgHEi
1vLm3H796JmvGr9OBvspOjHyk07ui2/We/j9Hoxm1VWhyi8HkLNDj70HKalTTFMz
RHO4O+0xCva+h9mKZrRMVktXr2jjdFn/0MYIZ2ECgYAIIsC1IeRLWQ3CHbCNlKsO
IwHlMvOFwKk/qsceXKOaOhA7szU1dr3gkXdL0Aw6mEZrrkqYdpUA46uVf54/rU+Z
445I8QxKvXiwK/uQKX+TkdGflPWWIG3jnnch4ejMvb/ihnn4B/bRB6A/fKNQXzUY
lTYUfI5j1VaEKTwz1W2l2QKBgByFCcSp+jZqhGUpc3dDsZyaOr3Q/Mvlju7uEVI5
hIAHpaT60a6GBd1UPAqymEJwivFHzW3D0NxU6VAK68UaHMaoWNfjHY9b9YsnKS2i
kE3XzN56Ks+/avHfdYPO+UHMenw5V28nh+hv5pdoZrlmanQTz3pkaOC8o3WNQZEB
nh/BAoGBAMY5z2f1pmMhrvtPDSlEVjgjELbaInxFaxPLR4Pdyzn83gtIIU14+R8X
2LPs6PPwrNjWnIgrUSVXncIFL3pa45B+Mx1pYCpOAB1+nCZjIBQmpeo4Y0dwA/XH
85EthKPvoszm+OPbyI16OcePV5ocX7lupRYuAo0pek7bomhmHWHz
-----END RSA PRIVATE KEY-----`)
)
var (
ErrEmptyChain = errors.New("empty chain")
)
func setKeepAlive(conn net.Conn, d time.Duration) error {
c, ok := conn.(*net.TCPConn)
if !ok {
return errors.New("Not a TCP connection")
}
if err := c.SetKeepAlive(true); err != nil {
return err
}
if err := c.SetKeepAlivePeriod(d); err != nil {
return err
}
return nil
}
// Load the certificate from cert and key files, will use the default certificate if the provided info are invalid.
func LoadCertificate(certFile, keyFile string) (tls.Certificate, error) {
tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err == nil {
return tlsCert, nil
}
glog.V(LWARNING).Infoln(err)
return tls.X509KeyPair(defaultRawCert, defaultRawKey)
}
// Replace the default certificate by your own
func SetDefaultCertificate(rawCert, rawKey []byte) {
defaultRawCert = rawCert
defaultRawKey = rawKey
}
func basicProxyAuth(proxyAuth string) (username, password string, ok bool) {
if proxyAuth == "" {
return
}
if !strings.HasPrefix(proxyAuth, "Basic ") {
return
}
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(proxyAuth, "Basic "))
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
func Transport(rw1, rw2 io.ReadWriter) error {
errc := make(chan error, 1)
go func() {
_, err := io.Copy(rw1, rw2)
errc <- err
}()
go func() {
_, err := io.Copy(rw2, rw1)
errc <- err
}()
return <-errc
}
This diff is collapsed.
// KCP feature is based on https://github.com/xtaci/kcptun
package gost
import (
"crypto/sha1"
"encoding/csv"
"encoding/json"
"fmt"
"github.com/golang/glog"
"github.com/klauspost/compress/snappy"
"golang.org/x/crypto/pbkdf2"
"gopkg.in/xtaci/kcp-go.v2"
"gopkg.in/xtaci/smux.v1"
"net"
"os"
"time"
)
const (
DefaultKCPConfigFile = "kcp.json"
)
var (
SALT = "kcp-go"
)
type KCPConfig struct {
Key string `json:"key"`
Crypt string `json:"crypt"`
Mode string `json:"mode"`
MTU int `json:"mtu"`
SndWnd int `json:"sndwnd"`
RcvWnd int `json:"rcvwnd"`
DataShard int `json:"datashard"`
ParityShard int `json:"parityshard"`
DSCP int `json:"dscp"`
NoComp bool `json:"nocomp"`
AckNodelay bool `json:"acknodelay"`
NoDelay int `json:"nodelay"`
Interval int `json:"interval"`
Resend int `json:"resend"`
NoCongestion int `json:"nc"`
SockBuf int `json:"sockbuf"`
KeepAlive int `json:"keepalive"`
SnmpLog string `json:"snmplog"`
SnmpPeriod int `json:"snmpperiod"`
}
func ParseKCPConfig(configFile string) (*KCPConfig, error) {
if configFile == "" {
configFile = DefaultKCPConfigFile
}
file, err := os.Open(configFile)
if err != nil {
return nil, err
}
defer file.Close()
config := &KCPConfig{}
if err = json.NewDecoder(file).Decode(config); err != nil {
return nil, err
}
return config, nil
}
func (c *KCPConfig) Init() {
switch c.Mode {
case "normal":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 50, 2, 1
case "fast2":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 30, 2, 1
case "fast3":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1
case "fast":
fallthrough
default:
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 40, 2, 1
}
}
var (
DefaultKCPConfig = &KCPConfig{
Key: "it's a secrect",
Crypt: "aes",
Mode: "fast",
MTU: 1350,
SndWnd: 1024,
RcvWnd: 1024,
DataShard: 10,
ParityShard: 3,
DSCP: 0,
NoComp: false,
AckNodelay: false,
NoDelay: 0,
Interval: 50,
Resend: 0,
NoCongestion: 0,
SockBuf: 4194304,
KeepAlive: 10,
SnmpLog: "",
SnmpPeriod: 60,
}
)
type KCPServer struct {
Base *ProxyServer
Config *KCPConfig
}
func NewKCPServer(base *ProxyServer, config *KCPConfig) *KCPServer {
return &KCPServer{Base: base, Config: config}
}
func (s *KCPServer) ListenAndServe() (err error) {
if s.Config == nil {
s.Config = DefaultKCPConfig
}
s.Config.Init()
ln, err := kcp.ListenWithOptions(s.Base.Node.Addr,
blockCrypt(s.Config.Key, s.Config.Crypt, SALT), s.Config.DataShard, s.Config.ParityShard)
if err != nil {
return err
}
if err = ln.SetDSCP(s.Config.DSCP); err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
}
if err = ln.SetReadBuffer(s.Config.SockBuf); err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
}
if err = ln.SetWriteBuffer(s.Config.SockBuf); err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
}
go snmpLogger(s.Config.SnmpLog, s.Config.SnmpPeriod)
go kcpSigHandler()
for {
conn, err := ln.AcceptKCP()
if err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
continue
}
conn.SetStreamMode(true)
conn.SetNoDelay(s.Config.NoDelay, s.Config.Interval, s.Config.Resend, s.Config.NoCongestion)
conn.SetMtu(s.Config.MTU)
conn.SetWindowSize(s.Config.SndWnd, s.Config.RcvWnd)
conn.SetACKNoDelay(s.Config.AckNodelay)
conn.SetKeepAlive(s.Config.KeepAlive)
go s.handleMux(conn)
}
}
func (s *KCPServer) handleMux(conn net.Conn) {
smuxConfig := smux.DefaultConfig()
smuxConfig.MaxReceiveBuffer = s.Config.SockBuf
glog.V(LINFO).Infof("[kcp] %s - %s", conn.RemoteAddr(), s.Base.Node.Addr)
if !s.Config.NoComp {
conn = newCompStreamConn(conn)
}
mux, err := smux.Server(conn, smuxConfig)
if err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
return
}
defer mux.Close()
glog.V(LINFO).Infof("[kcp] %s <-> %s", conn.RemoteAddr(), s.Base.Node.Addr)
defer glog.V(LINFO).Infof("[kcp] %s >-< %s", conn.RemoteAddr(), s.Base.Node.Addr)
for {
stream, err := mux.AcceptStream()
if err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
return
}
go s.Base.handleConn(NewKCPConn(conn, stream))
}
}
func blockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New)
switch crypt {
case "tea":
block, _ = kcp.NewTEABlockCrypt(pass[:16])
case "xor":
block, _ = kcp.NewSimpleXORBlockCrypt(pass)
case "none":
block, _ = kcp.NewNoneBlockCrypt(pass)
case "aes-128":
block, _ = kcp.NewAESBlockCrypt(pass[:16])
case "aes-192":
block, _ = kcp.NewAESBlockCrypt(pass[:24])
case "blowfish":
block, _ = kcp.NewBlowfishBlockCrypt(pass)
case "twofish":
block, _ = kcp.NewTwofishBlockCrypt(pass)
case "cast5":
block, _ = kcp.NewCast5BlockCrypt(pass[:16])
case "3des":
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
case "xtea":
block, _ = kcp.NewXTEABlockCrypt(pass[:16])
case "salsa20":
block, _ = kcp.NewSalsa20BlockCrypt(pass)
case "aes":
fallthrough
default: // aes
block, _ = kcp.NewAESBlockCrypt(pass)
}
return
}
func snmpLogger(path string, interval int) {
if path == "" || interval == 0 {
return
}
ticker := time.NewTicker(time.Duration(interval) * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
f, err := os.OpenFile(time.Now().Format(path), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
return
}
w := csv.NewWriter(f)
// write header in empty file
if stat, err := f.Stat(); err == nil && stat.Size() == 0 {
if err := w.Write(append([]string{"Unix"}, kcp.DefaultSnmp.Header()...)); err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
}
}
if err := w.Write(append([]string{fmt.Sprint(time.Now().Unix())}, kcp.DefaultSnmp.ToSlice()...)); err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
}
kcp.DefaultSnmp.Reset()
w.Flush()
f.Close()
}
}
}
type KCPSession struct {
conn net.Conn
session *smux.Session
}
func DialKCP(addr string, config *KCPConfig) (*KCPSession, error) {
if config == nil {
config = DefaultKCPConfig
}
config.Init()
kcpconn, err := kcp.DialWithOptions(addr,
blockCrypt(config.Key, config.Crypt, SALT), config.DataShard, config.ParityShard)
if err != nil {
return nil, err
}
kcpconn.SetStreamMode(true)
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion)
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd)
kcpconn.SetMtu(config.MTU)
kcpconn.SetACKNoDelay(config.AckNodelay)
kcpconn.SetKeepAlive(config.KeepAlive)
if err := kcpconn.SetDSCP(config.DSCP); err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
}
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
}
if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
}
// stream multiplex
smuxConfig := smux.DefaultConfig()
smuxConfig.MaxReceiveBuffer = config.SockBuf
var conn net.Conn = kcpconn
if !config.NoComp {
conn = newCompStreamConn(kcpconn)
}
session, err := smux.Client(conn, smuxConfig)
if err != nil {
conn.Close()
return nil, err
}
return &KCPSession{conn: conn, session: session}, nil
}
func (session *KCPSession) GetConn() (*KCPConn, error) {
stream, err := session.session.OpenStream()
if err != nil {
session.Close()
return nil, err
}
return NewKCPConn(session.conn, stream), nil
}
func (session *KCPSession) Close() error {
return session.session.Close()
}
func (session *KCPSession) IsClosed() bool {
return session.session.IsClosed()
}
func (session *KCPSession) NumStreams() int {
return session.session.NumStreams()
}
type KCPConn struct {
conn net.Conn
stream *smux.Stream
}
func NewKCPConn(conn net.Conn, stream *smux.Stream) *KCPConn {
return &KCPConn{conn: conn, stream: stream}
}
func (c *KCPConn) Read(b []byte) (n int, err error) {
return c.stream.Read(b)
}
func (c *KCPConn) Write(b []byte) (n int, err error) {
return c.stream.Write(b)
}
func (c *KCPConn) Close() error {
return c.stream.Close()
}
func (c *KCPConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *KCPConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *KCPConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *KCPConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *KCPConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
type compStreamConn struct {
conn net.Conn
w *snappy.Writer
r *snappy.Reader
}
func newCompStreamConn(conn net.Conn) *compStreamConn {
c := new(compStreamConn)
c.conn = conn
c.w = snappy.NewBufferedWriter(conn)
c.r = snappy.NewReader(conn)
return c
}
func (c *compStreamConn) Read(b []byte) (n int, err error) {
return c.r.Read(b)
}
func (c *compStreamConn) Write(b []byte) (n int, err error) {
n, err = c.w.Write(b)
err = c.w.Flush()
return n, err
}
func (c *compStreamConn) Close() error {
return c.conn.Close()
}
func (c *compStreamConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *compStreamConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *compStreamConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *compStreamConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *compStreamConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
package gost
import (
"bufio"
"fmt"
"github.com/golang/glog"
"net"
"net/url"
"os"
"strconv"
"strings"
)
// Proxy node represent a proxy
type ProxyNode struct {
Addr string // [host]:port
Protocol string // protocol: http/socks5/ss
Transport string // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp
Remote string // remote address, used by tcp/udp port forwarding
Users []*url.Userinfo // authentication for proxy
values url.Values
serverName string
conn net.Conn
}
// The proxy node string pattern is [scheme://][user:pass@host]:port.
//
// Scheme can be devided into two parts by character '+', such as: http+tls.
func ParseProxyNode(s string) (node ProxyNode, err error) {
if !strings.Contains(s, "://") {
s = "gost://" + s
}
u, err := url.Parse(s)
if err != nil {
return
}
node = ProxyNode{
Addr: u.Host,
values: u.Query(),
serverName: u.Host,
}
if u.User != nil {
node.Users = append(node.Users, u.User)
}
users, er := parseUsers(node.Get("secrets"))
if users != nil {
node.Users = append(node.Users, users...)
}
if er != nil {
glog.V(LWARNING).Infoln("secrets:", er)
}
if strings.Contains(u.Host, ":") {
node.serverName, _, _ = net.SplitHostPort(u.Host)
if node.serverName == "" {
node.serverName = "localhost" // default server name
}
}
schemes := strings.Split(u.Scheme, "+")
if len(schemes) == 1 {
node.Protocol = schemes[0]
node.Transport = schemes[0]
}
if len(schemes) == 2 {
node.Protocol = schemes[0]
node.Transport = schemes[1]
}
switch node.Transport {
case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu", "pht", "ssh":
case "https":
node.Protocol = "http"
node.Transport = "tls"
case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding
node.Remote = strings.Trim(u.EscapedPath(), "/")
case "rtcp", "rudp": // started from v2.1, rtcp and rudp are for remote port forwarding
node.Remote = strings.Trim(u.EscapedPath(), "/")
default:
node.Transport = ""
}
switch node.Protocol {
case "http", "http2", "socks", "socks4", "socks4a", "socks5", "ss":
default:
node.Protocol = ""
}
return
}
func parseUsers(authFile string) (users []*url.Userinfo, err error) {
if authFile == "" {
return
}
file, err := os.Open(authFile)
if err != nil {
return
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
s := strings.SplitN(line, " ", 2)
if len(s) == 1 {
users = append(users, url.User(strings.TrimSpace(s[0])))
} else if len(s) == 2 {
users = append(users, url.UserPassword(strings.TrimSpace(s[0]), strings.TrimSpace(s[1])))
}
}
err = scanner.Err()
return
}
// Get get node parameter by key
func (node *ProxyNode) Get(key string) string {
return node.values.Get(key)
}
func (node *ProxyNode) getBool(key string) bool {
s := node.Get(key)
if b, _ := strconv.ParseBool(s); b {
return b
}
n, _ := strconv.Atoi(s)
return n > 0
}
func (node *ProxyNode) Set(key, value string) {
node.values.Set(key, value)
}
func (node *ProxyNode) insecureSkipVerify() bool {
return !node.getBool("secure")
}
func (node *ProxyNode) certFile() string {
if cert := node.Get("cert"); cert != "" {
return cert
}
return DefaultCertFile
}
func (node *ProxyNode) keyFile() string {
if key := node.Get("key"); key != "" {
return key
}
return DefaultKeyFile
}
func (node ProxyNode) String() string {
return fmt.Sprintf("transport: %s, protocol: %s, addr: %s", node.Transport, node.Protocol, node.Addr)
}
package gost
import (
"bufio"
"crypto/tls"
"github.com/golang/glog"
"github.com/lucas-clemente/quic-go/h2quic"
"io"
"net/http"
"net/http/httputil"
)
type QuicServer struct {
Base *ProxyServer
Handler http.Handler
TLSConfig *tls.Config
}
func NewQuicServer(base *ProxyServer) *QuicServer {
return &QuicServer{Base: base}
}
func (s *QuicServer) ListenAndServeTLS(config *tls.Config) error {
server := &h2quic.Server{
Server: &http.Server{
Addr: s.Base.Node.Addr,
Handler: s.Handler,
TLSConfig: config,
},
}
if server.Handler == nil {
// server.Handler = http.HandlerFunc(s.HandleRequest)
server.Handler = http.HandlerFunc(NewHttp2Server(s.Base).HandleRequest)
}
return server.ListenAndServe()
}
func (s *QuicServer) HandleRequest(w http.ResponseWriter, req *http.Request) {
target := req.Host
glog.V(LINFO).Infof("[quic] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto)
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
c, err := s.Base.Chain.Dial(target)
if err != nil {
glog.V(LWARNING).Infof("[quic] %s -> %s : %s", req.RemoteAddr, target, err)
w.WriteHeader(http.StatusServiceUnavailable)
return
}
defer c.Close()
glog.V(LINFO).Infof("[quic] %s <-> %s", req.RemoteAddr, target)
req.Header.Set("Connection", "Keep-Alive")
if err = req.Write(c); err != nil {
glog.V(LWARNING).Infof("[quic] %s -> %s : %s", req.RemoteAddr, target, err)
return
}
resp, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
glog.V(LWARNING).Infoln(err)
return
}
defer resp.Body.Close()
for k, v := range resp.Header {
for _, vv := range v {
w.Header().Add(k, vv)
}
}
w.WriteHeader(resp.StatusCode)
if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil {
glog.V(LWARNING).Infof("[quic] %s <- %s : %s", req.RemoteAddr, target, err)
}
glog.V(LINFO).Infof("[quic] %s >-< %s", req.RemoteAddr, target)
}
// +build !windows
package gost
import (
"errors"
"fmt"
"github.com/golang/glog"
"net"
"syscall"
)
const (
SO_ORIGINAL_DST = 80
)
type RedsocksTCPServer struct {
Base *ProxyServer
}
func NewRedsocksTCPServer(base *ProxyServer) *RedsocksTCPServer {
return &RedsocksTCPServer{
Base: base,
}
}
func (s *RedsocksTCPServer) ListenAndServe() error {
laddr, err := net.ResolveTCPAddr("tcp", s.Base.Node.Addr)
if err != nil {
return err
}
ln, err := net.ListenTCP("tcp", laddr)
if err != nil {
return err
}
defer ln.Close()
for {
conn, err := ln.AcceptTCP()
if err != nil {
glog.V(LWARNING).Infoln(err)
continue
}
go s.handleRedirectTCP(conn)
}
}
func (s *RedsocksTCPServer) handleRedirectTCP(conn *net.TCPConn) {
srcAddr := conn.RemoteAddr()
dstAddr, conn, err := getOriginalDstAddr(conn)
if err != nil {
glog.V(LWARNING).Infof("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err)
return
}
defer conn.Close()
glog.V(LINFO).Infof("[red-tcp] %s -> %s", srcAddr, dstAddr)
cc, err := s.Base.Chain.Dial(dstAddr.String())
if err != nil {
glog.V(LWARNING).Infof("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err)
return
}
defer cc.Close()
glog.V(LINFO).Infof("[red-tcp] %s <-> %s", srcAddr, dstAddr)
s.Base.transport(conn, cc)
glog.V(LINFO).Infof("[red-tcp] %s >-< %s", srcAddr, dstAddr)
}
func getOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err error) {
defer conn.Close()
fc, err := conn.File()
if err != nil {
return
}
defer fc.Close()
mreq, err := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
if err != nil {
return
}
// only ipv4 support
ip := net.IPv4(mreq.Multiaddr[4], mreq.Multiaddr[5], mreq.Multiaddr[6], mreq.Multiaddr[7])
port := uint16(mreq.Multiaddr[2])<<8 + uint16(mreq.Multiaddr[3])
addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port))
if err != nil {
return
}
cc, err := net.FileConn(fc)
if err != nil {
return
}
c, ok := cc.(*net.TCPConn)
if !ok {
err = errors.New("not a TCP connection")
}
return
}
// +build windows
package gost
import (
"errors"
)
type RedsocksTCPServer struct{}
func NewRedsocksTCPServer(base *ProxyServer) *RedsocksTCPServer {
return &RedsocksTCPServer{}
}
func (s *RedsocksTCPServer) ListenAndServe() error {
return errors.New("Not supported")
}
package gost
import (
"bufio"
"crypto/tls"
"github.com/ginuerzh/gosocks4"
"github.com/ginuerzh/gosocks5"
"github.com/golang/glog"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"golang.org/x/crypto/ssh"
"io"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"
)
type ProxyServer struct {
Node ProxyNode
Chain *ProxyChain
TLSConfig *tls.Config
selector *serverSelector
cipher *ss.Cipher
ota bool
}
func NewProxyServer(node ProxyNode, chain *ProxyChain, config *tls.Config) *ProxyServer {
if chain == nil {
chain = NewProxyChain()
}
if config == nil {
config = &tls.Config{}
}
var cipher *ss.Cipher
var ota bool
if node.Protocol == "ss" || node.Transport == "ssu" {
var err error
var method, password string
if len(node.Users) > 0 {
method = node.Users[0].Username()
password, _ = node.Users[0].Password()
}
ota = node.getBool("ota")
if strings.HasSuffix(method, "-auth") {
ota = true
method = strings.TrimSuffix(method, "-auth")
}
cipher, err = ss.NewCipher(method, password)
if err != nil {
glog.Fatal(err)
}
}
return &ProxyServer{
Node: node,
Chain: chain,
TLSConfig: config,
selector: &serverSelector{ // socks5 server selector
// methods that socks5 server supported
methods: []uint8{
gosocks5.MethodNoAuth,
gosocks5.MethodUserPass,
MethodTLS,
MethodTLSAuth,
},
users: node.Users,
tlsConfig: config,
},
cipher: cipher,
ota: ota,
}
}
func (s *ProxyServer) Serve() error {
var ln net.Listener
var err error
node := s.Node
switch node.Transport {
case "ws": // websocket connection
return NewWebsocketServer(s).ListenAndServe()
case "wss": // websocket security connection
return NewWebsocketServer(s).ListenAndServeTLS(s.TLSConfig)
case "tls": // tls connection
ln, err = tls.Listen("tcp", node.Addr, s.TLSConfig)
case "http2": // Standard HTTP2 proxy server, compatible with HTTP1.x.
server := NewHttp2Server(s)
server.Handler = http.HandlerFunc(server.HandleRequest)
return server.ListenAndServeTLS(s.TLSConfig)
case "tcp": // Local TCP port forwarding
return NewTcpForwardServer(s).ListenAndServe()
case "udp": // Local UDP port forwarding
ttl, _ := strconv.Atoi(s.Node.Get("ttl"))
if ttl <= 0 {
ttl = DefaultTTL
}
return NewUdpForwardServer(s, ttl).ListenAndServe()
case "rtcp": // Remote TCP port forwarding
return NewRTcpForwardServer(s).Serve()
case "rudp": // Remote UDP port forwarding
return NewRUdpForwardServer(s).Serve()
case "quic":
return NewQuicServer(s).ListenAndServeTLS(s.TLSConfig)
case "kcp":
config, err := ParseKCPConfig(s.Node.Get("c"))
if err != nil {
glog.V(LWARNING).Infoln("[kcp]", err)
}
if config == nil {
config = DefaultKCPConfig
}
// override crypt and key if specified explicitly
if s.Node.Users != nil {
config.Crypt = s.Node.Users[0].Username()
config.Key, _ = s.Node.Users[0].Password()
}
return NewKCPServer(s, config).ListenAndServe()
case "redirect":
return NewRedsocksTCPServer(s).ListenAndServe()
case "ssu": // shadowsocks udp relay
ttl, _ := strconv.Atoi(s.Node.Get("ttl"))
if ttl <= 0 {
ttl = DefaultTTL
}
return NewShadowUdpServer(s, ttl).ListenAndServe()
case "pht": // pure http tunnel
return NewPureHttpServer(s).ListenAndServe()
case "ssh": // SSH tunnel
key := s.Node.Get("key")
privateBytes, err := ioutil.ReadFile(key)
if err != nil {
glog.V(LWARNING).Infoln("[ssh]", err)
privateBytes = defaultRawKey
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
return err
}
config := ssh.ServerConfig{
PasswordCallback: DefaultPasswordCallback(s.Node.Users),
}
if len(s.Node.Users) == 0 {
config.NoClientAuth = true
}
config.AddHostKey(private)
s := &SSHServer{
Addr: node.Addr,
Base: s,
Config: &config,
}
return s.ListenAndServe()
default:
ln, err = net.Listen("tcp", node.Addr)
}
if err != nil {
return err
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
glog.V(LWARNING).Infoln(err)
continue
}
setKeepAlive(conn, KeepAliveTime)
go s.handleConn(conn)
}
}
func (s *ProxyServer) handleConn(conn net.Conn) {
defer conn.Close()
switch s.Node.Protocol {
case "ss": // shadowsocks
server := NewShadowServer(ss.NewConn(conn, s.cipher.Copy()), s)
server.OTA = s.ota
server.Serve()
return
case "http":
req, err := http.ReadRequest(bufio.NewReader(conn))
if err != nil {
glog.V(LWARNING).Infoln("[http]", err)
return
}
NewHttpServer(conn, s).HandleRequest(req)
return
case "socks", "socks5":
conn = gosocks5.ServerConn(conn, s.selector)
req, err := gosocks5.ReadRequest(conn)
if err != nil {
glog.V(LWARNING).Infoln("[socks5]", err)
return
}
NewSocks5Server(conn, s).HandleRequest(req)
return
case "socks4", "socks4a":
req, err := gosocks4.ReadRequest(conn)
if err != nil {
glog.V(LWARNING).Infoln("[socks4]", err)
return
}
NewSocks4Server(conn, s).HandleRequest(req)
return
}
br := bufio.NewReader(conn)
b, err := br.Peek(1)
if err != nil {
glog.V(LWARNING).Infoln(err)
return
}
switch b[0] {
case gosocks4.Ver4:
req, err := gosocks4.ReadRequest(br)
if err != nil {
glog.V(LWARNING).Infoln("[socks4]", err)
return
}
NewSocks4Server(conn, s).HandleRequest(req)
case gosocks5.Ver5:
methods, err := gosocks5.ReadMethods(br)
if err != nil {
glog.V(LWARNING).Infoln("[socks5]", err)
return
}
method := s.selector.Select(methods...)
if _, err := conn.Write([]byte{gosocks5.Ver5, method}); err != nil {
glog.V(LWARNING).Infoln("[socks5] select:", err)
return
}
c, err := s.selector.OnSelected(method, conn)
if err != nil {
glog.V(LWARNING).Infoln("[socks5] onselected:", err)
return
}
conn = c
req, err := gosocks5.ReadRequest(conn)
if err != nil {
glog.V(LWARNING).Infoln("[socks5] request:", err)
return
}
NewSocks5Server(conn, s).HandleRequest(req)
default: // http
req, err := http.ReadRequest(br)
if err != nil {
glog.V(LWARNING).Infoln("[http]", err)
return
}
NewHttpServer(conn, s).HandleRequest(req)
}
}
func (_ *ProxyServer) transport(conn1, conn2 net.Conn) (err error) {
errc := make(chan error, 2)
go func() {
_, err := io.Copy(conn1, conn2)
errc <- err
}()
go func() {
_, err := io.Copy(conn2, conn1)
errc <- err
}()
select {
case err = <-errc:
// glog.V(LWARNING).Infoln("transport exit", err)
}
return
}
// +build windows
package gost
func kcpSigHandler() {}
// +build !windows
package gost
import (
"github.com/golang/glog"
"gopkg.in/xtaci/kcp-go.v2"
"os"
"os/signal"
"syscall"
)
func kcpSigHandler() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGUSR1)
for {
switch <-ch {
case syscall.SIGUSR1:
glog.V(LINFO).Infof("[kcp] SNMP: %+v", kcp.DefaultSnmp.Copy())
}
}
}
This diff is collapsed.
package gost
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/ginuerzh/gosocks5"
"github.com/golang/glog"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"io"
"net"
"strconv"
"time"
)
const (
idType = 0 // address type index
idIP0 = 1 // ip addres start index
idDmLen = 1 // domain address length index
idDm0 = 2 // domain address start index
typeIPv4 = 1 // type is ipv4 address
typeDm = 3 // type is domain address
typeIPv6 = 4 // type is ipv6 address
lenIPv4 = net.IPv4len + 2 // ipv4 + 2port
lenIPv6 = net.IPv6len + 2 // ipv6 + 2port
lenDmBase = 2 // 1addrLen + 2port, plus addrLen
lenHmacSha1 = 10
)
type ShadowServer struct {
conn *ss.Conn
Base *ProxyServer
OTA bool // one time auth
}
func NewShadowServer(conn *ss.Conn, base *ProxyServer) *ShadowServer {
return &ShadowServer{conn: conn, Base: base}
}
func (s *ShadowServer) Serve() {
glog.V(LINFO).Infof("[ss] %s - %s", s.conn.RemoteAddr(), s.conn.LocalAddr())
addr, ota, err := s.getRequest()
if err != nil {
glog.V(LWARNING).Infof("[ss] %s - %s : %s", s.conn.RemoteAddr(), s.conn.LocalAddr(), err)
return
}
glog.V(LINFO).Infof("[ss] %s -> %s, ota: %v", s.conn.RemoteAddr(), addr, ota)
cc, err := s.Base.Chain.Dial(addr)
if err != nil {
glog.V(LWARNING).Infof("[ss] %s -> %s : %s", s.conn.RemoteAddr(), addr, err)
return
}
defer cc.Close()
glog.V(LINFO).Infof("[ss] %s <-> %s", s.conn.RemoteAddr(), addr)
if ota {
s.transportOTA(s.conn, cc)
} else {
s.Base.transport(&shadowConn{conn: s.conn}, cc)
}
glog.V(LINFO).Infof("[ss] %s >-< %s", s.conn.RemoteAddr(), addr)
}
// This function is copied from shadowsocks library with some modification.
func (s *ShadowServer) getRequest() (host string, ota bool, err error) {
// buf size should at least have the same size with the largest possible
// request size (when addrType is 3, domain name has at most 256 bytes)
// 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
buf := make([]byte, SmallBufferSize)
// read till we get possible domain length field
s.conn.SetReadDeadline(time.Now().Add(ReadTimeout))
if _, err = io.ReadFull(s.conn, buf[:idType+1]); err != nil {
return
}
var reqStart, reqEnd int
addrType := buf[idType]
switch addrType & ss.AddrMask {
case typeIPv4:
reqStart, reqEnd = idIP0, idIP0+lenIPv4
case typeIPv6:
reqStart, reqEnd = idIP0, idIP0+lenIPv6
case typeDm:
if _, err = io.ReadFull(s.conn, buf[idType+1:idDmLen+1]); err != nil {
return
}
reqStart, reqEnd = idDm0, int(idDm0+buf[idDmLen]+lenDmBase)
default:
err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask)
return
}
if _, err = io.ReadFull(s.conn, buf[reqStart:reqEnd]); err != nil {
return
}
// Return string for typeIP is not most efficient, but browsers (Chrome,
// Safari, Firefox) all seems using typeDm exclusively. So this is not a
// big problem.
switch addrType & ss.AddrMask {
case typeIPv4:
host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
case typeIPv6:
host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
case typeDm:
host = string(buf[idDm0 : idDm0+buf[idDmLen]])
}
// parse port
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
host = net.JoinHostPort(host, strconv.Itoa(int(port)))
// if specified one time auth enabled, we should verify this
if s.OTA || addrType&ss.OneTimeAuthMask > 0 {
ota = true
if _, err = io.ReadFull(s.conn, buf[reqEnd:reqEnd+lenHmacSha1]); err != nil {
return
}
iv := s.conn.GetIv()
key := s.conn.GetKey()
actualHmacSha1Buf := ss.HmacSha1(append(iv, key...), buf[:reqEnd])
if !bytes.Equal(buf[reqEnd:reqEnd+lenHmacSha1], actualHmacSha1Buf) {
err = fmt.Errorf("verify one time auth failed, iv=%v key=%v data=%v", iv, key, buf[:reqEnd])
return
}
}
return
}
const (
dataLenLen = 2
hmacSha1Len = 10
idxData0 = dataLenLen + hmacSha1Len
)
// copyOta copies data from src to dst with ota verification.
//
// This function is copied from shadowsocks library with some modification.
func (s *ShadowServer) copyOta(dst net.Conn, src *ss.Conn) (int64, error) {
// sometimes it have to fill large block
buf := make([]byte, LargeBufferSize)
for {
src.SetReadDeadline(time.Now().Add(ReadTimeout))
if n, err := io.ReadFull(src, buf[:dataLenLen+hmacSha1Len]); err != nil {
return int64(n), err
}
src.SetReadDeadline(time.Time{})
dataLen := binary.BigEndian.Uint16(buf[:dataLenLen])
expectedHmacSha1 := buf[dataLenLen:idxData0]
var dataBuf []byte
if len(buf) < int(idxData0+dataLen) {
dataBuf = make([]byte, dataLen)
} else {
dataBuf = buf[idxData0 : idxData0+dataLen]
}
if n, err := io.ReadFull(src, dataBuf); err != nil {
return int64(n), err
}
chunkIdBytes := make([]byte, 4)
chunkId := src.GetAndIncrChunkId()
binary.BigEndian.PutUint32(chunkIdBytes, chunkId)
actualHmacSha1 := ss.HmacSha1(append(src.GetIv(), chunkIdBytes...), dataBuf)
if !bytes.Equal(expectedHmacSha1, actualHmacSha1) {
return 0, errors.New("ota error: mismatch")
}
if n, err := dst.Write(dataBuf); err != nil {
return int64(n), err
}
}
}
func (s *ShadowServer) transportOTA(sc *ss.Conn, cc net.Conn) (err error) {
errc := make(chan error, 2)
go func() {
_, err := io.Copy(&shadowConn{conn: sc}, cc)
errc <- err
}()
go func() {
_, err := s.copyOta(cc, sc)
errc <- err
}()
select {
case err = <-errc:
//glog.V(LWARNING).Infoln("transport exit", err)
}
return
}
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
// we wrap around it to make io.Copy happy
type shadowConn struct {
conn *ss.Conn
}
func (c *shadowConn) Read(b []byte) (n int, err error) {
return c.conn.Read(b)
}
func (c *shadowConn) Write(b []byte) (n int, err error) {
n = len(b) // force byte length consistent
_, err = c.conn.Write(b)
return
}
func (c *shadowConn) Close() error {
return c.conn.Close()
}
func (c *shadowConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *shadowConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *shadowConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *shadowConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *shadowConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
type ShadowUdpServer struct {
Base *ProxyServer
TTL int
}
func NewShadowUdpServer(base *ProxyServer, ttl int) *ShadowUdpServer {
return &ShadowUdpServer{Base: base, TTL: ttl}
}
func (s *ShadowUdpServer) ListenAndServe() error {
laddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Addr)
if err != nil {
return err
}
lconn, err := net.ListenUDP("udp", laddr)
if err != nil {
return err
}
defer lconn.Close()
conn := ss.NewSecurePacketConn(lconn, s.Base.cipher.Copy(), true) // force OTA on
rChan, wChan := make(chan *packet, 128), make(chan *packet, 128)
// start send queue
go func(ch chan<- *packet) {
for {
b := make([]byte, MediumBufferSize)
n, addr, err := conn.ReadFrom(b[3:]) // add rsv and frag fields to make it the standard SOCKS5 UDP datagram
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, laddr, err)
continue
}
b[3] &= ss.AddrMask // remove OTA flag
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n+3]))
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, laddr, err)
continue
}
select {
case ch <- &packet{srcAddr: addr.String(), dstAddr: dgram.Header.Addr.String(), data: dgram.Data}:
case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, dgram.Header.Addr.String(), "send queue is full, discard")
}
}
}(wChan)
// start recv queue
go func(ch <-chan *packet) {
for pkt := range ch {
srcAddr, err := net.ResolveUDPAddr("udp", pkt.srcAddr)
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
continue
}
dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr)
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
continue
}
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, ToSocksAddr(srcAddr)), pkt.data)
b := bytes.Buffer{}
dgram.Write(&b)
if b.Len() < 10 {
glog.V(LWARNING).Infof("[ssu] %s <- %s : invalid udp datagram", pkt.dstAddr, pkt.srcAddr)
continue
}
if _, err := conn.WriteTo(b.Bytes()[3:], dstAddr); err != nil { // remove rsv and frag fields to make it standard shadowsocks UDP datagram
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
return
}
}
}(rChan)
// mapping client to node
m := make(map[string]*cnode)
// start dispatcher
for pkt := range wChan {
// clear obsolete nodes
for k, node := range m {
if node != nil && node.err != nil {
close(node.wChan)
delete(m, k)
glog.V(LINFO).Infof("[ssu] clear node %s", k)
}
}
node, ok := m[pkt.srcAddr]
if !ok {
node = &cnode{
chain: s.Base.Chain,
srcAddr: pkt.srcAddr,
dstAddr: pkt.dstAddr,
rChan: rChan,
wChan: make(chan *packet, 32),
ttl: time.Duration(s.TTL) * time.Second,
}
m[pkt.srcAddr] = node
go node.run()
glog.V(LINFO).Infof("[ssu] %s -> %s : new client (%d)", pkt.srcAddr, pkt.dstAddr, len(m))
}
select {
case node.wChan <- pkt:
case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, "node send queue is full, discard")
}
}
return nil
}
This diff is collapsed.
This diff is collapsed.
...@@ -20,12 +20,6 @@ ...@@ -20,12 +20,6 @@
"revision": "0f737bddba2abd1496dc03c8b39b817cc5f33fa7", "revision": "0f737bddba2abd1496dc03c8b39b817cc5f33fa7",
"revisionTime": "2017-01-19T05:34:58Z" "revisionTime": "2017-01-19T05:34:58Z"
}, },
{
"checksumSHA1": "90Nj9KD5KzY15ZBn95Coz7G85+0=",
"path": "github.com/ginuerzh/gost",
"revision": "26f4e49f0538177eb5ec33c84a280afe2ae16042",
"revisionTime": "2017-03-03T13:33:05Z"
},
{ {
"checksumSHA1": "+XIOnTW0rv8Kr/amkXgMraNeUr4=", "checksumSHA1": "+XIOnTW0rv8Kr/amkXgMraNeUr4=",
"path": "github.com/ginuerzh/pht", "path": "github.com/ginuerzh/pht",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment