diff --git a/handler/socks/v5/connect.go b/handler/socks/v5/connect.go index 2037a2dc..0f2160c1 100644 --- a/handler/socks/v5/connect.go +++ b/handler/socks/v5/connect.go @@ -82,7 +82,10 @@ func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, networ ro.SrcAddr = cc.LocalAddr().String() ro.DstAddr = cc.RemoteAddr().String() - resp := gosocks5.NewReply(gosocks5.Succeeded, nil) + // Report the actual bound address (BND.ADDR) per RFC 1928 §6. + bndAddr := gosocks5.Addr{} + bndAddr.ParseFrom(cc.LocalAddr().String()) + resp := gosocks5.NewReply(gosocks5.Succeeded, &bndAddr) log.Trace(resp) if err := resp.Write(conn); err != nil { log.Error(err) diff --git a/handler/socks/v5/selector.go b/handler/socks/v5/selector.go index 700416df..5b0658d6 100644 --- a/handler/socks/v5/selector.go +++ b/handler/socks/v5/selector.go @@ -27,25 +27,42 @@ func (selector *serverSelector) Methods() []uint8 { func (s *serverSelector) Select(methods ...uint8) (method uint8) { s.logger.Debugf("%d %d %v", gosocks5.Ver5, len(methods), methods) - method = gosocks5.MethodNoAuth + + // Determine which methods the client offered. + var hasNoAuth, hasUserPass, hasTLS, hasTLSAuth bool for _, m := range methods { - if m == socks.MethodTLS && !s.noTLS && s.TLSConfig != nil { - method = m - break + switch m { + case gosocks5.MethodNoAuth: + hasNoAuth = true + case gosocks5.MethodUserPass: + hasUserPass = true + case socks.MethodTLS: + hasTLS = true + case socks.MethodTLSAuth: + hasTLSAuth = true } } - // when Authenticator is set, auth is mandatory + // When Authenticator is set, authentication is mandatory. if s.Authenticator != nil { - if method == gosocks5.MethodNoAuth { - method = gosocks5.MethodUserPass + if hasTLSAuth && !s.noTLS && s.TLSConfig != nil { + return socks.MethodTLSAuth } - if method == socks.MethodTLS && !s.noTLS { - method = socks.MethodTLSAuth + if hasUserPass { + return gosocks5.MethodUserPass } + return gosocks5.MethodNoAcceptable } - return + // No authenticator — select the best unauthenticated method. + if hasTLS && !s.noTLS && s.TLSConfig != nil { + return socks.MethodTLS + } + if hasNoAuth { + return gosocks5.MethodNoAuth + } + + return gosocks5.MethodNoAcceptable } func (s *serverSelector) OnSelected(method uint8, conn net.Conn) (string, net.Conn, error) { diff --git a/handler/socks/v5/udp.go b/handler/socks/v5/udp.go index 2d66383a..f90decdc 100644 --- a/handler/socks/v5/udp.go +++ b/handler/socks/v5/udp.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "io" "net" "strconv" @@ -68,6 +69,30 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, network st }) ro.SrcAddr = cc.LocalAddr().String() + // Verify the upstream chain is reachable before replying Success, + // per RFC 1928 §7. + var buf bytes.Buffer + c, err := h.options.Router.Dial(ictx.ContextWithBuffer(ctx, &buf), network, "") // UDP association + ro.Route = buf.String() + if err != nil { + log.Error(err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + log.Trace(reply) + reply.Write(conn) + return err + } + defer c.Close() + + pc, ok := c.(net.PacketConn) + if !ok { + err := errors.New("socks5: wrong connection type") + log.Error(err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + log.Trace(reply) + reply.Write(conn) + return err + } + saddr := gosocks5.Addr{} saddr.ParseFrom(cc.LocalAddr().String()) @@ -84,23 +109,6 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, network st } log.Debugf("bind on %s OK", cc.LocalAddr()) - - // obtain a udp connection - var buf bytes.Buffer - c, err := h.options.Router.Dial(ictx.ContextWithBuffer(ctx, &buf), network, "") // UDP association - ro.Route = buf.String() - if err != nil { - log.Error(err) - return err - } - defer c.Close() - - pc, ok := c.(net.PacketConn) - if !ok { - err := errors.New("socks5: wrong connection type") - log.Error(err) - return err - } pc = metrics.WrapPacketConn(ro.Service, pc) { @@ -132,10 +140,16 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, network st } } + tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr) + if !ok { + err := fmt.Errorf("socks5 udp: unexpected remote address type %T", conn.RemoteAddr()) + log.Error(err) + return err + } pc1 := socks.UDPConn( &filteredPacketConn{ PacketConn: cc, - filterIP: conn.RemoteAddr().(*net.TCPAddr).IP, + filterIP: tcpAddr.IP, }, h.md.udpBufferSize) @@ -179,7 +193,11 @@ func (f *filteredPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error return } - if udpAddr := addr.(*net.UDPAddr); udpAddr.IP.Equal(f.filterIP) { + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + continue + } + if udpAddr.IP.Equal(f.filterIP) { return } } @@ -228,7 +246,10 @@ func (c *domainResolvePacketConn) WriteTo(b []byte, addr net.Addr) (int, error) if c.log != nil { c.log.Warnf("socks5 udp: dropping datagram to %s (DNS resolution failed)", host) } - return 0, nil + if err == nil { + err = fmt.Errorf("socks5 udp: DNS resolution failed for %s", host) + } + return 0, err } // Pick the first IPv4 address from the result set, falling back to