-
Type:
Task
-
Resolution: Unresolved
-
Priority:
Minor - P4
-
None
-
Affects Version/s: None
-
Component/s: None
-
None
-
None
-
Java Drivers
-
None
-
None
-
None
-
None
-
None
-
None
SocksSocket.sendConnect() writes DOMAIN_NAME.getAddressTypeNumber() (0x03) into the SOCKS5 ATYP field when the target host is an IPv6 literal. Per RFC 1928 §4, the ATYP byte for an IPv6 address must be 0x04.
The remainder of the IPv6 payload (16-byte address, 2-byte port) is laid out correctly for ATYP=0x04 but is mislabeled, producing a malformed CONNECT request on the wire.
driver-core/src/main/com/mongodb/internal/connection/SocksSocket.java, the IP_V6 branch of the switch in sendConnect(...) (currently around line 197-201): case IP_V6: bufferSent[3] = DOMAIN_NAME.getAddressTypeNumber(); // BUG: writes 0x03; RFC 1928 requires 0x04 for IPv6 System.arraycopy(ipAddress, 0, bufferSent, 4, ipAddress.length); addPort(bufferSent, 4 + ipAddress.length, port); break; For context, the AddressType enum (around line 421-423) already defines the correct value: IP_V6(0x04, 16). The bug is purely a wrong reference at the assignment site. Impact A strict RFC-1928 proxy receiving this request will treat byte 4 (the first byte of the IPv6 address) as a 1-byte DOMAINNAME length, then read that many bytes as the hostname, then 2 bytes as the port. The bytes that follow are interpreted as garbage. The CONNECT either fails outright or is routed to an unintended destination. The bug only fires when: 1. A SOCKS5 proxy is configured (ProxySettings.isProxyEnabled() == true). 2. The target MongoDB host is an IPv6 literal (e.g. [::1], fe80::1) — i.e. SocksSocket.isDomainName(host) returns false and determineAddressType returns IP_V6. Hostnames and IPv4 literals are unaffected. The lack of field reports likely reflects that most production SOCKS5 deployments target MongoDB via hostnames, and that some proxy implementations are lenient about parsing. Steps to reproduce 1. Configure a SOCKS5 proxy in MongoClientSettings via applyToSocketSettings(...).applyToProxySettings(...). 2. Construct a ConnectionString whose host is an IPv6 literal (e.g. mongodb://[fe80::1]:27017/?proxyHost=...). 3. Capture the bytes written to the proxy on the CONNECT exchange (Wireshark or a custom test server). 4. Observe byte 3 of the CONNECT request is 0x03 (DOMAINNAME) instead of 0x04 (IP V6). Proposed fix One-line correction at the assignment site: case IP_V6: bufferSent[3] = IP_V6.getAddressTypeNumber(); System.arraycopy(ipAddress, 0, bufferSent, 4, ipAddress.length); addPort(bufferSent, 4 + ipAddress.length, port); break; No buffer-size changes are required — createBuffer(IP_V6, ...) already returns 6 + IP_V6.getLength() = 22 bytes, matching the RFC-1928 IPv6 CONNECT request layout (VER | CMD | RSV | ATYP | 16-byte address | 2-byte port).
Regression test Add a unit test in SocksSocketTest (or a new SocksSocketRequestEncodingTest) that captures the bytes the client writes during CONNECT. The existing connectWithMiniServer helper discards client bytes via a drain loop and only writes canned reply bytes; it does not currently expose what the client sent. Two options: 1. Extend connectWithMiniServer to thread a ByteArrayOutputStream (or similar capture) through which the drain loop tees client bytes, then expose it via a return value or out-parameter. 2. Add a sibling helper connectWithMiniServerCapturingRequest(...) that builds on the existing scaffold but records the CONNECT bytes specifically. Test outline (using option 1): @Test void sendConnectUsesIpV6AtypForIpv6LiteralTarget() throws Exception { byte[] successReply = { 0x05, 0x00, // negotiation OK, no auth 0x05, 0x00, 0x00, 0x04, // success, ATYP=IP_V6 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 0x00, 0x00 // 16-byte bnd.addr + 2-byte bnd.port }; // Override TARGET for this test: InetSocketAddress ipv6Target = InetSocketAddress.createUnresolved("::1", 27017); ClientRequest captured = connectWithMiniServerCapturingRequest(successReply, false, ipv6Target); // First request: client method-selection (3 bytes: VER, NMETHODS, METHOD). Skip past it. // Second request: CONNECT (22 bytes for IPv6 literal target). byte[] connectBytes = captured.getConnectRequestBytes(); assertEquals(22, connectBytes.length); assertEquals(0x05, connectBytes[0]); // SOCKS_VERSION assertEquals(SocksCommand.CONNECT.getCommandNumber(), connectBytes[1]); assertEquals(0x00, connectBytes[2]); // RESERVED assertEquals(0x04, connectBytes[3]); // ATYP = IP V6 (RFC 1928) // bytes [4..19] are the 16-byte IPv6 address; bytes [20..21] are the port. } Add a parallel sendConnectUsesIpV4AtypForIpv4LiteralTarget test to lock in the IPv4 path so it can't regress in the same direction. Risks / migration None. The fix flips the byte from 0x03 to 0x04 for a code path that is currently broken; any caller that happens to work today (because their proxy is lenient and misinterprets the request consistently) will work more correctly after the fix. No public API change, no on-the-wire compatibility surface to negotiate. References - RFC 1928 §4 (Request format), ATYP field values - Original SOCKS5 introduction: PR #1180 / JAVA-1547 - Discovered during review of PR #1968 (JAVA-6194), thread r3270288433