Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Host-agent DNS forwarder improvements (fixes #770) #319

Merged
merged 1 commit into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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).

dee-kryvenko marked this conversation as resolved.
Show resolved Hide resolved
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
dee-kryvenko marked this conversation as resolved.
Show resolved Hide resolved
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
}
dee-kryvenko marked this conversation as resolved.
Show resolved Hide resolved
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()
AkihiroSuda marked this conversation as resolved.
Show resolved Hide resolved
}

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