Update to new Hyperkit API / VPNKit protocol

This adds support for the updated Hyperkit API, which is needed to
request a specific IP address in new versions of VPNKit / Docker for
Mac. IPs encoded in the UUID (the old method) will now be ignored by
VPNKit.

A preferred IPv4 address can be requested directly via the new API. The
IP is then associated with the VPNKit UUID identifying the connection.
The UUID is either user specified or randomly assigned if left empty.
VMs launched with the same VPNKit UUID it will get the same IP address.

To avoid having to copy the assigned UUID manually, a file `uuid.vpnkit`
is now saved in the state directory when the UUID is generated.  The UUID
from this file is reused automatically if it exists, unless a different
VPNKit UUID is specified on the command line. This also means that VMs
that use dynamically assigned IPs will by default get the same IP each
time they are started, as long as the state directory exists.

This change is incompatible with earlier versions of VPNKit / Hyperkit
and a recent version of Docker for Mac has to be installed. If the
feature is unsupported using the `--ip` parameter will exit with an
error message.

Signed-off-by: Magnus Skjegstad <magnus@skjegstad.com>
This commit is contained in:
Magnus Skjegstad 2017-09-07 10:03:35 +02:00
parent 241136e910
commit c8ba942a80

View File

@ -41,11 +41,13 @@ func runHyperKit(args []string) {
var disks Disks var disks Disks
flags.Var(&disks, "disk", "Disk config. [file=]path[,size=1G]") flags.Var(&disks, "disk", "Disk config. [file=]path[,size=1G]")
data := flags.String("data", "", "Metadata to pass to VM (either a path to a file or a string)") data := flags.String("data", "", "Metadata to pass to VM (either a path to a file or a string)")
ipStr := flags.String("ip", "", "IP address for the VM") ipStr := flags.String("ip", "", "Preferred IPv4 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", 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.`") 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.`")
vpnKitUUID := flags.String("vpnkit-uuid", "", "Optional UUID used to identify the VPNKit connection. Overrides 'uuid.vpnkit' in the state directory.")
// Boot type; we try to determine automatically // Boot type; we try to determine automatically
uefiBoot := flags.Bool("uefi", false, "Use UEFI boot") uefiBoot := flags.Bool("uefi", false, "Use UEFI boot")
isoBoot := flags.Bool("iso", false, "Boot image is an ISO") isoBoot := flags.Bool("iso", false, "Boot image is an ISO")
@ -149,17 +151,22 @@ func runHyperKit(args []string) {
isoPaths = append(isoPaths, isoPath) isoPaths = append(isoPaths, isoPath)
} }
vpnKitKey := "" // Create UUID for VPNKit or reuse an existing one from state dir. IP addresses are
if *ipStr != "" { // assigned to the UUID, so to get the same IP we have to store the initial UUID. If
// If an IP address was requested construct a "special" UUID // has specified a VPNKit UUID the file is ignored.
// for the VM. if *vpnKitUUID == "" {
if ip := net.ParseIP(*ipStr); len(ip) > 0 { vpnKitUUIDFile := filepath.Join(*state, "uuid.vpnkit")
uuid := make([]byte, 16) if _, err := os.Stat(vpnKitUUIDFile); os.IsNotExist(err) {
uuid[12] = ip.To4()[0] *vpnKitUUID = uuid.NewV4().String()
uuid[13] = ip.To4()[1] if err := ioutil.WriteFile(vpnKitUUIDFile, []byte(*vpnKitUUID), 0600); err != nil {
uuid[14] = ip.To4()[2] log.Fatalf("Unable to write to %s: %v", vpnKitUUIDFile, err)
uuid[15] = ip.To4()[3] }
vpnKitKey = fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]) } else {
uuid, err := ioutil.ReadFile(vpnKitUUIDFile)
if err != nil {
log.Fatalf("Unable to read VPNKit UUID from %s: %v", vpnKitUUIDFile, err)
}
*vpnKitUUID = string(uuid)
} }
} }
@ -252,13 +259,21 @@ func runHyperKit(args []string) {
} else { } else {
h.Bootrom = *fw h.Bootrom = *fw
} }
h.VPNKitKey = vpnKitKey
h.UUID = vmUUID h.UUID = vmUUID
h.ISOImages = isoPaths h.ISOImages = isoPaths
h.VSock = true h.VSock = true
h.CPUs = *cpus h.CPUs = *cpus
h.Memory = *mem h.Memory = *mem
h.VPNKitUUID = *vpnKitUUID
if *ipStr != "" {
if ip := net.ParseIP(*ipStr); len(ip) > 0 && ip.To4() != nil {
h.VPNKitPreferredIPv4 = ip.String()
} else {
log.Fatalf("Unable to parse IPv4 address: %v", *ipStr)
}
}
err = h.Run(string(cmdline)) err = h.Run(string(cmdline))
if err != nil { if err != nil {
log.Fatalf("Cannot run hyperkit: %v", err) log.Fatalf("Cannot run hyperkit: %v", err)