Skip to content

feat(dns,gateway)!: serve .test over IPv4 loopback only - #407

Open
clvsh wants to merge 1 commit into
mainfrom
feat/ipv4-loopback-only
Open

clvsh wants to merge 1 commit into
mainfrom
feat/ipv4-loopback-only

Conversation

@clvsh

@clvsh clvsh commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

PV v1 now serves .test over IPv4 loopback. AAAA queries return authoritative NOERROR with no answers, and Caddy keeps an explicit 127.0.0.1 bind so neither Gateway nor workers listen on LAN interfaces.

This establishes the runtime bind contract needed by the macOS 27 port-probe fix. Dual-loopback serving needs IPv6 pf redirects and remains a separate feature. Actual daemon startup from old dual-loopback Gateway and worker fragments regenerates both without stale or duplicate sites; no migration shim was needed.

Validation: formatting, workspace Clippy with warnings denied, and cargo shear pass. All 9 focused tests pass. The full macOS 27 CI-profile suite ran 1,601 tests: 1,600 passed, with only the expected kernel-table acceptance failure tracked in #402. The final PR in this stack removes that API. #403 must merge after the stack.

Summary by CodeRabbit

  • Networking
    • .test hostnames now resolve to IPv4 loopback (127.0.0.1) for A queries. AAAA queries return no records.
    • Gateway and project sites now listen on IPv4 loopback only. IPv6 loopback access is not supported by this configuration.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: QUIET
  • Plan: Advanced
  • Run ID: 46e02469-0de5-48b3-9e02-44d6c8cc4cad
📥 Commits

Reviewing files that changed from the base of the PR and between b62b1a0 and 5b98363.

⛔ Files ignored due to path filters (7)
  • crates/daemon/tests/snapshots/daemon_foundation__startup_reconciliation_replaces_dual_loopback_project_fragments.snap is excluded by !**/*.snap
  • crates/daemon/tests/snapshots/gateway_config__config_renderers_quote_path_tokens_with_spaces.snap is excluded by !**/*.snap
  • crates/daemon/tests/snapshots/gateway_config__gateway_config_renderer_imports_project_configs_when_requested.snap is excluded by !**/*.snap
  • crates/daemon/tests/snapshots/gateway_config__gateway_config_renderer_outputs_empty_gateway_listener.snap is excluded by !**/*.snap
  • crates/daemon/tests/snapshots/gateway_config__gateway_config_renderer_outputs_gateway_caddyfile.snap is excluded by !**/*.snap
  • crates/daemon/tests/snapshots/gateway_config__gateway_project_config_renderer_outputs_project_caddyfile.snap is excluded by !**/*.snap
  • crates/daemon/tests/snapshots/gateway_config__worker_project_config_renderer_outputs_project_caddyfile.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • DESIGN.md
  • IMPLEMENTATION.md
  • crates/daemon/src/dns.rs
  • crates/daemon/src/gateway_config.rs
  • crates/daemon/tests/daemon_foundation.rs
  • crates/daemon/tests/gateway_config.rs

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


📝 Walkthrough

Walkthrough

The .test resolver now answers A queries with IPv4 loopback and returns NODATA for AAAA queries. Generated Gateway and worker sites bind only to IPv4 loopback. A startup reconciliation test covers existing dual-loopback fragments.

Changes

IPv4-only .test loopback

Layer / File(s) Summary
DNS answer policy
DESIGN.md, IMPLEMENTATION.md, crates/daemon/src/dns.rs, crates/daemon/tests/daemon_foundation.rs
The design and roadmap specify NODATA for .test AAAA queries. The resolver answers A queries with IPv4 loopback, and DNS tests expect no AAAA answers.
Gateway and worker site bindings
crates/daemon/src/gateway_config.rs, crates/daemon/tests/daemon_foundation.rs, crates/daemon/tests/gateway_config.rs
Generated health, fallback, Gateway project, and PHP worker sites bind only to 127.0.0.1. Tests check the fallback binding and startup reconciliation of existing dual-loopback fragments.

Priority: ➖ Normal

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

Change: Feature

Merge Risk: ⚪ Minimal · up to 5b983

No actionable merge-blocking issue was identified; the change is mergeable after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 5b983

The change narrows network reachability rather than expanding it. No introduced security attack path was identified, but preserved legacy configurations and interrupted upgrades leave limited uncertainty about uniform IPv4-only enforcement.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated change affects local .test resolution and generated project-serving listeners on the host. It removes IPv6-loopback reachability without adding a LAN listener or a new downstream service destination; preserved configurations remain a separate coverage limitation.

Trust Boundaries and Controls

  • observed — The host-local network boundary remains enforced by explicit loopback socket and site bindings. DNS query content cannot substitute an arbitrary response address, and Gateway project traffic continues to use a fixed loopback proxy target.

Resilience and Maintainability Implications

  • inferred — Existing byte-preservation and compensating rollback can retain prior listener policy rather than guarantee immediate convergence to IPv4-only. This is a control-convergence limitation, not evidence that the PR introduces non-loopback exposure.

Hardening Proposals

  • proposed — If IPv4-only binding must hold across every recovery state, explicitly define how preserved legacy fragments and restored backups converge to that policy, and validate the externally active configuration after interrupted promotion. This is a hardening proposal, not an observed vulnerability.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 4 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: serving .test over IPv4 loopback only. It matches the DNS and Gateway updates.
Full details: Docstring Coverage

Explanation

Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 4 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ 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
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 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-08T04:42:36.511871Z 5b98363 PR opened
🔒 Security Review ✅ Completed 2026-10-08T04:40:04.373161Z 5b98363 PR opened
ℹ️ 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.

