Skip to content

Bind the DNS server's TCP port independently of UDP's for port 0 - #1402

Open
ejc3 wants to merge 1 commit into
mainfrom
fix/aggregator-dns-test-port
Open

ejc3 wants to merge 1 commit into
mainfrom
fix/aggregator-dns-test-port

Conversation

@ejc3

@ejc3 ejc3 commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Mechanism

Authority::serve (src/fleet/coord/authority.rs) bound UDP to the requested address, then bound TCP to the port number the kernel had picked for UDP. UDP and TCP have separate port spaces, so a port that is free for UDP says nothing about TCP. The TCP bind failed with binding TCP 127.0.0.1:<port> whenever any other socket on the host held that TCP port. subzone_fixture_with (used by the two aggregator DNS tests and the other subzone tests) serves on 127.0.0.1:0, in a binary where other tests hold loopback TCP sockets.

Measured with a probe doing the same two binds (SO_REUSEADDR on the TCP socket, as tokio does): 16/2000 binds failed on this shared box with no ballast, 98/2000 with 1,000 loopback TCP listeners held, 368/2000 with 5,000.

Change

With port 0, serve asks the kernel for each protocol's port and returns Served { udp, tcp }. A fixed port is still both protocols' port. ask_dns in the tests sends UDP to served.udp and TCP to served.tcp. The production caller only logs the result.

Evidence

  • dns_port_zero_is_asked_of_the_kernel_once_per_protocol: 16 port-0 serves, at least one pair must differ. With TCP's port copied from UDP's (the old behavior, restored for the check) it fails every run; with the fix it passes (75/75 in make test-fleet-coordinator).
  • With 12,000 loopback TCP listeners held, the two aggregator tests (a_host_aggregator_is_named_in_the_subzone_and_answers, the_ec2_aggregator_is_named_in_the_subzone_and_answers) failed 17/20 runs on the old bind and 0/20 with the fix.
  • make clippy clean.

…port 0

Authority::serve bound UDP to the requested address, then bound TCP to the
port number the kernel had given the UDP socket. UDP and TCP have separate
port spaces, so a port free for UDP says nothing about TCP, and the TCP bind
failed with "binding TCP 127.0.0.1:<port>" whenever any other socket on the
host held that TCP port. The aggregator DNS tests (and every subzone_fixture
test) bind 127.0.0.1:0 in a binary where other tests hold many loopback TCP
sockets, so they failed intermittently, on main too.

Measured with a probe that does the same two binds, SO_REUSEADDR set on the
TCP socket as tokio does: 16 of 2000 binds failed on this shared box with no
ballast, 98 with 1,000 loopback TCP listeners held, 368 with 5,000.

serve now asks the kernel for each protocol's port when the requested port is
0, and returns a Served { udp, tcp }. A fixed port is still both protocols'
port. The tests' ask_dns sends UDP to served.udp and TCP to served.tcp.

Regression test dns_port_zero_is_asked_of_the_kernel_once_per_protocol: 16
port-0 serves, at least one must have different UDP and TCP ports. With TCP's
port copied from UDP's it fails every run; it passed in 75 of 75 with the fix.
With 12,000 loopback TCP listeners held, the two aggregator tests failed 17 of
20 runs on the old bind and 0 of 20 with the fix.

Tested: make test-fleet-coordinator (75 passed), make clippy
@ejc3

ejc3 commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-27T15:47:53.746129Z ddbedc5 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 33 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 480d41bc-5f43-4081-94b2-af2d2135f143
Commits

Reviewing files that changed from the base of the PR and between 7f5fb7e and ddbedc5.

Files selected for processing: 2
  • src/fleet/coord/authority.rs
  • tests/test_fleet_coord.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ddbedc5a3c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +664 to +665
let tcp = tokio::net::TcpListener::bind(SocketAddr::new(
listen.ip(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the IPv6 scope ID when binding TCP

When --dns-listen specifies a scoped IPv6 link-local address, reconstructing it with SocketAddr::new(listen.ip(), ...) discards its scope ID. UDP binds successfully, but TCP then fails with EINVAL, causing coordinator startup to abort even for a fixed port that previously worked. I reproduced the bind sequence against a local link-local interface: preserving the UDP socket address succeeds, while rebuilding it without the scope fails. Bind TCP directly to listen, which already preserves both the requested port-zero behavior and the IPv6 scope.

Useful? React with 👍 / 👎.

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.

1 participant