From bbaff80fb27c00f1e9a6d083f2f5914bd79990a4 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 15 Feb 2022 12:42:28 +0000 Subject: host/start-vm: clean up net after VM shuts down --- host/start-vm/net.c | 230 ++++++++++++++++++++++++---------------------------- 1 file changed, 107 insertions(+), 123 deletions(-) (limited to 'host/start-vm/net.c') diff --git a/host/start-vm/net.c b/host/start-vm/net.c index 9a094aa..41bf1c2 100644 --- a/host/start-vm/net.c +++ b/host/start-vm/net.c @@ -1,19 +1,19 @@ // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022 Alyssa Ross +#include "ch.h" #include "net-util.h" +#include +#include #include -#include #include +#include #include #include -#include +#include #include -#include -#include -#include #include #include @@ -27,119 +27,6 @@ int format_mac(char s[static MAC_STR_LEN + 1], const uint8_t mac[6]) mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } -static int dial_un(const char *sun_path) -{ - struct sockaddr_un addr = { 0 }; - int fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); - if (fd == -1) - return -1; - - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, sun_path, sizeof addr.sun_path); - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" - // Safe because if the last byte of addr.sun_path is non-zero, - // sun_path must be at least one byte longer. - if (addr.sun_path[sizeof addr.sun_path - 1] && - sun_path[sizeof addr.sun_path]) { -#pragma GCC diagnostic pop - errno = E2BIG; - goto fail; - } - - if (connect(fd, (struct sockaddr *)&addr, sizeof addr) == -1) - goto fail; - - return fd; -fail: - close(fd); - return -1; -} - -static int sendv_with_fd(int sock, const struct iovec iov[], size_t iovlen, - int fd, int flags) -{ - struct msghdr msg = { 0 }; - struct cmsghdr *cmsg; - union { - char buf[CMSG_SPACE(sizeof fd)]; - struct cmsghdr _align; - } u; - - msg.msg_iov = (struct iovec *)iov; - msg.msg_iovlen = iovlen; - msg.msg_control = u.buf; - msg.msg_controllen = sizeof u.buf; - - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof fd); - memcpy(CMSG_DATA(cmsg), &fd, sizeof fd); - - return sendmsg(sock, &msg, flags); -} - -static int ch_add_net(const char *vm_name, int tap, const uint8_t mac[6]) -{ - char mac_s[MAC_STR_LEN + 1]; - char path[sizeof ((struct sockaddr_un *)0)->sun_path] = { 0 }; - int sock = -1; - uint16_t status = 0; - FILE *f = NULL; - static const char buf1[] = - "PUT /api/v1/vm.add-net HTTP/1.1\r\n" - "Host: localhost\r\n" - "Content-Type: application/json\r\n" - "Content-Length: 27\r\n" - "\r\n" - "{\"mac\":\""; - static const char buf2[] = "\"}"; - - if (format_mac(mac_s, mac) == -1) - return -1; - - struct iovec iov[] = { - { .iov_base = (void *)buf1, .iov_len = sizeof buf1 - 1 }, - { .iov_base = (void *)mac_s, .iov_len = MAC_STR_LEN }, - { .iov_base = (void *)buf2, .iov_len = sizeof buf2 - 1 }, - }; - - if (snprintf(path, sizeof path, - "/run/service/ext-%s-vmm/env/cloud-hypervisor.sock", - vm_name) >= (ssize_t)sizeof path) { - errno = E2BIG; - return -1; - } - - if ((sock = dial_un(path)) == -1) - goto out; - - if (sendv_with_fd(sock, iov, sizeof iov / sizeof *iov, tap, 0) == -1) - goto out; - - f = fdopen(sock, "r"); - sock = -1; // now owned by f - if (!f) - goto out; - - if (fscanf(f, "%*s %" SCNu16, &status) != 1) - status = 0; - - if (status < 200 || status >= 300) { - fputs("Failed cloud-hypervisor API request:\n", stderr); - fflush(stderr); - writev(STDERR_FILENO, iov, sizeof iov / sizeof *iov); - fputs("\n", stderr); - } -out: - close(sock); - if (f) - fclose(f); - return (200 <= status && status < 300) - 1; -} - static int setup_tap(const char *bridge_name, const char *tap_prefix) { int fd; @@ -170,15 +57,104 @@ static int client_net_setup(const char *bridge_name) } static int router_net_setup(const char *bridge_name, const char *router_vm_name, - const uint8_t mac[6]) + const uint8_t mac[6], struct ch_device **out) { - int r, fd = setup_tap(bridge_name, "router"); + int e, fd = setup_tap(bridge_name, "router"); if (fd == -1) return -1; - r = ch_add_net(router_vm_name, fd, mac); + e = ch_add_net(router_vm_name, fd, mac, out); close(fd); - return r; + if (!e) + return 0; + errno = e; + return -1; +} + +static int router_net_cleanup(pid_t pid, const char *vm_name, + struct ch_device *vm_net_device) +{ + int e; + char name[IFNAMSIZ], newname[IFNAMSIZ], brname[IFNAMSIZ]; + + if ((e = ch_remove_device(vm_name, vm_net_device))) { + errno = e; + return -1; + } + + // Work around cloud-hypervisor not closing taps it's no + // longer using by freeing up the name. + // + // We assume ≤16-bit pids. + snprintf(name, sizeof name, "router%d", pid); + snprintf(newname, sizeof newname, "_dead%d", pid); + snprintf(brname, sizeof brname, "br%d", pid); + + if (bridge_remove_if(brname, name) == -1) + warn("removing %s from %s", name, brname); + + if (if_down(name) == -1) + return -1; + return if_rename(name, newname); +} + +static int bridge_cleanup(pid_t pid) +{ + char name[IFNAMSIZ]; + snprintf(name, sizeof name, "br%d", pid); + return bridge_delete(name); +} + +static noreturn void exit_listener_main(int fd, pid_t pid, + const char *router_vm_name, + struct ch_device *router_vm_net_device) +{ + // Wait for the other end of the pipe to be closed. + int status = EXIT_SUCCESS; + struct pollfd pollfd = { .fd = fd, .events = 0, .revents = 0 }; + while (poll(&pollfd, 1, -1) == -1) { + if (errno == EINTR || errno == EWOULDBLOCK) + continue; + + err(1, "poll"); + } + assert(pollfd.revents == POLLERR); + + if (router_net_cleanup(pid, router_vm_name, + router_vm_net_device) == -1) { + warn("cleaning up router tap"); + status = EXIT_FAILURE; + } + if (bridge_cleanup(pid) == -1) { + warn("cleaning up bridge"); + status = EXIT_FAILURE; + } + + exit(status); +} + +static int exit_listener_setup(const char *router_vm_name, + struct ch_device *router_vm_net_device) +{ + pid_t pid = getpid(); + int fd[2]; + + if (pipe(fd) == -1) + return -1; + + switch (fork()) { + case -1: + close(fd[0]); + close(fd[1]); + return -1; + case 0: + close(fd[0]); + exit_listener_main(fd[1], pid, router_vm_name, + router_vm_net_device); + default: + close(fd[1]); + return 0; + } } struct net_config { @@ -188,6 +164,7 @@ struct net_config { struct net_config net_setup(const char *router_vm_name) { + struct ch_device *router_vm_net_device = NULL; struct net_config r = { .fd = -1, .mac = { 0 } }; char bridge_name[IFNAMSIZ]; pid_t pid = getpid(); @@ -208,9 +185,15 @@ struct net_config net_setup(const char *router_vm_name) if ((r.fd = client_net_setup(bridge_name)) == -1) goto fail_bridge; - if (router_net_setup(bridge_name, router_vm_name, router_mac) == -1) + if (router_net_setup(bridge_name, router_vm_name, router_mac, + &router_vm_net_device) == -1) goto fail_bridge; + // Set up a process that will listen for this process dying, + // and remove the interface from the netvm, and delete the + // bridge. + exit_listener_setup(router_vm_name, router_vm_net_device); + goto out; fail_bridge: @@ -218,5 +201,6 @@ fail_bridge: close(r.fd); r.fd = -1; out: + ch_device_free(router_vm_net_device); return r; } -- cgit 1.4.1