Skip to content

Commit e3459c6

Browse files
committed
Add mechanism to define static hostnames in the hostResolver
This is an alternative to adding them globally to /etc/hosts on the host (not the guest). It also allows aliasing them to host.lima.internal. This allows names to be resolved not just inside the guest, but also inside containers inside the guest, which only have access to /etc/resolv.conf, and not the full resolver inside the guest. Signed-off-by: Jan Dubois <[email protected]>
1 parent 6d086d2 commit e3459c6

File tree

9 files changed

+112
-10
lines changed

9 files changed

+112
-10
lines changed

cmd/limactl/debug.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func debugDNSAction(cmd *cobra.Command, args []string) error {
4747
return err
4848
}
4949
}
50-
srv, err := dns.Start(udpLocalPort, tcpLocalPort, ipv6)
50+
srv, err := dns.Start(udpLocalPort, tcpLocalPort, ipv6, map[string]string{})
5151
if err != nil {
5252
return err
5353
}

examples/docker.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ containerd:
2323
user: false
2424
provision:
2525
- mode: system
26+
# This script defines the host.docker.internal hostname when hostResolver is disabled.
27+
# It is also needed for lima 0.8.2 and earlier, which does not support hostResolver.hosts.
28+
# Names defined in /etc/hosts inside the VM are not resolved inside containers when
29+
# using the hostResolver; use hostResolver.hosts instead (requires lima 0.8.3 or later).
2630
script: |
2731
#!/bin/sh
2832
sed -i 's/host.lima.internal.*/host.lima.internal host.docker.internal/' /etc/hosts
@@ -56,6 +60,11 @@ probes:
5660
exit 1
5761
fi
5862
hint: See "/var/log/cloud-init-output.log". in the guest
63+
hostResolver:
64+
# hostResolver.hosts requires lima 0.8.3 or later. Names defined here will also
65+
# resolve inside containers, and not just inside the VM itself.
66+
hosts:
67+
host.docker.internal: host.lima.internal
5968
portForwards:
6069
- guestSocket: "/run/user/{{.UID}}/docker.sock"
6170
hostSocket: "{{.Dir}}/sock/docker.sock"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#!/bin/bash
22
set -eux -o pipefail
33

4+
# Define host.lima.internal in case the hostResolver is disabled. When using
5+
# the hostResolver, the name is provided by the lima resolver itself because
6+
# it doesn't have access to /etc/hosts inside the VM.
47
sed -i '/host.lima.internal/d' /etc/hosts
58
echo -e "${LIMA_CIDATA_SLIRP_GATEWAY}\thost.lima.internal" >>/etc/hosts

pkg/hostagent/dns/dns.go

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net"
88
"strings"
99

10+
"github.com/lima-vm/lima/pkg/limayaml"
1011
"github.com/miekg/dns"
1112
"github.com/sirupsen/logrus"
1213
)
@@ -19,6 +20,8 @@ type Handler struct {
1920
clientConfig *dns.ClientConfig
2021
clients []*dns.Client
2122
IPv6 bool
23+
cname map[string]string
24+
ip map[string]net.IP
2225
}
2326

