From e0b8e54619d0caa1b1f79753bc80e07845da787e Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Thu, 20 Oct 2016 15:34:09 +0800 Subject: [PATCH 1/7] experimental quic support --- gost.go | 2 +- node.go | 2 +- quic.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ server.go | 4 +++ ss.go | 40 ++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 quic.go diff --git a/gost.go b/gost.go index 71215a8..ef98ce6 100644 --- a/gost.go +++ b/gost.go @@ -11,7 +11,7 @@ import ( ) const ( - Version = "2.2-rc2" + Version = "2.3-dev" ) // Log level for glog diff --git a/node.go b/node.go index 79be88c..8a4f043 100644 --- a/node.go +++ b/node.go @@ -57,7 +57,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { } switch node.Transport { - case "ws", "wss", "tls", "http2": + case "ws", "wss", "tls", "http2", "ssu", "quic": case "https": node.Protocol = "http" node.Transport = "tls" diff --git a/quic.go b/quic.go new file mode 100644 index 0000000..0d97061 --- /dev/null +++ b/quic.go @@ -0,0 +1,80 @@ +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) + } + 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) +} diff --git a/server.go b/server.go index dd2e727..8ca5597 100644 --- a/server.go +++ b/server.go @@ -80,6 +80,10 @@ func (s *ProxyServer) Serve() error { return NewRTcpForwardServer(s).Serve() case "rudp": // Remote UDP port forwarding return NewRUdpForwardServer(s).Serve() + case "ssu": // shadowsocks udp relay + return NewShadowUdpServer(s).ListenAndServe() + case "quic": + return NewQuicServer(s).ListenAndServeTLS(s.TLSConfig) default: ln, err = net.Listen("tcp", node.Addr) } diff --git a/ss.go b/ss.go index 41afff8..48fa6e4 100644 --- a/ss.go +++ b/ss.go @@ -65,6 +65,46 @@ func (s *ShadowServer) Serve() { glog.V(LINFO).Infof("[ss] %s >-< %s", s.conn.RemoteAddr(), addr) } +type ShadowUdpServer struct { + Base *ProxyServer + Handler func(conn *net.UDPConn, addr *net.UDPAddr, data []byte) +} + +func NewShadowUdpServer(base *ProxyServer) *ShadowUdpServer { + return &ShadowUdpServer{Base: base} +} + +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() + + if s.Handler == nil { + s.Handler = s.HandleConn + } + + for { + b := make([]byte, LargeBufferSize) + n, addr, err := lconn.ReadFromUDP(b) + if err != nil { + glog.V(LWARNING).Infoln(err) + continue + } + + go s.Handler(lconn, addr, b[:n]) + } +} + +func (s *ShadowUdpServer) HandleConn(conn *net.UDPConn, addr *net.UDPAddr, data []byte) { + +} + // 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 From 7b041de6a4a1da2de0cc1519e1d3da1b7d827bd8 Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Thu, 20 Oct 2016 16:30:49 +0800 Subject: [PATCH 2/7] update README.md --- README.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 84d253f..37398ce 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,10 @@ gost - GO Simple Tunnel * 支持标准HTTP/HTTPS/SOCKS5代理协议 * SOCKS5代理支持TLS协商加密 * Tunnel UDP over TCP -* 支持Shadowsocks协议,支持OTA (OTA功能需2.2及以上版本) -* 支持端口转发 (2.1及以上版本) -* 支持HTTP2.0 (2.2及以上版本) +* 支持Shadowsocks协议,支持OTA (OTA: >=2.2) +* 支持端口转发 (>=2.1) +* 支持HTTP2.0 (>=2.2) +* 实验性支持QUIC (>=2.3) 二进制文件下载:https://github.com/ginuerzh/gost/releases @@ -31,7 +32,7 @@ Google讨论组: https://groups.google.com/d/forum/go-gost ``` scheme分为两部分: protocol+transport -protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2), 二者可以任意组合,或单独使用: +protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic), 二者可以任意组合,或单独使用: > http - 作为HTTP代理: http://:8080 @@ -47,6 +48,8 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输 > ss - 作为Shadowsocks服务,ss://aes-256-cfb:123456@:8080 +> quic - 作为QUIC代理,quic://:6121 + #### 端口转发 适用于-L参数 @@ -149,6 +152,21 @@ gost的HTTP2支持两种模式并自适应: **注: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网站。** + 加密机制 ------ #### HTTP @@ -192,7 +210,7 @@ gost -L=:8080 -F=socks://server_ip:1080 注:如果transport已经支持加密(wss, tls, http2),则SOCKS5不会再使用加密方法,防止不必要的双重加密。 #### Shadowsocks -gost对Shadowsocks加密方法的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 +gost对Shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 服务端(可以通过ota参数开启OTA模式): ```bash From 21d4a537288f8ddcf2544c52b415d709267d82e3 Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Fri, 21 Oct 2016 18:07:49 +0800 Subject: [PATCH 3/7] update README --- README.md | 10 +-- README_en.md | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 README_en.md diff --git a/README.md b/README.md index 37398ce..4b2194a 100644 --- a/README.md +++ b/README.md @@ -36,17 +36,17 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输 > http - 作为HTTP代理: http://:8080 -> http+tls - 作为HTTPS代理(可能需要提供受信任的证书): http+tls://:8080 +> http+tls - 作为HTTPS代理(可能需要提供受信任的证书): http+tls://:443 > http2 - 作为HTTP2代理并向下兼容HTTPS代理: http2://:443 -> socks - 作为标准SOCKS5代理(支持tls协商加密): socks://:8080 +> socks - 作为标准SOCKS5代理(支持tls协商加密): socks://:1080 -> socks+ws - 作为SOCKS5代理,使用websocket传输数据: socks+ws://:8080 +> socks+ws - 作为SOCKS5代理,使用websocket传输数据: socks+ws://:1080 -> tls - 作为HTTPS/SOCKS5代理,使用tls传输数据: tls://:8080 +> tls - 作为HTTPS/SOCKS5代理,使用tls传输数据: tls://:443 -> ss - 作为Shadowsocks服务,ss://aes-256-cfb:123456@:8080 +> ss - 作为Shadowsocks服务,ss://aes-256-cfb:123456@:8338 > quic - 作为QUIC代理,quic://:6121 diff --git a/README_en.md b/README_en.md new file mode 100644 index 0000000..fe0a13c --- /dev/null +++ b/README_en.md @@ -0,0 +1,242 @@ +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/SOCKS5 proxy protocols +* TLS encryption via negotiation support for SOCKS5 proxy +* Tunnel UDP over TCP +* Shadowsocks protocol with OTA supported (OTA: >=2.2) +* Local/Remote port forwarding (>=2.1) +* HTTP2.0 (>=2.2) +* Experimental QUIC support (>=2.3) + +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, socks5, shadowsocks), transport: data transmission mode(ws, wss, tls, http2, quic), 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 + +> http2 - HTTP2 proxy and downwards compatible HTTPS proxy: http2://:443 + +> socks - standard SOCKS5 proxy: socks://:1080 + +> socks+ws - SOCKS5 protocol over websocket: socks+ws://:1080 + +> tls - HTTPS/SOCKS5 over tls: tls://:443 + +> ss - shadowsocks proxy, ss://aes-256-cfb:123456@:8338 + +> quic - QUIC proxy, quic://:6121 + +#### 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 + +#### 开启日志 + +> -logtostderr : log to console + +> -v=4 : log level(1-5),The higher the level, the more detailed the log (level 5 will enable HTTP2 debug) + +> -log_dir=. : log to dir + + +Usage +------ +#### No forward proxy + + + +* Standard HTTP/SOCKS5 proxy +```bash +gost -L=:8080 +``` + +* Proxy authentication +```bash +gost -L=admin:123456@localhost:8080 +``` + +* Listen on multiple ports +```bash +gost -L=http2://:443 -L=socks://:1080 -L=ss://aes-128-cfb:123456@:8338 +``` + +#### Forward proxy + + +```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 + + +```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上。 + +#### 本地端口转发(UDP) + +```bash +gost -L=udp://:5353/192.168.1.1:53 -F=... +``` +将本地UDP端口5353上的数据(通过代理链)转发到192.168.1.1:53上。 + +**注: 转发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上。 + +#### 远程端口转发(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),传输其他协议。 + +**注:gost的代理链仅支持一个HTTP2代理节点,采用就近原则,会将第一个遇到的HTTP2代理节点视为HTTP2代理,其他HTTP2代理节点则被视为HTTPS代理。** + +加密机制 +------ +#### 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传输。 + +服务端: +```bash +gost -L=http2://:443 +``` +客户端: +```bash +gost -L=:8080 -F=http2://server_ip:443 +``` + +#### 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),则SOCKS5不会再使用加密方法,防止不必要的双重加密。 + +#### Shadowsocks +gost对Shadowsocks加密方法的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 + +服务端(可以通过ota参数开启OTA模式): +```bash +gost -L=ss://aes-128-cfb:123456@:8338?ota=1 +``` +客户端: +```bash +gost -L=:8080 -F=ss://aes-128-cfb:123456@server_ip: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数据处理 +------ +#### 不设置转发代理 + + + +gost作为标准SOCKS5代理处理UDP数据 + +#### 设置转发代理 + + + +#### 设置多个转发代理(代理链) + + + +当设置转发代理时,gost会使用UDP-over-TCP方式转发UDP数据。proxy1 - proxyN可以为任意HTTP/HTTPS/HTTP2/SOCKS5/Shadowsocks类型代理。 + +限制条件 +------ +代理链中的HTTP代理节点必须支持CONNECT方法。 + +如果要转发SOCKS5的BIND和UDP请求,代理链的末端(最后一个-F参数)必须支持gost SOCKS5类型代理。 + + + From 8552498992b5f7e9aaa85ee4179b9f1a5ac4b4b6 Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Fri, 21 Oct 2016 22:21:57 +0800 Subject: [PATCH 4/7] update README --- README.md | 18 ++++---- README_en.md | 113 +++++++++++++++++++++++++++------------------------ 2 files changed, 71 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 4b2194a..5428014 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ gost - GO Simple Tunnel ### GO语言实现的安全隧道 +[English README](README_en.md) + 特性 ------ * 可同时监听多端口 @@ -42,7 +44,7 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输 > socks - 作为标准SOCKS5代理(支持tls协商加密): socks://:1080 -> socks+ws - 作为SOCKS5代理,使用websocket传输数据: socks+ws://:1080 +> socks+wss - 作为SOCKS5代理,使用websocket传输数据: socks+wss://:1080 > tls - 作为HTTPS/SOCKS5代理,使用tls传输数据: tls://:443 @@ -69,7 +71,7 @@ scheme://[bind_address]:port/[host]:hostport > -v=4 : 日志级别(1-5),级别越高,日志越详细(级别5将开启http2 debug) -> -log_dir=. : 输出到目录 +> -log_dir=/log/dir/path : 输出到目录/log/dir/path 使用方法 @@ -111,7 +113,7 @@ gost -L=:8080 -F=http://admin:123456@192.168.1.1:8081 ```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类型代理。 +gost按照-F设置的顺序通过代理链将请求最终转发给a.b.c.d:NNNN处理,每一个转发代理可以是任意HTTP/HTTPS/HTTP2/SOCKS5/Shadowsocks类型代理。 #### 本地端口转发(TCP) @@ -127,7 +129,7 @@ gost -L=udp://:5353/192.168.1.1:53 -F=... ``` 将本地UDP端口5353上的数据(通过代理链)转发到192.168.1.1:53上。 -**注: 转发UDP数据时,如果有代理链,则代理链的末端(最后一个-F参数)必须支持gost SOCKS5类型代理。** +**注:** 转发UDP数据时,如果有代理链,则代理链的末端(最后一个-F参数)必须是gost SOCKS5类型代理。 #### 远程端口转发(TCP) @@ -143,14 +145,14 @@ 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类型代理。** +**注:** 若要使用远程端口转发功能,代理链不能为空(至少要设置一个-F参数),且代理链的末端(最后一个-F参数)必须是gost SOCKS5类型代理。 #### HTTP2 gost的HTTP2支持两种模式并自适应: * 作为标准的HTTP2代理,并向下兼容HTTPS代理。 * 作为transport(类似于wss),传输其他协议。 -**注:gost的代理链仅支持一个HTTP2代理节点,采用就近原则,会将第一个遇到的HTTP2代理节点视为HTTP2代理,其他HTTP2代理节点则被视为HTTPS代理。** +**注:** gost的代理链仅支持一个HTTP2代理节点,采用就近原则,会将第一个遇到的HTTP2代理节点视为HTTP2代理,其他HTTP2代理节点则被视为HTTPS代理。 #### QUIC gost对QUIC的支持是基于[quic-go](https://github.com/lucas-clemente/quic-go)库。 @@ -165,7 +167,7 @@ gost -L=quic://:6121 chrome --enable-quic --proxy-server=quic://server_ip:6121 ``` -**注:由于Chrome自身的限制,目前只能通过QUIC访问HTTP网站,无法访问HTTPS网站。** +**注:** 由于Chrome自身的限制,目前只能通过QUIC访问HTTP网站,无法访问HTTPS网站。 加密机制 ------ @@ -207,7 +209,7 @@ gost -L=:8080 -F=socks://server_ip:1080 如果两端都是gost(如上)则数据传输会被加密(协商使用tls或tls-auth方法),否则使用标准SOCKS5进行通讯(no-auth或user/pass方法)。 -注:如果transport已经支持加密(wss, tls, http2),则SOCKS5不会再使用加密方法,防止不必要的双重加密。 +**注:** 如果transport已经支持加密(wss, tls, http2),则SOCKS5不会再使用加密方法,防止不必要的双重加密。 #### Shadowsocks gost对Shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 diff --git a/README_en.md b/README_en.md index fe0a13c..e0a3026 100644 --- a/README_en.md +++ b/README_en.md @@ -3,6 +3,8 @@ gost - GO Simple Tunnel ### A simple security tunnel written in Golang +[中文文档](README.md) + Features ------ * Listening on multiple ports @@ -11,7 +13,7 @@ Features * TLS encryption via negotiation support for SOCKS5 proxy * Tunnel UDP over TCP * Shadowsocks protocol with OTA supported (OTA: >=2.2) -* Local/Remote port forwarding (>=2.1) +* Local/remote port forwarding (>=2.1) * HTTP2.0 (>=2.2) * Experimental QUIC support (>=2.3) @@ -19,7 +21,8 @@ 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. +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 ------ @@ -32,23 +35,24 @@ Effective for the -L and -F parameters ``` scheme can be divided into two parts: protocol+transport -protocol: proxy protocol types(http, socks5, shadowsocks), transport: data transmission mode(ws, wss, tls, http2, quic), may be used in any combination or individually: +protocol: proxy protocol types(http, socks5, shadowsocks), +transport: data transmission mode(ws, wss, tls, http2, quic), 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 -> http2 - HTTP2 proxy and downwards compatible HTTPS proxy: http2://:443 +> http2 - HTTP2 proxy and backwards-compatible with HTTPS proxy: http2://:443 > socks - standard SOCKS5 proxy: socks://:1080 -> socks+ws - SOCKS5 protocol over websocket: socks+ws://:1080 +> socks+wss - SOCKS5 over websocket: socks+wss://:1080 > tls - HTTPS/SOCKS5 over tls: tls://:443 -> ss - shadowsocks proxy, ss://aes-256-cfb:123456@:8338 +> ss - standard shadowsocks proxy, ss://aes-256-cfb:123456@:8338 -> quic - QUIC proxy, quic://:6121 +> quic - standard QUIC proxy, quic://:6121 #### Port forwarding @@ -63,14 +67,13 @@ scheme://[bind_address]:port/[host]:hostport > host:hostport - target address -#### 开启日志 +#### Logging > -logtostderr : log to console > -v=4 : log level(1-5),The higher the level, the more detailed the log (level 5 will enable HTTP2 debug) -> -log_dir=. : log to dir - +> -log_dir=/log/dir/path : log to directory /log/dir/path Usage ------ @@ -111,132 +114,138 @@ gost -L=:8080 -F=http://admin:123456@192.168.1.1:8081 ```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类型代理。 +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. -#### 本地端口转发(TCP) +#### Local TCP port forwarding ```bash gost -L=tcp://:2222/192.168.1.1:22 -F=... ``` -将本地TCP端口2222上的数据(通过代理链)转发到192.168.1.1:22上。 +The data on the local TCP port 2222 is forwarded to 192.168.1.1:22 (through the proxy chain). -#### 本地端口转发(UDP) +#### Local UDP port forwarding ```bash gost -L=udp://:5353/192.168.1.1:53 -F=... ``` -将本地UDP端口5353上的数据(通过代理链)转发到192.168.1.1:53上。 +The data on the local UDP port 5353 is forwarded to 192.168.1.1:53 (through the proxy chain). -**注: 转发UDP数据时,如果有代理链,则代理链的末端(最后一个-F参数)必须支持gost SOCKS5类型代理。** +**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. -#### 远程端口转发(TCP) +#### Remote TCP port forwarding ```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上。 +The data on 172.24.10.1:2222 is forwarded to 192.168.1.1:22 (through the proxy chain). -#### 远程端口转发(UDP) +#### Remote UDP port forwarding ```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上。 +The data on 172.24.10.1:5353 is forwarded to 192.168.1.1:53 (through the proxy chain). -**注: 若要使用远程端口转发功能,代理链不能为空(至少要设置一个-F参数),且代理链的末端(最后一个-F参数)必须支持gost SOCKS5类型代理。** +**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支持两种模式并自适应: -* 作为标准的HTTP2代理,并向下兼容HTTPS代理。 -* 作为transport(类似于wss),传输其他协议。 +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. -**注:gost的代理链仅支持一个HTTP2代理节点,采用就近原则,会将第一个遇到的HTTP2代理节点视为HTTP2代理,其他HTTP2代理节点则被视为HTTPS代理。** -加密机制 +**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. + +Encryption Mechanism ------ #### HTTP -对于HTTP可以使用TLS加密整个通讯过程,即HTTPS代理: +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仅支持使用TLS加密的HTTP2协议,不支持明文HTTP2传输。 +Gost supports only the HTTP2 protocol that uses TLS encryption (h2) and does not support plaintext HTTP2 (h2c) transport. -服务端: +Server: ```bash gost -L=http2://:443 ``` -客户端: +Client: ```bash gost -L=:8080 -F=http2://server_ip:443 ``` #### SOCKS5 -gost支持标准SOCKS5协议的no-auth(0x00)和user/pass(0x02)方法,并在此基础上扩展了两个:tls(0x80)和tls-auth(0x82),用于数据加密。 +Gost supports the standard SOCKS5 protocol methods: no-auth (0x00) and user/pass (0x02), +and extends two methods for data encryption: tls(0x80)和tls-auth(0x82). -服务端: +Server: ```bash gost -L=socks://:1080 ``` -客户端: +Client: ```bash gost -L=:8080 -F=socks://server_ip:1080 ``` -如果两端都是gost(如上)则数据传输会被加密(协商使用tls或tls-auth方法),否则使用标准SOCKS5进行通讯(no-auth或user/pass方法)。 +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). -注:如果transport已经支持加密(wss, tls, http2),则SOCKS5不会再使用加密方法,防止不必要的双重加密。 +**NOTE:** If transport already supports encryption (wss, tls, http2), SOCKS5 will no longer use the encryption method to prevent unnecessary double encryption. #### Shadowsocks -gost对Shadowsocks加密方法的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 +Support for shadowsocks is based on library [shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go). -服务端(可以通过ota参数开启OTA模式): +Server (The OTA mode can be enabled with the ota parameter): ```bash gost -L=ss://aes-128-cfb:123456@:8338?ota=1 ``` -客户端: +Client: ```bash gost -L=:8080 -F=ss://aes-128-cfb:123456@server_ip:8338 ``` #### TLS -gost内置了TLS证书,如果需要使用其他TLS证书,有两种方法: -* 在gost运行目录放置cert.pem(公钥)和key.pem(私钥)两个文件即可,gost会自动加载运行目录下的cert.pem和key.pem文件。 -* 使用参数指定证书文件路径: +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数据处理 +SOCKS5 UDP Data Processing ------ -#### 不设置转发代理 +#### No forward proxy -gost作为标准SOCKS5代理处理UDP数据 +Gost acts as the standard SOCKS5 proxy for UDP relay. -#### 设置转发代理 +#### Forward proxy -#### 设置多个转发代理(代理链) +#### Multi-level forward proxy -当设置转发代理时,gost会使用UDP-over-TCP方式转发UDP数据。proxy1 - proxyN可以为任意HTTP/HTTPS/HTTP2/SOCKS5/Shadowsocks类型代理。 +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 ------ -代理链中的HTTP代理节点必须支持CONNECT方法。 +The HTTP proxy node in the proxy chain must support the CONNECT method. -如果要转发SOCKS5的BIND和UDP请求,代理链的末端(最后一个-F参数)必须支持gost SOCKS5类型代理。 +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. From 944af506397b452cfe9044a9d07e37db6f9c6e12 Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Sat, 22 Oct 2016 10:11:46 +0800 Subject: [PATCH 5/7] update README --- README.md | 2 +- README_en.md | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5428014..a01b653 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ gost -L=:8080 -F=socks://server_ip:1080 **注:** 如果transport已经支持加密(wss, tls, http2),则SOCKS5不会再使用加密方法,防止不必要的双重加密。 #### Shadowsocks -gost对Shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 +gost对shadowsocks的支持是基于[shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go)库。 服务端(可以通过ota参数开启OTA模式): ```bash diff --git a/README_en.md b/README_en.md index e0a3026..98360d1 100644 --- a/README_en.md +++ b/README_en.md @@ -3,8 +3,6 @@ gost - GO Simple Tunnel ### A simple security tunnel written in Golang -[中文文档](README.md) - Features ------ * Listening on multiple ports @@ -155,10 +153,24 @@ 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. - **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. + Encryption Mechanism ------ #### HTTP @@ -206,7 +218,7 @@ Otherwise, use standard SOCKS5 for communication (no-auth or user/pass). #### Shadowsocks Support for shadowsocks is based on library [shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go). -Server (The OTA mode can be enabled with the ota parameter): +Server (The OTA mode can be enabled by the ota parameter): ```bash gost -L=ss://aes-128-cfb:123456@:8338?ota=1 ``` From de6b165b681ab02b10d97aec66a23ad9730201d1 Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Sat, 22 Oct 2016 17:15:16 +0800 Subject: [PATCH 6/7] add KCP support --- README.md | 21 ++- chain.go | 79 ++++++++- cmd/gost/main.go | 3 +- cmd/tools/gost_client.go | 80 --------- cmd/tools/gost_server.go | 53 ------ conn.go | 2 + kcp.go | 369 +++++++++++++++++++++++++++++++++++++++ node.go | 2 +- server.go | 7 +- ss.go | 1 + 10 files changed, 476 insertions(+), 141 deletions(-) delete mode 100644 cmd/tools/gost_client.go delete mode 100644 cmd/tools/gost_server.go create mode 100644 kcp.go diff --git a/README.md b/README.md index a01b653..b307237 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ gost - GO Simple Tunnel * 支持端口转发 (>=2.1) * 支持HTTP2.0 (>=2.2) * 实验性支持QUIC (>=2.3) +* KCP (>=2.3) 二进制文件下载:https://github.com/ginuerzh/gost/releases @@ -34,7 +35,7 @@ Google讨论组: https://groups.google.com/d/forum/go-gost ``` scheme分为两部分: protocol+transport -protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic), 二者可以任意组合,或单独使用: +protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic, kcp), 二者可以任意组合,或单独使用: > http - 作为HTTP代理: http://:8080 @@ -52,6 +53,8 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输 > quic - 作为QUIC代理,quic://:6121 +> kcp - 作为KCP代理,kcp://:8388 + #### 端口转发 适用于-L参数 @@ -169,6 +172,22 @@ 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 +``` + +**注:** 客户端若要开启KCP转发,当且仅当代理链不为空且首个代理节点(第一个-F参数)为kcp类型。 +当KCP转发开启,代理链中的其他代理节点将被忽略。 + 加密机制 ------ #### HTTP diff --git a/chain.go b/chain.go index 78763d1..a323642 100644 --- a/chain.go +++ b/chain.go @@ -13,6 +13,7 @@ import ( "net/http/httputil" "net/url" "strings" + "sync" ) // Proxy chain holds a list of proxy nodes @@ -22,6 +23,10 @@ type ProxyChain struct { http2NodeIndex int http2Enabled bool http2Client *http.Client + kcpEnabled bool + kcpConfig *KCPConfig + kcpSession *KCPSession + kcpMutex sync.Mutex } func NewProxyChain(nodes ...ProxyNode) *ProxyChain { @@ -61,11 +66,12 @@ func (c *ProxyChain) SetNode(index int, node ProxyNode) { } } -// TryEnableHttp2 initialize HTTP2 if available. +// Init initialize the proxy chain. +// KCP will be enabled if the first proxy node is KCP proxy (transport == kcp), the remaining nodes are ignored. // 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, HTTP2 will not be enabled if this function not be called. -func (c *ProxyChain) TryEnableHttp2() { +// NOTE: Should be called immediately when proxy nodes are ready. +func (c *ProxyChain) Init() { length := len(c.nodes) if length == 0 { return @@ -73,6 +79,17 @@ func (c *ProxyChain) TryEnableHttp2() { c.lastNode = &c.nodes[length-1] + 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) + } + c.kcpConfig = config + return + } + // 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" { @@ -88,6 +105,10 @@ func (c *ProxyChain) TryEnableHttp2() { } } +func (c *ProxyChain) KCPEnabled() bool { + return c.kcpEnabled +} + func (c *ProxyChain) Http2Enabled() bool { return c.http2Enabled } @@ -130,6 +151,19 @@ func (c *ProxyChain) GetConn() (net.Conn, error) { return nil, ErrEmptyChain } + if c.KCPEnabled() { + kcpConn, err := c.getKCPConn() + if err != nil { + return nil, err + } + pc := NewProxyConn(kcpConn, c.nodes[0]) + if err := pc.Handshake(); err != nil { + pc.Close() + return nil, err + } + return pc, nil + } + if c.Http2Enabled() { nodes = nodes[c.http2NodeIndex+1:] if len(nodes) == 0 { @@ -162,6 +196,23 @@ func (c *ProxyChain) dialWithNodes(withHttp2 bool, addr string, nodes ...ProxyNo return net.DialTimeout("tcp", addr, DialTimeout) } + if c.KCPEnabled() { + kcpConn, err := c.getKCPConn() + if err != nil { + return nil, err + } + pc := NewProxyConn(kcpConn, nodes[0]) + if err := pc.Handshake(); err != nil { + pc.Close() + return nil, err + } + if err := pc.Connect(addr); err != nil { + pc.Close() + return nil, err + } + return pc, nil + } + if withHttp2 && c.Http2Enabled() { nodes = nodes[c.http2NodeIndex+1:] if len(nodes) == 0 { @@ -219,6 +270,28 @@ func (c *ProxyChain) travelNodes(withHttp2 bool, nodes ...ProxyNode) (conn *Prox 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() { diff --git a/cmd/gost/main.go b/cmd/gost/main.go index 12ccbe9..ae15b9e 100644 --- a/cmd/gost/main.go +++ b/cmd/gost/main.go @@ -43,8 +43,7 @@ func main() { if err := chain.AddProxyNodeString(chainNodes...); err != nil { glog.Fatal(err) } - // enable HTTP2 - chain.TryEnableHttp2() + chain.Init() var wg sync.WaitGroup for _, ns := range serverNodes { diff --git a/cmd/tools/gost_client.go b/cmd/tools/gost_client.go deleted file mode 100644 index 64030b3..0000000 --- a/cmd/tools/gost_client.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "bufio" - "flag" - "fmt" - "github.com/ginuerzh/gost" - "github.com/golang/glog" - "golang.org/x/net/http2" - "log" - "net/http" - "net/http/httputil" - "net/url" -) - -var ( - proxyNodes stringlist - urls []string -) - -func init() { - flag.Var(&proxyNodes, "F", "forward address, can make a forward chain") - flag.Parse() - if flag.NArg() == 0 { - log.Fatal("please specific at least one request URL") - } - urls = flag.Args() - if glog.V(5) { - http2.VerboseLogs = true - } -} - -type stringlist []string - -func (list *stringlist) String() string { - return fmt.Sprintf("%s", *list) -} -func (list *stringlist) Set(value string) error { - *list = append(*list, value) - return nil -} - -func main() { - chain := gost.NewProxyChain() - if err := chain.AddProxyNodeString(proxyNodes...); err != nil { - log.Fatal(err) - } - chain.TryEnableHttp2() - - for _, u := range urls { - url, err := url.Parse(u) - if err != nil { - log.Println("Invalid url:", u) - continue - } - - log.Println("GET", u) - conn, err := chain.Dial(url.Host) - if err != nil { - log.Fatal(err) - } - req, err := http.NewRequest("GET", u, nil) - if err != nil { - log.Fatal(err) - } - - if err := req.Write(conn); err != nil { - log.Fatal(err) - } - resp, err := http.ReadResponse(bufio.NewReader(conn), req) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - - header, _ := httputil.DumpResponse(resp, false) - log.Println(string(header)) - } - -} diff --git a/cmd/tools/gost_server.go b/cmd/tools/gost_server.go deleted file mode 100644 index a830c3b..0000000 --- a/cmd/tools/gost_server.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "crypto/tls" - "flag" - "fmt" - "github.com/ginuerzh/gost" - "log" - "sync" -) - -var ( - proxyNodes stringlist -) - -func init() { - flag.Var(&proxyNodes, "L", "proxy server node") - flag.Parse() -} - -type stringlist []string - -func (list *stringlist) String() string { - return fmt.Sprintf("%s", *list) -} -func (list *stringlist) Set(value string) error { - *list = append(*list, value) - return nil -} - -func main() { - chain := gost.NewProxyChain() - var wg sync.WaitGroup - for _, ns := range proxyNodes { - serverNode, err := gost.ParseProxyNode(ns) - if err != nil { - log.Println(err) - continue - } - wg.Add(1) - go func(node gost.ProxyNode) { - defer wg.Done() - cert, err := gost.LoadCertificate(node.Get("cert"), node.Get("key")) - if err != nil { - log.Println(err) - return - } - server := gost.NewProxyServer(node, chain, &tls.Config{Certificates: []tls.Certificate{cert}}) - log.Fatal(server.Serve()) - }(serverNode) - } - wg.Wait() -} diff --git a/conn.go b/conn.go index 053c672..f31e7ae 100644 --- a/conn.go +++ b/conn.go @@ -82,6 +82,8 @@ func (c *ProxyConn) handshake() error { 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: } diff --git a/kcp.go b/kcp.go new file mode 100644 index 0000000..10752c3 --- /dev/null +++ b/kcp.go @@ -0,0 +1,369 @@ +// KCP feature is based on https://github.com/xtaci/kcptun + +package gost + +import ( + "crypto/sha1" + "encoding/json" + "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"` +} + +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, 30, 2, 1 + case "fast2": + c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1 + case "fast3": + c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1 + case "fast": + fallthrough + default: + c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 20, 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: 40, + Resend: 0, + NoCongestion: 0, + SockBuf: 4194304, + KeepAlive: 10, + } +) + +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) + } + + for { + conn, err := ln.AcceptKCP() + if err != nil { + glog.V(LWARNING).Infoln(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 +} + +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) +} diff --git a/node.go b/node.go index 8a4f043..5c730ae 100644 --- a/node.go +++ b/node.go @@ -57,7 +57,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { } switch node.Transport { - case "ws", "wss", "tls", "http2", "ssu", "quic": + case "ws", "wss", "tls", "http2", "ssu", "quic", "kcp": case "https": node.Protocol = "http" node.Transport = "tls" diff --git a/server.go b/server.go index 8ca5597..20da5d9 100644 --- a/server.go +++ b/server.go @@ -84,6 +84,12 @@ func (s *ProxyServer) Serve() error { return NewShadowUdpServer(s).ListenAndServe() 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) + } + return NewKCPServer(s, config).ListenAndServe() default: ln, err = net.Listen("tcp", node.Addr) } @@ -135,7 +141,6 @@ func (s *ProxyServer) handleConn(conn net.Conn) { return } - glog.V(LINFO).Infof("%s - %s", conn.RemoteAddr(), s.Node.Addr) // http or socks5 b := make([]byte, MediumBufferSize) diff --git a/ss.go b/ss.go index 48fa6e4..12bc167 100644 --- a/ss.go +++ b/ss.go @@ -101,6 +101,7 @@ func (s *ShadowUdpServer) ListenAndServe() error { } } +// TODO: shadowsocks udp relay handler func (s *ShadowUdpServer) HandleConn(conn *net.UDPConn, addr *net.UDPAddr, data []byte) { } From 9feb67c302f029f52993bd770707ce38a2ef48c9 Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Sun, 23 Oct 2016 18:37:50 +0800 Subject: [PATCH 7/7] update README_en.md --- README_en.md | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/README_en.md b/README_en.md index 98360d1..1219279 100644 --- a/README_en.md +++ b/README_en.md @@ -7,13 +7,14 @@ Features ------ * Listening on multiple ports * Multi-level forward proxy - proxy chain -* Standard HTTP/HTTPS/SOCKS5 proxy protocols +* Standard HTTP/HTTPS/SOCKS5 proxy protocols support * TLS encryption via negotiation support for SOCKS5 proxy * Tunnel UDP over TCP -* Shadowsocks protocol with OTA supported (OTA: >=2.2) +* Shadowsocks protocol support with OTA option (OTA: >=2.2) * Local/remote port forwarding (>=2.1) * HTTP2.0 (>=2.2) * Experimental QUIC support (>=2.3) +* KCP (>=2.3) Binary file download:https://github.com/ginuerzh/gost/releases @@ -33,12 +34,12 @@ Effective for the -L and -F parameters ``` scheme can be divided into two parts: protocol+transport -protocol: proxy protocol types(http, socks5, shadowsocks), -transport: data transmission mode(ws, wss, tls, http2, quic), may be used in any combination or individually: +protocol: proxy protocol types (http, socks5, shadowsocks), +transport: data transmission mode (ws, wss, tls, http2, quic, kcp), 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 +> http+tls - standard HTTPS proxy (may need to provide a trusted certificate): http+tls://:443 > http2 - HTTP2 proxy and backwards-compatible with HTTPS proxy: http2://:443 @@ -52,6 +53,8 @@ transport: data transmission mode(ws, wss, tls, http2, quic), may be used in any > quic - standard QUIC proxy, quic://:6121 +> kcp - standard KCP tunnel,kcp://:8388 + #### Port forwarding Effective for the -L parameter @@ -69,7 +72,7 @@ scheme://[bind_address]:port/[host]:hostport > -logtostderr : log to console -> -v=4 : log level(1-5),The higher the level, the more detailed the log (level 5 will enable HTTP2 debug) +> -v=4 : 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 @@ -163,7 +166,6 @@ Server: ```bash gost -L=quic://:6121 ``` - Client(Chrome): ```bash chrome --enable-quic --proxy-server=quic://server_ip:6121 @@ -171,6 +173,21 @@ 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 +``` + +**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. +When KCP is enabled, other proxy nodes are ignored. + Encryption Mechanism ------ #### HTTP @@ -199,7 +216,7 @@ gost -L=:8080 -F=http2://server_ip:443 #### SOCKS5 Gost supports the standard SOCKS5 protocol methods: no-auth (0x00) and user/pass (0x02), -and extends two methods for data encryption: tls(0x80)和tls-auth(0x82). +and extends two methods for data encryption: tls(0x80) and tls-auth(0x82). Server: ```bash