Skip to content

Commit

Permalink
Host-agent DNS forwarder improvements:
Browse files Browse the repository at this point in the history
- Listen for TCP traffic
- Add AAAA, CNAME, NS, MX, TXT and SRV forwarding
- Fix `iptables` forward rules so hostagent is accessible from containers

Co-authored-by: Jan Dubois <jan@jandubois.com>

Signed-off-by: Dmytro Kryvenko <llibicpep@gmail.com>
  • Loading branch information
dee-kryvenko committed Oct 20, 2021
1 parent c4f3b33 commit 89a7a4e
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 26 deletions.
1 change: 1 addition & 0 deletions docs/internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,4 @@ The volume label is "cidata", as defined by [cloud-init NoCloud](https://cloudin
- `LIMA_CIDATA_SLIRP_GATEWAY`: set to the IP address of the host on the SLIRP network. `192.168.5.2`.
- `LIMA_CIDATA_SLIRP_DNS`: set to the IP address of the DNS on the SLIRP network. `192.168.5.3`.
- `LIMA_CIDATA_UDP_DNS_LOCAL_PORT`: set to the udp port number of the hostagent dns server (or 0 when not enabled).
- `LIMA_CIDATA_TCP_DNS_LOCAL_PORT`: set to the tcp port number of the hostagent dns server (or 0 when not enabled).
18 changes: 16 additions & 2 deletions docs/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,23 @@ The loopback addresses of the host is `192.168.5.2` and is accessible from the g

The DNS.

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`).
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).

This udp port is then forwarded via iptables rules to `192.168.5.3:53`, overriding the DNS provided by QEMU via slirp.
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.

Currently following request types are supported:

- A
- AAAA
- CNAME
- TXT
- NS
- MX
- SRV

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`).

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.

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.

Expand Down
11 changes: 11 additions & 0 deletions pkg/cidata/cidata.TEMPLATE.d/boot/07-host-dns-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ if command -v iptables >/dev/null 2>&1; then
if [ -n "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" ] && [ "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" -ne 0 ]; then
# Only add the rule once
if ! iptables-save | grep "udp.*${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}"; then
iptables -t nat -A PREROUTING -d "${LIMA_CIDATA_SLIRP_DNS}" -p udp --dport 53 -j DNAT \
--to-destination "${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}"
iptables -t nat -A OUTPUT -d "${LIMA_CIDATA_SLIRP_DNS}" -p udp --dport 53 -j DNAT \
--to-destination "${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}"
fi
fi
if [ -n "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" ] && [ "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" -ne 0 ]; then
# Only add the rule once
if ! iptables-save | grep "tcp.*${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}"; then
iptables -t nat -A PREROUTING -d "${LIMA_CIDATA_SLIRP_DNS}" -p tcp --dport 53 -j DNAT \
--to-destination "${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}"
iptables -t nat -A OUTPUT -d "${LIMA_CIDATA_SLIRP_DNS}" -p tcp --dport 53 -j DNAT \
--to-destination "${LIMA_CIDATA_SLIRP_GATEWAY}:${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}"
fi
fi
fi
9 changes: 8 additions & 1 deletion pkg/cidata/cidata.TEMPLATE.d/boot/30-install-packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ INSTALL_IPTABLES=0
if [ "${LIMA_CIDATA_CONTAINERD_SYSTEM}" = 1 ] || [ "${LIMA_CIDATA_CONTAINERD_USER}" = 1 ]; then
INSTALL_IPTABLES=1
fi
if [ "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" -ne 0 ]; then
if [ "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" -ne 0 ] || [ "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" -ne 0 ]; then
INSTALL_IPTABLES=1
fi

Expand Down Expand Up @@ -96,7 +96,14 @@ elif command -v apk >/dev/null 2>&1; then
fi
fi