2427
type Server struct {
@@ -44,7 +47,7 @@ func newStaticClientConfig(ips []net.IP) (*dns.ClientConfig, error) {
4447
return dns.ClientConfigFromReader(r)
4548
}
4649

47-
func newHandler(IPv6 bool) (dns.Handler, error) {
50+
func newHandler(IPv6 bool, hosts map[string]string) (dns.Handler, error) {
4851
cc, err := dns.ClientConfigFromFile("/etc/resolv.conf")
4952
if err != nil {
5053
fallbackIPs := []net.IP{net.ParseIP("8.8.8.8"), net.ParseIP("1.1.1.1")}
@@ -62,6 +65,15 @@ func newHandler(IPv6 bool) (dns.Handler, error) {
6265
clientConfig: cc,
6366
clients: clients,
6467
IPv6: IPv6,
68+
cname: make(map[string]string),
69+
ip: make(map[string]net.IP),
70+
}
71+
for host, address := range hosts {
72+
if ip := net.ParseIP(address); ip != nil {
73+
h.ip[host] = ip
74+
} else {
75+
h.cname[host] = limayaml.Cname(address)
76+
}
6577
}
6678
return h, nil
6779
}
@@ -87,10 +99,27 @@ func (h *Handler) handleQuery(w dns.ResponseWriter, req *dns.Msg) {
8799
}
88100
fallthrough
89101
case dns.TypeCNAME, dns.TypeA:
90-
cname, err := net.LookupCNAME(q.Name)
91-
if err != nil {
102+
cname := q.Name
103+
seen := make(map[string]bool)
104+
for {
105+
// break cyclic definition
106+
if seen[cname] {
107+
break
108+
}
109+
if _, ok := h.cname[cname]; ok {
110+
seen[cname] = true
111+
cname = h.cname[cname]
112+
continue
113+
}
92114
break
93115
}
116+
var err error
117+
if _, ok := h.ip[cname]; !ok {
118+
cname, err = net.LookupCNAME(cname)
119+
if err != nil {
120+
break
121+
}
122+
}
94123
if cname != "" && cname != q.Name {
95124
hdr.Rrtype = dns.TypeCNAME
96125
a := &dns.CNAME{
@@ -104,7 +133,13 @@ func (h *Handler) handleQuery(w dns.ResponseWriter, req *dns.Msg) {
104133
break
105134
}
106135
hdr.Name = cname
107-
addrs, err := net.LookupIP(q.Name)
136+
var addrs []net.IP
137+
if _, ok := h.ip[cname]; ok {
138+
addrs = []net.IP{h.ip[cname]}
139+
err = nil
140+
} else {
141+
addrs, err = net.LookupIP(cname)
142+
}
108143
if err == nil && len(addrs) > 0 {
109144
for _, ip := range addrs {
110145
var a dns.RR
@@ -220,8 +255,8 @@ func (h *Handler) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
220255
}
221256
}
222257

223-
func Start(udpLocalPort, tcpLocalPort int, IPv6 bool) (*Server, error) {
224-
h, err := newHandler(IPv6)
258+
func Start(udpLocalPort, tcpLocalPort int, IPv6 bool, hosts map[string]string) (*Server, error) {
259+
h, err := newHandler(IPv6, hosts)
225260
if err != nil {
226261
return nil, err
227262
}

pkg/hostagent/hostagent.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/lima-vm/lima/pkg/hostagent/events"
2828
"github.com/lima-vm/lima/pkg/limayaml"
2929
"github.com/lima-vm/lima/pkg/qemu"
30+
qemuconst "github.com/lima-vm/lima/pkg/qemu/const"
3031
"github.com/lima-vm/lima/pkg/sshutil"
3132
"github.com/lima-vm/lima/pkg/store"
3233
"github.com/lima-vm/lima/pkg/store/filenames"
@@ -249,7 +250,9 @@ func (a *HostAgent) Run(ctx context.Context) error {
249250
}()
250251

251252
if *a.y.HostResolver.Enabled {
252-
dnsServer, err := dns.Start(a.udpDNSLocalPort, a.tcpDNSLocalPort, *a.y.HostResolver.IPv6)
253+
hosts := a.y.HostResolver.Hosts
254+
hosts["host.lima.internal."] = qemuconst.SlirpGateway
255+
dnsServer, err := dns.Start(a.udpDNSLocalPort, a.tcpDNSLocalPort, *a.y.HostResolver.IPv6, hosts)
253256
if err != nil {
254257
return fmt.Errorf("cannot start DNS server: %w", err)
255258
}

pkg/limayaml/default.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,12 @@ hostResolver:
256256
enabled: null
257257
# Default: false
258258
ipv6: null
259+
# Static names can be defined here as an alternative to adding them to the hosts /etc/hosts.
260+
# Values can be either other hostnames, or IP addresses. The host.lima.internal name is
261+
# predefined to specify the gateway address to the host.
262+
hosts:
263+
# guest.name: 127.1.1.1
264+
# host.name: host.lima.internal
259265

260266
# If useHostResolver is false, then the following rules apply for configuring dns:
261267
# Explicitly set DNS addresses for qemu user-mode networking. By default qemu picks *one*

pkg/limayaml/defaults.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"path/filepath"
1111
"runtime"
1212
"strconv"
13+
"strings"
1314
"text/template"
1415

1516
"github.com/lima-vm/lima/pkg/guestagent/api"
@@ -180,6 +181,19 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
180181
y.SSH.ForwardAgent = pointer.Bool(false)
181182
}
182183

184+
hosts := make(map[string]string)
185+
// Values can be either names or IP addresses. Name values are canonicalized in the hostResolver.
186+
for k, v := range d.HostResolver.Hosts {
187+
hosts[Cname(k)] = v
188+
}
189+
for k, v := range y.HostResolver.Hosts {
190+
hosts[Cname(k)] = v
191+
}
192+
for k, v := range o.HostResolver.Hosts {
193+
hosts[Cname(k)] = v
194+
}
195+
y.HostResolver.Hosts = hosts
196+
183197
y.Provision = append(append(o.Provision, y.Provision...), d.Provision...)
184198
for i := range y.Provision {
185199
provision := &y.Provision[i]
@@ -486,3 +500,11 @@ func IsNativeArch(arch Arch) bool {
486500
nativeAARCH64 := arch == AARCH64 && runtime.GOARCH == "arm64"
487501
return nativeX8664 || nativeAARCH64
488502
}
503+
504+
func Cname(host string) string {
505+
host = strings.ToLower(host)
506+
if !strings.HasSuffix(host, ".") {
507+
host += "."
508+
}
509+
return host
510+
}

pkg/limayaml/defaults_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ func TestFillDefault(t *testing.T) {
8989
// All these slices and maps are empty in "builtin". Add minimal entries here to see that
9090
// their values are retained and defaults for their fields are applied correctly.
9191
y = LimaYAML{
92+
HostResolver: HostResolver{
93+
Hosts: map[string]string{
94+
"MY.Host": "host.lima.internal",
95+
},
96+
},
9297
Mounts: []Mount{
9398
{Location: "/tmp"},
9499
},
@@ -119,6 +124,10 @@ func TestFillDefault(t *testing.T) {
119124
}
120125

121126
expect := builtin
127+
expect.HostResolver.Hosts = map[string]string{
128+
"my.host.": "host.lima.internal",
129+
}
130+
122131
expect.Mounts = y.Mounts
123132
expect.Mounts[0].Writable = pointer.Bool(false)
124133
expect.Mounts[0].SSHFS.Cache = pointer.Bool(true)
@@ -195,6 +204,9 @@ func TestFillDefault(t *testing.T) {
195204
HostResolver: HostResolver{
196205
Enabled: pointer.Bool(false),
197206
IPv6: pointer.Bool(true),
207+
Hosts: map[string]string{
208+
"default": "localhost",
209+
},
198210
},
199211
PropagateProxyEnv: pointer.Bool(false),
200212

@@ -248,6 +260,9 @@ func TestFillDefault(t *testing.T) {
248260
expect.Containerd.Archives[0].Arch = *d.Arch
249261
expect.Mounts[0].SSHFS.Cache = pointer.Bool(true)
250262
expect.Mounts[0].SSHFS.FollowSymlinks = pointer.Bool(false)
263+
expect.HostResolver.Hosts = map[string]string{
264+
"default.": d.HostResolver.Hosts["default"],
265+
}
251266

252267
y = LimaYAML{}
253268
FillDefault(&y, &d, &LimaYAML{}, filePath)
@@ -270,6 +285,8 @@ func TestFillDefault(t *testing.T) {
270285
expect.Mounts = append(d.Mounts, y.Mounts...)
271286
expect.Networks = append(d.Networks, y.Networks...)
272287

288+
expect.HostResolver.Hosts["default."] = d.HostResolver.Hosts["default"]
289+
273290
// d.DNS will be ignored, and not appended to y.DNS
274291

275292
// "TWO" does not exist in filledDefaults.Env, so is set from d.Env
@@ -312,6 +329,9 @@ func TestFillDefault(t *testing.T) {
312329
HostResolver: HostResolver{
313330
Enabled: pointer.Bool(false),
314331
IPv6: pointer.Bool(false),
332+
Hosts: map[string]string{
333+
"override.": "underflow",
334+
},
315335
},
316336
PropagateProxyEnv: pointer.Bool(false),
317337

@@ -376,6 +396,9 @@ func TestFillDefault(t *testing.T) {
376396
expect.PortForwards = append(append(o.PortForwards, y.PortForwards...), d.PortForwards...)
377397
expect.Containerd.Archives = append(append(o.Containerd.Archives, y.Containerd.Archives...), d.Containerd.Archives...)
378398

399+
expect.HostResolver.Hosts["default."] = d.HostResolver.Hosts["default"]
400+
expect.HostResolver.Hosts["my.host."] = d.HostResolver.Hosts["host.lima.internal"]
401+
379402
// o.Mounts just makes d.Mounts[0] writable because the Location matches
380403
expect.Mounts = append(d.Mounts, y.Mounts...)
381404
expect.Mounts[0].Writable = pointer.Bool(true)

pkg/limayaml/limayaml.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,9 @@ type Network struct {
136136
}
137137

138138
type HostResolver struct {
139-
Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
140-
IPv6 *bool `yaml:"ipv6,omitempty" json:"ipv6,omitempty"`
139+
Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
140+
IPv6 *bool `yaml:"ipv6,omitempty" json:"ipv6,omitempty"`
141+
Hosts map[string]string `yaml:"hosts,omitempty" json:"hosts,omitempty"`
141142
}
142143

143144
// DEPRECATED types below

0 commit comments

Comments
 (0)