diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index eb025530f2..0331789b6b 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -3220,6 +3220,7 @@ qemuBuildHostNetStr(virDomainNetDefPtr net, case VIR_DOMAIN_NET_TYPE_BRIDGE: case VIR_DOMAIN_NET_TYPE_NETWORK: case VIR_DOMAIN_NET_TYPE_DIRECT: + case VIR_DOMAIN_NET_TYPE_ETHERNET: virBufferAsprintf(&buf, "tap%c", type_sep); /* for one tapfd 'fd=' shall be used, * for more than one 'fds=' is the right choice */ @@ -3237,20 +3238,6 @@ qemuBuildHostNetStr(virDomainNetDefPtr net, is_tap = true; break; - case VIR_DOMAIN_NET_TYPE_ETHERNET: - virBufferAddLit(&buf, "tap"); - if (net->ifname) { - virBufferAsprintf(&buf, "%cifname=%s", type_sep, net->ifname); - type_sep = ','; - } - if (net->script) { - virBufferAsprintf(&buf, "%cscript=%s", type_sep, - net->script); - type_sep = ','; - } - is_tap = true; - break; - case VIR_DOMAIN_NET_TYPE_CLIENT: virBufferAsprintf(&buf, "socket%cconnect=%s:%d", type_sep, @@ -7792,7 +7779,8 @@ qemuBuildInterfaceCommandLine(virCommandPtr cmd, if (net->driver.virtio.queues > 0 && !(actualType == VIR_DOMAIN_NET_TYPE_NETWORK || actualType == VIR_DOMAIN_NET_TYPE_BRIDGE || - actualType == VIR_DOMAIN_NET_TYPE_DIRECT)) { + actualType == VIR_DOMAIN_NET_TYPE_DIRECT || + actualType == VIR_DOMAIN_NET_TYPE_ETHERNET)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Multiqueue network is not supported for: %s"), virDomainNetTypeToString(actualType)); @@ -7802,7 +7790,8 @@ qemuBuildInterfaceCommandLine(virCommandPtr cmd, /* and only TAP devices support nwfilter rules */ if (net->filter && !(actualType == VIR_DOMAIN_NET_TYPE_NETWORK || - actualType == VIR_DOMAIN_NET_TYPE_BRIDGE)) { + actualType == VIR_DOMAIN_NET_TYPE_BRIDGE || + actualType == VIR_DOMAIN_NET_TYPE_ETHERNET)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("filterref is not supported for " "network interfaces of type %s"), @@ -7812,7 +7801,8 @@ qemuBuildInterfaceCommandLine(virCommandPtr cmd, if (net->backend.tap && !(actualType == VIR_DOMAIN_NET_TYPE_NETWORK || - actualType == VIR_DOMAIN_NET_TYPE_BRIDGE)) { + actualType == VIR_DOMAIN_NET_TYPE_BRIDGE || + actualType == VIR_DOMAIN_NET_TYPE_ETHERNET)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Custom tap device path is not supported for: %s"), virDomainNetTypeToString(actualType)); @@ -7850,6 +7840,20 @@ qemuBuildInterfaceCommandLine(virCommandPtr cmd, if (qemuInterfaceDirectConnect(def, driver, net, tapfd, tapfdSize, vmop) < 0) goto cleanup; + } else if (actualType == VIR_DOMAIN_NET_TYPE_ETHERNET) { + tapfdSize = net->driver.virtio.queues; + if (!tapfdSize) + tapfdSize = 1; + + if (VIR_ALLOC_N(tapfd, tapfdSize) < 0 || + VIR_ALLOC_N(tapfdName, tapfdSize) < 0) + goto cleanup; + + memset(tapfd, -1, tapfdSize * sizeof(tapfd[0])); + + if (qemuInterfaceEthernetConnect(def, driver, net, + tapfd, tapfdSize) < 0) + goto cleanup; } /* For types whose implementations use a netdev on the host, add diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index a78bc60d58..f5809064f2 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -895,7 +895,8 @@ int qemuDomainAttachNetDevice(virConnectPtr conn, if (net->driver.virtio.queues > 0 && !(actualType == VIR_DOMAIN_NET_TYPE_NETWORK || actualType == VIR_DOMAIN_NET_TYPE_BRIDGE || - actualType == VIR_DOMAIN_NET_TYPE_DIRECT)) { + actualType == VIR_DOMAIN_NET_TYPE_DIRECT || + actualType == VIR_DOMAIN_NET_TYPE_ETHERNET)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Multiqueue network is not supported for: %s"), virDomainNetTypeToString(actualType)); @@ -905,7 +906,8 @@ int qemuDomainAttachNetDevice(virConnectPtr conn, /* and only TAP devices support nwfilter rules */ if (net->filter && !(actualType == VIR_DOMAIN_NET_TYPE_NETWORK || - actualType == VIR_DOMAIN_NET_TYPE_BRIDGE)) { + actualType == VIR_DOMAIN_NET_TYPE_BRIDGE || + actualType == VIR_DOMAIN_NET_TYPE_ETHERNET)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("filterref is not supported for " "network interfaces of type %s"), @@ -950,10 +952,19 @@ int qemuDomainAttachNetDevice(virConnectPtr conn, vhostfd, &vhostfdSize) < 0) goto cleanup; } else if (actualType == VIR_DOMAIN_NET_TYPE_ETHERNET) { - vhostfdSize = 1; - if (VIR_ALLOC(vhostfd) < 0) + tapfdSize = vhostfdSize = net->driver.virtio.queues; + if (!tapfdSize) + tapfdSize = vhostfdSize = 1; + if (VIR_ALLOC_N(tapfd, tapfdSize) < 0) goto cleanup; - *vhostfd = -1; + memset(tapfd, -1, sizeof(*tapfd) * tapfdSize); + if (VIR_ALLOC_N(vhostfd, vhostfdSize) < 0) + goto cleanup; + memset(vhostfd, -1, sizeof(*vhostfd) * vhostfdSize); + if (qemuInterfaceEthernetConnect(vm->def, driver, net, + tapfd, tapfdSize) < 0) + goto cleanup; + iface_connected = true; if (qemuInterfaceOpenVhostNet(vm->def, net, priv->qemuCaps, vhostfd, &vhostfdSize) < 0) goto cleanup; @@ -2204,6 +2215,21 @@ int qemuDomainChangeNetLinkState(virQEMUDriverPtr driver, if (ret < 0) goto cleanup; + if (virDomainNetGetActualType(dev) == VIR_DOMAIN_NET_TYPE_ETHERNET) { + switch (linkstate) { + case VIR_DOMAIN_NET_INTERFACE_LINK_STATE_UP: + case VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DEFAULT: + if ((ret = virNetDevSetOnline(dev->ifname, true)) < 0) + goto cleanup; + break; + + case VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN: + if ((ret = virNetDevSetOnline(dev->ifname, false)) < 0) + goto cleanup; + break; + } + } + /* modify the device configuration */ dev->linkstate = linkstate; diff --git a/src/qemu/qemu_interface.c b/src/qemu/qemu_interface.c index 08af133581..13a5131528 100644 --- a/src/qemu/qemu_interface.c +++ b/src/qemu/qemu_interface.c @@ -387,6 +387,124 @@ qemuCreateInBridgePortWithHelper(virQEMUDriverConfigPtr cfg, return *tapfd < 0 ? -1 : 0; } +/** + * qemuExecuteEthernetScript: + * @ifname: the interface name + * @script: the script name + * + * This function executes script for new tap device created by libvirt. + * Returns 0 in case of success or -1 on failure + */ +static int +qemuExecuteEthernetScript(const char *ifname, const char *script) +{ + virCommandPtr cmd; + int ret; + + cmd = virCommandNew(script); + virCommandAddArgFormat(cmd, "%s", ifname); + virCommandClearCaps(cmd); +#ifdef CAP_NET_ADMIN + virCommandAllowCap(cmd, CAP_NET_ADMIN); +#endif + virCommandAddEnvPassCommon(cmd); + + ret = virCommandRun(cmd, NULL); + + virCommandFree(cmd); + return ret; +} + +/* qemuInterfaceEthernetConnect: + * @def: the definition of the VM + * @driver: qemu driver data + * @net: pointer to the VM's interface description + * @tapfd: array of file descriptor return value for the new device + * @tapfdsize: number of file descriptors in @tapfd + * + * Called *only* called if actualType is VIR_DOMAIN_NET_TYPE_ETHERNET + * (i.e. if the connection is made with a tap device) + */ +int +qemuInterfaceEthernetConnect(virDomainDefPtr def, + virQEMUDriverPtr driver, + virDomainNetDefPtr net, + int *tapfd, + size_t tapfdSize) +{ + virMacAddr tapmac; + int ret = -1; + unsigned int tap_create_flags = VIR_NETDEV_TAP_CREATE_IFUP; + bool template_ifname = false; + virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + const char *tunpath = "/dev/net/tun"; + + if (net->backend.tap) { + tunpath = net->backend.tap; + if (!virQEMUDriverIsPrivileged(driver)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot use custom tap device in session mode")); + goto cleanup; + } + } + + if (!net->ifname || + STRPREFIX(net->ifname, VIR_NET_GENERATED_PREFIX) || + strchr(net->ifname, '%')) { + VIR_FREE(net->ifname); + if (VIR_STRDUP(net->ifname, VIR_NET_GENERATED_PREFIX "%d") < 0) + goto cleanup; + /* avoid exposing vnet%d in getXMLDesc or error outputs */ + template_ifname = true; + } + + if (net->model && STREQ(net->model, "virtio")) + tap_create_flags |= VIR_NETDEV_TAP_CREATE_VNET_HDR; + + if (virNetDevTapCreate(&net->ifname, tunpath, tapfd, tapfdSize, + tap_create_flags) < 0) { + virDomainAuditNetDevice(def, net, tunpath, false); + goto cleanup; + } + + virDomainAuditNetDevice(def, net, tunpath, true); + virMacAddrSet(&tapmac, &net->mac); + tapmac.addr[0] = 0xFE; + + if (virNetDevSetMAC(net->ifname, &tapmac) < 0) + goto cleanup; + + if (net->script && + qemuExecuteEthernetScript(net->ifname, net->script) < 0) + goto cleanup; + + if (cfg->macFilter && + ebtablesAddForwardAllowIn(driver->ebtables, + net->ifname, + &net->mac) < 0) + goto cleanup; + + if (net->filter && + virDomainConfNWFilterInstantiate(def->uuid, net) < 0) { + goto cleanup; + } + + ret = 0; + + cleanup: + if (ret < 0) { + size_t i; + for (i = 0; i < tapfdSize && tapfd[i] >= 0; i++) + VIR_FORCE_CLOSE(tapfd[i]); + if (template_ifname) + VIR_FREE(net->ifname); + } + virObjectUnref(cfg); + + return ret; +} + + /* qemuInterfaceBridgeConnect: * @def: the definition of the VM * @driver: qemu driver data diff --git a/src/qemu/qemu_interface.h b/src/qemu/qemu_interface.h index bf4dedf84a..66ead2aef8 100644 --- a/src/qemu/qemu_interface.h +++ b/src/qemu/qemu_interface.h @@ -40,6 +40,11 @@ int qemuInterfaceDirectConnect(virDomainDefPtr def, int *tapfd, size_t tapfdSize, virNetDevVPortProfileOp vmop); +int qemuInterfaceEthernetConnect(virDomainDefPtr def, + virQEMUDriverPtr driver, + virDomainNetDefPtr net, + int *tapfd, + size_t tapfdSize); int qemuInterfaceBridgeConnect(virDomainDefPtr def, virQEMUDriverPtr driver, diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index fa05b2c831..6ec7b76694 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -5847,6 +5847,12 @@ void qemuProcessStop(virQEMUDriverPtr driver, virDomainNetGetActualVirtPortProfile(net), cfg->stateDir)); break; + case VIR_DOMAIN_NET_TYPE_ETHERNET: + if (net->ifname) { + ignore_value(virNetDevTapDelete(net->ifname, net->backend.tap)); + VIR_FREE(net->ifname); + } + break; case VIR_DOMAIN_NET_TYPE_BRIDGE: case VIR_DOMAIN_NET_TYPE_NETWORK: #ifdef VIR_NETDEV_TAP_REQUIRE_MANUAL_CLEANUP