mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-01 02:41:14 +00:00
4ce29411fc
When running in libvirtd, we are happy for any of the drivers to simply skip their initialization in virStateInitialize, as other drivers are still potentially useful. When running in per-driver daemons though, we want the daemon to abort startup if the driver cannot initialize itself, as the daemon will be useless without it. Reviewed-by: Andrea Bolognani <abologna@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
5966 lines
191 KiB
C
5966 lines
191 KiB
C
/*
|
|
* bridge_driver.c: core driver methods for managing network
|
|
*
|
|
* Copyright (C) 2006-2016 Red Hat, Inc.
|
|
* Copyright (C) 2006 Daniel P. Berrange
|
|
*
|
|
* 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>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/poll.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <pwd.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/ioctl.h>
|
|
#include <net/if.h>
|
|
#ifdef HAVE_SYSCTLBYNAME
|
|
# include <sys/sysctl.h>
|
|
#endif
|
|
|
|
#include "virerror.h"
|
|
#include "datatypes.h"
|
|
#include "bridge_driver.h"
|
|
#include "bridge_driver_platform.h"
|
|
#include "device_conf.h"
|
|
#include "driver.h"
|
|
#include "virbuffer.h"
|
|
#include "virpidfile.h"
|
|
#include "vircommand.h"
|
|
#include "viralloc.h"
|
|
#include "viruuid.h"
|
|
#include "viriptables.h"
|
|
#include "virlog.h"
|
|
#include "virdnsmasq.h"
|
|
#include "configmake.h"
|
|
#include "virnetlink.h"
|
|
#include "virnetdev.h"
|
|
#include "virnetdevip.h"
|
|
#include "virnetdevbridge.h"
|
|
#include "virnetdevopenvswitch.h"
|
|
#include "virnetdevtap.h"
|
|
#include "virnetdevvportprofile.h"
|
|
#include "virpci.h"
|
|
#include "virdbus.h"
|
|
#include "virfile.h"
|
|
#include "virstring.h"
|
|
#include "viraccessapicheck.h"
|
|
#include "network_event.h"
|
|
#include "virhook.h"
|
|
#include "virjson.h"
|
|
#include "virnetworkportdef.h"
|
|
|
|
#include <libxml/xpathInternals.h>
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_NETWORK
|
|
#define MAX_BRIDGE_ID 256
|
|
|
|
/**
|
|
* VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX:
|
|
*
|
|
* Macro providing the upper limit on the size of leases file
|
|
*/
|
|
#define VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX (32 * 1024 * 1024)
|
|
|
|
#define SYSCTL_PATH "/proc/sys"
|
|
|
|
VIR_LOG_INIT("network.bridge_driver");
|
|
|
|
#define DNSMASQ_NAMESPACE_HREF "http://libvirt.org/schemas/network/dnsmasq/1.0"
|
|
|
|
static virNetworkDriverStatePtr network_driver;
|
|
|
|
|
|
static virNetworkDriverStatePtr
|
|
networkGetDriver(void)
|
|
{
|
|
/* Maybe one day we can store @network_driver in the
|
|
* connection object, but until then, it's just a global
|
|
* variable which is returned. */
|
|
return network_driver;
|
|
}
|
|
|
|
|
|
static void
|
|
networkDriverLock(virNetworkDriverStatePtr driver)
|
|
{
|
|
virMutexLock(&driver->lock);
|
|
}
|
|
|
|
|
|
static void
|
|
networkDriverUnlock(virNetworkDriverStatePtr driver)
|
|
{
|
|
virMutexUnlock(&driver->lock);
|
|
}
|
|
|
|
|
|
static dnsmasqCapsPtr
|
|
networkGetDnsmasqCaps(virNetworkDriverStatePtr driver)
|
|
{
|
|
dnsmasqCapsPtr ret;
|
|
networkDriverLock(driver);
|
|
ret = virObjectRef(driver->dnsmasqCaps);
|
|
networkDriverUnlock(driver);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkDnsmasqCapsRefresh(virNetworkDriverStatePtr driver)
|
|
{
|
|
dnsmasqCapsPtr caps;
|
|
|
|
if (!(caps = dnsmasqCapsNewFromBinary(DNSMASQ)))
|
|
return -1;
|
|
|
|
networkDriverLock(driver);
|
|
virObjectUnref(driver->dnsmasqCaps);
|
|
driver->dnsmasqCaps = caps;
|
|
networkDriverUnlock(driver);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
networkDnsmasqDefNamespaceFree(void *nsdata)
|
|
{
|
|
networkDnsmasqXmlNsDefPtr def = nsdata;
|
|
if (!def)
|
|
return;
|
|
|
|
virStringListFreeCount(def->options, def->noptions);
|
|
|
|
VIR_FREE(def);
|
|
}
|
|
|
|
|
|
static int
|
|
networkDnsmasqDefNamespaceParseOptions(networkDnsmasqXmlNsDefPtr nsdef,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_AUTOFREE(xmlNodePtr *) nodes = NULL;
|
|
ssize_t nnodes;
|
|
size_t i;
|
|
|
|
if ((nnodes = virXPathNodeSet("./dnsmasq:options/dnsmasq:option",
|
|
ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (nnodes == 0)
|
|
return 0;
|
|
|
|
if (VIR_ALLOC_N(nsdef->options, nnodes) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < nnodes; i++) {
|
|
if (!(nsdef->options[nsdef->noptions++] = virXMLPropString(nodes[i], "value"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No dnsmasq options value specified"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
networkDnsmasqDefNamespaceParse(xmlXPathContextPtr ctxt,
|
|
void **data)
|
|
{
|
|
networkDnsmasqXmlNsDefPtr nsdata = NULL;
|
|
int ret = -1;
|
|
|
|
if (xmlXPathRegisterNs(ctxt, BAD_CAST "dnsmasq",
|
|
BAD_CAST DNSMASQ_NAMESPACE_HREF) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to register xml namespace '%s'"),
|
|
DNSMASQ_NAMESPACE_HREF);
|
|
return -1;
|
|
}
|
|
|
|
if (VIR_ALLOC(nsdata) < 0)
|
|
return -1;
|
|
|
|
if (networkDnsmasqDefNamespaceParseOptions(nsdata, ctxt))
|
|
goto cleanup;
|
|
|
|
if (nsdata->noptions > 0)
|
|
VIR_STEAL_PTR(*data, nsdata);
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
networkDnsmasqDefNamespaceFree(nsdata);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkDnsmasqDefNamespaceFormatXML(virBufferPtr buf,
|
|
void *nsdata)
|
|
{
|
|
networkDnsmasqXmlNsDefPtr def = nsdata;
|
|
size_t i;
|
|
|
|
if (!def->noptions)
|
|
return 0;
|
|
|
|
virBufferAddLit(buf, "<dnsmasq:options>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
for (i = 0; i < def->noptions; i++) {
|
|
virBufferEscapeString(buf, "<dnsmasq:option value='%s'/>\n",
|
|
def->options[i]);
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</dnsmasq:options>\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const char *
|
|
networkDnsmasqDefNamespaceHref(void)
|
|
{
|
|
return "xmlns:dnsmasq='" DNSMASQ_NAMESPACE_HREF "'";
|
|
}
|
|
|
|
|
|
virNetworkXMLNamespace networkDnsmasqXMLNamespace = {
|
|
.parse = networkDnsmasqDefNamespaceParse,
|
|
.free = networkDnsmasqDefNamespaceFree,
|
|
.format = networkDnsmasqDefNamespaceFormatXML,
|
|
.href = networkDnsmasqDefNamespaceHref,
|
|
};
|
|
|
|
|
|
virNetworkXMLOptionPtr
|
|
networkDnsmasqCreateXMLConf(void)
|
|
{
|
|
return virNetworkXMLOptionNew(&networkDnsmasqXMLNamespace);
|
|
}
|
|
|
|
|
|
static int
|
|
networkStateCleanup(void);
|
|
|
|
static int
|
|
networkStartNetwork(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj);
|
|
|
|
static int
|
|
networkShutdownNetwork(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj);
|
|
|
|
static int
|
|
networkStartNetworkVirtual(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj);
|
|
|
|
static int
|
|
networkShutdownNetworkVirtual(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj);
|
|
|
|
static int
|
|
networkStartNetworkExternal(virNetworkObjPtr obj);
|
|
|
|
static int
|
|
networkShutdownNetworkExternal(virNetworkObjPtr obj);
|
|
|
|
static void
|
|
networkReloadFirewallRules(virNetworkDriverStatePtr driver, bool startup);
|
|
|
|
static void
|
|
networkRefreshDaemons(virNetworkDriverStatePtr driver);
|
|
|
|
static int
|
|
networkPlugBandwidth(virNetworkObjPtr obj,
|
|
virMacAddrPtr mac,
|
|
virNetDevBandwidthPtr ifaceBand,
|
|
unsigned int *class_id);
|
|
|
|
static int
|
|
networkUnplugBandwidth(virNetworkObjPtr obj,
|
|
virNetDevBandwidthPtr ifaceBand,
|
|
unsigned int *class_id);
|
|
|
|
static void
|
|
networkNetworkObjTaint(virNetworkObjPtr obj,
|
|
virNetworkTaintFlags taint);
|
|
|
|
|
|
static virNetworkObjPtr
|
|
networkObjFromNetwork(virNetworkPtr net)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
|
|
obj = virNetworkObjFindByUUID(driver->networks, net->uuid);
|
|
if (!obj) {
|
|
virUUIDFormat(net->uuid, uuidstr);
|
|
virReportError(VIR_ERR_NO_NETWORK,
|
|
_("no network with matching uuid '%s' (%s)"),
|
|
uuidstr, net->name);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
static int
|
|
networkRunHook(virNetworkObjPtr obj,
|
|
virNetworkPortDefPtr port,
|
|
int op,
|
|
int sub_op)
|
|
{
|
|
virNetworkDefPtr def;
|
|
virBuffer buf = VIR_BUFFER_INITIALIZER;
|
|
char *xml = NULL;
|
|
int hookret;
|
|
int ret = -1;
|
|
|
|
if (virHookPresent(VIR_HOOK_DRIVER_NETWORK)) {
|
|
if (!obj) {
|
|
VIR_DEBUG("Not running hook as @obj is NULL");
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
virBufferAddLit(&buf, "<hookData>\n");
|
|
virBufferAdjustIndent(&buf, 2);
|
|
if (virNetworkDefFormatBuf(&buf, def, network_driver->xmlopt, 0) < 0)
|
|
goto cleanup;
|
|
if (port && virNetworkPortDefFormatBuf(&buf, port) < 0)
|
|
goto cleanup;
|
|
|
|
virBufferAdjustIndent(&buf, -2);
|
|
virBufferAddLit(&buf, "</hookData>");
|
|
|
|
if (virBufferCheckError(&buf) < 0)
|
|
goto cleanup;
|
|
|
|
xml = virBufferContentAndReset(&buf);
|
|
hookret = virHookCall(VIR_HOOK_DRIVER_NETWORK, def->name,
|
|
op, sub_op, NULL, xml, NULL);
|
|
|
|
/*
|
|
* If the script raised an error, pass it to the callee.
|
|
*/
|
|
if (hookret < 0)
|
|
goto cleanup;
|
|
|
|
networkNetworkObjTaint(obj, VIR_NETWORK_TAINT_HOOK);
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virBufferFreeAndReset(&buf);
|
|
VIR_FREE(xml);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static char *
|
|
networkDnsmasqLeaseFileNameDefault(virNetworkDriverStatePtr driver,
|
|
const char *netname)
|
|
{
|
|
char *leasefile;
|
|
|
|
ignore_value(virAsprintf(&leasefile, "%s/%s.leases",
|
|
driver->dnsmasqStateDir, netname));
|
|
return leasefile;
|
|
}
|
|
|
|
|
|
static char *
|
|
networkDnsmasqLeaseFileNameCustom(virNetworkDriverStatePtr driver,
|
|
const char *bridge)
|
|
{
|
|
char *leasefile;
|
|
|
|
ignore_value(virAsprintf(&leasefile, "%s/%s.status",
|
|
driver->dnsmasqStateDir, bridge));
|
|
return leasefile;
|
|
}
|
|
|
|
|
|
static char *
|
|
networkDnsmasqConfigFileName(virNetworkDriverStatePtr driver,
|
|
const char *netname)
|
|
{
|
|
char *conffile;
|
|
|
|
ignore_value(virAsprintf(&conffile, "%s/%s.conf",
|
|
driver->dnsmasqStateDir, netname));
|
|
return conffile;
|
|
}
|
|
|
|
|
|
static char *
|
|
networkRadvdPidfileBasename(const char *netname)
|
|
{
|
|
/* this is simple but we want to be sure it's consistently done */
|
|
char *pidfilebase;
|
|
|
|
ignore_value(virAsprintf(&pidfilebase, "%s-radvd", netname));
|
|
return pidfilebase;
|
|
}
|
|
|
|
|
|
static char *
|
|
networkRadvdConfigFileName(virNetworkDriverStatePtr driver,
|
|
const char *netname)
|
|
{
|
|
char *configfile;
|
|
|
|
ignore_value(virAsprintf(&configfile, "%s/%s-radvd.conf",
|
|
driver->radvdStateDir, netname));
|
|
return configfile;
|
|
}
|
|
|
|
|
|
/* do needed cleanup steps and remove the network from the list */
|
|
static int
|
|
networkRemoveInactive(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
char *leasefile = NULL;
|
|
char *customleasefile = NULL;
|
|
char *radvdconfigfile = NULL;
|
|
char *configfile = NULL;
|
|
char *radvdpidbase = NULL;
|
|
char *statusfile = NULL;
|
|
char *macMapFile = NULL;
|
|
dnsmasqContext *dctx = NULL;
|
|
virNetworkDefPtr def = virNetworkObjGetPersistentDef(obj);
|
|
|
|
int ret = -1;
|
|
|
|
/* remove the (possibly) existing dnsmasq and radvd files */
|
|
if (!(dctx = dnsmasqContextNew(def->name,
|
|
driver->dnsmasqStateDir))) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(leasefile = networkDnsmasqLeaseFileNameDefault(driver, def->name)))
|
|
goto cleanup;
|
|
|
|
if (!(customleasefile = networkDnsmasqLeaseFileNameCustom(driver, def->bridge)))
|
|
goto cleanup;
|
|
|
|
if (!(radvdconfigfile = networkRadvdConfigFileName(driver, def->name)))
|
|
goto cleanup;
|
|
|
|
if (!(radvdpidbase = networkRadvdPidfileBasename(def->name)))
|
|
goto cleanup;
|
|
|
|
if (!(configfile = networkDnsmasqConfigFileName(driver, def->name)))
|
|
goto cleanup;
|
|
|
|
if (!(statusfile = virNetworkConfigFile(driver->stateDir, def->name)))
|
|
goto cleanup;
|
|
|
|
if (!(macMapFile = virMacMapFileName(driver->dnsmasqStateDir, def->bridge)))
|
|
goto cleanup;
|
|
|
|
/* dnsmasq */
|
|
dnsmasqDelete(dctx);
|
|
unlink(leasefile);
|
|
unlink(customleasefile);
|
|
unlink(configfile);
|
|
|
|
/* MAC map manager */
|
|
unlink(macMapFile);
|
|
|
|
/* radvd */
|
|
unlink(radvdconfigfile);
|
|
virPidFileDelete(driver->pidDir, radvdpidbase);
|
|
|
|
/* remove status file */
|
|
unlink(statusfile);
|
|
|
|
/* remove the network definition */
|
|
virNetworkObjRemoveInactive(driver->networks, obj);
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
VIR_FREE(leasefile);
|
|
VIR_FREE(configfile);
|
|
VIR_FREE(customleasefile);
|
|
VIR_FREE(radvdconfigfile);
|
|
VIR_FREE(radvdpidbase);
|
|
VIR_FREE(statusfile);
|
|
VIR_FREE(macMapFile);
|
|
dnsmasqContextFree(dctx);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static char *
|
|
networkBridgeDummyNicName(const char *brname)
|
|
{
|
|
static const char dummyNicSuffix[] = "-nic";
|
|
char *nicname;
|
|
|
|
if (strlen(brname) + sizeof(dummyNicSuffix) > IFNAMSIZ) {
|
|
/* because the length of an ifname is limited to IFNAMSIZ-1
|
|
* (usually 15), and we're adding 4 more characters, we must
|
|
* truncate the original name to 11 to fit. In order to catch
|
|
* a possible numeric ending (eg virbr0, virbr1, etc), we grab
|
|
* the first 8 and last 3 characters of the string.
|
|
*/
|
|
ignore_value(virAsprintf(&nicname, "%.*s%s%s",
|
|
/* space for last 3 chars + "-nic" + NULL */
|
|
(int)(IFNAMSIZ - (3 + sizeof(dummyNicSuffix))),
|
|
brname, brname + strlen(brname) - 3,
|
|
dummyNicSuffix));
|
|
} else {
|
|
ignore_value(virAsprintf(&nicname, "%s%s", brname, dummyNicSuffix));
|
|
}
|
|
return nicname;
|
|
}
|
|
|
|
|
|
static int
|
|
networkUpdateState(virNetworkObjPtr obj,
|
|
void *opaque)
|
|
{
|
|
virNetworkDefPtr def;
|
|
virNetworkDriverStatePtr driver = opaque;
|
|
dnsmasqCapsPtr dnsmasq_caps = networkGetDnsmasqCaps(driver);
|
|
virMacMapPtr macmap;
|
|
char *macMapFile = NULL;
|
|
int ret = -1;
|
|
|
|
virObjectLock(obj);
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
switch ((virNetworkForwardType) def->forward.type) {
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
/* If bridge doesn't exist, then mark it inactive */
|
|
if (!(def->bridge && virNetDevExists(def->bridge) == 1))
|
|
virNetworkObjSetActive(obj, false);
|
|
|
|
if (!(macMapFile = virMacMapFileName(driver->dnsmasqStateDir,
|
|
def->bridge)))
|
|
goto cleanup;
|
|
|
|
if (!(macmap = virMacMapNew(macMapFile)))
|
|
goto cleanup;
|
|
|
|
virNetworkObjSetMacMap(obj, macmap);
|
|
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
if (def->bridge) {
|
|
if (virNetDevExists(def->bridge) != 1)
|
|
virNetworkObjSetActive(obj, false);
|
|
break;
|
|
}
|
|
/* intentionally drop through to common case for all
|
|
* macvtap networks (forward='bridge' with no bridge
|
|
* device defined is macvtap using its 'bridge' mode)
|
|
*/
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
/* so far no extra checks */
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_HOSTDEV:
|
|
/* so far no extra checks */
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, def->forward.type);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Try and read dnsmasq/radvd pids of active networks */
|
|
if (virNetworkObjIsActive(obj) && def->ips && (def->nips > 0)) {
|
|
pid_t radvdPid;
|
|
pid_t dnsmasqPid;
|
|
char *radvdpidbase;
|
|
|
|
ignore_value(virPidFileReadIfAlive(driver->pidDir,
|
|
def->name,
|
|
&dnsmasqPid,
|
|
dnsmasqCapsGetBinaryPath(dnsmasq_caps)));
|
|
virNetworkObjSetDnsmasqPid(obj, dnsmasqPid);
|
|
|
|
radvdpidbase = networkRadvdPidfileBasename(def->name);
|
|
if (!radvdpidbase)
|
|
goto cleanup;
|
|
|
|
ignore_value(virPidFileReadIfAlive(driver->pidDir,
|
|
radvdpidbase,
|
|
&radvdPid, RADVD));
|
|
virNetworkObjSetRadvdPid(obj, radvdPid);
|
|
VIR_FREE(radvdpidbase);
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virObjectUnlock(obj);
|
|
virObjectUnref(dnsmasq_caps);
|
|
VIR_FREE(macMapFile);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkAutostartConfig(virNetworkObjPtr obj,
|
|
void *opaque)
|
|
{
|
|
virNetworkDriverStatePtr driver = opaque;
|
|
int ret = -1;
|
|
|
|
virObjectLock(obj);
|
|
if (virNetworkObjIsAutostart(obj) &&
|
|
!virNetworkObjIsActive(obj) &&
|
|
networkStartNetwork(driver, obj) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virObjectUnlock(obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
#ifdef WITH_FIREWALLD
|
|
static DBusHandlerResult
|
|
firewalld_dbus_filter_bridge(DBusConnection *connection ATTRIBUTE_UNUSED,
|
|
DBusMessage *message,
|
|
void *user_data)
|
|
{
|
|
virNetworkDriverStatePtr driver = user_data;
|
|
bool reload = false;
|
|
|
|
if (dbus_message_is_signal(message,
|
|
"org.fedoraproject.FirewallD1", "Reloaded")) {
|
|
reload = true;
|
|
|
|
} else if (dbus_message_is_signal(message,
|
|
DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
|
|
|
|
VIR_AUTOFREE(char *) name = NULL;
|
|
VIR_AUTOFREE(char *) old_owner = NULL;
|
|
VIR_AUTOFREE(char *) new_owner = NULL;
|
|
|
|
if (virDBusMessageDecode(message, "sss", &name, &old_owner, &new_owner) < 0) {
|
|
VIR_WARN("Failed to decode DBus NameOwnerChanged message");
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
/*
|
|
* if new_owner is empty, firewalld is shutting down. If it is
|
|
* non-empty, then it is starting
|
|
*/
|
|
if (new_owner && *new_owner)
|
|
reload = true;
|
|
}
|
|
|
|
if (reload) {
|
|
VIR_DEBUG("Reload in bridge_driver because of firewalld.");
|
|
networkReloadFirewallRules(driver, false);
|
|
}
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* networkStateInitialize:
|
|
*
|
|
* Initialization function for the QEMU daemon
|
|
*/
|
|
static int
|
|
networkStateInitialize(bool privileged,
|
|
virStateInhibitCallback callback ATTRIBUTE_UNUSED,
|
|
void *opaque ATTRIBUTE_UNUSED)
|
|
{
|
|
int ret = VIR_DRV_STATE_INIT_ERROR;
|
|
char *configdir = NULL;
|
|
char *rundir = NULL;
|
|
#ifdef WITH_FIREWALLD
|
|
DBusConnection *sysbus = NULL;
|
|
#endif
|
|
|
|
if (VIR_ALLOC(network_driver) < 0)
|
|
goto error;
|
|
|
|
network_driver->lockFD = -1;
|
|
if (virMutexInit(&network_driver->lock) < 0) {
|
|
VIR_FREE(network_driver);
|
|
goto error;
|
|
}
|
|
|
|
network_driver->privileged = privileged;
|
|
|
|
if (!(network_driver->xmlopt = networkDnsmasqCreateXMLConf()))
|
|
goto error;
|
|
|
|
/* configuration/state paths are one of
|
|
* ~/.config/libvirt/... (session/unprivileged)
|
|
* /etc/libvirt/... && /var/(run|lib)/libvirt/... (system/privileged).
|
|
*/
|
|
if (privileged) {
|
|
if (VIR_STRDUP(network_driver->networkConfigDir,
|
|
SYSCONFDIR "/libvirt/qemu/networks") < 0 ||
|
|
VIR_STRDUP(network_driver->networkAutostartDir,
|
|
SYSCONFDIR "/libvirt/qemu/networks/autostart") < 0 ||
|
|
VIR_STRDUP(network_driver->stateDir,
|
|
LOCALSTATEDIR "/run/libvirt/network") < 0 ||
|
|
VIR_STRDUP(network_driver->pidDir,
|
|
LOCALSTATEDIR "/run/libvirt/network") < 0 ||
|
|
VIR_STRDUP(network_driver->dnsmasqStateDir,
|
|
LOCALSTATEDIR "/lib/libvirt/dnsmasq") < 0 ||
|
|
VIR_STRDUP(network_driver->radvdStateDir,
|
|
LOCALSTATEDIR "/lib/libvirt/radvd") < 0)
|
|
goto error;
|
|
} else {
|
|
configdir = virGetUserConfigDirectory();
|
|
rundir = virGetUserRuntimeDirectory();
|
|
if (!(configdir && rundir))
|
|
goto error;
|
|
|
|
if ((virAsprintf(&network_driver->networkConfigDir,
|
|
"%s/qemu/networks", configdir) < 0) ||
|
|
(virAsprintf(&network_driver->networkAutostartDir,
|
|
"%s/qemu/networks/autostart", configdir) < 0) ||
|
|
(virAsprintf(&network_driver->stateDir,
|
|
"%s/network/lib", rundir) < 0) ||
|
|
(virAsprintf(&network_driver->pidDir,
|
|
"%s/network/run", rundir) < 0) ||
|
|
(virAsprintf(&network_driver->dnsmasqStateDir,
|
|
"%s/dnsmasq/lib", rundir) < 0) ||
|
|
(virAsprintf(&network_driver->radvdStateDir,
|
|
"%s/radvd/lib", rundir) < 0)) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (virFileMakePath(network_driver->stateDir) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot create directory %s"),
|
|
network_driver->stateDir);
|
|
goto error;
|
|
}
|
|
|
|
if ((network_driver->lockFD =
|
|
virPidFileAcquire(network_driver->stateDir, "driver",
|
|
true, getpid())) < 0)
|
|
goto error;
|
|
|
|
/* if this fails now, it will be retried later with dnsmasqCapsRefresh() */
|
|
network_driver->dnsmasqCaps = dnsmasqCapsNewFromBinary(DNSMASQ);
|
|
|
|
if (!(network_driver->networks = virNetworkObjListNew()))
|
|
goto error;
|
|
|
|
if (virNetworkObjLoadAllState(network_driver->networks,
|
|
network_driver->stateDir,
|
|
network_driver->xmlopt) < 0)
|
|
goto error;
|
|
|
|
if (virNetworkObjLoadAllConfigs(network_driver->networks,
|
|
network_driver->networkConfigDir,
|
|
network_driver->networkAutostartDir,
|
|
network_driver->xmlopt) < 0)
|
|
goto error;
|
|
|
|
/* Update the internal status of all allegedly active
|
|
* networks according to external conditions on the host
|
|
* (i.e. anything that isn't stored directly in each
|
|
* network's state file). */
|
|
virNetworkObjListForEach(network_driver->networks,
|
|
networkUpdateState,
|
|
network_driver);
|
|
virNetworkObjListPrune(network_driver->networks,
|
|
VIR_CONNECT_LIST_NETWORKS_INACTIVE |
|
|
VIR_CONNECT_LIST_NETWORKS_TRANSIENT);
|
|
networkReloadFirewallRules(network_driver, true);
|
|
networkRefreshDaemons(network_driver);
|
|
|
|
virNetworkObjListForEach(network_driver->networks,
|
|
networkAutostartConfig,
|
|
network_driver);
|
|
|
|
network_driver->networkEventState = virObjectEventStateNew();
|
|
|
|
#ifdef WITH_FIREWALLD
|
|
if (!(sysbus = virDBusGetSystemBus())) {
|
|
VIR_WARN("DBus not available, disabling firewalld support "
|
|
"in bridge_network_driver: %s", virGetLastErrorMessage());
|
|
} else {
|
|
/* add matches for
|
|
* NameOwnerChanged on org.freedesktop.DBus for firewalld start/stop
|
|
* Reloaded on org.fedoraproject.FirewallD1 for firewalld reload
|
|
*/
|
|
dbus_bus_add_match(sysbus,
|
|
"type='signal'"
|
|
",interface='"DBUS_INTERFACE_DBUS"'"
|
|
",member='NameOwnerChanged'"
|
|
",arg0='org.fedoraproject.FirewallD1'",
|
|
NULL);
|
|
dbus_bus_add_match(sysbus,
|
|
"type='signal'"
|
|
",interface='org.fedoraproject.FirewallD1'"
|
|
",member='Reloaded'",
|
|
NULL);
|
|
dbus_connection_add_filter(sysbus, firewalld_dbus_filter_bridge,
|
|
network_driver, NULL);
|
|
}
|
|
#endif
|
|
|
|
ret = VIR_DRV_STATE_INIT_COMPLETE;
|
|
cleanup:
|
|
VIR_FREE(configdir);
|
|
VIR_FREE(rundir);
|
|
return ret;
|
|
|
|
error:
|
|
networkStateCleanup();
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
/**
|
|
* networkStateReload:
|
|
*
|
|
* Function to restart the QEMU daemon, it will recheck the configuration
|
|
* files and update its state and the networking
|
|
*/
|
|
static int
|
|
networkStateReload(void)
|
|
{
|
|
if (!network_driver)
|
|
return 0;
|
|
|
|
virNetworkObjLoadAllState(network_driver->networks,
|
|
network_driver->stateDir,
|
|
network_driver->xmlopt);
|
|
virNetworkObjLoadAllConfigs(network_driver->networks,
|
|
network_driver->networkConfigDir,
|
|
network_driver->networkAutostartDir,
|
|
network_driver->xmlopt);
|
|
networkReloadFirewallRules(network_driver, false);
|
|
networkRefreshDaemons(network_driver);
|
|
virNetworkObjListForEach(network_driver->networks,
|
|
networkAutostartConfig,
|
|
network_driver);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* networkStateCleanup:
|
|
*
|
|
* Shutdown the QEMU daemon, it will stop all active domains and networks
|
|
*/
|
|
static int
|
|
networkStateCleanup(void)
|
|
{
|
|
if (!network_driver)
|
|
return -1;
|
|
|
|
virObjectUnref(network_driver->networkEventState);
|
|
virObjectUnref(network_driver->xmlopt);
|
|
|
|
/* free inactive networks */
|
|
virObjectUnref(network_driver->networks);
|
|
|
|
if (network_driver->lockFD != -1)
|
|
virPidFileRelease(network_driver->stateDir, "driver",
|
|
network_driver->lockFD);
|
|
|
|
VIR_FREE(network_driver->networkConfigDir);
|
|
VIR_FREE(network_driver->networkAutostartDir);
|
|
VIR_FREE(network_driver->stateDir);
|
|
VIR_FREE(network_driver->pidDir);
|
|
VIR_FREE(network_driver->dnsmasqStateDir);
|
|
VIR_FREE(network_driver->radvdStateDir);
|
|
|
|
virObjectUnref(network_driver->dnsmasqCaps);
|
|
|
|
virMutexDestroy(&network_driver->lock);
|
|
|
|
VIR_FREE(network_driver);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virDrvOpenStatus
|
|
networkConnectOpen(virConnectPtr conn,
|
|
virConnectAuthPtr auth ATTRIBUTE_UNUSED,
|
|
virConfPtr conf ATTRIBUTE_UNUSED,
|
|
unsigned int flags)
|
|
{
|
|
virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR);
|
|
|
|
if (network_driver == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("network state driver is not active"));
|
|
return VIR_DRV_OPEN_ERROR;
|
|
}
|
|
|
|
if (network_driver->privileged) {
|
|
if (STRNEQ(conn->uri->path, "/system")) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected network URI path '%s', try network:///system"),
|
|
conn->uri->path);
|
|
return VIR_DRV_OPEN_ERROR;
|
|
}
|
|
} else {
|
|
if (STRNEQ(conn->uri->path, "/session")) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected network URI path '%s', try network:///session"),
|
|
conn->uri->path);
|
|
return VIR_DRV_OPEN_ERROR;
|
|
}
|
|
}
|
|
|
|
if (virConnectOpenEnsureACL(conn) < 0)
|
|
return VIR_DRV_OPEN_ERROR;
|
|
|
|
return VIR_DRV_OPEN_SUCCESS;
|
|
}
|
|
|
|
static int networkConnectClose(virConnectPtr conn ATTRIBUTE_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int networkConnectIsSecure(virConnectPtr conn ATTRIBUTE_UNUSED)
|
|
{
|
|
/* Trivially secure, since always inside the daemon */
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int networkConnectIsEncrypted(virConnectPtr conn ATTRIBUTE_UNUSED)
|
|
{
|
|
/* Not encrypted, but remote driver takes care of that */
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int networkConnectIsAlive(virConnectPtr conn ATTRIBUTE_UNUSED)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* networkKillDaemon:
|
|
*
|
|
* kill the specified pid/name, and wait a bit to make sure it's dead.
|
|
*/
|
|
static int
|
|
networkKillDaemon(pid_t pid,
|
|
const char *daemonName,
|
|
const char *networkName)
|
|
{
|
|
size_t i;
|
|
int ret = -1;
|
|
const char *signame = "TERM";
|
|
|
|
/* send SIGTERM, then wait up to 3 seconds for the process to
|
|
* disappear, send SIGKILL, then wait for up to another 2
|
|
* seconds. If that fails, log a warning and continue, hoping
|
|
* for the best.
|
|
*/
|
|
for (i = 0; i < 25; i++) {
|
|
int signum = 0;
|
|
if (i == 0) {
|
|
signum = SIGTERM;
|
|
} else if (i == 15) {
|
|
signum = SIGKILL;
|
|
signame = "KILL";
|
|
}
|
|
if (kill(pid, signum) < 0) {
|
|
if (errno == ESRCH) {
|
|
ret = 0;
|
|
} else {
|
|
char ebuf[1024];
|
|
VIR_WARN("Failed to terminate %s process %d "
|
|
"for network '%s' with SIG%s: %s",
|
|
daemonName, pid, networkName, signame,
|
|
virStrerror(errno, ebuf, sizeof(ebuf)));
|
|
}
|
|
goto cleanup;
|
|
}
|
|
/* NB: since networks have no reference count like
|
|
* domains, there is no safe way to unlock the network
|
|
* object temporarily, and so we can't follow the
|
|
* procedure used by the qemu driver of 1) unlock driver
|
|
* 2) sleep, 3) add ref to object 4) unlock object, 5)
|
|
* re-lock driver, 6) re-lock object. We may need to add
|
|
* that functionality eventually, but for now this
|
|
* function is rarely used and, at worst, leaving the
|
|
* network driver locked during this loop of sleeps will
|
|
* have the effect of holding up any other thread trying
|
|
* to make modifications to a network for up to 5 seconds;
|
|
* since modifications to networks are much less common
|
|
* than modifications to domains, this seems a reasonable
|
|
* tradeoff in exchange for less code disruption.
|
|
*/
|
|
usleep(20 * 1000);
|
|
}
|
|
VIR_WARN("Timed out waiting after SIG%s to %s process %d "
|
|
"(network '%s')",
|
|
signame, daemonName, pid, networkName);
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* the following does not build a file, it builds a list
|
|
* which is later saved into a file
|
|
*/
|
|
static int
|
|
networkBuildDnsmasqDhcpHostsList(dnsmasqContext *dctx,
|
|
virNetworkIPDefPtr ipdef)
|
|
{
|
|
size_t i;
|
|
bool ipv6 = false;
|
|
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6))
|
|
ipv6 = true;
|
|
for (i = 0; i < ipdef->nhosts; i++) {
|
|
virNetworkDHCPHostDefPtr host = &(ipdef->hosts[i]);
|
|
if (VIR_SOCKET_ADDR_VALID(&host->ip))
|
|
if (dnsmasqAddDhcpHost(dctx, host->mac, &host->ip,
|
|
host->name, host->id, ipv6) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
networkBuildDnsmasqHostsList(dnsmasqContext *dctx,
|
|
virNetworkDNSDefPtr dnsdef)
|
|
{
|
|
size_t i, j;
|
|
|
|
if (dnsdef) {
|
|
for (i = 0; i < dnsdef->nhosts; i++) {
|
|
virNetworkDNSHostDefPtr host = &(dnsdef->hosts[i]);
|
|
if (VIR_SOCKET_ADDR_VALID(&host->ip)) {
|
|
for (j = 0; j < host->nnames; j++)
|
|
if (dnsmasqAddHost(dctx, &host->ip, host->names[j]) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
networkDnsmasqConfLocalPTRs(virBufferPtr buf,
|
|
virNetworkDefPtr def)
|
|
{
|
|
virNetworkIPDefPtr ip;
|
|
size_t i;
|
|
char *ptr = NULL;
|
|
int rc;
|
|
|
|
for (i = 0; i < def->nips; i++) {
|
|
ip = def->ips + i;
|
|
|
|
if (ip->localPTR != VIR_TRISTATE_BOOL_YES)
|
|
continue;
|
|
|
|
if ((rc = virSocketAddrPTRDomain(&ip->address,
|
|
virNetworkIPDefPrefix(ip),
|
|
&ptr)) < 0) {
|
|
if (rc == -2) {
|
|
int family = VIR_SOCKET_ADDR_FAMILY(&ip->address);
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("PTR domain for %s network with prefix %u "
|
|
"cannot be automatically created"),
|
|
(family == AF_INET) ? "IPv4" : "IPv6",
|
|
virNetworkIPDefPrefix(ip));
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "local=/%s/\n", ptr);
|
|
VIR_FREE(ptr);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
networkDnsmasqConfContents(virNetworkObjPtr obj,
|
|
const char *pidfile,
|
|
char **configstr,
|
|
dnsmasqContext *dctx,
|
|
dnsmasqCapsPtr caps ATTRIBUTE_UNUSED)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
virBuffer configbuf = VIR_BUFFER_INITIALIZER;
|
|
int r, ret = -1;
|
|
int nbleases = 0;
|
|
size_t i;
|
|
virNetworkDNSDefPtr dns = &def->dns;
|
|
bool wantDNS = dns->enable != VIR_TRISTATE_BOOL_NO;
|
|
virNetworkIPDefPtr tmpipdef, ipdef, ipv4def, ipv6def;
|
|
bool ipv6SLAAC;
|
|
char *saddr = NULL, *eaddr = NULL;
|
|
|
|
*configstr = NULL;
|
|
|
|
/*
|
|
* All dnsmasq parameters are put into a configuration file, except the
|
|
* command line --conf-file=parameter which specifies the location of
|
|
* configuration file.
|
|
*
|
|
* All dnsmasq conf-file parameters must be specified as "foo=bar"
|
|
* as oppose to "--foo bar" which was acceptable on the command line.
|
|
*/
|
|
|
|
/*
|
|
* Needed to ensure dnsmasq uses same algorithm for processing
|
|
* multiple namedriver entries in /etc/resolv.conf as GLibC.
|
|
*/
|
|
|
|
/* create dnsmasq config file appropriate for this network */
|
|
|
|
/* Don't forget to update networkxml2conftest :-) */
|
|
virBufferAsprintf(&configbuf,
|
|
"##WARNING: THIS IS AN AUTO-GENERATED FILE. "
|
|
"CHANGES TO IT ARE LIKELY TO BE\n"
|
|
"##OVERWRITTEN AND LOST. Changes to this "
|
|
"configuration should be made using:\n"
|
|
"## virsh net-edit %s\n"
|
|
"## or other application using the libvirt API.\n"
|
|
"##\n## dnsmasq conf file created by libvirt\n"
|
|
"strict-order\n",
|
|
def->name);
|
|
|
|
/* if dns is disabled, set its listening port to 0, which
|
|
* tells dnsmasq to not listen
|
|
*/
|
|
if (!wantDNS)
|
|
virBufferAddLit(&configbuf, "port=0\n");
|
|
|
|
if (wantDNS && def->dns.forwarders) {
|
|
/* addNoResolv should be set to true if there are any entries
|
|
* that specify an IP address for requests, but no domain
|
|
* qualifier (implying that all requests otherwise "unclaimed"
|
|
* should be sent to that address). if it is still false when
|
|
* we've looked at all entries, it means we still need the
|
|
* host's resolv.conf for some cases.
|
|
*/
|
|
bool addNoResolv = false;
|
|
|
|
for (i = 0; i < def->dns.nfwds; i++) {
|
|
virNetworkDNSForwarderPtr fwd = &def->dns.forwarders[i];
|
|
|
|
virBufferAddLit(&configbuf, "server=");
|
|
if (fwd->domain)
|
|
virBufferAsprintf(&configbuf, "/%s/", fwd->domain);
|
|
if (VIR_SOCKET_ADDR_VALID(&fwd->addr)) {
|
|
char *addr = virSocketAddrFormat(&fwd->addr);
|
|
|
|
if (!addr)
|
|
goto cleanup;
|
|
virBufferAsprintf(&configbuf, "%s\n", addr);
|
|
VIR_FREE(addr);
|
|
if (!fwd->domain)
|
|
addNoResolv = true;
|
|
} else {
|
|
/* "don't forward requests for this domain" */
|
|
virBufferAddLit(&configbuf, "#\n");
|
|
}
|
|
}
|
|
if (addNoResolv)
|
|
virBufferAddLit(&configbuf, "no-resolv\n");
|
|
}
|
|
|
|
if (def->domain) {
|
|
if (def->domainLocalOnly == VIR_TRISTATE_BOOL_YES) {
|
|
virBufferAsprintf(&configbuf,
|
|
"local=/%s/\n",
|
|
def->domain);
|
|
}
|
|
virBufferAsprintf(&configbuf,
|
|
"domain=%s\n"
|
|
"expand-hosts\n",
|
|
def->domain);
|
|
}
|
|
|
|
if (wantDNS &&
|
|
networkDnsmasqConfLocalPTRs(&configbuf, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (wantDNS && def->dns.forwardPlainNames == VIR_TRISTATE_BOOL_NO) {
|
|
virBufferAddLit(&configbuf, "domain-needed\n");
|
|
/* need to specify local=// whether or not a domain is
|
|
* specified, unless the config says we should forward "plain"
|
|
* names (i.e. not fully qualified, no '.' characters)
|
|
*/
|
|
virBufferAddLit(&configbuf, "local=//\n");
|
|
}
|
|
|
|
if (pidfile)
|
|
virBufferAsprintf(&configbuf, "pid-file=%s\n", pidfile);
|
|
|
|
/* dnsmasq will *always* listen on localhost unless told otherwise */
|
|
#ifdef __linux__
|
|
virBufferAddLit(&configbuf, "except-interface=lo\n");
|
|
#else
|
|
/* BSD family OSes and Solaris call loopback interface as lo0 */
|
|
virBufferAddLit(&configbuf, "except-interface=lo0\n");
|
|
#endif
|
|
|
|
if (dnsmasqCapsGet(caps, DNSMASQ_CAPS_BIND_DYNAMIC)) {
|
|
/* using --bind-dynamic with only --interface (no
|
|
* --listen-address) prevents dnsmasq from responding to dns
|
|
* queries that arrive on some interface other than our bridge
|
|
* interface (in other words, requests originating somewhere
|
|
* other than one of the virtual guests connected directly to
|
|
* this network). This was added in response to CVE 2012-3411.
|
|
*/
|
|
virBufferAsprintf(&configbuf,
|
|
"bind-dynamic\n"
|
|
"interface=%s\n",
|
|
def->bridge);
|
|
} else {
|
|
virBufferAddLit(&configbuf, "bind-interfaces\n");
|
|
/*
|
|
* --interface does not actually work with dnsmasq < 2.47,
|
|
* due to DAD for ipv6 addresses on the interface.
|
|
*
|
|
* virCommandAddArgList(cmd, "--interface", def->bridge, NULL);
|
|
*
|
|
* So listen on all defined IPv[46] addresses
|
|
*/
|
|
for (i = 0;
|
|
(tmpipdef = virNetworkDefGetIPByIndex(def, AF_UNSPEC, i));
|
|
i++) {
|
|
char *ipaddr = virSocketAddrFormat(&tmpipdef->address);
|
|
|
|
if (!ipaddr)
|
|
goto cleanup;
|
|
|
|
/* also part of CVE 2012-3411 - if the host's version of
|
|
* dnsmasq doesn't have bind-dynamic, only allow listening on
|
|
* private/local IP addresses (see RFC1918/RFC3484/RFC4193)
|
|
*/
|
|
if (!dnsmasqCapsGet(caps, DNSMASQ_CAPS_BINDTODEVICE) &&
|
|
!virSocketAddrIsPrivate(&tmpipdef->address)) {
|
|
unsigned long version = dnsmasqCapsGetVersion(caps);
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Publicly routable address %s is prohibited. "
|
|
"The version of dnsmasq on this host (%d.%d) "
|
|
"doesn't support the bind-dynamic option or "
|
|
"use SO_BINDTODEVICE on listening sockets, "
|
|
"one of which is required for safe operation "
|
|
"on a publicly routable subnet "
|
|
"(see CVE-2012-3411). You must either "
|
|
"upgrade dnsmasq, or use a private/local "
|
|
"subnet range for this network "
|
|
"(as described in RFC1918/RFC3484/RFC4193)."),
|
|
ipaddr, (int)version / 1000000,
|
|
(int)(version % 1000000) / 1000);
|
|
VIR_FREE(ipaddr);
|
|
goto cleanup;
|
|
}
|
|
virBufferAsprintf(&configbuf, "listen-address=%s\n", ipaddr);
|
|
VIR_FREE(ipaddr);
|
|
}
|
|
}
|
|
|
|
/* If this is an isolated network, set the default route option
|
|
* (3) to be empty to avoid setting a default route that's
|
|
* guaranteed to not work, and set no-resolv so that no dns
|
|
* requests are forwarded on to the dns server listed in the
|
|
* host's /etc/resolv.conf (since this could be used as a channel
|
|
* to build a connection to the outside).
|
|
* IPv6 RA always contains an implicit default route
|
|
* via the sender's link-local address. The only thing we can do
|
|
* is set the lifetime of this route to 0, i.e. disable it.
|
|
*/
|
|
if (def->forward.type == VIR_NETWORK_FORWARD_NONE) {
|
|
virBufferAddLit(&configbuf, "dhcp-option=3\n"
|
|
"no-resolv\n");
|
|
if (dnsmasqCapsGet(caps, DNSMASQ_CAPS_RA_PARAM)) {
|
|
/* interface=* (any), interval=0 (default), lifetime=0 (seconds) */
|
|
virBufferAddLit(&configbuf, "ra-param=*,0,0\n");
|
|
}
|
|
}
|
|
|
|
if (wantDNS) {
|
|
for (i = 0; i < dns->ntxts; i++) {
|
|
virBufferAsprintf(&configbuf, "txt-record=%s,%s\n",
|
|
dns->txts[i].name,
|
|
dns->txts[i].value);
|
|
}
|
|
|
|
for (i = 0; i < dns->nsrvs; i++) {
|
|
/* service/protocol are required, and should have been validated
|
|
* by the parser.
|
|
*/
|
|
if (!dns->srvs[i].service) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Missing required 'service' "
|
|
"attribute in SRV record of network '%s'"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
if (!dns->srvs[i].protocol) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Missing required 'service' "
|
|
"attribute in SRV record of network '%s'"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
/* RFC2782 requires that service and protocol be preceded by
|
|
* an underscore.
|
|
*/
|
|
virBufferAsprintf(&configbuf, "srv-host=_%s._%s",
|
|
dns->srvs[i].service, dns->srvs[i].protocol);
|
|
|
|
/* domain is optional - it defaults to the domain of this network */
|
|
if (dns->srvs[i].domain)
|
|
virBufferAsprintf(&configbuf, ".%s", dns->srvs[i].domain);
|
|
|
|
/* If target is empty or ".", that means "the service is
|
|
* decidedly not available at this domain" (RFC2782). In that
|
|
* case, any port, priority, or weight is irrelevant.
|
|
*/
|
|
if (dns->srvs[i].target && STRNEQ(dns->srvs[i].target, ".")) {
|
|
|
|
virBufferAsprintf(&configbuf, ",%s", dns->srvs[i].target);
|
|
/* port, priority, and weight are optional, but are
|
|
* identified by their position in the line. If an item is
|
|
* unspecified, but something later in the line *is*
|
|
* specified, we need to give the default value for the
|
|
* unspecified item. (According to the dnsmasq manpage,
|
|
* the default for port is 1).
|
|
*/
|
|
if (dns->srvs[i].port ||
|
|
dns->srvs[i].priority || dns->srvs[i].weight)
|
|
virBufferAsprintf(&configbuf, ",%d",
|
|
dns->srvs[i].port ? dns->srvs[i].port : 1);
|
|
if (dns->srvs[i].priority || dns->srvs[i].weight)
|
|
virBufferAsprintf(&configbuf, ",%d", dns->srvs[i].priority);
|
|
if (dns->srvs[i].weight)
|
|
virBufferAsprintf(&configbuf, ",%d", dns->srvs[i].weight);
|
|
}
|
|
virBufferAddLit(&configbuf, "\n");
|
|
}
|
|
}
|
|
|
|
/* Find the first dhcp for both IPv4 and IPv6 */
|
|
for (i = 0, ipv4def = NULL, ipv6def = NULL, ipv6SLAAC = false;
|
|
(ipdef = virNetworkDefGetIPByIndex(def, AF_UNSPEC, i));
|
|
i++) {
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) {
|
|
if (ipdef->nranges || ipdef->nhosts) {
|
|
if (ipv4def) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("For IPv4, multiple DHCP definitions "
|
|
"cannot be specified."));
|
|
goto cleanup;
|
|
} else {
|
|
ipv4def = ipdef;
|
|
}
|
|
}
|
|
}
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) {
|
|
if (ipdef->nranges || ipdef->nhosts) {
|
|
if (!DNSMASQ_DHCPv6_SUPPORT(caps)) {
|
|
unsigned long version = dnsmasqCapsGetVersion(caps);
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("The version of dnsmasq on this host "
|
|
"(%d.%d) doesn't adequately support "
|
|
"IPv6 dhcp range or dhcp host "
|
|
"specification. Version %d.%d or later "
|
|
"is required."),
|
|
(int)version / 1000000,
|
|
(int)(version % 1000000) / 1000,
|
|
DNSMASQ_DHCPv6_MAJOR_REQD,
|
|
DNSMASQ_DHCPv6_MINOR_REQD);
|
|
goto cleanup;
|
|
}
|
|
if (ipv6def) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("For IPv6, multiple DHCP definitions "
|
|
"cannot be specified."));
|
|
goto cleanup;
|
|
} else {
|
|
ipv6def = ipdef;
|
|
}
|
|
} else {
|
|
ipv6SLAAC = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ipv6def && ipv6SLAAC) {
|
|
VIR_WARN("For IPv6, when DHCP is specified for one address, then "
|
|
"state-full Router Advertising will occur. The additional "
|
|
"IPv6 addresses specified require manually configured guest "
|
|
"network to work properly since both state-full (DHCP) "
|
|
"and state-less (SLAAC) addressing are not supported "
|
|
"on the same network interface.");
|
|
}
|
|
|
|
ipdef = ipv4def ? ipv4def : ipv6def;
|
|
|
|
while (ipdef) {
|
|
int prefix;
|
|
|
|
prefix = virNetworkIPDefPrefix(ipdef);
|
|
if (prefix < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("bridge '%s' has an invalid prefix"),
|
|
def->bridge);
|
|
goto cleanup;
|
|
}
|
|
for (r = 0; r < ipdef->nranges; r++) {
|
|
int thisRange;
|
|
|
|
if (!(saddr = virSocketAddrFormat(&ipdef->ranges[r].start)) ||
|
|
!(eaddr = virSocketAddrFormat(&ipdef->ranges[r].end)))
|
|
goto cleanup;
|
|
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) {
|
|
virBufferAsprintf(&configbuf, "dhcp-range=%s,%s,%d\n",
|
|
saddr, eaddr, prefix);
|
|
} else {
|
|
/* IPv4 - dnsmasq requires a netmask rather than prefix */
|
|
virSocketAddr netmask;
|
|
VIR_AUTOFREE(char *) netmaskStr = NULL;
|
|
|
|
if (virSocketAddrPrefixToNetmask(prefix, &netmask, AF_INET) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to translate bridge '%s' "
|
|
"prefix %d to netmask"),
|
|
def->bridge, prefix);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(netmaskStr = virSocketAddrFormat(&netmask)))
|
|
goto cleanup;
|
|
virBufferAsprintf(&configbuf, "dhcp-range=%s,%s,%s\n",
|
|
saddr, eaddr, netmaskStr);
|
|
}
|
|
|
|
VIR_FREE(saddr);
|
|
VIR_FREE(eaddr);
|
|
thisRange = virSocketAddrGetRange(&ipdef->ranges[r].start,
|
|
&ipdef->ranges[r].end,
|
|
&ipdef->address,
|
|
virNetworkIPDefPrefix(ipdef));
|
|
if (thisRange < 0)
|
|
goto cleanup;
|
|
nbleases += thisRange;
|
|
}
|
|
|
|
/*
|
|
* For static-only DHCP, i.e. with no range but at least one
|
|
* host element, we have to add a special --dhcp-range option
|
|
* to enable the service in dnsmasq. (this is for dhcp-hosts=
|
|
* support)
|
|
*/
|
|
if (!ipdef->nranges && ipdef->nhosts) {
|
|
char *bridgeaddr = virSocketAddrFormat(&ipdef->address);
|
|
if (!bridgeaddr)
|
|
goto cleanup;
|
|
virBufferAsprintf(&configbuf, "dhcp-range=%s,static",
|
|
bridgeaddr);
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6))
|
|
virBufferAsprintf(&configbuf, ",%d", prefix);
|
|
virBufferAddLit(&configbuf, "\n");
|
|
VIR_FREE(bridgeaddr);
|
|
}
|
|
|
|
if (networkBuildDnsmasqDhcpHostsList(dctx, ipdef) < 0)
|
|
goto cleanup;
|
|
|
|
/* Note: the following is IPv4 only */
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) {
|
|
if (ipdef->nranges || ipdef->nhosts) {
|
|
virBufferAddLit(&configbuf, "dhcp-no-override\n");
|
|
virBufferAddLit(&configbuf, "dhcp-authoritative\n");
|
|
}
|
|
|
|
if (ipdef->tftproot) {
|
|
virBufferAddLit(&configbuf, "enable-tftp\n");
|
|
virBufferAsprintf(&configbuf, "tftp-root=%s\n", ipdef->tftproot);
|
|
}
|
|
|
|
if (ipdef->bootfile) {
|
|
if (VIR_SOCKET_ADDR_VALID(&ipdef->bootserver)) {
|
|
char *bootserver = virSocketAddrFormat(&ipdef->bootserver);
|
|
|
|
if (!bootserver)
|
|
goto cleanup;
|
|
virBufferAsprintf(&configbuf, "dhcp-boot=%s%s%s\n",
|
|
ipdef->bootfile, ",,", bootserver);
|
|
VIR_FREE(bootserver);
|
|
} else {
|
|
virBufferAsprintf(&configbuf, "dhcp-boot=%s\n", ipdef->bootfile);
|
|
}
|
|
}
|
|
}
|
|
ipdef = (ipdef == ipv6def) ? NULL : ipv6def;
|
|
}
|
|
|
|
if (nbleases > 0)
|
|
virBufferAsprintf(&configbuf, "dhcp-lease-max=%d\n", nbleases);
|
|
|
|
/* this is done once per interface */
|
|
if (networkBuildDnsmasqHostsList(dctx, dns) < 0)
|
|
goto cleanup;
|
|
|
|
/* Even if there are currently no static hosts, if we're
|
|
* listening for DHCP, we should write a 0-length hosts
|
|
* file to allow for runtime additions.
|
|
*/
|
|
if (ipv4def || ipv6def)
|
|
virBufferAsprintf(&configbuf, "dhcp-hostsfile=%s\n",
|
|
dctx->hostsfile->path);
|
|
|
|
/* Likewise, always create this file and put it on the
|
|
* commandline, to allow for runtime additions.
|
|
*/
|
|
if (wantDNS) {
|
|
virBufferAsprintf(&configbuf, "addn-hosts=%s\n",
|
|
dctx->addnhostsfile->path);
|
|
}
|
|
|
|
/* Configure DHCP to tell clients about the MTU. */
|
|
if (def->mtu > 0)
|
|
virBufferAsprintf(&configbuf, "dhcp-option=option:mtu,%d\n", def->mtu);
|
|
|
|
/* Are we doing RA instead of radvd? */
|
|
if (DNSMASQ_RA_SUPPORT(caps)) {
|
|
if (ipv6def) {
|
|
virBufferAddLit(&configbuf, "enable-ra\n");
|
|
} else {
|
|
for (i = 0;
|
|
(ipdef = virNetworkDefGetIPByIndex(def, AF_INET6, i));
|
|
i++) {
|
|
if (!(ipdef->nranges || ipdef->nhosts)) {
|
|
char *bridgeaddr = virSocketAddrFormat(&ipdef->address);
|
|
if (!bridgeaddr)
|
|
goto cleanup;
|
|
virBufferAsprintf(&configbuf,
|
|
"dhcp-range=%s,ra-only\n", bridgeaddr);
|
|
VIR_FREE(bridgeaddr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (def->namespaceData) {
|
|
networkDnsmasqXmlNsDefPtr dnsmasqxmlns = def->namespaceData;
|
|
for (i = 0; i < dnsmasqxmlns->noptions; i++)
|
|
virBufferAsprintf(&configbuf, "%s\n", dnsmasqxmlns->options[i]);
|
|
}
|
|
|
|
if (!(*configstr = virBufferContentAndReset(&configbuf)))
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
VIR_FREE(saddr);
|
|
VIR_FREE(eaddr);
|
|
virBufferFreeAndReset(&configbuf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* build the dnsmasq command line */
|
|
static int ATTRIBUTE_NONNULL(3)
|
|
networkBuildDhcpDaemonCommandLine(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj,
|
|
virCommandPtr *cmdout,
|
|
char *pidfile,
|
|
dnsmasqContext *dctx)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
dnsmasqCapsPtr dnsmasq_caps = networkGetDnsmasqCaps(driver);
|
|
virCommandPtr cmd = NULL;
|
|
int ret = -1;
|
|
char *configfile = NULL;
|
|
char *configstr = NULL;
|
|
char *leaseshelper_path = NULL;
|
|
|
|
virNetworkObjSetDnsmasqPid(obj, -1);
|
|
|
|
if (networkDnsmasqConfContents(obj, pidfile, &configstr,
|
|
dctx, dnsmasq_caps) < 0)
|
|
goto cleanup;
|
|
if (!configstr)
|
|
goto cleanup;
|
|
|
|
/* construct the filename */
|
|
if (!(configfile = networkDnsmasqConfigFileName(driver, def->name)))
|
|
goto cleanup;
|
|
|
|
/* Write the file */
|
|
if (virFileWriteStr(configfile, configstr, 0600) < 0) {
|
|
virReportSystemError(errno,
|
|
_("couldn't write dnsmasq config file '%s'"),
|
|
configfile);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* This helper is used to create custom leases file for libvirt */
|
|
if (!(leaseshelper_path = virFileFindResource("libvirt_leaseshelper",
|
|
abs_top_builddir "/src",
|
|
LIBEXECDIR)))
|
|
goto cleanup;
|
|
|
|
cmd = virCommandNew(dnsmasqCapsGetBinaryPath(dnsmasq_caps));
|
|
virCommandAddArgFormat(cmd, "--conf-file=%s", configfile);
|
|
/* Libvirt gains full control of leases database */
|
|
virCommandAddArgFormat(cmd, "--leasefile-ro");
|
|
virCommandAddArgFormat(cmd, "--dhcp-script=%s", leaseshelper_path);
|
|
virCommandAddEnvPair(cmd, "VIR_BRIDGE_NAME", def->bridge);
|
|
|
|
*cmdout = cmd;
|
|
ret = 0;
|
|
cleanup:
|
|
virObjectUnref(dnsmasq_caps);
|
|
VIR_FREE(configfile);
|
|
VIR_FREE(configstr);
|
|
VIR_FREE(leaseshelper_path);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkStartDhcpDaemon(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
virNetworkIPDefPtr ipdef;
|
|
size_t i;
|
|
bool needDnsmasq = false;
|
|
virCommandPtr cmd = NULL;
|
|
char *pidfile = NULL;
|
|
pid_t dnsmasqPid;
|
|
int ret = -1;
|
|
dnsmasqContext *dctx = NULL;
|
|
|
|
/* see if there are any IP addresses that need a dhcp server */
|
|
i = 0;
|
|
while ((ipdef = virNetworkDefGetIPByIndex(def, AF_UNSPEC, i))) {
|
|
i++;
|
|
if (ipdef->nranges || ipdef->nhosts)
|
|
needDnsmasq = true;
|
|
}
|
|
|
|
if (i == 0) {
|
|
/* no IP addresses at all, so we don't need to run */
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!needDnsmasq && def->dns.enable == VIR_TRISTATE_BOOL_NO) {
|
|
/* no DHCP services needed, and user disabled DNS service */
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virFileMakePath(driver->pidDir) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot create directory %s"),
|
|
driver->pidDir);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(pidfile = virPidFileBuildPath(driver->pidDir, def->name)))
|
|
goto cleanup;
|
|
|
|
if (virFileMakePath(driver->dnsmasqStateDir) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot create directory %s"),
|
|
driver->dnsmasqStateDir);
|
|
goto cleanup;
|
|
}
|
|
|
|
dctx = dnsmasqContextNew(def->name, driver->dnsmasqStateDir);
|
|
if (dctx == NULL)
|
|
goto cleanup;
|
|
|
|
if (networkDnsmasqCapsRefresh(driver) < 0)
|
|
goto cleanup;
|
|
|
|
ret = networkBuildDhcpDaemonCommandLine(driver, obj, &cmd, pidfile, dctx);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
|
|
ret = dnsmasqSave(dctx);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
|
|
ret = virCommandRun(cmd, NULL);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
|
|
/*
|
|
* There really is no race here - when dnsmasq daemonizes, its
|
|
* leader process stays around until its child has actually
|
|
* written its pidfile. So by time virCommandRun exits it has
|
|
* waitpid'd and guaranteed the proess has started and written a
|
|
* pid
|
|
*/
|
|
|
|
ret = virPidFileRead(driver->pidDir, def->name, &dnsmasqPid);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
virNetworkObjSetDnsmasqPid(obj, dnsmasqPid);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
VIR_FREE(pidfile);
|
|
virCommandFree(cmd);
|
|
dnsmasqContextFree(dctx);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* networkRefreshDhcpDaemon:
|
|
* Update dnsmasq config files, then send a SIGHUP so that it rereads
|
|
* them. This only works for the dhcp-hostsfile and the
|
|
* addn-hosts file.
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
networkRefreshDhcpDaemon(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
int ret = -1;
|
|
size_t i;
|
|
pid_t dnsmasqPid;
|
|
virNetworkIPDefPtr ipdef, ipv4def, ipv6def;
|
|
dnsmasqContext *dctx = NULL;
|
|
|
|
/* if no IP addresses specified, nothing to do */
|
|
if (!virNetworkDefGetIPByIndex(def, AF_UNSPEC, 0))
|
|
return 0;
|
|
|
|
/* if there's no running dnsmasq, just start it */
|
|
dnsmasqPid = virNetworkObjGetDnsmasqPid(obj);
|
|
if (dnsmasqPid <= 0 || (kill(dnsmasqPid, 0) < 0))
|
|
return networkStartDhcpDaemon(driver, obj);
|
|
|
|
VIR_INFO("Refreshing dnsmasq for network %s", def->bridge);
|
|
if (!(dctx = dnsmasqContextNew(def->name,
|
|
driver->dnsmasqStateDir))) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Look for first IPv4 address that has dhcp defined.
|
|
* We only support dhcp-host config on one IPv4 subnetwork
|
|
* and on one IPv6 subnetwork.
|
|
*/
|
|
ipv4def = NULL;
|
|
for (i = 0;
|
|
(ipdef = virNetworkDefGetIPByIndex(def, AF_INET, i));
|
|
i++) {
|
|
if (!ipv4def && (ipdef->nranges || ipdef->nhosts))
|
|
ipv4def = ipdef;
|
|
}
|
|
|
|
ipv6def = NULL;
|
|
for (i = 0;
|
|
(ipdef = virNetworkDefGetIPByIndex(def, AF_INET6, i));
|
|
i++) {
|
|
if (!ipv6def && (ipdef->nranges || ipdef->nhosts))
|
|
ipv6def = ipdef;
|
|
}
|
|
|
|
if (ipv4def && (networkBuildDnsmasqDhcpHostsList(dctx, ipv4def) < 0))
|
|
goto cleanup;
|
|
|
|
if (ipv6def && (networkBuildDnsmasqDhcpHostsList(dctx, ipv6def) < 0))
|
|
goto cleanup;
|
|
|
|
if (networkBuildDnsmasqHostsList(dctx, &def->dns) < 0)
|
|
goto cleanup;
|
|
|
|
if ((ret = dnsmasqSave(dctx)) < 0)
|
|
goto cleanup;
|
|
|
|
dnsmasqPid = virNetworkObjGetDnsmasqPid(obj);
|
|
ret = kill(dnsmasqPid, SIGHUP);
|
|
cleanup:
|
|
dnsmasqContextFree(dctx);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* networkRestartDhcpDaemon:
|
|
*
|
|
* kill and restart dnsmasq, in order to update any config that is on
|
|
* the dnsmasq commandline (and any placed in separate config files).
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
networkRestartDhcpDaemon(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
pid_t dnsmasqPid = virNetworkObjGetDnsmasqPid(obj);
|
|
|
|
/* if there is a running dnsmasq, kill it */
|
|
if (dnsmasqPid > 0) {
|
|
networkKillDaemon(dnsmasqPid, "dnsmasq", def->name);
|
|
virNetworkObjSetDnsmasqPid(obj, -1);
|
|
}
|
|
/* now start dnsmasq if it should be started */
|
|
return networkStartDhcpDaemon(driver, obj);
|
|
}
|
|
|
|
|
|
static char radvd1[] = " AdvOtherConfigFlag off;\n\n";
|
|
static char radvd2[] = " AdvAutonomous off;\n";
|
|
static char radvd3[] = " AdvOnLink on;\n"
|
|
" AdvAutonomous on;\n"
|
|
" AdvRouterAddr off;\n";
|
|
|
|
static int
|
|
networkRadvdConfContents(virNetworkObjPtr obj,
|
|
char **configstr)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
virBuffer configbuf = VIR_BUFFER_INITIALIZER;
|
|
int ret = -1;
|
|
size_t i;
|
|
virNetworkIPDefPtr ipdef;
|
|
bool v6present = false, dhcp6 = false;
|
|
|
|
*configstr = NULL;
|
|
|
|
/* Check if DHCPv6 is needed */
|
|
for (i = 0; (ipdef = virNetworkDefGetIPByIndex(def, AF_INET6, i)); i++) {
|
|
v6present = true;
|
|
if (ipdef->nranges || ipdef->nhosts) {
|
|
dhcp6 = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If there are no IPv6 addresses, then we are done */
|
|
if (!v6present) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* create radvd config file appropriate for this network;
|
|
* IgnoreIfMissing allows radvd to start even when the bridge is down
|
|
*/
|
|
virBufferAsprintf(&configbuf, "interface %s\n"
|
|
"{\n"
|
|
" AdvSendAdvert on;\n"
|
|
" IgnoreIfMissing on;\n"
|
|
" AdvManagedFlag %s;\n"
|
|
"%s",
|
|
def->bridge,
|
|
dhcp6 ? "on" : "off",
|
|
dhcp6 ? "\n" : radvd1);
|
|
|
|
/* add a section for each IPv6 address in the config */
|
|
for (i = 0; (ipdef = virNetworkDefGetIPByIndex(def, AF_INET6, i)); i++) {
|
|
int prefix;
|
|
char *netaddr;
|
|
|
|
prefix = virNetworkIPDefPrefix(ipdef);
|
|
if (prefix < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("bridge '%s' has an invalid prefix"),
|
|
def->bridge);
|
|
goto cleanup;
|
|
}
|
|
if (!(netaddr = virSocketAddrFormat(&ipdef->address)))
|
|
goto cleanup;
|
|
virBufferAsprintf(&configbuf,
|
|
" prefix %s/%d\n"
|
|
" {\n%s };\n",
|
|
netaddr, prefix,
|
|
dhcp6 ? radvd2 : radvd3);
|
|
VIR_FREE(netaddr);
|
|
}
|
|
|
|
virBufferAddLit(&configbuf, "};\n");
|
|
|
|
if (virBufferCheckError(&configbuf) < 0)
|
|
goto cleanup;
|
|
|
|
*configstr = virBufferContentAndReset(&configbuf);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virBufferFreeAndReset(&configbuf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* write file and return its name (which must be freed by caller) */
|
|
static int
|
|
networkRadvdConfWrite(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj,
|
|
char **configFile)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
int ret = -1;
|
|
char *configStr = NULL;
|
|
char *myConfigFile = NULL;
|
|
|
|
if (!configFile)
|
|
configFile = &myConfigFile;
|
|
|
|
*configFile = NULL;
|
|
|
|
if (networkRadvdConfContents(obj, &configStr) < 0)
|
|
goto cleanup;
|
|
|
|
if (!configStr) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* construct the filename */
|
|
if (!(*configFile = networkRadvdConfigFileName(driver, def->name)))
|
|
goto cleanup;
|
|
/* write the file */
|
|
if (virFileWriteStr(*configFile, configStr, 0600) < 0) {
|
|
virReportSystemError(errno,
|
|
_("couldn't write radvd config file '%s'"),
|
|
*configFile);
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
VIR_FREE(configStr);
|
|
VIR_FREE(myConfigFile);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkStartRadvd(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
dnsmasqCapsPtr dnsmasq_caps = networkGetDnsmasqCaps(driver);
|
|
pid_t radvdPid;
|
|
char *pidfile = NULL;
|
|
char *radvdpidbase = NULL;
|
|
char *configfile = NULL;
|
|
virCommandPtr cmd = NULL;
|
|
int ret = -1;
|
|
|
|
virNetworkObjSetRadvdPid(obj, -1);
|
|
|
|
/* Is dnsmasq handling RA? */
|
|
if (DNSMASQ_RA_SUPPORT(dnsmasq_caps)) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!virNetworkDefGetIPByIndex(def, AF_INET6, 0)) {
|
|
/* no IPv6 addresses, so we don't need to run radvd */
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!virFileIsExecutable(RADVD)) {
|
|
virReportSystemError(errno,
|
|
_("Cannot find %s - "
|
|
"Possibly the package isn't installed"),
|
|
RADVD);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virFileMakePath(driver->pidDir) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot create directory %s"),
|
|
driver->pidDir);
|
|
goto cleanup;
|
|
}
|
|
if (virFileMakePath(driver->radvdStateDir) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot create directory %s"),
|
|
driver->radvdStateDir);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* construct pidfile name */
|
|
if (!(radvdpidbase = networkRadvdPidfileBasename(def->name)))
|
|
goto cleanup;
|
|
if (!(pidfile = virPidFileBuildPath(driver->pidDir, radvdpidbase)))
|
|
goto cleanup;
|
|
|
|
if (networkRadvdConfWrite(driver, obj, &configfile) < 0)
|
|
goto cleanup;
|
|
|
|
/* prevent radvd from daemonizing itself with "--debug 1", and use
|
|
* a dummy pidfile name - virCommand will create the pidfile we
|
|
* want to use (this is necessary because radvd's internal
|
|
* daemonization and pidfile creation causes a race, and the
|
|
* virPidFileRead() below will fail if we use them).
|
|
* Unfortunately, it isn't possible to tell radvd to not create
|
|
* its own pidfile, so we just let it do so, with a slightly
|
|
* different name. Unused, but harmless.
|
|
*/
|
|
cmd = virCommandNewArgList(RADVD, "--debug", "1",
|
|
"--config", configfile,
|
|
"--pidfile", NULL);
|
|
virCommandAddArgFormat(cmd, "%s-bin", pidfile);
|
|
|
|
virCommandSetPidFile(cmd, pidfile);
|
|
virCommandDaemonize(cmd);
|
|
|
|
if (virCommandRun(cmd, NULL) < 0)
|
|
goto cleanup;
|
|
|
|
if (virPidFileRead(driver->pidDir, radvdpidbase, &radvdPid) < 0)
|
|
goto cleanup;
|
|
virNetworkObjSetRadvdPid(obj, radvdPid);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virObjectUnref(dnsmasq_caps);
|
|
virCommandFree(cmd);
|
|
VIR_FREE(configfile);
|
|
VIR_FREE(radvdpidbase);
|
|
VIR_FREE(pidfile);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkRefreshRadvd(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
dnsmasqCapsPtr dnsmasq_caps = networkGetDnsmasqCaps(driver);
|
|
char *radvdpidbase;
|
|
pid_t radvdPid;
|
|
|
|
/* Is dnsmasq handling RA? */
|
|
if (DNSMASQ_RA_SUPPORT(dnsmasq_caps)) {
|
|
virObjectUnref(dnsmasq_caps);
|
|
radvdPid = virNetworkObjGetRadvdPid(obj);
|
|
if (radvdPid <= 0)
|
|
return 0;
|
|
/* radvd should not be running but in case it is */
|
|
if ((networkKillDaemon(radvdPid, "radvd", def->name) >= 0) &&
|
|
((radvdpidbase = networkRadvdPidfileBasename(def->name))
|
|
!= NULL)) {
|
|
virPidFileDelete(driver->pidDir, radvdpidbase);
|
|
VIR_FREE(radvdpidbase);
|
|
}
|
|
virNetworkObjSetRadvdPid(obj, -1);
|
|
return 0;
|
|
}
|
|
virObjectUnref(dnsmasq_caps);
|
|
|
|
/* if there's no running radvd, just start it */
|
|
radvdPid = virNetworkObjGetRadvdPid(obj);
|
|
if (radvdPid <= 0 || (kill(radvdPid, 0) < 0))
|
|
return networkStartRadvd(driver, obj);
|
|
|
|
if (!virNetworkDefGetIPByIndex(def, AF_INET6, 0)) {
|
|
/* no IPv6 addresses, so we don't need to run radvd */
|
|
return 0;
|
|
}
|
|
|
|
if (networkRadvdConfWrite(driver, obj, NULL) < 0)
|
|
return -1;
|
|
|
|
return kill(radvdPid, SIGHUP);
|
|
}
|
|
|
|
|
|
#if 0
|
|
/* currently unused, so it causes a build error unless we #if it out */
|
|
static int
|
|
networkRestartRadvd(virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
char *radvdpidbase;
|
|
pid_t radvdPid = virNeworkObjGetRadvdPid(obj);
|
|
|
|
/* if there is a running radvd, kill it */
|
|
if (radvdPid > 0) {
|
|
/* essentially ignore errors from the following two functions,
|
|
* since there's really no better recovery to be done than to
|
|
* just push ahead (and that may be exactly what's needed).
|
|
*/
|
|
if ((networkKillDaemon(radvdPid, "radvd", def->name) >= 0) &&
|
|
((radvdpidbase = networkRadvdPidfileBasename(def->name))
|
|
!= NULL)) {
|
|
virPidFileDelete(driver->pidDir, radvdpidbase);
|
|
VIR_FREE(radvdpidbase);
|
|
}
|
|
virNetworkObjSetRadvdPid(obj, -1);
|
|
}
|
|
/* now start radvd if it should be started */
|
|
return networkStartRadvd(obj);
|
|
}
|
|
#endif /* #if 0 */
|
|
|
|
|
|
static int
|
|
networkRefreshDaemonsHelper(virNetworkObjPtr obj,
|
|
void *opaque)
|
|
{
|
|
virNetworkDefPtr def;
|
|
virNetworkDriverStatePtr driver = opaque;
|
|
|
|
virObjectLock(obj);
|
|
def = virNetworkObjGetDef(obj);
|
|
if (virNetworkObjIsActive(obj)) {
|
|
switch ((virNetworkForwardType) def->forward.type) {
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
/* Only the three L3 network types that are configured by
|
|
* libvirt will have a dnsmasq or radvd daemon associated
|
|
* with them. Here we send a SIGHUP to an existing
|
|
* dnsmasq and/or radvd, or restart them if they've
|
|
* disappeared.
|
|
*/
|
|
networkRefreshDhcpDaemon(driver, obj);
|
|
networkRefreshRadvd(driver, obj);
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
case VIR_NETWORK_FORWARD_HOSTDEV:
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, def->forward.type);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
virObjectUnlock(obj);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* SIGHUP/restart any dnsmasq or radvd daemons.
|
|
* This should be called when libvirtd is restarted.
|
|
*/
|
|
static void
|
|
networkRefreshDaemons(virNetworkDriverStatePtr driver)
|
|
{
|
|
VIR_INFO("Refreshing network daemons");
|
|
virNetworkObjListForEach(driver->networks,
|
|
networkRefreshDaemonsHelper,
|
|
driver);
|
|
}
|
|
|
|
|
|
static int
|
|
networkReloadFirewallRulesHelper(virNetworkObjPtr obj,
|
|
void *opaque ATTRIBUTE_UNUSED)
|
|
{
|
|
virNetworkDefPtr def;
|
|
|
|
virObjectLock(obj);
|
|
def = virNetworkObjGetDef(obj);
|
|
if (virNetworkObjIsActive(obj)) {
|
|
switch ((virNetworkForwardType) def->forward.type) {
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
/* Only three of the L3 network types that are configured by
|
|
* libvirt need to have iptables rules reloaded. The 4th L3
|
|
* network type, forward='open', doesn't need this because it
|
|
* has no iptables rules.
|
|
*/
|
|
networkRemoveFirewallRules(def);
|
|
ignore_value(networkAddFirewallRules(def));
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
case VIR_NETWORK_FORWARD_HOSTDEV:
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, def->forward.type);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
virObjectUnlock(obj);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
networkReloadFirewallRules(virNetworkDriverStatePtr driver, bool startup)
|
|
{
|
|
VIR_INFO("Reloading iptables rules");
|
|
/* Ideally we'd not even register the driver when unprivilegd
|
|
* but until we untangle the virt driver that's not viable */
|
|
if (!driver->privileged)
|
|
return;
|
|
networkPreReloadFirewallRules(driver, startup);
|
|
virNetworkObjListForEach(driver->networks,
|
|
networkReloadFirewallRulesHelper,
|
|
NULL);
|
|
networkPostReloadFirewallRules(startup);
|
|
}
|
|
|
|
|
|
/* Enable IP Forwarding. Return 0 for success, -1 for failure. */
|
|
static int
|
|
networkEnableIPForwarding(bool enableIPv4,
|
|
bool enableIPv6)
|
|
{
|
|
int ret = 0;
|
|
#ifdef HAVE_SYSCTLBYNAME
|
|
int enabled = 1;
|
|
if (enableIPv4)
|
|
ret = sysctlbyname("net.inet.ip.forwarding", NULL, 0,
|
|
&enabled, sizeof(enabled));
|
|
if (enableIPv6 && ret == 0)
|
|
ret = sysctlbyname("net.inet6.ip6.forwarding", NULL, 0,
|
|
&enabled, sizeof(enabled));
|
|
#else
|
|
if (enableIPv4)
|
|
ret = virFileWriteStr(SYSCTL_PATH "/net/ipv4/ip_forward", "1\n", 0);
|
|
if (enableIPv6 && ret == 0)
|
|
ret = virFileWriteStr(SYSCTL_PATH "/net/ipv6/conf/all/forwarding", "1\n", 0);
|
|
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkSetIPv6Sysctls(virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
char *field = NULL;
|
|
int ret = -1;
|
|
bool enableIPv6 = !!virNetworkDefGetIPByIndex(def, AF_INET6, 0);
|
|
|
|
/* set disable_ipv6 if there are no ipv6 addresses defined for the
|
|
* network. But also unset it if there *are* ipv6 addresses, as we
|
|
* can't be sure of its default value.
|
|
*/
|
|
if (virAsprintf(&field, SYSCTL_PATH "/net/ipv6/conf/%s/disable_ipv6",
|
|
def->bridge) < 0)
|
|
goto cleanup;
|
|
|
|
if (access(field, W_OK) < 0 && errno == ENOENT) {
|
|
if (!enableIPv6)
|
|
VIR_DEBUG("ipv6 appears to already be disabled on %s",
|
|
def->bridge);
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virFileWriteStr(field, enableIPv6 ? "0" : "1", 0) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot write to %s to enable/disable IPv6 "
|
|
"on bridge %s"), field, def->bridge);
|
|
goto cleanup;
|
|
}
|
|
VIR_FREE(field);
|
|
|
|
/* The rest of the ipv6 sysctl tunables should always be set the
|
|
* same, whether or not we're using ipv6 on this bridge.
|
|
*/
|
|
|
|
/* Prevent guests from hijacking the host network by sending out
|
|
* their own router advertisements.
|
|
*/
|
|
if (virAsprintf(&field, SYSCTL_PATH "/net/ipv6/conf/%s/accept_ra",
|
|
def->bridge) < 0)
|
|
goto cleanup;
|
|
|
|
if (virFileWriteStr(field, "0", 0) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot disable %s"), field);
|
|
goto cleanup;
|
|
}
|
|
VIR_FREE(field);
|
|
|
|
/* All interfaces used as a gateway (which is what this is, by
|
|
* definition), must always have autoconf=0.
|
|
*/
|
|
if (virAsprintf(&field, SYSCTL_PATH "/net/ipv6/conf/%s/autoconf",
|
|
def->bridge) < 0)
|
|
goto cleanup;
|
|
|
|
if (virFileWriteStr(field, "0", 0) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot disable %s"), field);
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
VIR_FREE(field);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* add an IP address to a bridge */
|
|
static int
|
|
networkAddAddrToBridge(virNetworkObjPtr obj,
|
|
virNetworkIPDefPtr ipdef)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
int prefix = virNetworkIPDefPrefix(ipdef);
|
|
|
|
if (prefix < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("bridge '%s' has an invalid netmask or IP address"),
|
|
def->bridge);
|
|
return -1;
|
|
}
|
|
|
|
if (virNetDevIPAddrAdd(def->bridge, &ipdef->address, NULL, prefix) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
networkStartHandleMACTableManagerMode(virNetworkObjPtr obj,
|
|
const char *macTapIfName)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
const char *brname = def->bridge;
|
|
|
|
if (brname &&
|
|
def->macTableManager == VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_LIBVIRT) {
|
|
if (virNetDevBridgeSetVlanFiltering(brname, true) < 0)
|
|
return -1;
|
|
if (macTapIfName) {
|
|
if (virNetDevBridgePortSetLearning(brname, macTapIfName, false) < 0)
|
|
return -1;
|
|
if (virNetDevBridgePortSetUnicastFlood(brname, macTapIfName, false) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* add an IP (static) route to a bridge */
|
|
static int
|
|
networkAddRouteToBridge(virNetworkObjPtr obj,
|
|
virNetDevIPRoutePtr routedef)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
int prefix = virNetDevIPRouteGetPrefix(routedef);
|
|
unsigned int metric = virNetDevIPRouteGetMetric(routedef);
|
|
virSocketAddrPtr addr = virNetDevIPRouteGetAddress(routedef);
|
|
virSocketAddrPtr gateway = virNetDevIPRouteGetGateway(routedef);
|
|
|
|
if (prefix < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' has an invalid netmask "
|
|
"or IP address in route definition"),
|
|
def->name);
|
|
return -1;
|
|
}
|
|
|
|
if (virNetDevIPRouteAdd(def->bridge, addr, prefix, gateway, metric) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
networkWaitDadFinish(virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
virNetworkIPDefPtr ipdef;
|
|
virSocketAddrPtr *addrs = NULL, addr = NULL;
|
|
size_t naddrs = 0;
|
|
int ret = -1;
|
|
|
|
VIR_DEBUG("Begin waiting for IPv6 DAD on network %s", def->name);
|
|
|
|
while ((ipdef = virNetworkDefGetIPByIndex(def, AF_INET6, naddrs))) {
|
|
addr = &ipdef->address;
|
|
if (VIR_APPEND_ELEMENT_COPY(addrs, naddrs, addr) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = (naddrs == 0) ? 0 : virNetDevIPWaitDadFinish(addrs, naddrs);
|
|
|
|
cleanup:
|
|
VIR_FREE(addrs);
|
|
VIR_DEBUG("Finished waiting for IPv6 DAD on network %s with status %d",
|
|
def->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkStartNetworkVirtual(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
size_t i;
|
|
bool v4present = false, v6present = false;
|
|
virErrorPtr save_err = NULL;
|
|
virNetworkIPDefPtr ipdef;
|
|
virNetDevIPRoutePtr routedef;
|
|
char *macTapIfName = NULL;
|
|
virMacMapPtr macmap;
|
|
char *macMapFile = NULL;
|
|
int tapfd = -1;
|
|
bool dnsmasqStarted = false;
|
|
bool devOnline = false;
|
|
bool firewalRulesAdded = false;
|
|
|
|
/* Check to see if any network IP collides with an existing route */
|
|
if (networkCheckRouteCollision(def) < 0)
|
|
return -1;
|
|
|
|
/* Create and configure the bridge device */
|
|
if (!def->bridge) {
|
|
/* bridge name can only be empty if the config files were
|
|
* edited directly. Otherwise networkValidate() (called after
|
|
* parsing the XML from networkCreateXML() and
|
|
* networkDefine()) guarantees we will have a valid bridge
|
|
* name before this point. Since hand editing of the config
|
|
* files is explicitly prohibited we can, with clear
|
|
* conscience, log an error and fail at this point.
|
|
*/
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' has no bridge name defined"),
|
|
def->name);
|
|
return -1;
|
|
}
|
|
if (virNetDevBridgeCreate(def->bridge) < 0)
|
|
return -1;
|
|
|
|
if (def->mac_specified) {
|
|
/* To set a mac for the bridge, we need to define a dummy tap
|
|
* device, set its mac, then attach it to the bridge. As long
|
|
* as its mac address is lower than any other interface that
|
|
* gets attached, the bridge will always maintain this mac
|
|
* address.
|
|
*/
|
|
macTapIfName = networkBridgeDummyNicName(def->bridge);
|
|
if (!macTapIfName)
|
|
goto error;
|
|
/* Keep tun fd open and interface up to allow for IPv6 DAD to happen */
|
|
if (virNetDevTapCreateInBridgePort(def->bridge,
|
|
&macTapIfName, &def->mac,
|
|
NULL, NULL, &tapfd, 1, NULL, NULL,
|
|
NULL, def->mtu, NULL,
|
|
VIR_NETDEV_TAP_CREATE_USE_MAC_FOR_BRIDGE |
|
|
VIR_NETDEV_TAP_CREATE_IFUP |
|
|
VIR_NETDEV_TAP_CREATE_PERSIST) < 0) {
|
|
VIR_FREE(macTapIfName);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (!(macMapFile = virMacMapFileName(driver->dnsmasqStateDir,
|
|
def->bridge)) ||
|
|
!(macmap = virMacMapNew(macMapFile)))
|
|
goto error;
|
|
|
|
virNetworkObjSetMacMap(obj, macmap);
|
|
macmap = NULL;
|
|
|
|
/* Set bridge options */
|
|
|
|
/* delay is configured in seconds, but virNetDevBridgeSetSTPDelay
|
|
* expects milliseconds
|
|
*/
|
|
if (virNetDevBridgeSetSTPDelay(def->bridge, def->delay * 1000) < 0)
|
|
goto error;
|
|
|
|
if (virNetDevBridgeSetSTP(def->bridge, def->stp ? true : false) < 0)
|
|
goto error;
|
|
|
|
/* Disable IPv6 on the bridge if there are no IPv6 addresses
|
|
* defined, and set other IPv6 sysctl tunables appropriately.
|
|
*/
|
|
if (networkSetIPv6Sysctls(obj) < 0)
|
|
goto error;
|
|
|
|
/* Add "once per network" rules */
|
|
if (def->forward.type != VIR_NETWORK_FORWARD_OPEN &&
|
|
networkAddFirewallRules(def) < 0)
|
|
goto error;
|
|
|
|
firewalRulesAdded = true;
|
|
|
|
for (i = 0; (ipdef = virNetworkDefGetIPByIndex(def, AF_UNSPEC, i)); i++) {
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET))
|
|
v4present = true;
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6))
|
|
v6present = true;
|
|
|
|
/* Add the IP address/netmask to the bridge */
|
|
if (networkAddAddrToBridge(obj, ipdef) < 0)
|
|
goto error;
|
|
}
|
|
|
|
if (networkStartHandleMACTableManagerMode(obj, macTapIfName) < 0)
|
|
goto error;
|
|
|
|
/* Bring up the bridge interface */
|
|
if (virNetDevSetOnline(def->bridge, 1) < 0)
|
|
goto error;
|
|
|
|
devOnline = true;
|
|
|
|
for (i = 0; i < def->nroutes; i++) {
|
|
virSocketAddrPtr gateway = NULL;
|
|
|
|
routedef = def->routes[i];
|
|
gateway = virNetDevIPRouteGetGateway(routedef);
|
|
|
|
/* Add the IP route to the bridge */
|
|
/* ignore errors, error msg will be generated */
|
|
/* but libvirt will not know and net-destroy will work. */
|
|
if (VIR_SOCKET_ADDR_VALID(gateway)) {
|
|
if (networkAddRouteToBridge(obj, routedef) < 0) {
|
|
/* an error occurred adding the static route */
|
|
continue; /* for now, do nothing */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If forward.type != NONE, turn on global IP forwarding */
|
|
if (def->forward.type != VIR_NETWORK_FORWARD_NONE) {
|
|
if (v6present && !virNetDevIPCheckIPv6Forwarding())
|
|
goto error; /* Precise error message already provided */
|
|
|
|
if (networkEnableIPForwarding(v4present, v6present) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("failed to enable IP forwarding"));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
|
|
/* start dnsmasq if there are any IP addresses (v4 or v6) */
|
|
if ((v4present || v6present) &&
|
|
networkStartDhcpDaemon(driver, obj) < 0)
|
|
goto error;
|
|
|
|
dnsmasqStarted = true;
|
|
|
|
/* start radvd if there are any ipv6 addresses */
|
|
if (v6present && networkStartRadvd(driver, obj) < 0)
|
|
goto error;
|
|
|
|
/* dnsmasq does not wait for DAD to complete before daemonizing,
|
|
* so we need to wait for it ourselves.
|
|
*/
|
|
if (v6present && networkWaitDadFinish(obj) < 0)
|
|
goto error;
|
|
|
|
/* DAD has finished, dnsmasq is now bound to the
|
|
* bridge's IPv6 address, so we can set the dummy tun down.
|
|
*/
|
|
if (tapfd >= 0) {
|
|
if (virNetDevSetOnline(macTapIfName, false) < 0)
|
|
goto error;
|
|
VIR_FORCE_CLOSE(tapfd);
|
|
}
|
|
|
|
if (virNetDevBandwidthSet(def->bridge, def->bandwidth, true, true) < 0)
|
|
goto error;
|
|
|
|
VIR_FREE(macTapIfName);
|
|
VIR_FREE(macMapFile);
|
|
|
|
return 0;
|
|
|
|
error:
|
|
virErrorPreserveLast(&save_err);
|
|
if (def->bandwidth)
|
|
virNetDevBandwidthClear(def->bridge);
|
|
|
|
if (dnsmasqStarted) {
|
|
pid_t dnsmasqPid = virNetworkObjGetDnsmasqPid(obj);
|
|
kill(dnsmasqPid, SIGTERM);
|
|
virNetworkObjSetDnsmasqPid(obj, -1);
|
|
}
|
|
|
|
if (devOnline)
|
|
ignore_value(virNetDevSetOnline(def->bridge, 0));
|
|
|
|
if (firewalRulesAdded &&
|
|
def->forward.type != VIR_NETWORK_FORWARD_OPEN)
|
|
networkRemoveFirewallRules(def);
|
|
|
|
if (macTapIfName) {
|
|
VIR_FORCE_CLOSE(tapfd);
|
|
ignore_value(virNetDevTapDelete(macTapIfName, NULL));
|
|
VIR_FREE(macTapIfName);
|
|
}
|
|
virNetworkObjUnrefMacMap(obj);
|
|
VIR_FREE(macMapFile);
|
|
|
|
ignore_value(virNetDevBridgeDelete(def->bridge));
|
|
|
|
virErrorRestore(&save_err);
|
|
/* coverity[leaked_handle] - 'tapfd' is not leaked */
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
networkShutdownNetworkVirtual(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
pid_t radvdPid;
|
|
pid_t dnsmasqPid;
|
|
|
|
if (def->bandwidth)
|
|
virNetDevBandwidthClear(def->bridge);
|
|
|
|
virNetworkObjUnrefMacMap(obj);
|
|
|
|
radvdPid = virNetworkObjGetRadvdPid(obj);
|
|
if (radvdPid > 0) {
|
|
char *radvdpidbase;
|
|
|
|
kill(radvdPid, SIGTERM);
|
|
/* attempt to delete the pidfile we created */
|
|
if ((radvdpidbase = networkRadvdPidfileBasename(def->name))) {
|
|
virPidFileDelete(driver->pidDir, radvdpidbase);
|
|
VIR_FREE(radvdpidbase);
|
|
}
|
|
}
|
|
|
|
dnsmasqPid = virNetworkObjGetDnsmasqPid(obj);
|
|
if (dnsmasqPid > 0)
|
|
kill(dnsmasqPid, SIGTERM);
|
|
|
|
if (def->mac_specified) {
|
|
char *macTapIfName = networkBridgeDummyNicName(def->bridge);
|
|
if (macTapIfName) {
|
|
ignore_value(virNetDevTapDelete(macTapIfName, NULL));
|
|
VIR_FREE(macTapIfName);
|
|
}
|
|
}
|
|
|
|
ignore_value(virNetDevSetOnline(def->bridge, 0));
|
|
|
|
if (def->forward.type != VIR_NETWORK_FORWARD_OPEN)
|
|
networkRemoveFirewallRules(def);
|
|
|
|
ignore_value(virNetDevBridgeDelete(def->bridge));
|
|
|
|
/* See if its still alive and really really kill it */
|
|
dnsmasqPid = virNetworkObjGetDnsmasqPid(obj);
|
|
if (dnsmasqPid > 0 &&
|
|
(kill(dnsmasqPid, 0) == 0))
|
|
kill(dnsmasqPid, SIGKILL);
|
|
virNetworkObjSetDnsmasqPid(obj, -1);
|
|
|
|
radvdPid = virNetworkObjGetRadvdPid(obj);
|
|
if (radvdPid > 0 &&
|
|
(kill(radvdPid, 0) == 0))
|
|
kill(radvdPid, SIGKILL);
|
|
virNetworkObjSetRadvdPid(obj, -1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
networkStartNetworkBridge(virNetworkObjPtr obj)
|
|
{
|
|
/* put anything here that needs to be done each time a network of
|
|
* type BRIDGE, is started. On failure, undo anything you've done,
|
|
* and return -1. On success return 0.
|
|
*/
|
|
return networkStartHandleMACTableManagerMode(obj, NULL);
|
|
}
|
|
|
|
|
|
static int
|
|
networkShutdownNetworkBridge(virNetworkObjPtr obj ATTRIBUTE_UNUSED)
|
|
{
|
|
/* put anything here that needs to be done each time a network of
|
|
* type BRIDGE is shutdown. On failure, undo anything you've done,
|
|
* and return -1. On success return 0.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* networkCreateInterfacePool:
|
|
* @netdef: the original NetDef from the network
|
|
*
|
|
* Creates an implicit interface pool of VF's when a PF dev is given
|
|
*/
|
|
static int
|
|
networkCreateInterfacePool(virNetworkDefPtr netdef)
|
|
{
|
|
size_t numVirtFns = 0;
|
|
unsigned int maxVirtFns = 0;
|
|
char **vfNames = NULL;
|
|
virPCIDeviceAddressPtr *virtFns;
|
|
|
|
int ret = -1;
|
|
size_t i;
|
|
|
|
if (netdef->forward.npfs == 0 || netdef->forward.nifs > 0)
|
|
return 0;
|
|
|
|
if ((virNetDevGetVirtualFunctions(netdef->forward.pfs->dev, &vfNames,
|
|
&virtFns, &numVirtFns, &maxVirtFns)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Could not get Virtual functions on %s"),
|
|
netdef->forward.pfs->dev);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (VIR_ALLOC_N(netdef->forward.ifs, numVirtFns) < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < numVirtFns; i++) {
|
|
virPCIDeviceAddressPtr thisVirtFn = virtFns[i];
|
|
const char *thisName = vfNames[i];
|
|
virNetworkForwardIfDefPtr thisIf
|
|
= &netdef->forward.ifs[netdef->forward.nifs];
|
|
|
|
switch ((virNetworkForwardType) netdef->forward.type) {
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
if (thisName) {
|
|
if (VIR_STRDUP(thisIf->device.dev, thisName) < 0)
|
|
goto cleanup;
|
|
thisIf->type = VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV;
|
|
netdef->forward.nifs++;
|
|
} else {
|
|
VIR_WARN("VF %zu of SRIOV PF %s couldn't be added to the "
|
|
"interface pool because it isn't bound "
|
|
"to a network driver - possibly in use elsewhere",
|
|
i, netdef->forward.pfs->dev);
|
|
}
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_HOSTDEV:
|
|
/* VF's are always PCI devices */
|
|
thisIf->type = VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_PCI;
|
|
thisIf->device.pci.domain = thisVirtFn->domain;
|
|
thisIf->device.pci.bus = thisVirtFn->bus;
|
|
thisIf->device.pci.slot = thisVirtFn->slot;
|
|
thisIf->device.pci.function = thisVirtFn->function;
|
|
netdef->forward.nifs++;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
/* by definition these will never be encountered here */
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, netdef->forward.type);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (netdef->forward.nifs == 0) {
|
|
/* If we don't get at least one interface in the pool, declare
|
|
* failure
|
|
*/
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("No usable Vf's present on SRIOV PF %s"),
|
|
netdef->forward.pfs->dev);
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0) {
|
|
/* free all the entries made before error */
|
|
for (i = 0; i < netdef->forward.nifs; i++) {
|
|
if (netdef->forward.ifs[i].type
|
|
== VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV)
|
|
VIR_FREE(netdef->forward.ifs[i].device.dev);
|
|
}
|
|
netdef->forward.nifs = 0;
|
|
}
|
|
if (netdef->forward.nifs == 0)
|
|
VIR_FREE(netdef->forward.ifs);
|
|
|
|
for (i = 0; i < numVirtFns; i++) {
|
|
VIR_FREE(vfNames[i]);
|
|
VIR_FREE(virtFns[i]);
|
|
}
|
|
VIR_FREE(vfNames);
|
|
VIR_FREE(virtFns);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkStartNetworkExternal(virNetworkObjPtr obj)
|
|
{
|
|
/* put anything here that needs to be done each time a network of
|
|
* type BRIDGE, PRIVATE, VEPA, HOSTDEV or PASSTHROUGH is started. On
|
|
* failure, undo anything you've done, and return -1. On success
|
|
* return 0.
|
|
*/
|
|
return networkCreateInterfacePool(virNetworkObjGetDef(obj));
|
|
}
|
|
|
|
|
|
static int
|
|
networkShutdownNetworkExternal(virNetworkObjPtr obj ATTRIBUTE_UNUSED)
|
|
{
|
|
/* put anything here that needs to be done each time a network of
|
|
* type BRIDGE, PRIVATE, VEPA, HOSTDEV or PASSTHROUGH is shutdown. On
|
|
* failure, undo anything you've done, and return -1. On success
|
|
* return 0.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
networkStartNetwork(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
int ret = -1;
|
|
|
|
VIR_DEBUG("driver=%p, network=%p", driver, obj);
|
|
|
|
if (virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
"%s", _("network is already active"));
|
|
return ret;
|
|
}
|
|
|
|
VIR_DEBUG("Beginning network startup process");
|
|
|
|
virNetworkObjDeleteAllPorts(obj, driver->stateDir);
|
|
|
|
VIR_DEBUG("Setting current network def as transient");
|
|
if (virNetworkObjSetDefTransient(obj, true, network_driver->xmlopt) < 0)
|
|
goto cleanup;
|
|
|
|
/* Run an early hook to set-up missing devices.
|
|
* If the script raised an error abort the launch. */
|
|
if (networkRunHook(obj, NULL,
|
|
VIR_HOOK_NETWORK_OP_START,
|
|
VIR_HOOK_SUBOP_BEGIN) < 0)
|
|
goto cleanup;
|
|
|
|
switch ((virNetworkForwardType) def->forward.type) {
|
|
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
if (networkStartNetworkVirtual(driver, obj) < 0)
|
|
goto cleanup;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
if (def->bridge) {
|
|
if (networkStartNetworkBridge(obj) < 0)
|
|
goto cleanup;
|
|
break;
|
|
}
|
|
/* intentionally fall through to the macvtap/direct case for
|
|
* VIR_NETWORK_FORWARD_BRIDGE with no bridge device defined
|
|
* (since that is macvtap bridge mode).
|
|
*/
|
|
ATTRIBUTE_FALLTHROUGH;
|
|
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
case VIR_NETWORK_FORWARD_HOSTDEV:
|
|
if (networkStartNetworkExternal(obj) < 0)
|
|
goto cleanup;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, def->forward.type);
|
|
goto cleanup;
|
|
}
|
|
|
|
virNetworkObjSetFloorSum(obj, 0);
|
|
|
|
/* finally we can call the 'started' hook script if any */
|
|
if (networkRunHook(obj, NULL,
|
|
VIR_HOOK_NETWORK_OP_STARTED,
|
|
VIR_HOOK_SUBOP_BEGIN) < 0)
|
|
goto cleanup;
|
|
|
|
/* Persist the live configuration now that anything autogenerated
|
|
* is setup.
|
|
*/
|
|
VIR_DEBUG("Writing network status to disk");
|
|
if (virNetworkObjSaveStatus(driver->stateDir,
|
|
obj, network_driver->xmlopt) < 0)
|
|
goto cleanup;
|
|
|
|
virNetworkObjSetActive(obj, true);
|
|
VIR_INFO("Network '%s' started up", def->name);
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (ret < 0) {
|
|
virNetworkObjUnsetDefTransient(obj);
|
|
virErrorPtr save_err = virSaveLastError();
|
|
int save_errno = errno;
|
|
networkShutdownNetwork(driver, obj);
|
|
virSetError(save_err);
|
|
virFreeError(save_err);
|
|
errno = save_errno;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkShutdownNetwork(virNetworkDriverStatePtr driver,
|
|
virNetworkObjPtr obj)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
int ret = 0;
|
|
char *stateFile;
|
|
|
|
VIR_INFO("Shutting down network '%s'", def->name);
|
|
|
|
if (!virNetworkObjIsActive(obj))
|
|
return 0;
|
|
|
|
stateFile = virNetworkConfigFile(driver->stateDir, def->name);
|
|
if (!stateFile)
|
|
return -1;
|
|
|
|
unlink(stateFile);
|
|
VIR_FREE(stateFile);
|
|
|
|
switch ((virNetworkForwardType) def->forward.type) {
|
|
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
ret = networkShutdownNetworkVirtual(driver, obj);
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
if (def->bridge) {
|
|
ret = networkShutdownNetworkBridge(obj);
|
|
break;
|
|
}
|
|
/* intentionally fall through to the macvtap/direct case for
|
|
* VIR_NETWORK_FORWARD_BRIDGE with no bridge device defined
|
|
* (since that is macvtap bridge mode).
|
|
*/
|
|
ATTRIBUTE_FALLTHROUGH;
|
|
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
case VIR_NETWORK_FORWARD_HOSTDEV:
|
|
ret = networkShutdownNetworkExternal(obj);
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, def->forward.type);
|
|
return -1;
|
|
}
|
|
|
|
/* now that we know it's stopped call the hook if present */
|
|
networkRunHook(obj, NULL, VIR_HOOK_NETWORK_OP_STOPPED,
|
|
VIR_HOOK_SUBOP_END);
|
|
|
|
virNetworkObjSetActive(obj, false);
|
|
virNetworkObjUnsetDefTransient(obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virNetworkPtr
|
|
networkLookupByUUID(virConnectPtr conn,
|
|
const unsigned char *uuid)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
virNetworkPtr net = NULL;
|
|
|
|
obj = virNetworkObjFindByUUID(driver->networks, uuid);
|
|
if (!obj) {
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
virUUIDFormat(uuid, uuidstr);
|
|
virReportError(VIR_ERR_NO_NETWORK,
|
|
_("no network with matching uuid '%s'"),
|
|
uuidstr);
|
|
goto cleanup;
|
|
}
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkLookupByUUIDEnsureACL(conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
net = virGetNetwork(conn, def->name, def->uuid);
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return net;
|
|
}
|
|
|
|
|
|
static virNetworkPtr
|
|
networkLookupByName(virConnectPtr conn,
|
|
const char *name)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
virNetworkPtr net = NULL;
|
|
|
|
obj = virNetworkObjFindByName(driver->networks, name);
|
|
if (!obj) {
|
|
virReportError(VIR_ERR_NO_NETWORK,
|
|
_("no network with matching name '%s'"), name);
|
|
goto cleanup;
|
|
}
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkLookupByNameEnsureACL(conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
net = virGetNetwork(conn, def->name, def->uuid);
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return net;
|
|
}
|
|
|
|
|
|
static int
|
|
networkConnectNumOfNetworks(virConnectPtr conn)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
int nactive;
|
|
|
|
if (virConnectNumOfNetworksEnsureACL(conn) < 0)
|
|
return -1;
|
|
|
|
nactive = virNetworkObjListNumOfNetworks(driver->networks,
|
|
true,
|
|
virConnectNumOfNetworksCheckACL,
|
|
conn);
|
|
|
|
return nactive;
|
|
}
|
|
|
|
|
|
static int
|
|
networkConnectListNetworks(virConnectPtr conn,
|
|
char **const names,
|
|
int maxnames)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
int got = 0;
|
|
|
|
if (virConnectListNetworksEnsureACL(conn) < 0)
|
|
return -1;
|
|
|
|
got = virNetworkObjListGetNames(driver->networks,
|
|
true, names, maxnames,
|
|
virConnectListNetworksCheckACL,
|
|
conn);
|
|
|
|
return got;
|
|
}
|
|
|
|
|
|
static int
|
|
networkConnectNumOfDefinedNetworks(virConnectPtr conn)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
int ninactive = 0;
|
|
|
|
if (virConnectNumOfDefinedNetworksEnsureACL(conn) < 0)
|
|
return -1;
|
|
|
|
ninactive = virNetworkObjListNumOfNetworks(driver->networks,
|
|
false,
|
|
virConnectNumOfDefinedNetworksCheckACL,
|
|
conn);
|
|
|
|
return ninactive;
|
|
}
|
|
|
|
|
|
static int
|
|
networkConnectListDefinedNetworks(virConnectPtr conn,
|
|
char **const names,
|
|
int maxnames)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
int got = 0;
|
|
|
|
if (virConnectListDefinedNetworksEnsureACL(conn) < 0)
|
|
return -1;
|
|
|
|
got = virNetworkObjListGetNames(driver->networks,
|
|
false, names, maxnames,
|
|
virConnectListDefinedNetworksCheckACL,
|
|
conn);
|
|
return got;
|
|
}
|
|
|
|
|
|
static int
|
|
networkConnectListAllNetworks(virConnectPtr conn,
|
|
virNetworkPtr **nets,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
int ret = -1;
|
|
|
|
virCheckFlags(VIR_CONNECT_LIST_NETWORKS_FILTERS_ALL, -1);
|
|
|
|
if (virConnectListAllNetworksEnsureACL(conn) < 0)
|
|
goto cleanup;
|
|
|
|
ret = virNetworkObjListExport(conn, driver->networks, nets,
|
|
virConnectListAllNetworksCheckACL,
|
|
flags);
|
|
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkConnectNetworkEventRegisterAny(virConnectPtr conn,
|
|
virNetworkPtr net,
|
|
int eventID,
|
|
virConnectNetworkEventGenericCallback callback,
|
|
void *opaque,
|
|
virFreeCallback freecb)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
int ret = -1;
|
|
|
|
if (virConnectNetworkEventRegisterAnyEnsureACL(conn) < 0)
|
|
goto cleanup;
|
|
|
|
if (virNetworkEventStateRegisterID(conn, driver->networkEventState,
|
|
net, eventID, callback,
|
|
opaque, freecb, &ret) < 0)
|
|
ret = -1;
|
|
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkConnectNetworkEventDeregisterAny(virConnectPtr conn,
|
|
int callbackID)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
int ret = -1;
|
|
|
|
if (virConnectNetworkEventDeregisterAnyEnsureACL(conn) < 0)
|
|
goto cleanup;
|
|
|
|
if (virObjectEventStateDeregisterID(conn,
|
|
driver->networkEventState,
|
|
callbackID, true) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkIsActive(virNetworkPtr net)
|
|
{
|
|
virNetworkObjPtr obj;
|
|
int ret = -1;
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
return ret;
|
|
|
|
if (virNetworkIsActiveEnsureACL(net->conn, virNetworkObjGetDef(obj)) < 0)
|
|
goto cleanup;
|
|
|
|
ret = virNetworkObjIsActive(obj);
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkIsPersistent(virNetworkPtr net)
|
|
{
|
|
virNetworkObjPtr obj;
|
|
int ret = -1;
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
return ret;
|
|
|
|
if (virNetworkIsPersistentEnsureACL(net->conn, virNetworkObjGetDef(obj)) < 0)
|
|
goto cleanup;
|
|
|
|
ret = virNetworkObjIsPersistent(obj);
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* networkFindUnusedBridgeName() - try to find a bridge name that is
|
|
* unused by the currently configured libvirt networks, as well as by
|
|
* the host system itself (possibly created by someone/something other
|
|
* than libvirt). Set this network's name to that new name.
|
|
*/
|
|
static int
|
|
networkFindUnusedBridgeName(virNetworkObjListPtr nets,
|
|
virNetworkDefPtr def)
|
|
{
|
|
int ret = -1, id = 0;
|
|
char *newname = NULL;
|
|
const char *templ = "virbr%d";
|
|
const char *p;
|
|
|
|
if (def->bridge &&
|
|
(p = strchr(def->bridge, '%')) == strrchr(def->bridge, '%') &&
|
|
p && p[1] == 'd')
|
|
templ = def->bridge;
|
|
|
|
do {
|
|
if (virAsprintf(&newname, templ, id) < 0)
|
|
goto cleanup;
|
|
/* check if this name is used in another libvirt network or
|
|
* there is an existing device with that name. ignore errors
|
|
* from virNetDevExists(), just in case it isn't implemented
|
|
* on this platform (probably impossible).
|
|
*/
|
|
if (!(virNetworkObjBridgeInUse(nets, newname, def->name) ||
|
|
virNetDevExists(newname) == 1)) {
|
|
VIR_FREE(def->bridge); /*could contain template */
|
|
def->bridge = newname;
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
VIR_FREE(newname);
|
|
} while (++id <= MAX_BRIDGE_ID);
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Bridge generation exceeded max id %d"),
|
|
MAX_BRIDGE_ID);
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0)
|
|
VIR_FREE(newname);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* networkValidateBridgeName() - if no bridge name is set, or if the
|
|
* bridge name contains a %d (indicating that this is a template for
|
|
* the actual name) try to set an appropriate bridge name. If a
|
|
* bridge name *is* set, make sure it doesn't conflict with any other
|
|
* network's bridge name.
|
|
*/
|
|
static int
|
|
networkBridgeNameValidate(virNetworkObjListPtr nets,
|
|
virNetworkDefPtr def)
|
|
{
|
|
int ret = -1;
|
|
|
|
if (def->bridge && !strstr(def->bridge, "%d")) {
|
|
if (virNetworkObjBridgeInUse(nets, def->bridge, def->name)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("bridge name '%s' already in use."),
|
|
def->bridge);
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
/* Allocate a bridge name */
|
|
if (networkFindUnusedBridgeName(nets, def) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkValidate(virNetworkDriverStatePtr driver,
|
|
virNetworkDefPtr def)
|
|
{
|
|
size_t i, j;
|
|
bool vlanUsed, vlanAllowed, badVlanUse = false;
|
|
virPortGroupDefPtr defaultPortGroup = NULL;
|
|
virNetworkIPDefPtr ipdef;
|
|
bool ipv4def = false, ipv6def = false;
|
|
bool bandwidthAllowed = false;
|
|
bool usesInterface = false, usesAddress = false;
|
|
|
|
if (virXMLCheckIllegalChars("name", def->name, "\n") < 0)
|
|
return -1;
|
|
|
|
/* Only the three L3 network types that are configured by libvirt
|
|
* need to have a bridge device name / mac address provided
|
|
*/
|
|
switch ((virNetworkForwardType) def->forward.type) {
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
/* if no bridge name was given in the config, find a name
|
|
* unused by any other libvirt networks and assign it.
|
|
*/
|
|
if (networkBridgeNameValidate(driver->networks, def) < 0)
|
|
return -1;
|
|
|
|
virNetworkSetBridgeMacAddr(def);
|
|
bandwidthAllowed = true;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
if (def->bridge != NULL)
|
|
bandwidthAllowed = true;
|
|
|
|
ATTRIBUTE_FALLTHROUGH;
|
|
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
case VIR_NETWORK_FORWARD_HOSTDEV:
|
|
/* They are also the only types that currently support setting
|
|
* a MAC or IP address for the host-side device (bridge), DNS
|
|
* configuration, or network-wide bandwidth limits.
|
|
*/
|
|
if (def->mac_specified) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unsupported <mac> element in network %s "
|
|
"with forward mode='%s'"),
|
|
def->name,
|
|
virNetworkForwardTypeToString(def->forward.type));
|
|
return -1;
|
|
}
|
|
if (virNetworkDefGetIPByIndex(def, AF_UNSPEC, 0)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unsupported <ip> element in network %s "
|
|
"with forward mode='%s'"),
|
|
def->name,
|
|
virNetworkForwardTypeToString(def->forward.type));
|
|
return -1;
|
|
}
|
|
if (def->dns.ntxts || def->dns.nhosts || def->dns.nsrvs) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unsupported <dns> element in network %s "
|
|
"with forward mode='%s'"),
|
|
def->name,
|
|
virNetworkForwardTypeToString(def->forward.type));
|
|
return -1;
|
|
}
|
|
if (def->domain) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unsupported <domain> element in network %s "
|
|
"with forward mode='%s'"),
|
|
def->name,
|
|
virNetworkForwardTypeToString(def->forward.type));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, def->forward.type);
|
|
return -1;
|
|
}
|
|
|
|
if (def->bandwidth &&
|
|
!bandwidthAllowed) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unsupported network-wide <bandwidth> element "
|
|
"in network %s with forward mode='%s'"),
|
|
def->name,
|
|
virNetworkForwardTypeToString(def->forward.type));
|
|
return -1;
|
|
}
|
|
|
|
/* we support configs with a single PF defined:
|
|
* <pf dev='eth0'/>
|
|
* or with a list of netdev names:
|
|
* <interface dev='eth9'/>
|
|
* OR a list of PCI addresses
|
|
* <address type='pci' domain='0' bus='4' slot='0' function='1'/>
|
|
* but not any combination of those.
|
|
*
|
|
* Since <interface> and <address> are for some strange reason
|
|
* stored in the same array, we need to cycle through it and check
|
|
* the type of each.
|
|
*/
|
|
for (i = 0; i < def->forward.nifs; i++) {
|
|
virNetworkForwardIfDefPtr iface = &def->forward.ifs[i];
|
|
char *sysfs_path = NULL;
|
|
|
|
switch ((virNetworkForwardHostdevDeviceType)iface->type) {
|
|
case VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV:
|
|
usesInterface = true;
|
|
|
|
if (def->forward.type == VIR_NETWORK_FORWARD_HOSTDEV) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("hostdev network '%s' lists '%s' "
|
|
"in the device pool, but hostdev "
|
|
"networks require all devices to "
|
|
"be listed by PCI address, not "
|
|
"network device name"),
|
|
def->name, iface->device.dev);
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_PCI: {
|
|
usesAddress = true;
|
|
|
|
if (def->forward.type != VIR_NETWORK_FORWARD_HOSTDEV) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("network '%s' has forward mode '%s' "
|
|
" but lists a device by PCI address "
|
|
"in the device pool. This is only "
|
|
"supported for networks with forward "
|
|
"mode 'hostdev'"),
|
|
def->name,
|
|
virNetworkForwardTypeToString(def->forward.type));
|
|
return -1;
|
|
}
|
|
|
|
if (virPCIDeviceAddressGetSysfsFile(&iface->device.pci, &sysfs_path) < 0)
|
|
return -1;
|
|
|
|
if (!virPCIIsVirtualFunction(sysfs_path)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("device '%s' in network '%s' is not "
|
|
"an SR-IOV Virtual Function"),
|
|
sysfs_path, def->name);
|
|
VIR_FREE(sysfs_path);
|
|
return -1;
|
|
}
|
|
VIR_FREE(sysfs_path);
|
|
break;
|
|
}
|
|
|
|
case VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NONE:
|
|
case VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
if ((def->forward.npfs > 0) + usesInterface + usesAddress > 1) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("<address>, <interface>, and <pf> elements of "
|
|
"<forward> in network %s are mutually exclusive"),
|
|
def->name);
|
|
return -1;
|
|
}
|
|
|
|
/* We only support dhcp on one IPv4 address and
|
|
* on one IPv6 address per defined network
|
|
*/
|
|
for (i = 0;
|
|
(ipdef = virNetworkDefGetIPByIndex(def, AF_UNSPEC, i));
|
|
i++) {
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) {
|
|
if (ipdef->nranges || ipdef->nhosts) {
|
|
if (ipv4def) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Multiple IPv4 dhcp sections found -- "
|
|
"dhcp is supported only for a "
|
|
"single IPv4 address on each network"));
|
|
return -1;
|
|
} else {
|
|
ipv4def = true;
|
|
}
|
|
}
|
|
}
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) {
|
|
if (ipdef->nranges || ipdef->nhosts) {
|
|
if (ipv6def) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Multiple IPv6 dhcp sections found -- "
|
|
"dhcp is supported only for a "
|
|
"single IPv6 address on each network"));
|
|
return -1;
|
|
} else {
|
|
ipv6def = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The only type of networks that currently support transparent
|
|
* vlan configuration are those using hostdev sr-iov devices from
|
|
* a pool, and those using an Open vSwitch bridge.
|
|
*/
|
|
|
|
vlanAllowed = (def->forward.type == VIR_NETWORK_FORWARD_HOSTDEV ||
|
|
def->forward.type == VIR_NETWORK_FORWARD_PASSTHROUGH ||
|
|
(def->forward.type == VIR_NETWORK_FORWARD_BRIDGE &&
|
|
def->virtPortProfile &&
|
|
def->virtPortProfile->virtPortType
|
|
== VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH));
|
|
|
|
vlanUsed = def->vlan.nTags > 0;
|
|
for (i = 0; i < def->nPortGroups; i++) {
|
|
if (vlanUsed || def->portGroups[i].vlan.nTags > 0) {
|
|
/* anyone using this portgroup will get a vlan tag. Verify
|
|
* that they will also be using an openvswitch connection,
|
|
* as that is the only type of network that currently
|
|
* supports a vlan tag.
|
|
*/
|
|
if (def->portGroups[i].virtPortProfile) {
|
|
if (def->forward.type != VIR_NETWORK_FORWARD_BRIDGE ||
|
|
def->portGroups[i].virtPortProfile->virtPortType
|
|
!= VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH) {
|
|
badVlanUse = true;
|
|
}
|
|
} else if (!vlanAllowed) {
|
|
/* virtualport taken from base network definition */
|
|
badVlanUse = true;
|
|
}
|
|
}
|
|
if (def->portGroups[i].isDefault) {
|
|
if (defaultPortGroup) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("network '%s' has multiple default "
|
|
"<portgroup> elements (%s and %s), "
|
|
"but only one default is allowed"),
|
|
def->name, defaultPortGroup->name,
|
|
def->portGroups[i].name);
|
|
return -1;
|
|
}
|
|
defaultPortGroup = &def->portGroups[i];
|
|
}
|
|
for (j = i + 1; j < def->nPortGroups; j++) {
|
|
if (STREQ(def->portGroups[i].name, def->portGroups[j].name)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("multiple <portgroup> elements with the "
|
|
"same name (%s) in network '%s'"),
|
|
def->portGroups[i].name, def->name);
|
|
return -1;
|
|
}
|
|
}
|
|
if (def->portGroups[i].bandwidth && !bandwidthAllowed) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unsupported <bandwidth> element in network '%s' "
|
|
"in portgroup '%s' with forward mode='%s'"),
|
|
def->name, def->portGroups[i].name,
|
|
virNetworkForwardTypeToString(def->forward.type));
|
|
return -1;
|
|
}
|
|
}
|
|
if (badVlanUse ||
|
|
(vlanUsed && !vlanAllowed && !defaultPortGroup)) {
|
|
/* NB: if defaultPortGroup is set, we don't directly look at
|
|
* vlanUsed && !vlanAllowed, because the network will never be
|
|
* used without having a portgroup added in, so all necessary
|
|
* checks were done in the loop above.
|
|
*/
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("<vlan> element specified for network %s, "
|
|
"whose type doesn't support vlan configuration"),
|
|
def->name);
|
|
return -1;
|
|
}
|
|
|
|
if (def->forward.type == VIR_NETWORK_FORWARD_HOSTDEV) {
|
|
for (i = 0; i < def->nPortGroups; i++) {
|
|
if (def->portGroups[i].bandwidth) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported <bandwidth> element "
|
|
"in <portgroup name='%s'> of "
|
|
"network '%s' with forward mode='%s'"),
|
|
def->portGroups[i].name, def->name,
|
|
virNetworkForwardTypeToString(def->forward.type));
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virNetworkPtr
|
|
networkCreateXML(virConnectPtr conn,
|
|
const char *xml)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkDefPtr newDef;
|
|
virNetworkObjPtr obj = NULL;
|
|
virNetworkDefPtr def;
|
|
virNetworkPtr net = NULL;
|
|
virObjectEventPtr event = NULL;
|
|
|
|
if (!(newDef = virNetworkDefParseString(xml, network_driver->xmlopt)))
|
|
goto cleanup;
|
|
|
|
if (virNetworkCreateXMLEnsureACL(conn, newDef) < 0)
|
|
goto cleanup;
|
|
|
|
if (networkValidate(driver, newDef) < 0)
|
|
goto cleanup;
|
|
|
|
/* NB: even though this transient network hasn't yet been started,
|
|
* we assign the def with live = true in anticipation that it will
|
|
* be started momentarily.
|
|
*/
|
|
if (!(obj = virNetworkObjAssignDef(driver->networks, newDef,
|
|
VIR_NETWORK_OBJ_LIST_ADD_LIVE |
|
|
VIR_NETWORK_OBJ_LIST_ADD_CHECK_LIVE)))
|
|
goto cleanup;
|
|
newDef = NULL;
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (networkStartNetwork(driver, obj) < 0) {
|
|
virNetworkObjRemoveInactive(driver->networks, obj);
|
|
goto cleanup;
|
|
}
|
|
|
|
event = virNetworkEventLifecycleNew(def->name,
|
|
def->uuid,
|
|
VIR_NETWORK_EVENT_STARTED,
|
|
0);
|
|
|
|
VIR_INFO("Creating network '%s'", def->name);
|
|
net = virGetNetwork(conn, def->name, def->uuid);
|
|
|
|
cleanup:
|
|
virNetworkDefFree(newDef);
|
|
virObjectEventStateQueue(driver->networkEventState, event);
|
|
virNetworkObjEndAPI(&obj);
|
|
return net;
|
|
}
|
|
|
|
|
|
static virNetworkPtr
|
|
networkDefineXML(virConnectPtr conn,
|
|
const char *xml)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkDefPtr def = NULL;
|
|
bool freeDef = true;
|
|
virNetworkObjPtr obj = NULL;
|
|
virNetworkPtr net = NULL;
|
|
virObjectEventPtr event = NULL;
|
|
|
|
if (!(def = virNetworkDefParseString(xml, network_driver->xmlopt)))
|
|
goto cleanup;
|
|
|
|
if (virNetworkDefineXMLEnsureACL(conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (networkValidate(driver, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (!(obj = virNetworkObjAssignDef(driver->networks, def, 0)))
|
|
goto cleanup;
|
|
|
|
/* def was assigned to network object */
|
|
freeDef = false;
|
|
|
|
if (virNetworkSaveConfig(driver->networkConfigDir,
|
|
def, network_driver->xmlopt) < 0) {
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virNetworkObjRemoveInactive(driver->networks, obj);
|
|
goto cleanup;
|
|
}
|
|
/* if network was active already, just undo new persistent
|
|
* definition by making it transient.
|
|
* XXX - this isn't necessarily the correct thing to do.
|
|
*/
|
|
virNetworkObjUpdateAssignDef(obj, NULL, false);
|
|
goto cleanup;
|
|
}
|
|
|
|
event = virNetworkEventLifecycleNew(def->name, def->uuid,
|
|
VIR_NETWORK_EVENT_DEFINED,
|
|
0);
|
|
|
|
VIR_INFO("Defining network '%s'", def->name);
|
|
net = virGetNetwork(conn, def->name, def->uuid);
|
|
|
|
cleanup:
|
|
virObjectEventStateQueue(driver->networkEventState, event);
|
|
if (freeDef)
|
|
virNetworkDefFree(def);
|
|
virNetworkObjEndAPI(&obj);
|
|
return net;
|
|
}
|
|
|
|
|
|
static int
|
|
networkUndefine(virNetworkPtr net)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
int ret = -1;
|
|
bool active = false;
|
|
virObjectEventPtr event = NULL;
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
goto cleanup;
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkUndefineEnsureACL(net->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (virNetworkObjIsActive(obj))
|
|
active = true;
|
|
|
|
if (!virNetworkObjIsPersistent(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("can't undefine transient network"));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* remove autostart link */
|
|
if (virNetworkObjDeleteConfig(driver->networkConfigDir,
|
|
driver->networkAutostartDir,
|
|
obj) < 0)
|
|
goto cleanup;
|
|
|
|
event = virNetworkEventLifecycleNew(def->name,
|
|
def->uuid,
|
|
VIR_NETWORK_EVENT_UNDEFINED,
|
|
0);
|
|
|
|
VIR_INFO("Undefining network '%s'", def->name);
|
|
if (!active) {
|
|
if (networkRemoveInactive(driver, obj) < 0)
|
|
goto cleanup;
|
|
} else {
|
|
|
|
/* if the network still exists, it was active, and we need to make
|
|
* it transient (by deleting the persistent def)
|
|
*/
|
|
virNetworkObjUpdateAssignDef(obj, NULL, false);
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virObjectEventStateQueue(driver->networkEventState, event);
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkUpdate(virNetworkPtr net,
|
|
unsigned int command,
|
|
unsigned int section,
|
|
int parentIndex,
|
|
const char *xml,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj = NULL;
|
|
virNetworkDefPtr def;
|
|
int isActive, ret = -1;
|
|
size_t i;
|
|
virNetworkIPDefPtr ipdef;
|
|
bool oldDhcpActive = false;
|
|
bool needFirewallRefresh = false;
|
|
|
|
|
|
virCheckFlags(VIR_NETWORK_UPDATE_AFFECT_LIVE |
|
|
VIR_NETWORK_UPDATE_AFFECT_CONFIG,
|
|
-1);
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
goto cleanup;
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkUpdateEnsureACL(net->conn, def, flags) < 0)
|
|
goto cleanup;
|
|
|
|
/* see if we are listening for dhcp pre-modification */
|
|
for (i = 0;
|
|
(ipdef = virNetworkDefGetIPByIndex(def, AF_INET, i));
|
|
i++) {
|
|
if (ipdef->nranges || ipdef->nhosts) {
|
|
oldDhcpActive = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* VIR_NETWORK_UPDATE_AFFECT_CURRENT means "change LIVE if network
|
|
* is active, else change CONFIG
|
|
*/
|
|
isActive = virNetworkObjIsActive(obj);
|
|
if ((flags & (VIR_NETWORK_UPDATE_AFFECT_LIVE |
|
|
VIR_NETWORK_UPDATE_AFFECT_CONFIG)) ==
|
|
VIR_NETWORK_UPDATE_AFFECT_CURRENT) {
|
|
if (isActive)
|
|
flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE;
|
|
else
|
|
flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG;
|
|
}
|
|
|
|
if (isActive && (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE)) {
|
|
/* Take care of anything that must be done before updating the
|
|
* live NetworkDef.
|
|
*/
|
|
switch ((virNetworkForwardType) def->forward.type) {
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
switch (section) {
|
|
case VIR_NETWORK_SECTION_FORWARD:
|
|
case VIR_NETWORK_SECTION_FORWARD_INTERFACE:
|
|
case VIR_NETWORK_SECTION_IP:
|
|
case VIR_NETWORK_SECTION_IP_DHCP_RANGE:
|
|
case VIR_NETWORK_SECTION_IP_DHCP_HOST:
|
|
/* these could affect the firewall rules, so remove the
|
|
* old rules (and remember to load new ones after the
|
|
* update).
|
|
*/
|
|
networkRemoveFirewallRules(def);
|
|
needFirewallRefresh = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
case VIR_NETWORK_FORWARD_HOSTDEV:
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, def->forward.type);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* update the network config in memory/on disk */
|
|
if (virNetworkObjUpdate(obj, command, section,
|
|
parentIndex, xml,
|
|
network_driver->xmlopt, flags) < 0) {
|
|
if (needFirewallRefresh)
|
|
ignore_value(networkAddFirewallRules(def));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* @def is replaced */
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (needFirewallRefresh && networkAddFirewallRules(def) < 0)
|
|
goto cleanup;
|
|
|
|
if (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) {
|
|
/* save updated persistent config to disk */
|
|
if (virNetworkSaveConfig(driver->networkConfigDir,
|
|
virNetworkObjGetPersistentDef(obj),
|
|
network_driver->xmlopt) < 0) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (isActive && (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE)) {
|
|
/* rewrite dnsmasq host files, restart dnsmasq, update iptables
|
|
* rules, etc, according to which section was modified. Note that
|
|
* some sections require multiple actions, so a single switch
|
|
* statement is inadequate.
|
|
*/
|
|
if (section == VIR_NETWORK_SECTION_BRIDGE ||
|
|
section == VIR_NETWORK_SECTION_DOMAIN ||
|
|
section == VIR_NETWORK_SECTION_IP ||
|
|
section == VIR_NETWORK_SECTION_IP_DHCP_RANGE ||
|
|
section == VIR_NETWORK_SECTION_DNS_TXT ||
|
|
section == VIR_NETWORK_SECTION_DNS_SRV) {
|
|
/* these sections all change things on the dnsmasq
|
|
* commandline (i.e. in the .conf file), so we need to
|
|
* kill and restart dnsmasq, because dnsmasq sets its uid
|
|
* to "nobody" after it starts, and is unable to re-read
|
|
* the conf file (owned by root, mode 600)
|
|
*/
|
|
if (networkRestartDhcpDaemon(driver, obj) < 0)
|
|
goto cleanup;
|
|
|
|
} else if (section == VIR_NETWORK_SECTION_IP_DHCP_HOST) {
|
|
/* if we previously weren't listening for dhcp and now we
|
|
* are (or vice-versa) then we need to do a restart,
|
|
* otherwise we just need to do a refresh (redo the config
|
|
* files and send SIGHUP)
|
|
*/
|
|
bool newDhcpActive = false;
|
|
|
|
for (i = 0; (ipdef = virNetworkDefGetIPByIndex(def, AF_INET, i));
|
|
i++) {
|
|
if (ipdef->nranges || ipdef->nhosts) {
|
|
newDhcpActive = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((newDhcpActive != oldDhcpActive &&
|
|
networkRestartDhcpDaemon(driver, obj) < 0) ||
|
|
networkRefreshDhcpDaemon(driver, obj) < 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
} else if (section == VIR_NETWORK_SECTION_DNS_HOST) {
|
|
/* this section only changes data in an external file
|
|
* (not the .conf file) so we can just update the config
|
|
* files and send SIGHUP to dnsmasq.
|
|
*/
|
|
if (networkRefreshDhcpDaemon(driver, obj) < 0)
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (section == VIR_NETWORK_SECTION_IP) {
|
|
/* only a change in IP addresses will affect radvd, and all of radvd's
|
|
* config is stored in the conf file which will be re-read with a SIGHUP.
|
|
*/
|
|
if (networkRefreshRadvd(driver, obj) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* save current network state to disk */
|
|
if ((ret = virNetworkObjSaveStatus(driver->stateDir,
|
|
obj, network_driver->xmlopt)) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* call the 'updated' network hook script */
|
|
if (networkRunHook(obj, NULL, VIR_HOOK_NETWORK_OP_UPDATED,
|
|
VIR_HOOK_SUBOP_BEGIN) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkCreate(virNetworkPtr net)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
int ret = -1;
|
|
virObjectEventPtr event = NULL;
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
goto cleanup;
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkCreateEnsureACL(net->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if ((ret = networkStartNetwork(driver, obj)) < 0)
|
|
goto cleanup;
|
|
|
|
event = virNetworkEventLifecycleNew(def->name,
|
|
def->uuid,
|
|
VIR_NETWORK_EVENT_STARTED,
|
|
0);
|
|
|
|
cleanup:
|
|
virObjectEventStateQueue(driver->networkEventState, event);
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkDestroy(virNetworkPtr net)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
int ret = -1;
|
|
virObjectEventPtr event = NULL;
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
goto cleanup;
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkDestroyEnsureACL(net->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((ret = networkShutdownNetwork(driver, obj)) < 0)
|
|
goto cleanup;
|
|
|
|
virNetworkObjDeleteAllPorts(obj, driver->stateDir);
|
|
|
|
/* @def replaced in virNetworkObjUnsetDefTransient*/
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
event = virNetworkEventLifecycleNew(def->name,
|
|
def->uuid,
|
|
VIR_NETWORK_EVENT_STOPPED,
|
|
0);
|
|
|
|
if (!virNetworkObjIsPersistent(obj) &&
|
|
networkRemoveInactive(driver, obj) < 0) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
virObjectEventStateQueue(driver->networkEventState, event);
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static char *
|
|
networkGetXMLDesc(virNetworkPtr net,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr curDef;
|
|
virNetworkDefPtr def;
|
|
virNetworkDefPtr newDef;
|
|
char *ret = NULL;
|
|
|
|
virCheckFlags(VIR_NETWORK_XML_INACTIVE, NULL);
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
return ret;
|
|
def = virNetworkObjGetDef(obj);
|
|
newDef = virNetworkObjGetNewDef(obj);
|
|
|
|
if (virNetworkGetXMLDescEnsureACL(net->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if ((flags & VIR_NETWORK_XML_INACTIVE) && newDef)
|
|
curDef = newDef;
|
|
else
|
|
curDef = def;
|
|
|
|
ret = virNetworkDefFormat(curDef, network_driver->xmlopt, flags);
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static char *
|
|
networkGetBridgeName(virNetworkPtr net)
|
|
{
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
char *bridge = NULL;
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
return bridge;
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkGetBridgeNameEnsureACL(net->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (!(def->bridge)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' does not have a bridge name."),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
ignore_value(VIR_STRDUP(bridge, def->bridge));
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return bridge;
|
|
}
|
|
|
|
|
|
static int
|
|
networkGetAutostart(virNetworkPtr net,
|
|
int *autostart)
|
|
{
|
|
virNetworkObjPtr obj;
|
|
int ret = -1;
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
return ret;
|
|
|
|
if (virNetworkGetAutostartEnsureACL(net->conn, virNetworkObjGetDef(obj)) < 0)
|
|
goto cleanup;
|
|
|
|
*autostart = virNetworkObjIsAutostart(obj) ? 1 : 0;
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkSetAutostart(virNetworkPtr net,
|
|
int autostart)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
char *configFile = NULL, *autostartLink = NULL;
|
|
bool new_autostart;
|
|
bool cur_autostart;
|
|
int ret = -1;
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
goto cleanup;
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkSetAutostartEnsureACL(net->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (!virNetworkObjIsPersistent(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
"%s", _("cannot set autostart for transient network"));
|
|
goto cleanup;
|
|
}
|
|
|
|
new_autostart = (autostart != 0);
|
|
cur_autostart = virNetworkObjIsAutostart(obj);
|
|
if (cur_autostart != new_autostart) {
|
|
if ((configFile = virNetworkConfigFile(driver->networkConfigDir,
|
|
def->name)) == NULL)
|
|
goto cleanup;
|
|
if ((autostartLink = virNetworkConfigFile(driver->networkAutostartDir,
|
|
def->name)) == NULL)
|
|
goto cleanup;
|
|
|
|
if (new_autostart) {
|
|
if (virFileMakePath(driver->networkAutostartDir) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot create autostart directory '%s'"),
|
|
driver->networkAutostartDir);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (symlink(configFile, autostartLink) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Failed to create symlink '%s' to '%s'"),
|
|
autostartLink, configFile);
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
if (unlink(autostartLink) < 0 && errno != ENOENT && errno != ENOTDIR) {
|
|
virReportSystemError(errno,
|
|
_("Failed to delete symlink '%s'"),
|
|
autostartLink);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
virNetworkObjSetAutostart(obj, new_autostart);
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
VIR_FREE(configFile);
|
|
VIR_FREE(autostartLink);
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkGetDHCPLeases(virNetworkPtr net,
|
|
const char *mac,
|
|
virNetworkDHCPLeasePtr **leases,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
size_t i, j;
|
|
size_t nleases = 0;
|
|
int rv = -1;
|
|
size_t size = 0;
|
|
int custom_lease_file_len = 0;
|
|
bool need_results = !!leases;
|
|
long long currtime = 0;
|
|
long long expirytime_tmp = -1;
|
|
bool ipv6 = false;
|
|
char *lease_entries = NULL;
|
|
char *custom_lease_file = NULL;
|
|
const char *ip_tmp = NULL;
|
|
const char *mac_tmp = NULL;
|
|
virJSONValuePtr lease_tmp = NULL;
|
|
virJSONValuePtr leases_array = NULL;
|
|
virNetworkIPDefPtr ipdef_tmp = NULL;
|
|
virNetworkDHCPLeasePtr lease = NULL;
|
|
virNetworkDHCPLeasePtr *leases_ret = NULL;
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
virMacAddr mac_addr;
|
|
|
|
virCheckFlags(0, -1);
|
|
|
|
/* only to check if the MAC is valid */
|
|
if (mac && virMacAddrParse(mac, &mac_addr) < 0) {
|
|
virReportError(VIR_ERR_INVALID_MAC, "%s", mac);
|
|
return -1;
|
|
}
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
return -1;
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkGetDHCPLeasesEnsureACL(net->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
/* Retrieve custom leases file location */
|
|
custom_lease_file = networkDnsmasqLeaseFileNameCustom(driver, def->bridge);
|
|
|
|
/* Read entire contents */
|
|
if ((custom_lease_file_len = virFileReadAllQuiet(custom_lease_file,
|
|
VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX,
|
|
&lease_entries)) < 0) {
|
|
/* Not all networks are guaranteed to have leases file.
|
|
* Only those which run dnsmasq. Therefore, if we failed
|
|
* to read the leases file, don't report error. Return 0
|
|
* leases instead. */
|
|
if (errno == ENOENT) {
|
|
rv = 0;
|
|
} else {
|
|
virReportSystemError(errno,
|
|
_("Unable to read leases file: %s"),
|
|
custom_lease_file);
|
|
}
|
|
goto error;
|
|
}
|
|
|
|
if (custom_lease_file_len) {
|
|
if (!(leases_array = virJSONValueFromString(lease_entries))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("invalid json in file: %s"), custom_lease_file);
|
|
goto error;
|
|
}
|
|
|
|
if (!virJSONValueIsArray(leases_array)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Malformed lease_entries array"));
|
|
goto error;
|
|
}
|
|
size = virJSONValueArraySize(leases_array);
|
|
}
|
|
|
|
currtime = (long long)time(NULL);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (!(lease_tmp = virJSONValueArrayGet(leases_array, i))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to parse json"));
|
|
goto error;
|
|
}
|
|
|
|
if (!(mac_tmp = virJSONValueObjectGetString(lease_tmp, "mac-address"))) {
|
|
/* leaseshelper program guarantees that lease will be stored only if
|
|
* mac-address is known otherwise not */
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("found lease without mac-address"));
|
|
goto error;
|
|
}
|
|
|
|
if (mac && virMacAddrCompare(mac, mac_tmp))
|
|
continue;
|
|
|
|
if (virJSONValueObjectGetNumberLong(lease_tmp, "expiry-time", &expirytime_tmp) < 0) {
|
|
/* A lease cannot be present without expiry-time */
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("found lease without expiry-time"));
|
|
goto error;
|
|
}
|
|
|
|
/* Do not report expired lease */
|
|
if (expirytime_tmp < currtime)
|
|
continue;
|
|
|
|
if (need_results) {
|
|
if (VIR_ALLOC(lease) < 0)
|
|
goto error;
|
|
|
|
lease->expirytime = expirytime_tmp;
|
|
|
|
if (!(ip_tmp = virJSONValueObjectGetString(lease_tmp, "ip-address"))) {
|
|
/* A lease without ip-address makes no sense */
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("found lease without ip-address"));
|
|
goto error;
|
|
}
|
|
|
|
/* Unlike IPv4, IPv6 uses ':' instead of '.' as separator */
|
|
ipv6 = strchr(ip_tmp, ':') ? true : false;
|
|
lease->type = ipv6 ? VIR_IP_ADDR_TYPE_IPV6 : VIR_IP_ADDR_TYPE_IPV4;
|
|
|
|
/* Obtain prefix */
|
|
for (j = 0; j < def->nips; j++) {
|
|
ipdef_tmp = &def->ips[j];
|
|
|
|
if (ipv6 && VIR_SOCKET_ADDR_IS_FAMILY(&ipdef_tmp->address,
|
|
AF_INET6)) {
|
|
lease->prefix = ipdef_tmp->prefix;
|
|
break;
|
|
}
|
|
if (!ipv6 && VIR_SOCKET_ADDR_IS_FAMILY(&ipdef_tmp->address,
|
|
AF_INET)) {
|
|
lease->prefix = virSocketAddrGetIPPrefix(&ipdef_tmp->address,
|
|
&ipdef_tmp->netmask,
|
|
ipdef_tmp->prefix);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((VIR_STRDUP(lease->mac, mac_tmp) < 0) ||
|
|
(VIR_STRDUP(lease->ipaddr, ip_tmp) < 0) ||
|
|
(VIR_STRDUP(lease->iface, def->bridge) < 0))
|
|
goto error;
|
|
|
|
/* Fields that can be NULL */
|
|
if ((VIR_STRDUP(lease->iaid,
|
|
virJSONValueObjectGetString(lease_tmp, "iaid")) < 0) ||
|
|
(VIR_STRDUP(lease->clientid,
|
|
virJSONValueObjectGetString(lease_tmp, "client-id")) < 0) ||
|
|
(VIR_STRDUP(lease->hostname,
|
|
virJSONValueObjectGetString(lease_tmp, "hostname")) < 0))
|
|
goto error;
|
|
|
|
if (VIR_INSERT_ELEMENT(leases_ret, nleases, nleases, lease) < 0)
|
|
goto error;
|
|
|
|
} else {
|
|
nleases++;
|
|
}
|
|
|
|
VIR_FREE(lease);
|
|
}
|
|
|
|
if (leases_ret) {
|
|
/* NULL terminated array */
|
|
ignore_value(VIR_REALLOC_N(leases_ret, nleases + 1));
|
|
*leases = leases_ret;
|
|
leases_ret = NULL;
|
|
}
|
|
|
|
rv = nleases;
|
|
|
|
cleanup:
|
|
VIR_FREE(lease);
|
|
VIR_FREE(lease_entries);
|
|
VIR_FREE(custom_lease_file);
|
|
virJSONValueFree(leases_array);
|
|
|
|
virNetworkObjEndAPI(&obj);
|
|
|
|
return rv;
|
|
|
|
error:
|
|
if (leases_ret) {
|
|
for (i = 0; i < nleases; i++)
|
|
virNetworkDHCPLeaseFree(leases_ret[i]);
|
|
VIR_FREE(leases_ret);
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
/* A unified function to log network connections and disconnections */
|
|
|
|
static void
|
|
networkLogAllocation(virNetworkDefPtr netdef,
|
|
virNetworkForwardIfDefPtr dev,
|
|
virMacAddrPtr mac,
|
|
bool inUse)
|
|
{
|
|
char macStr[VIR_MAC_STRING_BUFLEN];
|
|
const char *verb = inUse ? "using" : "releasing";
|
|
|
|
virMacAddrFormat(mac, macStr);
|
|
if (!dev) {
|
|
VIR_INFO("MAC %s %s network %s (%d connections)",
|
|
macStr, verb, netdef->name, netdef->connections);
|
|
} else {
|
|
if (dev->type == VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_PCI) {
|
|
VIR_INFO("MAC %s %s network %s (%d connections) "
|
|
"physical device %04x:%02x:%02x.%x (%d connections)",
|
|
macStr, verb, netdef->name, netdef->connections,
|
|
dev->device.pci.domain, dev->device.pci.bus,
|
|
dev->device.pci.slot, dev->device.pci.function,
|
|
dev->connections);
|
|
} else {
|
|
VIR_INFO("MAC %s %s network %s (%d connections) "
|
|
"physical device %s (%d connections)",
|
|
macStr, verb, netdef->name, netdef->connections,
|
|
dev->device.dev, dev->connections);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Private API to deal with logical switch capabilities.
|
|
* These functions are exported so that other parts of libvirt can
|
|
* call them, but are not part of the public API and not in the
|
|
* driver's function table. If we ever have more than one network
|
|
* driver, we will need to present these functions via a second
|
|
* "backend" function table.
|
|
*/
|
|
|
|
/* networkAllocatePort:
|
|
* @obj: the network to allocate from
|
|
* @port: the port definition to allocate
|
|
*
|
|
* Looks up the network reference by port, allocates a physical
|
|
* device from that network (if appropriate), and returns with the
|
|
* port configuration filled in accordingly.
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
networkAllocatePort(virNetworkObjPtr obj,
|
|
virNetworkPortDefPtr port)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkDefPtr netdef = NULL;
|
|
virPortGroupDefPtr portgroup = NULL;
|
|
virNetworkForwardIfDefPtr dev = NULL;
|
|
size_t i;
|
|
int ret = -1;
|
|
virNetDevVPortProfilePtr portprofile = NULL;
|
|
|
|
netdef = virNetworkObjGetDef(obj);
|
|
VIR_DEBUG("Allocating port from net %s", netdef->name);
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
VIR_DEBUG("Interface port group %s", port->group);
|
|
/* portgroup can be present for any type of network, in particular
|
|
* for bandwidth information, so we need to check for that and
|
|
* fill it in appropriately for all forward types.
|
|
*/
|
|
portgroup = virPortGroupFindByName(netdef, port->group);
|
|
|
|
if (!port->bandwidth) {
|
|
if (portgroup && portgroup->bandwidth &&
|
|
virNetDevBandwidthCopy(&port->bandwidth,
|
|
portgroup->bandwidth) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (port->vlan.nTags == 0) {
|
|
virNetDevVlanPtr vlan = NULL;
|
|
if (portgroup && portgroup->vlan.nTags > 0)
|
|
vlan = &portgroup->vlan;
|
|
else if (netdef->vlan.nTags > 0)
|
|
vlan = &netdef->vlan;
|
|
|
|
if (vlan && virNetDevVlanCopy(&port->vlan, vlan) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!port->trustGuestRxFilters) {
|
|
if (portgroup && portgroup->trustGuestRxFilters)
|
|
port->trustGuestRxFilters = portgroup->trustGuestRxFilters;
|
|
else if (netdef->trustGuestRxFilters)
|
|
port->trustGuestRxFilters = netdef->trustGuestRxFilters;
|
|
}
|
|
|
|
/* merge virtualports from interface, network, and portgroup to
|
|
* arrive at actual virtualport to use
|
|
*/
|
|
if (virNetDevVPortProfileMerge3(&portprofile,
|
|
port->virtPortProfile,
|
|
netdef->virtPortProfile,
|
|
portgroup
|
|
? portgroup->virtPortProfile : NULL) < 0) {
|
|
goto cleanup;
|
|
}
|
|
if (portprofile) {
|
|
VIR_FREE(port->virtPortProfile);
|
|
port->virtPortProfile = portprofile;
|
|
}
|
|
|
|
VIR_DEBUG("Processing forward type %d", netdef->forward.type);
|
|
switch ((virNetworkForwardType) netdef->forward.type) {
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
/* for these forward types, the actual net type really *is*
|
|
* NETWORK; we just keep the info from the portgroup in
|
|
* iface->data.network.actual
|
|
*/
|
|
port->plugtype = VIR_NETWORK_PORT_PLUG_TYPE_NETWORK;
|
|
|
|
if (VIR_STRDUP(port->plug.bridge.brname, netdef->bridge) < 0)
|
|
goto cleanup;
|
|
port->plug.bridge.macTableManager = netdef->macTableManager;
|
|
|
|
if (port->virtPortProfile) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("<virtualport type='%s'> not supported for network "
|
|
"'%s' which uses IP forwarding"),
|
|
virNetDevVPortTypeToString(port->virtPortProfile->virtPortType),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (networkPlugBandwidth(obj, &port->mac, port->bandwidth, &port->class_id) < 0)
|
|
goto cleanup;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_HOSTDEV: {
|
|
port->plugtype = VIR_NETWORK_PORT_PLUG_TYPE_HOSTDEV_PCI;
|
|
|
|
if (networkCreateInterfacePool(netdef) < 0)
|
|
goto cleanup;
|
|
|
|
/* pick first dev with 0 connections */
|
|
for (i = 0; i < netdef->forward.nifs; i++) {
|
|
if (netdef->forward.ifs[i].connections == 0) {
|
|
dev = &netdef->forward.ifs[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!dev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' requires exclusive access "
|
|
"to interfaces, but none are available"),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
port->plug.hostdevpci.addr = dev->device.pci;
|
|
port->plug.hostdevpci.driver = netdef->forward.driverName;
|
|
port->plug.hostdevpci.managed = netdef->forward.managed;
|
|
|
|
if (port->virtPortProfile) {
|
|
/* make sure type is supported for hostdev connections */
|
|
if (port->virtPortProfile->virtPortType != VIR_NETDEV_VPORT_PROFILE_8021QBG &&
|
|
port->virtPortProfile->virtPortType != VIR_NETDEV_VPORT_PROFILE_8021QBH) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("<virtualport type='%s'> not supported for network "
|
|
"'%s' which uses an SR-IOV Virtual Function "
|
|
"via PCI passthrough"),
|
|
virNetDevVPortTypeToString(port->virtPortProfile->virtPortType),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
if (netdef->bridge) {
|
|
/* <forward type='bridge'/> <bridge name='xxx'/>
|
|
* is VIR_DOMAIN_NET_TYPE_BRIDGE
|
|
*/
|
|
|
|
port->plugtype = VIR_NETWORK_PORT_PLUG_TYPE_BRIDGE;
|
|
if (VIR_STRDUP(port->plug.bridge.brname, netdef->bridge) < 0)
|
|
goto cleanup;
|
|
port->plug.bridge.macTableManager = netdef->macTableManager;
|
|
|
|
if (port->virtPortProfile) {
|
|
/* only type='openvswitch' is allowed for bridges */
|
|
if (port->virtPortProfile->virtPortType != VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("<virtualport type='%s'> not supported for network "
|
|
"'%s' which uses a bridge device"),
|
|
virNetDevVPortTypeToString(port->virtPortProfile->virtPortType),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (networkPlugBandwidth(obj, &port->mac, port->bandwidth, &port->class_id) < 0)
|
|
goto cleanup;
|
|
break;
|
|
}
|
|
|
|
/* intentionally fall through to the direct case for
|
|
* VIR_NETWORK_FORWARD_BRIDGE with no bridge device defined
|
|
*/
|
|
ATTRIBUTE_FALLTHROUGH;
|
|
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
/* <forward type='bridge|private|vepa|passthrough'> are all
|
|
* VIR_DOMAIN_NET_TYPE_DIRECT.
|
|
*/
|
|
|
|
/* Set type=direct and appropriate <source mode='xxx'/> */
|
|
port->plugtype = VIR_NETWORK_PORT_PLUG_TYPE_DIRECT;
|
|
|
|
/* NO need to check the value returned from virNetDevMacVLanModeTypeFromString
|
|
* it must be valid for these forward type(bridge|private|vepa|passthrough)
|
|
*/
|
|
port->plug.direct.mode =
|
|
virNetDevMacVLanModeTypeFromString(virNetworkForwardTypeToString(netdef->forward.type));
|
|
|
|
if (port->virtPortProfile) {
|
|
/* make sure type is supported for macvtap connections */
|
|
if (port->virtPortProfile->virtPortType != VIR_NETDEV_VPORT_PROFILE_8021QBG &&
|
|
port->virtPortProfile->virtPortType != VIR_NETDEV_VPORT_PROFILE_8021QBH) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("<virtualport type='%s'> not supported for network "
|
|
"'%s' which uses a macvtap device"),
|
|
virNetDevVPortTypeToString(port->virtPortProfile->virtPortType),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* If there is only a single device, just return it (caller will detect
|
|
* any error if exclusive use is required but could not be acquired).
|
|
*/
|
|
if ((netdef->forward.nifs <= 0) && (netdef->forward.npfs <= 0)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' uses a direct mode, but "
|
|
"has no forward dev and no interface pool"),
|
|
netdef->name);
|
|
goto cleanup;
|
|
} else {
|
|
/* pick an interface from the pool */
|
|
|
|
if (networkCreateInterfacePool(netdef) < 0)
|
|
goto cleanup;
|
|
|
|
/* PASSTHROUGH mode, and PRIVATE Mode + 802.1Qbh both
|
|
* require exclusive access to a device, so current
|
|
* connections count must be 0. Other modes can share, so
|
|
* just search for the one with the lowest number of
|
|
* connections.
|
|
*/
|
|
if ((netdef->forward.type == VIR_NETWORK_FORWARD_PASSTHROUGH) ||
|
|
((netdef->forward.type == VIR_NETWORK_FORWARD_PRIVATE) &&
|
|
port->virtPortProfile &&
|
|
(port->virtPortProfile->virtPortType
|
|
== VIR_NETDEV_VPORT_PROFILE_8021QBH))) {
|
|
|
|
/* pick first dev with 0 connections */
|
|
for (i = 0; i < netdef->forward.nifs; i++) {
|
|
if (netdef->forward.ifs[i].connections == 0) {
|
|
dev = &netdef->forward.ifs[i];
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* pick least used dev */
|
|
dev = &netdef->forward.ifs[0];
|
|
for (i = 1; i < netdef->forward.nifs; i++) {
|
|
if (netdef->forward.ifs[i].connections < dev->connections)
|
|
dev = &netdef->forward.ifs[i];
|
|
}
|
|
}
|
|
/* dev points at the physical device we want to use */
|
|
if (!dev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' requires exclusive access "
|
|
"to interfaces, but none are available"),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
if (VIR_STRDUP(port->plug.direct.linkdev,
|
|
dev->device.dev) < 0)
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, netdef->forward.type);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virNetworkObjMacMgrAdd(obj, driver->dnsmasqStateDir,
|
|
port->ownername, &port->mac) < 0)
|
|
goto cleanup;
|
|
|
|
if (virNetDevVPortProfileCheckComplete(port->virtPortProfile, true) < 0)
|
|
goto cleanup;
|
|
|
|
/* make sure that everything now specified for the device is
|
|
* actually supported on this type of network. NB: network,
|
|
* netdev, and iface->data.network.actual may all be NULL.
|
|
*/
|
|
VIR_DEBUG("Sanity check port config");
|
|
|
|
if (port->vlan.nTags) {
|
|
/* vlan configuration via libvirt is only supported for PCI
|
|
* Passthrough SR-IOV devices (hostdev or macvtap passthru
|
|
* mode) and openvswitch bridges. Otherwise log an error and
|
|
* fail
|
|
*/
|
|
if (!(port->plugtype == VIR_NETWORK_PORT_PLUG_TYPE_HOSTDEV_PCI ||
|
|
(port->plugtype == VIR_NETWORK_PORT_PLUG_TYPE_DIRECT &&
|
|
port->plug.direct.mode == VIR_NETDEV_MACVLAN_MODE_PASSTHRU) ||
|
|
(port->plugtype == VIR_NETWORK_PORT_PLUG_TYPE_BRIDGE &&
|
|
port->virtPortProfile &&
|
|
port->virtPortProfile->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("an interface connecting to network '%s' "
|
|
"is requesting a vlan tag, but that is not "
|
|
"supported for this type of network"),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* bandwidth configuration via libvirt is not supported for
|
|
* hostdev network devices
|
|
*/
|
|
if (port->bandwidth && port->plugtype == VIR_NETWORK_PORT_PLUG_TYPE_HOSTDEV_PCI) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("bandwidth settings are not supported "
|
|
"for hostdev interfaces"));
|
|
goto cleanup;
|
|
}
|
|
|
|
netdef->connections++;
|
|
if (dev)
|
|
dev->connections++;
|
|
/* finally we can call the 'plugged' hook script if any */
|
|
if (networkRunHook(obj, port,
|
|
VIR_HOOK_NETWORK_OP_PORT_CREATED,
|
|
VIR_HOOK_SUBOP_BEGIN) < 0) {
|
|
/* adjust for failure */
|
|
netdef->connections--;
|
|
if (dev)
|
|
dev->connections--;
|
|
goto cleanup;
|
|
}
|
|
networkLogAllocation(netdef, dev, &port->mac, true);
|
|
|
|
VIR_DEBUG("Port allocated");
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* networkNotifyPort:
|
|
* @obj: the network to notify
|
|
* @port: the port definition to notify
|
|
*
|
|
* Called to notify the network driver when libvirtd is restarted and
|
|
* finds an already running domain. If appropriate it will force an
|
|
* allocation of the actual->direct.linkdev to get everything back in
|
|
* order.
|
|
*/
|
|
static int
|
|
networkNotifyPort(virNetworkObjPtr obj,
|
|
virNetworkPortDefPtr port)
|
|
{
|
|
virNetworkDefPtr netdef;
|
|
virNetworkForwardIfDefPtr dev = NULL;
|
|
size_t i;
|
|
int ret = -1;
|
|
|
|
netdef = virNetworkObjGetDef(obj);
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
switch (port->plugtype) {
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_NONE:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Unexpectedly got a network port without a plug"));
|
|
goto cleanup;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_NETWORK:
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_BRIDGE:
|
|
/* see if we're connected to the correct bridge */
|
|
if (!netdef->bridge) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Unexpectedly got a network port without a network bridge"));
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_DIRECT:
|
|
if (networkCreateInterfacePool(netdef) < 0)
|
|
goto cleanup;
|
|
|
|
/* find the matching interface and increment its connections */
|
|
for (i = 0; i < netdef->forward.nifs; i++) {
|
|
if (netdef->forward.ifs[i].type
|
|
== VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV &&
|
|
STREQ(port->plug.direct.linkdev,
|
|
netdef->forward.ifs[i].device.dev)) {
|
|
dev = &netdef->forward.ifs[i];
|
|
break;
|
|
}
|
|
}
|
|
/* dev points at the physical device we want to use */
|
|
if (!dev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' doesn't have dev='%s' "
|
|
"in use by network port '%s'"),
|
|
netdef->name, port->plug.direct.linkdev,
|
|
port->uuid);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* PASSTHROUGH mode and PRIVATE Mode + 802.1Qbh both require
|
|
* exclusive access to a device, so current connections count
|
|
* must be 0 in those cases.
|
|
*/
|
|
if ((dev->connections > 0) &&
|
|
((netdef->forward.type == VIR_NETWORK_FORWARD_PASSTHROUGH) ||
|
|
((netdef->forward.type == VIR_NETWORK_FORWARD_PRIVATE) &&
|
|
port->virtPortProfile &&
|
|
(port->virtPortProfile->virtPortType == VIR_NETDEV_VPORT_PROFILE_8021QBH)))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' claims dev='%s' is already in "
|
|
"use by a different port"),
|
|
netdef->name, port->plug.direct.linkdev);
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_HOSTDEV_PCI:
|
|
|
|
if (networkCreateInterfacePool(netdef) < 0)
|
|
goto cleanup;
|
|
|
|
/* find the matching interface and increment its connections */
|
|
for (i = 0; i < netdef->forward.nifs; i++) {
|
|
if (netdef->forward.ifs[i].type
|
|
== VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_PCI &&
|
|
virPCIDeviceAddressEqual(&port->plug.hostdevpci.addr,
|
|
&netdef->forward.ifs[i].device.pci)) {
|
|
dev = &netdef->forward.ifs[i];
|
|
break;
|
|
}
|
|
}
|
|
/* dev points at the physical device we want to use */
|
|
if (!dev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' doesn't have "
|
|
"PCI device %04x:%02x:%02x.%x in use by network port"),
|
|
netdef->name,
|
|
port->plug.hostdevpci.addr.domain,
|
|
port->plug.hostdevpci.addr.bus,
|
|
port->plug.hostdevpci.addr.slot,
|
|
port->plug.hostdevpci.addr.function);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* PASSTHROUGH mode, PRIVATE Mode + 802.1Qbh, and hostdev (PCI
|
|
* passthrough) all require exclusive access to a device, so
|
|
* current connections count must be 0 in those cases.
|
|
*/
|
|
if ((dev->connections > 0) &&
|
|
netdef->forward.type == VIR_NETWORK_FORWARD_HOSTDEV) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' claims the PCI device at "
|
|
"domain=%d bus=%d slot=%d function=%d "
|
|
"is already in use by a different network port"),
|
|
netdef->name,
|
|
dev->device.pci.domain, dev->device.pci.bus,
|
|
dev->device.pci.slot, dev->device.pci.function);
|
|
goto cleanup;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkPortPlugType, port->plugtype);
|
|
goto cleanup;
|
|
}
|
|
|
|
netdef->connections++;
|
|
if (dev)
|
|
dev->connections++;
|
|
/* finally we can call the 'plugged' hook script if any */
|
|
if (networkRunHook(obj, port, VIR_HOOK_NETWORK_OP_PORT_CREATED,
|
|
VIR_HOOK_SUBOP_BEGIN) < 0) {
|
|
/* adjust for failure */
|
|
if (dev)
|
|
dev->connections--;
|
|
netdef->connections--;
|
|
goto cleanup;
|
|
}
|
|
networkLogAllocation(netdef, dev, &port->mac, true);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* networkReleasePort:
|
|
* @obj: the network to release from
|
|
* @port: the port definition to release
|
|
*
|
|
* Given a domain <interface> element that previously had its <actual>
|
|
* element filled in (and possibly a physical device allocated to it),
|
|
* free up the physical device for use by someone else, and free the
|
|
* virDomainActualNetDef.
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
networkReleasePort(virNetworkObjPtr obj,
|
|
virNetworkPortDefPtr port)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkDefPtr netdef;
|
|
virNetworkForwardIfDefPtr dev = NULL;
|
|
size_t i;
|
|
int ret = -1;
|
|
|
|
netdef = virNetworkObjGetDef(obj);
|
|
|
|
switch ((virNetworkPortPlugType)port->plugtype) {
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_NONE:
|
|
VIR_DEBUG("Releasing network device with no plug type");
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_NETWORK:
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_BRIDGE:
|
|
if (networkUnplugBandwidth(obj, port->bandwidth,
|
|
&port->class_id) < 0)
|
|
goto cleanup;
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_DIRECT:
|
|
if (netdef->forward.nifs == 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' uses a direct mode, but "
|
|
"has no forward dev and no interface pool"),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
for (i = 0; i < netdef->forward.nifs; i++) {
|
|
if (netdef->forward.ifs[i].type
|
|
== VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV &&
|
|
STREQ(port->plug.direct.linkdev, netdef->forward.ifs[i].device.dev)) {
|
|
dev = &netdef->forward.ifs[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' doesn't have dev='%s' "
|
|
"in use by domain"),
|
|
netdef->name, port->plug.direct.linkdev);
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_HOSTDEV_PCI:
|
|
if (netdef->forward.nifs == 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' uses a hostdev mode, but "
|
|
"has no forward dev and no interface pool"),
|
|
netdef->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
for (i = 0; i < netdef->forward.nifs; i++) {
|
|
if (netdef->forward.ifs[i].type
|
|
== VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_PCI &&
|
|
virPCIDeviceAddressEqual(&port->plug.hostdevpci.addr,
|
|
&netdef->forward.ifs[i].device.pci)) {
|
|
dev = &netdef->forward.ifs[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network '%s' doesn't have "
|
|
"PCI device %04x:%02x:%02x.%x in use by domain"),
|
|
netdef->name,
|
|
port->plug.hostdevpci.addr.domain,
|
|
port->plug.hostdevpci.addr.bus,
|
|
port->plug.hostdevpci.addr.slot,
|
|
port->plug.hostdevpci.addr.function);
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkPortPlugType, port->plugtype);
|
|
goto cleanup;
|
|
}
|
|
|
|
virNetworkObjMacMgrDel(obj, driver->dnsmasqStateDir, port->ownername, &port->mac);
|
|
|
|
netdef->connections--;
|
|
if (dev)
|
|
dev->connections--;
|
|
/* finally we can call the 'unplugged' hook script if any */
|
|
networkRunHook(obj, port, VIR_HOOK_NETWORK_OP_PORT_DELETED,
|
|
VIR_HOOK_SUBOP_BEGIN);
|
|
networkLogAllocation(netdef, dev, &port->mac, false);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* networkCheckBandwidth:
|
|
* @net: network QoS
|
|
* @ifaceBand: interface QoS (may be NULL if no QoS)
|
|
* @oldBandwidth: new interface QoS (may be NULL if no QoS)
|
|
* @ifaceMac: interface MAC (used in error messages for identification)
|
|
* @new_rate: new rate for non guaranteed class
|
|
*
|
|
* Function checks if @ifaceBand can be satisfied on @net. However, sometimes it
|
|
* may happen that the interface that @ifaceBand corresponds to is already
|
|
* plugged into the @net and the bandwidth is to be updated. In that case we
|
|
* need to check if new bandwidth can be satisfied. If that's the case
|
|
* @ifaceBand should point to new bandwidth settings and @oldBandwidth to
|
|
* current ones. If you want to suppress this functionality just pass
|
|
* @oldBandwidth == NULL.
|
|
*
|
|
* Returns: -1 if plugging would overcommit network QoS
|
|
* 0 if plugging is safe (@new_rate updated)
|
|
* 1 if no QoS is set (@new_rate untouched)
|
|
*/
|
|
static int
|
|
networkCheckBandwidth(virNetworkObjPtr obj,
|
|
virNetDevBandwidthPtr ifaceBand,
|
|
virNetDevBandwidthPtr oldBandwidth,
|
|
virMacAddrPtr ifaceMac,
|
|
unsigned long long *new_rate)
|
|
{
|
|
int ret = -1;
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
virNetDevBandwidthPtr netBand = def->bandwidth;
|
|
unsigned long long tmp_floor_sum = virNetworkObjGetFloorSum(obj);
|
|
unsigned long long tmp_new_rate = 0;
|
|
char ifmac[VIR_MAC_STRING_BUFLEN];
|
|
|
|
virMacAddrFormat(ifaceMac, ifmac);
|
|
|
|
if (ifaceBand && ifaceBand->in && ifaceBand->in->floor &&
|
|
!(netBand && netBand->in)) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("Invalid use of 'floor' on interface with MAC "
|
|
"address %s - network '%s' has no inbound QoS set"),
|
|
ifmac, def->name);
|
|
return -1;
|
|
}
|
|
|
|
if (((!ifaceBand || !ifaceBand->in || !ifaceBand->in->floor) &&
|
|
(!oldBandwidth || !oldBandwidth->in || !oldBandwidth->in->floor)) ||
|
|
!netBand || !netBand->in) {
|
|
/* no QoS required, claim success */
|
|
return 1;
|
|
}
|
|
|
|
tmp_new_rate = netBand->in->average;
|
|
if (oldBandwidth && oldBandwidth->in)
|
|
tmp_floor_sum -= oldBandwidth->in->floor;
|
|
if (ifaceBand && ifaceBand->in)
|
|
tmp_floor_sum += ifaceBand->in->floor;
|
|
|
|
/* check against peak */
|
|
if (netBand->in->peak) {
|
|
tmp_new_rate = netBand->in->peak;
|
|
if (tmp_floor_sum > netBand->in->peak) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("Cannot plug '%s' interface into '%s' because "
|
|
"new combined inbound floor=%llu would overcommit "
|
|
"peak=%llu on network '%s'"),
|
|
ifmac,
|
|
def->bridge,
|
|
tmp_floor_sum,
|
|
netBand->in->peak,
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
} else if (tmp_floor_sum > netBand->in->average) {
|
|
/* tmp_floor_sum can be between 'average' and 'peak' iff 'peak' is set.
|
|
* Otherwise, tmp_floor_sum must be below 'average'. */
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("Cannot plug '%s' interface into '%s' because "
|
|
"new combined inbound floor=%llu would overcommit "
|
|
"average=%llu on network '%s'"),
|
|
ifmac,
|
|
def->bridge,
|
|
tmp_floor_sum,
|
|
netBand->in->average,
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (new_rate)
|
|
*new_rate = tmp_new_rate;
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* networkNextClassID:
|
|
* @net: network object
|
|
*
|
|
* Find next free class ID. @net is supposed
|
|
* to be locked already. If there is a free ID,
|
|
* it is marked as used and returned.
|
|
*
|
|
* Returns next free class ID or -1 if none is available.
|
|
*/
|
|
static ssize_t
|
|
networkNextClassID(virNetworkObjPtr obj)
|
|
{
|
|
ssize_t ret = 0;
|
|
virBitmapPtr classIdMap = virNetworkObjGetClassIdMap(obj);
|
|
|
|
if ((ret = virBitmapNextClearBit(classIdMap, -1)) < 0)
|
|
ret = virBitmapSize(classIdMap);
|
|
|
|
if (virBitmapSetBitExpand(classIdMap, ret) < 0)
|
|
return -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkPlugBandwidthImpl(virNetworkObjPtr obj,
|
|
virMacAddrPtr mac,
|
|
virNetDevBandwidthPtr ifaceBand,
|
|
unsigned int *class_id,
|
|
unsigned long long new_rate)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
virBitmapPtr classIdMap = virNetworkObjGetClassIdMap(obj);
|
|
unsigned long long tmp_floor_sum = virNetworkObjGetFloorSum(obj);
|
|
ssize_t next_id = 0;
|
|
int plug_ret;
|
|
int ret = -1;
|
|
|
|
/* generate new class_id */
|
|
if ((next_id = networkNextClassID(obj)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Could not generate next class ID"));
|
|
goto cleanup;
|
|
}
|
|
|
|
plug_ret = virNetDevBandwidthPlug(def->bridge, def->bandwidth,
|
|
mac, ifaceBand, next_id);
|
|
if (plug_ret < 0) {
|
|
ignore_value(virNetDevBandwidthUnplug(def->bridge, next_id));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* QoS was set, generate new class ID */
|
|
*class_id = next_id;
|
|
/* update sum of 'floor'-s of attached NICs */
|
|
tmp_floor_sum += ifaceBand->in->floor;
|
|
virNetworkObjSetFloorSum(obj, tmp_floor_sum);
|
|
/* update status file */
|
|
if (virNetworkObjSaveStatus(driver->stateDir, obj, network_driver->xmlopt) < 0) {
|
|
ignore_value(virBitmapClearBit(classIdMap, next_id));
|
|
tmp_floor_sum -= ifaceBand->in->floor;
|
|
virNetworkObjSetFloorSum(obj, tmp_floor_sum);
|
|
*class_id = 0;
|
|
ignore_value(virNetDevBandwidthUnplug(def->bridge, next_id));
|
|
goto cleanup;
|
|
}
|
|
/* update rate for non guaranteed NICs */
|
|
new_rate -= tmp_floor_sum;
|
|
if (virNetDevBandwidthUpdateRate(def->bridge, 2,
|
|
def->bandwidth, new_rate) < 0)
|
|
VIR_WARN("Unable to update rate for 1:2 class on %s bridge",
|
|
def->bridge);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkPlugBandwidth(virNetworkObjPtr obj,
|
|
virMacAddrPtr mac,
|
|
virNetDevBandwidthPtr ifaceBand,
|
|
unsigned int *class_id)
|
|
{
|
|
int ret = -1;
|
|
int plug_ret;
|
|
unsigned long long new_rate = 0;
|
|
char ifmac[VIR_MAC_STRING_BUFLEN];
|
|
|
|
if ((plug_ret = networkCheckBandwidth(obj, ifaceBand, NULL,
|
|
mac, &new_rate)) < 0) {
|
|
/* helper reported error */
|
|
goto cleanup;
|
|
}
|
|
|
|
if (plug_ret > 0) {
|
|
/* no QoS needs to be set; claim success */
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
virMacAddrFormat(mac, ifmac);
|
|
|
|
if (networkPlugBandwidthImpl(obj, mac, ifaceBand, class_id, new_rate) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkUnplugBandwidth(virNetworkObjPtr obj,
|
|
virNetDevBandwidthPtr ifaceBand,
|
|
unsigned int *class_id)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
virBitmapPtr classIdMap = virNetworkObjGetClassIdMap(obj);
|
|
unsigned long long tmp_floor_sum = virNetworkObjGetFloorSum(obj);
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
int ret = 0;
|
|
unsigned long long new_rate;
|
|
|
|
if (class_id && *class_id) {
|
|
if (!def->bandwidth || !def->bandwidth->in) {
|
|
VIR_WARN("Network %s has no bandwidth but unplug requested",
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
/* we must remove class from bridge */
|
|
new_rate = def->bandwidth->in->average;
|
|
|
|
if (def->bandwidth->in->peak > 0)
|
|
new_rate = def->bandwidth->in->peak;
|
|
|
|
ret = virNetDevBandwidthUnplug(def->bridge, *class_id);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
/* update sum of 'floor'-s of attached NICs */
|
|
tmp_floor_sum -= ifaceBand->in->floor;
|
|
virNetworkObjSetFloorSum(obj, tmp_floor_sum);
|
|
|
|
/* return class ID */
|
|
ignore_value(virBitmapClearBit(classIdMap, *class_id));
|
|
/* update status file */
|
|
if (virNetworkObjSaveStatus(driver->stateDir,
|
|
obj, network_driver->xmlopt) < 0) {
|
|
tmp_floor_sum += ifaceBand->in->floor;
|
|
virNetworkObjSetFloorSum(obj, tmp_floor_sum);
|
|
ignore_value(virBitmapSetBit(classIdMap, *class_id));
|
|
goto cleanup;
|
|
}
|
|
/* update rate for non guaranteed NICs */
|
|
new_rate -= tmp_floor_sum;
|
|
if (virNetDevBandwidthUpdateRate(def->bridge, 2,
|
|
def->bandwidth, new_rate) < 0)
|
|
VIR_WARN("Unable to update rate for 1:2 class on %s bridge",
|
|
def->bridge);
|
|
/* no class is associated any longer */
|
|
*class_id = 0;
|
|
}
|
|
|
|
cleanup:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
networkNetworkObjTaint(virNetworkObjPtr obj,
|
|
virNetworkTaintFlags taint)
|
|
{
|
|
virNetworkDefPtr def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkObjTaint(obj, taint)) {
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
virUUIDFormat(def->uuid, uuidstr);
|
|
|
|
VIR_WARN("Network name='%s' uuid=%s is tainted: %s",
|
|
def->name, uuidstr, virNetworkTaintTypeToString(taint));
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
networkUpdatePortBandwidth(virNetworkObjPtr obj,
|
|
virMacAddrPtr mac,
|
|
unsigned int *class_id,
|
|
virNetDevBandwidthPtr oldBandwidth,
|
|
virNetDevBandwidthPtr newBandwidth)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkDefPtr def;
|
|
unsigned long long tmp_floor_sum;
|
|
unsigned long long new_rate = 0;
|
|
unsigned long long old_floor, new_floor;
|
|
int plug_ret;
|
|
|
|
old_floor = new_floor = 0;
|
|
|
|
if (oldBandwidth && oldBandwidth->in)
|
|
old_floor = oldBandwidth->in->floor;
|
|
if (newBandwidth && newBandwidth->in)
|
|
new_floor = newBandwidth->in->floor;
|
|
|
|
if (new_floor == old_floor)
|
|
return 0;
|
|
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if ((plug_ret = networkCheckBandwidth(obj, newBandwidth, oldBandwidth,
|
|
mac, &new_rate)) < 0) {
|
|
/* helper reported error */
|
|
return -1;
|
|
}
|
|
|
|
if (plug_ret > 0) {
|
|
/* no QoS needs to be set; claim success */
|
|
return 0;
|
|
}
|
|
|
|
/* Okay, there are three possible scenarios: */
|
|
|
|
if (oldBandwidth && oldBandwidth->in && oldBandwidth->in->floor &&
|
|
newBandwidth->in && newBandwidth->in->floor) {
|
|
/* Either we just need to update @floor .. */
|
|
|
|
if (virNetDevBandwidthUpdateRate(def->bridge,
|
|
*class_id,
|
|
def->bandwidth,
|
|
newBandwidth->in->floor) < 0)
|
|
return -1;
|
|
|
|
tmp_floor_sum = virNetworkObjGetFloorSum(obj);
|
|
tmp_floor_sum -= oldBandwidth->in->floor;
|
|
tmp_floor_sum += newBandwidth->in->floor;
|
|
virNetworkObjSetFloorSum(obj, tmp_floor_sum);
|
|
new_rate -= tmp_floor_sum;
|
|
|
|
if (virNetDevBandwidthUpdateRate(def->bridge, 2,
|
|
def->bandwidth, new_rate) < 0 ||
|
|
virNetworkObjSaveStatus(driver->stateDir,
|
|
obj, network_driver->xmlopt) < 0) {
|
|
/* Ouch, rollback */
|
|
tmp_floor_sum -= newBandwidth->in->floor;
|
|
tmp_floor_sum += oldBandwidth->in->floor;
|
|
virNetworkObjSetFloorSum(obj, tmp_floor_sum);
|
|
|
|
ignore_value(virNetDevBandwidthUpdateRate(def->bridge,
|
|
*class_id,
|
|
def->bandwidth,
|
|
oldBandwidth->in->floor));
|
|
return -1;
|
|
}
|
|
} else if (newBandwidth->in && newBandwidth->in->floor) {
|
|
/* .. or we need to plug in new .. */
|
|
|
|
if (networkPlugBandwidthImpl(obj, mac, newBandwidth,
|
|
class_id,
|
|
new_rate) < 0)
|
|
return -1;
|
|
} else {
|
|
/* .. or unplug old. */
|
|
|
|
if (networkUnplugBandwidth(obj, oldBandwidth, class_id) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virNetworkPortPtr
|
|
networkPortLookupByUUID(virNetworkPtr net,
|
|
const unsigned char *uuid)
|
|
{
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
virNetworkPortDefPtr portdef = NULL;
|
|
virNetworkPortPtr ret = NULL;
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
virUUIDFormat(uuid, uuidstr);
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
return ret;
|
|
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (!(portdef = virNetworkObjLookupPort(obj, uuid)))
|
|
goto cleanup;
|
|
|
|
if (virNetworkPortLookupByUUIDEnsureACL(net->conn, def, portdef) < 0)
|
|
goto cleanup;
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = virGetNetworkPort(net, uuid);
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virNetworkPortPtr
|
|
networkPortCreateXML(virNetworkPtr net,
|
|
const char *xmldesc,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
virNetworkPortDefPtr portdef = NULL;
|
|
virNetworkPortPtr ret = NULL;
|
|
int rc;
|
|
|
|
virCheckFlags(VIR_NETWORK_PORT_CREATE_RECLAIM, NULL);
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
return ret;
|
|
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (!(portdef = virNetworkPortDefParseString(xmldesc)))
|
|
goto cleanup;
|
|
|
|
if (virNetworkPortCreateXMLEnsureACL(net->conn, def, portdef) < 0)
|
|
goto cleanup;
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (portdef->plugtype == VIR_NETWORK_PORT_PLUG_TYPE_NONE) {
|
|
if (flags & VIR_NETWORK_PORT_CREATE_RECLAIM) {
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
_("Port reclaim requested but plug type is none"));
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
if (!(flags & VIR_NETWORK_PORT_CREATE_RECLAIM)) {
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
_("Port reclaim not requested but plug type is not none"));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (flags & VIR_NETWORK_PORT_CREATE_RECLAIM)
|
|
rc = networkNotifyPort(obj, portdef);
|
|
else
|
|
rc = networkAllocatePort(obj, portdef);
|
|
if (rc < 0) {
|
|
virErrorPtr saved;
|
|
saved = virSaveLastError();
|
|
ignore_value(networkReleasePort(obj, portdef));
|
|
virSetError(saved);
|
|
virFreeError(saved);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virNetworkObjAddPort(obj, portdef, driver->stateDir) < 0) {
|
|
virNetworkPortDefFree(portdef);
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = virGetNetworkPort(net, portdef->uuid);
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static char *
|
|
networkPortGetXMLDesc(virNetworkPortPtr port,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
virNetworkPortDefPtr portdef = NULL;
|
|
char *ret = NULL;
|
|
|
|
virCheckFlags(0, NULL);
|
|
|
|
if (!(obj = networkObjFromNetwork(port->net)))
|
|
return ret;
|
|
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (!(portdef = virNetworkObjLookupPort(obj, port->uuid)))
|
|
goto cleanup;
|
|
|
|
if (virNetworkPortGetXMLDescEnsureACL(port->net->conn, def, portdef) < 0)
|
|
goto cleanup;
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(ret = virNetworkPortDefFormat(portdef)))
|
|
goto cleanup;
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkPortDelete(virNetworkPortPtr port,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
virNetworkPortDefPtr portdef;
|
|
int ret = -1;
|
|
|
|
virCheckFlags(0, -1);
|
|
|
|
if (!(obj = networkObjFromNetwork(port->net)))
|
|
return ret;
|
|
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (!(portdef = virNetworkObjLookupPort(obj, port->uuid)))
|
|
goto cleanup;
|
|
|
|
if (virNetworkPortDeleteEnsureACL(port->net->conn, def, portdef) < 0)
|
|
goto cleanup;
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (networkReleasePort(obj, portdef) < 0)
|
|
goto cleanup;
|
|
|
|
virNetworkObjDeletePort(obj, port->uuid, driver->stateDir);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkPortSetParameters(virNetworkPortPtr port,
|
|
virTypedParameterPtr params,
|
|
int nparams,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkDriverStatePtr driver = networkGetDriver();
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
virNetworkPortDefPtr portdef;
|
|
virNetDevBandwidthPtr bandwidth = NULL;
|
|
char *dir = NULL;
|
|
int ret = -1;
|
|
size_t i;
|
|
|
|
virCheckFlags(0, -1);
|
|
|
|
if (!(obj = networkObjFromNetwork(port->net)))
|
|
return ret;
|
|
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (!(portdef = virNetworkObjLookupPort(obj, port->uuid)))
|
|
goto cleanup;
|
|
|
|
if (virNetworkPortSetParametersEnsureACL(port->net->conn, def, portdef) < 0)
|
|
goto cleanup;
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(dir = virNetworkObjGetPortStatusDir(obj, driver->stateDir)))
|
|
goto cleanup;
|
|
|
|
if ((VIR_ALLOC(bandwidth) < 0) ||
|
|
(VIR_ALLOC(bandwidth->in) < 0) ||
|
|
(VIR_ALLOC(bandwidth->out) < 0))
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < nparams; i++) {
|
|
virTypedParameterPtr param = ¶ms[i];
|
|
|
|
if (STREQ(param->field, VIR_NETWORK_PORT_BANDWIDTH_IN_AVERAGE)) {
|
|
bandwidth->in->average = param->value.ui;
|
|
} else if (STREQ(param->field, VIR_NETWORK_PORT_BANDWIDTH_IN_PEAK)) {
|
|
bandwidth->in->peak = param->value.ui;
|
|
} else if (STREQ(param->field, VIR_NETWORK_PORT_BANDWIDTH_IN_BURST)) {
|
|
bandwidth->in->burst = param->value.ui;
|
|
} else if (STREQ(param->field, VIR_NETWORK_PORT_BANDWIDTH_IN_FLOOR)) {
|
|
bandwidth->in->floor = param->value.ui;
|
|
} else if (STREQ(param->field, VIR_NETWORK_PORT_BANDWIDTH_OUT_AVERAGE)) {
|
|
bandwidth->out->average = param->value.ui;
|
|
} else if (STREQ(param->field, VIR_NETWORK_PORT_BANDWIDTH_OUT_PEAK)) {
|
|
bandwidth->out->peak = param->value.ui;
|
|
} else if (STREQ(param->field, VIR_NETWORK_PORT_BANDWIDTH_OUT_BURST)) {
|
|
bandwidth->out->burst = param->value.ui;
|
|
}
|
|
}
|
|
|
|
/* average or floor are mandatory, peak and burst are optional.
|
|
* So if no average or floor is given, we free inbound/outbound
|
|
* here which causes inbound/outbound to not be set. */
|
|
if (!bandwidth->in->average && !bandwidth->in->floor)
|
|
VIR_FREE(bandwidth->in);
|
|
if (!bandwidth->out->average)
|
|
VIR_FREE(bandwidth->out);
|
|
|
|
if (networkUpdatePortBandwidth(obj,
|
|
&portdef->mac,
|
|
&portdef->class_id,
|
|
portdef->bandwidth,
|
|
bandwidth) < 0)
|
|
goto cleanup;
|
|
|
|
virNetDevBandwidthFree(portdef->bandwidth);
|
|
portdef->bandwidth = bandwidth;
|
|
bandwidth = NULL;
|
|
|
|
if (virNetworkPortDefSaveStatus(portdef, dir) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virNetDevBandwidthFree(bandwidth);
|
|
virNetworkObjEndAPI(&obj);
|
|
VIR_FREE(dir);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkPortGetParameters(virNetworkPortPtr port,
|
|
virTypedParameterPtr *params,
|
|
int *nparams,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
virNetworkPortDefPtr portdef;
|
|
int maxparams = 0;
|
|
int ret = -1;
|
|
|
|
virCheckFlags(0, -1);
|
|
|
|
*params = NULL;
|
|
*nparams = 0;
|
|
|
|
if (!(obj = networkObjFromNetwork(port->net)))
|
|
return ret;
|
|
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (!(portdef = virNetworkObjLookupPort(obj, port->uuid)))
|
|
goto cleanup;
|
|
|
|
if (virNetworkPortGetParametersEnsureACL(port->net->conn, def, portdef) < 0)
|
|
goto cleanup;
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (portdef->bandwidth) {
|
|
if ((portdef->bandwidth->in != NULL) &&
|
|
(virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_IN_AVERAGE,
|
|
portdef->bandwidth->in->average) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_IN_PEAK,
|
|
portdef->bandwidth->in->peak) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_IN_FLOOR,
|
|
portdef->bandwidth->in->floor) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_IN_BURST,
|
|
portdef->bandwidth->in->burst) < 0))
|
|
goto cleanup;
|
|
|
|
if ((portdef->bandwidth->out != NULL) &&
|
|
(virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_OUT_AVERAGE,
|
|
portdef->bandwidth->out->average) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_OUT_PEAK,
|
|
portdef->bandwidth->out->peak) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_OUT_BURST,
|
|
portdef->bandwidth->out->burst) < 0))
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
networkListAllPorts(virNetworkPtr net,
|
|
virNetworkPortPtr **ports,
|
|
unsigned int flags)
|
|
{
|
|
virNetworkObjPtr obj;
|
|
virNetworkDefPtr def;
|
|
int ret = -1;
|
|
|
|
virCheckFlags(0, -1);
|
|
|
|
if (!(obj = networkObjFromNetwork(net)))
|
|
return ret;
|
|
|
|
def = virNetworkObjGetDef(obj);
|
|
|
|
if (virNetworkListAllPortsEnsureACL(net->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (!virNetworkObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("network '%s' is not active"),
|
|
def->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = virNetworkObjPortListExport(net, obj, ports,
|
|
virNetworkListAllPortsCheckACL);
|
|
|
|
cleanup:
|
|
virNetworkObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virNetworkDriver networkDriver = {
|
|
.name = "bridge",
|
|
.connectNumOfNetworks = networkConnectNumOfNetworks, /* 0.2.0 */
|
|
.connectListNetworks = networkConnectListNetworks, /* 0.2.0 */
|
|
.connectNumOfDefinedNetworks = networkConnectNumOfDefinedNetworks, /* 0.2.0 */
|
|
.connectListDefinedNetworks = networkConnectListDefinedNetworks, /* 0.2.0 */
|
|
.connectListAllNetworks = networkConnectListAllNetworks, /* 0.10.2 */
|
|
.connectNetworkEventRegisterAny = networkConnectNetworkEventRegisterAny, /* 1.2.1 */
|
|
.connectNetworkEventDeregisterAny = networkConnectNetworkEventDeregisterAny, /* 1.2.1 */
|
|
.networkLookupByUUID = networkLookupByUUID, /* 0.2.0 */
|
|
.networkLookupByName = networkLookupByName, /* 0.2.0 */
|
|
.networkCreateXML = networkCreateXML, /* 0.2.0 */
|
|
.networkDefineXML = networkDefineXML, /* 0.2.0 */
|
|
.networkUndefine = networkUndefine, /* 0.2.0 */
|
|
.networkUpdate = networkUpdate, /* 0.10.2 */
|
|
.networkCreate = networkCreate, /* 0.2.0 */
|
|
.networkDestroy = networkDestroy, /* 0.2.0 */
|
|
.networkGetXMLDesc = networkGetXMLDesc, /* 0.2.0 */
|
|
.networkGetBridgeName = networkGetBridgeName, /* 0.2.0 */
|
|
.networkGetAutostart = networkGetAutostart, /* 0.2.1 */
|
|
.networkSetAutostart = networkSetAutostart, /* 0.2.1 */
|
|
.networkIsActive = networkIsActive, /* 0.7.3 */
|
|
.networkIsPersistent = networkIsPersistent, /* 0.7.3 */
|
|
.networkGetDHCPLeases = networkGetDHCPLeases, /* 1.2.6 */
|
|
.networkPortLookupByUUID = networkPortLookupByUUID, /* 5.5.0 */
|
|
.networkPortCreateXML = networkPortCreateXML, /* 5.5.0 */
|
|
.networkPortGetXMLDesc = networkPortGetXMLDesc, /* 5.5.0 */
|
|
.networkPortDelete = networkPortDelete, /* 5.5.0 */
|
|
.networkListAllPorts = networkListAllPorts, /* 5.5.0 */
|
|
.networkPortGetParameters = networkPortGetParameters, /* 5.5.0 */
|
|
.networkPortSetParameters = networkPortSetParameters, /* 5.5.0 */
|
|
};
|
|
|
|
|
|
static virHypervisorDriver networkHypervisorDriver = {
|
|
.name = "network",
|
|
.connectOpen = networkConnectOpen, /* 4.1.0 */
|
|
.connectClose = networkConnectClose, /* 4.1.0 */
|
|
.connectIsEncrypted = networkConnectIsEncrypted, /* 4.1.0 */
|
|
.connectIsSecure = networkConnectIsSecure, /* 4.1.0 */
|
|
.connectIsAlive = networkConnectIsAlive, /* 4.1.0 */
|
|
};
|
|
|
|
|
|
static virConnectDriver networkConnectDriver = {
|
|
.localOnly = true,
|
|
.uriSchemes = (const char *[]){ "network", NULL },
|
|
.hypervisorDriver = &networkHypervisorDriver,
|
|
.networkDriver = &networkDriver,
|
|
};
|
|
|
|
|
|
static virStateDriver networkStateDriver = {
|
|
.name = "bridge",
|
|
.stateInitialize = networkStateInitialize,
|
|
.stateCleanup = networkStateCleanup,
|
|
.stateReload = networkStateReload,
|
|
};
|
|
|
|
int
|
|
networkRegister(void)
|
|
{
|
|
if (virRegisterConnectDriver(&networkConnectDriver, false) < 0)
|
|
return -1;
|
|
if (virSetSharedNetworkDriver(&networkDriver) < 0)
|
|
return -1;
|
|
if (virRegisterStateDriver(&networkStateDriver) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|