From 241136e9108d41d72a621a16253564d5a778b8fa Mon Sep 17 00:00:00 2001 From: Magnus Skjegstad Date: Thu, 7 Sep 2017 10:37:16 +0200 Subject: [PATCH 1/2] Update Hyperkit to latest version Signed-off-by: Magnus Skjegstad --- src/cmd/linuxkit/vendor.conf | 2 +- .../github.com/moby/hyperkit/go/hyperkit.go | 24 +++- .../hyperkit/src/lib/pci_virtio_net_vpnkit.c | 103 +++++++++++++++--- 3 files changed, 104 insertions(+), 25 deletions(-) diff --git a/src/cmd/linuxkit/vendor.conf b/src/cmd/linuxkit/vendor.conf index c9a7139e3..3de6de48c 100644 --- a/src/cmd/linuxkit/vendor.conf +++ b/src/cmd/linuxkit/vendor.conf @@ -11,7 +11,7 @@ github.com/googleapis/gax-go 8c5154c0fe5bf18cf649634d4c6df50897a32751 github.com/gophercloud/gophercloud 2804b72cf099b41d2e25c8afcca786f9f962ddee github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d github.com/mitchellh/go-ps 4fdf99ab29366514c69ccccddab5dc58b8d84062 -github.com/moby/hyperkit a82b409a87f12fa3306813410c37f4eed270efac +github.com/moby/hyperkit 3e31617ae866c93925e2b3bc5d8006b60985e920 github.com/packethost/packngo 131798f2804a1b3e895ca98047d56f0d7e094e2a github.com/radu-matei/azure-sdk-for-go 3b12823551999669c9a325a32472508e0af7978e github.com/radu-matei/azure-vhd-utils e52754d5569d2a643a7775f72ff2a6cf524f4c25 diff --git a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/hyperkit.go b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/hyperkit.go index 46c069321..db0a9b5aa 100644 --- a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/hyperkit.go +++ b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/hyperkit.go @@ -28,6 +28,7 @@ import ( "io" "io/ioutil" "log" + "net" "os" "os/exec" "os/user" @@ -85,8 +86,10 @@ type HyperKit struct { StateDir string `json:"state_dir"` // VPNKitSock is the location of the VPNKit socket used for networking. VPNKitSock string `json:"vpnkit_sock"` - // VPNKitKey is a string containing a UUID, it can be used in conjunction with VPNKit to get consistent IP address. - VPNKitKey string `json:"vpnkit_key"` + // VPNKitUUID is a string containing a UUID, it can be used in conjunction with VPNKit to get consistent IP address. + VPNKitUUID string `json:"vpnkit_uuid"` + // VPNKitPreferredIPv4 is a string containing an IPv4 address, it can be used to request a specific IP for a UUID from VPNKit. + VPNKitPreferredIPv4 string `json:"vpnkit_preferred_ipv4"` // UUID is a string containing a UUID, it sets BIOS DMI UUID for the VM (as found in /sys/class/dmi/id/product_uuid on Linux). UUID string `json:"uuid"` // Disks contains disk images to use/create. @@ -249,6 +252,11 @@ func (h *HyperKit) execute(cmdline string) error { return fmt.Errorf("Bootrom %s does not exist", h.Bootrom) } } + if h.VPNKitPreferredIPv4 != "" { + if ip := net.ParseIP(h.VPNKitPreferredIPv4); ip == nil { + return fmt.Errorf("Invalid VPNKit IP: %s", h.VPNKitPreferredIPv4) + } + } // Create files if h.StateDir != "" { @@ -420,11 +428,15 @@ func (h *HyperKit) buildArgs(cmdline string) { nextSlot := 1 if h.VPNKitSock != "" { - if h.VPNKitKey == "" { - a = append(a, "-s", fmt.Sprintf("%d:0,virtio-vpnkit,path=%s", nextSlot, h.VPNKitSock)) - } else { - a = append(a, "-s", fmt.Sprintf("%d:0,virtio-vpnkit,path=%s,uuid=%s", nextSlot, h.VPNKitSock, h.VPNKitKey)) + var uuid string + if h.VPNKitUUID != "" { + uuid = fmt.Sprintf(",uuid=%s", h.VPNKitUUID) } + var preferredIPv4 string + if h.VPNKitPreferredIPv4 != "" { + preferredIPv4 = fmt.Sprintf(",preferred_ipv4=%s", h.VPNKitPreferredIPv4) + } + a = append(a, "-s", fmt.Sprintf("%d:0,virtio-vpnkit,path=%s%s%s", nextSlot, h.VPNKitSock, uuid, preferredIPv4)) nextSlot++ } diff --git a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/src/lib/pci_virtio_net_vpnkit.c b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/src/lib/pci_virtio_net_vpnkit.c index ad64a0df1..b6ad97047 100644 --- a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/src/lib/pci_virtio_net_vpnkit.c +++ b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/src/lib/pci_virtio_net_vpnkit.c @@ -77,6 +77,8 @@ #include #include #include +#include +#include #define WPRINTF(format, ...) printf(format, __VA_ARGS__) @@ -93,13 +95,18 @@ struct msg_init { } __packed; #define CMD_ETHERNET 1 -struct msg_common { - uint8_t command; +#define CMD_PREFERRED_IPV4 8 + +#define RESP_VIF 1 +#define RESP_DISCONNECT 2 + +struct cmd_ethernet { + char uuid[36]; } __packed; -struct msg_ethernet { - uint8_t command; /* CMD_ETHERNET */ +struct cmd_preferred_ipv4 { char uuid[36]; + in_addr_t ip; } __packed; struct vif_info { @@ -108,6 +115,26 @@ struct vif_info { uint8_t mac[6]; } __packed; +struct disconnect_reason { + uint8_t len; + char msg[256]; +} __packed; + +struct msg_command { + uint8_t command; + union { + struct cmd_ethernet ethernet; + struct cmd_preferred_ipv4 preferred_ipv4; + }; +} __packed; + +struct msg_response { + uint8_t response_type; + union { + struct disconnect_reason disconnect; + struct vif_info vif; + }; +} __packed; /* * Host capabilities. Note that we only offer a few of these. @@ -286,11 +313,11 @@ err: /* * wire protocol */ -static int vpnkit_connect(int fd, const char uuid[36], struct vif_info *vif) +static int vpnkit_connect(int fd, const char uuid[36], struct vif_info *vif, in_addr_t preferred_ipv4) { struct msg_init init_msg = { .magic = { 'V', 'M', 'N', '3', 'T' }, - .version = 1U, + .version = 22U, }; /* msg.commit is not NULL terminated */ @@ -324,21 +351,49 @@ static int vpnkit_connect(int fd, const char uuid[36], struct vif_info *vif) init_reply.magic[4], init_reply.version, (int)sizeof(init_reply.commit), init_reply.commit); - struct msg_ethernet cmd_ethernet = { - .command = CMD_ETHERNET, - }; - memcpy(cmd_ethernet.uuid, uuid, sizeof(cmd_ethernet.uuid)); + if (init_reply.version != init_msg.version) { + fprintf(stderr, "virtio-net-vpnkit: protocol version mismatch: version %d requested, got version %d.\n", + init_msg.version, + init_reply.version); + } - if (really_write(fd, (uint8_t*)&cmd_ethernet, sizeof(cmd_ethernet)) < 0) { - fprintf(stderr, "virtio-net-vpnkit: failed to write ethernet cmd\n"); + struct msg_command cmd; + memset(&cmd, 0, sizeof(cmd)); + + if (preferred_ipv4 != 0) { + cmd.command = CMD_PREFERRED_IPV4; + memcpy(cmd.preferred_ipv4.uuid, uuid, sizeof(cmd.preferred_ipv4.uuid)); + /* VPNKit uses LE, so swap the IP from network byte order */ + cmd.preferred_ipv4.ip = OSSwapInt32(preferred_ipv4); + } else { + /* No preferred IPv4 address, falling back to requesting a dynamic address */ + cmd.command = CMD_ETHERNET; + memcpy(cmd.ethernet.uuid, uuid, sizeof(cmd.ethernet.uuid)); + } + + if (really_write(fd, (uint8_t*)&cmd, sizeof(cmd)) < 0) { + fprintf(stderr, "virtio-net-vpnkit: failed to write command\n"); return -1; } - if (really_read(fd, (uint8_t*)vif, sizeof(*vif)) < 0) { - fprintf(stderr, "virtio-net-vpnkit: failed to read vif info\n"); + struct msg_response reply; + if (really_read(fd, (uint8_t*)&reply, sizeof(reply)) < 0) { + fprintf(stderr, "virtio-net-vpnkit: failed to read response message\n"); return -1; } + switch (reply.response_type) { + case RESP_VIF: + memcpy((uint8_t*)vif, (uint8_t*)&reply.vif, sizeof(*vif)); + break; + case RESP_DISCONNECT: + fprintf(stderr, "virtio-net-vpnkit: server disconnected: %*s\n", reply.disconnect.len, reply.disconnect.msg); + return -1; + default: + fprintf(stderr, "virtio-net-vpnkit: unknown response from server: %d\n", reply.response_type); + return -1; + } + return 0; } @@ -362,9 +417,11 @@ vpnkit_create(struct pci_vtnet_softc *sc, const char *opts) const char *path = "/var/tmp/com.docker.slirp.socket"; char *macfile = NULL; char *tmp = NULL; + char *ipv4 = NULL; uuid_t uuid; char uuid_string[37]; struct sockaddr_un addr; + struct in_addr preferred_ipv4 = { .s_addr = 0 }; int fd; struct vpnkit_state *state = malloc(sizeof(struct vpnkit_state)); if (!state) abort(); @@ -393,11 +450,16 @@ vpnkit_create(struct pci_vtnet_softc *sc, const char *opts) return 1; } memcpy(&uuid_string[0], &tmp[0], 36); - fprintf(stdout, "Interface will have uuid %s\n", tmp); free(tmp); tmp = NULL; } else if (strncmp(opts, "macfile=", 8) == 0) { macfile = copy_up_to_comma(opts + 8); + } else if (strncmp(opts, "preferred_ipv4=", 15) == 0) { + ipv4 = copy_up_to_comma(opts + 15); + if (inet_aton(ipv4, &preferred_ipv4) == 0) { + fprintf(stderr, "Unable to parse requested IP %s\n", ipv4); + return 1; + } } else { fprintf(stderr, "invalid option: %s\r\n", opts); return 1; @@ -407,6 +469,11 @@ vpnkit_create(struct pci_vtnet_softc *sc, const char *opts) opts = &next[1]; } + fprintf(stdout, "virtio-net-vpnkit: interface will have uuid %s\n", uuid_string); + if (ipv4 != NULL) { + fprintf(stdout, "virtio-net-vpnkit: requesting ip %s\n", ipv4); + } + state->vif.max_packet_size = 1500; sc->state = state; @@ -423,7 +490,7 @@ vpnkit_create(struct pci_vtnet_softc *sc, const char *opts) goto err; } - if (vpnkit_connect(fd, uuid_string, &state->vif) == 0) + if (vpnkit_connect(fd, uuid_string, &state->vif, preferred_ipv4.s_addr) == 0) /* success */ break; @@ -441,8 +508,8 @@ err: state->fd = fd; struct vif_info *info = &state->vif; - fprintf(stdout, "Connection established with MAC=%02x:%02x:%02x:%02x:%02x:%02x and MTU %d\n", - info->mac[0], info->mac[1], info->mac[2], info->mac[3], info->mac[4], info->mac[5], + fprintf(stdout, "virtio-net-vpnkit: Connection established with MAC=%02x:%02x:%02x:%02x:%02x:%02x and MTU %d\n", + info->mac[0], info->mac[1], info->mac[2], info->mac[3], info->mac[4], info->mac[5], (int)info->mtu); if (macfile) { From c8ba942a8094c6b2f899701610a77d34bf361bff Mon Sep 17 00:00:00 2001 From: Magnus Skjegstad Date: Thu, 7 Sep 2017 10:03:35 +0200 Subject: [PATCH 2/2] 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 --- src/cmd/linuxkit/run_hyperkit.go | 41 ++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/cmd/linuxkit/run_hyperkit.go b/src/cmd/linuxkit/run_hyperkit.go index e7ec2477b..9d6ee40a2 100644 --- a/src/cmd/linuxkit/run_hyperkit.go +++ b/src/cmd/linuxkit/run_hyperkit.go @@ -41,11 +41,13 @@ func runHyperKit(args []string) { var disks Disks 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)") - 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") 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.`") + 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 uefiBoot := flags.Bool("uefi", false, "Use UEFI boot") isoBoot := flags.Bool("iso", false, "Boot image is an ISO") @@ -149,17 +151,22 @@ func runHyperKit(args []string) { isoPaths = append(isoPaths, isoPath) } - vpnKitKey := "" - if *ipStr != "" { - // If an IP address was requested construct a "special" UUID - // for the VM. - if ip := net.ParseIP(*ipStr); len(ip) > 0 { - uuid := make([]byte, 16) - uuid[12] = ip.To4()[0] - uuid[13] = ip.To4()[1] - uuid[14] = ip.To4()[2] - 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:]) + // Create UUID for VPNKit or reuse an existing one from state dir. IP addresses are + // assigned to the UUID, so to get the same IP we have to store the initial UUID. If + // has specified a VPNKit UUID the file is ignored. + if *vpnKitUUID == "" { + vpnKitUUIDFile := filepath.Join(*state, "uuid.vpnkit") + if _, err := os.Stat(vpnKitUUIDFile); os.IsNotExist(err) { + *vpnKitUUID = uuid.NewV4().String() + if err := ioutil.WriteFile(vpnKitUUIDFile, []byte(*vpnKitUUID), 0600); err != nil { + log.Fatalf("Unable to write to %s: %v", vpnKitUUIDFile, err) + } + } 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 { h.Bootrom = *fw } - h.VPNKitKey = vpnKitKey h.UUID = vmUUID h.ISOImages = isoPaths h.VSock = true h.CPUs = *cpus 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)) if err != nil { log.Fatalf("Cannot run hyperkit: %v", err)