Experimental support for IPv6#7671
Conversation
|
@copilot build, run the tests, summarize failures and take them into account to apply a tentative fix. |
|
@achamayou I've opened a new pull request, #7732, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds initial end-to-end coverage for IPv6 loopback usage in the test suite, and updates host/node networking code paths to better handle IPv6 addresses (notably in TCP connection setup and certificate SAN generation).
Changes:
- Add an e2e test that starts a network with RPC interfaces bound to
::1and exercises common endpoints. - Adjust test infra to avoid binding the IPv4-only “client interface” used for partition simulation when the node RPC host is IPv6.
- Improve IPv6 handling in node certificate SAN construction and in host TCP client socket creation.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tests/infra/node.py |
Skips node_client_host binding when RPC host is IPv6 to avoid incompatible partition-simulation client interface. |
tests/e2e_logging.py |
Registers a new common_ipv6 sub-test using the logging sample app. |
tests/e2e_common_endpoints.py |
Adds run_ipv6() which forces nodes to bind to ::1 (skipping if IPv6 loopback is unavailable). |
src/node/node_state.h |
Strips brackets from IPv6 hosts when constructing default SANs; updates IPv6-related comment in is_ip(). |
src/host/tcp.h |
Defers client socket creation to use the resolved address family (IPv4 vs IPv6). |
Comments suppressed due to low confidence (1)
src/node/node_state.h:2347
- The is_ip() heuristic does not correctly detect general IPv6 literals: it only returns true when the final ':' component is purely numeric, so valid IPv6 addresses ending with hex letters (eg "2001:db8::a") will be treated as DNS names. This can produce incorrect SAN types in generated node certificates and break TLS validation when connecting by IP. Consider replacing this heuristic with a real IPv4/IPv6 parse (eg inet_pton / uv_inet_pton) after stripping brackets (and any zone ID if present).
bool is_ip(const std::string_view& hostname)
{
// IP address components are purely numeric. DNS names may be largely
// numeric, but at least the final component (TLD) must not be
// all-numeric. So this distinguishes "1.2.3.4" (an IP address) from
// "1.2.3.c4m" (a DNS name). "1.2.3." is invalid for either, and will
// throw. Handles IPv6 by splitting on ':' after splitting on '.'.
const auto final_component =
ccf::nonstd::split(ccf::nonstd::split(hostname, ".").back(), ":")
.back();
if (final_component.empty())
{
throw std::runtime_error(fmt::format(
"{} has a trailing period, is not a valid hostname", hostname));
}
return std::ranges::all_of(
final_component, [](char c) { return c >= '0' && c <= '9'; });
You can also share your feedback on Copilot code review. Take the survey.
Agent-Logs-Url: https://github.com/microsoft/CCF/sessions/025065b0-abc1-490d-9d50-efba3e4d7373 Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/CCF/sessions/025065b0-abc1-490d-9d50-efba3e4d7373 Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
Done in 1ae1a18. I ran local checks and reviewed CI failures:
Tentative fix applied in
|
|
@copilot add to the description of the PR mermaid flowcharts that show the connection flow before and after your changes. |
Agent-Logs-Url: https://github.com/microsoft/CCF/sessions/0cf0011c-18a2-4003-951c-55a903a0b6a5 Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
|
@copilot add to the description of the PR mermaid flowcharts that show the connection flow before and after your changes. |
Agent-Logs-Url: https://github.com/microsoft/CCF/sessions/2caf1046-8ae3-4198-98b2-d97b68196b3f Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
Updated the PR description with Mermaid flowcharts showing the connection flow before and after the TCP IPv6 retry/timeout changes (update pushed with 262f563).
Updated the PR description with Mermaid flowcharts showing the connection flow before and after the TCP IPv6 retry/timeout changes (update pushed with 262f563). |
Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot formatting |
Co-authored-by: achamayou <4016369+achamayou@users.noreply.github.com>
Done — |
|
Tick the box to add this pull request to the merge queue (same as
|
Tracking issue: #3215
Overview
Experimental support for IPv6 in CCF's host networking. Node RPC and node-to-node interface hosts may be specified as IPv6 literals in bracketed form (e.g.
[::1]:8000), and addresses are consistently parsed, bound, connected (with fallback across mixed IPv4/IPv6 resolved addresses), serialised, and embedded in redirect URLs for both IPv4 and IPv6. IPv4 and DNS behaviour is unchanged.Changes
Host TCP (
src/host/tcp.h)connect_resolved(), where the resolved address family (AF_INET/AF_INET6) is known, instead of being hard-coded toAF_INET.TCP_USER_TIMEOUTis applied on every client socket, including theclient_bind()path, viaset_connection_timeout()/set_connection_timeout_on_uv_handle().uv_tcp_tcannot switch family in place).Address handling
make_net_address/split_net_address(include/ccf/service/node_info_network.h) andcli::validate_address(src/ds/cli_helper.h) handle bracketed IPv6 literals ([host]:port): IPv6 hosts are wrapped in brackets when joined and stripped when split.split_net_addresskeeps a port-less host in the host position (not the port slot), andcli::validate_addressvalidates the port range and rejects malformed brackets. The two are intentionally kept separate (documented inline):split_net_addressis on the consensus deserialisation path and must stay lenient, whilevalidate_addressis strict input validation at the CLI boundary.Configuration::NodeInfoserialisation (src/kv/kv_types.h) and resolved RPC address output (src/host/run.cpp) use these helpers for consistent, URL-safe addresses.is_ipviainet_pton) and store them unbracketed (src/node/node_state.h).Test infrastructure
ipv6option (tests/infra/network.py) is threaded to each node at construction (tests/infra/node.py); when set,localhostis expanded to the IPv6 loopback::1viaexpand_localhost(ipv6=...)(tests/infra/net.py), so nodes added later are covered too. The previousCCF_IPV6/ IPv4-mapped::ffff:mechanism has been removed.ipv6option is also inherited by networks constructed from anexisting_network(tests/infra/network.py), so it propagates across recovery, where recovered networks are built from the previous one.infra.net.ipv6_loopback_available()reports whether::1can be bound, so IPv6 tests skip cleanly where it is unavailable.infra.interfaces.make_addressis bracket-aware and idempotent, and is used for published addresses and for redirectLocationassertions (tests/e2e_operations.py,tests/redirects.py), the recovery-share script (tests/infra/member.py), and node addresses.socket.create_connection(address-family agnostic) instead of a hard-codedAF_INET(tests/infra/clients.py).tests/infra/node.pyskips the IPv4-only "client interface" used for partition simulation when the node RPC host is IPv6.Tests
common_ipv6(tests/e2e_common_endpoints.py, registered intests/e2e_logging.py) starts a network on the IPv6 loopback::1and exercises common endpoints and redirects.reconfiguration_ipv6(tests/reconfiguration.py, registered intests/nodes.py) runs the full reconfiguration scenario - join, retire, replace, snapshot - with every node bound to::1, and asserts that no IPv4 literal appears in any node config (assert_no_ipv4_in_node_configs).recovery_ipv6(tests/recovery.py) runs the full recovery scenario - repeated service recoveries with and without snapshots, primary changes, and historical receipt checks - with every node bound to::1, and reusesassert_no_ipv4_in_node_configsto confirm no IPv4 literal appears in any node config.src/node/test/node_info_json.cpp).Documentation
CHANGELOG.mdrecords the experimental IPv6 support under the current version.Out of scope (for now)
sockaddr(udp::sockaddr_encode/sockaddr_decode,PendingIO, the host UDP read/write path), which truncates IPv6sockaddr_in6addresses, so UDP/QUIC replies do not reach IPv6 peers reliably. Only the TCP path has been made address-family-aware; UDP/QUIC over IPv6 is left for follow-up.::1loopback, so the IPv6 e2e tests only execute on the virtual/VMSS runners and are skipped on SNP.