Skip to content

net: IPv6 zones and strict literal parsing - #6

Open
david-yu wants to merge 2 commits into
ipv6/04-dual-stack-listenersfrom
ipv6/05-zones-literals
Open

david-yu wants to merge 2 commits into
ipv6/04-dual-stack-listenersfrom
ipv6/05-zones-literals

Conversation

@david-yu

@david-yu david-yu commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Part 5 of 10 of the IPv6 series, split out of #1 and rebased on current scylladb/seastar master. Based on #5, so the diff here is only this PR's 2 commits.

Why

Zone support came with 3e479dc9e (scylladb/seastar#692), and link-local destinations get a zone picked automatically since 75e189c6b. Two things around it are still off:

  • inet_address::invalid_scope (0xffffffff), an in-memory "no zone" marker, was written into sin6_scope_id, and the kernel's 0 came back as zone 0, so every accepted IPv6 peer printed as [addr%0]:port.
  • A %zone that matched no local interface was dropped silently, a zone was accepted on an IPv4 literal, and [::1]:70000 became port 4464.

What

  • net: keep IPv6 zone 0 and the invalid_scope marker apart (6dadac58b) — translate at the sockaddr_in6 boundary; also fixes the inet_pton result check in the /proc/net/ipv6_route parser.
  • net: parse IPv6 literals strictly: zones must exist or be numeric, ports must fit (5d7764650) — RFC 4007 §11: an unknown interface name makes the literal invalid, a numeric zone is kept as given; the port is range-checked.

Equality still ignores the zone, as decided in scylladb/seastar#704; the header now says so.

Behaviour change

before after
[::1]:70000 port 4464 std::invalid_argument
fe80::1%no-such-iface address kept, zone dropped rejected

Testing

On the core-aws VM (Ubuntu 24.04, GCC 14, C++23, RelWithDebInfo, GnuTLS and OpenSSL in one tree), this PR's head builds and passes dns, ipv6, socket, rpc, httpd, httpd_openssl, tls, tls_openssl, network_interface, websocket and unix_domain with SEASTAR_TEST_REQUIRE_IPV6=1, so none of their IPv6 cases skipped.

Stack

  1. tests, ci: fail instead of skip when IPv6 is expected #2 tests, ci: fail instead of skip when IPv6 is expected
  2. dns: reach nameservers over IPv6 #3 dns: reach nameservers over IPv6
  3. dns: literals for any family, c-ares error text, link-local nameservers #4 dns: literals for any family, c-ares error text, link-local nameservers
  4. net, http, rpc: dual-stack listeners (IPV6_V6ONLY, IPv4-mapped peers) #5 net, http, rpc: dual-stack listeners (IPV6_V6ONLY, IPv4-mapped peers)
  5. net: IPv6 zones and strict literal parsing #6 net: IPv6 zones and strict literal parsing ← this PR
  6. tls: do not send IP literals in the SNI extension #7 tls: do not send IP literals in the SNI extension
  7. tls: verify server_name on OpenSSL, opt-in #8 tls: verify server_name on OpenSSL, opt-in
  8. net: ipv6_addr equality and hash, ipv4_addr(socket_address) throws instead of terminating #9 net: ipv6_addr equality and hash, ipv4_addr(socket_address) throws instead of terminating
  9. net: listen on an unspecified address binds any family #10 net: listen on an unspecified address binds any family
  10. http: build the Host header from an address in authority form #11 http: build the Host header from an address in authority form

inet_address::invalid_scope (0xffffffff), from the zone support added in
3e479dc, is an in-memory "no zone" marker, but socket_address wrote it
straight into sin6_scope_id, and addr() copied the kernel's
sin6_scope_id (0 for the common no-zone case) straight back into _scope.
Every accepted IPv6 peer therefore printed as [addr%0]:port and reported
scope() == 0, while connect()/sendto() were handed a bogus zone for
link-local destinations.

Translate at the boundary: write 0 for invalid_scope, read 0 as
invalid_scope, and key the link-local zone lookup in
resolve_outgoing_address on sin6_scope_id == 0. Also fix the inet_pton
result check in the /proc/net/ipv6_route parser (0 means 'not an
address', not < 0), pin the sin_port/sin6_port aliasing that port()
relies on with a static_assert, and document that socket_address
equality deliberately ignores the zone (scylladb#704).
…rts must fit

inet_address::parse_numerical dropped a %zone that matched no interface
on the box, leaving the literal silently unscoped, and accepted a zone on
an IPv4 literal. Follow RFC 4007 §11 instead: an interface name that
matches nothing makes the literal invalid, and a numeric zone is kept as
given so literals written for another host survive.

ipv6_addr(const std::string&) converted the port with std::stoul and a
bare uint16_t cast, so "[::1]:70000" became port 4464. Parse the
bracketed form explicitly, range-check the port and reject stray text
after the bracket; an unbracketed string is the address alone ("::1:9092"
is a valid address, so it cannot carry a port). Zoned literals still
throw: ipv6_addr has no field for a zone, which the header now says.
@david-yu
david-yu force-pushed the ipv6/05-zones-literals branch from bc8140f to 5d77646 Compare September 25, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant