Skip to content

feat: forward guest DNS to Docker's embedded resolver#793

Open
alimx07 wants to merge 1 commit into
urunc-dev:mainfrom
alimx07:feat/add-docker-dns
Open

feat: forward guest DNS to Docker's embedded resolver#793
alimx07 wants to merge 1 commit into
urunc-dev:mainfrom
alimx07:feat/add-docker-dns

Conversation

@alimx07

@alimx07 alimx07 commented Jun 28, 2026

Copy link
Copy Markdown

Description

On Docker user custom networks the DNS resolver (127.0.0.11) is loopback only and unreachable by the unikernel guest. Detect this case per container, expose the resolver via a virtual resolver IP configured by user using tc redirects between the tap and lo , and rewrite the guest's resolv.conf to point at that IP in our unikernel is linux one. Also we add a custom rules DNAT rules according to the enviroment (e.g. Docker -> PREROUTING DNAT (for DNS server ports))

Two TC rules added on tap and lo:

  • Tap : Redirect packets to resolvIP into lo
  • Lo : Redirect packets with src=ResolvIP into Tap

Related issues

How was this tested?

Tested in user custom docker network with urunc container u1 and normal one u2:

u1 lookup on github.com

/ # nslookup github.com
Server:		172.24.255.254
Address:	172.24.255.254:53

Non-authoritative answer:
Name:	github.com
Address: 140.82.121.3

u1 lookup on u2

/ # nslookup u2.
Server:		172.24.255.254
Address:	172.24.255.254:53

Non-authoritative answer:

Non-authoritative answer:
Name:	u2
Address: 172.24.0.3

u2 lookup on u1

Server:		127.0.0.11
Address:	127.0.0.11:53

Non-authoritative answer:

Non-authoritative answer:
Name:	u1
Address: 172.24.0.2

LLM usage

Checklist

  • I have read the contribution guide.
  • The linter passes locally (make lint).
  • The e2e tests of at least one tool pass locally (make test_ctr, make test_nerdctl, make test_docker, make test_crictl).
  • If LLMs were used: I have read the llm policy.

@netlify

netlify Bot commented Jun 28, 2026

Copy link
Copy Markdown

Deploy Preview for urunc ready!

Name Link
🔨 Latest commit 89b24e6
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/6a6225670f97ee0008ff26a0
😎 Deploy Preview https://deploy-preview-793--urunc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@alimx07
alimx07 force-pushed the feat/add-docker-dns branch 2 times, most recently from 25a6350 to a743f29 Compare June 28, 2026 07:44
@alimx07
alimx07 force-pushed the feat/add-docker-dns branch from a743f29 to 6723c67 Compare July 10, 2026 15:49
@alimx07

alimx07 commented Jul 10, 2026

Copy link
Copy Markdown
Author

Hey @cmainas, I have implemented the changes we discussed during our sync. Could you please take a look and review them? This will allow us to make any necessary changes before we extend this logic to other unikernels.

@alimx07
alimx07 force-pushed the feat/add-docker-dns branch 5 times, most recently from c15a2e2 to 22a5784 Compare July 22, 2026 15:39
On Docker user custom networks the DNS resolver (127.0.0.11) is
loopback only and unreachable by the unikernel guest. Detect this
case per container, expose the resolver via a virtual resolver IP
using tc redirects between the tap and lo + a PREROUTING DNAT,
and rewrite the guest's resolv.conf to point at that IP.

Signed-off-by: Ali Mohamed <amx746@gmail.com>
@alimx07
alimx07 force-pushed the feat/add-docker-dns branch from 22a5784 to 89b24e6 Compare July 23, 2026 14:29

@cmainas cmainas 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.

Hello @alimx07 ,

thank you for all the changes and the fix. A few comments:

  • In the e2e testing the docker network does not exist and therefore creating the contianer will fail. We need to setup the network. Maybe in https://github.com/urunc-dev/urunc/blob/main/tests/e2e/docker_test.go#L25 and also remove later.
  • Let;s find a better name for loclahost.go. Maybe something like "dns_at_localhost.go"?
  • I think I got confused with the genericRules and dockerRules. Have we found a case where we should apply the genericRules (e.g. a CNI) rather than dockerRules?
  • We can wrap the execution of iptables in a function, instead of having cmd and cmd.Run in various places.
  • We should check if we can obtain the information we gather form iptables from other sources (e.g. netlink).
  • We can restructure the code and this logic in the network_dynamic.go (the only mode where these changes apply) and avoid re-opening the tap device etc. We can me use of https://github.com/urunc-dev/urunc/blob/main/pkg/network/network_dynamic.go#L23C6-L23C20 for passing information.
  • We should also clean up all the rules we apply.

Comment thread tests/e2e/test_cases.go
StaticNet: false,
SideContainers: []string{},
Skippable: false,
TestFunc: dnsResolveExternalTest,

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.

We can make this matchTest. Look at https://github.com/urunc-dev/urunc/blob/main/tests/e2e/test_cases.go#L19 for example.

Comment on lines +67 to +69
if isDocker(resolvConf) {
f.custom = dockerRules
}

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.

The decision if we are going to apply the "docerRules" should be based on the localhost detection as a nameserver and not if there is a "docker" string in /etc/reolv.conf

// Rule: DNSServer stays empty unless the rules actually got installed.
if fwd != nil && networkType == "dynamic" {
if err := fwd.Apply(networkInfo.TapDevice, networkInfo.EthDevice.Interface); err != nil {
uniklog.Warnf("failed to apply localhost forwarding rules: %v", err)

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.

nit: We can return here instead of using else.

Comment on lines +160 to +163
ipt, err := exec.LookPath("iptables")
if err != nil {
return err
}

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.

nit: This should be placed in dnat.

return err
}
dst := net.JoinHostPort(f.LoIP.String(), "53")
for _, proto := range []string{"udp", "tcp"} {

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.

We are not going to have more than udp and tcp as protocols here, so it would be cleaner if we unfold the loop. As it happens in docker.go

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.

We can merge this file with "loclahost.go" or its new name.

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