diff --git a/connector/ss/connector.go b/connector/ss/connector.go index 65860f84..98c88238 100644 --- a/connector/ss/connector.go +++ b/connector/ss/connector.go @@ -9,11 +9,13 @@ import ( "github.com/go-gost/core/common/bufpool" "github.com/go-gost/core/connector" md "github.com/go-gost/core/metadata" + "github.com/go-gost/go-shadowsocks2/core" + "github.com/go-gost/go-shadowsocks2/socks" + "github.com/go-gost/go-shadowsocks2/utils" "github.com/go-gost/gosocks5" ctxvalue "github.com/go-gost/x/ctx" "github.com/go-gost/x/internal/util/ss" "github.com/go-gost/x/registry" - "github.com/shadowsocks/go-shadowsocks2/core" ) func init() { @@ -21,7 +23,7 @@ func init() { } type ssConnector struct { - cipher core.Cipher + client core.TCPClient md metadata options connector.Options } @@ -45,7 +47,13 @@ func (c *ssConnector) Init(md md.Metadata) (err error) { if c.options.Auth != nil { method := c.options.Auth.Username() password, _ := c.options.Auth.Password() - c.cipher, err = ss.ShadowCipher(method, password, c.md.key) + + clientConfig, err := utils.NewClientConfig(method, password) + if err != nil { + return nil + } + + c.client = core.NewTCPClient(clientConfig) } return @@ -93,21 +101,24 @@ func (c *ssConnector) Connect(ctx context.Context, conn net.Conn, network, addre defer conn.SetDeadline(time.Time{}) } - if c.cipher != nil { - conn = c.cipher.StreamConn(conn) + target := socks.Addr(rawaddr[:n]) + _, padding, err := utils.GeneratePadding() + if err != nil { + return nil, err + } + conn, err = c.client.WrapConn(conn, target, padding, nil) + if err != nil { + return nil, err } var sc net.Conn if c.md.noDelay { - sc = ss.ShadowConn(conn, nil) - // write the addr at once. - if _, err := sc.Write(rawaddr[:n]); err != nil { + err := conn.(core.TCPConn).ClientFirstWrite() + if err != nil { return nil, err } - } else { - // cache the header - sc = ss.ShadowConn(conn, rawaddr[:n]) } + sc = ss.ShadowConn(conn, nil) return sc, nil } diff --git a/connector/ss/udp/connector.go b/connector/ss/udp/connector.go index 4e6a74b4..4d5d8118 100644 --- a/connector/ss/udp/connector.go +++ b/connector/ss/udp/connector.go @@ -4,15 +4,18 @@ import ( "context" "fmt" "net" + "sync" "time" "github.com/go-gost/core/connector" md "github.com/go-gost/core/metadata" + "github.com/go-gost/go-shadowsocks2/core" + "github.com/go-gost/go-shadowsocks2/socks" + "github.com/go-gost/go-shadowsocks2/utils" ctxvalue "github.com/go-gost/x/ctx" "github.com/go-gost/x/internal/util/relay" "github.com/go-gost/x/internal/util/ss" "github.com/go-gost/x/registry" - "github.com/shadowsocks/go-shadowsocks2/core" ) func init() { @@ -20,9 +23,11 @@ func init() { } type ssuConnector struct { - cipher core.Cipher - md metadata - options connector.Options + client core.UDPClient + tcpClient core.TCPClient + md metadata + options connector.Options + sessionMap sync.Map } func NewConnector(opts ...connector.Option) connector.Connector { @@ -44,7 +49,11 @@ func (c *ssuConnector) Init(md md.Metadata) (err error) { if c.options.Auth != nil { method := c.options.Auth.Username() password, _ := c.options.Auth.Password() - c.cipher, err = ss.ShadowCipher(method, password, c.md.key) + clientConfig, err := utils.NewClientConfig(method, password) + if err != nil { + return nil + } + c.client = core.NewUDPClient(clientConfig, 60) } return @@ -80,16 +89,19 @@ func (c *ssuConnector) Connect(ctx context.Context, conn net.Conn, network, addr pc, ok := conn.(net.PacketConn) if ok { - if c.cipher != nil { - pc = c.cipher.PacketConn(pc) - } - // standard UDP relay - return ss.UDPClientConn(pc, conn.RemoteAddr(), taddr, c.md.udpBufferSize), nil + return ss.UDPClientConn(pc, conn.RemoteAddr(), taddr, c.md.udpBufferSize, &c.client, &c.sessionMap), nil } - if c.cipher != nil { - conn = ss.ShadowConn(c.cipher.StreamConn(conn), nil) + _, padding, err := utils.GeneratePadding() + if err != nil { + return nil, err + } + + target := socks.ParseAddr(taddr.String()) + conn, err = c.tcpClient.WrapConn(conn, target, padding, nil) + if err != nil { + return nil, err } // UDP over TCP diff --git a/dialer/udp/conn.go b/dialer/udp/conn.go index 33e962db..5b0d95b0 100644 --- a/dialer/udp/conn.go +++ b/dialer/udp/conn.go @@ -1,6 +1,8 @@ package udp -import "net" +import ( + "net" +) type conn struct { *net.UDPConn diff --git a/go.mod b/go.mod index ef04110d..7040af9c 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/gin-contrib/cors v1.7.2 github.com/gin-gonic/gin v1.10.1 github.com/go-gost/core v0.3.3 + github.com/go-gost/go-shadowsocks2 v0.1.0 github.com/go-gost/gosocks4 v0.0.1 github.com/go-gost/gosocks5 v0.4.2 github.com/go-gost/plugin v0.2.1 @@ -30,7 +31,7 @@ require ( github.com/quic-go/quic-go v0.54.1 github.com/quic-go/webtransport-go v0.9.0 github.com/rs/xid v1.3.0 - github.com/shadowsocks/go-shadowsocks2 v0.1.6-0.20241020092332-e1fe9ea73740 + github.com/shadowsocks/go-shadowsocks2 v0.1.5 github.com/sirupsen/logrus v1.9.3 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/spf13/viper v1.19.0 @@ -116,6 +117,7 @@ require ( github.com/tjfoc/gmsm v1.4.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.8.0 // indirect diff --git a/go.sum b/go.sum index a7688f22..76c703a5 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-gost/core v0.3.3 h1:YN15FQptQoNB0MlMR283C99w2EeGql9Cm+4QVlN6zs0= github.com/go-gost/core v0.3.3/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A= +github.com/go-gost/go-shadowsocks2 v0.1.0 h1:aIJwIe9raITw/aiVFTETstJ0TFpIcurwwhJVqT39vic= +github.com/go-gost/go-shadowsocks2 v0.1.0/go.mod h1:866zFNNI3He6Wef1M/IvAjTal74WhcfKfBgRpTlkKys= github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s= github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc= github.com/go-gost/gosocks5 v0.4.2 h1:IianxHTkACPqCwiOAT3MHoMdSUl+SEPSRu1ikawC1Pc= @@ -192,8 +194,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/shadowsocks/go-shadowsocks2 v0.1.6-0.20241020092332-e1fe9ea73740 h1:XdDrN8rtxdgW3TLn7pAuobI9PhPMbf6Geu9nvFzXn2E= -github.com/shadowsocks/go-shadowsocks2 v0.1.6-0.20241020092332-e1fe9ea73740/go.mod h1:Oqfn/ykzqjeX00+7IuPyR7wGYgOzld0Tni6djgElacI= +github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28= +github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -259,6 +261,12 @@ github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -271,6 +279,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= @@ -324,6 +333,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= diff --git a/handler/ss/handler.go b/handler/ss/handler.go index 4108f4a8..53f34164 100644 --- a/handler/ss/handler.go +++ b/handler/ss/handler.go @@ -5,7 +5,6 @@ import ( "bytes" "context" "crypto/tls" - "io" "net" "time" @@ -14,7 +13,8 @@ import ( md "github.com/go-gost/core/metadata" "github.com/go-gost/core/observer/stats" "github.com/go-gost/core/recorder" - "github.com/go-gost/gosocks5" + "github.com/go-gost/go-shadowsocks2/core" + "github.com/go-gost/go-shadowsocks2/utils" xctx "github.com/go-gost/x/ctx" ictx "github.com/go-gost/x/internal/ctx" xnet "github.com/go-gost/x/internal/net" @@ -26,7 +26,6 @@ import ( stats_wrapper "github.com/go-gost/x/observer/stats/wrapper" xrecorder "github.com/go-gost/x/recorder" "github.com/go-gost/x/registry" - "github.com/shadowsocks/go-shadowsocks2/core" ) func init() { @@ -34,11 +33,11 @@ func init() { } type ssHandler struct { - cipher core.Cipher md metadata options handler.Options recorder recorder.RecorderObject certPool tls_util.CertPool + server core.TCPServer } func NewHandler(opts ...handler.Option) handler.Handler { @@ -59,9 +58,15 @@ func (h *ssHandler) Init(md md.Metadata) (err error) { if h.options.Auth != nil { method := h.options.Auth.Username() password, _ := h.options.Auth.Password() - h.cipher, err = ss.ShadowCipher(method, password, h.md.key) + + serverConfig, err := utils.NewServerConfig(method, password, h.md.users) if err != nil { - return + return err + } + h.server = core.NewTCPServer(serverConfig) + err = h.server.Init() + if err != nil { + return err } } @@ -131,41 +136,38 @@ func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.H return rate_limiter.ErrRateLimit } - if h.cipher != nil { - conn = ss.ShadowConn(h.cipher.StreamConn(conn), nil) + wrappedConn, err := h.server.WrapConn(conn) + if err != nil { + return } + target := wrappedConn.Target() + conn = ss.ShadowConn(wrappedConn, nil) conn.SetReadDeadline(time.Now().Add(h.md.readTimeout)) - addr := &gosocks5.Addr{} - if _, err := addr.ReadFrom(conn); err != nil { - log.Error(err) - io.Copy(io.Discard, conn) - return err - } - ro.Host = addr.String() + ro.Host = target.String() conn.SetReadDeadline(time.Time{}) log = log.WithFields(map[string]any{ - "dst": addr.String(), - "host": addr.String(), + "dst": target.String(), + "host": target.String(), }) - log.Debugf("%s >> %s", conn.RemoteAddr(), addr) + log.Debugf("%s >> %s", conn.RemoteAddr(), target) - if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", addr.String(), bypass.WithService(h.options.Service)) { - log.Debug("bypass: ", addr.String()) + if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", target.String(), bypass.WithService(h.options.Service)) { + log.Debug("bypass: ", target.String()) return nil } switch h.md.hash { case "host": - ctx = xctx.ContextWithHash(ctx, &xctx.Hash{Source: addr.String()}) + ctx = xctx.ContextWithHash(ctx, &xctx.Hash{Source: target.String()}) } var buf bytes.Buffer - cc, err := h.options.Router.Dial(ictx.ContextWithBuffer(ctx, &buf), "tcp", addr.String()) + cc, err := h.options.Router.Dial(ictx.ContextWithBuffer(ctx, &buf), "tcp", target.String()) ro.Route = buf.String() if err != nil { return err diff --git a/handler/ss/metadata.go b/handler/ss/metadata.go index 33c27a9c..2683f482 100644 --- a/handler/ss/metadata.go +++ b/handler/ss/metadata.go @@ -8,6 +8,7 @@ import ( "github.com/go-gost/core/bypass" mdata "github.com/go-gost/core/metadata" + "github.com/go-gost/go-shadowsocks2/core" mdutil "github.com/go-gost/x/metadata/util" "github.com/go-gost/x/registry" ) @@ -26,6 +27,8 @@ type metadata struct { privateKey crypto.PrivateKey alpn string mitmBypass bypass.Bypass + + users []core.UserConfig } func (h *ssHandler) parseMetadata(md mdata.Metadata) (err error) { @@ -34,6 +37,14 @@ func (h *ssHandler) parseMetadata(md mdata.Metadata) (err error) { h.md.readTimeout = 15 * time.Second } + usersMap := mdutil.GetStringMapString(md, "users") + for name, pass := range usersMap { + h.md.users = append(h.md.users, core.UserConfig{ + Name: name, + Password: pass, + }) + } + h.md.key = mdutil.GetString(md, "key") h.md.hash = mdutil.GetString(md, "hash") diff --git a/handler/ss/udp/handler.go b/handler/ss/udp/handler.go index b3ecd559..853d9f00 100644 --- a/handler/ss/udp/handler.go +++ b/handler/ss/udp/handler.go @@ -5,6 +5,8 @@ import ( "context" "errors" "net" + "net/netip" + "sync" "time" "github.com/go-gost/core/bypass" @@ -13,14 +15,14 @@ import ( "github.com/go-gost/core/logger" md "github.com/go-gost/core/metadata" "github.com/go-gost/core/recorder" + "github.com/go-gost/go-shadowsocks2/core" + "github.com/go-gost/go-shadowsocks2/utils" xctx "github.com/go-gost/x/ctx" ictx "github.com/go-gost/x/internal/ctx" "github.com/go-gost/x/internal/util/relay" - "github.com/go-gost/x/internal/util/ss" rate_limiter "github.com/go-gost/x/limiter/rate" xrecorder "github.com/go-gost/x/recorder" "github.com/go-gost/x/registry" - "github.com/shadowsocks/go-shadowsocks2/core" ) func init() { @@ -28,10 +30,12 @@ func init() { } type ssuHandler struct { - cipher core.Cipher - md metadata - options handler.Options - recorder recorder.RecorderObject + udpServer core.UDPServer + tcpServer core.TCPServer // for udp over tcp + connMap sync.Map + md metadata + options handler.Options + recorder recorder.RecorderObject } func NewHandler(opts ...handler.Option) handler.Handler { @@ -53,9 +57,21 @@ func (h *ssuHandler) Init(md md.Metadata) (err error) { if h.options.Auth != nil { method := h.options.Auth.Username() password, _ := h.options.Auth.Password() - h.cipher, err = ss.ShadowCipher(method, password, h.md.key) + + serverConfig, err := utils.NewServerConfig(method, password, h.md.users) if err != nil { - return + return err + } + h.udpServer = core.NewUDPServer(serverConfig, time.Duration(60*time.Second)) + err = h.udpServer.Init() + if err != nil { + return err + } + + h.tcpServer = core.NewTCPServer(serverConfig) + err = h.udpServer.Init() + if err != nil { + return err } } @@ -113,123 +129,208 @@ func (h *ssuHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler. } pc, ok := conn.(net.PacketConn) - if ok { - if h.cipher != nil { - pc = h.cipher.PacketConn(pc) - } - // standard UDP relay. - pc = ss.UDPServerConn(pc, conn.RemoteAddr(), h.md.udpBufferSize) - } else { - if h.cipher != nil { - conn = ss.ShadowConn(h.cipher.StreamConn(conn), nil) - } - // UDP over TCP - pc = relay.UDPTunServerConn(conn) - } - - // obtain a udp connection - var buf bytes.Buffer - c, err := h.options.Router.Dial(ictx.ContextWithBuffer(ctx, &buf), "udp", "") // UDP association - ro.Route = buf.String() - if err != nil { - log.Error(err) - return err - } - defer c.Close() - - cc, ok := c.(net.PacketConn) if !ok { - err := errors.New("ss: wrong connection type") - log.Error(err) - return err + // UDP over TCP + conn, err := h.tcpServer.WrapConn(conn) + if err != nil { + return err + } + + // Note that UDPTunServerConn returns a wraped net.Conn, but it's behavior is not ordinary. + // ReadFrom will return target addr of socks5 instead of normal remote addr + // WriteTo will write data to original remote addr instead of the addr parameter passed to + pc = relay.UDPTunServerConn(conn) + + return h.relayPacketTCP(ctx, pc, ro, log) + } else { + return h.relayPacketUDP(ctx, pc, ro, log) } - - t := time.Now() - log.Infof("%s <-> %s", conn.LocalAddr(), cc.LocalAddr()) - h.relayPacket(ctx, pc, cc, ro, log) - log.WithFields(map[string]any{"duration": time.Since(t)}). - Infof("%s >-< %s", conn.LocalAddr(), cc.LocalAddr()) - - return nil } -func (h *ssuHandler) relayPacket(ctx context.Context, pc1, pc2 net.PacketConn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) { - errc := make(chan error, 2) - +// UDP over TCP +func (h *ssuHandler) relayPacketTCP(ctx context.Context, src net.PacketConn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error { bufferSize := h.md.udpBufferSize if bufferSize <= 0 { bufferSize = defaultBufferSize } - go func() { - b := bufpool.Get(bufferSize) - defer bufpool.Put(b) + b := bufpool.Get(bufferSize) + defer bufpool.Put(b) - for { - err := func() error { - n, addr, err := pc1.ReadFrom(b) - if err != nil { - return err - } - - if ro.Host == "" { - ro.Host = addr.String() - } - - if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, addr.Network(), addr.String(), bypass.WithService(h.options.Service)) { - log.Warn("bypass: ", addr) - return nil - } - - if _, err = pc2.WriteTo(b[:n], addr); err != nil { - return err - } - - log.Tracef("%s >>> %s data: %d", - pc2.LocalAddr(), addr, n) - return nil - }() - - if err != nil { - errc <- err - return - } + var dstConn net.Conn + defer func() { + if dstConn != nil { + dstConn.Close() } }() - go func() { - b := bufpool.Get(bufferSize) - defer bufpool.Put(b) - - for { - err := func() error { - n, raddr, err := pc2.ReadFrom(b) - if err != nil { - return err - } - - if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, raddr.Network(), raddr.String(), bypass.WithService(h.options.Service)) { - log.Warn("bypass: ", raddr) - return nil - } - - if _, err = pc1.WriteTo(b[:n], raddr); err != nil { - return err - } - - log.Tracef("%s <<< %s data: %d", - pc2.LocalAddr(), raddr, n) - return nil - }() - - if err != nil { - errc <- err - return - } + for { + n, targetAddr, err := src.ReadFrom(b) + if err != nil { + return err } - }() - return <-errc + if ro.Host == "" { + ro.Host = targetAddr.String() + } + + if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, targetAddr.Network(), targetAddr.String(), bypass.WithService(h.options.Service)) { + log.Warn("bypass: ", targetAddr) + return nil + } + + if dstConn == nil { + // obtain a udp connection + var buf bytes.Buffer + dstConn, err := h.options.Router.Dial(ictx.ContextWithBuffer(ctx, &buf), "udp", "") // UDP association + ro.Route = buf.String() + if err != nil { + log.Error(err) + return err + } + + cc, ok := dstConn.(net.PacketConn) + if !ok { + err := errors.New("ss: wrong connection type") + log.Error(err) + return err + } + + log.Infof("%s <-> %s", src.LocalAddr(), cc.LocalAddr()) + + go func() { + defer dstConn.Close() + for { + n, raddr, err := cc.ReadFrom(b) + if err != nil { + log.Warnf("failed to read response from %v: %v", raddr, err) + return + } + + if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, raddr.Network(), raddr.String(), bypass.WithService(h.options.Service)) { + log.Warn("bypass: ", raddr) + return + } + + if _, err = src.WriteTo(b[:n], targetAddr); err != nil { + log.Warnf("failed to write response from %v: %v", targetAddr, err) + return + } + + log.Tracef("%s <<< %s data: %d", + cc.LocalAddr(), raddr, n) + } + }() + } + + if _, err = dstConn.(net.PacketConn).WriteTo(b[:n], targetAddr); err != nil { + return err + } + + log.Tracef("%s >>> %s data: %d", + dstConn.(net.PacketConn).LocalAddr(), targetAddr, n) + } +} + +// standard udp relay +// Dataflow: src (encrypted data) <-> dst (plaintext data) +func (h *ssuHandler) relayPacketUDP(ctx context.Context, src net.PacketConn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error { + bufferSize := h.md.udpBufferSize + if bufferSize <= 0 { + bufferSize = defaultBufferSize + } + + b := bufpool.Get(bufferSize) + defer bufpool.Put(b) + + for { + n, addr, err := src.ReadFrom(b) + if err != nil { + return err + } + + clientAddr, err := netip.ParseAddrPort(addr.String()) + if err != nil { + return err + } + session, payload, err := h.udpServer.Inbound(b[:n], clientAddr) + if err != nil { + return err + } + + targetAddr, err := net.ResolveUDPAddr("udp", session.Target().String()) + if ro.Host == "" { + ro.Host = targetAddr.String() + } + + if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, targetAddr.Network(), targetAddr.String(), bypass.WithService(h.options.Service)) { + log.Warn("bypass: ", addr) + return nil + } + if err != nil { + return err + } + + dstConn, ok := h.connMap.Load(session.Hash()) + if !ok { + // obtain a udp connection + var buf bytes.Buffer + c, err := h.options.Router.Dial(ictx.ContextWithBuffer(ctx, &buf), "udp", "") // UDP association + ro.Route = buf.String() + if err != nil { + log.Error(err) + return err + } + + cc, ok := c.(net.PacketConn) + if !ok { + err := errors.New("ss: wrong connection type") + log.Error(err) + return err + } + h.connMap.Store(session.Hash(), c) + + dstConn = cc + + log.Infof("%s <-> %s", src.LocalAddr(), cc.LocalAddr()) + + go func() { + defer func() { + c.Close() + h.connMap.Delete(session.Hash()) + }() + + for { + n, raddr, err := dstConn.(net.PacketConn).ReadFrom(b) + if err != nil { + log.Warnf("failed to read response: %v", err) + return + } + + clientAddr := session.ClientAddr() + if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, raddr.Network(), raddr.String(), bypass.WithService(h.options.Service)) { + log.Warn("bypass: ", raddr) + return + } + encrypted, err := h.udpServer.Outbound(b[:n], session) + if _, err = src.WriteTo(encrypted, net.UDPAddrFromAddrPort(clientAddr)); err != nil { + log.Warnf("failed to write response to %v: %v", clientAddr, err) + return + } + + log.Tracef("%s <<< %s data: %d", + dstConn.(net.PacketConn).LocalAddr(), raddr, n) + } + }() + } + + if n, err = dstConn.(net.PacketConn).WriteTo(payload, targetAddr); err != nil { + return err + } + + log.Tracef("%s >>> %s data: %d", + dstConn.(net.PacketConn).LocalAddr(), targetAddr, n) + } } func (h *ssuHandler) checkRateLimit(addr net.Addr) bool { diff --git a/handler/ss/udp/metadata.go b/handler/ss/udp/metadata.go index b6da0734..dfe19cff 100644 --- a/handler/ss/udp/metadata.go +++ b/handler/ss/udp/metadata.go @@ -4,6 +4,7 @@ import ( "time" mdata "github.com/go-gost/core/metadata" + "github.com/go-gost/go-shadowsocks2/core" mdutil "github.com/go-gost/x/metadata/util" ) @@ -15,6 +16,8 @@ type metadata struct { key string readTimeout time.Duration udpBufferSize int + + users []core.UserConfig } func (h *ssuHandler) parseMetadata(md mdata.Metadata) (err error) { @@ -23,5 +26,13 @@ func (h *ssuHandler) parseMetadata(md mdata.Metadata) (err error) { h.md.readTimeout = mdutil.GetDuration(md, "readTimeout") h.md.udpBufferSize = mdutil.GetInt(md, "udpBufferSize", "udp.bufferSize") + usersMap := mdutil.GetStringMapString(md, "users") + for name, pass := range usersMap { + h.md.users = append(h.md.users, core.UserConfig{ + Name: name, + Password: pass, + }) + } + return } diff --git a/handler/tap/handler.go b/handler/tap/handler.go index b990c56b..b1a77ded 100644 --- a/handler/tap/handler.go +++ b/handler/tap/handler.go @@ -15,8 +15,8 @@ import ( "github.com/go-gost/core/hop" "github.com/go-gost/core/logger" md "github.com/go-gost/core/metadata" - ictx "github.com/go-gost/x/internal/ctx" xctx "github.com/go-gost/x/ctx" + ictx "github.com/go-gost/x/internal/ctx" "github.com/go-gost/x/internal/util/ss" tap_util "github.com/go-gost/x/internal/util/tap" "github.com/go-gost/x/registry" diff --git a/internal/util/ss/conn.go b/internal/util/ss/conn.go index c712aee6..80554b02 100644 --- a/internal/util/ss/conn.go +++ b/internal/util/ss/conn.go @@ -1,11 +1,14 @@ package ss import ( - "bytes" + "errors" "net" + "net/netip" + "sync" "github.com/go-gost/core/common/bufpool" - "github.com/go-gost/gosocks5" + "github.com/go-gost/go-shadowsocks2/core" + "github.com/go-gost/go-shadowsocks2/socks" ) const ( @@ -17,14 +20,19 @@ var ( _ net.Conn = (*UDPConn)(nil) ) +// This wrapped connection has different behavior than ordinary connection: +// 1. ReadFrom will return target addr of shadowsocks instead of remote addr type UDPConn struct { + client *core.UDPClient + server *core.UDPServer + sessionMap *sync.Map net.PacketConn raddr net.Addr taddr net.Addr bufferSize int } -func UDPClientConn(c net.PacketConn, remoteAddr, targetAddr net.Addr, bufferSize int) *UDPConn { +func UDPClientConn(c net.PacketConn, remoteAddr, targetAddr net.Addr, bufferSize int, client *core.UDPClient, sessionMap *sync.Map) *UDPConn { if bufferSize <= 0 { bufferSize = defaultBufferSize } @@ -34,18 +42,8 @@ func UDPClientConn(c net.PacketConn, remoteAddr, targetAddr net.Addr, bufferSize raddr: remoteAddr, taddr: targetAddr, bufferSize: bufferSize, - } -} - -func UDPServerConn(c net.PacketConn, remoteAddr net.Addr, bufferSize int) *UDPConn { - if bufferSize <= 0 { - bufferSize = defaultBufferSize - } - - return &UDPConn{ - PacketConn: c, - raddr: remoteAddr, - bufferSize: bufferSize, + client: client, + sessionMap: sessionMap, } } @@ -53,19 +51,34 @@ func (c *UDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) { buf := bufpool.Get(c.bufferSize) defer bufpool.Put(buf) + clientAddr, err := netip.ParseAddrPort(c.LocalAddr().String()) + if err != nil { + return + } + n, _, err = c.PacketConn.ReadFrom(buf) if err != nil { return } - saddr := gosocks5.Addr{} - addrLen, err := saddr.ReadFrom(bytes.NewReader(buf[:n])) - if err != nil { - return + var payload []byte + var session core.UDPSession + if c.client != nil { + s, ok := c.sessionMap.Load(core.SessionHashFromAddrPort(clientAddr)) + if !ok { + return 0, nil, errors.New("udp session cannot find") + } + session = s.(core.UDPSession) + payload, err = c.client.Outbound(buf[:n], session) + if err != nil { + return + } + } else { + return 0, nil, errors.New("UDPConn must be client ") } - n = copy(b, buf[addrLen:n]) - addr, err = net.ResolveUDPAddr("udp", saddr.String()) + n = copy(b, payload) + addr, err = net.ResolveUDPAddr("udp", session.Target().String()) return } @@ -76,22 +89,26 @@ func (c *UDPConn) Read(b []byte) (n int, err error) { } func (c *UDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { - socksAddr := gosocks5.Addr{} - if err = socksAddr.ParseFrom(addr.String()); err != nil { - return - } - - buf := bufpool.Get(c.bufferSize) - defer bufpool.Put(buf) - - addrLen, err := socksAddr.Encode(buf) + target := socks.ParseAddr(c.taddr.String()) + clientAddr, err := netip.ParseAddrPort(c.LocalAddr().String()) if err != nil { return } - n = copy(buf[addrLen:], b) - _, err = c.PacketConn.WriteTo(buf[:addrLen+n], c.raddr) + var session core.UDPSession + var encrypted []byte + if c.client != nil { + session, encrypted, err = c.client.Inbound(b, clientAddr, target) + if err != nil { + return + } + c.sessionMap.Store(session.Hash(), session) + } else { + return 0, errors.New("UDPConn must be client") + } + _, err = c.PacketConn.WriteTo(encrypted, c.raddr) + n = len(b) return } diff --git a/service/service.go b/service/service.go index 4988ec2d..49a56be8 100644 --- a/service/service.go +++ b/service/service.go @@ -147,6 +147,7 @@ func (s *defaultService) Serve() error { }) gctx, cancel := context.WithCancel(context.Background()) + defer cancel() if s.status.Stats() != nil {