SETUP_DNS=0
if [ -n "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" ] && [ "${LIMA_CIDATA_UDP_DNS_LOCAL_PORT}" -ne 0 ]; then
SETUP_DNS=1
fi
if [ -n "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" ] && [ "${LIMA_CIDATA_TCP_DNS_LOCAL_PORT}" -ne 0 ]; then
SETUP_DNS=1
fi
if [ "${SETUP_DNS}" = 1 ]; then
# Try to setup iptables rule again, in case we just installed iptables
"${LIMA_CIDATA_MNT}/boot/07-host-dns-setup.sh"
fi
Expand Down
1 change: 1 addition & 0 deletions pkg/cidata/cidata.TEMPLATE.d/lima.env
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ LIMA_CIDATA_CONTAINERD_SYSTEM=
LIMA_CIDATA_SLIRP_DNS={{.SlirpDNS}}
LIMA_CIDATA_SLIRP_GATEWAY={{.SlirpGateway}}
LIMA_CIDATA_UDP_DNS_LOCAL_PORT={{.UDPDNSLocalPort}}
LIMA_CIDATA_TCP_DNS_LOCAL_PORT={{.TCPDNSLocalPort}}
3 changes: 2 additions & 1 deletion pkg/cidata/cidata.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func setupEnv(y *limayaml.LimaYAML) (map[string]string, error) {
return env, nil
}

