diff --git a/cmd/gost/config.go b/cmd/gost/config.go index 64e32ec..1ea1591 100644 --- a/cmd/gost/config.go +++ b/cmd/gost/config.go @@ -57,6 +57,9 @@ func buildService(cfg *config.Config) (services []*service.Service) { chainable.WithChain(chains[svc.Chain]) } + if svc.Listener.Metadata == nil { + svc.Listener.Metadata = make(map[string]interface{}) + } if err := ln.Init(metadata.MapMetadata(svc.Listener.Metadata)); err != nil { listenerLogger.Fatal("init: ", err) } @@ -78,6 +81,9 @@ func buildService(cfg *config.Config) (services []*service.Service) { forwarder.Forward(forwarderFromConfig(svc.Forwarder)) } + if svc.Handler.Metadata == nil { + svc.Handler.Metadata = make(map[string]interface{}) + } if err := h.Init(metadata.MapMetadata(svc.Handler.Metadata)); err != nil { handlerLogger.Fatal("init: ", err) } @@ -119,6 +125,10 @@ func chainFromConfig(cfg *config.ChainConfig) *chain.Chain { cr := registry.GetConnector(v.Connector.Type)( connector.LoggerOption(connectorLogger), ) + + if v.Connector.Metadata == nil { + v.Connector.Metadata = make(map[string]interface{}) + } if err := cr.Init(metadata.MapMetadata(v.Connector.Metadata)); err != nil { connectorLogger.Fatal("init: ", err) } @@ -133,6 +143,10 @@ func chainFromConfig(cfg *config.ChainConfig) *chain.Chain { d := registry.GetDialer(v.Dialer.Type)( dialer.LoggerOption(dialerLogger), ) + + if v.Dialer.Metadata == nil { + v.Dialer.Metadata = make(map[string]interface{}) + } if err := d.Init(metadata.MapMetadata(v.Dialer.Metadata)); err != nil { dialerLogger.Fatal("init: ", err) } diff --git a/cmd/gost/register.go b/cmd/gost/register.go index a35a487..a0a2f0d 100644 --- a/cmd/gost/register.go +++ b/cmd/gost/register.go @@ -5,6 +5,7 @@ import ( _ "github.com/go-gost/gost/pkg/connector/forward" _ "github.com/go-gost/gost/pkg/connector/http" _ "github.com/go-gost/gost/pkg/connector/relay" + _ "github.com/go-gost/gost/pkg/connector/sni" _ "github.com/go-gost/gost/pkg/connector/socks/v4" _ "github.com/go-gost/gost/pkg/connector/socks/v5" _ "github.com/go-gost/gost/pkg/connector/ss" diff --git a/go.mod b/go.mod index 9425e0f..ff59bc1 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.17 require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/coreos/go-iptables v0.5.0 // indirect github.com/ginuerzh/tls-dissector v0.0.2-0.20201202075250-98fa925912da github.com/go-gost/gosocks4 v0.0.1 diff --git a/go.sum b/go.sum index 08621e8..763385d 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -115,8 +117,6 @@ github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09 h1:A95M6UWcfZgO github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4= github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7 h1:itaaJhQJ19kUXEB4Igb0EbY8m+1Py2AaNNSBds/9gk4= github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8= -github.com/go-gost/tls-dissector v0.0.1 h1:cySZTSa7o5aOg/bqZXVFzi3NMudsiLwkzArFoxjUWCY= -github.com/go-gost/tls-dissector v0.0.1/go.mod h1:8CmRTbp7v4Ebd/lewu/Y/4dEJOP9ke6nwumyJ9WlOec= github.com/go-gost/tls-dissector v0.0.2-0.20211125135007-2b5d5bd9c07e h1:73NGqAs22ey3wJkIYVD/ACEoovuIuOlEzQTEoqrO5+U= github.com/go-gost/tls-dissector v0.0.2-0.20211125135007-2b5d5bd9c07e/go.mod h1:/9QfdewqmHdaE362Hv5nDaSWLx3pCmtD870d6GaquXs= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= diff --git a/pkg/common/bufpool/pool.go b/pkg/common/bufpool/pool.go index 8550941..dca6468 100644 --- a/pkg/common/bufpool/pool.go +++ b/pkg/common/bufpool/pool.go @@ -82,15 +82,14 @@ var ( } ) -// Get returns a buffer size range from (0, 65]KB, -// panic if size > 65KB. +// Get returns a buffer size. func Get(size int) []byte { for i := range pools { if size <= pools[i].size { - return pools[i].pool.Get().([]byte) + return pools[i].pool.Get().([]byte)[:size] } } - panic("size too large (max=65KB)") + return make([]byte, size) } func Put(b []byte) { diff --git a/pkg/connector/sni/conn.go b/pkg/connector/sni/conn.go new file mode 100644 index 0000000..36471cf --- /dev/null +++ b/pkg/connector/sni/conn.go @@ -0,0 +1,128 @@ +package sni + +import ( + "bufio" + "bytes" + "encoding/base64" + "encoding/binary" + "hash/crc32" + "io" + "net" + "strings" + + dissector "github.com/go-gost/tls-dissector" +) + +type sniClientConn struct { + host string + obfuscated bool + net.Conn +} + +func (c *sniClientConn) Write(p []byte) (int, error) { + b, err := c.obfuscate(p) + if err != nil { + return 0, err + } + if _, err = c.Conn.Write(b); err != nil { + return 0, err + } + return len(p), nil +} + +func (c *sniClientConn) obfuscate(p []byte) ([]byte, error) { + if c.host == "" { + return p, nil + } + + if c.obfuscated { + return p, nil + } + + if p[0] == dissector.Handshake { + b, err := readClientHelloRecord(bytes.NewReader(p), c.host) + if err != nil { + return nil, err + } + c.obfuscated = true + return b, nil + } + + buf := &bytes.Buffer{} + br := bufio.NewReader(bytes.NewReader(p)) + for { + s, err := br.ReadString('\n') + if err != nil { + if err != io.EOF { + return nil, err + } + if s != "" { + buf.Write([]byte(s)) + } + break + } + + // end of HTTP header + if s == "\r\n" { + buf.Write([]byte(s)) + // drain the remain bytes. + io.Copy(buf, br) + break + } + + if strings.HasPrefix(s, "Host") { + s = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(s, "Host:"), "\r\n")) + host := encodeServerName(s) + buf.WriteString("Host: " + c.host + "\r\n") + buf.WriteString("Gost-Target: " + host + "\r\n") + // drain the remain bytes. + io.Copy(buf, br) + break + } + buf.Write([]byte(s)) + } + c.obfuscated = true + return buf.Bytes(), nil +} + +func readClientHelloRecord(r io.Reader, host string) ([]byte, error) { + record, err := dissector.ReadRecord(r) + if err != nil { + return nil, err + } + clientHello := dissector.ClientHelloMsg{} + if err := clientHello.Decode(record.Opaque); err != nil { + return nil, err + } + + for _, ext := range clientHello.Extensions { + if ext.Type() == dissector.ExtServerName { + snExtension := ext.(*dissector.ServerNameExtension) + if host != "" { + e, _ := dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name))) + clientHello.Extensions = append(clientHello.Extensions, e) + snExtension.Name = host + } + + break + } + } + record.Opaque, err = clientHello.Encode() + if err != nil { + return nil, err + } + + buf := &bytes.Buffer{} + if _, err := record.WriteTo(buf); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func encodeServerName(name string) string { + buf := &bytes.Buffer{} + binary.Write(buf, binary.BigEndian, crc32.ChecksumIEEE([]byte(name))) + buf.WriteString(base64.RawURLEncoding.EncodeToString([]byte(name))) + return base64.RawURLEncoding.EncodeToString(buf.Bytes()) +} diff --git a/pkg/connector/sni/connector.go b/pkg/connector/sni/connector.go new file mode 100644 index 0000000..ee0a7e7 --- /dev/null +++ b/pkg/connector/sni/connector.go @@ -0,0 +1,47 @@ +package sni + +import ( + "context" + "net" + + "github.com/go-gost/gost/pkg/connector" + "github.com/go-gost/gost/pkg/logger" + md "github.com/go-gost/gost/pkg/metadata" + "github.com/go-gost/gost/pkg/registry" +) + +func init() { + registry.RegiserConnector("sni", NewConnector) +} + +type sniConnector struct { + md metadata + logger logger.Logger +} + +func NewConnector(opts ...connector.Option) connector.Connector { + options := &connector.Options{} + for _, opt := range opts { + opt(options) + } + + return &sniConnector{ + logger: options.Logger, + } +} + +func (c *sniConnector) Init(md md.Metadata) (err error) { + return c.parseMetadata(md) +} + +func (c *sniConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) { + c.logger = c.logger.WithFields(map[string]interface{}{ + "remote": conn.RemoteAddr().String(), + "local": conn.LocalAddr().String(), + "network": network, + "address": address, + }) + c.logger.Infof("connect %s/%s", address, network) + + return &sniClientConn{Conn: conn, host: c.md.host}, nil +} diff --git a/pkg/connector/sni/metadata.go b/pkg/connector/sni/metadata.go new file mode 100644 index 0000000..f383093 --- /dev/null +++ b/pkg/connector/sni/metadata.go @@ -0,0 +1,24 @@ +package sni + +import ( + "time" + + md "github.com/go-gost/gost/pkg/metadata" +) + +type metadata struct { + host string + connectTimeout time.Duration +} + +func (c *sniConnector) parseMetadata(md md.Metadata) (err error) { + const ( + host = "host" + connectTimeout = "timeout" + ) + + c.md.host = md.GetString(host) + c.md.connectTimeout = md.GetDuration(connectTimeout) + + return +} diff --git a/pkg/handler/auto/handler.go b/pkg/handler/auto/handler.go index e1de705..5b5b6a0 100644 --- a/pkg/handler/auto/handler.go +++ b/pkg/handler/auto/handler.go @@ -9,10 +9,6 @@ import ( "github.com/go-gost/gosocks4" "github.com/go-gost/gosocks5" "github.com/go-gost/gost/pkg/handler" - http_handler "github.com/go-gost/gost/pkg/handler/http" - relay_handler "github.com/go-gost/gost/pkg/handler/relay" - socks4_handler "github.com/go-gost/gost/pkg/handler/socks/v4" - socks5_handler "github.com/go-gost/gost/pkg/handler/socks/v5" "github.com/go-gost/gost/pkg/logger" md "github.com/go-gost/gost/pkg/metadata" "github.com/go-gost/gost/pkg/registry" @@ -46,37 +42,52 @@ func NewHandler(opts ...handler.Option) handler.Handler { log: log, } - v := append(opts, - handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "http"}))) - h.httpHandler = http_handler.NewHandler(v...) + if f := registry.GetHandler("http"); f != nil { + v := append(opts, + handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "http"}))) + h.httpHandler = f(v...) + } + if f := registry.GetHandler("socks4"); f != nil { + v := append(opts, + handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "socks4"}))) + h.socks4Handler = f(v...) + } + if f := registry.GetHandler("socks5"); f != nil { + v := append(opts, + handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "socks5"}))) + h.socks5Handler = f(v...) + } + if f := registry.GetHandler("relay"); f != nil { + v := append(opts, + handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "relay"}))) + h.relayHandler = f(v...) + } - v = append(opts, - handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "socks4"}))) - h.socks4Handler = socks4_handler.NewHandler(v...) - - v = append(opts, - handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "socks5"}))) - h.socks5Handler = socks5_handler.NewHandler(v...) - - v = append(opts, - handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "relay"}))) - h.relayHandler = relay_handler.NewHandler(v...) return h } func (h *autoHandler) Init(md md.Metadata) error { - if err := h.httpHandler.Init(md); err != nil { - return err + if h.httpHandler != nil { + if err := h.httpHandler.Init(md); err != nil { + return err + } } - if err := h.socks4Handler.Init(md); err != nil { - return err + if h.socks4Handler != nil { + if err := h.socks4Handler.Init(md); err != nil { + return err + } } - if err := h.socks5Handler.Init(md); err != nil { - return err + if h.socks5Handler != nil { + if err := h.socks5Handler.Init(md); err != nil { + return err + } } - if err := h.relayHandler.Init(md); err != nil { - return err + if h.relayHandler != nil { + if err := h.relayHandler.Init(md); err != nil { + return err + } } + return nil } @@ -105,13 +116,21 @@ func (h *autoHandler) Handle(ctx context.Context, conn net.Conn) { conn = handler.NewBufferReaderConn(conn, br) switch b[0] { case gosocks4.Ver4: // socks4 - h.socks4Handler.Handle(ctx, conn) + if h.socks4Handler != nil { + h.socks4Handler.Handle(ctx, conn) + } case gosocks5.Ver5: // socks5 - h.socks5Handler.Handle(ctx, conn) + if h.socks5Handler != nil { + h.socks5Handler.Handle(ctx, conn) + } case relay.Version1: // relay - h.relayHandler.Handle(ctx, conn) + if h.relayHandler != nil { + h.relayHandler.Handle(ctx, conn) + } default: // http - h.httpHandler.Handle(ctx, conn) + if h.httpHandler != nil { + h.httpHandler.Handle(ctx, conn) + } } } diff --git a/pkg/handler/http/handler.go b/pkg/handler/http/handler.go index f3f4c20..affaf81 100644 --- a/pkg/handler/http/handler.go +++ b/pkg/handler/http/handler.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/asaskevich/govalidator" "github.com/go-gost/gost/pkg/bypass" "github.com/go-gost/gost/pkg/chain" "github.com/go-gost/gost/pkg/handler" @@ -59,12 +60,10 @@ func (h *httpHandler) Handle(ctx context.Context, conn net.Conn) { defer conn.Close() start := time.Now() - h.logger = h.logger.WithFields(map[string]interface{}{ "remote": conn.RemoteAddr().String(), "local": conn.LocalAddr().String(), }) - h.logger.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) defer func() { h.logger.WithFields(map[string]interface{}{ @@ -87,6 +86,10 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt return } + if h.md.sni && !req.URL.IsAbs() && govalidator.IsDNSName(req.Host) { + req.URL.Scheme = "http" + } + // Try to get the actual host. // Compatible with GOST 2.x. if v := req.Header.Get("Gost-Target"); v != "" { diff --git a/pkg/handler/http/metadata.go b/pkg/handler/http/metadata.go index 5371561..b8d8cfa 100644 --- a/pkg/handler/http/metadata.go +++ b/pkg/handler/http/metadata.go @@ -12,6 +12,7 @@ type metadata struct { proxyAgent string retryCount int probeResist *probeResist + sni bool } func (h *httpHandler) parseMetadata(md md.Metadata) error { @@ -21,6 +22,7 @@ func (h *httpHandler) parseMetadata(md md.Metadata) error { probeResistKey = "probeResist" knock = "knock" retryCount = "retry" + sni = "sni" ) h.md.proxyAgent = md.GetString(proxyAgent) @@ -50,6 +52,7 @@ func (h *httpHandler) parseMetadata(md md.Metadata) error { } } h.md.retryCount = md.GetInt(retryCount) + h.md.sni = md.GetBool(sni) return nil } diff --git a/pkg/handler/sni/conn.go b/pkg/handler/sni/conn.go new file mode 100644 index 0000000..22d1035 --- /dev/null +++ b/pkg/handler/sni/conn.go @@ -0,0 +1,20 @@ +package sni + +import ( + "net" +) + +type cacheConn struct { + net.Conn + buf []byte +} + +func (c *cacheConn) Read(b []byte) (n int, err error) { + if len(c.buf) > 0 { + n = copy(b, c.buf) + c.buf = c.buf[n:] + return + } + + return c.Conn.Read(b) +} diff --git a/pkg/handler/sni/handler.go b/pkg/handler/sni/handler.go index f03c13a..2421b12 100644 --- a/pkg/handler/sni/handler.go +++ b/pkg/handler/sni/handler.go @@ -1,7 +1,7 @@ package sni import ( - "bufio" + "bytes" "context" "encoding/base64" "encoding/binary" @@ -13,8 +13,8 @@ import ( "github.com/go-gost/gost/pkg/bypass" "github.com/go-gost/gost/pkg/chain" + "github.com/go-gost/gost/pkg/common/bufpool" "github.com/go-gost/gost/pkg/handler" - http_handler "github.com/go-gost/gost/pkg/handler/http" "github.com/go-gost/gost/pkg/logger" md "github.com/go-gost/gost/pkg/metadata" "github.com/go-gost/gost/pkg/registry" @@ -49,9 +49,11 @@ func NewHandler(opts ...handler.Option) handler.Handler { logger: log, } - v := append(opts, - handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "http"}))) - h.httpHandler = http_handler.NewHandler(v...) + if f := registry.GetHandler("http"); f != nil { + v := append(opts, + handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "http"}))) + h.httpHandler = f(v...) + } return h } @@ -60,8 +62,13 @@ func (h *sniHandler) Init(md md.Metadata) (err error) { if err = h.parseMetadata(md); err != nil { return } - if err = h.httpHandler.Init(md); err != nil { - return + if h.httpHandler != nil { + if md != nil { + md.Set("sni", true) + } + if err = h.httpHandler.Init(md); err != nil { + return + } } return nil @@ -88,22 +95,36 @@ func (h *sniHandler) Handle(ctx context.Context, conn net.Conn) { }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }() - br := bufio.NewReader(conn) - hdr, err := br.Peek(dissector.RecordHeaderLen) - if err != nil { + var hdr [dissector.RecordHeaderLen]byte + if _, err := io.ReadFull(conn, hdr[:]); err != nil { h.logger.Error(err) return } - conn = handler.NewBufferReaderConn(conn, br) - if hdr[0] != dissector.Handshake { // We assume it is an HTTP request - h.httpHandler.Handle(ctx, conn) + conn = &cacheConn{ + Conn: conn, + buf: hdr[:], + } + + if h.httpHandler != nil { + h.httpHandler.Handle(ctx, conn) + } return } - host, err := h.decodeHost(conn) + length := binary.BigEndian.Uint16(hdr[3:5]) + + buf := bufpool.Get(int(length) + dissector.RecordHeaderLen) + defer bufpool.Put(buf) + if _, err := io.ReadFull(conn, buf[dissector.RecordHeaderLen:]); err != nil { + h.logger.Error(err) + return + } + copy(buf, hdr[:]) + + buf, host, err := h.decodeHost(bytes.NewReader(buf)) if err != nil { h.logger.Error(err) return @@ -130,6 +151,11 @@ func (h *sniHandler) Handle(ctx context.Context, conn net.Conn) { } defer cc.Close() + if _, err := cc.Write(buf); err != nil { + h.logger.Error(err) + return + } + t := time.Now() h.logger.Infof("%s <-> %s", conn.RemoteAddr(), target) handler.Transport(conn, cc) @@ -140,27 +166,51 @@ func (h *sniHandler) Handle(ctx context.Context, conn net.Conn) { Infof("%s >-< %s", conn.RemoteAddr(), target) } -func (h *sniHandler) decodeHost(r io.Reader) (host string, err error) { +func (h *sniHandler) decodeHost(r io.Reader) (opaque []byte, host string, err error) { record, err := dissector.ReadRecord(r) if err != nil { return } - clientHello := &dissector.ClientHelloMsg{} + clientHello := dissector.ClientHelloMsg{} if err = clientHello.Decode(record.Opaque); err != nil { return } + var extensions []dissector.Extension for _, ext := range clientHello.Extensions { if ext.Type() == 0xFFFE { b, _ := ext.Encode() - return h.decodeServerName(string(b)) + if v, err := h.decodeServerName(string(b)); err == nil { + host = v + } + continue } + extensions = append(extensions, ext) + } + clientHello.Extensions = extensions + for _, ext := range clientHello.Extensions { if ext.Type() == dissector.ExtServerName { snExtension := ext.(*dissector.ServerNameExtension) - host = snExtension.Name + if host == "" { + host = snExtension.Name + } else { + snExtension.Name = host + } + break } } + + record.Opaque, err = clientHello.Encode() + if err != nil { + return + } + + buf := &bytes.Buffer{} + if _, err = record.WriteTo(buf); err != nil { + return + } + opaque = buf.Bytes() return } diff --git a/pkg/metadata/metadata.go b/pkg/metadata/metadata.go index 300abae..f1899ac 100644 --- a/pkg/metadata/metadata.go +++ b/pkg/metadata/metadata.go @@ -7,6 +7,7 @@ import ( type Metadata interface { IsExists(key string) bool + Set(key string, value interface{}) Get(key string) interface{} GetBool(key string) bool GetInt(key string) int @@ -22,6 +23,10 @@ func (m MapMetadata) IsExists(key string) bool { return ok } +func (m MapMetadata) Set(key string, value interface{}) { + m[key] = value +} + func (m MapMetadata) Get(key string) interface{} { if m != nil { return m[key]