From b74e4cc8a4394531a438cd4ac62dae6f0c8292ce Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Tue, 30 Mar 2021 22:34:01 +0800 Subject: [PATCH] initial commit --- README.md | 4 + client/client.go | 11 ++ client/connector/connector.go | 11 ++ client/transporter/transporter.go | 14 ++ go.mod | 11 ++ go.sum | 27 ++++ logger/gost_logger.go | 96 ++++++++++++ logger/logger.go | 57 ++++++++ server/handler/handler.go | 11 ++ server/handler/http/handler.go | 233 ++++++++++++++++++++++++++++++ server/handler/http/metadata.go | 6 + server/handler/metadata.go | 3 + server/handler/option.go | 17 +++ server/handler/ss/handler.go | 129 +++++++++++++++++ server/handler/ss/metadata.go | 19 +++ server/handler/ssu/handler.go | 80 ++++++++++ server/handler/ssu/metadata.go | 19 +++ server/handler/transport.go | 43 ++++++ server/listener/listener.go | 14 ++ server/listener/metadata.go | 3 + server/listener/tcp/metadata.go | 19 +++ server/listener/tcp/tcp.go | 86 +++++++++++ server/server.go | 12 ++ 23 files changed, 925 insertions(+) create mode 100644 README.md create mode 100644 client/client.go create mode 100644 client/connector/connector.go create mode 100644 client/transporter/transporter.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 logger/gost_logger.go create mode 100644 logger/logger.go create mode 100644 server/handler/handler.go create mode 100644 server/handler/http/handler.go create mode 100644 server/handler/http/metadata.go create mode 100644 server/handler/metadata.go create mode 100644 server/handler/option.go create mode 100644 server/handler/ss/handler.go create mode 100644 server/handler/ss/metadata.go create mode 100644 server/handler/ssu/handler.go create mode 100644 server/handler/ssu/metadata.go create mode 100644 server/handler/transport.go create mode 100644 server/listener/listener.go create mode 100644 server/listener/metadata.go create mode 100644 server/listener/tcp/metadata.go create mode 100644 server/listener/tcp/tcp.go create mode 100644 server/server.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf703cb --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +gost 3.0 +====== + +WORK IN PROGRESS... \ No newline at end of file diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..445548c --- /dev/null +++ b/client/client.go @@ -0,0 +1,11 @@ +package client + +import ( + "github.com/go-gost/gost/client/connector" + "github.com/go-gost/gost/client/transporter" +) + +type Client struct { + Connector connector.Connector + Transporter transporter.Transporter +} diff --git a/client/connector/connector.go b/client/connector/connector.go new file mode 100644 index 0000000..2c113e2 --- /dev/null +++ b/client/connector/connector.go @@ -0,0 +1,11 @@ +package connector + +import ( + "context" + "net" +) + +// Connector is responsible for connecting to the destination address. +type Connector interface { + Connect(ctx context.Context, conn net.Conn, network, address string) (net.Conn, error) +} diff --git a/client/transporter/transporter.go b/client/transporter/transporter.go new file mode 100644 index 0000000..1d3f8e0 --- /dev/null +++ b/client/transporter/transporter.go @@ -0,0 +1,14 @@ +package transporter + +import ( + "context" + "net" +) + +// Transporter is responsible for handshaking with server. +type Transporter interface { + Dial(ctx context.Context, addr string) (net.Conn, error) + Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) + // Indicate that the Transporter supports multiplex + Multiplex() bool +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2a94684 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/go-gost/gost + +go 1.16 + +require ( + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/go-gost/gosocks5 v0.3.0 + github.com/shadowsocks/go-shadowsocks2 v0.1.4 + github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 + github.com/sirupsen/logrus v1.8.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9638f00 --- /dev/null +++ b/go.sum @@ -0,0 +1,27 @@ +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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-gost/gosocks5 v0.3.0 h1:Hkmp9YDRBSCJd7xywW6dBPT6B9aQTkuWd+3WCheJiJA= +github.com/go-gost/gosocks5 v0.3.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= +github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= +github.com/shadowsocks/go-shadowsocks2 v0.1.4 h1:4VzajPL7RwwmImysBSvI+lm/UaegDGQq3hr42dYo3gs= +github.com/shadowsocks/go-shadowsocks2 v0.1.4/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM= +github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 h1:XU9hik0exChEmY92ALW4l9WnDodxLVS9yOSNh2SizaQ= +github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/logger/gost_logger.go b/logger/gost_logger.go new file mode 100644 index 0000000..fb7047d --- /dev/null +++ b/logger/gost_logger.go @@ -0,0 +1,96 @@ +package logger + +import ( + "os" + + "github.com/sirupsen/logrus" +) + +var ( + _ Logger = (*logger)(nil) +) + +type logger struct { + logger *logrus.Entry +} + +func newLogger(name string) *logger { + l := logrus.New() + l.SetOutput(os.Stdout) + + gl := &logger{ + logger: l.WithFields(logrus.Fields{ + logFieldScope: name, + }), + } + + return gl +} + +// EnableJSONOutput enables JSON formatted output log. +func (l *logger) EnableJSONOutput(enabled bool) { + +} + +// SetOutputLevel sets log output level +func (l *logger) SetLevel(level LogLevel) { + lvl, _ := logrus.ParseLevel(string(level)) + l.logger.Logger.SetLevel(lvl) +} + +// WithFields adds new fields to log. +func (l *logger) WithFields(fields map[string]interface{}) Logger { + return &logger{ + logger: l.logger.WithFields(logrus.Fields(fields)), + } +} + +// Info logs a message at level Info. +func (l *logger) Info(args ...interface{}) { + l.logger.Log(logrus.InfoLevel, args...) +} + +// Infof logs a message at level Info. +func (l *logger) Infof(format string, args ...interface{}) { + l.logger.Logf(logrus.InfoLevel, format, args...) +} + +// Debug logs a message at level Debug. +func (l *logger) Debug(args ...interface{}) { + l.logger.Log(logrus.DebugLevel, args...) +} + +// Debugf logs a message at level Debug. +func (l *logger) Debugf(format string, args ...interface{}) { + l.logger.Logf(logrus.DebugLevel, format, args...) +} + +// Warn logs a message at level Warn. +func (l *logger) Warn(args ...interface{}) { + l.logger.Log(logrus.WarnLevel, args...) +} + +// Warnf logs a message at level Warn. +func (l *logger) Warnf(format string, args ...interface{}) { + l.logger.Logf(logrus.WarnLevel, format, args...) +} + +// Error logs a message at level Error. +func (l *logger) Error(args ...interface{}) { + l.logger.Log(logrus.ErrorLevel, args...) +} + +// Errorf logs a message at level Error. +func (l *logger) Errorf(format string, args ...interface{}) { + l.logger.Logf(logrus.ErrorLevel, format, args...) +} + +// Fatal logs a message at level Fatal then the process will exit with status set to 1. +func (l *logger) Fatal(args ...interface{}) { + l.logger.Fatal(args...) +} + +// Fatalf logs a message at level Fatal then the process will exit with status set to 1. +func (l *logger) Fatalf(format string, args ...interface{}) { + l.logger.Fatalf(format, args...) +} diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000..c1e05b4 --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,57 @@ +package logger + +import "sync" + +const ( + logFieldScope = "scope" +) + +// LogLevel is Logger Level type +type LogLevel string + +const ( + // DebugLevel has verbose message + DebugLevel LogLevel = "debug" + // InfoLevel is default log level + InfoLevel LogLevel = "info" + // WarnLevel is for logging messages about possible issues + WarnLevel LogLevel = "warn" + // ErrorLevel is for logging errors + ErrorLevel LogLevel = "error" + // FatalLevel is for logging fatal messages. The system shuts down after logging the message. + FatalLevel LogLevel = "fatal" +) + +var ( + globalLoggers = make(map[string]Logger) + globalLoggersLock sync.RWMutex +) + +type Logger interface { + EnableJSONOutput(enabled bool) + SetLevel(level LogLevel) + WithFields(map[string]interface{}) Logger + Debug(args ...interface{}) + Debugf(format string, args ...interface{}) + Info(args ...interface{}) + Infof(format string, args ...interface{}) + Warn(args ...interface{}) + Warnf(format string, args ...interface{}) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) +} + +func NewLogger(name string) Logger { + globalLoggersLock.Lock() + defer globalLoggersLock.Unlock() + + logger, ok := globalLoggers[name] + if !ok { + logger = newLogger(name) + globalLoggers[name] = logger + } + + return logger +} diff --git a/server/handler/handler.go b/server/handler/handler.go new file mode 100644 index 0000000..3a87239 --- /dev/null +++ b/server/handler/handler.go @@ -0,0 +1,11 @@ +package handler + +import ( + "context" + "net" +) + +type Handler interface { + Init(md Metadata) error + Handle(context.Context, net.Conn) +} diff --git a/server/handler/http/handler.go b/server/handler/http/handler.go new file mode 100644 index 0000000..9f5f34a --- /dev/null +++ b/server/handler/http/handler.go @@ -0,0 +1,233 @@ +package http + +import ( + "bufio" + "context" + "net" + "net/http" + + "github.com/go-gost/gost/logger" + "github.com/go-gost/gost/server/handler" +) + +var ( + _ handler.Handler = (*Handler)(nil) +) + +type Handler struct { + logger logger.Logger + md metadata +} + +func NewHandler(opts ...handler.Option) *Handler { + options := &handler.Options{} + for _, opt := range opts { + opt(options) + } + + return &Handler{ + logger: options.Logger, + } +} + +func (h *Handler) Init(md handler.Metadata) error { + return nil +} + +func (h *Handler) Handle(ctx context.Context, conn net.Conn) { + defer conn.Close() + + req, err := http.ReadRequest(bufio.NewReader(conn)) + if err != nil { + h.logger.WithFields(map[string]interface{}{ + "src": conn.RemoteAddr(), + "local": conn.LocalAddr(), + }).Error(err) + return + } + defer req.Body.Close() + + h.handleRequest(conn, req) +} + +func (h *Handler) handleRequest(conn net.Conn, req *http.Request) { + if req == nil { + return + } + + /* + // try to get the actual host. + if v := req.Header.Get("Gost-Target"); v != "" { + if h, err := decodeServerName(v); err == nil { + req.Host = h + } + } + */ + + host := req.Host + if _, port, _ := net.SplitHostPort(host); port == "" { + host = net.JoinHostPort(host, "80") + } + + /* + u, _, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) + if u != "" { + u += "@" + } + log.Logf("[http] %s%s -> %s -> %s", + u, conn.RemoteAddr(), h.options.Node.String(), host) + + if Debug { + dump, _ := httputil.DumpRequest(req, false) + log.Logf("[http] %s -> %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(dump)) + } + + req.Header.Del("Gost-Target") + */ + resp := &http.Response{ + ProtoMajor: 1, + ProtoMinor: 1, + Header: http.Header{}, + } + resp.Header.Add("Proxy-Agent", h.md.proxyAgent) + + /* + if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) { + log.Logf("[http] %s - %s : Unauthorized to tcp connect to %s", + conn.RemoteAddr(), conn.LocalAddr(), host) + resp.StatusCode = http.StatusForbidden + + if Debug { + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(dump)) + } + + resp.Write(conn) + return + } + */ + + /* + if h.options.Bypass.Contains(host) { + resp.StatusCode = http.StatusForbidden + + log.Logf("[http] %s - %s bypass %s", + conn.RemoteAddr(), conn.LocalAddr(), host) + if Debug { + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(dump)) + } + + resp.Write(conn) + return + } + */ + + /* + if !h.authenticate(conn, req, resp) { + return + } + */ + + if req.Method == "PRI" || + (req.Method != http.MethodConnect && req.URL.Scheme != "http") { + resp.StatusCode = http.StatusBadRequest + /* + if Debug { + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[http] %s <- %s\n%s", + conn.RemoteAddr(), conn.LocalAddr(), string(dump)) + } + */ + + resp.Write(conn) + return + } + + req.Header.Del("Proxy-Authorization") + + /* + 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 err error + 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("[http] %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()) + + // forward http request + lastNode := route.LastNode() + if req.Method != http.MethodConnect && lastNode.Protocol == "http" { + err = h.forwardRequest(conn, req, route) + if err == nil { + return + } + log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) + continue + } + + cc, err = route.Dial(host, + TimeoutChainOption(h.options.Timeout), + HostsChainOption(h.options.Hosts), + ResolverChainOption(h.options.Resolver), + ) + if err == nil { + break + } + log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) + } + + if err != nil { + resp.StatusCode = http.StatusServiceUnavailable + + if Debug { + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(dump)) + } + + resp.Write(conn) + return + } + */ + cc, err := net.Dial("tcp", host) + if err != nil { + resp.StatusCode = http.StatusServiceUnavailable + resp.Write(conn) + return + } + defer cc.Close() + + if req.Method == http.MethodConnect { + b := []byte("HTTP/1.1 200 Connection established\r\n" + + "Proxy-Agent: " + h.md.proxyAgent + "\r\n\r\n") + conn.Write(b) + } else { + req.Header.Del("Proxy-Connection") + + if err = req.Write(cc); err != nil { + return + } + } + + handler.Transport(conn, cc) +} diff --git a/server/handler/http/metadata.go b/server/handler/http/metadata.go new file mode 100644 index 0000000..2b8c70d --- /dev/null +++ b/server/handler/http/metadata.go @@ -0,0 +1,6 @@ +package http + +type metadata struct { + addr string + proxyAgent string +} diff --git a/server/handler/metadata.go b/server/handler/metadata.go new file mode 100644 index 0000000..4f37725 --- /dev/null +++ b/server/handler/metadata.go @@ -0,0 +1,3 @@ +package handler + +type Metadata map[string]string diff --git a/server/handler/option.go b/server/handler/option.go new file mode 100644 index 0000000..75a4095 --- /dev/null +++ b/server/handler/option.go @@ -0,0 +1,17 @@ +package handler + +import ( + "github.com/go-gost/gost/logger" +) + +type Options struct { + Logger logger.Logger +} + +type Option func(opts *Options) + +func LoggerOption(logger logger.Logger) Option { + return func(opts *Options) { + opts.Logger = logger + } +} diff --git a/server/handler/ss/handler.go b/server/handler/ss/handler.go new file mode 100644 index 0000000..3145e6c --- /dev/null +++ b/server/handler/ss/handler.go @@ -0,0 +1,129 @@ +package ss + +import ( + "bytes" + "context" + "net" + "time" + + "github.com/go-gost/gosocks5" + "github.com/go-gost/gost/logger" + "github.com/go-gost/gost/server/handler" + "github.com/shadowsocks/go-shadowsocks2/core" + ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" +) + +var ( + _ handler.Handler = (*Handler)(nil) +) + +type Handler struct { + logger logger.Logger + md metadata +} + +func NewHandler(opts ...handler.Option) *Handler { + options := &handler.Options{} + for _, opt := range opts { + opt(options) + } + + return &Handler{ + logger: options.Logger, + } +} + +func (h *Handler) Init(md handler.Metadata) (err error) { + h.md, err = h.parseMetadata(md) + if err != nil { + return + } + return nil +} + +func (h *Handler) Handle(ctx context.Context, conn net.Conn) { + defer conn.Close() + + if h.md.cipher != nil { + conn = &shadowConn{ + Conn: h.md.cipher.StreamConn(conn), + } + } + + if h.md.readTimeout > 0 { + conn.SetReadDeadline(time.Now().Add(h.md.readTimeout)) + } + + addr := &gosocks5.Addr{} + _, err := addr.ReadFrom(conn) + if err != nil { + h.logger.Error(err) + return + } + + conn.SetReadDeadline(time.Time{}) + + host := addr.String() + cc, err := net.Dial("tcp", host) + if err != nil { + return + } + defer cc.Close() + + handler.Transport(conn, cc) +} + +func (h *Handler) parseMetadata(md handler.Metadata) (m metadata, err error) { + m.cipher, err = h.initCipher(md[method], md[password], md[key]) + if err != nil { + return + } + if v, ok := md[readTimeout]; ok { + m.readTimeout, _ = time.ParseDuration(v) + } + return +} + +func (h *Handler) initCipher(method, password string, key string) (core.Cipher, error) { + if method == "" && password == "" { + return nil, nil + } + + c, _ := ss.NewCipher(method, password) + if c != nil { + return &shadowCipher{cipher: c}, nil + } + + return core.PickCipher(method, []byte(key), password) +} + +type shadowCipher struct { + cipher *ss.Cipher +} + +func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn { + return ss.NewConn(conn, c.cipher.Copy()) +} + +func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn { + return ss.NewSecurePacketConn(conn, c.cipher.Copy()) +} + +// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write, +// 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 +} diff --git a/server/handler/ss/metadata.go b/server/handler/ss/metadata.go new file mode 100644 index 0000000..76d49f0 --- /dev/null +++ b/server/handler/ss/metadata.go @@ -0,0 +1,19 @@ +package ss + +import ( + "time" + + "github.com/shadowsocks/go-shadowsocks2/core" +) + +const ( + method = "method" + password = "password" + key = "key" + readTimeout = "readTimeout" +) + +type metadata struct { + cipher core.Cipher + readTimeout time.Duration +} diff --git a/server/handler/ssu/handler.go b/server/handler/ssu/handler.go new file mode 100644 index 0000000..0588241 --- /dev/null +++ b/server/handler/ssu/handler.go @@ -0,0 +1,80 @@ +package ss + +import ( + "context" + "net" + "time" + + "github.com/go-gost/gost/logger" + "github.com/go-gost/gost/server/handler" + "github.com/shadowsocks/go-shadowsocks2/core" + ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" +) + +var ( + _ handler.Handler = (*Handler)(nil) +) + +type Handler struct { + logger logger.Logger + md metadata +} + +func NewHandler(opts ...handler.Option) *Handler { + options := &handler.Options{} + for _, opt := range opts { + opt(options) + } + + return &Handler{ + logger: options.Logger, + } +} + +func (h *Handler) Init(md handler.Metadata) (err error) { + h.md, err = h.parseMetadata(md) + if err != nil { + return + } + return nil +} + +func (h *Handler) Handle(ctx context.Context, conn net.Conn) { + defer conn.Close() +} + +func (h *Handler) parseMetadata(md handler.Metadata) (m metadata, err error) { + m.cipher, err = h.initCipher(md[method], md[password], md[key]) + if err != nil { + return + } + if v, ok := md[readTimeout]; ok { + m.readTimeout, _ = time.ParseDuration(v) + } + return +} + +func (h *Handler) initCipher(method, password string, key string) (core.Cipher, error) { + if method == "" && password == "" { + return nil, nil + } + + c, _ := ss.NewCipher(method, password) + if c != nil { + return &shadowCipher{cipher: c}, nil + } + + return core.PickCipher(method, []byte(key), password) +} + +type shadowCipher struct { + cipher *ss.Cipher +} + +func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn { + return ss.NewConn(conn, c.cipher.Copy()) +} + +func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn { + return ss.NewSecurePacketConn(conn, c.cipher.Copy()) +} diff --git a/server/handler/ssu/metadata.go b/server/handler/ssu/metadata.go new file mode 100644 index 0000000..76d49f0 --- /dev/null +++ b/server/handler/ssu/metadata.go @@ -0,0 +1,19 @@ +package ss + +import ( + "time" + + "github.com/shadowsocks/go-shadowsocks2/core" +) + +const ( + method = "method" + password = "password" + key = "key" + readTimeout = "readTimeout" +) + +type metadata struct { + cipher core.Cipher + readTimeout time.Duration +} diff --git a/server/handler/transport.go b/server/handler/transport.go new file mode 100644 index 0000000..67c244c --- /dev/null +++ b/server/handler/transport.go @@ -0,0 +1,43 @@ +package handler + +import ( + "io" + "sync" +) + +const ( + poolBufferSize = 32 * 1024 +) + +var ( + pool = sync.Pool{ + New: func() interface{} { + return make([]byte, poolBufferSize) + }, + } +) + +func Transport(rw1, rw2 io.ReadWriter) error { + errc := make(chan error, 1) + go func() { + errc <- copyBuffer(rw1, rw2) + }() + + go func() { + errc <- copyBuffer(rw2, rw1) + }() + + err := <-errc + if err != nil && err == io.EOF { + err = nil + } + return err +} + +func copyBuffer(dst io.Writer, src io.Reader) error { + buf := pool.Get().([]byte) + defer pool.Put(buf) + + _, err := io.CopyBuffer(dst, src, buf) + return err +} diff --git a/server/listener/listener.go b/server/listener/listener.go new file mode 100644 index 0000000..2c5d359 --- /dev/null +++ b/server/listener/listener.go @@ -0,0 +1,14 @@ +package listener + +import "net" + +// Listener is a server listener, just like a net.Listener. +type Listener interface { + Init(md Metadata) error + net.Listener +} + +// Accepter represents a network endpoint that can accept connection from peer. +type Accepter interface { + Accept() (net.Conn, error) +} diff --git a/server/listener/metadata.go b/server/listener/metadata.go new file mode 100644 index 0000000..6138db0 --- /dev/null +++ b/server/listener/metadata.go @@ -0,0 +1,3 @@ +package listener + +type Metadata map[string]string diff --git a/server/listener/tcp/metadata.go b/server/listener/tcp/metadata.go new file mode 100644 index 0000000..b3d79b6 --- /dev/null +++ b/server/listener/tcp/metadata.go @@ -0,0 +1,19 @@ +package tcp + +import "time" + +const ( + addr = "addr" + keepAlive = "keepAlive" + keepAlivePeriod = "keepAlivePeriod" +) + +const ( + defaultKeepAlivePeriod = 180 * time.Second +) + +type metadata struct { + addr string + keepAlive bool + keepAlivePeriod time.Duration +} diff --git a/server/listener/tcp/tcp.go b/server/listener/tcp/tcp.go new file mode 100644 index 0000000..61c8feb --- /dev/null +++ b/server/listener/tcp/tcp.go @@ -0,0 +1,86 @@ +package tcp + +import ( + "errors" + "net" + "strconv" + "time" + + "github.com/go-gost/gost/server/listener" +) + +type Listener struct { + md metadata + net.Listener +} + +func NewTCPListener() *Listener { + return &Listener{} +} + +func (l *Listener) Init(md listener.Metadata) (err error) { + l.md, err = l.parseMetadata(md) + if err != nil { + return + } + + laddr, err := net.ResolveTCPAddr("tcp", l.md.addr) + if err != nil { + return + } + ln, err := net.ListenTCP("tcp", laddr) + if err != nil { + return + } + + if l.md.keepAlive { + l.Listener = &keepAliveListener{ + TCPListener: ln, + keepAlivePeriod: l.md.keepAlivePeriod, + } + return + } + + l.Listener = ln + return +} + +func (l *Listener) parseMetadata(md listener.Metadata) (m metadata, err error) { + if val, ok := md[addr]; ok { + m.addr = val + } else { + err = errors.New("tcp listener: missing address") + return + } + + m.keepAlive = true + if val, ok := md[keepAlive]; ok { + m.keepAlive, _ = strconv.ParseBool(val) + } + + if val, ok := md[keepAlivePeriod]; ok { + m.keepAlivePeriod, _ = time.ParseDuration(val) + } + if m.keepAlivePeriod <= 0 { + m.keepAlivePeriod = defaultKeepAlivePeriod + } + + return +} + +type keepAliveListener struct { + keepAlivePeriod time.Duration + *net.TCPListener +} + +func (l *keepAliveListener) Accept() (c net.Conn, err error) { + tc, err := l.AcceptTCP() + if err != nil { + return + } + + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(l.keepAlivePeriod) + + return tc, nil +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..83f0a8c --- /dev/null +++ b/server/server.go @@ -0,0 +1,12 @@ +package server + +import ( + "github.com/go-gost/gost/server/handler" + "github.com/go-gost/gost/server/listener" +) + +// Server is a proxy server. +type Server struct { + Handler handler.Handler + Listener listener.Listener +}