func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort int, nerdctlArchive string) error {
func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, nerdctlArchive string) error {
if err := limayaml.Validate(*y, false); err != nil {
return err
}
Expand Down Expand Up @@ -119,6 +119,7 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
}
if *y.UseHostResolver {
args.UDPDNSLocalPort = udpDNSLocalPort
args.TCPDNSLocalPort = tcpDNSLocalPort
args.DNSAddresses = append(args.DNSAddresses, qemu.SlirpDNS)
} else if len(y.DNS) > 0 {
for _, addr := range y.DNS {
Expand Down
1 change: 1 addition & 0 deletions pkg/cidata/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type TemplateArgs struct {
SlirpGateway string
SlirpDNS string
UDPDNSLocalPort int
TCPDNSLocalPort int
Env map[string]string
DNSAddresses []string
}
Expand Down
150 changes: 131 additions & 19 deletions pkg/hostagent/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ type Handler struct {
clients []*dns.Client
}

type Server struct {
udp *dns.Server
tcp *dns.Server
}

func (s *Server) Shutdown() {
if s.udp != nil {
_ = s.udp.Shutdown()
}
if s.tcp != nil {
_ = s.tcp.Shutdown()
}
}

func newStaticClientConfig(ips []net.IP) (*dns.ClientConfig, error) {
s := ``
for _, ip := range ips {
Expand Down Expand Up @@ -52,27 +66,111 @@ func (h *Handler) handleQuery(w dns.ResponseWriter, req *dns.Msg) {
handled bool
)
reply.SetReply(req)
for _, q := range reply.Question {
for _, q := range req.Question {
hdr := dns.RR_Header{
Name: q.Name,
Rrtype: q.Qtype,
Class: q.Qclass,
Ttl: 5,
}
switch q.Qtype {
case dns.TypeA:
case dns.TypeCNAME, dns.TypeA, dns.TypeAAAA:
cname, err := net.LookupCNAME(q.Name)
if err != nil {
break
}
if cname != "" && cname != q.Name {
hdr.Rrtype = dns.TypeCNAME
a := &dns.CNAME{
Hdr: hdr,
Target: cname,
}
reply.Answer = append(reply.Answer, a)
handled = true
}
if q.Qtype == dns.TypeCNAME {
break
}
hdr.Name = cname
addrs, err := net.LookupIP(q.Name)
if err == nil && len(addrs) > 0 {
for _, ip := range addrs {
if ip.To4() != nil {
a := &dns.A{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
A: ip.To4(),
var a dns.RR
ipv6 := ip.To4() == nil
if q.Qtype == dns.TypeA && !ipv6 {
hdr.Rrtype = dns.TypeA
a = &dns.A{
Hdr: hdr,
A: ip.To4(),
}
} else if q.Qtype == dns.TypeAAAA && ipv6 {
hdr.Rrtype = dns.TypeAAAA
a = &dns.AAAA{
Hdr: hdr,
AAAA: ip.To16(),
}
} else {
continue
}
reply.Answer = append(reply.Answer, a)
handled = true
}
}
case dns.TypeTXT:
txt, err := net.LookupTXT(q.Name)
if err == nil && len(txt) > 0 {
a := &dns.TXT{
Hdr: hdr,
Txt: txt,
}
reply.Answer = append(reply.Answer, a)
handled = true
}
case dns.TypeNS:
ns, err := net.LookupNS(q.Name)
if err == nil && len(ns) > 0 {
for _, s := range ns {
if s.Host != "" {
a := &dns.NS{
Hdr: hdr,
Ns: s.Host,
}
reply.Answer = append(reply.Answer, a)
handled = true
}
}
}
case dns.TypeMX:
mx, err := net.LookupMX(q.Name)
if err == nil && len(mx) > 0 {
for _, s := range mx {
if s.Host != "" {
a := &dns.MX{
Hdr: hdr,
Mx: s.Host,
Preference: s.Pref,
}
reply.Answer = append(reply.Answer, a)
handled = true
break
}
}
}
case dns.TypeSRV:
_, addrs, err := net.LookupSRV("", "", q.Name)
if err == nil {
hdr.Rrtype = dns.TypeSRV
for _, addr := range addrs {
a := &dns.SRV{
Hdr: hdr,
Target: addr.Target,
Port: addr.Port,
Priority: addr.Priority,
Weight: addr.Weight,
}
reply.Answer = append(reply.Answer, a)
handled = true
}
}
}
}
if handled {
Expand Down Expand Up @@ -107,17 +205,31 @@ func (h *Handler) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
}
}

func (a *HostAgent) StartDNS() (*dns.Server, error) {
func (a *HostAgent) StartDNS() (*Server, error) {
h, err := newHandler()
if err != nil {
panic(err)
}
addr := fmt.Sprintf("127.0.0.1:%d", a.udpDNSLocalPort)
server := &dns.Server{Net: "udp", Addr: addr, Handler: h}
go func() {
if e := server.ListenAndServe(); e != nil {
panic(e)
}
}()
server := &Server{}
if a.udpDNSLocalPort > 0 {
addr := fmt.Sprintf("127.0.0.1:%d", a.udpDNSLocalPort)
s := &dns.Server{Net: "udp", Addr: addr, Handler: h}
server.udp = s
go func() {
if e := s.ListenAndServe(); e != nil {
panic(e)
}
}()
}
if a.tcpDNSLocalPort > 0 {
addr := fmt.Sprintf("127.0.0.1:%d", a.tcpDNSLocalPort)
s := &dns.Server{Net: "tcp", Addr: addr, Handler: h}
server.tcp = s
go func() {
if e := s.ListenAndServe(); e != nil {
panic(e)
}
}()
}
return server, nil
}
12 changes: 9 additions & 3 deletions pkg/hostagent/hostagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type HostAgent struct {
y *limayaml.LimaYAML
sshLocalPort int
udpDNSLocalPort int
tcpDNSLocalPort int
instDir string
sshConfig *ssh.SSHConfig
portForwarder *portForwarder
Expand Down Expand Up @@ -87,15 +88,19 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt
return nil, err
}

var udpDNSLocalPort int
var udpDNSLocalPort, tcpDNSLocalPort int
if *y.UseHostResolver {
udpDNSLocalPort, err = findFreeUDPLocalPort()
if err != nil {
return nil, err
}
tcpDNSLocalPort, err = findFreeTCPLocalPort()
if err != nil {
return nil, err
}
}

if err := cidata.GenerateISO9660(inst.Dir, instName, y, udpDNSLocalPort, o.nerdctlArchive); err != nil {
if err := cidata.GenerateISO9660(inst.Dir, instName, y, udpDNSLocalPort, tcpDNSLocalPort, o.nerdctlArchive); err != nil {
return nil, err
}

Expand Down Expand Up @@ -135,6 +140,7 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt
y: y,
sshLocalPort: sshLocalPort,
udpDNSLocalPort: udpDNSLocalPort,
tcpDNSLocalPort: tcpDNSLocalPort,
instDir: inst.Dir,
sshConfig: sshConfig,
portForwarder: newPortForwarder(sshConfig, sshLocalPort, rules),
Expand Down Expand Up @@ -244,7 +250,7 @@ func (a *HostAgent) Run(ctx context.Context) error {
if err != nil {
return fmt.Errorf("cannot start DNS server: %w", err)
}
defer func() { _ = dnsServer.Shutdown() }()
defer dnsServer.Shutdown()
}

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

0 comments on commit 89a7a4e

Please sign in to comment.