From af037ec36b7b93681d3853c0a610907c8d93d461 Mon Sep 17 00:00:00 2001 From: Balaji Vijayakumar Date: Tue, 11 Jul 2023 13:09:58 +0530 Subject: [PATCH] Support for configurable network address for user-v2 Signed-off-by: Balaji Vijayakumar --- cmd/limactl/usernet.go | 6 + examples/experimental/net-user-v2.yaml | 5 + go.mod | 2 +- hack/test-example.sh | 9 +- pkg/cidata/cidata.go | 44 ++++-- pkg/cidata/cidata_test.go | 6 +- pkg/cidata/template.go | 27 ++-- pkg/networks/config_test.go | 4 +- pkg/networks/const.go | 1 - pkg/networks/networks.TEMPLATE.yaml | 2 + pkg/networks/usernet/client.go | 67 ++++++-- pkg/networks/usernet/config.go | 62 ++++++++ pkg/networks/usernet/config_test.go | 45 ++++++ pkg/networks/usernet/dns.go | 84 ++++++++++ pkg/networks/usernet/dns_test.go | 208 +++++++++++++++++++++++++ pkg/networks/usernet/gvproxy.go | 23 ++- pkg/networks/usernet/recoincile.go | 8 +- pkg/qemu/qemu_driver.go | 11 +- pkg/vz/vm_darwin.go | 15 +- 19 files changed, 566 insertions(+), 63 deletions(-) create mode 100644 pkg/networks/usernet/config_test.go create mode 100644 pkg/networks/usernet/dns.go create mode 100644 pkg/networks/usernet/dns_test.go diff --git a/cmd/limactl/usernet.go b/cmd/limactl/usernet.go index 6a1b608baa42..40428a221082 100644 --- a/cmd/limactl/usernet.go +++ b/cmd/limactl/usernet.go @@ -22,6 +22,7 @@ func newUsernetCommand() *cobra.Command { hostagentCommand.Flags().StringP("endpoint", "e", "", "exposes usernet api(s) on this endpoint") hostagentCommand.Flags().String("listen-qemu", "", "listen for qemu connections") hostagentCommand.Flags().String("listen", "", "listen on a Unix socket and receive Bess-compatible FDs as SCM_RIGHTS messages") + hostagentCommand.Flags().String("subnet", "192.168.5.0/24", "sets subnet value for the usernet network") hostagentCommand.Flags().Int("mtu", 1500, "mtu") return hostagentCommand } @@ -52,6 +53,10 @@ func usernetAction(cmd *cobra.Command, _ []string) error { if err != nil { return err } + subnet, err := cmd.Flags().GetString("subnet") + if err != nil { + return err + } mtu, err := cmd.Flags().GetInt("mtu") if err != nil { @@ -67,5 +72,6 @@ func usernetAction(cmd *cobra.Command, _ []string) error { Endpoint: endpoint, QemuSocket: qemuSocket, FdSocket: fdSocket, + Subnet: subnet, }) } diff --git a/examples/experimental/net-user-v2.yaml b/examples/experimental/net-user-v2.yaml index 70747d6f1322..851e8b7ed651 100644 --- a/examples/experimental/net-user-v2.yaml +++ b/examples/experimental/net-user-v2.yaml @@ -6,6 +6,11 @@ images: - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img" arch: "aarch64" +hostResolver: + # Marking enabled false will make use of system dns / inbuilt dns resolver of usernet for user-v2 network + enabled: false + hosts: + host.docker.internal: host.lima.internal mounts: - location: "~" - location: "/tmp/lima" diff --git a/go.mod b/go.mod index 9777127a854c..755954f73cb0 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Code-Hex/vz/v3 v3.0.6 github.com/alessio/shellescape v1.4.1 + github.com/apparentlymart/go-cidr v1.1.0 github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e github.com/cheggaaa/pb/v3 v3.1.2 github.com/containerd/containerd v1.7.2 @@ -54,7 +55,6 @@ require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/a8m/envsubst v1.4.2 // indirect github.com/alecthomas/participle/v2 v2.0.0 // indirect - github.com/apparentlymart/go-cidr v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/digitalocean/go-libvirt v0.0.0-20220804181439-8648fbde413e // indirect github.com/dimchansky/utfbom v1.1.1 // indirect diff --git a/hack/test-example.sh b/hack/test-example.sh index c7a9b55ff976..e44bd3b36180 100755 --- a/hack/test-example.sh +++ b/hack/test-example.sh @@ -133,7 +133,8 @@ INFO "Testing proxy settings are imported" got=$(limactl shell "$NAME" env | grep FTP_PROXY) # Expected: FTP_PROXY is set in addition to ftp_proxy, localhost is replaced # by the gateway address, and the value is set immediately without a restart -expected="FTP_PROXY=http://192.168.5.2:2121" +gatewayIp=$(limactl shell "$NAME" ip route show 0.0.0.0/0 dev eth0 | cut -d\ -f3) +expected="FTP_PROXY=http://${gatewayIp}:2121" INFO "FTP_PROXY: expected=${expected} got=${got}" if [ "$got" != "$expected" ]; then ERROR "proxy environment variable not set to correct value" @@ -335,10 +336,10 @@ if [[ -n ${CHECKS["user-v2"]} ]]; then secondvm="$NAME-1" "${LIMACTL_CREATE[@]}" "$FILE" --name "$secondvm" limactl start "$secondvm" - guestNewip="$(limactl shell "$secondvm" ip -4 -j addr show dev eth0 | jq -r '.[0].addr_info[0].local')" - INFO "IP of $secondvm is $guestNewip" + secondvmDNS="lima-$secondvm.internal" + INFO "DNS of $secondvm is $secondvmDNS" set -x - if ! limactl shell "$NAME" ping -c 1 "$guestNewip"; then + if ! limactl shell "$NAME" ping -c 1 "$secondvmDNS"; then ERROR "Failed to do vm->vm communication via user-v2" INFO "Stopping \"$secondvm\"" limactl stop "$secondvm" diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go index 819422e5b2e6..f1b34cf5f79e 100644 --- a/pkg/cidata/cidata.go +++ b/pkg/cidata/cidata.go @@ -12,6 +12,8 @@ import ( "strings" "time" + "github.com/lima-vm/lima/pkg/networks/usernet" + "github.com/lima-vm/lima/pkg/networks" "github.com/docker/go-units" @@ -35,7 +37,7 @@ var netLookupIP = func(host string) []net.IP { return ips } -func setupEnv(y *limayaml.LimaYAML) (map[string]string, error) { +func setupEnv(y *limayaml.LimaYAML, args TemplateArgs) (map[string]string, error) { // Start with the proxy variables from the system settings. env, err := osutil.ProxySettings() if err != nil { @@ -74,7 +76,7 @@ func setupEnv(y *limayaml.LimaYAML) (map[string]string, error) { for _, ip := range netLookupIP(u.Hostname()) { if ip.IsLoopback() { - newHost := networks.SlirpGateway + newHost := args.SlirpGateway if u.Port() != "" { newHost = net.JoinHostPort(newHost, u.Port()) } @@ -123,13 +125,35 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort UID: uid, Containerd: Containerd{System: *y.Containerd.System, User: *y.Containerd.User}, SlirpNICName: networks.SlirpNICName, - SlirpGateway: networks.SlirpGateway, - SlirpDNS: networks.SlirpDNS, - SlirpIPAddress: networks.SlirpIPAddress, RosettaEnabled: *y.Rosetta.Enabled, RosettaBinFmt: *y.Rosetta.BinFmt, } + firstUsernetIndex := limayaml.FirstUsernetIndex(y) + var subnet net.IP + + if firstUsernetIndex != -1 { + usernetName := y.Networks[firstUsernetIndex].Lima + subnet, err = usernet.Subnet(usernetName) + if err != nil { + return err + } + args.SlirpGateway = usernet.GatewayIP(subnet) + args.SlirpDNS = usernet.GatewayIP(subnet) + } else { + subnet, err = usernet.ParseSubnet(networks.SlirpNetwork) + if err != nil { + return err + } + args.SlirpGateway = usernet.GatewayIP(subnet) + if *y.VMType == limayaml.VZ { + args.SlirpDNS = usernet.GatewayIP(subnet) + } else { + args.SlirpDNS = usernet.DNSIP(subnet) + } + args.SlirpIPAddress = networks.SlirpIPAddress + } + // change instance id on every boot so network config will be processed again args.IID = fmt.Sprintf("iid-%d", time.Now().Unix()) @@ -209,9 +233,7 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort }) } - slirpMACAddress := limayaml.MACAddress(instDir) - args.Networks = append(args.Networks, Network{MACAddress: slirpMACAddress, Interface: networks.SlirpNICName}) - firstUsernetIndex := limayaml.FirstUsernetIndex(y) + args.Networks = append(args.Networks, Network{MACAddress: limayaml.MACAddress(instDir), Interface: networks.SlirpNICName}) for i, nw := range y.Networks { if i == firstUsernetIndex { continue @@ -219,14 +241,16 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort args.Networks = append(args.Networks, Network{MACAddress: nw.MACAddress, Interface: nw.Interface}) } - args.Env, err = setupEnv(y) + args.Env, err = setupEnv(y, args) if err != nil { return err } if *y.HostResolver.Enabled { args.UDPDNSLocalPort = udpDNSLocalPort args.TCPDNSLocalPort = tcpDNSLocalPort - args.DNSAddresses = append(args.DNSAddresses, networks.SlirpDNS) + args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS) + } else if firstUsernetIndex != -1 || *y.VMType == limayaml.VZ { + args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS) } else if len(y.DNS) > 0 { for _, addr := range y.DNS { args.DNSAddresses = append(args.DNSAddresses, addr.String()) diff --git a/pkg/cidata/cidata_test.go b/pkg/cidata/cidata_test.go index 2ff729dfc8fc..6b62df3c4b59 100644 --- a/pkg/cidata/cidata_test.go +++ b/pkg/cidata/cidata_test.go @@ -48,7 +48,8 @@ func TestSetupEnv(t *testing.T) { t.Run(httpProxy.Host, func(t *testing.T) { envKey := "http_proxy" envValue := httpProxy.String() - envs, err := setupEnv(&limayaml.LimaYAML{PropagateProxyEnv: pointer.Bool(false), Env: map[string]string{envKey: envValue}}) + templateArgs := TemplateArgs{SlirpGateway: networks.SlirpGateway} + envs, err := setupEnv(&limayaml.LimaYAML{PropagateProxyEnv: pointer.Bool(false), Env: map[string]string{envKey: envValue}}, templateArgs) assert.NilError(t, err) assert.Equal(t, envs[envKey], strings.ReplaceAll(envValue, httpProxy.Hostname(), networks.SlirpGateway)) }) @@ -58,7 +59,8 @@ func TestSetupEnv(t *testing.T) { func TestSetupInvalidEnv(t *testing.T) { envKey := "http_proxy" envValue := "://localhost:8080" - envs, err := setupEnv(&limayaml.LimaYAML{PropagateProxyEnv: pointer.Bool(false), Env: map[string]string{envKey: envValue}}) + templateArgs := TemplateArgs{SlirpGateway: networks.SlirpGateway} + envs, err := setupEnv(&limayaml.LimaYAML{PropagateProxyEnv: pointer.Bool(false), Env: map[string]string{envKey: envValue}}, templateArgs) assert.NilError(t, err) assert.Equal(t, envs[envKey], envValue) } diff --git a/pkg/cidata/template.go b/pkg/cidata/template.go index 37703e6860e2..d9f0a1a7b40e 100644 --- a/pkg/cidata/template.go +++ b/pkg/cidata/template.go @@ -50,19 +50,20 @@ type Disk struct { Device string } type TemplateArgs struct { - Name string // instance name - IID string // instance id - User string // user name - UID int - SSHPubKeys []string - Mounts []Mount - MountType string - Disks []Disk - Containerd Containerd - Networks []Network - SlirpNICName string - SlirpGateway string - SlirpDNS string + Name string // instance name + IID string // instance id + User string // user name + UID int + SSHPubKeys []string + Mounts []Mount + MountType string + Disks []Disk + Containerd Containerd + Networks []Network + SlirpNICName string + SlirpGateway string + SlirpDNS string + //Deprecated SlirpIPAddress string UDPDNSLocalPort int TCPDNSLocalPort int diff --git a/pkg/networks/config_test.go b/pkg/networks/config_test.go index 7c8ff0e9786e..956d3a351154 100644 --- a/pkg/networks/config_test.go +++ b/pkg/networks/config_test.go @@ -15,7 +15,7 @@ func TestFillDefault(t *testing.T) { userNet := newYaml.Networks[ModeUserV2] assert.Equal(t, userNet.Mode, ModeUserV2) assert.Equal(t, userNet.Interface, "") - assert.DeepEqual(t, userNet.NetMask, net.IP{}) - assert.DeepEqual(t, userNet.Gateway, net.IP{}) + assert.DeepEqual(t, userNet.NetMask, net.ParseIP("255.255.255.0")) + assert.DeepEqual(t, userNet.Gateway, net.ParseIP("192.168.104.1")) assert.DeepEqual(t, userNet.DHCPEnd, net.IP{}) } diff --git a/pkg/networks/const.go b/pkg/networks/const.go index b1aa47ed3c64..16671fd6208a 100644 --- a/pkg/networks/const.go +++ b/pkg/networks/const.go @@ -5,6 +5,5 @@ const ( // CIDR is intentionally hardcoded to 192.168.5.0/24, as each of QEMU has its own independent slirp network. SlirpNetwork = "192.168.5.0/24" SlirpGateway = "192.168.5.2" - SlirpDNS = "192.168.5.3" SlirpIPAddress = "192.168.5.15" ) diff --git a/pkg/networks/networks.TEMPLATE.yaml b/pkg/networks/networks.TEMPLATE.yaml index 0916473fa5c3..41540adf2957 100644 --- a/pkg/networks/networks.TEMPLATE.yaml +++ b/pkg/networks/networks.TEMPLATE.yaml @@ -24,6 +24,8 @@ group: everyone networks: user-v2: mode: user-v2 + gateway: 192.168.104.1 + netmask: 255.255.255.0 # user-v2 network is experimental network mode which supports all functionalities of default usernet network and also allows vm -> vm communication. # Doesn't support configuration of custom gateway; hardcoded to 192.168.5.0/24 shared: diff --git a/pkg/networks/usernet/client.go b/pkg/networks/usernet/client.go index 2187038f1e89..eb5312664ea1 100644 --- a/pkg/networks/usernet/client.go +++ b/pkg/networks/usernet/client.go @@ -9,6 +9,9 @@ import ( "net/http" "time" + "github.com/lima-vm/lima/pkg/driver" + "github.com/lima-vm/lima/pkg/limayaml" + gvproxyclient "github.com/containers/gvisor-tap-vsock/pkg/client" "github.com/containers/gvisor-tap-vsock/pkg/types" ) @@ -19,6 +22,23 @@ type Client struct { client *http.Client delegate *gvproxyclient.Client base string + subnet net.IP +} + +func (c *Client) ConfigureDriver(driver *driver.BaseDriver) error { + macAddress := limayaml.MACAddress(driver.Instance.Dir) + ipAddress, err := c.ResolveIPAddress(macAddress) + if err != nil { + return err + } + err = c.ResolveAndForwardSSH(ipAddress, driver.SSHLocalPort) + if err != nil { + return err + } + var hosts = driver.Yaml.HostResolver.Hosts + hosts[fmt.Sprintf("lima-%s.internal", driver.Instance.Name)] = ipAddress + err = c.AddDNSHosts(hosts) + return err } func (c *Client) UnExposeSSH(sshPort int) error { @@ -28,30 +48,46 @@ func (c *Client) UnExposeSSH(sshPort int) error { }) } -func (c *Client) ResolveAndForwardSSH(vmMacAddr string, sshPort int) error { +func (c *Client) AddDNSHosts(hosts map[string]string) error { + hosts["host.lima.internal"] = GatewayIP(c.subnet) + zones := extractZones(hosts) + for _, zone := range zones { + err := c.delegate.AddDNS(&zone) + if err != nil { + return err + } + } + return nil +} + +func (c *Client) ResolveAndForwardSSH(ipAddr string, sshPort int) error { + err := c.delegate.Expose(&types.ExposeRequest{ + Local: fmt.Sprintf("127.0.0.1:%d", sshPort), + Remote: fmt.Sprintf("%s:22", ipAddr), + Protocol: "tcp", + }) + if err != nil { + return err + } + return nil +} + +func (c *Client) ResolveIPAddress(vmMacAddr string) (string, error) { timeout := time.After(2 * time.Minute) ticker := time.NewTicker(500 * time.Millisecond) for { select { case <-timeout: - return errors.New("usernet unable to resolve IP for SSH forwarding") + return "", errors.New("usernet unable to resolve IP for SSH forwarding") case <-ticker.C: leases, err := c.leases() if err != nil { - return err + return "", err } for ipAddr, leaseAddr := range leases { if vmMacAddr == leaseAddr { - err = c.delegate.Expose(&types.ExposeRequest{ - Local: fmt.Sprintf("127.0.0.1:%d", sshPort), - Remote: fmt.Sprintf("%s:22", ipAddr), - Protocol: "tcp", - }) - if err != nil { - return err - } - return nil + return ipAddr, nil } } } @@ -75,11 +111,11 @@ func (c *Client) leases() (map[string]string, error) { return leases, nil } -func NewClient(endpointSock string) *Client { - return create(endpointSock, "http://lima") +func NewClient(endpointSock string, subnet net.IP) *Client { + return create(endpointSock, subnet, "http://lima") } -func create(sock string, base string) *Client { +func create(sock string, subnet net.IP, base string) *Client { client := &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { @@ -92,5 +128,6 @@ func create(sock string, base string) *Client { client: client, delegate: delegate, base: base, + subnet: subnet, } } diff --git a/pkg/networks/usernet/config.go b/pkg/networks/usernet/config.go index d97cc18d8a83..021329913766 100644 --- a/pkg/networks/usernet/config.go +++ b/pkg/networks/usernet/config.go @@ -2,8 +2,12 @@ package usernet import ( "fmt" + "net" "path/filepath" + "github.com/apparentlymart/go-cidr/cidr" + "github.com/lima-vm/lima/pkg/networks" + "github.com/lima-vm/lima/pkg/store/dirnames" ) @@ -37,3 +41,61 @@ func PIDFile(name string) (string, error) { } return filepath.Join(dir, name, fmt.Sprintf("usernet_%s.pid", name)), nil } + +// SubnetCIDR returns a subnet in form of net.IPNet for the given network name. +func SubnetCIDR(name string) (*net.IPNet, error) { + config, err := networks.Config() + if err != nil { + return nil, err + } + err = config.Check(name) + if err != nil { + return nil, err + } + _, ipNet, err := netmaskToCidr(config.Networks[name].Gateway, config.Networks[name].NetMask) + if err != nil { + return nil, err + } + return ipNet, err +} + +// Subnet returns a subnet net.IP for the given network name. +func Subnet(name string) (net.IP, error) { + config, err := networks.Config() + if err != nil { + return nil, err + } + err = config.Check(name) + if err != nil { + return nil, err + } + _, ipNet, err := netmaskToCidr(config.Networks[name].Gateway, config.Networks[name].NetMask) + if err != nil { + return nil, err + } + return ipNet.IP, err +} + +// ParseSubnet converts subnet string to net.IP +func ParseSubnet(subnet string) (net.IP, error) { + subnetIP, _, err := net.ParseCIDR(subnet) + if err != nil { + return nil, err + } + return subnetIP, nil +} + +// GatewayIP returns the 2nd IP for the given subnet +func GatewayIP(subnet net.IP) string { + return cidr.Inc(cidr.Inc(subnet)).String() +} + +// DNSIP returns the 3rd IP for the given subnet +func DNSIP(subnet net.IP) string { + return cidr.Inc(cidr.Inc(cidr.Inc(subnet))).String() +} + +func netmaskToCidr(baseIP net.IP, netMask net.IP) (net.IP, *net.IPNet, error) { + size, _ := net.IPMask(netMask.To4()).Size() + return net.ParseCIDR(fmt.Sprintf("%s/%d", baseIP.String(), size)) +} diff --git a/pkg/networks/usernet/config_test.go b/pkg/networks/usernet/config_test.go new file mode 100644 index 000000000000..3375b6728ddf --- /dev/null +++ b/pkg/networks/usernet/config_test.go @@ -0,0 +1,45 @@ +package usernet + +import ( + "net" + "testing" + + "github.com/lima-vm/lima/pkg/networks" + + "gotest.tools/v3/assert" +) + +func TestUsernetConfig(t *testing.T) { + + t.Run("parse subnet", func(t *testing.T) { + subnet, err := ParseSubnet(networks.SlirpNetwork) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, subnet.String(), "192.168.5.0") + }) + + t.Run("verify dns ip", func(t *testing.T) { + subnet, _, err := net.ParseCIDR(networks.SlirpNetwork) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, DNSIP(subnet), "192.168.5.3") + }) + + t.Run("verify gateway ip", func(t *testing.T) { + subnet, _, err := net.ParseCIDR(networks.SlirpNetwork) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, GatewayIP(subnet), "192.168.5.2") + }) + + t.Run("verify subnet via config ip", func(t *testing.T) { + subnet, err := Subnet("user-v2") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, subnet.String(), "192.168.104.0") + }) +} diff --git a/pkg/networks/usernet/dns.go b/pkg/networks/usernet/dns.go new file mode 100644 index 000000000000..bb4d0d34bd83 --- /dev/null +++ b/pkg/networks/usernet/dns.go @@ -0,0 +1,84 @@ +// From https://raw.githubusercontent.com/abiosoft/colima/v0.5.5/daemon/process/gvproxy/dnshosts.go + +package usernet + +import ( + "net" + "strings" + + "github.com/containers/gvisor-tap-vsock/pkg/types" +) + +func extractZones(hosts hostMap) (zones []types.Zone) { + list := make(map[string]types.Zone) + + for host := range hosts { + h := zoneHost(host) + + zone := types.Zone{Name: h.name()} + if existingZone, ok := list[h.name()]; ok { + zone = existingZone + } + + if h.recordName() == "" { + if zone.DefaultIP == nil { + zone.DefaultIP = hosts.hostIP(host) + } + } else { + zone.Records = append(zone.Records, types.Record{ + Name: h.recordName(), + IP: hosts.hostIP(host), + }) + } + + list[h.name()] = zone + } + + for _, zone := range list { + zones = append(zones, zone) + } + return +} + +type hostMap map[string]string + +func (z hostMap) hostIP(host string) net.IP { + for { + // check if host entry exists + h, ok := z[host] + if !ok || h == "" { + return nil + } + + // if it's a valid ip, return + if ip := net.ParseIP(h); ip != nil { + return ip + } + + // otherwise, a string i.e. another host + // loop through the process again. + host = h + } +} + +type zoneHost string + +func (z zoneHost) name() string { + i := z.dotIndex() + if i < 0 { + return string(z) + } + return string(z)[i+1:] + "." +} + +func (z zoneHost) recordName() string { + i := z.dotIndex() + if i < 0 { + return "" + } + return string(z)[:i] +} + +func (z zoneHost) dotIndex() int { + return strings.LastIndex(string(z), ".") +} diff --git a/pkg/networks/usernet/dns_test.go b/pkg/networks/usernet/dns_test.go new file mode 100644 index 000000000000..2cdb5092dcaf --- /dev/null +++ b/pkg/networks/usernet/dns_test.go @@ -0,0 +1,208 @@ +// From https://raw.githubusercontent.com/abiosoft/colima/v0.5.5/daemon/process/gvproxy/dnshosts_test.go + +package usernet + +import ( + "fmt" + "net" + "sort" + "testing" + + "github.com/containers/gvisor-tap-vsock/pkg/types" +) + +func Test_hostsMapIP(t *testing.T) { + hosts := hostMap{} + hosts["sample"] = "1.1.1.1" + hosts["another.sample"] = "1.2.2.1" + hosts["google.com"] = "8.8.8.8" + hosts["google.ae"] = "google.com" + hosts["google.ie"] = "google.ae" + + tests := []struct { + host string + want net.IP + }{ + {host: "sample", want: net.ParseIP("1.1.1.1")}, + {host: "another.sample", want: net.ParseIP("1.2.2.1")}, + {host: "google.com", want: net.ParseIP("8.8.8.8")}, + {host: "google.ae", want: net.ParseIP("8.8.8.8")}, + {host: "google.ie", want: net.ParseIP("8.8.8.8")}, + {host: "google.sample", want: nil}, + } + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + got := hosts.hostIP(tt.host) + if !got.Equal(tt.want) { + t.Errorf("hostsMapIP() = %v, want %v", got, tt.want) + return + } + }) + } +} + +func Test_zoneHost(t *testing.T) { + type val struct { + name string + recordName string + } + tests := []struct { + host zoneHost + want val + }{ + {}, // test for empty value as well + {host: "sample", want: val{name: "sample"}}, + {host: "another.sample", want: val{name: "sample.", recordName: "another"}}, + {host: "another.sample.com", want: val{name: "com.", recordName: "another.sample"}}, + {host: "a.c", want: val{name: "c.", recordName: "a"}}, + {host: "a.b.c.d", want: val{name: "d.", recordName: "a.b.c"}}, + } + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + got := val{ + name: tt.host.name(), + recordName: tt.host.recordName(), + } + if got != tt.want { + t.Errorf("host = %+v, want %+v", got, tt.want) + return + } + }) + } +} + +func Test_extractZones(t *testing.T) { + equalZones := func(za, zb []types.Zone) bool { + find := func(list []types.Zone, name string) (types.Zone, bool) { + for _, z := range list { + if z.Name == name { + return z, true + } + } + return types.Zone{}, false + } + equal := func(a, b types.Zone) bool { + if a.Name != b.Name { + return false + } + if !a.DefaultIP.Equal(b.DefaultIP) { + return false + } + for i := range a.Records { + a, b := a.Records[i], b.Records[i] + if !a.IP.Equal(b.IP) { + return false + } + if a.Name != b.Name { + return false + } + } + + return true + } + + for _, a := range za { + b, ok := find(zb, a.Name) + if !ok { + return false + } + if !equal(a, b) { + return false + } + } + return true + } + + hosts := hostMap{ + "google.com": "8.8.4.4", + "local.google.com": "8.8.8.8", + "google.ae": "google.com", + "localhost": "127.0.0.1", + "host.lima.internal": "192.168.5.2", + "host.docker.internal": "host.lima.internal", + } + + tests := []struct { + wantZones []types.Zone + }{ + { + wantZones: []types.Zone{ + { + Name: "ae.", + Records: []types.Record{ + {Name: "google", IP: net.ParseIP("8.8.4.4")}, + }, + }, + { + Name: "com.", + Records: []types.Record{ + {Name: "google", IP: net.ParseIP("8.8.4.4")}, + {Name: "local.google", IP: net.ParseIP("8.8.8.8")}, + }, + }, + { + Name: "internal.", + Records: []types.Record{ + {Name: "host.docker", IP: net.ParseIP("192.168.5.2")}, + {Name: "host.lima", IP: net.ParseIP("192.168.5.2")}, + }, + }, + { + Name: "localhost", + DefaultIP: net.ParseIP("127.0.0.1"), + }, + }, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + gotZones := extractZones(hosts) + for _, zone := range gotZones { + sort.Sort(recordSorter(zone.Records)) + } + sort.Sort(zoneSorter(gotZones)) + + if !equalZones(gotZones, tt.wantZones) { + t.Errorf("extractZones() = %+v, want %+v", gotZones, tt.wantZones) + } + }) + } +} + +var _ sort.Interface = (recordSorter)(nil) +var _ sort.Interface = (zoneSorter)(nil) + +type recordSorter []types.Record + +// Len implements sort.Interface +func (r recordSorter) Len() int { + return len(r) +} + +// Less implements sort.Interface +func (r recordSorter) Less(i int, j int) bool { + return r[i].Name < r[j].Name +} + +// Swap implements sort.Interface +func (r recordSorter) Swap(i int, j int) { + r[i], r[j] = r[j], r[i] +} + +type zoneSorter []types.Zone + +// Len implements sort.Interface +func (z zoneSorter) Len() int { + return len(z) +} + +// Less implements sort.Interface +func (z zoneSorter) Less(i int, j int) bool { + return z[i].Name < z[j].Name +} + +// Swap implements sort.Interface +func (z zoneSorter) Swap(i int, j int) { + z[i], z[j] = z[j], z[i] +} diff --git a/pkg/networks/usernet/gvproxy.go b/pkg/networks/usernet/gvproxy.go index 6a5f8eae4379..eb45bdbc72ee 100644 --- a/pkg/networks/usernet/gvproxy.go +++ b/pkg/networks/usernet/gvproxy.go @@ -11,8 +11,6 @@ import ( "strings" "time" - "github.com/lima-vm/lima/pkg/networks" - "github.com/balajiv113/fd" "github.com/containers/gvisor-tap-vsock/pkg/transport" "github.com/containers/gvisor-tap-vsock/pkg/types" @@ -28,6 +26,8 @@ type GVisorNetstackOpts struct { FdSocket string Endpoint string + Subnet string + Async bool DefaultLeases map[string]string @@ -40,13 +40,20 @@ var ( func StartGVisorNetstack(ctx context.Context, gVisorOpts *GVisorNetstackOpts) error { opts = gVisorOpts + ip, err := ParseSubnet(opts.Subnet) + if err != nil { + return err + } + gatewayIP := GatewayIP(ip) + leases := map[string]string{} if opts.DefaultLeases != nil { for k, v := range opts.DefaultLeases { leases[k] = v } } - leases[networks.SlirpGateway] = "5a:94:ef:e4:0c:df" + leases[gatewayIP] = "5a:94:ef:e4:0c:df" + // The way gvisor-tap-vsock implemented slirp is different from tradition SLIRP, // - GatewayIP handling all request, also answers DNS queries // - based on NAT configuration, gateway forwards and translates calls to host @@ -56,21 +63,21 @@ func StartGVisorNetstack(ctx context.Context, gVisorOpts *GVisorNetstackOpts) er config := types.Configuration{ Debug: false, MTU: opts.MTU, - Subnet: networks.SlirpNetwork, - GatewayIP: networks.SlirpDNS, + Subnet: opts.Subnet, + GatewayIP: gatewayIP, GatewayMacAddress: "5a:94:ef:e4:0c:dd", DHCPStaticLeases: leases, Forwards: map[string]string{}, DNS: []types.Zone{}, DNSSearchDomains: searchDomains(), NAT: map[string]string{ - networks.SlirpGateway: "127.0.0.1", + gatewayIP: "127.0.0.1", }, - GatewayVirtualIPs: []string{networks.SlirpGateway}, + GatewayVirtualIPs: []string{gatewayIP}, } groupErrs, ctx := errgroup.WithContext(ctx) - err := run(ctx, groupErrs, &config) + err = run(ctx, groupErrs, &config) if err != nil { return err } diff --git a/pkg/networks/usernet/recoincile.go b/pkg/networks/usernet/recoincile.go index 115bd9fe5341..a6131dc3522e 100644 --- a/pkg/networks/usernet/recoincile.go +++ b/pkg/networks/usernet/recoincile.go @@ -53,6 +53,11 @@ func Start(ctx context.Context, name string) error { return err } + subnet, err := SubnetCIDR(name) + if err != nil { + return err + } + err = lockutil.WithDirLock(usernetDir, func() error { self, err := os.Executable() if err != nil { @@ -61,7 +66,8 @@ func Start(ctx context.Context, name string) error { args := []string{"usernet", "-p", pidFile, "-e", endpointSock, "--listen-qemu", qemuSock, - "--listen", fdSock} + "--listen", fdSock, + "--subnet", subnet.String()} cmd := exec.CommandContext(ctx, self, args...) stdoutPath := filepath.Join(usernetDir, fmt.Sprintf("%s.%s.%s.log", "usernet", name, "stdout")) diff --git a/pkg/qemu/qemu_driver.go b/pkg/qemu/qemu_driver.go index 00867246d88a..35637aa51593 100644 --- a/pkg/qemu/qemu_driver.go +++ b/pkg/qemu/qemu_driver.go @@ -192,7 +192,10 @@ func (l *LimaQemuDriver) Start(ctx context.Context) (chan error, error) { go func() { if usernetIndex := limayaml.FirstUsernetIndex(l.Yaml); usernetIndex != -1 { client := newUsernetClient(l.Yaml.Networks[usernetIndex].Lima) - err = client.ResolveAndForwardSSH(limayaml.MACAddress(l.Instance.Dir), l.SSHLocalPort) + err := client.ConfigureDriver(l.BaseDriver) + if err != nil { + l.qWaitCh <- err + } } }() return l.qWaitCh, nil @@ -353,7 +356,11 @@ func newUsernetClient(nwName string) *usernet.Client { if err != nil { return nil } - return usernet.NewClient(endpointSock) + subnet, err := usernet.Subnet(nwName) + if err != nil { + return nil + } + return usernet.NewClient(endpointSock, subnet) } func logPipeRoutine(r io.Reader, header string) { diff --git a/pkg/vz/vm_darwin.go b/pkg/vz/vm_darwin.go index 2d08e5e5915b..42aec600bd42 100644 --- a/pkg/vz/vm_darwin.go +++ b/pkg/vz/vm_darwin.go @@ -96,7 +96,7 @@ func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWra filesToRemove[pidFile] = struct{}{} logrus.Info("[VZ] - vm state change: running") - err := usernetClient.ResolveAndForwardSSH(limayaml.MACAddress(driver.Instance.Dir), driver.SSHLocalPort) + err := usernetClient.ConfigureDriver(driver) if err != nil { errCh <- err } @@ -133,17 +133,24 @@ func startUsernet(ctx context.Context, driver *driver.BaseDriver) (*usernet.Clie DefaultLeases: map[string]string{ networks.SlirpIPAddress: limayaml.MACAddress(driver.Instance.Dir), }, + Subnet: networks.SlirpNetwork, }) if err != nil { return nil, err } - return usernet.NewClient(endpointSock), nil + subnetIP, err := usernet.ParseSubnet(networks.SlirpNetwork) + return usernet.NewClient(endpointSock, subnetIP), nil } - endpointSock, err := usernet.Sock(driver.Yaml.Networks[firstUsernetIndex].Lima, usernet.EndpointSock) + nwName := driver.Yaml.Networks[firstUsernetIndex].Lima + endpointSock, err := usernet.Sock(nwName, usernet.EndpointSock) if err != nil { return nil, err } - return usernet.NewClient(endpointSock), nil + subnetIP, err := usernet.Subnet(nwName) + if err != nil { + return nil, err + } + return usernet.NewClient(endpointSock, subnetIP), nil } func createVM(driver *driver.BaseDriver) (*vz.VirtualMachine, error) {