qemu: Add -networking option, with various new alternatives

This follows the model in the hyperkit runner, although the options are
different.

The options are:

- `user`: the existing user mode networking (the default).
- `tap,«device»`: replaces the previous `-tap-device «device»` option.
- `bridge,«name»`: tap device on (preexisting) named bridge.
- `none`: No networking at all.

If not running as root then `bridge` mode requires host configuration
http://wiki.qemu.org/Features/HelperNetworking. TL;DR: you need to `chmod u+s`
the `qemu-bridge-helper` and to whitelist specific bridges in
`/etc/qemu/bridge.conf`.

Pass an explicit virtio nic and configure a random MAC since QEMU seems to use
the same one by default.

In the hyperkit runner the various `networking*` constants become
`hyperkitNetworking*` to avoid namespace clashes (e.g. for `None`). The QEMU
equivalents are `qemuNetworking*`.

Both hyperkit and qemu now support an explicit `-networking default` or
`-networking ''` to make scripting easier.

Signed-off-by: Ian Campbell <ijc@docker.com>
This commit is contained in:
Ian Campbell 2017-07-25 15:26:25 +01:00
parent cb86cdb027
commit 096aec0a19
2 changed files with 91 additions and 22 deletions

View File

@ -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])

View File

@ -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 {