Skip to content

Add DNS forwarding groundwork#1813

Open
thromel wants to merge 3 commits into
apple:mainfrom
thromel:codex/container-facing-dns
Open

Add DNS forwarding groundwork#1813
thromel wants to merge 3 commits into
apple:mainfrom
thromel:codex/container-facing-dns

Conversation

@thromel

@thromel thromel commented Jun 25, 2026

Copy link
Copy Markdown

Summary

This PR keeps the reusable DNS plumbing needed for future container-facing service discovery, but it no longer starts a container-facing listener.

  • adds request context propagation through DNS handlers
  • lets DNS servers optionally drop unhandled queries instead of returning notImplemented
  • adds a forwarding resolver that reads system upstream resolvers while filtering loopback, invalid, and unspecified nameservers
  • preserves raw DNS response resource records so forwarded responses can be serialized without losing unsupported record types
  • keeps hostname normalization from Normalize network hostname lookup #1810 as the stack base
  • adds docs/design/dns.md to document current DNS paths, behavioral invariants, extension points, and the latest vmnet validation result

Design note

This previously tried to bind a container-facing resolver on UDP/53. Review called out that wildcard binding races with mDNSResponder and may not cover vmnet bridge IPs.

I tested a narrower temp-install design that moved the listener into the vmnet helper and bound it to each reported network gateway address. Gateway binds failed with EADDRNOTAVAIL.

I also tested the maintainer-suggested local change to call vmnet_network_configuration_disable_dns_proxy(vmnetConfiguration) before vmnet_network_create. With a signed temp-root helper and the repo vmnet entitlements, the vmnet network still started successfully, but binding 192.168.64.1:53 still failed with EADDRNOTAVAIL.

So this PR intentionally keeps listener startup out of the runtime path. The remaining container-facing listener design question is whether vmnet exposes a different host-bindable endpoint, or whether this needs an mDNSResponder or packet-filter integration path.

Related

This PR is stacked and currently includes the commit from #1810. I can rebase once #1810 lands or retarget however maintainers prefer.

Testing

  • git diff --check origin/main..HEAD
  • swift test -c debug -Xswiftc -warnings-as-errors --filter 'ForwardingResolverTest|CompositeResolverTest|RecordsTests|AttachmentAllocatorTest'
  • swift build -c debug -Xswiftc -warnings-as-errors --product container-apiserver
  • swift build -c debug -Xswiftc -warnings-as-errors --product container-network-vmnet
  • Temp-root vmnet-helper smoke with vmnet DNS proxy enabled: network started, gateway bind to 192.168.64.1:53 failed with EADDRNOTAVAIL.
  • Temp-root vmnet-helper smoke with vmnet_network_configuration_disable_dns_proxy applied: network started, gateway bind to 192.168.64.1:53 still failed with EADDRNOTAVAIL.

@jglogan

jglogan commented Jun 26, 2026

Copy link
Copy Markdown
Contributor
  • starts a best-effort DNS listener on 0.0.0.0:53

If any containers are running, mDNSResponder will already be trying to do this.

Are you sure you can do that listen as a non-root user?

Either of those would cause the listen to not happen on the vmnet bridge IP addresses.

@thromel thromel force-pushed the codex/container-facing-dns branch from 5e30134 to 712f517 Compare June 27, 2026 01:03
@thromel thromel changed the title Add container-facing DNS forwarding Add DNS forwarding groundwork Jun 27, 2026
@thromel

thromel commented Jun 27, 2026

Copy link
Copy Markdown
Author

Fixed by removing listener startup from this PR.

I tested the narrower design after your comment: move the listener into the vmnet helper and bind to each network gateway instead of APIServer wildcard 0.0.0.0:53. A temp-install smoke created the default and a custom vmnet network, but both gateway binds failed with EADDRNOTAVAIL (192.168.64.1:53 and 192.168.65.1:53).

The current PR now keeps only the reusable DNS forwarding/message groundwork. The actual listener should be a separate design, likely mDNSResponder integration or a privileged packet-filter redirection path from vmnet gateway DNS to a host-local resolver.

@jglogan

jglogan commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

A temp-install smoke created the default and a custom vmnet network, but both gateway binds failed with EADDRNOTAVAIL (192.168.64.1:53 and 192.168.65.1:53).

Let's not give up on this yet. What happens if in your local build, you make a change to the vmnet network configuration in ReservedVmnetNetwork to [disable the DNS proxy]((https://developer.apple.com/documentation/vmnet/vmnet_network_configuration_disable_dns_proxy(_:))?

Given the breadth of the DNS work you're doing, I think we really need a design doc docs/design/dns.md that describes how container does DNS handling. We're not really ready to commit to any of the recent spec-driven methodologies like OpenSpec or Spec Kit but we could have the design doc use OpenSpec's language and structure for the behavioral specifications/invariants.

We don't need to do all the design up front but perhaps it does include a stubbed outline based on what exists and the various enhancements you're proposing.

This should help us manage fixes and enhancements such that the subsystem functions reliably and the user gets the best possible experience.

Facets of the container DNS subsystem include:

  • Default DNS communication path from workloads to upstream resolvers (today we go from workload to mDNSResponder proxy on NAT bridge IP to mDNSResponder (and to container-apiserver via an /etc/resolvers config for scoped domains)
  • Distinction (and amount of coordination) between DNS server configuration and container resolver.conf configuration (today the configuration for the former is separate from the latter).
  • container-apiserver DNS handling (this includes specifying how we validate and normalize domain names - you have a PR that's changing this)
  • How container configuration gets mapped to DNS records. What are the rules for the domain name, if any, that a container gets? How do we prevent configurations where a container may not be able to start because another container has a conflicting domain. Currently we prevent creation of a container if it could conflict, and I think we want to continue with this policy as we add --alias, --hostname, etc.
  • container-to-host networking (host.docker.internal) support, which is hampered by issues with using pfctl today
  • Interoperability and security considerations when running 3rd party DNS, VPN, and network security software (we have a fair number of open issues related to this, which is why I'm particularly interested in this PR).

@thromel

thromel commented Jun 28, 2026

Copy link
Copy Markdown
Author

I tested this with a signed temp-root vmnet helper using the repo entitlements and a full temp plugin layout.

Result:

  • With vmnet DNS proxy left enabled, the vmnet network starts, then binding 192.168.64.1:53 fails with EADDRNOTAVAIL.
  • With vmnet_network_configuration_disable_dns_proxy(vmnetConfiguration) applied before vmnet_network_create, the vmnet network still starts, but binding 192.168.64.1:53 still fails with EADDRNOTAVAIL.

So disabling the DNS proxy alone does not make the gateway address host-bindable in this environment. I left listener startup out of the runtime path and added docs/design/dns.md with the current DNS paths, invariants, extension points, and validation result. The remaining listener design question is whether vmnet exposes another bindable endpoint or whether this needs an mDNSResponder or packet-filter style path.

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