diff options
Diffstat (limited to 'qrap.c')
| -rw-r--r-- | qrap.c | 429 |
1 files changed, 0 insertions, 429 deletions
@@ -1,429 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -/* PASST - Plug A Simple Socket Transport - * - * qrap.c - qemu wrapper connecting UNIX domain socket to file descriptor - * - * Copyright (c) 2020-2021 Red Hat GmbH - * Author: Stefano Brivio <sbrivio@redhat.com> - * - * TODO: Drop this implementation once qemu commit 13c6be96618c ("net: stream: - * add unix socket") is included in a release (7.2), and once we can reasonably - * assume existing users switched to it. - */ - -#include <stdio.h> -#include <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <errno.h> -#include <linux/limits.h> -#include <limits.h> -#include <fcntl.h> -#include <net/if_arp.h> -#include <netinet/in.h> -#include <netinet/ip6.h> -#include <netinet/if_ether.h> -#include <time.h> - -#include <linux/icmpv6.h> - -#include "util.h" -#include "ip.h" -#include "passt.h" -#include "arp.h" - -static char *qemu_names[] = { - "kvm", - "qemu-kvm", -#ifdef ARCH - ( "qemu-system-" ARCH ), -#endif - "/usr/libexec/qemu-kvm", - NULL, -}; - -/** - * struct drop_arg - Drop matching arguments on command line - * @name: Option name - * @val: Substring in option value, NULL matches any value - */ -static const struct drop_arg { - char *name; - char *val; -} drop_args[] = { - { "-netdev", NULL }, - { "-net", NULL }, - { "-device", "virtio-net-pci," }, - { "-device", "{\"driver\":\"virtio-net-pci\"," }, - { "-device", "virtio-net-ccw," }, - { "-device", "{\"driver\":\"virtio-net-ccw\"," }, - { "-device", "e1000," }, - { "-device", "{\"driver\":\"e1000\"," }, - { "-device", "e1000e," }, - { "-device", "{\"driver\":\"e1000e\"," }, - { "-device", "rtl8139," }, - { "-device", "{\"driver\":\"rtl8139\"," }, - { 0 }, -}; - -/** - * struct pci_dev - PCI devices to add on command line depending on machine name - * @mach: Machine name - * @name: Device ("-device") name to insert - * @template: Prefix for device specification (first part of address) - * @template_post: Suffix for device specification (last part of address) - * @template_json: Device prefix for when JSON is used - * @template_json_post: Device suffix for when JSON is used - * @base: Base used for PCI addresses - * @first: First usable PCI address - * @last: Last usable PCI address - */ -static const struct pci_dev { - char *mach; - char *name; - char *template; - char *template_post; - char *template_json; - char *template_json_post; - int base; - int first; - int last; -} pci_devs[] = { - { - "pc-q35", "virtio-net-pci", - "bus=pci.", ",addr=0x0", - "\"bus\":\"pci.", ",\"addr\":\"0x0\"", - 10, 3, /* 2: hotplug bus */ 31 - }, - { - "pc-", "virtio-net-pci", - "bus=pci.0,addr=0x", "", - "\"bus\":\"pci.0\",\"addr\":\"0x", "", - 16, 2, /* 1: ISA bridge */ 31 - }, - { - "s390-ccw", "virtio-net-ccw", - "devno=fe.0.", "", - "\"devno\":\"fe.0.", "", - 16, 1, 16 - }, - { 0 }, -}; - -#define DEFAULT_FD 5 - -/** - * usage() - Print usage and exit - * @name: Executable name - */ -void usage(const char *name) -{ - fprintf(stderr, "Usage: %s [FDNUM QEMU_CMD] [QEMU_ARG]...\n", name); - fprintf(stderr, "\n"); - fprintf(stderr, "If first and second arguments aren't a socket number\n" - "and a path, %s will try to locate a qemu binary\n" - "and directly patch the command line\n", name); - - exit(EXIT_FAILURE); -} - -/** - * main() - Entry point and main loop - * @argc: Argument count - * @argv: File descriptor number, then qemu with arguments - * - * Return: 0 once interrupted, non-zero on failure - */ -int main(int argc, char **argv) -{ - int i, s, qemu_argc = 0, addr_map = 0, has_dev = 0, has_json = 0, retry_on_reset, rc; - struct timeval tv = { .tv_sec = 0, .tv_usec = (long)(500 * 1000) }; - char *qemu_argv[ARG_MAX], dev_str[ARG_MAX]; - struct sockaddr_un addr = { - .sun_family = AF_UNIX, - }; - const struct pci_dev *dev = NULL; - long fd; - struct { - uint32_t vnet_len4; - struct ethhdr eh4; - struct arphdr ah; - struct arpmsg am; - - uint32_t vnet_len6; - struct ethhdr eh6; - struct ipv6hdr ip6hr; - struct icmp6hdr ihr; - struct in6_addr target; - } __attribute__((__packed__)) probe = { - .vnet_len4 = htonl(42), - { - .h_dest = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - .h_source = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - .h_proto = htons(ETH_P_ARP), - }, - { .ar_hrd = htons(ARPHRD_ETHER), - .ar_pro = htons(ETH_P_IP), - .ar_hln = ETH_ALEN, - .ar_pln = 4, - .ar_op = htons(ARPOP_REQUEST), - }, - { - .sha = { 0 }, .sip = { 0 }, .tha = { 0 }, .tip = { 0 }, - }, - .vnet_len6 = htonl(78), - { - .h_dest = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - .h_source = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - .h_proto = htons(ETH_P_IPV6), - }, - { - .version = 6, - .payload_len = htons(24), - .nexthdr = IPPROTO_ICMPV6, - .hop_limit = 255, - .saddr = IN6ADDR_LOOPBACK_INIT, - .daddr = IN6ADDR_ANY_INIT, - }, - { - .icmp6_type = 135, - .icmp6_code = 0, - }, - IN6ADDR_ANY_INIT, - }; - char probe_r; - - if (argc >= 3) { - const char *path = getenv("PATH"); - errno = 0; - fd = strtol(argv[1], NULL, 0); - if (fd >= 3 && fd < INT_MAX && !errno && path) { - char env_path[ARG_MAX + 1], *p, command[ARG_MAX]; - - strncpy(env_path, path, ARG_MAX); - /* cppcheck-suppress strtokCalled */ - p = strtok(env_path, ":"); - while (p) { - snprintf(command, ARG_MAX, "%s/%s", p, argv[2]); - if (!access(command, X_OK)) - goto valid_args; - - /* cppcheck-suppress strtokCalled */ - p = strtok(NULL, ":"); - } - } - } - - fd = DEFAULT_FD; - - for (i = 1; i < argc - 1; i++) { - if (strcmp(argv[i], "-machine")) - continue; - - for (dev = pci_devs; dev->mach; dev++) { - if (strstr(argv[i + 1], dev->mach) == argv[i + 1]) - break; - } - } - - if (!dev || !dev->mach) - dev = pci_devs; - - for (qemu_argc = 1, i = 1; i < argc; i++) { - const struct drop_arg *a; - - for (a = drop_args; a->name; a++) { - if (!strcmp(argv[i], a->name)) { - if (!a->val) - break; - - if (i + 1 < argc && - strstr(argv[i + 1], a->val) == argv[i + 1]) - break; - } - } - if (a->name) { - i++; - continue; - } - - if (!strcmp(argv[i], "-device") && i + 1 < argc) { - const char *template = NULL; - const char *p; - - has_dev = 1; - - if ((p = strstr(argv[i + 1], dev->template))) { - template = dev->template; - } else if ((p = strstr(argv[i + 1], dev->template_json))) { - template = dev->template_json; - has_json = 1; - } - - if (template) { - long n; - - n = strtol(p + strlen(template), NULL, dev->base); - if (!errno) - addr_map |= (1 << n); - } - } - - qemu_argv[qemu_argc++] = argv[i]; - } - - for (i = dev->first; i < dev->last; i++) { - if (!(addr_map & (1 << i))) - break; - } - if (i == dev->last) { - fprintf(stderr, "Couldn't find free address for device\n"); - usage(argv[0]); - } - - if (has_dev) { - qemu_argv[qemu_argc++] = "-device"; - if (!has_json) { - if (dev->base == 16) { - snprintf(dev_str, ARG_MAX, - "%s,%s%x%s,netdev=hostnet0,x-txburst=4096", - dev->name, dev->template, i, dev->template_post); - } else if (dev->base == 10) { - snprintf(dev_str, ARG_MAX, - "%s,%s%d%s,netdev=hostnet0,x-txburst=4096", - dev->name, dev->template, i, dev->template_post); - } - } else { - if (dev->base == 16) { - snprintf(dev_str, ARG_MAX, - "{\"driver\":\"%s\",%s%x\"%s,\"netdev\":\"hostnet0\",\"x-txburst\":4096}", - dev->name, dev->template_json, i, dev->template_json_post); - } else if (dev->base == 10) { - snprintf(dev_str, ARG_MAX, - "{\"driver\":\"%s\",%s%d\"%s,\"netdev\":\"hostnet0\",\"x-txburst\":4096}", - dev->name, dev->template_json, i, dev->template_json_post); - } - } - qemu_argv[qemu_argc++] = dev_str; - } - - qemu_argv[qemu_argc++] = "-netdev"; - if (!has_json) { - qemu_argv[qemu_argc++] = "socket,fd=" STR(DEFAULT_FD) ",id=hostnet0"; - } else { - qemu_argv[qemu_argc++] = "{\"type\":\"socket\",\"fd\":\"" STR(DEFAULT_FD) "\",\"id\":\"hostnet0\"}"; - } - qemu_argv[qemu_argc] = NULL; - -valid_args: - for (i = 1; i < UNIX_SOCK_MAX; i++) { - retry_on_reset = 50; - -retry: - s = socket(AF_UNIX, SOCK_STREAM, 0); - if (s < 0) { - perror("socket"); - exit(EXIT_FAILURE); - } - - if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) - perror("setsockopt SO_RCVTIMEO"); - if (setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv))) - perror("setsockopt SO_SNDTIMEO"); - - snprintf(addr.sun_path, UNIX_PATH_MAX, UNIX_SOCK_PATH, i); - - errno = 0; - - if (connect(s, (const struct sockaddr *)&addr, sizeof(addr))) { - rc = errno; - perror("connect"); - } else if (send(s, &probe, sizeof(probe), 0) != sizeof(probe)) { - rc = errno; - perror("send"); - } else if (recv(s, &probe_r, 1, MSG_PEEK) <= 0) { - rc = errno; - perror("recv"); - } else { - break; - } - - /* FIXME: in a KubeVirt environment, libvirtd invokes qrap three - * times in a strict sequence when a virtual machine needs to - * be started, namely, when: - * - the domain XML is saved - * - the domain is started (for "probing") - * - the virtual machine is started for real - * and it often happens that the qemu process is still running - * when qrap is invoked again, so passt will refuse the new - * connection because the previous one is still active. This - * overlap seems to be anywhere between 0 and 3ms. - * - * If we get a connection reset, retry a few times, to allow for - * the previous qemu instance to terminate and, in turn, for the - * connection to passt to be closed. - * - * This should be fixed in libvirt instead. It probably makes - * sense to check this behaviour once native libvirt support is - * there, and this implies native qemu support too, so at that - * point qrap will have no reason to exist anymore -- that is, - * this FIXME will probably remain until the tool itself is - * obsoleted. - */ - if (retry_on_reset && rc == ECONNRESET) { - retry_on_reset--; - /* cppcheck-suppress usleepCalled */ - usleep(50 * 1000); - goto retry; - } - - fprintf(stderr, "Probe of %s failed\n", addr.sun_path); - - close(s); - } - - if (i == UNIX_SOCK_MAX) { - perror("connect"); - exit(EXIT_FAILURE); - } - - tv.tv_usec = 0; - if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) - perror("setsockopt, SO_RCVTIMEO reset"); - if (setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv))) - perror("setsockopt, SO_SNDTIMEO reset"); - - fprintf(stderr, "Connected to %s\n", addr.sun_path); - - if (dup2(s, (int)fd) < 0) { - perror("dup"); - exit(EXIT_FAILURE); - } - - close(s); - - if (qemu_argc) { - char **name; - - for (name = qemu_names; *name; name++) { - qemu_argv[0] = *name; - execvp(*name, qemu_argv); - if (errno != ENOENT) { - perror("execvp"); - usage(argv[0]); - } - } - if (errno == ENOENT) - fprintf(stderr, "Couldn't find qemu command\n"); - } else { - execvp(argv[2], argv + 2); - } - - perror("execvp"); - - return EXIT_FAILURE; -} |
