From 9b2bd7a88c101623ed1ed5a99c9c80c1a162cabf Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Sat, 22 Jul 2017 23:21:04 +0800 Subject: [PATCH] add KCP support --- gost/chain.go | 2 +- gost/cli/cli.go | 110 ++++++----- gost/client.go | 23 +-- gost/gost.go | 24 ++- gost/handler.go | 6 + gost/http.go | 3 + gost/kcp.go | 472 ++++++++++++++++++++++++++++++++++++++++++++ gost/node.go | 2 +- gost/server.go | 3 +- gost/signal.go | 5 + gost/signal_unix.go | 24 +++ gost/socks.go | 35 ++-- gost/srv/srv.go | 14 ++ gost/ss.go | 8 +- gost/tls.go | 7 +- gost/ws.go | 30 ++- 16 files changed, 667 insertions(+), 101 deletions(-) create mode 100644 gost/kcp.go create mode 100644 gost/signal.go create mode 100644 gost/signal_unix.go diff --git a/gost/chain.go b/gost/chain.go index 2d48e80..4ceb822 100644 --- a/gost/chain.go +++ b/gost/chain.go @@ -6,7 +6,7 @@ import ( ) var ( - // ErrEmptyChain is an error that implies the chain is empty + // ErrEmptyChain is an error that implies the chain is empty. ErrEmptyChain = errors.New("empty chain") ) diff --git a/gost/cli/cli.go b/gost/cli/cli.go index d183827..ce92618 100644 --- a/gost/cli/cli.go +++ b/gost/cli/cli.go @@ -5,7 +5,6 @@ import ( "log" "net/http" "net/http/httputil" - "net/url" "github.com/ginuerzh/gost/gost" ) @@ -28,61 +27,72 @@ func main() { }, */ - // socks5+tcp - gost.Node{ - Addr: "127.0.0.1:1080", - Client: gost.NewClient( - gost.SOCKS5Connector(url.UserPassword("admin", "123456")), - gost.TCPTransporter(), - ), - }, - - /* - // ss+tcp - gost.Node{ - Addr: "127.0.0.1:8338", - Client: gost.NewClient( - gost.ShadowConnector(url.UserPassword("chacha20", "123456")), - gost.TCPTransporter(), - ), - }, - */ - - /* - // http+ws - gost.Node{ - Addr: "127.0.0.1:8000", - Client: gost.NewClient( - gost.HTTPConnector(url.UserPassword("admin", "123456")), - gost.WSTransporter("127.0.0.1:8000", nil), - ), - }, - */ - - /* - // http+wss - gost.Node{ - Addr: "127.0.0.1:8443", - Client: gost.NewClient( - gost.HTTPConnector(url.UserPassword("admin", "123456")), - gost.WSSTransporter( - "127.0.0.1:8443", - &gost.WSOptions{TLSConfig: &tls.Config{InsecureSkipVerify: true}}, + /* + // socks5+tcp + gost.Node{ + Addr: "127.0.0.1:1080", + Client: gost.NewClient( + gost.SOCKS5Connector(url.UserPassword("admin", "123456")), + gost.TCPTransporter(), ), - ), - }, - */ + }, + */ - /* - // http+tls + /* + // ss+tcp + gost.Node{ + Addr: "127.0.0.1:8338", + Client: gost.NewClient( + gost.ShadowConnector(url.UserPassword("chacha20", "123456")), + gost.TCPTransporter(), + ), + }, + */ + + /* + // http+ws + gost.Node{ + Addr: "127.0.0.1:8000", + Client: gost.NewClient( + gost.HTTPConnector(url.UserPassword("admin", "123456")), + gost.WSTransporter("127.0.0.1:8000", nil), + ), + }, + */ + + /* + // http+wss + gost.Node{ + Addr: "127.0.0.1:8443", + Client: gost.NewClient( + gost.HTTPConnector(url.UserPassword("admin", "123456")), + gost.WSSTransporter( + "127.0.0.1:8443", + &gost.WSOptions{TLSConfig: &tls.Config{InsecureSkipVerify: true}}, + ), + ), + }, + */ + + /* + // http+tls + gost.Node{ + Addr: "127.0.0.1:1443", + Client: gost.NewClient( + gost.HTTPConnector(url.UserPassword("admin", "123456")), + gost.TLSTransporter(&tls.Config{InsecureSkipVerify: true}), + ), + }, + */ + + // http+kcp gost.Node{ - Addr: "127.0.0.1:1443", + Addr: "127.0.0.1:8388", Client: gost.NewClient( - gost.HTTPConnector(url.UserPassword("admin", "123456")), - gost.TLSTransporter(&tls.Config{InsecureSkipVerify: true}), + gost.HTTPConnector(nil), + gost.KCPTransporter(nil), ), }, - */ ) conn, err := chain.Dial("localhost:10000") diff --git a/gost/client.go b/gost/client.go index b285e09..fc911f2 100644 --- a/gost/client.go +++ b/gost/client.go @@ -5,15 +5,15 @@ import ( ) // Client is a proxy client. +// A client is divided into two layers: connector and transporter. +// Connector is responsible for connecting to the destination address through this proxy. +// Transporter performs a handshake with this proxy. type Client struct { Connector Connector Transporter Transporter } // NewClient creates a proxy client. -// A client is divided into two layers: connector and transporter. -// Connector is responsible for connecting to the destination address through this proxy. -// Transporter performs a handshake with this proxy. func NewClient(c Connector, tr Transporter) *Client { return &Client{ Connector: c, @@ -21,9 +21,9 @@ func NewClient(c Connector, tr Transporter) *Client { } } -// Dial connects to the target address +// Dial connects to the target address. func (c *Client) Dial(addr string) (net.Conn, error) { - return net.Dial(c.Transporter.Network(), addr) + return c.Transporter.Dial(addr) } // Handshake performs a handshake with the proxy. @@ -38,7 +38,7 @@ func (c *Client) Connect(conn net.Conn, addr string) (net.Conn, error) { return c.Connector.Connect(conn, addr) } -// DefaultClient is a standard HTTP proxy client +// DefaultClient is a standard HTTP proxy client. var DefaultClient = NewClient(HTTPConnector(nil), TCPTransporter()) // Dial connects to the address addr via the DefaultClient. @@ -46,7 +46,7 @@ func Dial(addr string) (net.Conn, error) { return DefaultClient.Dial(addr) } -// Handshake performs a handshake via the DefaultClient +// Handshake performs a handshake via the DefaultClient. func Handshake(conn net.Conn) (net.Conn, error) { return DefaultClient.Handshake(conn) } @@ -56,26 +56,27 @@ func Connect(conn net.Conn, addr string) (net.Conn, error) { return DefaultClient.Connect(conn, addr) } -// Connector is responsible for connecting to the destination address +// Connector is responsible for connecting to the destination address. type Connector interface { Connect(conn net.Conn, addr string) (net.Conn, error) } // Transporter is responsible for handshaking with the proxy server. type Transporter interface { - Network() string + Dial(addr string) (net.Conn, error) Handshake(conn net.Conn) (net.Conn, error) } type tcpTransporter struct { } +// TCPTransporter creates a transporter for TCP proxy client. func TCPTransporter() Transporter { return &tcpTransporter{} } -func (tr *tcpTransporter) Network() string { - return "tcp" +func (tr *tcpTransporter) Dial(addr string) (net.Conn, error) { + return net.Dial("tcp", addr) } func (tr *tcpTransporter) Handshake(conn net.Conn) (net.Conn, error) { diff --git a/gost/gost.go b/gost/gost.go index b8bd2ce..d2e3f3f 100644 --- a/gost/gost.go +++ b/gost/gost.go @@ -6,24 +6,30 @@ import ( "github.com/go-log/log" ) +// Version is the gost version. const Version = "2.4-dev20170722" +// Debug is a flag that enables the debug log. var Debug bool var ( - TinyBufferSize = 128 - SmallBufferSize = 1 * 1024 // 1KB small buffer - MediumBufferSize = 8 * 1024 // 8KB medium buffer - LargeBufferSize = 32 * 1024 // 32KB large buffer + tinyBufferSize = 128 + smallBufferSize = 1 * 1024 // 1KB small buffer + mediumBufferSize = 8 * 1024 // 8KB medium buffer + largeBufferSize = 32 * 1024 // 32KB large buffer ) var ( + // KeepAliveTime is the keep alive time period for TCP connection. KeepAliveTime = 180 * time.Second - DialTimeout = 30 * time.Second - ReadTimeout = 90 * time.Second - WriteTimeout = 90 * time.Second - - DefaultTTL = 60 // default udp node TTL in second for udp port forwarding + // DialTimeout is the timeout of dial. + DialTimeout = 30 * time.Second + // ReadTimeout is the timeout for reading. + ReadTimeout = 90 * time.Second + // WriteTimeout is the timeout for writing. + WriteTimeout = 90 * time.Second + // default udp node TTL in second for udp port forwarding. + defaultTTL = 60 ) func init() { diff --git a/gost/handler.go b/gost/handler.go index 5b7612f..78282c2 100644 --- a/gost/handler.go +++ b/gost/handler.go @@ -6,30 +6,36 @@ import ( "net/url" ) +// Handler is a proxy server handler type Handler interface { Handle(net.Conn) } +// HandlerOptions describes the options for Handler. type HandlerOptions struct { Chain *Chain Users []*url.Userinfo TLSConfig *tls.Config } +// HandlerOption allows a common way to set handler options. type HandlerOption func(opts *HandlerOptions) +// ChainHandlerOption sets the Chain option of HandlerOptions. func ChainHandlerOption(chain *Chain) HandlerOption { return func(opts *HandlerOptions) { opts.Chain = chain } } +// UsersHandlerOption sets the Users option of HandlerOptions. func UsersHandlerOption(users ...*url.Userinfo) HandlerOption { return func(opts *HandlerOptions) { opts.Users = users } } +// TLSConfigHandlerOption sets the TLSConfig option of HandlerOptions. func TLSConfigHandlerOption(config *tls.Config) HandlerOption { return func(opts *HandlerOptions) { opts.TLSConfig = config diff --git a/gost/http.go b/gost/http.go index 3d328f8..63f2d82 100644 --- a/gost/http.go +++ b/gost/http.go @@ -17,6 +17,8 @@ type httpConnector struct { User *url.Userinfo } +// HTTPConnector creates a Connector for HTTP proxy client. +// It accepts an optional auth info for HTTP Basic Authentication. func HTTPConnector(user *url.Userinfo) Connector { return &httpConnector{User: user} } @@ -71,6 +73,7 @@ type httpHandler struct { options *HandlerOptions } +// HTTPHandler creates a server Handler for HTTP proxy server. func HTTPHandler(opts ...HandlerOption) Handler { h := &httpHandler{ options: &HandlerOptions{ diff --git a/gost/kcp.go b/gost/kcp.go new file mode 100644 index 0000000..5c4f8d8 --- /dev/null +++ b/gost/kcp.go @@ -0,0 +1,472 @@ +package gost + +import ( + "crypto/sha1" + "encoding/csv" + "fmt" + "net" + "os" + "time" + + "golang.org/x/crypto/pbkdf2" + + "sync" + + "github.com/go-log/log" + "github.com/klauspost/compress/snappy" + "gopkg.in/xtaci/kcp-go.v2" + "gopkg.in/xtaci/smux.v1" +) + +var ( + // SALT is the default salt for KCP cipher. + SALT = "kcp-go" +) + +// KCPConfig describes the config for KCP. +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"` + SnmpLog string `json:"snmplog"` + SnmpPeriod int `json:"snmpperiod"` + Signal bool `json:"signal"` // Signal enables the signal SIGUSR1 feature. +} + +// Init initializes the KCP config. +func (c *KCPConfig) Init() { + switch c.Mode { + case "normal": + c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 50, 2, 1 + case "fast2": + c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 30, 2, 1 + case "fast3": + c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1 + case "fast": + fallthrough + default: + c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 40, 2, 1 + } +} + +var ( + // DefaultKCPConfig is the default KCP config. + 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: 50, + Resend: 0, + NoCongestion: 0, + SockBuf: 4194304, + KeepAlive: 10, + SnmpLog: "", + SnmpPeriod: 60, + Signal: false, + } +) + +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 kcpSession struct { + conn net.Conn + session *smux.Session +} + +func (session *kcpSession) GetConn() (*kcpConn, error) { + stream, err := session.session.OpenStream() + if err != nil { + 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 kcpTransporter struct { + sessions map[string]*kcpSession + sessionMutex sync.Mutex + config *KCPConfig +} + +// KCPTransporter creates a Transporter that is used by KCP proxy client. +func KCPTransporter(config *KCPConfig) Transporter { + if config == nil { + config = DefaultKCPConfig + } + config.Init() + + go snmpLogger(config.SnmpLog, config.SnmpPeriod) + if config.Signal { + go kcpSigHandler() + } + + return &kcpTransporter{ + config: config, + sessions: make(map[string]*kcpSession), + } +} + +func (tr *kcpTransporter) Dial(addr string) (conn net.Conn, err error) { + tr.sessionMutex.Lock() + session, ok := tr.sessions[addr] + if !ok { + session, err = tr.dial(addr, tr.config) + if err != nil { + tr.sessionMutex.Unlock() + return + } + tr.sessions[addr] = session + } + tr.sessionMutex.Unlock() + + conn, err = session.GetConn() + if err != nil { + tr.sessionMutex.Lock() + session.Close() + delete(tr.sessions, addr) + tr.sessionMutex.Unlock() + } + return +} + +func (tr *kcpTransporter) dial(addr string, config *KCPConfig) (*kcpSession, error) { + 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 { + log.Log("[kcp]", err) + } + if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil { + log.Log("[kcp]", err) + } + if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil { + log.Log("[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 (tr *kcpTransporter) Handshake(conn net.Conn) (net.Conn, error) { + return conn, nil +} + +type kcpListener struct { + config *KCPConfig + ln *kcp.Listener + connChan chan net.Conn + errChan chan error +} + +// KCPListener creates a Listener for KCP proxy server. +func KCPListener(addr string, config *KCPConfig) (Listener, error) { + if config == nil { + config = DefaultKCPConfig + } + config.Init() + + ln, err := kcp.ListenWithOptions(addr, + blockCrypt(config.Key, config.Crypt, SALT), config.DataShard, config.ParityShard) + if err != nil { + return nil, err + } + if err = ln.SetDSCP(config.DSCP); err != nil { + log.Log("[kcp]", err) + } + if err = ln.SetReadBuffer(config.SockBuf); err != nil { + log.Log("[kcp]", err) + } + if err = ln.SetWriteBuffer(config.SockBuf); err != nil { + log.Log("[kcp]", err) + } + + go snmpLogger(config.SnmpLog, config.SnmpPeriod) + if config.Signal { + go kcpSigHandler() + } + + l := &kcpListener{ + config: config, + ln: ln, + connChan: make(chan net.Conn, 128), + errChan: make(chan error), + } + go l.acceptLoop() + + return l, nil +} + +func (l *kcpListener) acceptLoop() { + for { + conn, err := l.ln.AcceptKCP() + if err != nil { + log.Log("[kcp] accept:", err) + continue + } + conn.SetStreamMode(true) + conn.SetNoDelay(l.config.NoDelay, l.config.Interval, l.config.Resend, l.config.NoCongestion) + conn.SetMtu(l.config.MTU) + conn.SetWindowSize(l.config.SndWnd, l.config.RcvWnd) + conn.SetACKNoDelay(l.config.AckNodelay) + conn.SetKeepAlive(l.config.KeepAlive) + go l.mux(conn) + } +} + +func (l *kcpListener) Accept() (conn net.Conn, err error) { + select { + case conn = <-l.connChan: + case err = <-l.errChan: + } + return +} + +func (l *kcpListener) mux(conn net.Conn) { + smuxConfig := smux.DefaultConfig() + smuxConfig.MaxReceiveBuffer = l.config.SockBuf + + log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr()) + + if !l.config.NoComp { + conn = newCompStreamConn(conn) + } + + mux, err := smux.Server(conn, smuxConfig) + if err != nil { + log.Log("[kcp]", err) + return + } + defer mux.Close() + + log.Logf("[kcp] %s <-> %s", conn.RemoteAddr(), l.Addr()) + defer log.Logf("[kcp] %s >-< %s", conn.RemoteAddr(), l.Addr()) + + for { + stream, err := mux.AcceptStream() + if err != nil { + log.Log("[kcp] accept stream:", err) + return + } + + select { + case l.connChan <- newKCPConn(conn, stream): + default: + log.Logf("[kcp] %s - %s: connection queue is full", conn.RemoteAddr(), l.Addr()) + } + } +} + +func (l *kcpListener) Addr() net.Addr { + return l.ln.Addr() +} + +func (l *kcpListener) Close() error { + return l.ln.Close() +} + +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 +} + +func snmpLogger(format string, interval int) { + if format == "" || interval == 0 { + return + } + ticker := time.NewTicker(time.Duration(interval) * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + f, err := os.OpenFile(time.Now().Format(format), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Log("[kcp]", err) + return + } + w := csv.NewWriter(f) + // write header in empty file + if stat, err := f.Stat(); err == nil && stat.Size() == 0 { + if err := w.Write(append([]string{"Unix"}, kcp.DefaultSnmp.Header()...)); err != nil { + log.Log("[kcp]", err) + } + } + if err := w.Write(append([]string{fmt.Sprint(time.Now().Unix())}, kcp.DefaultSnmp.ToSlice()...)); err != nil { + log.Log("[kcp]", err) + } + kcp.DefaultSnmp.Reset() + w.Flush() + f.Close() + } + } +} + +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/gost/node.go b/gost/node.go index 10fbd93..a710206 100644 --- a/gost/node.go +++ b/gost/node.go @@ -1,6 +1,6 @@ package gost -// Node is a proxy node, mainly used to construct a proxy chain +// Node is a proxy node, mainly used to construct a proxy chain. type Node struct { Addr string Protocol string diff --git a/gost/server.go b/gost/server.go index eff8606..f7aa799 100644 --- a/gost/server.go +++ b/gost/server.go @@ -14,7 +14,7 @@ type Server struct { handler Handler } -// Handle sets a handler for the server +// Handle sets a handler for the server. func (s *Server) Handle(h Handler) { s.handler = h } @@ -57,6 +57,7 @@ type tcpListener struct { net.Listener } +// TCPListener creates a Listener for TCP proxy server. func TCPListener(addr string) (Listener, error) { ln, err := net.Listen("tcp", addr) if err != nil { diff --git a/gost/signal.go b/gost/signal.go new file mode 100644 index 0000000..f12e902 --- /dev/null +++ b/gost/signal.go @@ -0,0 +1,5 @@ +// +build windows + +package gost + +func kcpSigHandler() {} diff --git a/gost/signal_unix.go b/gost/signal_unix.go new file mode 100644 index 0000000..a761318 --- /dev/null +++ b/gost/signal_unix.go @@ -0,0 +1,24 @@ +// +build !windows + +package gost + +import ( + "os" + "os/signal" + "syscall" + + "github.com/go-log/log" + "gopkg.in/xtaci/kcp-go.v2" +) + +func kcpSigHandler() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGUSR1) + + for { + switch <-ch { + case syscall.SIGUSR1: + log.Logf("[kcp] SNMP: %+v", kcp.DefaultSnmp.Copy()) + } + } +} diff --git a/gost/socks.go b/gost/socks.go index afb558a..81e8019 100644 --- a/gost/socks.go +++ b/gost/socks.go @@ -18,12 +18,15 @@ import ( ) const ( - MethodTLS uint8 = 0x80 // extended method for tls - MethodTLSAuth uint8 = 0x82 // extended method for tls+auth + // MethodTLS is an extended SOCKS5 method for TLS. + MethodTLS uint8 = 0x80 + // MethodTLSAuth is an extended SOCKS5 method for TLS+AUTH. + MethodTLSAuth uint8 = 0x82 ) const ( - CmdUdpTun uint8 = 0xF3 // extended method for udp over tcp + // CmdUDPTun is an extended SOCKS5 method for UDP over TCP. + CmdUDPTun uint8 = 0xF3 ) type clientSelector struct { @@ -189,6 +192,8 @@ type socks5Connector struct { User *url.Userinfo } +// SOCKS5Connector creates a connector for SOCKS5 proxy client. +// It accepts an optional auth info for SOCKS5 Username/Password Authentication. func SOCKS5Connector(user *url.Userinfo) Connector { return &socks5Connector{User: user} } @@ -246,6 +251,7 @@ func (c *socks5Connector) Connect(conn net.Conn, addr string) (net.Conn, error) type socks4Connector struct{} +// SOCKS4Connector creates a Connector for SOCKS4 proxy client. func SOCKS4Connector() Connector { return &socks4Connector{} } @@ -289,6 +295,7 @@ func (c *socks4Connector) Connect(conn net.Conn, addr string) (net.Conn, error) type socks4aConnector struct{} +// SOCKS4AConnector creates a Connector for SOCKS4A proxy client. func SOCKS4AConnector() Connector { return &socks4aConnector{} } @@ -331,7 +338,7 @@ type socks5Handler struct { options *HandlerOptions } -// SOCKS5Handler returns a SOCKS5 server handler +// SOCKS5Handler creates a server Handler for SOCKS5 proxy server. func SOCKS5Handler(opts ...HandlerOption) Handler { options := &HandlerOptions{ Chain: new(Chain), @@ -381,7 +388,7 @@ func (h *socks5Handler) Handle(conn net.Conn) { log.Logf("[socks5-udp] %s - %s", conn.RemoteAddr(), req.Addr) h.handleUDPRelay(conn, req) - case CmdUdpTun: + case CmdUDPTun: log.Logf("[socks5-rudp] %s - %s", conn.RemoteAddr(), req.Addr) h.handleUDPTunnel(conn, req) @@ -613,7 +620,7 @@ func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) { defer cc.Close() cc.SetWriteDeadline(time.Now().Add(WriteTimeout)) - r := gosocks5.NewRequest(CmdUdpTun, nil) + r := gosocks5.NewRequest(CmdUDPTun, nil) if err := r.Write(cc); err != nil { log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), cc.RemoteAddr(), err) return @@ -647,8 +654,8 @@ func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) { log.Logf("[socks5-udp] %s >-< %s", conn.RemoteAddr(), socksAddr) } -func (s *socks5Handler) discardClientData(conn net.Conn) (err error) { - b := make([]byte, TinyBufferSize) +func (h *socks5Handler) discardClientData(conn net.Conn) (err error) { + b := make([]byte, tinyBufferSize) n := 0 for { n, err = conn.Read(b) // discard any data from tcp connection @@ -663,13 +670,13 @@ func (s *socks5Handler) discardClientData(conn net.Conn) (err error) { return } -func (s *socks5Handler) transportUDP(relay, peer *net.UDPConn) (err error) { +func (h *socks5Handler) transportUDP(relay, peer *net.UDPConn) (err error) { errc := make(chan error, 2) var clientAddr *net.UDPAddr go func() { - b := make([]byte, LargeBufferSize) + b := make([]byte, largeBufferSize) for { n, laddr, err := relay.ReadFromUDP(b) @@ -701,7 +708,7 @@ func (s *socks5Handler) transportUDP(relay, peer *net.UDPConn) (err error) { }() go func() { - b := make([]byte, LargeBufferSize) + b := make([]byte, largeBufferSize) for { n, raddr, err := peer.ReadFromUDP(b) @@ -739,7 +746,7 @@ func (h *socks5Handler) tunnelClientUDP(uc *net.UDPConn, cc net.Conn) (err error var clientAddr *net.UDPAddr go func() { - b := make([]byte, LargeBufferSize) + b := make([]byte, largeBufferSize) for { n, addr, err := uc.ReadFromUDP(b) @@ -864,7 +871,7 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, uc *net.UDPConn) (err error errc := make(chan error, 2) go func() { - b := make([]byte, LargeBufferSize) + b := make([]byte, largeBufferSize) for { n, addr, err := uc.ReadFromUDP(b) @@ -939,7 +946,7 @@ type socks4Handler struct { options *HandlerOptions } -// SOCKS4Handler returns a SOCKS4 server handler +// SOCKS4Handler creates a server Handler for SOCKS4(A) proxy server. func SOCKS4Handler(opts ...HandlerOption) Handler { options := &HandlerOptions{ Chain: new(Chain), diff --git a/gost/srv/srv.go b/gost/srv/srv.go index 91fb036..0f212cc 100644 --- a/gost/srv/srv.go +++ b/gost/srv/srv.go @@ -30,6 +30,8 @@ func main() { go wsServer(&wg) wg.Add(1) go wssServer(&wg) + wg.Add(1) + go kcpServer(&wg) wg.Wait() } @@ -131,3 +133,15 @@ func wssServer(wg *sync.WaitGroup) { } log.Fatal(s.Serve(ln)) } + +func kcpServer(wg *sync.WaitGroup) { + defer wg.Done() + + s := &gost.Server{} + s.Handle(gost.HTTPHandler()) + ln, err := gost.KCPListener(":8388", nil) + if err != nil { + log.Fatal(err) + } + log.Fatal(s.Serve(ln)) +} diff --git a/gost/ss.go b/gost/ss.go index ea4404f..d053f7d 100644 --- a/gost/ss.go +++ b/gost/ss.go @@ -14,7 +14,7 @@ import ( ) // Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write, -// we wrap around it to make io.Copy happy +// we wrap around it to make io.Copy happy. type shadowConn struct { conn net.Conn } @@ -57,6 +57,9 @@ type shadowConnector struct { Cipher *url.Userinfo } +// ShadowConnector creates a Connector for shadowsocks proxy client. +// It accepts a cipher info for shadowsocks data encryption/decryption. +// The cipher must not be nil. func ShadowConnector(cipher *url.Userinfo) Connector { return &shadowConnector{Cipher: cipher} } @@ -89,6 +92,7 @@ type shadowHandler struct { options *HandlerOptions } +// ShadowHandler creates a server Handler for shadowsocks proxy server. func ShadowHandler(opts ...HandlerOption) Handler { h := &shadowHandler{ options: &HandlerOptions{ @@ -158,7 +162,7 @@ func (h *shadowHandler) getRequest(conn net.Conn) (host string, err error) { // buf size should at least have the same size with the largest possible // request size (when addrType is 3, domain name has at most 256 bytes) // 1(addrType) + 1(lenByte) + 256(max length address) + 2(port) - buf := make([]byte, SmallBufferSize) + buf := make([]byte, smallBufferSize) // read till we get possible domain length field conn.SetReadDeadline(time.Now().Add(30 * time.Second)) diff --git a/gost/tls.go b/gost/tls.go index f4cf824..79309fe 100644 --- a/gost/tls.go +++ b/gost/tls.go @@ -9,12 +9,14 @@ type tlsTransporter struct { TLSClientConfig *tls.Config } +// TLSTransporter creates a Transporter that is used by TLS proxy client. +// It accepts a TLS config for TLS handshake. func TLSTransporter(cfg *tls.Config) Transporter { return &tlsTransporter{TLSClientConfig: cfg} } -func (tr *tlsTransporter) Network() string { - return "tcp" +func (tr *tlsTransporter) Dial(addr string) (net.Conn, error) { + return net.Dial("tcp", addr) } func (tr *tlsTransporter) Handshake(conn net.Conn) (net.Conn, error) { @@ -25,6 +27,7 @@ type tlsListener struct { net.Listener } +// TLSListener creates a Listener for TLS proxy server. func TLSListener(addr string, config *tls.Config) (Listener, error) { ln, err := tls.Listen("tcp", addr, config) if err != nil { diff --git a/gost/ws.go b/gost/ws.go index 14db8b4..0045575 100644 --- a/gost/ws.go +++ b/gost/ws.go @@ -13,6 +13,7 @@ import ( "gopkg.in/gorilla/websocket.v1" ) +// WSOptions describes the options for websocket. type WSOptions struct { ReadBufferSize int WriteBufferSize int @@ -82,11 +83,11 @@ func (c *websocketConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() } -func (conn *websocketConn) SetDeadline(t time.Time) error { - if err := conn.SetReadDeadline(t); err != nil { +func (c *websocketConn) SetDeadline(t time.Time) error { + if err := c.SetReadDeadline(t); err != nil { return err } - return conn.SetWriteDeadline(t) + return c.SetWriteDeadline(t) } func (c *websocketConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) @@ -101,6 +102,7 @@ type wsTransporter struct { options *WSOptions } +// WSTransporter creates a Transporter that is used by websocket proxy client. func WSTransporter(addr string, opts *WSOptions) Transporter { return &wsTransporter{ addr: addr, @@ -108,8 +110,9 @@ func WSTransporter(addr string, opts *WSOptions) Transporter { } } -func (tr *wsTransporter) Network() string { - return "tcp" +func (tr *wsTransporter) Dial(addr string) (net.Conn, error) { + tr.addr = addr // NOTE: the addr must match the initial tr.addr + return net.Dial("tcp", addr) } func (tr *wsTransporter) Handshake(conn net.Conn) (net.Conn, error) { @@ -122,6 +125,7 @@ type wssTransporter struct { options *WSOptions } +// WSSTransporter creates a Transporter that is used by websocket secure proxy client. func WSSTransporter(addr string, opts *WSOptions) Transporter { return &wssTransporter{ addr: addr, @@ -129,8 +133,8 @@ func WSSTransporter(addr string, opts *WSOptions) Transporter { } } -func (tr *wssTransporter) Network() string { - return "tcp" +func (tr *wssTransporter) Dial(addr string) (net.Conn, error) { + return net.Dial("tcp", addr) } func (tr *wssTransporter) Handshake(conn net.Conn) (net.Conn, error) { @@ -146,6 +150,7 @@ type wsListener struct { errChan chan error } +// WSListener creates a Listener for websocket proxy server. func WSListener(addr string, options *WSOptions) (Listener, error) { tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { @@ -162,7 +167,7 @@ func WSListener(addr string, options *WSOptions) (Listener, error) { CheckOrigin: func(r *http.Request) bool { return true }, EnableCompression: options.EnableCompression, }, - connChan: make(chan net.Conn, 32), + connChan: make(chan net.Conn, 128), errChan: make(chan error, 1), } @@ -202,7 +207,11 @@ func (l *wsListener) upgrade(w http.ResponseWriter, r *http.Request) { log.Logf("[ws] %s - %s : %s", r.RemoteAddr, l.addr, err) return } - l.connChan <- websocketServerConn(conn) + select { + case l.connChan <- websocketServerConn(conn): + default: + log.Logf("[ws] %s - %s: connection queue is full", r.RemoteAddr, l.addr) + } } func (l *wsListener) Accept() (conn net.Conn, err error) { @@ -225,6 +234,7 @@ type wssListener struct { *wsListener } +// WSSListener creates a Listener for websocket secure proxy server. func WSSListener(addr string, options *WSOptions) (Listener, error) { tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { @@ -242,7 +252,7 @@ func WSSListener(addr string, options *WSOptions) (Listener, error) { CheckOrigin: func(r *http.Request) bool { return true }, EnableCompression: options.EnableCompression, }, - connChan: make(chan net.Conn, 32), + connChan: make(chan net.Conn, 128), errChan: make(chan error, 1), }, }