Skip to content

Commit 89b24e6

Browse files
committed
feat(network): forward guest DNS to Docker's embedded resolver
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>
1 parent f288099 commit 89b24e6

14 files changed

Lines changed: 645 additions & 8 deletions

File tree

.github/contributors.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,6 @@ users:
107107
Chennamma-Hotkar:
108108
name: Chennamma Hotkar
109109
email: channuhotkar@gmail.com
110+
alimx07:
111+
name: Ali Mohamed
112+
email: amx746@gmail.com

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ URUNC_SRC += $(wildcard $(CURDIR)/pkg/unikontainers/unikernels/*.go)
7171
URUNC_SRC += $(wildcard $(CURDIR)/pkg/unikontainers/types/*.go)
7272
URUNC_SRC += $(wildcard $(CURDIR)/pkg/unikontainers/initrd/*.go)
7373
URUNC_SRC += $(wildcard $(CURDIR)/pkg/network/*.go)
74+
URUNC_SRC += $(wildcard $(CURDIR)/pkg/network/localhost/*.go)
7475
SHIM_SRC := $(wildcard $(CURDIR)/cmd/containerd-shim-urunc-v2/*.go)
7576
SHIM_SRC += $(wildcard $(CURDIR)/pkg/containerd-shim/*.go)
7677
SHIM_SRC += $(wildcard $(CURDIR)/pkg/containerd-shim/containerd/*.go)

internal/constants/network_constants.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
StaticNetworkTapIP = "172.16.1.1"
1919
StaticNetworkUnikernelIP = "172.16.1.2"
2020
// TODO: Experiment with DynamicNetworkTapIP starting from 172.16.X.1
21-
DynamicNetworkTapIP = "172.16.X.2"
22-
QueueProxyRedirectIP = "172.16.1.2"
21+
DynamicNetworkTapIP = "172.16.X.2"
22+
QueueProxyRedirectIP = "172.16.1.2"
23+
LocalhostDNSResolverIP = "192.168.100.100"
2324
)

pkg/network/localhost/docker.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) 2023-2026, Nubificus LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package localhost
16+
17+
import (
18+
"bytes"
19+
"fmt"
20+
"net"
21+
"os/exec"
22+
"regexp"
23+
"strings"
24+
)
25+
26+
// dockerCustomNetDNS is where Docker's embedded DNS resolver listens.
27+
const dockerCustomNetDNS = "127.0.0.11"
28+
29+
var (
30+
dockerDNSTCPRe = regexp.MustCompile(`-p\s+tcp.*to-destination\s+127\.0\.0\.11:(\d+)`)
31+
dockerDNSUDPRe = regexp.MustCompile(`-p\s+udp.*to-destination\s+127\.0\.0\.11:(\d+)`)
32+
)
33+
34+
func isDocker(resolvConf string) bool {
35+
return strings.Contains(resolvConf, "docker")
36+
}
37+
38+
func dockerRules(f *Forwarder) error {
39+
ipt, err := exec.LookPath("iptables")
40+
if err != nil {
41+
return err
42+
}
43+
44+
tcpPort, udpPort, err := dockerDNSPorts(ipt)
45+
if err != nil {
46+
return err
47+
}
48+
lhlog.Debugf("Docker DNS resolver: tcp/%s udp/%s", tcpPort, udpPort)
49+
50+
if err := dnat(ipt, f.VirtIP, "udp", net.JoinHostPort(dockerCustomNetDNS, udpPort)); err != nil {
51+
return err
52+
}
53+
if err := dnat(ipt, f.VirtIP, "tcp", net.JoinHostPort(dockerCustomNetDNS, tcpPort)); err != nil {
54+
return err
55+
}
56+
lhlog.Debug("Applied Docker DNAT rules")
57+
58+
return nil
59+
}
60+
61+
func dockerDNSPorts(ipt string) (tcpPort, udpPort string, err error) {
62+
var stdout, stderr bytes.Buffer
63+
cmd := exec.Cmd{
64+
Path: ipt,
65+
Args: []string{ipt, "-t", "nat", "-S", "DOCKER_OUTPUT", "--wait", "1"},
66+
Stdout: &stdout,
67+
Stderr: &stderr,
68+
}
69+
if err := cmd.Run(); err != nil {
70+
if _, ok := err.(*exec.ExitError); ok {
71+
return "", "", fmt.Errorf("iptables command %s failed: %s", cmd.String(), stderr.String())
72+
}
73+
return "", "", err
74+
}
75+
76+
out := stdout.String()
77+
if m := dockerDNSTCPRe.FindStringSubmatch(out); m != nil {
78+
tcpPort = m[1]
79+
}
80+
if m := dockerDNSUDPRe.FindStringSubmatch(out); m != nil {
81+
udpPort = m[1]
82+
}
83+
if tcpPort == "" || udpPort == "" {
84+
return "", "", fmt.Errorf("could not find Docker DNS ports in DOCKER_OUTPUT chain")
85+
}
86+
return tcpPort, udpPort, nil
87+
}

0 commit comments

Comments
 (0)