Skip to content

feat(transports): advertise an address and port that the transport does not bind - #153

Open
ryanolson wants to merge 3 commits into
mainfrom
ai/advertise-addrs
Open

ryanolson wants to merge 3 commits into
mainfrom
ai/advertise-addrs

Conversation

@ryanolson

@ryanolson ryanolson commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

What

A TCP or QUIC transport can advertise socket addresses, ports included, that differ from its bind address. A peer that reaches the transport through a proxy, a NAT, or an overlay that maps another port then dials the mapped address.

  • TcpTransportBuilder::advertise_addrs and QuicTransportBuilder::advertise_addrs. The given addresses replace the interface_filter result in the WorkerAddress. The transport still listens on its bind address.
  • InterfaceFilter::Explicit keeps its behavior: it replaces each port with the bound port. Existing callers can depend on that, so this PR adds a builder method and does not change the variant. The change is additive. The semver gate accepts it under the 0.21.0 bump that main already carries.
  • QUIC: with advertised addresses, the entry lists no per-socket ports. Every lane of a peer then dials the advertised port, because a mapping covers one port. Lanes from one peer share one server socket in this mode.
  • QuicTransport::bound_addr gives the first server socket address, which is where such a mapping forwards.
  • QUIC register keeps only the advertised addresses that the dial sockets reach. These are IPv4 for an IPv4 transport. For an IPv6 transport, they are IPv6, and also IPv4 if its sockets are dual-stack. A peer with no reachable address is refused at registration. Before this, the transport registered the peer, and every frame then failed.

Out of scope

The per-stream TCP listener that VeloBuilder starts does not use advertise_addrs. Behind a proxy, use .mux_only(). The book says this.

Tests

lib/velo/tests/advertised_address.rs:

  • Two TCP nodes, each behind its own proxy on its own port, each advertising its proxy. A unary request crosses the server's proxy and the response crosses the client's proxy. Both proxies count more than 0 bytes.
  • Two QUIC transports, each behind its own UDP relay. The client sends on two lanes, and the relay of the server sees one source for each lane. The relay forwards to the first server socket only, so a lane that dialed another socket does not arrive.
  • The advertised TCP entry holds the given address and port, and not the bound port.

With advertise_addrs ignored in both builders, all three tests fail.

Conflicts

git merge-tree against #154 (QUIC mTLS) shows conflicts in README.md (both add a paragraph above the lanes paragraph) and in lib/velo/src/transports/quic/transport.rs (register and the struct fields). builder.rs, Cargo.toml and the book merge cleanly. Whichever PR merges second needs a small rebase. The hub TLS PR (#155) merges cleanly with both.

Review ledger

An independent reviewer did three rounds (rust-code-review).

Round Finding Ruling Change
1 Medium. IPv4-bound QUIC nodes behind IPv6 proxies register, and then every frame fails with "invalid remote address". Partly valid. The defect is real. The cause is wider than this PR: QUIC dial sockets take the family of the bind address, so any peer whose first or only address is of the other family fails. register keeps only the reachable addresses, and refuses a peer with none (InvalidEndpoint). Two tests, which failed first.
2 Medium. The round 1 filter refuses IPv4 peers that a dual-stack IPv6 dialer reaches. Valid. The round 1 fix caused this regression. The builder records whether the dial sockets reach IPv4 (IPV6_V6ONLY off). Test a_dual_stack_dialer_reaches_an_ipv4_peer fails with the round 1 filter.
3 None Verdict: ready

The dial sockets were not made dual-stack. That would change every QUIC dialer, and it fails on hosts with IPv6 off.

…es not bind

A peer can reach a transport through a proxy, a NAT, or an overlay
that maps another address and port to the bound socket.
InterfaceFilter::Explicit cannot express this, because it replaces
each port with the bound port.

TcpTransportBuilder and QuicTransportBuilder get advertise_addrs. It
advertises the given socket addresses, ports included, in place of
the interface_filter result. InterfaceFilter::Explicit keeps its
behavior.

A QUIC transport with advertised addresses lists no per-socket ports.
Every lane of a peer then dials the advertised port, because a
mapping covers one port. QuicTransport::bound_addr gives the address
that such a mapping forwards to.

Tests: two TCP nodes, each behind a byte-counting proxy on its own
port, exchange a request and its response through the proxies. Two
QUIC transports do the same through UDP relays, on two lanes.

Signed-off-by: Ryan Olson <rolson@nvidia.com>
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between cee2834 and 04cf9e8.

📒 Files selected for processing: 10
  • README.md
  • docs/src/concepts/transports.md
  • lib/velo/Cargo.toml
  • lib/velo/src/transports/quic/endpoint.rs
  • lib/velo/src/transports/quic/tests.rs
  • lib/velo/src/transports/quic/transport.rs
  • lib/velo/src/transports/quic/transport/builder.rs
  • lib/velo/src/transports/tcp/transport/builder.rs
  • lib/velo/src/transports/utils/interfaces.rs
  • lib/velo/tests/advertised_address.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Walkthrough

TCP and QUIC builders now accept explicit advertised socket addresses while transports listen on their bound addresses. QUIC registration filters peer endpoints by address-family reachability. Documentation and integration tests cover address configuration and traffic through proxies and relays.

Changes

