Parse bracketed IPv6 authorities - #155
Open
AYastrebov wants to merge 2 commits into
Open
AYastrebov wants to merge 2 commits into
AYastrebov wants to merge 2 commits into
Conversation
RFC 3986 wraps an IPv6 literal in brackets so its colons cannot be read as the port separator, and that is the form both configs and HTTP clients use: [2001:db8::1]:443. Address::from cleared possible_ipv4 and possible_ipv6 on the leading '[' but left possible_hostname set, so the whole bracketed string became a hostname and went to the resolver, which answered "Name or service not known". An IPv6 outbound was unreachable and the message said nothing about why. The unbracketed form worked, and rule masks already accepted brackets (NetLocationMask::from), so this was also an inconsistency between the two notations. Brackets denote an IP literal and nothing else - a hostname cannot contain them - so a bracketed string that is not an IPv6 address is now an error rather than a name to look up. Found while testing an outbound against a dual-stack server: the same address worked without brackets and failed with them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both request forms split the authority on the first ':'. For the bracketed IPv6 form an HTTP client sends - CONNECT [2001:db8::1]:443 - that colon is inside the address, so the host came out as "[2001" and the port failed to parse. Every IPv6 literal was unreachable through the HTTP inbound. Both sites now use NetLocation::from_str, which knows where the address ends: None for CONNECT, which has no default port, and Some(80) for an absolute http:// URL, which may leave the port out. That also drops the hand-rolled bounds check the CONNECT path carried. The file had no tests for request parsing. There are now five, driving the handler over a loopback pair and asserting the destination it picks; they cover both request forms, both address kinds, the default port and the missing-port rejection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AYastrebov
force-pushed
the
fix/bracketed-ipv6
branch
from
August 24, 2026 08:40
1f12a72 to
3f71b48
Compare
AYastrebov
marked this pull request as ready for review
August 24, 2026 08:46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A bracketed IPv6 authority is parsed as a hostname. On master:
Address::fromclassifies by scanning bytes, and a leading[is neither adigit, a hex letter,
:nor., so it clears the IPv4 and IPv6 flags and thestring falls through to the hostname branch. It then goes to the resolver,
which answers "Name or service not known".
[host]:portis the form RFC 3986defines and the form HTTP clients send, so an IPv6 outbound written the normal
way is unreachable, and the error says nothing about why.
The HTTP inbound has the same problem for a different reason: it splits the
CONNECT authority on the first
:, which for an IPv6 literal sits inside theaddress.
CONNECT [2001:db8::1]:443is refused, and so is a forward URL likeGET http://[2001:db8::1]/.Fix
address: parse the bracketed IPv6 formstrips the brackets inAddress::fromand parses the interior as an
Ipv6Addr. Brackets mean an IP literal andnothing else, so a bracketed string that is not an IPv6 address is now an error
rather than a name to look up:
[example.com],[1.2.3.4]and an unterminated[::1are all refused.NetLocation::from_strneeded no change, since itsrfind(':')already lands on the port separator once the address itself parses.http: accept a bracketed IPv6 authorityparses the CONNECT target and theforward-URL authority through
NetLocationinstead of splitting on the firstcolon.
Tests
Five in
address.rs: the bracketed form (compressed, full eight-group andIPv4-mapped), the refusals above, a bracketed authority with a port, a
bracketed port range, and unbracketed addresses still parsing as they did. Two
in
http_handler.rsdrive a request through the handler, one for CONNECT andone for a forward URL.
cargo testpasses (783),cargo fmt --checkis clean, andcargo clippyreports nothing new.