Skip to content

Commit dbc04dc

Browse files
committed
Host-agent DNS forwarder improvements:
- Listen for TCP traffic - Add AAAA, CNAME, NS, MX, TXT and SRV forwarding - Fix `iptables` forward rules so hostagent is accessible from containers Signed-off-by: Dmytro Kryvenko <[email protected]>
1 parent c4f3b33 commit dbc04dc

File tree

9 files changed

+177
-26
lines changed

9 files changed

+177
-26
lines changed

docs/internal.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,4 @@ The volume label is "cidata", as defined by [cloud-init NoCloud](https://cloudin
112112
- `LIMA_CIDATA_SLIRP_GATEWAY`: set to the IP address of the host on the SLIRP network. `192.168.5.2`.
113113
- `LIMA_CIDATA_SLIRP_DNS`: set to the IP address of the DNS on the SLIRP network. `192.168.5.3`.
114114
- `LIMA_CIDATA_UDP_DNS_LOCAL_PORT`: set to the udp port number of the hostagent dns server (or 0 when not enabled).
115+
- `LIMA_CIDATA_TCP_DNS_LOCAL_PORT`: set to the tcp port number of the hostagent dns server (or 0 when not enabled).

docs/network.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,23 @@ The loopback addresses of the host is `192.168.5.2` and is accessible from the g
2020

2121
The DNS.
2222

23-
If `useHostResolver` in `lima.yaml` is true, then the hostagent is going to run a DNS server over udp, using the same socket number as `ssh.localPort`. This server does a local lookup using the native host resolver, so will deal correctly with VPN configurations and split-DNS setups, as well a mDNS (for this the hostagent has to be compiled with `CGO_ENABLED=1`).
23+
If `useHostResolver` in `lima.yaml` is true, then the hostagent is going to run a DNS server over tcp and udp - each on a separate randomly selected free port. This server does a local lookup using the native host resolver, so it will deal correctly with VPN configurations and split-DNS setups, as well as mDNS, local `/etc/hosts` etc. For this the hostagent has to be compiled with `CGO_ENABLED=1` as default Go resolver is [broken](https://github.com/golang/go/issues/12524).
2424

25-
This udp port is then forwarded via iptables rules to `192.168.5.3:53`, overriding the DNS provided by QEMU via slirp.
25+
These tcp and udp ports are then forwarded via iptables rules to `192.168.5.3:53`, overriding the DNS provided by QEMU via slirp.
26+
27+
Currently following request types are supported:
28+
29+
- A
30+
- AAAA
31+
- CNAME
32+
- TXT
33+
- NS
34+
- MX
35+
- SRV
36+
37+
For all other queries hostagent will redirect the query to the nameservers specified in `/etc/resolv.conf` (or, if that fails - to `8.8.8.8` and `1.1.1.1`).
38+
39+
DNS over tcp is rarely used. It is usually only used either when user explicitly requires it, or when request+response can't fit into a single UDP packet (most likely in case of DNSSEC), or in the case of certain management operations such as domain transfers. Neither DNSSEC nor management operations are currently supported by a hostagent, but on the off chance that the response may contain an unusually long list of records - hostagent will also listen for the tcp traffic.
2640

2741
During initial cloud-init bootstrap, `iptables` may not yet be installed. In that case the repo server is determined using the slirp DNS. After `iptables` has been installed, the forwarding rule is applied, switching over to the hostagent DNS.
2842

pkg/cidata/cidata.TEMPLATE.d/boot/07-host-dns-setup.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,19 @@ if command -v iptables >/dev/null 2>&1; then
66
if [ -n "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" ] && [ "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" -ne 0 ]; then
77
# Only add the rule once
88
if ! iptables-save | grep "udp.*${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}"; then
9+
iptables -t nat -A PREROUTING -d "${LIMA_CIDATA_SLIRP_DNS}" -p udp --dport 53 -j DNAT \
10+
--to-destination "${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}"
911
iptables -t nat -A OUTPUT -d "${LIMA_CIDATA_SLIRP_DNS}" -p udp --dport 53 -j DNAT \
1012
--to-destination "${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}"
1113
fi
1214
fi
15+
if [ -n "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" ] && [ "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" -ne 0 ]; then
16+
# Only add the rule once
17+
if ! iptables-save | grep "tcp.*${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}"; then
18+
iptables -t nat -A PREROUTING -d "${LIMA_CIDATA_SLIRP_DNS}" -p tcp --dport 53 -j DNAT \
19+
--to-destination "${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}"
20+
iptables -t nat -A OUTPUT -d "${LIMA_CIDATA_SLIRP_DNS}" -p tcp --dport 53 -j DNAT \
21+
--to-destination "${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}"
22+
fi
23+
fi
1324
fi

pkg/cidata/cidata.TEMPLATE.d/boot/30-install-packages.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ INSTALL_IPTABLES=0
1414
if [ "${LIMA_CIDATA_CONTAINERD_SYSTEM}" = 1 ] || [ "${LIMA_CIDATA_CONTAINERD_USER}" = 1 ]; then
1515
INSTALL_IPTABLES=1
1616
fi
17-
if [ "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" -ne 0 ]; then
17+
if [ "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" -ne 0 ] || [ "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" -ne 0 ]; then
1818
INSTALL_IPTABLES=1
1919
fi
2020

@@ -96,7 +96,14 @@ elif command -v apk >/dev/null 2>&1; then
9696
fi
9797
fi
9898

99+
SETUP_DNS=0
99100
if [ -n "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" ] && [ "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" -ne 0 ]; then
101+
SETUP_DNS=1
102+
fi
103+
if [ -n "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" ] && [ "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" -ne 0 ]; then
104+
SETUP_DNS=1
105+
fi
106+
if [ "${SETUP_DNS}" = 1 ]; then
100107
# Try to setup iptables rule again, in case we just installed iptables
101108
"${LIMA_CIDATA_MNT}/boot/07-host-dns-setup.sh"
102109
fi

pkg/cidata/cidata.TEMPLATE.d/lima.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ LIMA_CIDATA_CONTAINERD_SYSTEM=
1717
LIMA_CIDATA_SLIRP_DNS={{.SlirpDNS}}
1818
LIMA_CIDATA_SLIRP_GATEWAY={{.SlirpGateway}}
1919
LIMA_CIDATA_UDP_DNS_LOCAL_PORT={{.UDPDNSLocalPort}}
20+
LIMA_CIDATA_TCP_DNS_LOCAL_PORT={{.TCPDNSLocalPort}}

pkg/cidata/cidata.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func setupEnv(y *limayaml.LimaYAML) (map[string]string, error) {
6363
return env, nil
6464
}
6565

66-
func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort int, nerdctlArchive string) error {
66+
func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, nerdctlArchive string) error {
6767
if err := limayaml.Validate(*y, false); err != nil {
6868
return err
6969
}
@@ -119,6 +119,7 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
119119
}
120120
if *y.UseHostResolver {
121121
args.UDPDNSLocalPort = udpDNSLocalPort
122+
args.TCPDNSLocalPort = tcpDNSLocalPort
122123
args.DNSAddresses = append(args.DNSAddresses, qemu.SlirpDNS)
123124
} else if len(y.DNS) > 0 {
124125
for _, addr := range y.DNS {

pkg/cidata/template.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type TemplateArgs struct {
4040
SlirpGateway string
4141
SlirpDNS string
4242
UDPDNSLocalPort int
43+
TCPDNSLocalPort int
4344
Env map[string]string
4445
DNSAddresses []string
4546
}

pkg/hostagent/dns.go

Lines changed: 128 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@ type Handler struct {
1616
clients []*dns.Client
1717
}
1818

19+
type Server struct {
20+
udp *dns.Server
21+
tcp *dns.Server
22+
}
23+
24+
func (s *Server) Shutdown() {
25+
if s.udp != nil {
26+
_ = s.udp.Shutdown()
27+
}
28+
if s.tcp != nil {
29+
_ = s.tcp.Shutdown()
30+
}
31+
}
32+
1933
func newStaticClientConfig(ips []net.IP) (*dns.ClientConfig, error) {
2034
s := ``
2135
for _, ip := range ips {
@@ -52,27 +66,108 @@ func (h *Handler) handleQuery(w dns.ResponseWriter, req *dns.Msg) {
5266
handled bool
5367
)
5468
reply.SetReply(req)
55-
for _, q := range reply.Question {
69+
for _, q := range req.Question {
70+
hdr := dns.RR_Header{
71+
Name: q.Name,
72+
Rrtype: q.Qtype,
73+
Class: q.Qclass,
74+
Ttl: 5,
75+
}
5676
switch q.Qtype {
57-
case dns.TypeA:
77+
case dns.TypeCNAME, dns.TypeA, dns.TypeAAAA:
78+
cname, err := net.LookupCNAME(q.Name)
79+
if err == nil && cname != "" && cname != q.Name {
80+
hdr.Rrtype = dns.TypeCNAME
81+
a := &dns.CNAME{
82+
Hdr: hdr,
83+
Target: cname,
84+
}
85+
reply.Answer = append(reply.Answer, a)
86+
handled = true
87+
}
88+
if q.Qtype == dns.TypeCNAME {
89+
break
90+
}
91+
hdr.Name = cname
5892
addrs, err := net.LookupIP(q.Name)
5993
if err == nil && len(addrs) > 0 {
6094
for _, ip := range addrs {
61-
if ip.To4() != nil {
62-
a := &dns.A{
63-
Hdr: dns.RR_Header{
64-
Name: q.Name,
65-
Rrtype: dns.TypeA,
66-
Class: dns.ClassINET,
67-
},
68-
A: ip.To4(),
95+
var a dns.RR
96+
ipv6 := ip.To4() == nil
97+
if q.Qtype == dns.TypeA && !ipv6 {
98+
hdr.Rrtype = dns.TypeA
99+
a = &dns.A{
100+
Hdr: hdr,
101+
A: ip.To4(),
102+
}
103+
} else if q.Qtype == dns.TypeAAAA && ipv6 {
104+
hdr.Rrtype = dns.TypeAAAA
105+
a = &dns.AAAA{
106+
Hdr: hdr,
107+
AAAA: ip.To16(),
108+
}
109+
} else {
110+
continue
111+
}
112+
reply.Answer = append(reply.Answer, a)
113+
handled = true
114+
}
115+
}
116+
case dns.TypeTXT:
117+
txt, err := net.LookupTXT(q.Name)
118+
if err == nil && len(txt) > 0 {
119+
a := &dns.TXT{
120+
Hdr: hdr,
121+
Txt: txt,
122+
}
123+
reply.Answer = append(reply.Answer, a)
124+
handled = true
125+
}
126+
case dns.TypeNS:
127+
ns, err := net.LookupNS(q.Name)
128+
if err == nil && len(ns) > 0 {
129+
for _, s := range ns {
130+
if s.Host != "" {
131+
a := &dns.NS{
132+
Hdr: hdr,
133+
Ns: s.Host,
134+
}
135+
reply.Answer = append(reply.Answer, a)
136+
handled = true
137+
}
138+
}
139+
}
140+
case dns.TypeMX:
141+
mx, err := net.LookupMX(q.Name)
142+
if err == nil && len(mx) > 0 {
143+
for _, s := range mx {
144+
if s.Host != "" {
145+
a := &dns.MX{
146+
Hdr: hdr,
147+
Mx: s.Host,
148+
Preference: s.Pref,
69149
}
70150
reply.Answer = append(reply.Answer, a)
71151
handled = true
72-
break
73152
}
74153
}
75154
}
155+
case dns.TypeSRV:
156+
_, addrs, err := net.LookupSRV("", "", q.Name)
157+
if err == nil {
158+
hdr.Rrtype = dns.TypeSRV
159+
for _, addr := range addrs {
160+
a := &dns.SRV{
161+
Hdr: hdr,
162+
Target: addr.Target,
163+
Port: addr.Port,
164+
Priority: addr.Priority,
165+
Weight: addr.Weight,
166+
}
167+
reply.Answer = append(reply.Answer, a)
168+
handled = true
169+
}
170+
}
76171
}
77172
}
78173
if handled {
@@ -107,17 +202,31 @@ func (h *Handler) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
107202
}
108203
}
109204

110-
func (a *HostAgent) StartDNS() (*dns.Server, error) {
205+
func (a *HostAgent) StartDNS() (*Server, error) {
111206
h, err := newHandler()
112207
if err != nil {
113208
panic(err)
114209
}
115-
addr := fmt.Sprintf("127.0.0.1:%d", a.udpDNSLocalPort)
116-
server := &dns.Server{Net: "udp", Addr: addr, Handler: h}
117-
go func() {
118-
if e := server.ListenAndServe(); e != nil {
119-
panic(e)
120-
}
121-
}()
210+
server := &Server{}
211+
if a.udpDNSLocalPort > 0 {
212+
addr := fmt.Sprintf("127.0.0.1:%d", a.udpDNSLocalPort)
213+
s := &dns.Server{Net: "udp", Addr: addr, Handler: h}
214+
server.udp = s
215+
go func() {
216+
if e := s.ListenAndServe(); e != nil {
217+
panic(e)
218+
}
219+
}()
220+
}
221+
if a.tcpDNSLocalPort > 0 {
222+
addr := fmt.Sprintf("127.0.0.1:%d", a.tcpDNSLocalPort)
223+
s := &dns.Server{Net: "tcp", Addr: addr, Handler: h}
224+
server.tcp = s
225+
go func() {
226+
if e := s.ListenAndServe(); e != nil {
227+
panic(e)
228+
}
229+
}()
230+
}
122231
return server, nil
123232
}

pkg/hostagent/hostagent.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type HostAgent struct {
3535
y *limayaml.LimaYAML
3636
sshLocalPort int
3737
udpDNSLocalPort int
38+
tcpDNSLocalPort int
3839
instDir string
3940
sshConfig *ssh.SSHConfig
4041
portForwarder *portForwarder
@@ -87,15 +88,19 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt
8788
return nil, err
8889
}
8990

90-
var udpDNSLocalPort int
91+
var udpDNSLocalPort, tcpDNSLocalPort int
9192
if *y.UseHostResolver {
9293
udpDNSLocalPort, err = findFreeUDPLocalPort()
9394
if err != nil {
9495
return nil, err
9596
}
97+
tcpDNSLocalPort, err = findFreeTCPLocalPort()
98+
if err != nil {
99+
return nil, err
100+
}
96101
}
97102

98-
if err := cidata.GenerateISO9660(inst.Dir, instName, y, udpDNSLocalPort, o.nerdctlArchive); err != nil {
103+
if err := cidata.GenerateISO9660(inst.Dir, instName, y, udpDNSLocalPort, tcpDNSLocalPort, o.nerdctlArchive); err != nil {
99104
return nil, err
100105
}
101106

@@ -135,6 +140,7 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt
135140
y: y,
136141
sshLocalPort: sshLocalPort,
137142
udpDNSLocalPort: udpDNSLocalPort,
143+
tcpDNSLocalPort: tcpDNSLocalPort,
138144
instDir: inst.Dir,
139145
sshConfig: sshConfig,
140146
portForwarder: newPortForwarder(sshConfig, sshLocalPort, rules),
@@ -244,7 +250,7 @@ func (a *HostAgent) Run(ctx context.Context) error {
244250
if err != nil {
245251
return fmt.Errorf("cannot start DNS server: %w", err)
246252
}
247-
defer func() { _ = dnsServer.Shutdown() }()
253+
defer dnsServer.Shutdown()
248254
}
249255

250256
qCmd := exec.CommandContext(ctx, a.qExe, a.qArgs...)

0 commit comments

Comments
 (0)