From 6f0bf7da03fc37f0bec65be4c270fba9ad6ac1c9 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Wed, 5 Jun 2019 18:17:32 +0800 Subject: [PATCH] add go-shadowsocks2 support --- cmd/gost/route.go | 4 + go.mod | 14 +-- go.sum | 28 +++--- gost.go | 2 +- node.go | 2 +- ss2.go | 229 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+), 20 deletions(-) create mode 100644 ss2.go diff --git a/cmd/gost/route.go b/cmd/gost/route.go index 0aad21f..434d6e3 100644 --- a/cmd/gost/route.go +++ b/cmd/gost/route.go @@ -184,6 +184,8 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { connector = gost.SOCKS4AConnector() case "ss": connector = gost.ShadowConnector(node.User) + case "ss2": + connector = gost.Shadow2Connector(node.User) case "direct": connector = gost.SSHDirectForwardConnector() case "remote": @@ -375,6 +377,8 @@ func (r *route) GenRouters() ([]router, error) { handler = gost.SOCKS4Handler() case "ss": handler = gost.ShadowHandler() + case "ss2": + handler = gost.Shadow2Handler() case "http": handler = gost.HTTPHandler() case "tcp": diff --git a/go.mod b/go.mod index fda2862..ec10b66 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/dchest/siphash v1.2.1 // indirect @@ -21,19 +22,20 @@ require ( github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect github.com/lucas-clemente/quic-go v0.10.0 github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect - github.com/miekg/dns v1.1.1 + github.com/miekg/dns v1.1.3 github.com/onsi/ginkgo v1.7.0 // indirect github.com/onsi/gomega v1.4.3 // indirect - github.com/pkg/errors v0.8.0 // indirect + github.com/pkg/errors v0.8.1 // indirect github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 + github.com/shadowsocks/go-shadowsocks2 v0.0.11 github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect github.com/tjfoc/gmsm v1.0.1 // indirect - golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 - golang.org/x/net v0.0.0-20181201002055-351d144fa1fc - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect - golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e // indirect + golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664 + golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 + golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect + golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc // indirect gopkg.in/gorilla/websocket.v1 v1.4.0 gopkg.in/xtaci/kcp-go.v4 v4.3.2 gopkg.in/xtaci/smux.v1 v1.0.7 diff --git a/go.sum b/go.sum index 369737c..9181901 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470 git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e/go.mod h1:jRZbfRcLIgFQoCw6tRmsnETVyIj54jOmXhHCYYa0jbs= github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0= github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4= @@ -48,17 +50,19 @@ github.com/lucas-clemente/quic-go v0.10.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA github.com/lucas-clemente/quic-go v0.10.0/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY= github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug= github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= -github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o= -github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM= +github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/shadowsocks/go-shadowsocks2 v0.0.11 h1:dXloqEhYnZV40jblWTK8kWeC0Eb+dgql4S0tj99e8j0= +github.com/shadowsocks/go-shadowsocks2 v0.0.11/go.mod h1:R+KWaoIwRRhnpw6XV+dZil0XHi64Hc1D7hXUyXTjUzQ= github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba h1:tJgNXb3S+RkB4kNPi6N5OmEWe3m+Y3Qs6LUMiNDAONM= github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY= github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= @@ -68,18 +72,18 @@ github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mn github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU= github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664 h1:YbZJ76lQ1BqNhVe7dKTSB67wDrc2VPRR75IyGyyPDX8= +golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo= -golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc h1:WiYx1rIFmx8c0mXAFtv5D/mHyKe1+jmuP7PViuwqwuQ= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/gost.go b/gost.go index 740b116..8e1efbf 100644 --- a/gost.go +++ b/gost.go @@ -19,7 +19,7 @@ import ( ) // Version is the gost version. -const Version = "2.7.2" +const Version = "2.8.0" // Debug is a flag that enables the debug log. var Debug bool diff --git a/node.go b/node.go index 282a027..9acb2a0 100644 --- a/node.go +++ b/node.go @@ -85,7 +85,7 @@ func ParseNode(s string) (node Node, err error) { } switch node.Protocol { - case "http", "http2", "socks4", "socks4a", "ss", "ssu", "sni": + case "http", "http2", "socks4", "socks4a", "ss", "ss2", "ssu", "sni": case "socks", "socks5": node.Protocol = "socks5" case "tcp", "udp", "rtcp", "rudp": // port forwarding diff --git a/ss2.go b/ss2.go new file mode 100644 index 0000000..44f0ccd --- /dev/null +++ b/ss2.go @@ -0,0 +1,229 @@ +package gost + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "net/url" + "time" + + "github.com/ginuerzh/gosocks5" + "github.com/go-log/log" + "github.com/shadowsocks/go-shadowsocks2/core" +) + +type shadow2Connector struct { + Cipher *url.Userinfo +} + +// Shadow2Connector creates a Connector for go-shadowsocks2 proxy client. +// It accepts a cipher info for shadowsocks data encryption/decryption. +// The cipher must not be nil. +func Shadow2Connector(cipher *url.Userinfo) Connector { + return &shadow2Connector{Cipher: cipher} +} + +func (c *shadow2Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { + opts := &ConnectOptions{} + for _, option := range options { + option(opts) + } + + timeout := opts.Timeout + if timeout <= 0 { + timeout = ConnectTimeout + } + + conn.SetDeadline(time.Now().Add(timeout)) + defer conn.SetDeadline(time.Time{}) + + socksAddr, err := gosocks5.NewAddr(addr) + if err != nil { + return nil, err + } + + rawaddr := sPool.Get().([]byte) + defer sPool.Put(rawaddr) + + n, err := socksAddr.Encode(rawaddr) + if err != nil { + return nil, err + } + + var method, password string + cp := opts.User + if cp == nil { + cp = c.Cipher + } + if cp != nil { + method = cp.Username() + password, _ = cp.Password() + } + + cipher, err := core.PickCipher(method, nil, password) + if err != nil { + return nil, err + } + conn = cipher.StreamConn(conn) + if _, err := conn.Write(rawaddr[:n]); err != nil { + return nil, err + } + + return conn, nil +} + +type shadow2Handler struct { + options *HandlerOptions +} + +// Shadow2Handler creates a server Handler for go-shadowsocks2 proxy server. +func Shadow2Handler(opts ...HandlerOption) Handler { + h := &shadow2Handler{} + h.Init(opts...) + + return h +} + +func (h *shadow2Handler) Init(options ...HandlerOption) { + if h.options == nil { + h.options = &HandlerOptions{} + } + + for _, opt := range options { + opt(h.options) + } +} + +func (h *shadow2Handler) Handle(conn net.Conn) { + defer conn.Close() + + var method, password string + users := h.options.Users + if len(users) > 0 { + method = users[0].Username() + password, _ = users[0].Password() + } + + cipher, err := core.PickCipher(method, nil, password) + if err != nil { + log.Logf("[ss2] %s -> %s : %s", + conn.RemoteAddr(), conn.LocalAddr(), err) + return + } + + conn = cipher.StreamConn(conn) + conn.SetReadDeadline(time.Now().Add(ReadTimeout)) + + addr, err := readAddr(conn) + if err != nil { + log.Logf("[ss2] %s -> %s : %s", + conn.RemoteAddr(), conn.LocalAddr(), err) + return + } + + // clear timer + conn.SetReadDeadline(time.Time{}) + + host := addr.String() + log.Logf("[ss2] %s -> %s -> %s", + conn.RemoteAddr(), h.options.Node.String(), host) + + if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) { + log.Logf("[ss2] %s - %s : Unauthorized to tcp connect to %s", + conn.RemoteAddr(), conn.LocalAddr(), host) + return + } + + if h.options.Bypass.Contains(host) { + log.Logf("[ss2] %s - %s : Bypass %s", + conn.RemoteAddr(), conn.LocalAddr(), host) + return + } + + retries := 1 + if h.options.Chain != nil && h.options.Chain.Retries > 0 { + retries = h.options.Chain.Retries + } + if h.options.Retries > 0 { + retries = h.options.Retries + } + + var cc net.Conn + var route *Chain + for i := 0; i < retries; i++ { + route, err = h.options.Chain.selectRouteFor(host) + if err != nil { + log.Logf("[ss2] %s -> %s : %s", + conn.RemoteAddr(), conn.LocalAddr(), err) + continue + } + + buf := bytes.Buffer{} + fmt.Fprintf(&buf, "%s -> %s -> ", + conn.RemoteAddr(), h.options.Node.String()) + for _, nd := range route.route { + fmt.Fprintf(&buf, "%d@%s -> ", nd.ID, nd.String()) + } + fmt.Fprintf(&buf, "%s", host) + log.Log("[route]", buf.String()) + + cc, err = route.Dial(host, + TimeoutChainOption(h.options.Timeout), + HostsChainOption(h.options.Hosts), + ResolverChainOption(h.options.Resolver), + ) + if err == nil { + break + } + log.Logf("[ss2] %s -> %s : %s", + conn.RemoteAddr(), conn.LocalAddr(), err) + } + + if err != nil { + return + } + defer cc.Close() + + log.Logf("[ss2] %s <-> %s", conn.RemoteAddr(), host) + transport(conn, cc) + log.Logf("[ss2] %s >-< %s", conn.RemoteAddr(), host) +} + +func readAddr(r io.Reader) (*gosocks5.Addr, error) { + addr := &gosocks5.Addr{} + b := sPool.Get().([]byte) + defer sPool.Put(b) + + _, err := io.ReadFull(r, b[:1]) + if err != nil { + return nil, err + } + addr.Type = b[0] + + switch addr.Type { + case gosocks5.AddrIPv4: + _, err = io.ReadFull(r, b[:net.IPv4len]) + addr.Host = net.IP(b[0:net.IPv4len]).String() + case gosocks5.AddrIPv6: + _, err = io.ReadFull(r, b[:net.IPv6len]) + addr.Host = net.IP(b[0:net.IPv6len]).String() + case gosocks5.AddrDomain: + if _, err = io.ReadFull(r, b[:1]); err != nil { + return nil, err + } + addrlen := int(b[0]) + _, err = io.ReadFull(r, b[:addrlen]) + addr.Host = string(b[:addrlen]) + default: + return nil, gosocks5.ErrBadAddrType + } + if err != nil { + return nil, err + } + + _, err = io.ReadFull(r, b[:2]) + addr.Port = binary.BigEndian.Uint16(b[:2]) + return addr, err +}