diff --git a/cmd/server/README.md b/cmd/server/README.md index 2894314..141ff47 100644 --- a/cmd/server/README.md +++ b/cmd/server/README.md @@ -35,6 +35,7 @@ Mini configuration: "certificate": "/path/to/fullchain.cer", "private_key": "/path/to/private.key", "congestion_control": "bbr", + "disable_outbound_udp443": true, "log_level": "info" } ``` @@ -53,14 +54,16 @@ Full configuration: "log_level": "info", "fwmark": "0x1000", "send_through": "113.25.132.3", - "dialer_link": "socks5://127.0.0.1:1080" + "dialer_link": "socks5://127.0.0.1:1080", + "disable_outbound_udp443": true } ``` -- Optional values of `congestion_control`: cubic, bbr, new_reno. +- `congestion_control`: one of cubic, bbr, new_reno. - `fwmark` is useful for iptables/nft. - `send_through` is the interface IP to specify to use. - `dialer_link` can be extreme flexible. Juicity support many protocols, even proxy chains. See [proxy-protocols](https://github.com/daeuniverse/dae/blob/main/docs/en/proxy-protocols.md) [中文](https://github.com/daeuniverse/dae/blob/main/docs/zh/proxy-protocols.md). +- `disable_outbound_udp443`: usually quic traffic. Suggest to disable it because quic usually consumes too much cpu/mem resources. ## Arguments diff --git a/cmd/server/run.go b/cmd/server/run.go index 7260b4a..ea64ad4 100644 --- a/cmd/server/run.go +++ b/cmd/server/run.go @@ -73,14 +73,15 @@ func Serve(conf *config.Config) (err error) { } } s, err := server.New(&server.Options{ - Logger: logger, - Users: conf.Users, - Certificate: conf.Certificate, - PrivateKey: conf.PrivateKey, - CongestionControl: conf.CongestionControl, - Fwmark: int(fwmark), - SendThrough: conf.SendThrough, - DialerLink: conf.DialerLink, + Logger: logger, + Users: conf.Users, + Certificate: conf.Certificate, + PrivateKey: conf.PrivateKey, + CongestionControl: conf.CongestionControl, + Fwmark: int(fwmark), + SendThrough: conf.SendThrough, + DialerLink: conf.DialerLink, + DisableOutboundUdp443: conf.DisableOutboundUdp443, }) if err != nil { return err diff --git a/config/config.go b/config/config.go index 9553eb9..d59446a 100644 --- a/config/config.go +++ b/config/config.go @@ -21,12 +21,13 @@ type Config struct { Forward map[string]string `json:"forward"` // Server - Users map[string]string `json:"users"` - Certificate string `json:"certificate"` - PrivateKey string `json:"private_key"` - Fwmark string `json:"fwmark"` - SendThrough string `json:"send_through"` - DialerLink string `json:"dialer_link"` + Users map[string]string `json:"users"` + Certificate string `json:"certificate"` + PrivateKey string `json:"private_key"` + Fwmark string `json:"fwmark"` + SendThrough string `json:"send_through"` + DialerLink string `json:"dialer_link"` + DisableOutboundUdp443 bool `json:"disable_outbound_udp443"` // Common Listen string `json:"listen"` diff --git a/go.mod b/go.mod index 22b661c..368a193 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.21.0 require ( github.com/daeuniverse/outbound v0.0.0-20230814161100-5d9b25e38843 - github.com/daeuniverse/softwind v0.0.0-20230821142121-f4d871b5a8c9 + github.com/daeuniverse/softwind v0.0.0-20230827075246-db555b38f9f7 github.com/google/uuid v1.3.0 github.com/miekg/dns v1.1.55 - github.com/mzz2017/quic-go v0.0.0-20230821141654-3dd2575ee6bc + github.com/mzz2017/quic-go v0.0.0-20230826094347-f6ce422906f4 github.com/nadoo/glider v0.16.3 github.com/rs/zerolog v1.30.0 github.com/sourcegraph/conc v0.3.0 @@ -37,7 +37,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mzz2017/disk-bloom v1.0.1 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect - github.com/quic-go/qtls-go1-20 v0.3.2 // indirect + github.com/quic-go/qtls-go1-20 v0.3.3 // indirect github.com/quic-go/quic-go v0.37.4 // indirect github.com/refraction-networking/utls v1.4.3 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect @@ -62,3 +62,4 @@ replace github.com/nadoo/glider => github.com/juicity/glider v0.0.0-202308051437 // replace github.com/daeuniverse/softwind => ../softwind // replace github.com/mzz2017/quic-go => ../quic-go +// replace github.com/mzz2017/quic-go => github.com/mzz2017/quic-go v0.0.0-20230821141654-3dd2575ee6bc diff --git a/go.sum b/go.sum index 1619793..4e1feda 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/daeuniverse/outbound v0.0.0-20230814161100-5d9b25e38843 h1:DQCB9XdxWmI9ySh7lh1Yyer+1RM+d+1oXG586NBPJ5U= github.com/daeuniverse/outbound v0.0.0-20230814161100-5d9b25e38843/go.mod h1:0MOSc+twby808YzJjBA2VOSd928vLQFhUzHLSdL7aKM= -github.com/daeuniverse/softwind v0.0.0-20230821142121-f4d871b5a8c9 h1:88k/mjYFuA5294C50M3rXMqRYaqSNAnuk9hnjY/xGMM= -github.com/daeuniverse/softwind v0.0.0-20230821142121-f4d871b5a8c9/go.mod h1:K9Au9LY2ttqfAhZXxPPnyqt4Bue1dd/Xi8WPsZEgxOk= +github.com/daeuniverse/softwind v0.0.0-20230827075246-db555b38f9f7 h1:3gbOD+bVTILxwwfsDufnPjGDXZgoNjcbls0INqr1/j4= +github.com/daeuniverse/softwind v0.0.0-20230827075246-db555b38f9f7/go.mod h1:Didl+N+gp/UIZ6+n9lwHQjdQEBNWYbdFXvRlmlVcth8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -68,8 +68,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mzz2017/disk-bloom v1.0.1 h1:rEF9MiXd9qMW3ibRpqcerLXULoTgRlM21yqqJl1B90M= github.com/mzz2017/disk-bloom v1.0.1/go.mod h1:JLHETtUu44Z6iBmsqzkOtFlRvXSlKnxjwiBRDapizDI= -github.com/mzz2017/quic-go v0.0.0-20230821141654-3dd2575ee6bc h1:2gjLlS2yBxXUGICgHSWGLS5LyRa0Lr6+w5GFiqOco/o= -github.com/mzz2017/quic-go v0.0.0-20230821141654-3dd2575ee6bc/go.mod h1:j4yzgjc6nLseaxzQT/As8D7VRQMKASjVWXBunJGTF8Y= +github.com/mzz2017/quic-go v0.0.0-20230826094347-f6ce422906f4 h1:GlGWHALzJpMsdbJMKIRg0XNjkjkWjL7x0x7JfiGQaBI= +github.com/mzz2017/quic-go v0.0.0-20230826094347-f6ce422906f4/go.mod h1:tWtXPktBZvMi0SzXP4QFO8SKDNsAkGEijAeiNe8QmyM= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= @@ -77,8 +77,8 @@ github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJK github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= -github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= +github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4= github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= github.com/refraction-networking/utls v1.4.3 h1:BdWS3BSzCwWCFfMIXP3mjLAyQkdmog7diaD/OqFbAzM= diff --git a/server/forwarder.go b/server/forwarder.go index ba11256..616cc02 100644 --- a/server/forwarder.go +++ b/server/forwarder.go @@ -170,7 +170,7 @@ func (s *Forwarder) Serve() (err error) { go func(buf pool.PB, lAddr netip.AddrPort) { defer buf.Put() endpoint, isNew, err := s.udpEndpointPool.GetOrCreate(lAddr, &UdpEndpointOptions{ - Handler: func(data []byte, from netip.AddrPort) error { + Handler: func(data []byte, from netip.AddrPort, metadata any) error { _, err := s.udpListener.WriteToUDPAddrPort(data, lAddr) return err }, diff --git a/server/inflight.go b/server/inflight.go new file mode 100644 index 0000000..b5d7850 --- /dev/null +++ b/server/inflight.go @@ -0,0 +1,91 @@ +package server + +import ( + "context" + "sync" + "time" + + "github.com/daeuniverse/softwind/protocol/juicity" +) + +type inFlightKey = [juicity.UnderlaySaltLen]byte + +type ContextCancel struct { + Ctx context.Context + Cancel func() +} +type InFlightUnderlayKey struct { + ttl time.Duration + mu sync.Mutex + m map[inFlightKey]*juicity.UnderlayAuth + notify map[inFlightKey]*ContextCancel +} + +func NewInFlightUnderlayKey(ttl time.Duration) *InFlightUnderlayKey { + return &InFlightUnderlayKey{ + ttl: ttl, + mu: sync.Mutex{}, + m: make(map[inFlightKey]*juicity.UnderlayAuth, 64), + notify: make(map[inFlightKey]*ContextCancel, 64), + } +} + +func (i *InFlightUnderlayKey) Evict(k [juicity.UnderlaySaltLen]byte) *juicity.UnderlayAuth { + i.mu.Lock() + cc, ok := i.notify[k] + if !ok { + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(i.ttl)) + cc = &ContextCancel{ + Ctx: ctx, + Cancel: cancel, + } + i.notify[k] = cc + i.mu.Unlock() + _ = context.AfterFunc(ctx, func() { + i.mu.Lock() + defer i.mu.Unlock() + if i.notify[k] == cc { + delete(i.notify, k) + } + }) + <-cc.Ctx.Done() + i.mu.Lock() + defer i.mu.Unlock() + } else { + delete(i.notify, k) + defer cc.Cancel() + defer i.mu.Unlock() + } + auth, ok := i.m[k] + if !ok { + return nil + } + delete(i.m, k) + return auth +} + +func (i *InFlightUnderlayKey) Store(k [juicity.UnderlaySaltLen]byte, auth *juicity.UnderlayAuth) { + i.mu.Lock() + defer i.mu.Unlock() + cc, ok := i.notify[k] + if !ok { + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(i.ttl)) + cc = &ContextCancel{ + Ctx: ctx, + Cancel: cancel, + } + i.notify[k] = cc + i.m[k] = auth + _ = context.AfterFunc(ctx, func() { + i.mu.Lock() + defer i.mu.Unlock() + if i.notify[k] == cc { + delete(i.notify, k) + } + }) + } else { + i.m[k] = auth + delete(i.notify, k) + cc.Cancel() + } +} diff --git a/server/server.go b/server/server.go index b5a27dd..b03f6a0 100644 --- a/server/server.go +++ b/server/server.go @@ -18,10 +18,13 @@ import ( "github.com/juicity/juicity/pkg/log" "github.com/daeuniverse/outbound/dialer" + "github.com/daeuniverse/softwind/ciphers" "github.com/daeuniverse/softwind/netproxy" + "github.com/daeuniverse/softwind/pkg/fastrand" "github.com/daeuniverse/softwind/pool" "github.com/daeuniverse/softwind/protocol/direct" "github.com/daeuniverse/softwind/protocol/juicity" + "github.com/daeuniverse/softwind/protocol/shadowsocks" "github.com/daeuniverse/softwind/protocol/tuic" "github.com/daeuniverse/softwind/protocol/tuic/common" "github.com/google/uuid" @@ -31,23 +34,26 @@ import ( const ( AuthenticateTimeout = 10 * time.Second AcceptTimeout = AuthenticateTimeout + inFlightUnderlayTtl = AuthenticateTimeout ) var ( ErrUnexpectedVersion = fmt.Errorf("unexpected version") ErrUnexpectedCmdType = fmt.Errorf("unexpected cmd type") ErrAuthenticationFailed = fmt.Errorf("authentication failed") + ErrDisabledTrafficType = fmt.Errorf("disabled traffic type") ) type Options struct { - Logger *log.Logger - Users map[string]string - Certificate string - PrivateKey string - CongestionControl string - Fwmark int - SendThrough string - DialerLink string + Logger *log.Logger + Users map[string]string + Certificate string + PrivateKey string + CongestionControl string + Fwmark int + SendThrough string + DialerLink string + DisableOutboundUdp443 bool } type Server struct { @@ -60,6 +66,9 @@ type Server struct { cwnd int users map[uuid.UUID]string fwmark int + disableOutboundUdp443 bool + inFlightUnderlayKey *InFlightUnderlayKey + udpEndpointPool *UdpEndpointPool } func New(opts *Options) (*Server, error) { @@ -104,29 +113,34 @@ func New(opts *Options) (*Server, error) { Str("addr", property.Address). Msg("Dial use given dialer") } + return &Server{ - logger: opts.Logger, - relay: relay.NewRelay(opts.Logger), - dialer: &netproxy.ContextDialerConverter{ - Dialer: d, - }, - tlsConfig: &tls.Config{ - NextProtos: []string{"h3"}, // h3 only. - MinVersion: tls.VersionTLS13, - Certificates: []tls.Certificate{cert}, - }, + logger: opts.Logger, + relay: relay.NewRelay(opts.Logger), + dialer: &netproxy.ContextDialerConverter{Dialer: d}, + tlsConfig: &tls.Config{NextProtos: []string{"h3"}, MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{cert}}, maxOpenIncomingStreams: 100, congestionControl: opts.CongestionControl, cwnd: 10, users: users, fwmark: opts.Fwmark, + disableOutboundUdp443: opts.DisableOutboundUdp443, + inFlightUnderlayKey: NewInFlightUnderlayKey(inFlightUnderlayTtl), + udpEndpointPool: NewUdpEndpointPool(), }, nil } func (s *Server) Serve(addr string) (err error) { quicMaxOpenIncomingStreams := int64(s.maxOpenIncomingStreams) - listener, err := quic.ListenAddr(addr, s.tlsConfig, &quic.Config{ + pktConn, err := net.ListenPacket("udp", addr) + if err != nil { + return err + } + transport := quic.Transport{ + Conn: pktConn, + } + listener, err := transport.Listen(s.tlsConfig, &quic.Config{ InitialStreamReceiveWindow: common.InitialStreamReceiveWindow, MaxStreamReceiveWindow: common.MaxStreamReceiveWindow, InitialConnectionReceiveWindow: common.InitialConnectionReceiveWindow, @@ -141,6 +155,29 @@ func (s *Server) Serve(addr string) (err error) { if err != nil { return err } + go func() { + buf := pool.GetFullCap(consts.EthernetMtu) + defer buf.Put() + for { + n, addr, err := transport.ReadNonQUICPacket(context.Background(), buf) + if err != nil { + s.logger.Error(). + Err(err). + Send() + return + } + newBuf := pool.Get(n) + copy(newBuf, buf) + go func(transport *quic.Transport, buf pool.PB, ulAddr *net.UDPAddr) { + defer buf.Put() + if err := s.handleNonQuicPacket(transport, buf, ulAddr); err != nil { + s.logger.Info(). + Err(err). + Send() + } + }(&transport, newBuf, addr.(*net.UDPAddr)) + } + }() for { conn, err := listener.Accept(context.Background()) if err != nil { @@ -160,6 +197,81 @@ func (s *Server) Serve(addr string) (err error) { } } +func (s *Server) handleNonQuicPacket(transport *quic.Transport, buf []byte, ulAddr *net.UDPAddr) (err error) { + if len(buf) < juicity.CipherConf.SaltLen { + return fmt.Errorf("insuffient [underlay] data: len %v", len(buf)) + } + lAddr := ulAddr.AddrPort() + // source ip/port -> dst mapping. + endpoint, isNew, err := s.udpEndpointPool.GetOrCreate(lAddr, &UdpEndpointOptions{ + Handler: func(data []byte, from netip.AddrPort, metadata any) error { + masterKey := metadata.([]byte) + salt := pool.Get(juicity.CipherConf.SaltLen) + defer salt.Put() + _, _ = fastrand.Read(salt) + salt[0] = 0 + salt[1] = 0 + buf, err := shadowsocks.EncryptUDPFromPool(&shadowsocks.Key{ + CipherConf: juicity.CipherConf, + MasterKey: masterKey, + }, data, salt, ciphers.JuicityReusedInfo) + if err != nil { + return err + } + defer buf.Put() + _, err = transport.WriteTo(buf, ulAddr) + return err + }, + NatTimeout: 0, + GetDialOption: func() (*DialOption, error) { + iv := buf[:juicity.CipherConf.SaltLen] + auth := s.inFlightUnderlayKey.Evict(inFlightKey(iv)) + if auth == nil { + return nil, fmt.Errorf("[underlay] auth fail") + } + if s.disableOutboundUdp443 && auth.Metadata.Port == 443 && auth.Metadata.Network == "udp" { + return nil, ErrDisabledTrafficType + } + return &DialOption{ + Target: net.JoinHostPort(auth.Metadata.Hostname, strconv.Itoa(int(auth.Metadata.Port))), + Dialer: s.dialer, + Metadata: auth.Psk, + }, nil + }, + }) + if err != nil { + if errors.Is(err, ErrDisabledTrafficType) { + s.logger.Debug(). + Str("target", endpoint.DialTarget). + Str("source", lAddr.String()). + Msg("juicity blocked an [underlay] request") + return nil + } + return err + } + if !isNew { + masterKey := endpoint.Metadata.([]byte) + decrypted, err := shadowsocks.DecryptUDPFromPool(&shadowsocks.Key{ + CipherConf: juicity.CipherConf, + MasterKey: masterKey, + }, buf, ciphers.JuicityReusedInfo) + if err != nil { + return err + } + defer decrypted.Put() + buf = decrypted + } else { + s.logger.Debug(). + Str("target", endpoint.DialTarget). + Str("source", lAddr.String()). + Msg("juicity performed an [underlay] request") + } + if _, err = endpoint.WriteTo(buf, endpoint.DialTarget); err != nil { + return err + } + return nil +} + func (s *Server) handleConn(conn quic.Connection) (err error) { common.SetCongestionController(conn, s.congestionControl, s.cwnd) ctx, cancel := context.WithCancel(context.Background()) @@ -167,7 +279,11 @@ func (s *Server) handleConn(conn quic.Connection) (err error) { authCtx, authDone := context.WithTimeout(ctx, AuthenticateTimeout) defer authDone() go func() { - if _, err := s.handleAuth(authCtx, conn); err != nil { + var ( + uniStream quic.ReceiveStream + err error + ) + if _, uniStream, err = s.handleConnAuth(authCtx, conn); err != nil { s.logger.Warn(). Err(err). Msg("handleAuth") @@ -176,6 +292,25 @@ func (s *Server) handleConn(conn quic.Connection) (err error) { return } authDone() + for { + select { + case <-ctx.Done(): + return + default: + } + if err = s.handleUnderlayAuth(ctx, uniStream); err != nil { + if errors.Is(err, io.EOF) { + s.logger.Debug(). + Err(err). + Msg("handleUnderlayAuth: maybe is an old client?") + return + } + s.logger.Error(). + Err(err). + Msg("handleUnderlayAuth") + return + } + } }() for { stream, err := conn.AcceptStream(ctx) @@ -214,7 +349,7 @@ func (s *Server) handleStream(ctx context.Context, authCtx context.Context, conn s.logger.Debug(). Str("target", target). Str("source", source). - Msg("juicity received a tcp request") + Msg("juicity received a [tcp] request") magicNetwork := netproxy.MagicNetwork{ Network: "tcp", Mark: uint32(s.fwmark), @@ -241,7 +376,13 @@ func (s *Server) handleStream(ctx context.Context, authCtx context.Context, conn return fmt.Errorf("relay tcp error: %w", err) } case "udp": - // can dial any target + if s.disableOutboundUdp443 && mdata.Port == 443 { + s.logger.Debug(). + Str("target", net.JoinHostPort(mdata.Hostname, strconv.Itoa(int(mdata.Port)))). + Str("source", source). + Msg("juicity blocked a [udp] request") + return nil + } lConn := &juicity.PacketConn{Conn: lConn} buf := pool.GetFullCap(consts.EthernetMtu) defer pool.Put(buf) @@ -261,7 +402,7 @@ func (s *Server) handleStream(ctx context.Context, authCtx context.Context, conn s.logger.Debug(). Str("target", addr.String()). Str("source", source). - Msg("juicity received a udp request") + Msg("juicity received a [udp] request") if err != nil { var netErr net.Error if errors.As(err, &netErr) && netErr.Timeout() { @@ -297,45 +438,56 @@ func (s *Server) handleStream(ctx context.Context, authCtx context.Context, conn return nil } -func (s *Server) handleAuth(ctx context.Context, conn quic.Connection) (uuid *uuid.UUID, err error) { - uniStream, err := conn.AcceptUniStream(ctx) +func (s *Server) handleConnAuth(authCtx context.Context, conn quic.Connection) (uuid *uuid.UUID, uniStream quic.ReceiveStream, err error) { + uniStream, err = conn.AcceptUniStream(authCtx) if err != nil { - return nil, err + return nil, nil, err } r := bufio.NewReader(uniStream) v, err := r.Peek(1) if err != nil { - return nil, err + return nil, nil, err } switch v[0] { case juicity.Version0: commandHead, err := tuic.ReadCommandHead(r) if err != nil { - return nil, fmt.Errorf("ReadCommandHead: %w", err) + return nil, nil, fmt.Errorf("ReadCommandHead: %w", err) } switch commandHead.TYPE { case tuic.AuthenticateType: authenticate, err := tuic.ReadAuthenticateWithHead(commandHead, r) if err != nil { - return nil, fmt.Errorf("ReadAuthenticateWithHead: %w", err) + return nil, nil, fmt.Errorf("ReadAuthenticateWithHead: %w", err) } var token [32]byte if password, ok := s.users[authenticate.UUID]; ok { token, err = tuic.GenToken(conn.ConnectionState(), authenticate.UUID, password) if err != nil { - return nil, fmt.Errorf("GenToken: %w", err) + return nil, nil, fmt.Errorf("GenToken: %w", err) } if token == authenticate.TOKEN { - return &authenticate.UUID, nil + return &authenticate.UUID, uniStream, nil } else { _ = conn.CloseWithError(tuic.AuthenticationFailed, "") } } - return nil, fmt.Errorf("%w: %v", ErrAuthenticationFailed, authenticate.UUID) + return nil, nil, fmt.Errorf("%w: %v", ErrAuthenticationFailed, authenticate.UUID) default: - return nil, fmt.Errorf("%w: %v", ErrUnexpectedCmdType, commandHead.TYPE) + return nil, nil, fmt.Errorf("%w: %v", ErrUnexpectedCmdType, commandHead.TYPE) } default: - return nil, fmt.Errorf("%w: %v", ErrUnexpectedVersion, v) + return nil, nil, fmt.Errorf("%w: %v", ErrUnexpectedVersion, v) + } +} +func (s *Server) handleUnderlayAuth(ctx context.Context, uniStream quic.ReceiveStream) (err error) { + // Read an auth from the connection. + var auth juicity.UnderlayAuth + if _, err = auth.Unpack(uniStream); err != nil { + return err } + + // Store the key. + s.inFlightUnderlayKey.Store(inFlightKey(auth.IV), &auth) + return nil } diff --git a/server/udp_endpoint.go b/server/udp_endpoint.go index 166c971..ea1fd0c 100644 --- a/server/udp_endpoint.go +++ b/server/udp_endpoint.go @@ -12,7 +12,7 @@ import ( "github.com/juicity/juicity/common/consts" ) -type UdpHandler func(data []byte, from netip.AddrPort) error +type UdpHandler func(data []byte, from netip.AddrPort, metadata any) error type UdpEndpoint struct { conn netproxy.PacketConn @@ -22,7 +22,9 @@ type UdpEndpoint struct { handler UdpHandler NatTimeout time.Duration - Dialer netproxy.Dialer + Dialer netproxy.Dialer + Metadata any + DialTarget string } func (ue *UdpEndpoint) start() { @@ -36,7 +38,7 @@ func (ue *UdpEndpoint) start() { ue.mu.Lock() ue.deadlineTimer.Reset(ue.NatTimeout) ue.mu.Unlock() - if err = ue.handler(buf[:n], from); err != nil { + if err = ue.handler(buf[:n], from, ue.Metadata); err != nil { break } } @@ -59,8 +61,9 @@ func (ue *UdpEndpoint) Close() error { } type DialOption struct { - Target string - Dialer netproxy.Dialer + Target string + Dialer netproxy.Dialer + Metadata any } // UdpEndpointPool is a full-cone udp conn pool @@ -132,6 +135,7 @@ begin: } ue := &UdpEndpoint{ conn: udpConn.(netproxy.PacketConn), + mu: sync.Mutex{}, deadlineTimer: time.AfterFunc(createOption.NatTimeout, func() { if ue, ok := p.pool.LoadAndDelete(lAddr); ok { ue.(*UdpEndpoint).Close() @@ -140,6 +144,8 @@ begin: handler: createOption.Handler, NatTimeout: createOption.NatTimeout, Dialer: dialOption.Dialer, + Metadata: dialOption.Metadata, + DialTarget: dialOption.Target, } _ue = ue p.pool.Store(lAddr, ue)