/* * qemu_passt.c: QEMU passt support * * Copyright (C) 2022 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include "qemu_dbus.h" #include "qemu_extdevice.h" #include "qemu_security.h" #include "qemu_passt.h" #include "virenum.h" #include "virerror.h" #include "virjson.h" #include "virlog.h" #include "virpidfile.h" #define VIR_FROM_THIS VIR_FROM_NONE VIR_LOG_INIT("qemu.passt"); static char * qemuPasstCreatePidFilename(virDomainObj *vm, virDomainNetDef *net) { qemuDomainObjPrivate *priv = vm->privateData; virQEMUDriver *driver = priv->driver; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); g_autofree char *name = NULL; name = g_strdup_printf("%s-%s-passt", vm->def->name, net->info.alias); return virPidFileBuildPath(cfg->passtStateDir, name); } static char * qemuPasstCreateSocketPath(virDomainObj *vm, virDomainNetDef *net) { char uuidstr[VIR_UUID_STRING_BUFLEN]; qemuDomainObjPrivate *priv = vm->privateData; virQEMUDriver *driver = priv->driver; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); return g_strdup_printf("%s/%s-%s.socket", cfg->passtStateDir, virUUIDFormat(vm->def->uuid, uuidstr), net->info.alias); } static int qemuPasstGetPid(virDomainObj *vm, virDomainNetDef *net, pid_t *pid) { g_autofree char *pidfile = qemuPasstCreatePidFilename(vm, net); return virPidFileReadPathIfLocked(pidfile, pid); } int qemuPasstAddNetProps(virDomainObj *vm, virDomainNetDef *net, virJSONValue **netprops) { g_autofree char *passtSocketName = qemuPasstCreateSocketPath(vm, net); g_autoptr(virJSONValue) addrprops = NULL; if (virJSONValueObjectAdd(&addrprops, "s:type", "unix", "s:path", passtSocketName, NULL) < 0) { return -1; } if (virJSONValueObjectAdd(netprops, "s:type", "stream", "a:addr", &addrprops, "b:server", false, /* "u:reconnect", 5, */ NULL) < 0) { return -1; } return 0; } void qemuPasstStop(virDomainObj *vm, virDomainNetDef *net) { g_autofree char *pidfile = qemuPasstCreatePidFilename(vm, net); virErrorPtr orig_err; virErrorPreserveLast(&orig_err); if (virPidFileForceCleanupPath(pidfile) < 0) VIR_WARN("Unable to kill passt process"); virErrorRestore(&orig_err); } int qemuPasstSetupCgroup(virDomainObj *vm, virDomainNetDef *net, virCgroup *cgroup) { pid_t pid = (pid_t) -1; if (qemuPasstGetPid(vm, net, &pid) < 0 || pid <= 0) return -1; return virCgroupAddProcess(cgroup, pid); } int qemuPasstStart(virDomainObj *vm, virDomainNetDef *net) { qemuDomainObjPrivate *priv = vm->privateData; virQEMUDriver *driver = priv->driver; g_autofree char *passtSocketName = qemuPasstCreateSocketPath(vm, net); g_autoptr(virCommand) cmd = NULL; g_autofree char *pidfile = qemuPasstCreatePidFilename(vm, net); char macaddr[VIR_MAC_STRING_BUFLEN]; size_t i; pid_t pid = (pid_t) -1; int exitstatus = 0; int cmdret = 0; VIR_AUTOCLOSE errfd = -1; cmd = virCommandNew(PASST); virCommandClearCaps(cmd); virCommandSetPidFile(cmd, pidfile); virCommandSetErrorFD(cmd, &errfd); virCommandDaemonize(cmd); virCommandAddArgList(cmd, "--one-off", "--socket", passtSocketName, "--mac-addr", virMacAddrFormat(&net->mac, macaddr), NULL); if (net->mtu) { virCommandAddArg(cmd, "--mtu"); virCommandAddArgFormat(cmd, "%u", net->mtu); } if (net->backend.upstream) virCommandAddArgList(cmd, "--interface", net->backend.upstream, NULL); if (net->backend.logFile) virCommandAddArgList(cmd, "--log-file", net->backend.logFile, NULL); /* Add IP address info */ for (i = 0; i < net->guestIP.nips; i++) { const virNetDevIPAddr *ip = net->guestIP.ips[i]; g_autofree char *addr = NULL; /* validation has already limited us to * a single IPv4 and single IPv6 address */ if (!(addr = virSocketAddrFormat(&ip->address))) return -1; virCommandAddArgList(cmd, "--address", addr, NULL); if (ip->prefix && VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET)) { /* validation already made sure no prefix is * specified for IPv6 (not allowed by passt) */ virCommandAddArg(cmd, "--netmask"); virCommandAddArgFormat(cmd, "%u", ip->prefix); } } /* Add port forwarding info */ for (i = 0; i < net->nPortForwards; i++) { virDomainNetPortForward *pf = net->portForwards[i]; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; if (pf->proto == VIR_DOMAIN_NET_PROTO_TCP) { virCommandAddArg(cmd, "--tcp-ports"); } else if (pf->proto == VIR_DOMAIN_NET_PROTO_UDP) { virCommandAddArg(cmd, "--udp-ports"); } else { /* validation guarantees this will never happen */ virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid portForward proto value %u"), pf->proto); goto error; } if (VIR_SOCKET_ADDR_VALID(&pf->address)) { g_autofree char *addr = NULL; if (!(addr = virSocketAddrFormat(&pf->address))) goto error; virBufferAddStr(&buf, addr); if (pf->dev) virBufferAsprintf(&buf, "%%%s", pf->dev); virBufferAddChar(&buf, '/'); } if (!pf->nRanges) { virBufferAddLit(&buf, "all"); } else { size_t r; for (r = 0; r < pf->nRanges; r++) { virDomainNetPortForwardRange *range = pf->ranges[r]; if (r > 0) virBufferAddChar(&buf, ','); if (range->exclude == VIR_TRISTATE_BOOL_YES) virBufferAddChar(&buf, '~'); virBufferAsprintf(&buf, "%u", range->start); if (range->end) virBufferAsprintf(&buf, "-%u", range->end); if (range->to) { virBufferAsprintf(&buf, ":%u", range->to); if (range->end) { virBufferAsprintf(&buf, "-%u", range->end + range->to - range->start); } } } } virCommandAddArg(cmd, virBufferCurrentContent(&buf)); } if (qemuExtDeviceLogCommand(driver, vm, cmd, "passt") < 0) goto error; if (qemuSecurityCommandRun(driver, vm, cmd, -1, -1, &exitstatus, &cmdret) < 0) goto error; if (cmdret < 0 || exitstatus != 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not start 'passt'. exitstatus: %d"), exitstatus); goto error; } return 0; error: ignore_value(virPidFileReadPathIfLocked(pidfile, &pid)); if (pid != -1) virProcessKillPainfully(pid, true); if (pidfile) unlink(pidfile); return -1; }