diff --git a/client.go b/client.go index ad2e366..586d565 100644 --- a/client.go +++ b/client.go @@ -197,6 +197,7 @@ type ConnectOptions struct { Selector gosocks5.Selector UserAgent string NoTLS bool + NoDelay bool } // ConnectOption allows a common way to set ConnectOptions. @@ -243,3 +244,10 @@ func NoTLSConnectOption(b bool) ConnectOption { opts.NoTLS = b } } + +// NoDelayConnectOption specifies the NoDelay option for ss.Connect. +func NoDelayConnectOption(b bool) ConnectOption { + return func(opts *ConnectOptions) { + opts.NoDelay = b + } +} diff --git a/cmd/gost/route.go b/cmd/gost/route.go index 36f76c2..aecaea7 100644 --- a/cmd/gost/route.go +++ b/cmd/gost/route.go @@ -137,8 +137,6 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { timeout := node.GetDuration("timeout") - var host string - var tr gost.Transporter switch node.Transport { case "tls": @@ -195,8 +193,9 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { case "obfs4": tr = gost.Obfs4Transporter() case "ohttp": - host = node.Get("host") tr = gost.ObfsHTTPTransporter() + case "otls": + tr = gost.ObfsTLSTransporter() case "ftcp": tr = gost.FakeTCPTransporter() case "udp": @@ -240,8 +239,10 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { node.ConnectOptions = []gost.ConnectOption{ gost.UserAgentConnectOption(node.Get("agent")), gost.NoTLSConnectOption(node.GetBool("notls")), + gost.NoDelayConnectOption(node.GetBool("nodelay")), } + host := node.Get("host") if host == "" { host = node.Host } @@ -441,6 +442,8 @@ func (r *route) GenRouters() ([]router, error) { ln, err = gost.Obfs4Listener(node.Addr) case "ohttp": ln, err = gost.ObfsHTTPListener(node.Addr) + case "otls": + ln, err = gost.ObfsTLSListener(node.Addr) case "tun": cfg := gost.TunConfig{ Name: node.Get("name"), diff --git a/go.mod b/go.mod index c6794f6..7394e6d 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/docker/libcontainer v2.2.1+incompatible github.com/ginuerzh/gosocks4 v0.0.1 github.com/ginuerzh/gosocks5 v0.2.0 - github.com/ginuerzh/tls-dissector v0.0.1 + github.com/ginuerzh/tls-dissector v0.0.2-0.20200224064855-24ab2b3a3796 github.com/go-log/log v0.1.0 github.com/gobwas/glob v0.2.3 github.com/golang/mock v1.2.0 // indirect diff --git a/go.sum b/go.sum index 59f803a..961e6c0 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,16 @@ github.com/ginuerzh/gosocks5 v0.2.0 h1:K0Ua23U9LU3BZrf3XpGDcs0mP8DiEpa6PJE4TA/MU github.com/ginuerzh/gosocks5 v0.2.0/go.mod h1:qp22mr6tH/prEoaN0pFukq76LlScIE+F2rP2ZP5ZHno= github.com/ginuerzh/tls-dissector v0.0.1 h1:yF6fIt78TO4CdjiLLn6R8r0XajQJE1Lbnuq6rP8mGW8= github.com/ginuerzh/tls-dissector v0.0.1/go.mod h1:u/kbBOqIOgJv39gywuUb3VwyzdZG5DKquOqfToKE6lk= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200223041816-c0cb3da7ea91 h1:bFBTbZglO4xNVWSLwDEcVKBIurTXGL2sNKi9UuQima4= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200223041816-c0cb3da7ea91/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200223072427-83db9c3e4eb5 h1:pmGmno31njvF5xncoDcDuM8mE1984cxrQ0DeVD4lVfA= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200223072427-83db9c3e4eb5/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200223110639-e9c10af0eb19 h1:t/AZCq8FiVNN+Mx6UmIv7bXj3+OVThg070G8ajZ3wJw= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200223110639-e9c10af0eb19/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200223121713-a8bf02a99d69 h1:h9lREy0OWSTrjweGxduikppA2tCjGPoUj32SVHI3dr0= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200223121713-a8bf02a99d69/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200224064855-24ab2b3a3796 h1:VPXbYRvZUzTemsI7u0FzOnEuHeHwQuMTPXApAu8aeX4= +github.com/ginuerzh/tls-dissector v0.0.2-0.20200224064855-24ab2b3a3796/go.mod h1:YyzP8PQrGwDH/XsfHJXwqdHLwWvBYxu77YVKm0+68f0= github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U= github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= diff --git a/gost.go b/gost.go index cf75028..e135113 100644 --- a/gost.go +++ b/gost.go @@ -20,7 +20,7 @@ import ( ) // Version is the gost version. -const Version = "2.10.1" +const Version = "2.10.2-dev" // Debug is a flag that enables the debug log. var Debug bool diff --git a/node.go b/node.go index 336ac15..0e31451 100644 --- a/node.go +++ b/node.go @@ -83,10 +83,9 @@ func ParseNode(s string) (node Node, err error) { case "kcp", "ssh", "quic": case "ssu": node.Transport = "udp" - case "obfs4": + case "ohttp", "otls", "obfs4": // obfs case "tcp", "udp": case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding - case "ohttp": // obfs-http case "tun", "tap": // tun/tap device case "ftcp": // fake TCP case "dns": diff --git a/obfs.go b/obfs.go index 7f1e1e0..684fbac 100644 --- a/obfs.go +++ b/obfs.go @@ -5,6 +5,8 @@ package gost import ( "bufio" "bytes" + "crypto/rand" + "crypto/tls" "errors" "fmt" "io" @@ -20,6 +22,7 @@ import ( pt "git.torproject.org/pluggable-transports/goptlib.git" "git.torproject.org/pluggable-transports/obfs4.git/transports/base" "git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4" + dissector "github.com/ginuerzh/tls-dissector" ) type obfsHTTPTransporter struct { @@ -249,6 +252,315 @@ func (c *obfsHTTPConn) Write(b []byte) (n int, err error) { return c.Conn.Write(b) } +type obfsTLSTransporter struct { + tcpTransporter +} + +// ObfsTLSTransporter creates a Transporter that is used by TLS obfuscating. +func ObfsTLSTransporter() Transporter { + return &obfsTLSTransporter{} +} + +func (tr *obfsTLSTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { + opts := &HandshakeOptions{} + for _, option := range options { + option(opts) + } + return ClientObfsTLSConn(conn, opts.Host), nil +} + +type obfsTLSListener struct { + net.Listener +} + +// ObfsTLSListener creates a Listener for TLS obfuscating server. +func ObfsTLSListener(addr string) (Listener, error) { + laddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return nil, err + } + ln, err := net.ListenTCP("tcp", laddr) + if err != nil { + return nil, err + } + return &obfsTLSListener{Listener: tcpKeepAliveListener{ln}}, nil +} + +func (l *obfsTLSListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + + return ServerObfsTLSConn(conn, ""), nil +} + +var ( + cipherSuites = []uint16{ + 0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8, 0xccaa, 0xc02b, 0xc02f, + 0x009e, 0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067, 0xc00a, + 0xc014, 0x0039, 0xc009, 0xc013, 0x0033, 0x009d, 0x009c, 0x003d, + 0x003c, 0x0035, 0x002f, 0x00ff, + } + + compressionMethods = []uint8{0x00} + + algorithms = []uint16{ + 0x0601, 0x0602, 0x0603, 0x0501, 0x0502, 0x0503, 0x0401, 0x0402, + 0x0403, 0x0301, 0x0302, 0x0303, 0x0201, 0x0202, 0x0203, + } +) + +type obfsTLSConn struct { + net.Conn + rbuf bytes.Buffer + wbuf bytes.Buffer + host string + isServer bool + handshaked chan struct{} + handshakeMutex sync.Mutex +} + +func ClientObfsTLSConn(conn net.Conn, host string) net.Conn { + return &obfsTLSConn{ + Conn: conn, + host: host, + handshaked: make(chan struct{}), + } +} + +func ServerObfsTLSConn(conn net.Conn, host string) net.Conn { + return &obfsTLSConn{ + Conn: conn, + host: host, + isServer: true, + handshaked: make(chan struct{}), + } +} + +func (c *obfsTLSConn) Handshaked() bool { + select { + case <-c.handshaked: + return true + default: + return false + } +} + +func (c *obfsTLSConn) Handshake(payload []byte) (err error) { + c.handshakeMutex.Lock() + defer c.handshakeMutex.Unlock() + + if c.Handshaked() { + return + } + + if c.isServer { + err = c.serverHandshake() + } else { + err = c.clientHandshake(payload) + } + if err != nil { + return + } + + close(c.handshaked) + return nil +} + +func (c *obfsTLSConn) clientHandshake(payload []byte) error { + clientMsg := &dissector.ClientHelloMsg{ + Version: tls.VersionTLS12, + SessionID: make([]byte, 32), + CipherSuites: cipherSuites, + CompressionMethods: compressionMethods, + Extensions: []dissector.Extension{ + &dissector.SessionTicketExtension{ + Data: payload, + }, + &dissector.ServerNameExtension{ + Name: c.host, + }, + &dissector.ECPointFormatsExtension{ + Formats: []uint8{0x01, 0x00, 0x02}, + }, + &dissector.SupportedGroupsExtension{ + Groups: []uint16{0x001d, 0x0017, 0x0019, 0x0018}, + }, + &dissector.SignatureAlgorithmsExtension{ + Algorithms: algorithms, + }, + &dissector.EncryptThenMacExtension{}, + &dissector.ExtendedMasterSecretExtension{}, + }, + } + clientMsg.Random.Time = uint32(time.Now().Unix()) + rand.Read(clientMsg.Random.Opaque[:]) + rand.Read(clientMsg.SessionID) + b, err := clientMsg.Encode() + if err != nil { + return err + } + + record := &dissector.Record{ + Type: dissector.Handshake, + Version: tls.VersionTLS10, + Opaque: b, + } + if _, err := record.WriteTo(c.Conn); err != nil { + return err + } + + // server hello handshake message + if _, err := record.ReadFrom(c.Conn); err != nil { + return err + } + if record.Type != dissector.Handshake { + return dissector.ErrBadType + } + + // change cipher spec message + if _, err := record.ReadFrom(c.Conn); err != nil { + return err + } + if record.Type != dissector.ChangeCipherSpec { + return dissector.ErrBadType + } + + // encrypted handshake message + if _, err := record.ReadFrom(c.Conn); err != nil { + return err + } + if record.Type != dissector.Handshake { + return dissector.ErrBadType + } + + _, err = c.rbuf.Write(record.Opaque) + return err +} + +func (c *obfsTLSConn) serverHandshake() error { + record := &dissector.Record{} + if _, err := record.ReadFrom(c.Conn); err != nil { + log.Log(err) + return err + } + if record.Type != dissector.Handshake { + return dissector.ErrBadType + } + + clientMsg := &dissector.ClientHelloMsg{} + if err := clientMsg.Decode(record.Opaque); err != nil { + log.Log(err) + return err + } + + for _, ext := range clientMsg.Extensions { + if ext.Type() == dissector.ExtSessionTicket { + b, err := ext.Encode() + if err != nil { + log.Log(err) + return err + } + c.rbuf.Write(b) + break + } + } + + serverMsg := &dissector.ServerHelloMsg{ + Version: tls.VersionTLS12, + SessionID: clientMsg.SessionID, + CipherSuite: 0xcca8, + CompressionMethod: 0x00, + Extensions: []dissector.Extension{ + &dissector.RenegotiationInfoExtension{}, + &dissector.ExtendedMasterSecretExtension{}, + &dissector.ECPointFormatsExtension{ + Formats: []uint8{0x00}, + }, + }, + } + + serverMsg.Random.Time = uint32(time.Now().Unix()) + rand.Read(serverMsg.Random.Opaque[:]) + b, err := serverMsg.Encode() + if err != nil { + return err + } + + record = &dissector.Record{ + Type: dissector.Handshake, + Version: tls.VersionTLS10, + Opaque: b, + } + + if _, err := record.WriteTo(&c.wbuf); err != nil { + return err + } + + record = &dissector.Record{ + Type: dissector.ChangeCipherSpec, + Version: tls.VersionTLS12, + Opaque: []byte{0x01}, + } + if _, err := record.WriteTo(&c.wbuf); err != nil { + return err + } + return nil +} + +func (c *obfsTLSConn) Read(b []byte) (n int, err error) { + if c.isServer { // NOTE: only Write performs the handshake operation on client side. + if err = c.Handshake(nil); err != nil { + return + } + } + select { + case <-c.handshaked: + } + + if c.rbuf.Len() > 0 { + return c.rbuf.Read(b) + } + record := &dissector.Record{} + if _, err = record.ReadFrom(c.Conn); err != nil { + return + } + n = copy(b, record.Opaque) + _, err = c.rbuf.Write(record.Opaque[n:]) + return +} + +func (c *obfsTLSConn) Write(b []byte) (n int, err error) { + n = len(b) + if !c.Handshaked() { + if err = c.Handshake(b); err != nil { + return + } + if !c.isServer { // the data b has been sended during handshake phase. + return + } + } + + record := &dissector.Record{ + Type: dissector.AppData, + Version: tls.VersionTLS12, + Opaque: b, + } + + if c.wbuf.Len() > 0 { + record.Type = dissector.Handshake + record.WriteTo(&c.wbuf) + _, err = c.wbuf.WriteTo(c.Conn) + return + } + + if _, err = record.WriteTo(c.Conn); err != nil { + return + } + return +} + type obfs4Context struct { cf base.ClientFactory cargs interface{} // type obfs4ClientArgs diff --git a/snapcraft.yaml b/snapcraft.yaml index 54556e9..0b407b1 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,6 +1,6 @@ name: gost type: app -version: '2.10.1' +version: '2.10.2' title: GO Simple Tunnel summary: A simple security tunnel written in golang description: | diff --git a/sni.go b/sni.go index 3cfeb26..62e5fb7 100644 --- a/sni.go +++ b/sni.go @@ -270,7 +270,7 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str if err != nil { return nil, "", err } - clientHello := &dissector.ClientHelloHandshake{} + clientHello := &dissector.ClientHelloMsg{} if err := clientHello.Decode(record.Opaque); err != nil { return nil, "", err } @@ -280,7 +280,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str for _, ext := range clientHello.Extensions { if ext.Type() == 0xFFFE { - if host, err = decodeServerName(string(ext.Bytes()[4:])); err == nil { + b, _ := ext.Encode() + if host, err = decodeServerName(string(b)); err == nil { continue } } @@ -296,8 +297,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str host = snExtension.Name } if isClient { - clientHello.Extensions = append(clientHello.Extensions, - dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))) + e, _ := dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name))) + clientHello.Extensions = append(clientHello.Extensions, e) } if host != "" { snExtension.Name = host diff --git a/ss.go b/ss.go index 17bace4..8aa5516 100644 --- a/ss.go +++ b/ss.go @@ -75,10 +75,21 @@ func (c *shadowConnector) ConnectContext(ctx context.Context, conn net.Conn, net if c.cipher != nil { conn = c.cipher.StreamConn(conn) } - if _, err := conn.Write(rawaddr[:n]); err != nil { - return nil, err + + sc := &shadowConn{ + Conn: conn, } - return conn, nil + + // write the addr at once. + if opts.NoDelay { + if _, err := sc.Write(rawaddr[:n]); err != nil { + return nil, err + } + } else { + sc.wbuf.Write(rawaddr[:n]) // cache the header + } + + return sc, nil } type shadowHandler struct { @@ -111,7 +122,9 @@ func (h *shadowHandler) Handle(conn net.Conn) { defer conn.Close() if h.cipher != nil { - conn = h.cipher.StreamConn(conn) + conn = &shadowConn{ + Conn: h.cipher.StreamConn(conn), + } } conn.SetReadDeadline(time.Now().Add(ReadTimeout)) @@ -244,7 +257,9 @@ func (c *shadowUDPConnector) ConnectContext(ctx context.Context, conn net.Conn, } if c.cipher != nil { - conn = c.cipher.StreamConn(conn) + conn = &shadowConn{ + Conn: c.cipher.StreamConn(conn), + } } return &socks5UDPTunnelConn{ @@ -302,7 +317,9 @@ func (h *shadowUDPHandler) Handle(conn net.Conn) { } if h.cipher != nil { - conn = h.cipher.StreamConn(conn) + conn = &shadowConn{ + Conn: h.cipher.StreamConn(conn), + } } log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr()) @@ -469,10 +486,17 @@ func (h *shadowUDPHandler) transportUDP(conn net.Conn, cc net.PacketConn) error // we wrap around it to make io.Copy happy. type shadowConn struct { net.Conn + wbuf bytes.Buffer } func (c *shadowConn) Write(b []byte) (n int, err error) { n = len(b) // force byte length consistent + if c.wbuf.Len() > 0 { + c.wbuf.Write(b) // append the data to the cached header + _, err = c.Conn.Write(c.wbuf.Bytes()) + c.wbuf.Reset() + return + } _, err = c.Conn.Write(b) return } @@ -546,9 +570,7 @@ type shadowCipher struct { } func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn { - return &shadowConn{ - Conn: ss.NewConn(conn, c.cipher.Copy()), - } + return ss.NewConn(conn, c.cipher.Copy()) } func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn { @@ -566,14 +588,17 @@ func initShadowCipher(info *url.Userinfo) (cipher core.Cipher) { return } - cipher, _ = core.PickCipher(method, nil, password) + cp, _ := ss.NewCipher(method, password) + if cp != nil { + cipher = &shadowCipher{cipher: cp} + } if cipher == nil { - cp, err := ss.NewCipher(method, password) + var err error + cipher, err = core.PickCipher(method, nil, password) if err != nil { log.Logf("[ss] %s", err) return } - cipher = &shadowCipher{cipher: cp} } return }