diff --git a/README.md b/README.md index a01b653..af53e42 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参数 @@ -69,7 +72,7 @@ scheme://[bind_address]:port/[host]:hostport > -logtostderr : 输出到控制台 -> -v=4 : 日志级别(1-5),级别越高,日志越详细(级别5将开启http2 debug) +> -v=3 : 日志级别(1-5),级别越高,日志越详细(级别5将开启http2 debug) > -log_dir=/log/dir/path : 输出到目录/log/dir/path @@ -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/README_en.md b/README_en.md index 98360d1..f29e2b7 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=3 : log level (1-5),The higher the level, the more detailed the log (level 5 will enable HTTP2 debug) > -log_dir=/log/dir/path : log to directory /log/dir/path @@ -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 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/gost.go b/gost.go index 66be983..ef98ce6 100644 --- a/gost.go +++ b/gost.go @@ -11,7 +11,7 @@ import ( ) const ( - Version = "2.2" + Version = "2.3-dev" ) // Log level for glog 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 79be88c..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": + case "ws", "wss", "tls", "http2", "ssu", "quic", "kcp": 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..20da5d9 100644 --- a/server.go +++ b/server.go @@ -80,6 +80,16 @@ 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) + 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) } @@ -131,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 41afff8..12bc167 100644 --- a/ss.go +++ b/ss.go @@ -65,6 +65,47 @@ 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]) + } +} + +// TODO: shadowsocks udp relay handler +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