v1: accept plain IPv4 addresses in TCP6 headers#167
Conversation
|
@pires can you take a look at this PR and let me know what you think? |
|
Hi @cmarker-gl sorry but I was AFK past week. I now have some work to attend to, and will come back to you soon. |
pires
left a comment
There was a problem hiding this comment.
First of all, thank you for your contribution. And then thank you for your patience, I have been immensely busy.
Please, fix the linter complain(s) and add tests for the following scenarios:
- IPv4 src, IPv4 dst in
TCP6: PROXY TCP6 192.0.2.1 192.0.2.2 1234 5678 - IPv4 src, IPv6 dst in TCP6: the reversed mixed case
The remote address is logged intentionally in this example server.
- Add spec-departure and lossy-conversion comments per reviewer - Refactor Is4() branch to use netip.AddrFrom16 for consistency - Remove duplicate test fixtures (TCP4 simple, TCP6 loopback) - Add TCP6 invalid address test case - Add IPv4 src + IPv4 dst and IPv4 src + IPv6 dst test scenarios
|
Thanks for the review! I've pushed two commits addressing your feedback: Linter fix ( Review feedback ( |
pires
left a comment
There was a problem hiding this comment.
Thanks a ton for your contribution 🙇🏻
|
Appreciate the merge! Just need a version tag and release pushed to wrap this up. Let me know if you need anything from my end. |
The standard `net.IP` implementation can not represent IPv4-mapped IPv6 addresses, `String()` on IPv4-mapped-IPv6 addresses will return IPv4 addresses, which may lead to formatting issues in v1 headers [1]. Migrate from `net.IP` to `netip.Addr` for IP state management to ensure `String()` outputs strictly follow the expected version format. Link: [1]: pires#167
The standard `net.IP` implementation can not represent IPv4-mapped IPv6 addresses, `String()` on IPv4-mapped-IPv6 addresses will return IPv4 addresses, which may lead to formatting issues in v1 headers [1]. Migrate from `net.IP` to `netip.Addr` for IP state management to ensure `String()` outputs strictly follow the expected version format. Link: [1]: pires#167 Signed-off-by: Coia Prant <coiaprant@gmail.com>
Problem
When proxying SSH connections through a chain involving nginx's OSS stream module, PROXY protocol v1 headers with mixed address families are emitted and currently rejected by this library.
The chain looks like:
nginx's OSS stream module (
proxy_protocol on) always re-encodes as PROXY protocol v1. When the original client is IPv6 but the backend is IPv4, nginx emits:This is technically outside the strict spec (which says both addresses in a
TCP6header should be in IPv6 format), but it is the real-world output of a widely deployed proxy and there is no workaround available in nginx OSS.go-proxyproto was returning
proxyproto: invalid addressfor these headers, causing the connection to fail. IPv4-only connections worked fine.Root Cause
parseV1IPAddressinv1.gohandledTCPv6by accepting addresses whereaddr.Is6()oraddr.Is4In6()(i.e.::ffff:x.x.x.xnotation). A plain IPv4 address like192.0.2.1parses asaddr.Is4()-- neither condition matched, so it fell through toErrInvalidAddress.Fix
When parsing a
TCPv6header and the address is a plain IPv4, promote it to a 16-byte IPv4-mapped-IPv6 representation vianetip.AddrFrom16(addr.As16()). This is consistent with hownet.IP.To16()works elsewhere in the library and produces a validnet.IPthat callers can use normally.Note: this is a lossy conversion -- round-trip serialization will render the address as
::ffff:x.x.x.xrather than the originalx.x.x.x.Tests
"TCP6 with invalid address"test case (PROXY TCP6 not-an-ip ::1 1234 5678)PROXY TCP6 192.0.2.1 192.0.2.2 1234 5678-- IPv4 src, IPv4 dstPROXY TCP6 2001:db8::1 192.0.2.1 51512 22-- IPv6 src, IPv4 dst (the nginx mixed case)PROXY TCP6 192.0.2.1 2001:db8::1 51512 22-- IPv4 src, IPv6 dstskipWrite: truesince round-trip serialization changes the address representationfixtureTCP4V1andfixtureTCP6V1Notes