diff --git a/src/cmd/linuxkit/run_hyperkit.go b/src/cmd/linuxkit/run_hyperkit.go index f0f3466bc..d13a3cef2 100644 --- a/src/cmd/linuxkit/run_hyperkit.go +++ b/src/cmd/linuxkit/run_hyperkit.go @@ -17,10 +17,11 @@ import ( ) const ( - networkingNone string = "none" - networkingDockerForMac = "docker-for-mac" - networkingVPNKit = "vpnkit" - networkingVMNet = "vmnet" + hyperkitNetworkingNone string = "none" + hyperkitNetworkingDockerForMac = "docker-for-mac" + hyperkitNetworkingVPNKit = "vpnkit" + hyperkitNetworkingVMNet = "vmnet" + hyperkitNetworkingDefault = hyperkitNetworkingDockerForMac ) // Process the run arguments and execute run @@ -43,7 +44,7 @@ func runHyperKit(args []string) { ipStr := flags.String("ip", "", "IP address for the VM") state := flags.String("state", "", "Path to directory to keep VM state in") vsockports := flags.String("vsock-ports", "", "List of vsock ports to forward from the guest on startup (comma separated). A unix domain socket for each port will be created in the state directory") - networking := flags.String("networking", networkingDockerForMac, "Networking mode. Valid options are 'docker-for-mac', 'vpnkit[,socket-path]', 'vmnet' and 'none'. 'docker-for-mac' connects to the network used by Docker for Mac. 'vpnkit' connects to the VPNKit socket specified. If socket-path is omitted a new VPNKit instance will be started and 'vpnkit_eth.sock' will be created in the state directory. 'vmnet' uses the Apple vmnet framework, requires root/sudo. 'none' disables networking.`") + networking := flags.String("networking", hyperkitNetworkingDefault, "Networking mode. Valid options are 'default', 'docker-for-mac', 'vpnkit[,socket-path]', 'vmnet' and 'none'. 'docker-for-mac' connects to the network used by Docker for Mac. 'vpnkit' connects to the VPNKit socket specified. If socket-path is omitted a new VPNKit instance will be started and 'vpnkit_eth.sock' will be created in the state directory. 'vmnet' uses the Apple vmnet framework, requires root/sudo. 'none' disables networking.`") if err := flags.Parse(args); err != nil { log.Fatal("Unable to parse args") @@ -130,12 +131,16 @@ func runHyperKit(args []string) { // Select network mode var vpnKitProcess *os.Process + if *networking == "" || *networking == "default" { + dflt := hyperkitNetworkingDefault + networking = &dflt + } netMode := strings.SplitN(*networking, ",", 2) switch netMode[0] { - case networkingDockerForMac: + case hyperkitNetworkingDockerForMac: h.VPNKitSock = filepath.Join(os.Getenv("HOME"), "Library/Containers/com.docker.docker/Data/s50") - case networkingVPNKit: + case hyperkitNetworkingVPNKit: if len(netMode) > 1 { // Socket path specified, try to use existing VPNKit instance h.VPNKitSock = netMode[1] @@ -161,10 +166,10 @@ func runHyperKit(args []string) { // VSOCK port 62373 is used to pass traffic from host->guest h.VSockPorts = append(h.VSockPorts, 62373) } - case networkingVMNet: + case hyperkitNetworkingVMNet: h.VPNKitSock = "" h.VMNet = true - case networkingNone: + case hyperkitNetworkingNone: h.VPNKitSock = "" default: log.Fatalf("Invalid networking mode: %s", netMode[0]) diff --git a/src/cmd/linuxkit/run_qemu.go b/src/cmd/linuxkit/run_qemu.go index 05f6bf298..59fd1fa46 100644 --- a/src/cmd/linuxkit/run_qemu.go +++ b/src/cmd/linuxkit/run_qemu.go @@ -1,9 +1,11 @@ package main import ( + "crypto/rand" "flag" "fmt" "io/ioutil" + "net" "os" "os/exec" "path/filepath" @@ -36,10 +38,18 @@ type QemuConfig struct { QemuBinPath string QemuImgPath string PublishedPorts []string - TapDevice string + NetdevConfig string UUID uuid.UUID } +const ( + qemuNetworkingNone string = "none" + qemuNetworkingUser = "user" + qemuNetworkingTap = "tap" + qemuNetworkingBridge = "bridge" + qemuNetworkingDefault = qemuNetworkingUser +) + func haveKVM() bool { _, err := os.Stat("/dev/kvm") return !os.IsNotExist(err) @@ -58,6 +68,20 @@ func envOverrideBool(env string, b *bool) { } } +func generateMAC() net.HardwareAddr { + mac := make([]byte, 6) + n, err := rand.Read(mac) + if err != nil { + log.WithError(err).Fatal("failed to generate random mac address") + } + if n != 6 { + log.WithError(err).Fatal("generated %d bytes for random mac address", n) + } + mac[0] &^= 0x01 // Clear multicast bit + mac[0] |= 0x2 // Set locally administered bit + return net.HardwareAddr(mac) +} + func runQemu(args []string) { invoked := filepath.Base(os.Args[0]) flags := flag.NewFlagSet("qemu", flag.ExitOnError) @@ -67,6 +91,10 @@ func runQemu(args []string) { fmt.Printf("\n") fmt.Printf("Options:\n") flags.PrintDefaults() + fmt.Printf("\n") + fmt.Printf("If not running as root note that '-networking bridge,br0' requires a\n") + fmt.Printf("setuid network helper and appropriate host configuration, see\n") + fmt.Printf("http://wiki.qemu.org/Features/HelperNetworking.\n") } // Display flags @@ -100,9 +128,11 @@ func runQemu(args []string) { // Generate UUID, so that /sys/class/dmi/id/product_uuid is populated vmUUID := uuid.NewV4() + // Networking + networking := flags.String("networking", qemuNetworkingDefault, "Networking mode. Valid options are 'default', 'user', 'bridge[,name]', tap[,name] and 'none'. 'user' uses QEMUs userspace networking. 'bridge' connects to a preexisting bridge. 'tap' uses a prexisting tap device. 'none' disables networking.`") + publishFlags := multipleFlag{} flags.Var(&publishFlags, "publish", "Publish a vm's port(s) to the host (default [])") - tapDevice := flags.String("tap-device", "", "Tap device to use as eth0 (optional)") if err := flags.Parse(args); err != nil { log.Fatal("Unable to parse args") @@ -206,6 +236,40 @@ func runQemu(args []string) { if *isoBoot && isoPath != "" { log.Fatalf("metadata and ISO boot currently cannot coexist") } + if *networking == "" || *networking == "default" { + dflt := qemuNetworkingDefault + networking = &dflt + } + netMode := strings.SplitN(*networking, ",", 2) + + var netdevConfig string + switch netMode[0] { + case qemuNetworkingUser: + netdevConfig = "user" + case qemuNetworkingTap: + if len(netMode) != 2 { + log.Fatalf("Not enough arugments for %q networking mode", qemuNetworkingTap) + } + if len(publishFlags) != 0 { + log.Fatalf("Port publishing requires %q networking mode", qemuNetworkingUser) + } + netdevConfig = fmt.Sprintf("tap,ifname=%s,script=no,downscript=no", netMode[1]) + case qemuNetworkingBridge: + if len(netMode) != 2 { + log.Fatalf("Not enough arugments for %q networking mode", qemuNetworkingBridge) + } + if len(publishFlags) != 0 { + log.Fatalf("Port publishing requires %q networking mode", qemuNetworkingUser) + } + netdevConfig = fmt.Sprintf("bridge,br=%s", netMode[1]) + case qemuNetworkingNone: + if len(publishFlags) != 0 { + log.Fatalf("Port publishing requires %q networking mode", qemuNetworkingUser) + } + netdevConfig = "" + default: + log.Fatalf("Invalid networking mode: %s", netMode[0]) + } config := QemuConfig{ Path: path, @@ -222,7 +286,7 @@ func runQemu(args []string) { KVM: *enableKVM, Containerized: *qemuContainerized, PublishedPorts: publishFlags, - TapDevice: *tapDevice, + NetdevConfig: netdevConfig, UUID: vmUUID, } @@ -448,19 +512,16 @@ func buildQemuCmdline(config QemuConfig) (QemuConfig, []string) { } } - if config.PublishedPorts != nil && len(config.PublishedPorts) > 0 { + if config.NetdevConfig == "" { + qemuArgs = append(qemuArgs, "-net", "none") + } else { + mac := generateMAC() + qemuArgs = append(qemuArgs, "-net", "nic,model=virtio,macaddr="+mac.String()) forwardings, err := buildQemuForwardings(config.PublishedPorts, config.Containerized) if err != nil { log.Error(err) } - qemuArgs = append(qemuArgs, "-net", forwardings) - qemuArgs = append(qemuArgs, "-net", "nic") - } - - if config.TapDevice != "" { - qemuArgs = append(qemuArgs, "-net", "nic,model=virtio") - tapArg := fmt.Sprintf("tap,ifname=%s,script=no,downscript=no", config.TapDevice) - qemuArgs = append(qemuArgs, "-net", tapArg) + qemuArgs = append(qemuArgs, "-net", config.NetdevConfig+forwardings) } if config.GUI != true { @@ -554,7 +615,10 @@ func splitPublish(publish string) (publishedPorts, error) { } func buildQemuForwardings(publishFlags multipleFlag, containerized bool) (string, error) { - forwardings := "user" + if len(publishFlags) == 0 { + return "", nil + } + var forwardings string for _, publish := range publishFlags { p, err := splitPublish(publish) if err != nil {