add sni connector

This commit is contained in:
ginuerzh 2021-11-26 17:20:10 +08:00
parent 5b97b878fb
commit efbabd4052
14 changed files with 370 additions and 56 deletions

View File

@ -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)
}

View File

@ -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"

1
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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) {

128
pkg/connector/sni/conn.go Normal file
View File

@ -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())
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
}
if f := registry.GetHandler("http"); f != nil {
v := append(opts,
handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "http"})))
h.httpHandler = http_handler.NewHandler(v...)
v = append(opts,
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 = socks4_handler.NewHandler(v...)
v = append(opts,
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 = socks5_handler.NewHandler(v...)
v = append(opts,
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 = relay_handler.NewHandler(v...)
h.relayHandler = f(v...)
}
return h
}
func (h *autoHandler) Init(md md.Metadata) error {
if h.httpHandler != nil {
if err := h.httpHandler.Init(md); err != nil {
return err
}
}
if h.socks4Handler != nil {
if err := h.socks4Handler.Init(md); err != nil {
return err
}
}
if h.socks5Handler != nil {
if err := h.socks5Handler.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
if h.socks4Handler != nil {
h.socks4Handler.Handle(ctx, conn)
}
case gosocks5.Ver5: // socks5
if h.socks5Handler != nil {
h.socks5Handler.Handle(ctx, conn)
}
case relay.Version1: // relay
if h.relayHandler != nil {
h.relayHandler.Handle(ctx, conn)
}
default: // http
if h.httpHandler != nil {
h.httpHandler.Handle(ctx, conn)
}
}
}

View File

@ -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 != "" {

View File

@ -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
}

20
pkg/handler/sni/conn.go Normal file
View File

@ -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)
}

View File

@ -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,
}
if f := registry.GetHandler("http"); f != nil {
v := append(opts,
handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "http"})))
h.httpHandler = http_handler.NewHandler(v...)
h.httpHandler = f(v...)
}
return h
}
@ -60,9 +62,14 @@ func (h *sniHandler) Init(md md.Metadata) (err error) {
if err = h.parseMetadata(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
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)
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
}

View File

@ -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]