2014-09-16 20:50:53 +00:00
|
|
|
/*
|
|
|
|
* qemu_interface.c: QEMU interface management
|
|
|
|
*
|
2016-02-15 15:26:40 +00:00
|
|
|
* Copyright (C) 2015-2016 Red Hat, Inc.
|
2014-09-16 20:50:53 +00:00
|
|
|
* Copyright IBM Corp. 2014
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
* <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
2014-12-10 17:41:16 +00:00
|
|
|
#include "network_conf.h"
|
2016-02-15 15:26:40 +00:00
|
|
|
#include "domain_audit.h"
|
2016-02-15 15:52:50 +00:00
|
|
|
#include "domain_nwfilter.h"
|
2014-09-16 20:50:53 +00:00
|
|
|
#include "qemu_interface.h"
|
2016-02-15 15:26:40 +00:00
|
|
|
#include "viralloc.h"
|
2016-02-15 15:52:50 +00:00
|
|
|
#include "virlog.h"
|
|
|
|
#include "virstring.h"
|
2014-09-16 20:50:53 +00:00
|
|
|
#include "virnetdev.h"
|
|
|
|
#include "virnetdevtap.h"
|
|
|
|
#include "virnetdevmacvlan.h"
|
2014-12-10 17:41:16 +00:00
|
|
|
#include "virnetdevbridge.h"
|
2014-09-16 20:50:53 +00:00
|
|
|
#include "virnetdevvportprofile.h"
|
2020-01-17 11:57:17 +00:00
|
|
|
#include "virsocket.h"
|
2014-09-16 20:50:53 +00:00
|
|
|
|
2016-02-15 16:51:48 +00:00
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
2016-02-15 15:52:50 +00:00
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
|
|
|
|
VIR_LOG_INIT("qemu.qemu_interface");
|
|
|
|
|
2014-09-16 20:50:53 +00:00
|
|
|
/**
|
|
|
|
* qemuInterfaceStartDevice:
|
|
|
|
* @net: net device to start
|
|
|
|
*
|
|
|
|
* Based upon the type of device provided, perform the appropriate
|
|
|
|
* work to completely activate the device and make it reachable from
|
|
|
|
* the rest of the network.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuInterfaceStartDevice(virDomainNetDefPtr net)
|
|
|
|
{
|
2014-12-11 20:11:10 +00:00
|
|
|
virDomainNetType actualType = virDomainNetGetActualType(net);
|
2014-09-16 20:50:53 +00:00
|
|
|
|
2014-12-11 20:11:10 +00:00
|
|
|
switch (actualType) {
|
2014-09-16 20:50:53 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
2019-04-30 12:26:25 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
2014-12-10 17:41:16 +00:00
|
|
|
if (virDomainNetGetActualBridgeMACTableManager(net)
|
|
|
|
== VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_LIBVIRT) {
|
|
|
|
/* libvirt is managing the FDB of the bridge this device
|
|
|
|
* is attaching to, so we have turned off learning and
|
|
|
|
* unicast_flood on the device to prevent the kernel from
|
|
|
|
* adding any FDB entries for it. This means we need to
|
|
|
|
* add an fdb entry ourselves, using the MAC address from
|
|
|
|
* the interface config.
|
|
|
|
*/
|
|
|
|
if (virNetDevBridgeFDBAdd(&net->mac, net->ifname,
|
|
|
|
VIR_NETDEVBRIDGE_FDB_FLAG_MASTER |
|
|
|
|
VIR_NETDEVBRIDGE_FDB_FLAG_TEMP) < 0)
|
2019-11-12 20:46:27 +00:00
|
|
|
return -1;
|
2014-12-10 17:41:16 +00:00
|
|
|
}
|
2014-09-16 20:50:53 +00:00
|
|
|
break;
|
2015-04-13 17:26:54 +00:00
|
|
|
|
|
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT: {
|
|
|
|
const char *physdev = virDomainNetGetActualDirectDev(net);
|
|
|
|
bool isOnline = true;
|
|
|
|
|
|
|
|
/* set the physdev online if necessary. It may already be up,
|
|
|
|
* in which case we shouldn't re-up it just in case that causes
|
|
|
|
* some sort of "blip" in the physdev's status.
|
|
|
|
*/
|
|
|
|
if (physdev && virNetDevGetOnline(physdev, &isOnline) < 0)
|
2019-11-12 20:46:27 +00:00
|
|
|
return -1;
|
2015-04-13 17:26:54 +00:00
|
|
|
if (!isOnline && virNetDevSetOnline(physdev, true) < 0)
|
2019-11-12 20:46:27 +00:00
|
|
|
return -1;
|
2015-04-13 17:26:54 +00:00
|
|
|
|
2014-09-16 20:50:53 +00:00
|
|
|
/* macvtap devices share their MAC address with the guest
|
|
|
|
* domain, and if they are set online prior to the domain CPUs
|
|
|
|
* being started, the host may send out traffic from this
|
|
|
|
* device that could confuse other entities on the network (in
|
|
|
|
* particular, if this new domain is the destination of a
|
|
|
|
* migration, and the source domain is still running, another
|
|
|
|
* host may mistakenly direct traffic for the guest to the
|
|
|
|
* destination domain rather than source domain). To prevent
|
|
|
|
* this, we create the macvtap device with IFF_UP false
|
|
|
|
* (i.e. "offline") then wait to bring it online until just as
|
|
|
|
* we are starting the domain CPUs.
|
|
|
|
*/
|
|
|
|
if (virNetDevSetOnline(net->ifname, true) < 0)
|
2019-11-12 20:46:27 +00:00
|
|
|
return -1;
|
2014-09-16 20:50:53 +00:00
|
|
|
break;
|
2015-04-13 17:26:54 +00:00
|
|
|
}
|
2014-09-16 20:50:53 +00:00
|
|
|
|
|
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
2016-08-24 16:09:22 +00:00
|
|
|
if (virNetDevIPInfoAddToDev(net->ifname, &net->hostIP) < 0)
|
2019-11-12 20:46:27 +00:00
|
|
|
return -1;
|
2016-08-24 16:09:22 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
2014-09-16 20:50:53 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
2015-08-29 20:19:10 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
2014-09-16 20:50:53 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
2020-10-14 17:08:25 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
2014-09-16 20:50:53 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
|
|
/* these types all require no action */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-11-12 20:46:27 +00:00
|
|
|
return 0;
|
2014-09-16 20:50:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuInterfaceStartDevices:
|
|
|
|
* @def: domain definition
|
|
|
|
*
|
|
|
|
* Set all ifaces associated with this domain to the online state.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuInterfaceStartDevices(virDomainDefPtr def)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < def->nnets; i++) {
|
|
|
|
if (qemuInterfaceStartDevice(def->nets[i]) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2014-12-11 20:11:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuInterfaceStopDevice:
|
|
|
|
* @net: net device to stop
|
|
|
|
*
|
|
|
|
* Based upon the type of device provided, perform the appropriate
|
|
|
|
* work to deactivate the device so that packets aren't forwarded to
|
|
|
|
* it from the rest of the network.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuInterfaceStopDevice(virDomainNetDefPtr net)
|
|
|
|
{
|
|
|
|
virDomainNetType actualType = virDomainNetGetActualType(net);
|
|
|
|
|
|
|
|
switch (actualType) {
|
|
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
2019-04-30 12:26:25 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
2014-12-10 17:41:16 +00:00
|
|
|
if (virDomainNetGetActualBridgeMACTableManager(net)
|
|
|
|
== VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_LIBVIRT) {
|
|
|
|
/* remove the FDB entries that were added during
|
|
|
|
* qemuInterfaceStartDevices()
|
|
|
|
*/
|
|
|
|
if (virNetDevBridgeFDBDel(&net->mac, net->ifname,
|
|
|
|
VIR_NETDEVBRIDGE_FDB_FLAG_MASTER |
|
|
|
|
VIR_NETDEVBRIDGE_FDB_FLAG_TEMP) < 0)
|
2019-11-12 20:46:27 +00:00
|
|
|
return -1;
|
2014-12-10 17:41:16 +00:00
|
|
|
}
|
2014-12-11 20:11:10 +00:00
|
|
|
break;
|
|
|
|
|
2015-04-13 17:26:54 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT: {
|
|
|
|
const char *physdev = virDomainNetGetActualDirectDev(net);
|
|
|
|
|
2014-12-11 20:11:10 +00:00
|
|
|
/* macvtap interfaces need to be marked !IFF_UP (ie "down") to
|
|
|
|
* prevent any host-generated traffic sent from this interface
|
|
|
|
* from putting bad info into the arp caches of other machines
|
|
|
|
* on this network.
|
|
|
|
*/
|
|
|
|
if (virNetDevSetOnline(net->ifname, false) < 0)
|
2019-11-12 20:46:27 +00:00
|
|
|
return -1;
|
2015-04-13 17:26:54 +00:00
|
|
|
|
|
|
|
/* also mark the physdev down for passthrough macvtap, as the
|
|
|
|
* physdev has the same MAC address as the macvtap device.
|
|
|
|
*/
|
|
|
|
if (virDomainNetGetActualDirectMode(net) ==
|
|
|
|
VIR_NETDEV_MACVLAN_MODE_PASSTHRU &&
|
|
|
|
physdev && virNetDevSetOnline(physdev, false) < 0)
|
2019-11-12 20:46:27 +00:00
|
|
|
return -1;
|
2014-12-11 20:11:10 +00:00
|
|
|
break;
|
2015-04-13 17:26:54 +00:00
|
|
|
}
|
2014-12-11 20:11:10 +00:00
|
|
|
|
2016-06-27 09:56:16 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
2016-06-21 16:00:45 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
2014-12-11 20:11:10 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
2015-08-29 20:19:10 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
2014-12-11 20:11:10 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
2020-10-14 17:08:25 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
2014-12-11 20:11:10 +00:00
|
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
|
|
/* these types all require no action */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-11-12 20:46:27 +00:00
|
|
|
return 0;
|
2014-12-11 20:11:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuInterfaceStopDevices:
|
|
|
|
* @def: domain definition
|
|
|
|
*
|
|
|
|
* Make all interfaces associated with this domain inaccessible from
|
|
|
|
* the rest of the network.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuInterfaceStopDevices(virDomainDefPtr def)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < def->nnets; i++) {
|
|
|
|
if (qemuInterfaceStopDevice(def->nets[i]) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2016-02-15 15:26:40 +00:00
|
|
|
|
|
|
|
|
2020-08-08 13:59:13 +00:00
|
|
|
static bool
|
|
|
|
qemuInterfaceIsVnetCompatModel(const virDomainNetDef *net)
|
|
|
|
{
|
|
|
|
return (virDomainNetIsVirtioModel(net) ||
|
|
|
|
net->model == VIR_DOMAIN_NET_MODEL_E1000E ||
|
|
|
|
net->model == VIR_DOMAIN_NET_MODEL_VMXNET3);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-15 15:26:40 +00:00
|
|
|
/**
|
|
|
|
* qemuInterfaceDirectConnect:
|
|
|
|
* @def: the definition of the VM (needed by 802.1Qbh and audit)
|
|
|
|
* @driver: pointer to the driver instance
|
|
|
|
* @net: pointer to the VM's interface description with direct device type
|
|
|
|
* @tapfd: array of file descriptor return value for the new device
|
|
|
|
* @tapfdSize: number of file descriptors in @tapfd
|
|
|
|
* @vmop: VM operation type
|
|
|
|
*
|
|
|
|
* Returns 0 on success or -1 in case of error.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuInterfaceDirectConnect(virDomainDefPtr def,
|
|
|
|
virQEMUDriverPtr driver,
|
|
|
|
virDomainNetDefPtr net,
|
|
|
|
int *tapfd,
|
|
|
|
size_t tapfdSize,
|
|
|
|
virNetDevVPortProfileOp vmop)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
char *res_ifname = NULL;
|
2020-09-04 06:59:08 +00:00
|
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
2016-02-15 15:26:40 +00:00
|
|
|
unsigned int macvlan_create_flags = VIR_NETDEV_MACVLAN_CREATE_WITH_TAP;
|
|
|
|
|
2020-08-08 13:59:13 +00:00
|
|
|
if (qemuInterfaceIsVnetCompatModel(net))
|
2016-02-15 15:26:40 +00:00
|
|
|
macvlan_create_flags |= VIR_NETDEV_MACVLAN_VNET_HDR;
|
|
|
|
|
|
|
|
if (virNetDevMacVLanCreateWithVPortProfile(net->ifname,
|
|
|
|
&net->mac,
|
|
|
|
virDomainNetGetActualDirectDev(net),
|
|
|
|
virDomainNetGetActualDirectMode(net),
|
2016-05-04 17:18:16 +00:00
|
|
|
virDomainNetGetActualVlan(net),
|
2016-02-15 15:26:40 +00:00
|
|
|
def->uuid,
|
|
|
|
virDomainNetGetActualVirtPortProfile(net),
|
|
|
|
&res_ifname,
|
|
|
|
vmop, cfg->stateDir,
|
|
|
|
tapfd, tapfdSize,
|
|
|
|
macvlan_create_flags) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
virDomainAuditNetDevice(def, net, res_ifname, true);
|
|
|
|
VIR_FREE(net->ifname);
|
|
|
|
net->ifname = res_ifname;
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (ret < 0) {
|
|
|
|
while (tapfdSize--)
|
|
|
|
VIR_FORCE_CLOSE(tapfd[tapfdSize]);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
2016-02-15 15:52:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuCreateInBridgePortWithHelper:
|
|
|
|
* @cfg: the configuration object in which the helper name is looked up
|
|
|
|
* @brname: the bridge name
|
|
|
|
* @ifname: the returned interface name
|
|
|
|
* @macaddr: the returned MAC address
|
|
|
|
* @tapfd: file descriptor return value for the new tap device
|
|
|
|
* @flags: OR of virNetDevTapCreateFlags:
|
|
|
|
|
|
|
|
* VIR_NETDEV_TAP_CREATE_VNET_HDR
|
|
|
|
* - Enable IFF_VNET_HDR on the tap device
|
|
|
|
*
|
|
|
|
* This function creates a new tap device on a bridge using an external
|
|
|
|
* helper. The final name for the bridge will be stored in @ifname.
|
|
|
|
*
|
|
|
|
* Returns 0 in case of success or -1 on failure
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
qemuCreateInBridgePortWithHelper(virQEMUDriverConfigPtr cfg,
|
|
|
|
const char *brname,
|
|
|
|
char **ifname,
|
|
|
|
int *tapfd,
|
|
|
|
unsigned int flags)
|
|
|
|
{
|
|
|
|
virCommandPtr cmd;
|
|
|
|
char *errbuf = NULL, *cmdstr = NULL;
|
|
|
|
int pair[2] = { -1, -1 };
|
|
|
|
|
|
|
|
if ((flags & ~VIR_NETDEV_TAP_CREATE_VNET_HDR) != VIR_NETDEV_TAP_CREATE_IFUP)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) {
|
|
|
|
virReportSystemError(errno, "%s", _("failed to create socket"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!virFileIsExecutable(cfg->bridgeHelperName)) {
|
|
|
|
virReportSystemError(errno, _("'%s' is not a suitable bridge helper"),
|
|
|
|
cfg->bridgeHelperName);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd = virCommandNew(cfg->bridgeHelperName);
|
|
|
|
if (flags & VIR_NETDEV_TAP_CREATE_VNET_HDR)
|
|
|
|
virCommandAddArgFormat(cmd, "--use-vnet");
|
|
|
|
virCommandAddArgFormat(cmd, "--br=%s", brname);
|
|
|
|
virCommandAddArgFormat(cmd, "--fd=%d", pair[1]);
|
|
|
|
virCommandSetErrorBuffer(cmd, &errbuf);
|
|
|
|
virCommandDoAsyncIO(cmd);
|
|
|
|
virCommandPassFD(cmd, pair[1],
|
|
|
|
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
|
|
|
|
virCommandClearCaps(cmd);
|
|
|
|
#ifdef CAP_NET_ADMIN
|
|
|
|
virCommandAllowCap(cmd, CAP_NET_ADMIN);
|
|
|
|
#endif
|
|
|
|
if (virCommandRunAsync(cmd, NULL) < 0) {
|
|
|
|
*tapfd = -1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
2020-01-17 11:57:17 +00:00
|
|
|
*tapfd = virSocketRecvFD(pair[0], 0);
|
2016-02-15 15:52:50 +00:00
|
|
|
} while (*tapfd < 0 && errno == EINTR);
|
|
|
|
|
|
|
|
if (*tapfd < 0) {
|
|
|
|
char *errstr = NULL;
|
|
|
|
|
qemu: use line breaks in command line args written to log
The QEMU command line arguments are very long and currently all written
on a single line to /var/log/libvirt/qemu/$GUEST.log. This introduces
logic to add line breaks after every env variable and "-" optional
argument, and every positional argument. This will create a clearer log
file, which will in turn present better in bug reports when people cut +
paste from the log into a bug comment.
An example log file entry now looks like this:
2018-12-14 12:57:03.677+0000: starting up libvirt version: 5.0.0, qemu version: 3.0.0qemu-3.0.0-1.fc29, kernel: 4.19.5-300.fc29.x86_64, hostname: localhost.localdomain
LC_ALL=C \
PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin \
HOME=/home/berrange \
USER=berrange \
LOGNAME=berrange \
QEMU_AUDIO_DRV=none \
/usr/bin/qemu-system-ppc64 \
-name guest=guest,debug-threads=on \
-S \
-object secret,id=masterKey0,format=raw,file=/home/berrange/.config/libvirt/qemu/lib/domain-33-guest/master-key.aes \
-machine pseries-2.10,accel=tcg,usb=off,dump-guest-core=off \
-m 1024 \
-realtime mlock=off \
-smp 1,sockets=1,cores=1,threads=1 \
-uuid c8a74977-ab18-41d0-ae3b-4041c7fffbcd \
-display none \
-no-user-config \
-nodefaults \
-chardev socket,id=charmonitor,fd=23,server,nowait \
-mon chardev=charmonitor,id=monitor,mode=control \
-rtc base=utc \
-no-shutdown \
-boot strict=on \
-device qemu-xhci,id=usb,bus=pci.0,addr=0x1 \
-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x2 \
-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
-msg timestamp=on
2018-12-14 12:57:03.730+0000: shutting down, reason=failed
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
2018-12-14 12:07:08 +00:00
|
|
|
if (!(cmdstr = virCommandToString(cmd, false)))
|
2016-02-15 15:52:50 +00:00
|
|
|
goto cleanup;
|
|
|
|
virCommandAbort(cmd);
|
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
if (errbuf && *errbuf)
|
|
|
|
errstr = g_strdup_printf("\nstderr=%s", errbuf);
|
2016-02-15 15:52:50 +00:00
|
|
|
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("%s: failed to communicate with bridge helper: %s%s"),
|
2020-02-26 17:57:34 +00:00
|
|
|
cmdstr, g_strerror(errno),
|
2019-02-12 16:25:06 +00:00
|
|
|
NULLSTR_EMPTY(errstr));
|
2016-02-15 15:52:50 +00:00
|
|
|
VIR_FREE(errstr);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virNetDevTapGetName(*tapfd, ifname) < 0 ||
|
|
|
|
virCommandWait(cmd, NULL) < 0) {
|
|
|
|
VIR_FORCE_CLOSE(*tapfd);
|
|
|
|
*tapfd = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
VIR_FREE(cmdstr);
|
|
|
|
VIR_FREE(errbuf);
|
|
|
|
virCommandFree(cmd);
|
|
|
|
VIR_FORCE_CLOSE(pair[0]);
|
|
|
|
return *tapfd < 0 ? -1 : 0;
|
|
|
|
}
|
|
|
|
|
2016-03-23 11:37:59 +00:00
|
|
|
|
|
|
|
/* 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;
|
2020-09-04 06:59:08 +00:00
|
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
2016-03-23 11:37:59 +00:00
|
|
|
const char *tunpath = "/dev/net/tun";
|
2019-08-27 16:18:35 +00:00
|
|
|
const char *auditdev = tunpath;
|
2016-03-23 11:37:59 +00:00
|
|
|
|
|
|
|
if (net->backend.tap) {
|
|
|
|
tunpath = net->backend.tap;
|
2020-03-31 15:42:43 +00:00
|
|
|
if (!driver->privileged) {
|
2016-03-23 11:37:59 +00:00
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("cannot use custom tap device in session mode"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-08 13:59:13 +00:00
|
|
|
if (qemuInterfaceIsVnetCompatModel(net))
|
2019-08-27 16:18:35 +00:00
|
|
|
tap_create_flags |= VIR_NETDEV_TAP_CREATE_VNET_HDR;
|
|
|
|
|
2019-08-26 04:24:34 +00:00
|
|
|
if (net->managed_tap == VIR_TRISTATE_BOOL_NO) {
|
|
|
|
if (!net->ifname) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("target dev must be supplied when managed='no'"));
|
2016-03-23 11:37:59 +00:00
|
|
|
goto cleanup;
|
2019-08-26 04:24:34 +00:00
|
|
|
}
|
|
|
|
if (virNetDevExists(net->ifname) != 1) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("target managed='no' but specified dev doesn't exist"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2019-08-26 17:05:19 +00:00
|
|
|
if (virNetDevMacVLanIsMacvtap(net->ifname)) {
|
|
|
|
auditdev = net->ifname;
|
|
|
|
if (virNetDevMacVLanTapOpen(net->ifname, tapfd, tapfdSize) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
if (virNetDevMacVLanTapSetup(tapfd, tapfdSize,
|
2020-08-08 13:59:13 +00:00
|
|
|
qemuInterfaceIsVnetCompatModel(net)) < 0) {
|
2019-08-26 17:05:19 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (virNetDevTapCreate(&net->ifname, tunpath, tapfd, tapfdSize,
|
|
|
|
tap_create_flags) < 0)
|
|
|
|
goto cleanup;
|
2019-08-26 04:24:34 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-12-15 19:35:35 +00:00
|
|
|
|
|
|
|
if (!net->ifname)
|
2019-08-26 04:24:34 +00:00
|
|
|
template_ifname = true;
|
2020-12-15 19:35:35 +00:00
|
|
|
|
2019-08-26 04:24:34 +00:00
|
|
|
if (virNetDevTapCreate(&net->ifname, tunpath, tapfd, tapfdSize,
|
|
|
|
tap_create_flags) < 0) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2016-03-23 11:37:59 +00:00
|
|
|
|
2019-08-26 04:24:34 +00:00
|
|
|
/* The tap device's MAC address cannot match the MAC address
|
|
|
|
* used by the guest. This results in "received packet on
|
|
|
|
* vnetX with own address as source address" error logs from
|
|
|
|
* the kernel.
|
|
|
|
*/
|
|
|
|
virMacAddrSet(&tapmac, &net->mac);
|
|
|
|
if (tapmac.addr[0] == 0xFE)
|
|
|
|
tapmac.addr[0] = 0xFA;
|
|
|
|
else
|
|
|
|
tapmac.addr[0] = 0xFE;
|
2016-08-25 05:46:37 +00:00
|
|
|
|
2019-08-26 04:24:34 +00:00
|
|
|
if (virNetDevSetMAC(net->ifname, &tapmac) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (virNetDevSetOnline(net->ifname, true) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2016-03-23 11:37:59 +00:00
|
|
|
|
|
|
|
if (net->script &&
|
2016-04-13 08:33:36 +00:00
|
|
|
virNetDevRunEthernetScript(net->ifname, net->script) < 0)
|
2016-03-23 11:37:59 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (cfg->macFilter &&
|
|
|
|
ebtablesAddForwardAllowIn(driver->ebtables,
|
|
|
|
net->ifname,
|
|
|
|
&net->mac) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (net->filter &&
|
2018-05-11 17:39:27 +00:00
|
|
|
virDomainConfNWFilterInstantiate(def->name, def->uuid, net, false) < 0) {
|
2016-03-23 11:37:59 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2019-08-27 16:18:35 +00:00
|
|
|
virDomainAuditNetDevice(def, net, auditdev, true);
|
|
|
|
|
2016-03-23 11:37:59 +00:00
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (ret < 0) {
|
2016-04-29 15:32:09 +00:00
|
|
|
size_t i;
|
2019-08-27 16:18:35 +00:00
|
|
|
|
|
|
|
virDomainAuditNetDevice(def, net, auditdev, false);
|
2016-03-23 11:37:59 +00:00
|
|
|
for (i = 0; i < tapfdSize && tapfd[i] >= 0; i++)
|
|
|
|
VIR_FORCE_CLOSE(tapfd[i]);
|
|
|
|
if (template_ifname)
|
|
|
|
VIR_FREE(net->ifname);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-15 15:52:50 +00:00
|
|
|
/* qemuInterfaceBridgeConnect:
|
|
|
|
* @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_NETWORK or
|
|
|
|
* VIR_DOMAIN_NET_TYPE_BRIDGE (i.e. if the connection is made with a tap
|
|
|
|
* device connecting to a bridge device)
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuInterfaceBridgeConnect(virDomainDefPtr def,
|
|
|
|
virQEMUDriverPtr driver,
|
|
|
|
virDomainNetDefPtr net,
|
|
|
|
int *tapfd,
|
2017-05-18 18:16:27 +00:00
|
|
|
size_t *tapfdSize)
|
2016-02-15 15:52:50 +00:00
|
|
|
{
|
|
|
|
const char *brname;
|
|
|
|
int ret = -1;
|
|
|
|
unsigned int tap_create_flags = VIR_NETDEV_TAP_CREATE_IFUP;
|
|
|
|
bool template_ifname = false;
|
2020-09-04 06:59:08 +00:00
|
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
2016-02-15 15:52:50 +00:00
|
|
|
const char *tunpath = "/dev/net/tun";
|
|
|
|
|
|
|
|
if (net->backend.tap) {
|
|
|
|
tunpath = net->backend.tap;
|
2020-03-31 15:42:43 +00:00
|
|
|
if (!driver->privileged) {
|
2016-02-15 15:52:50 +00:00
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("cannot use custom tap device in session mode"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(brname = virDomainNetGetActualBridgeName(net))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing bridge name"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2020-12-15 19:35:35 +00:00
|
|
|
if (!net->ifname)
|
2016-02-15 15:52:50 +00:00
|
|
|
template_ifname = true;
|
|
|
|
|
2020-08-08 13:59:13 +00:00
|
|
|
if (qemuInterfaceIsVnetCompatModel(net))
|
2016-02-15 15:52:50 +00:00
|
|
|
tap_create_flags |= VIR_NETDEV_TAP_CREATE_VNET_HDR;
|
|
|
|
|
2020-03-31 15:42:43 +00:00
|
|
|
if (driver->privileged) {
|
2016-02-15 15:52:50 +00:00
|
|
|
if (virNetDevTapCreateInBridgePort(brname, &net->ifname, &net->mac,
|
|
|
|
def->uuid, tunpath, tapfd, *tapfdSize,
|
|
|
|
virDomainNetGetActualVirtPortProfile(net),
|
|
|
|
virDomainNetGetActualVlan(net),
|
2020-02-13 17:57:47 +00:00
|
|
|
virDomainNetGetActualPortOptionsIsolated(net),
|
2017-05-18 18:16:27 +00:00
|
|
|
net->coalesce, 0, NULL,
|
2016-02-15 15:52:50 +00:00
|
|
|
tap_create_flags) < 0) {
|
|
|
|
virDomainAuditNetDevice(def, net, tunpath, false);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (virDomainNetGetActualBridgeMACTableManager(net)
|
|
|
|
== VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_LIBVIRT) {
|
|
|
|
/* libvirt is managing the FDB of the bridge this device
|
|
|
|
* is attaching to, so we need to turn off learning and
|
|
|
|
* unicast_flood on the device to prevent the kernel from
|
2017-04-22 19:06:20 +00:00
|
|
|
* adding any FDB entries for it. We will add an fdb
|
2016-02-15 15:52:50 +00:00
|
|
|
* entry ourselves (during qemuInterfaceStartDevices(),
|
|
|
|
* using the MAC address from the interface config.
|
|
|
|
*/
|
|
|
|
if (virNetDevBridgePortSetLearning(brname, net->ifname, false) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
if (virNetDevBridgePortSetUnicastFlood(brname, net->ifname, false) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (qemuCreateInBridgePortWithHelper(cfg, brname,
|
|
|
|
&net->ifname,
|
|
|
|
tapfd, tap_create_flags) < 0) {
|
|
|
|
virDomainAuditNetDevice(def, net, tunpath, false);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
/* qemuCreateInBridgePortWithHelper can only create a single FD */
|
|
|
|
if (*tapfdSize > 1) {
|
|
|
|
VIR_WARN("Ignoring multiqueue network request");
|
|
|
|
*tapfdSize = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virDomainAuditNetDevice(def, net, tunpath, true);
|
|
|
|
|
|
|
|
if (cfg->macFilter &&
|
|
|
|
ebtablesAddForwardAllowIn(driver->ebtables,
|
|
|
|
net->ifname,
|
|
|
|
&net->mac) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (net->filter &&
|
2018-05-11 17:39:27 +00:00
|
|
|
virDomainConfNWFilterInstantiate(def->name, def->uuid, net, false) < 0) {
|
2016-02-15 15:52:50 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2016-02-15 16:51:48 +00:00
|
|
|
|
|
|
|
|
2020-10-14 17:08:27 +00:00
|
|
|
/* qemuInterfaceVDPAConnect:
|
|
|
|
* @net: pointer to the VM's interface description
|
|
|
|
*
|
|
|
|
* returns: file descriptor of the vdpa device
|
|
|
|
*
|
|
|
|
* Called *only* called if actualType is VIR_DOMAIN_NET_TYPE_VDPA
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuInterfaceVDPAConnect(virDomainNetDefPtr net)
|
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
|
|
|
|
if ((fd = open(net->data.vdpa.devicepath, O_RDWR)) < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Unable to open '%s' for vdpa device"),
|
|
|
|
net->data.vdpa.devicepath);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-15 11:12:15 +00:00
|
|
|
/*
|
|
|
|
* Returns: -1 on error, 0 if slirp isn't available, 1 on succcess
|
|
|
|
*/
|
|
|
|
int
|
2019-08-08 14:55:12 +00:00
|
|
|
qemuInterfacePrepareSlirp(virQEMUDriverPtr driver,
|
2020-10-15 11:12:15 +00:00
|
|
|
virDomainNetDefPtr net,
|
|
|
|
qemuSlirpPtr *slirpret)
|
2019-08-08 14:55:12 +00:00
|
|
|
{
|
2020-09-04 06:55:39 +00:00
|
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(qemuSlirp) slirp = NULL;
|
2019-08-08 14:55:12 +00:00
|
|
|
size_t i;
|
|
|
|
|
2020-10-15 11:12:15 +00:00
|
|
|
if (!cfg->slirpHelperName ||
|
|
|
|
!virFileExists(cfg->slirpHelperName))
|
|
|
|
return 0; /* fallback to builtin slirp impl */
|
|
|
|
|
2019-08-08 14:55:12 +00:00
|
|
|
if (!(slirp = qemuSlirpNewForHelper(cfg->slirpHelperName)))
|
2020-10-15 11:12:15 +00:00
|
|
|
return -1;
|
2019-08-08 14:55:12 +00:00
|
|
|
|
|
|
|
for (i = 0; i < net->guestIP.nips; i++) {
|
|
|
|
const virNetDevIPAddr *ip = net->guestIP.ips[i];
|
|
|
|
|
|
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET) &&
|
|
|
|
!qemuSlirpHasFeature(slirp, QEMU_SLIRP_FEATURE_IPV4))
|
2020-10-15 11:12:15 +00:00
|
|
|
return 0;
|
2019-08-08 14:55:12 +00:00
|
|
|
|
|
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET6) &&
|
|
|
|
!qemuSlirpHasFeature(slirp, QEMU_SLIRP_FEATURE_IPV6))
|
2020-10-15 11:12:15 +00:00
|
|
|
return 0;
|
2019-08-08 14:55:12 +00:00
|
|
|
}
|
|
|
|
|
2020-10-15 11:12:15 +00:00
|
|
|
*slirpret = g_steal_pointer(&slirp);
|
|
|
|
return 1;
|
2019-08-08 14:55:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-15 16:51:48 +00:00
|
|
|
/**
|
|
|
|
* qemuInterfaceOpenVhostNet:
|
|
|
|
* @def: domain definition
|
|
|
|
* @net: network definition
|
|
|
|
* @qemuCaps: qemu binary capabilities
|
|
|
|
* @vhostfd: array of opened vhost-net device
|
|
|
|
* @vhostfdSize: number of file descriptors in @vhostfd array
|
|
|
|
*
|
|
|
|
* Open vhost-net, multiple times - if requested.
|
|
|
|
* In case, no vhost-net is needed, @vhostfdSize is set to 0
|
|
|
|
* and 0 is returned.
|
|
|
|
*
|
|
|
|
* Returns: 0 on success
|
|
|
|
* -1 on failure
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuInterfaceOpenVhostNet(virDomainDefPtr def,
|
|
|
|
virDomainNetDefPtr net,
|
|
|
|
int *vhostfd,
|
|
|
|
size_t *vhostfdSize)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
const char *vhostnet_path = net->backend.vhost;
|
|
|
|
|
|
|
|
if (!vhostnet_path)
|
|
|
|
vhostnet_path = "/dev/vhost-net";
|
|
|
|
|
|
|
|
/* If running a plain QEMU guest, or
|
2020-01-24 20:30:04 +00:00
|
|
|
* if the config says explicitly to not use vhost, return now */
|
2016-02-15 16:51:48 +00:00
|
|
|
if (def->virtType != VIR_DOMAIN_VIRT_KVM ||
|
|
|
|
net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_QEMU) {
|
|
|
|
*vhostfdSize = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-03-28 23:36:20 +00:00
|
|
|
/* If qemu doesn't support vhost-net mode (including the -netdev and
|
|
|
|
* -device command options), don't try to open the device.
|
2016-02-15 16:51:48 +00:00
|
|
|
*/
|
2018-03-29 10:51:55 +00:00
|
|
|
if (!qemuDomainSupportsNicdev(def, net)) {
|
2016-02-15 16:51:48 +00:00
|
|
|
if (net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_VHOST) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
"%s", _("vhost-net is not supported with "
|
|
|
|
"this QEMU binary"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
*vhostfdSize = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the nic model isn't virtio, don't try to open. */
|
2019-01-21 22:59:02 +00:00
|
|
|
if (!virDomainNetIsVirtioModel(net)) {
|
2016-02-15 16:51:48 +00:00
|
|
|
if (net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_VHOST) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
"%s", _("vhost-net is only supported for "
|
|
|
|
"virtio network interfaces"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
*vhostfdSize = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < *vhostfdSize; i++) {
|
|
|
|
vhostfd[i] = open(vhostnet_path, O_RDWR);
|
|
|
|
|
|
|
|
/* If the config says explicitly to use vhost and we couldn't open it,
|
|
|
|
* report an error.
|
|
|
|
*/
|
|
|
|
if (vhostfd[i] < 0) {
|
|
|
|
virDomainAuditNetDevice(def, net, vhostnet_path, false);
|
|
|
|
if (net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_VHOST) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
"%s", _("vhost-net was requested for an interface, "
|
|
|
|
"but is unavailable"));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
VIR_WARN("Unable to open vhost-net. Opened so far %zu, requested %zu",
|
|
|
|
i, *vhostfdSize);
|
|
|
|
*vhostfdSize = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
virDomainAuditNetDevice(def, net, vhostnet_path, *vhostfdSize);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
while (i--)
|
|
|
|
VIR_FORCE_CLOSE(vhostfd[i]);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|