mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 17:49:10 +00:00
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:
parent
cb86cdb027
commit
096aec0a19
@ -17,10 +17,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
networkingNone string = "none"
|
hyperkitNetworkingNone string = "none"
|
||||||
networkingDockerForMac = "docker-for-mac"
|
hyperkitNetworkingDockerForMac = "docker-for-mac"
|
||||||
networkingVPNKit = "vpnkit"
|
hyperkitNetworkingVPNKit = "vpnkit"
|
||||||
networkingVMNet = "vmnet"
|
hyperkitNetworkingVMNet = "vmnet"
|
||||||
|
hyperkitNetworkingDefault = hyperkitNetworkingDockerForMac
|
||||||
)
|
)
|
||||||
|
|
||||||
// Process the run arguments and execute run
|
// Process the run arguments and execute run
|
||||||
@ -43,7 +44,7 @@ func runHyperKit(args []string) {
|
|||||||
ipStr := flags.String("ip", "", "IP address for the VM")
|
ipStr := flags.String("ip", "", "IP address for the VM")
|
||||||
state := flags.String("state", "", "Path to directory to keep VM state in")
|
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")
|
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 {
|
if err := flags.Parse(args); err != nil {
|
||||||
log.Fatal("Unable to parse args")
|
log.Fatal("Unable to parse args")
|
||||||
@ -130,12 +131,16 @@ func runHyperKit(args []string) {
|
|||||||
|
|
||||||
// Select network mode
|
// Select network mode
|
||||||
var vpnKitProcess *os.Process
|
var vpnKitProcess *os.Process
|
||||||
|
if *networking == "" || *networking == "default" {
|
||||||
|
dflt := hyperkitNetworkingDefault
|
||||||
|
networking = &dflt
|
||||||
|
}
|
||||||
netMode := strings.SplitN(*networking, ",", 2)
|
netMode := strings.SplitN(*networking, ",", 2)
|
||||||
|
|
||||||
switch netMode[0] {
|
switch netMode[0] {
|
||||||
case networkingDockerForMac:
|
case hyperkitNetworkingDockerForMac:
|
||||||
h.VPNKitSock = filepath.Join(os.Getenv("HOME"), "Library/Containers/com.docker.docker/Data/s50")
|
h.VPNKitSock = filepath.Join(os.Getenv("HOME"), "Library/Containers/com.docker.docker/Data/s50")
|
||||||
case networkingVPNKit:
|
case hyperkitNetworkingVPNKit:
|
||||||
if len(netMode) > 1 {
|
if len(netMode) > 1 {
|
||||||
// Socket path specified, try to use existing VPNKit instance
|
// Socket path specified, try to use existing VPNKit instance
|
||||||
h.VPNKitSock = netMode[1]
|
h.VPNKitSock = netMode[1]
|
||||||
@ -161,10 +166,10 @@ func runHyperKit(args []string) {
|
|||||||
// VSOCK port 62373 is used to pass traffic from host->guest
|
// VSOCK port 62373 is used to pass traffic from host->guest
|
||||||
h.VSockPorts = append(h.VSockPorts, 62373)
|
h.VSockPorts = append(h.VSockPorts, 62373)
|
||||||
}
|
}
|
||||||
case networkingVMNet:
|
case hyperkitNetworkingVMNet:
|
||||||
h.VPNKitSock = ""
|
h.VPNKitSock = ""
|
||||||
h.VMNet = true
|
h.VMNet = true
|
||||||
case networkingNone:
|
case hyperkitNetworkingNone:
|
||||||
h.VPNKitSock = ""
|
h.VPNKitSock = ""
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Invalid networking mode: %s", netMode[0])
|
log.Fatalf("Invalid networking mode: %s", netMode[0])
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -36,10 +38,18 @@ type QemuConfig struct {
|
|||||||
QemuBinPath string
|
QemuBinPath string
|
||||||
QemuImgPath string
|
QemuImgPath string
|
||||||
PublishedPorts []string
|
PublishedPorts []string
|
||||||
TapDevice string
|
NetdevConfig string
|
||||||
UUID uuid.UUID
|
UUID uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
qemuNetworkingNone string = "none"
|
||||||
|
qemuNetworkingUser = "user"
|
||||||
|
qemuNetworkingTap = "tap"
|
||||||
|
qemuNetworkingBridge = "bridge"
|
||||||
|
qemuNetworkingDefault = qemuNetworkingUser
|
||||||
|
)
|
||||||
|
|
||||||
func haveKVM() bool {
|
func haveKVM() bool {
|
||||||
_, err := os.Stat("/dev/kvm")
|
_, err := os.Stat("/dev/kvm")
|
||||||
return !os.IsNotExist(err)
|
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) {
|
func runQemu(args []string) {
|
||||||
invoked := filepath.Base(os.Args[0])
|
invoked := filepath.Base(os.Args[0])
|
||||||
flags := flag.NewFlagSet("qemu", flag.ExitOnError)
|
flags := flag.NewFlagSet("qemu", flag.ExitOnError)
|
||||||
@ -67,6 +91,10 @@ func runQemu(args []string) {
|
|||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
fmt.Printf("Options:\n")
|
fmt.Printf("Options:\n")
|
||||||
flags.PrintDefaults()
|
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
|
// Display flags
|
||||||
@ -100,9 +128,11 @@ func runQemu(args []string) {
|
|||||||
// Generate UUID, so that /sys/class/dmi/id/product_uuid is populated
|
// Generate UUID, so that /sys/class/dmi/id/product_uuid is populated
|
||||||
vmUUID := uuid.NewV4()
|
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{}
|
publishFlags := multipleFlag{}
|
||||||
flags.Var(&publishFlags, "publish", "Publish a vm's port(s) to the host (default [])")
|
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 {
|
if err := flags.Parse(args); err != nil {
|
||||||
log.Fatal("Unable to parse args")
|
log.Fatal("Unable to parse args")
|
||||||
@ -206,6 +236,40 @@ func runQemu(args []string) {
|
|||||||
if *isoBoot && isoPath != "" {
|
if *isoBoot && isoPath != "" {
|
||||||
log.Fatalf("metadata and ISO boot currently cannot coexist")
|
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{
|
config := QemuConfig{
|
||||||
Path: path,
|
Path: path,
|
||||||
@ -222,7 +286,7 @@ func runQemu(args []string) {
|
|||||||
KVM: *enableKVM,
|
KVM: *enableKVM,
|
||||||
Containerized: *qemuContainerized,
|
Containerized: *qemuContainerized,
|
||||||
PublishedPorts: publishFlags,
|
PublishedPorts: publishFlags,
|
||||||
TapDevice: *tapDevice,
|
NetdevConfig: netdevConfig,
|
||||||
UUID: vmUUID,
|
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)
|
forwardings, err := buildQemuForwardings(config.PublishedPorts, config.Containerized)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
qemuArgs = append(qemuArgs, "-net", forwardings)
|
qemuArgs = append(qemuArgs, "-net", config.NetdevConfig+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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.GUI != true {
|
if config.GUI != true {
|
||||||
@ -554,7 +615,10 @@ func splitPublish(publish string) (publishedPorts, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildQemuForwardings(publishFlags multipleFlag, containerized bool) (string, error) {
|
func buildQemuForwardings(publishFlags multipleFlag, containerized bool) (string, error) {
|
||||||
forwardings := "user"
|
if len(publishFlags) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
var forwardings string
|
||||||
for _, publish := range publishFlags {
|
for _, publish := range publishFlags {
|
||||||
p, err := splitPublish(publish)
|
p, err := splitPublish(publish)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user