@codspeed

codspeed Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 7 untouched benchmarks


Comparing feat/ipv4-loopback-only (5b98363) with main (b62b1a0)

Open in CodSpeed

@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: 5b98363775

ℹ️ 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".

comma_separated_hostnames(&route.primary_hostname)?
));
output.push_str(" bind 127.0.0.1 ::1\n");
output.push_str(" bind 127.0.0.1\n");

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 Rewrite preserved fragments to the IPv4 bind contract

When upgrading while a linked Project's config is unreadable or invalid, build_runtime_plan sets render_config: false, and gateway_project_config_fragments plus worker_project_config_fragments reuse the active fragments verbatim. These new IPv4-only renderers are therefore bypassed, leaving legacy bind 127.0.0.1 ::1 Gateway and worker fragments active after reconciliation and violating the new IPv4-only runtime contract. The startup test covers only a valid Project config, so the preserved-fragment path also needs to apply this bind transition.

Useful? React with 👍 / 👎.

output.push_str("}\n");
output.push_str(&format!(
"\nhttp://{GATEWAY_HEALTH_HOSTNAME} {{\n bind 127.0.0.1 ::1\n"
"\nhttp://{GATEWAY_HEALTH_HOSTNAME} {{\n bind 127.0.0.1\n"

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 Ignore IPv6-only listeners in IPv4 port checks

With the Gateway now bound only to IPv4, pv ports:install still rejects every low port returned by loopback_tcp_listener_ports() and uses the same family-agnostic set when assigning Gateway backend ports. On macOS that set deliberately includes pure ::1 listeners, so an unrelated IPv6-only service on port 80 or 443 blocks setup even though both the generated inet PF redirects and Caddy use 127.0.0.1; an IPv6-only listener on a preferred backend port also needlessly forces fallback allocation. The conflict and allocation checks need to consider IPv4 occupancy for this IPv4-only contract.

Useful? React with 👍 / 👎.

Comment thread crates/daemon/src/dns.rs
));
}
_ => {}
if is_test_name(query.name()) && query.query_type() == RecordType::A {

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 Include an SOA record in AAAA NODATA responses

For every common .test AAAA lookup, this condition now emits an authoritative NOERROR/NODATA response with empty answer and authority sections. RFC 2308 section 3 requires an authoritative server to include the zone SOA when the requested record type has no data, because that record supplies the negative-cache TTL; without it, resolvers should not cache the response and can query PV again on every lookup rather than honoring the intended five-second TTL. The new tests only verify that the answer section is empty, so they do not catch the missing SOA.

Useful? React with 👍 / 👎.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important

The IPv4-only bind policy does not cover preserved last-valid project fragments, so an upgrade can leave Gateway and worker IPv6 listeners active.

Reviewed changes Reviewed the full DNS, Gateway, worker, documentation, and test changes in this PR.

  • DNS responses: .test A queries retain 127.0.0.1 answers; AAAA queries now return authoritative NOERROR without answers.
  • Loopback listeners: Generated Gateway health, fallback, project, and worker sites bind explicitly to 127.0.0.1 only.
  • Startup coverage: A new daemon startup test checks replacement of old dual-loopback fragments for a valid project, with updated renderer snapshots and DNS assertions.
  • Design contract: IPv4-only serving becomes the v1 requirement; IPv6 serving is deferred until dual-loopback PF routing is implemented.

Local verification passed all eight renderer tests and the DNS response unit test. The daemon integration test could not run on this Linux host: daemon IPC is unsupported on linux; startup preservation behavior was reviewed statically.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

comma_separated_hostnames(&route.primary_hostname)?
));
output.push_str(" bind 127.0.0.1 ::1\n");
output.push_str(" bind 127.0.0.1\n");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Apply the bind policy to preserved project fragments too

If an already-served project's pv.yml is invalid when this version starts, build_runtime_plan sets render_config: false and both fragment builders copy the old Caddyfiles verbatim, bypassing these changed renderers. The Gateway and worker can therefore retain bind 127.0.0.1 ::1 and continue listening on IPv6, contrary to the new runtime bind contract; the added startup test misses this because it seeds only a valid project.

Technical details
# Preserved fragments bypass IPv4-only rendering

## Affected sites
- `crates/daemon/src/gateway.rs:2656-2701` - invalid config selects `append_persisted_runtime_project`.
- `crates/daemon/src/gateway.rs:3303-3369` - reconstructs the previous runtime with `render_config: false`.
- `crates/daemon/src/gateway.rs:5075-5113` and `5116-5161` - reuse preserved Gateway and worker fragment contents without applying the new bind policy.
- `crates/daemon/src/jobs.rs:2691-2711` - system/startup reconciliation still attempts Gateway reconciliation after project errors.
- `crates/daemon/tests/daemon_foundation.rs:1655-1702` - the new replacement test covers valid config only.

## Required outcome
- Apply the IPv4-only runtime bind contract to preserved last-valid Gateway and worker fragments while retaining their last-valid routing and serving configuration.
- Add a macOS startup regression case with previously served/persisted project state, old dual-loopback fragments, and an invalid `pv.yml` before daemon startup; verify both layers no longer contain an IPv6 bind.
- Changing only the valid-project renderer or its snapshots is insufficient, because the invalid-project path never invokes those renderers.

This does not imply LAN exposure or a proven IPv4 routing failure. The concrete issue is that the old IPv6 listeners remain active, and the worker can retain its unchanged fragment/fingerprint on the no-op path.

Code references: invalid-config planning, preserved fragment construction, and existing invalid-config preservation coverage. Caddy documents that each host in bind creates a listener for that interface.

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