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