Advertised transport addresses

Layer / File(s) Summary
Advertised address configuration
lib/velo/src/transports/utils/interfaces.rs, lib/velo/src/transports/tcp/transport/builder.rs, lib/velo/src/transports/quic/transport/builder.rs, lib/velo/src/transports/quic/endpoint.rs, README.md, docs/src/concepts/transports.md
TCP and QUIC builders accept explicit socket addresses, including ports, for peer advertisement. QUIC endpoint information omits per-socket ports when explicit addresses are supplied. The documentation describes the settings and address-family behavior.
QUIC endpoint reachability
lib/velo/src/transports/quic/transport.rs, lib/velo/src/transports/quic/transport/builder.rs, lib/velo/src/transports/quic/tests.rs
QUIC registration filters advertised endpoints by the dial transport’s address-family reachability. Tests cover IPv4 selection, unreachable endpoints, and IPv4 access through eligible dual-stack IPv6 sockets.
Proxy and relay integration tests
lib/velo/Cargo.toml, lib/velo/tests/advertised_address.rs
Integration tests verify advertised TCP addresses through proxies and QUIC traffic through UDP relays, including traffic across multiple client lanes.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant ClientLane
  participant ServerUdpRelay
  participant QUICServer
  participant ClientUdpRelay
  ClientLane->>ServerUdpRelay: Send datagrams to the advertised endpoint
  ServerUdpRelay->>QUICServer: Forward datagrams to the bound server socket
  QUICServer->>ClientUdpRelay: Send reply to the advertised client endpoint
  ClientUdpRelay->>ClientLane: Relay reply to the client source
Loading

Suggested reviewers: jthomson04

Merge Risk: ⚪ Minimal · up to 04cf9

The advertised-address changes have no identified merge-blocking issue after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 69.70% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 33 functions across 7 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: allowing TCP and QUIC transports to advertise an address and port different from their bound address.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 69.70% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 33 functions across 7 files. (3 skipped: 3 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.10345% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/velo/src/transports/quic/transport/builder.rs 88.88% 0 Missing and 2 partials ⚠️
lib/velo/src/transports/quic/transport.rs 94.44% 1 Missing ⚠️
lib/velo/src/transports/tcp/transport/builder.rs 87.50% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

…family

The QUIC dial sockets bind the family of the bind address. A peer that
advertised an address of the other family first, or only, was
registered, and then every frame to it failed with "invalid remote
address". This happened with any such peer, and advertise_addrs made
it easy to reach: an IPv4 node behind an IPv6 proxy.

register now selects among the advertised addresses of the dial
family only, and refuses a peer that advertises none with
InvalidEndpoint.

Signed-off-by: Ryan Olson <rolson@nvidia.com>
The previous commit kept only the advertised addresses of the bind
address family. On Linux an IPv6 dial socket is dual-stack by default
and reaches IPv4 peers through mapped addresses, so that filter
refused IPv4 peers that an IPv6 transport could already reach.

The builder now records whether the dial sockets reach IPv4: an IPv4
socket does, and an IPv6 socket does when IPV6_V6ONLY is off. The
filter keeps IPv4 addresses on that condition, and IPv6 addresses
only for an IPv6 transport.

Signed-off-by: Ryan Olson <rolson@nvidia.com>
@ryanolson
ryanolson marked this pull request as ready for review October 22, 2026 18:24
@ryanolson
ryanolson requested a lite review from Copilot October 22, 2026 18:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Moderate issues remain for unused QUIC server sockets and loss of scoped IPv6 address information.

No open issues 🎉

What changed in this PR

Adds support for TCP and QUIC transports to advertise externally reachable addresses and ports while continuing to bind locally, enabling proxy, NAT, and overlay deployments.

Changes:

  • Adds advertise_addrs builder APIs for TCP and QUIC.
  • Filters QUIC peer endpoints by dial-socket address-family reachability and exposes bound_addr().
  • Adds proxy/relay integration tests and documentation for advertised-address behavior.
File Reviewed changes
README.md Links to advertised-address documentation.
lib/​velo/​tests/​advertised_address.rs Tests TCP proxy traversal, QUIC relay traversal, advertised ports, and lane behavior.
lib/​velo/​src/​transports/​utils/​interfaces.rs Converts advertised socket addresses into transport endpoints; scoped IPv6 address handling requires changes.
lib/​velo/​src/​transports/​tcp/​transport/​builder.rs Adds TCP advertised-address configuration.
lib/​velo/​src/​transports/​quic/​transport/​builder.rs Adds QUIC advertised-address configuration and dial-family detection; extra server sockets remain live in advertised-address mode.
lib/​velo/​src/​transports/​quic/​transport.rs Exposes the bound address and filters unreachable QUIC endpoints during registration.
lib/​velo/​src/​transports/​quic/​tests.rs Tests QUIC address-family selection and dual-stack behavior.
lib/​velo/​src/​transports/​quic/​endpoint.rs Documents advertised endpoint behavior; descriptions need updating for proxy/NAT addresses.
lib/​velo/​Cargo.toml Registers the advertised-address integration test target.
docs/​src/​concepts/​transports.md Documents advertised addresses, QUIC lane behavior, and stream limitations.

Review effort: Lite

This branch has not been deployed

No deployments
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.

2 participants