From a53a0267e4a621137755e6f269ca24ae503f828f Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Thu, 11 Jun 2026 13:50:50 +0000 Subject: [PATCH] hotfix(socks5): support domain name udp association --- internal/socks5/socks5.go | 8 ++--- internal/socks5/socks5_test.go | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/internal/socks5/socks5.go b/internal/socks5/socks5.go index c7595629..bdde400b 100644 --- a/internal/socks5/socks5.go +++ b/internal/socks5/socks5.go @@ -256,10 +256,10 @@ func udpAssociateExpectedClientEndpoint(request request) (expectedAddrPort netip } return netip.AddrPortFrom(netip.Addr{}, request.port), nil case domainName: - if request.destination != "" || request.port != 0 { - return netip.AddrPort{}, fmt.Errorf("domain name is not supported for UDP associate destination") - } - return netip.AddrPort{}, nil + // For UDP associate, client endpoint matching is based on observed UDP source + // address/port. A hostname is not directly matchable at this stage, so we + // ignore the domain name request destination entirely. + return netip.AddrPortFrom(netip.Addr{}, request.port), nil default: return netip.AddrPort{}, fmt.Errorf("address type %d is not supported", request.addressType) } diff --git a/internal/socks5/socks5_test.go b/internal/socks5/socks5_test.go index 3844805c..0bc5487a 100644 --- a/internal/socks5/socks5_test.go +++ b/internal/socks5/socks5_test.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net" + "net/netip" "strconv" "strings" "testing" @@ -703,6 +704,70 @@ func Test_decodeRequest(t *testing.T) { } } +func Test_udpAssociateExpectedClientEndpoint(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + request request + expected netip.AddrPort + expectedErr string + }{ + "ipv4_endpoint": { + request: request{ + addressType: ipv4, + destination: "192.0.2.10", + port: 5555, + }, + expected: netip.MustParseAddrPort("192.0.2.10:5555"), + }, + "ipv4_unspecified_address": { + request: request{ + addressType: ipv4, + destination: "0.0.0.0", + port: 6000, + }, + expected: netip.AddrPortFrom(netip.Addr{}, 6000), + }, + "domain_name_with_port": { + request: request{ + addressType: domainName, + destination: "client.example", + port: 7000, + }, + expected: netip.AddrPortFrom(netip.Addr{}, 7000), + }, + "domain_name_without_port": { + request: request{ + addressType: domainName, + destination: "client.example", + }, + expected: netip.AddrPort{}, + }, + "unsupported_address_type": { + request: request{ + addressType: 255, + }, + expectedErr: "address type 255 is not supported", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + result, err := udpAssociateExpectedClientEndpoint(testCase.request) + + if testCase.expectedErr != "" { + assert.ErrorContains(t, err, testCase.expectedErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, testCase.expected, result) + }) + } +} + func Test_verifyFirstNegotiation(t *testing.T) { t.Parallel() testCases := map[string]struct {