/* * network_conf.c: network XML handling * * Copyright (C) 2006-2015 Red Hat, Inc. * Copyright (C) 2006-2008 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 * . * * Author: Daniel P. Berrange */ #include #include #include #include #include #include #include #include #include "virerror.h" #include "datatypes.h" #include "network_conf.h" #include "netdev_vport_profile_conf.h" #include "netdev_bandwidth_conf.h" #include "netdev_vlan_conf.h" #include "viralloc.h" #include "virxml.h" #include "viruuid.h" #include "virbuffer.h" #include "c-ctype.h" #include "virfile.h" #include "virstring.h" #define MAX_BRIDGE_ID 256 #define VIR_FROM_THIS VIR_FROM_NETWORK #define NEXT_FREE_CLASS_ID 3 /* currently, /sbin/tc implementation allows up to 16 bits for minor class size */ #define CLASS_ID_BITMAP_SIZE (1<<16) VIR_ENUM_IMPL(virNetworkForward, VIR_NETWORK_FORWARD_LAST, "none", "nat", "route", "bridge", "private", "vepa", "passthrough", "hostdev") VIR_ENUM_IMPL(virNetworkBridgeMACTableManager, VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_LAST, "default", "kernel", "libvirt") VIR_ENUM_DECL(virNetworkForwardHostdevDevice) VIR_ENUM_IMPL(virNetworkForwardHostdevDevice, VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_LAST, "none", "pci", "netdev") VIR_ENUM_IMPL(virNetworkForwardDriverName, VIR_NETWORK_FORWARD_DRIVER_NAME_LAST, "default", "kvm", "vfio") VIR_ENUM_IMPL(virNetworkTaint, VIR_NETWORK_TAINT_LAST, "hook-script"); bool virNetworkObjTaint(virNetworkObjPtr obj, virNetworkTaintFlags taint) { unsigned int flag = (1 << taint); if (obj->taint & flag) return false; obj->taint |= flag; return true; } virNetworkObjPtr virNetworkFindByUUID(virNetworkObjListPtr nets, const unsigned char *uuid) { size_t i; for (i = 0; i < nets->count; i++) { virNetworkObjLock(nets->objs[i]); if (!memcmp(nets->objs[i]->def->uuid, uuid, VIR_UUID_BUFLEN)) return nets->objs[i]; virNetworkObjUnlock(nets->objs[i]); } return NULL; } virNetworkObjPtr virNetworkFindByName(virNetworkObjListPtr nets, const char *name) { size_t i; for (i = 0; i < nets->count; i++) { virNetworkObjLock(nets->objs[i]); if (STREQ(nets->objs[i]->def->name, name)) return nets->objs[i]; virNetworkObjUnlock(nets->objs[i]); } return NULL; } static void virPortGroupDefClear(virPortGroupDefPtr def) { VIR_FREE(def->name); VIR_FREE(def->virtPortProfile); virNetDevBandwidthFree(def->bandwidth); virNetDevVlanClear(&def->vlan); def->bandwidth = NULL; } static void virNetworkForwardIfDefClear(virNetworkForwardIfDefPtr def) { if (def->type == VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV) VIR_FREE(def->device.dev); } static void virNetworkForwardPfDefClear(virNetworkForwardPfDefPtr def) { VIR_FREE(def->dev); } static void virNetworkDHCPHostDefClear(virNetworkDHCPHostDefPtr def) { VIR_FREE(def->mac); VIR_FREE(def->id); VIR_FREE(def->name); } static void virNetworkIpDefClear(virNetworkIpDefPtr def) { VIR_FREE(def->family); VIR_FREE(def->ranges); while (def->nhosts) virNetworkDHCPHostDefClear(&def->hosts[--def->nhosts]); VIR_FREE(def->hosts); VIR_FREE(def->tftproot); VIR_FREE(def->bootfile); } static void virNetworkDNSTxtDefClear(virNetworkDNSTxtDefPtr def) { VIR_FREE(def->name); VIR_FREE(def->value); } static void virNetworkDNSHostDefClear(virNetworkDNSHostDefPtr def) { while (def->nnames) VIR_FREE(def->names[--def->nnames]); VIR_FREE(def->names); } static void virNetworkDNSSrvDefClear(virNetworkDNSSrvDefPtr def) { VIR_FREE(def->domain); VIR_FREE(def->service); VIR_FREE(def->protocol); VIR_FREE(def->target); } static void virNetworkDNSDefClear(virNetworkDNSDefPtr def) { if (def->forwarders) { while (def->nfwds) VIR_FREE(def->forwarders[--def->nfwds]); VIR_FREE(def->forwarders); } if (def->txts) { while (def->ntxts) virNetworkDNSTxtDefClear(&def->txts[--def->ntxts]); VIR_FREE(def->txts); } if (def->hosts) { while (def->nhosts) virNetworkDNSHostDefClear(&def->hosts[--def->nhosts]); VIR_FREE(def->hosts); } if (def->srvs) { while (def->nsrvs) virNetworkDNSSrvDefClear(&def->srvs[--def->nsrvs]); VIR_FREE(def->srvs); } } static void virNetworkForwardDefClear(virNetworkForwardDefPtr def) { size_t i; for (i = 0; i < def->npfs && def->pfs; i++) virNetworkForwardPfDefClear(&def->pfs[i]); VIR_FREE(def->pfs); for (i = 0; i < def->nifs && def->ifs; i++) virNetworkForwardIfDefClear(&def->ifs[i]); VIR_FREE(def->ifs); def->nifs = def->npfs = 0; } void virNetworkDefFree(virNetworkDefPtr def) { size_t i; if (!def) return; VIR_FREE(def->name); VIR_FREE(def->bridge); VIR_FREE(def->domain); virNetworkForwardDefClear(&def->forward); for (i = 0; i < def->nips && def->ips; i++) virNetworkIpDefClear(&def->ips[i]); VIR_FREE(def->ips); for (i = 0; i < def->nroutes && def->routes; i++) virNetworkRouteDefFree(def->routes[i]); VIR_FREE(def->routes); for (i = 0; i < def->nPortGroups && def->portGroups; i++) virPortGroupDefClear(&def->portGroups[i]); VIR_FREE(def->portGroups); virNetworkDNSDefClear(&def->dns); VIR_FREE(def->virtPortProfile); virNetDevBandwidthFree(def->bandwidth); virNetDevVlanClear(&def->vlan); VIR_FREE(def); } void virNetworkObjFree(virNetworkObjPtr net) { if (!net) return; virNetworkDefFree(net->def); virNetworkDefFree(net->newDef); virBitmapFree(net->class_id); virMutexDestroy(&net->lock); VIR_FREE(net); } void virNetworkObjListFree(virNetworkObjListPtr nets) { size_t i; for (i = 0; i < nets->count; i++) virNetworkObjFree(nets->objs[i]); VIR_FREE(nets->objs); nets->count = 0; } /* * virNetworkObjAssignDef: * @network: the network object to update * @def: the new NetworkDef (will be consumed by this function) * @live: is this new def the "live" version, or the "persistent" version * * Replace the appropriate copy of the given network's def or newDef * with def. Use "live" and current state of the network to determine * which to replace and what to do with the old defs. When a non-live * def is set, indicate that the network is now persistent. * * NB: a persistent network can be made transient by calling with: * virNetworkObjAssignDef(network, NULL, false) (i.e. set the * persistent def to NULL) * */ void virNetworkObjAssignDef(virNetworkObjPtr network, virNetworkDefPtr def, bool live) { if (live) { /* before setting new live def, save (into newDef) any * existing persistent (!live) def to be restored when the * network is destroyed, unless there is one already saved. */ if (network->persistent && !network->newDef) network->newDef = network->def; else virNetworkDefFree(network->def); network->def = def; } else { /* !live */ virNetworkDefFree(network->newDef); if (virNetworkObjIsActive(network)) { /* save new configuration to be restored on network * shutdown, leaving current live def alone */ network->newDef = def; } else { /* !live and !active */ if (network->def && !network->persistent) { /* network isn't (yet) marked active or persistent, * but already has a "live" def set. This means we are * currently setting the persistent def as a part of * the process of starting the network, so we need to * preserve the "not yet live" def in network->def. */ network->newDef = def; } else { /* either there is no live def set, or this network * was already set as persistent, so the proper thing * is to overwrite network->def. */ network->newDef = NULL; virNetworkDefFree(network->def); network->def = def; } } network->persistent = !!def; } } /* * virNetworkAssignDef: * @nets: list of all networks * @def: the new NetworkDef (will be consumed by this function iff successful) * @live: is this new def the "live" version, or the "persistent" version * * Either replace the appropriate copy of the NetworkDef with name * matching def->name or, if not found, create a new NetworkObj with * def. For an existing network, use "live" and current state of the * network to determine which to replace. * * Returns NULL on error, virNetworkObjPtr on success. */ virNetworkObjPtr virNetworkAssignDef(virNetworkObjListPtr nets, virNetworkDefPtr def, bool live) { virNetworkObjPtr network; if ((network = virNetworkFindByName(nets, def->name))) { virNetworkObjAssignDef(network, def, live); return network; } if (VIR_ALLOC(network) < 0) return NULL; if (virMutexInit(&network->lock) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cannot initialize mutex")); VIR_FREE(network); return NULL; } virNetworkObjLock(network); if (VIR_APPEND_ELEMENT_COPY(nets->objs, nets->count, network) < 0 || !(network->class_id = virBitmapNew(CLASS_ID_BITMAP_SIZE))) goto error; /* The first three class IDs are already taken */ ignore_value(virBitmapSetBit(network->class_id, 0)); ignore_value(virBitmapSetBit(network->class_id, 1)); ignore_value(virBitmapSetBit(network->class_id, 2)); network->def = def; network->persistent = !live; return network; error: virNetworkObjUnlock(network); virNetworkObjFree(network); return NULL; } /* * virNetworkObjSetDefTransient: * @network: network object pointer * @live: if true, run this operation even for an inactive network. * this allows freely updated network->def with runtime defaults * before starting the network, which will be discarded on network * shutdown. Any cleanup paths need to be sure to handle newDef if * the network is never started. * * Mark the active network config as transient. Ensures live-only update * operations do not persist past network destroy. * * Returns 0 on success, -1 on failure */ int virNetworkObjSetDefTransient(virNetworkObjPtr network, bool live) { if (!virNetworkObjIsActive(network) && !live) return 0; if (!network->persistent || network->newDef) return 0; network->newDef = virNetworkDefCopy(network->def, VIR_NETWORK_XML_INACTIVE); return network->newDef ? 0 : -1; } /* virNetworkObjUnsetDefTransient: * * This *undoes* what virNetworkObjSetDefTransient did. */ void virNetworkObjUnsetDefTransient(virNetworkObjPtr network) { if (network->newDef) { virNetworkDefFree(network->def); network->def = network->newDef; network->newDef = NULL; } } /* * virNetworkObjGetPersistentDef: * @network: network object pointer * * Return the persistent network configuration. If network is transient, * return the running config. * * Returns NULL on error, virNetworkDefPtr on success. */ virNetworkDefPtr virNetworkObjGetPersistentDef(virNetworkObjPtr network) { if (network->newDef) return network->newDef; else return network->def; } /* * virNetworkObjReplacePersistentDef: * @network: network object pointer * @def: new virNetworkDef to replace current persistent config * * Replace the "persistent" network configuration with the given new * virNetworkDef. This pays attention to whether or not the network * is active. * * Returns -1 on error, 0 on success */ int virNetworkObjReplacePersistentDef(virNetworkObjPtr network, virNetworkDefPtr def) { if (virNetworkObjIsActive(network)) { virNetworkDefFree(network->newDef); network->newDef = def; } else { virNetworkDefFree(network->def); network->def = def; } return 0; } /* * virNetworkDefCopy: * @def: NetworkDef to copy * @flags: VIR_NETWORK_XML_INACTIVE if appropriate * * make a deep copy of the given NetworkDef * * Returns a new NetworkDef on success, or NULL on failure. */ virNetworkDefPtr virNetworkDefCopy(virNetworkDefPtr def, unsigned int flags) { char *xml = NULL; virNetworkDefPtr newDef = NULL; if (!def) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("NULL NetworkDef")); return NULL; } /* deep copy with a format/parse cycle */ if (!(xml = virNetworkDefFormat(def, flags))) goto cleanup; newDef = virNetworkDefParseString(xml); cleanup: VIR_FREE(xml); return newDef; } /* * virNetworkConfigChangeSetup: * * 1) checks whether network state is consistent with the requested * type of modification. * * 3) make sure there are separate "def" and "newDef" copies of * networkDef if appropriate. * * Returns 0 on success, -1 on error. */ int virNetworkConfigChangeSetup(virNetworkObjPtr network, unsigned int flags) { bool isActive; int ret = -1; isActive = virNetworkObjIsActive(network); if (!isActive && (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("network is not running")); goto cleanup; } if (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) { if (!network->persistent) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot change persistent config of a " "transient network")); goto cleanup; } /* this should already have been done by the driver, but do it * anyway just in case. */ if (isActive && (virNetworkObjSetDefTransient(network, false) < 0)) goto cleanup; } ret = 0; cleanup: return ret; } void virNetworkRemoveInactive(virNetworkObjListPtr nets, virNetworkObjPtr net) { size_t i; virNetworkObjUnlock(net); for (i = 0; i < nets->count; i++) { virNetworkObjLock(nets->objs[i]); if (nets->objs[i] == net) { virNetworkObjUnlock(nets->objs[i]); virNetworkObjFree(nets->objs[i]); VIR_DELETE_ELEMENT(nets->objs, i, nets->count); break; } virNetworkObjUnlock(nets->objs[i]); } } /* return ips[index], or NULL if there aren't enough ips */ virNetworkIpDefPtr virNetworkDefGetIpByIndex(const virNetworkDef *def, int family, size_t n) { size_t i; if (!def->ips || n >= def->nips) return NULL; if (family == AF_UNSPEC) return &def->ips[n]; /* find the nth ip of type "family" */ for (i = 0; i < def->nips; i++) { if (VIR_SOCKET_ADDR_IS_FAMILY(&def->ips[i].address, family) && (n-- <= 0)) { return &def->ips[i]; } } /* failed to find enough of the right family */ return NULL; } /* return number of 1 bits in netmask for the network's ipAddress, * or -1 on error */ int virNetworkIpDefPrefix(const virNetworkIpDef *def) { return virSocketAddrGetIpPrefix(&def->address, &def->netmask, def->prefix); } /* Fill in a virSocketAddr with the proper netmask for this * definition, based on either the definition's netmask, or its * prefix. Return -1 on error (and set the netmask family to AF_UNSPEC) */ int virNetworkIpDefNetmask(const virNetworkIpDef *def, virSocketAddrPtr netmask) { if (VIR_SOCKET_ADDR_IS_FAMILY(&def->netmask, AF_INET)) { *netmask = def->netmask; return 0; } return virSocketAddrPrefixToNetmask(virNetworkIpDefPrefix(def), netmask, VIR_SOCKET_ADDR_FAMILY(&def->address)); } static int virSocketAddrRangeParseXML(const char *networkName, xmlNodePtr node, virSocketAddrRangePtr range) { char *start = NULL, *end = NULL; int ret = -1; if (!(start = virXMLPropString(node, "start"))) { virReportError(VIR_ERR_XML_ERROR, _("Missing 'start' attribute in dhcp range for network '%s'"), networkName); goto cleanup; } if (virSocketAddrParse(&range->start, start, AF_UNSPEC) < 0) goto cleanup; if (!(end = virXMLPropString(node, "end"))) { virReportError(VIR_ERR_XML_ERROR, _("Missing 'end' attribute in dhcp range for network '%s'"), networkName); goto cleanup; } if (virSocketAddrParse(&range->end, end, AF_UNSPEC) < 0) goto cleanup; /* do a sanity check of the range */ if (virSocketAddrGetRange(&range->start, &range->end) < 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid dhcp range '%s' to '%s' in network '%s'"), start, end, networkName); goto cleanup; } ret = 0; cleanup: VIR_FREE(start); VIR_FREE(end); return ret; } static int virNetworkDHCPHostDefParseXML(const char *networkName, virNetworkIpDefPtr def, xmlNodePtr node, virNetworkDHCPHostDefPtr host, bool partialOkay) { char *mac = NULL, *name = NULL, *ip = NULL, *id = NULL; virMacAddr addr; virSocketAddr inaddr; int ret = -1; mac = virXMLPropString(node, "mac"); if (mac != NULL) { if (VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) { virReportError(VIR_ERR_XML_ERROR, _("Invalid to specify MAC address '%s' " "in network '%s' IPv6 static host definition"), mac, networkName); goto cleanup; } if (virMacAddrParse(mac, &addr) < 0) { virReportError(VIR_ERR_XML_ERROR, _("Cannot parse MAC address '%s' in network '%s'"), mac, networkName); goto cleanup; } if (virMacAddrIsMulticast(&addr)) { virReportError(VIR_ERR_XML_ERROR, _("expected unicast mac address, found " "multicast '%s' in network '%s'"), (const char *)mac, networkName); goto cleanup; } } id = virXMLPropString(node, "id"); if (id) { char *cp = id + strspn(id, "0123456789abcdefABCDEF:"); if (*cp) { virReportError(VIR_ERR_XML_ERROR, _("Invalid character '%c' in id '%s' of network '%s'"), *cp, id, networkName); } } name = virXMLPropString(node, "name"); if (name && (!c_isalpha(name[0]))) { virReportError(VIR_ERR_XML_ERROR, _("Cannot use host name '%s' in network '%s'"), name, networkName); goto cleanup; } ip = virXMLPropString(node, "ip"); if (ip && (virSocketAddrParse(&inaddr, ip, AF_UNSPEC) < 0)) { virReportError(VIR_ERR_XML_ERROR, _("Invalid IP address in static host definition " "for network '%s'"), networkName); goto cleanup; } if (partialOkay) { /* for search/match, you just need one of the three */ if (!(mac || name || ip)) { virReportError(VIR_ERR_XML_ERROR, _("At least one of name, mac, or ip attribute " "must be specified for static host definition " "in network '%s' "), networkName); } } else { /* normal usage - you need at least name (IPv6) or one of MAC * address or name (IPv4) */ if (VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) { if (!(id || name)) { virReportError(VIR_ERR_XML_ERROR, _("Static host definition in IPv6 network '%s' " "must have id or name attribute"), networkName); goto cleanup; } } else if (!(mac || name)) { virReportError(VIR_ERR_XML_ERROR, _("Static host definition in IPv4 network '%s' " "must have mac or name attribute"), networkName); goto cleanup; } if (!ip) { virReportError(VIR_ERR_XML_ERROR, _("Missing IP address in static host definition " "for network '%s'"), networkName); goto cleanup; } } host->mac = mac; mac = NULL; host->id = id; id = NULL; host->name = name; name = NULL; if (ip) host->ip = inaddr; ret = 0; cleanup: VIR_FREE(mac); VIR_FREE(id); VIR_FREE(name); VIR_FREE(ip); return ret; } static int virNetworkDHCPDefParseXML(const char *networkName, xmlNodePtr node, virNetworkIpDefPtr def) { xmlNodePtr cur; cur = node->children; while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "range")) { if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0) return -1; if (virSocketAddrRangeParseXML(networkName, cur, &def->ranges[def->nranges]) < 0) { return -1; } def->nranges++; } else if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "host")) { if (VIR_REALLOC_N(def->hosts, def->nhosts + 1) < 0) return -1; if (virNetworkDHCPHostDefParseXML(networkName, def, cur, &def->hosts[def->nhosts], false) < 0) { return -1; } def->nhosts++; } else if (VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) && cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "bootp")) { char *file; char *server; virSocketAddr inaddr; memset(&inaddr, 0, sizeof(inaddr)); if (!(file = virXMLPropString(cur, "file"))) { cur = cur->next; continue; } server = virXMLPropString(cur, "server"); if (server && virSocketAddrParse(&inaddr, server, AF_UNSPEC) < 0) { VIR_FREE(file); VIR_FREE(server); return -1; } def->bootfile = file; def->bootserver = inaddr; VIR_FREE(server); } cur = cur->next; } return 0; } static int virNetworkDNSHostDefParseXML(const char *networkName, xmlNodePtr node, virNetworkDNSHostDefPtr def, bool partialOkay) { xmlNodePtr cur; char *ip; if (!(ip = virXMLPropString(node, "ip")) && !partialOkay) { virReportError(VIR_ERR_XML_DETAIL, _("Missing IP address in network '%s' DNS HOST record"), networkName); goto error; } if (ip && (virSocketAddrParse(&def->ip, ip, AF_UNSPEC) < 0)) { virReportError(VIR_ERR_XML_DETAIL, _("Invalid IP address in network '%s' DNS HOST record"), networkName); VIR_FREE(ip); goto error; } VIR_FREE(ip); cur = node->children; while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "hostname")) { if (cur->children != NULL) { char *name = (char *) xmlNodeGetContent(cur); if (!name) { virReportError(VIR_ERR_XML_DETAIL, _("Missing hostname in network '%s' DNS HOST record"), networkName); goto error; } if (VIR_APPEND_ELEMENT(def->names, def->nnames, name) < 0) { VIR_FREE(name); goto error; } } } cur = cur->next; } if (def->nnames == 0 && !partialOkay) { virReportError(VIR_ERR_XML_DETAIL, _("Missing hostname in network '%s' DNS HOST record"), networkName); goto error; } if (!VIR_SOCKET_ADDR_VALID(&def->ip) && def->nnames == 0) { virReportError(VIR_ERR_XML_DETAIL, _("Missing ip and hostname in network '%s' DNS HOST record"), networkName); goto error; } return 0; error: virNetworkDNSHostDefClear(def); return -1; } /* This includes all characters used in the names of current * /etc/services and /etc/protocols files (on Fedora 20), except ".", * which we can't allow because it would conflict with the use of "." * as a field separator in the SRV record, there appears to be no way * to escape it in, and the protocols/services that use "." in the * name are obscure and unlikely to be used anyway. */ #define PROTOCOL_CHARS \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \ "-+/" #define SERVICE_CHARS \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \ "_-+/*" static int virNetworkDNSSrvDefParseXML(const char *networkName, xmlNodePtr node, xmlXPathContextPtr ctxt, virNetworkDNSSrvDefPtr def, bool partialOkay) { int ret; xmlNodePtr save_ctxt = ctxt->node; ctxt->node = node; if (!(def->service = virXMLPropString(node, "service")) && !partialOkay) { virReportError(VIR_ERR_XML_DETAIL, _("missing required service attribute in DNS SRV record " "of network '%s'"), networkName); goto error; } if (def->service) { if (strlen(def->service) > DNS_RECORD_LENGTH_SRV) { virReportError(VIR_ERR_XML_DETAIL, _("service attribute '%s' in network '%s' is too long, " "limit is %d bytes"), def->service, networkName, DNS_RECORD_LENGTH_SRV); goto error; } if (strspn(def->service, SERVICE_CHARS) < strlen(def->service)) { virReportError(VIR_ERR_XML_DETAIL, _("invalid character in service attribute '%s' " "in DNS SRV record of network '%s'"), def->service, networkName); goto error; } } if (!(def->protocol = virXMLPropString(node, "protocol")) && !partialOkay) { virReportError(VIR_ERR_XML_DETAIL, _("missing required protocol attribute " "in DNS SRV record '%s' of network '%s'"), def->service, networkName); goto error; } if (def->protocol && strspn(def->protocol, PROTOCOL_CHARS) < strlen(def->protocol)) { virReportError(VIR_ERR_XML_DETAIL, _("invalid character in protocol attribute '%s' " "in DNS SRV record of network '%s'"), def->protocol, networkName); goto error; } /* Following attributes are optional */ def->domain = virXMLPropString(node, "domain"); def->target = virXMLPropString(node, "target"); ret = virXPathUInt("string(./@port)", ctxt, &def->port); if (ret >= 0 && !def->target) { virReportError(VIR_ERR_XML_DETAIL, _("DNS SRV port attribute not permitted without " "target for service '%s' in network '%s'"), def->service, networkName); goto error; } if (ret == -2 || (ret >= 0 && (def->port < 1 || def->port > 65535))) { virReportError(VIR_ERR_XML_DETAIL, _("invalid DNS SRV port attribute " "for service '%s' in network '%s'"), def->service, networkName); goto error; } ret = virXPathUInt("string(./@priority)", ctxt, &def->priority); if (ret >= 0 && !def->target) { virReportError(VIR_ERR_XML_DETAIL, _("DNS SRV priority attribute not permitted without " "target for service '%s' in network '%s'"), def->service, networkName); goto error; } if (ret == -2 || (ret >= 0 && def->priority > 65535)) { virReportError(VIR_ERR_XML_DETAIL, _("Invalid DNS SRV priority attribute " "for service '%s' in network '%s'"), def->service, networkName); goto error; } ret = virXPathUInt("string(./@weight)", ctxt, &def->weight); if (ret >= 0 && !def->target) { virReportError(VIR_ERR_XML_DETAIL, _("DNS SRV weight attribute not permitted without " "target for service '%s' in network '%s'"), def->service, networkName); goto error; } if (ret == -2 || (ret >= 0 && def->weight > 65535)) { virReportError(VIR_ERR_XML_DETAIL, _("invalid DNS SRV weight attribute " "for service '%s' in network '%s'"), def->service, networkName); goto error; } ctxt->node = save_ctxt; return 0; error: virNetworkDNSSrvDefClear(def); ctxt->node = save_ctxt; return -1; } static int virNetworkDNSTxtDefParseXML(const char *networkName, xmlNodePtr node, virNetworkDNSTxtDefPtr def, bool partialOkay) { const char *bad = " ,"; if (!(def->name = virXMLPropString(node, "name"))) { virReportError(VIR_ERR_XML_DETAIL, _("missing required name attribute in DNS TXT record " "of network %s"), networkName); goto error; } if (strcspn(def->name, bad) != strlen(def->name)) { virReportError(VIR_ERR_XML_DETAIL, _("prohibited character in DNS TXT record " "name '%s' of network %s"), def->name, networkName); goto error; } if (!(def->value = virXMLPropString(node, "value")) && !partialOkay) { virReportError(VIR_ERR_XML_DETAIL, _("missing required value attribute in DNS TXT record " "named '%s' of network %s"), def->name, networkName); goto error; } if (!(def->name || def->value)) { virReportError(VIR_ERR_XML_DETAIL, _("Missing required name or value " "in DNS TXT record of network %s"), networkName); goto error; } return 0; error: virNetworkDNSTxtDefClear(def); return -1; } static int virNetworkDNSDefParseXML(const char *networkName, xmlNodePtr node, xmlXPathContextPtr ctxt, virNetworkDNSDefPtr def) { xmlNodePtr *hostNodes = NULL; xmlNodePtr *srvNodes = NULL; xmlNodePtr *txtNodes = NULL; xmlNodePtr *fwdNodes = NULL; char *forwardPlainNames = NULL; int nhosts, nsrvs, ntxts, nfwds; size_t i; int ret = -1; xmlNodePtr save = ctxt->node; ctxt->node = node; forwardPlainNames = virXPathString("string(./@forwardPlainNames)", ctxt); if (forwardPlainNames) { def->forwardPlainNames = virTristateBoolTypeFromString(forwardPlainNames); if (def->forwardPlainNames <= 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid dns forwardPlainNames setting '%s' " "in network '%s'"), forwardPlainNames, networkName); goto cleanup; } } nfwds = virXPathNodeSet("./forwarder", ctxt, &fwdNodes); if (nfwds < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid element found in of network %s"), networkName); goto cleanup; } if (nfwds > 0) { if (VIR_ALLOC_N(def->forwarders, nfwds) < 0) goto cleanup; for (i = 0; i < nfwds; i++) { def->forwarders[i] = virXMLPropString(fwdNodes[i], "addr"); if (virSocketAddrParse(NULL, def->forwarders[i], AF_UNSPEC) < 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid forwarder IP address '%s' " "in network '%s'"), def->forwarders[i], networkName); goto cleanup; } def->nfwds++; } } nhosts = virXPathNodeSet("./host", ctxt, &hostNodes); if (nhosts < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid element found in of network %s"), networkName); goto cleanup; } if (nhosts > 0) { if (VIR_ALLOC_N(def->hosts, nhosts) < 0) goto cleanup; for (i = 0; i < nhosts; i++) { if (virNetworkDNSHostDefParseXML(networkName, hostNodes[i], &def->hosts[def->nhosts], false) < 0) { goto cleanup; } def->nhosts++; } } nsrvs = virXPathNodeSet("./srv", ctxt, &srvNodes); if (nsrvs < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid element found in of network %s"), networkName); goto cleanup; } if (nsrvs > 0) { if (VIR_ALLOC_N(def->srvs, nsrvs) < 0) goto cleanup; for (i = 0; i < nsrvs; i++) { if (virNetworkDNSSrvDefParseXML(networkName, srvNodes[i], ctxt, &def->srvs[def->nsrvs], false) < 0) { goto cleanup; } def->nsrvs++; } } ntxts = virXPathNodeSet("./txt", ctxt, &txtNodes); if (ntxts < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid element found in of network %s"), networkName); goto cleanup; } if (ntxts > 0) { if (VIR_ALLOC_N(def->txts, ntxts) < 0) goto cleanup; for (i = 0; i < ntxts; i++) { if (virNetworkDNSTxtDefParseXML(networkName, txtNodes[i], &def->txts[def->ntxts], false) < 0) { goto cleanup; } def->ntxts++; } } ret = 0; cleanup: VIR_FREE(forwardPlainNames); VIR_FREE(fwdNodes); VIR_FREE(hostNodes); VIR_FREE(srvNodes); VIR_FREE(txtNodes); ctxt->node = save; return ret; } static int virNetworkIPDefParseXML(const char *networkName, xmlNodePtr node, xmlXPathContextPtr ctxt, virNetworkIpDefPtr def) { /* * virNetworkIpDef object is already allocated as part of an array. * On failure clear it out, but don't free it. */ xmlNodePtr cur, save; char *address = NULL, *netmask = NULL; unsigned long prefix = 0; int prefixRc; int result = -1; save = ctxt->node; ctxt->node = node; /* grab raw data from XML */ def->family = virXPathString("string(./@family)", ctxt); address = virXPathString("string(./@address)", ctxt); if (!address) { virReportError(VIR_ERR_XML_ERROR, _("Missing required address attribute in network '%s'"), networkName); goto cleanup; } if (virSocketAddrParse(&def->address, address, AF_UNSPEC) < 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid address '%s' in network '%s'"), address, networkName); goto cleanup; } netmask = virXPathString("string(./@netmask)", ctxt); if (netmask && (virSocketAddrParse(&def->netmask, netmask, AF_UNSPEC) < 0)) { virReportError(VIR_ERR_XML_ERROR, _("Invalid netmask '%s' in network '%s'"), netmask, networkName); goto cleanup; } prefixRc = virXPathULong("string(./@prefix)", ctxt, &prefix); if (prefixRc == -2) { virReportError(VIR_ERR_XML_ERROR, _("Invalid ULong value specified for prefix in definition of network '%s'"), networkName); goto cleanup; } if (prefixRc < 0) def->prefix = 0; else def->prefix = prefix; /* validate address, etc. for each family */ if ((def->family == NULL) || (STREQ(def->family, "ipv4"))) { if (!(VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET) || VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_UNSPEC))) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("%s family specified for non-IPv4 address '%s' in network '%s'"), def->family == NULL? "no" : "ipv4", address, networkName); goto cleanup; } if (netmask) { if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->netmask, AF_INET)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid netmask '%s' for address '%s' " "in network '%s' (both must be IPv4)"), netmask, address, networkName); goto cleanup; } if (def->prefix > 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Network '%s' IP address cannot have " "both a prefix and a netmask"), networkName); goto cleanup; } } else if (def->prefix > 32) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid IPv4 prefix '%lu' in network '%s'"), prefix, networkName); goto cleanup; } } else if (STREQ(def->family, "ipv6")) { if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET6)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Family 'ipv6' specified for non-IPv6 address '%s' in network '%s'"), address, networkName); goto cleanup; } if (netmask) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("netmask not allowed for IPv6 address '%s' in network '%s'"), address, networkName); goto cleanup; } if (def->prefix > 128) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid IPv6 prefix '%lu' in network '%s'"), prefix, networkName); goto cleanup; } } else { virReportError(VIR_ERR_XML_ERROR, _("Unrecognized family '%s' in network '%s'"), def->family, networkName); goto cleanup; } cur = node->children; while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "dhcp")) { if (virNetworkDHCPDefParseXML(networkName, cur, def) < 0) goto cleanup; } else if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "tftp")) { char *root; if (!VIR_SOCKET_ADDR_IS_FAMILY(&def->address, AF_INET)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Unsupported element in an IPv6 element in network '%s'"), networkName); goto cleanup; } if (!(root = virXMLPropString(cur, "root"))) { cur = cur->next; continue; } def->tftproot = (char *)root; } cur = cur->next; } result = 0; cleanup: if (result < 0) virNetworkIpDefClear(def); VIR_FREE(address); VIR_FREE(netmask); ctxt->node = save; return result; } static int virNetworkPortGroupParseXML(virPortGroupDefPtr def, xmlNodePtr node, xmlXPathContextPtr ctxt) { /* * virPortGroupDef object is already allocated as part of an array. * On failure clear it out, but don't free it. */ xmlNodePtr save; xmlNodePtr virtPortNode; xmlNodePtr vlanNode; xmlNodePtr bandwidth_node; char *isDefault = NULL; char *trustGuestRxFilters = NULL; int result = -1; save = ctxt->node; ctxt->node = node; /* grab raw data from XML */ def->name = virXPathString("string(./@name)", ctxt); if (!def->name) { virReportError(VIR_ERR_XML_ERROR, "%s", _("Missing required name attribute in portgroup")); goto cleanup; } isDefault = virXPathString("string(./@default)", ctxt); def->isDefault = isDefault && STRCASEEQ(isDefault, "yes"); trustGuestRxFilters = virXPathString("string(./@trustGuestRxFilters)", ctxt); if (trustGuestRxFilters) { if ((def->trustGuestRxFilters = virTristateBoolTypeFromString(trustGuestRxFilters)) <= 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid trustGuestRxFilters setting '%s' " "in portgroup"), trustGuestRxFilters); goto cleanup; } } virtPortNode = virXPathNode("./virtualport", ctxt); if (virtPortNode && (!(def->virtPortProfile = virNetDevVPortProfileParse(virtPortNode, 0)))) { goto cleanup; } bandwidth_node = virXPathNode("./bandwidth", ctxt); if (bandwidth_node && virNetDevBandwidthParse(&def->bandwidth, bandwidth_node, -1) < 0) goto cleanup; vlanNode = virXPathNode("./vlan", ctxt); if (vlanNode && virNetDevVlanParse(vlanNode, ctxt, &def->vlan) < 0) goto cleanup; result = 0; cleanup: if (result < 0) virPortGroupDefClear(def); VIR_FREE(isDefault); VIR_FREE(trustGuestRxFilters); ctxt->node = save; return result; } static int virNetworkForwardNatDefParseXML(const char *networkName, xmlNodePtr node, xmlXPathContextPtr ctxt, virNetworkForwardDefPtr def) { int ret = -1; xmlNodePtr *natAddrNodes = NULL; xmlNodePtr *natPortNodes = NULL; int nNatAddrs, nNatPorts; char *addrStart = NULL; char *addrEnd = NULL; xmlNodePtr save = ctxt->node; ctxt->node = node; if (def->type != VIR_NETWORK_FORWARD_NAT) { virReportError(VIR_ERR_XML_ERROR, _("The element can only be used when 'mode' is 'nat' in network %s"), networkName); goto cleanup; } /* addresses for SNAT */ nNatAddrs = virXPathNodeSet("./address", ctxt, &natAddrNodes); if (nNatAddrs < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid
element found in of " "network %s"), networkName); goto cleanup; } else if (nNatAddrs > 1) { virReportError(VIR_ERR_XML_ERROR, _("Only one
element is allowed in in " " in network %s"), networkName); goto cleanup; } else if (nNatAddrs == 1) { addrStart = virXMLPropString(*natAddrNodes, "start"); if (addrStart == NULL) { virReportError(VIR_ERR_XML_ERROR, _("missing 'start' attribute in
element in in " " in network %s"), networkName); goto cleanup; } addrEnd = virXMLPropString(*natAddrNodes, "end"); if (addrEnd == NULL) { virReportError(VIR_ERR_XML_ERROR, _("missing 'end' attribute in
element in in " " in network %s"), networkName); goto cleanup; } } if (addrStart && virSocketAddrParse(&def->addr.start, addrStart, AF_INET) < 0) { virReportError(VIR_ERR_XML_ERROR, _("Bad ipv4 start address '%s' in in in " "network '%s'"), addrStart, networkName); goto cleanup; } if (addrEnd && virSocketAddrParse(&def->addr.end, addrEnd, AF_INET) < 0) { virReportError(VIR_ERR_XML_ERROR, _("Bad ipv4 end address '%s' in in in " "network '%s'"), addrEnd, networkName); goto cleanup; } /* ports for SNAT and MASQUERADE */ nNatPorts = virXPathNodeSet("./port", ctxt, &natPortNodes); if (nNatPorts < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid element found in of " "network %s"), networkName); goto cleanup; } else if (nNatPorts > 1) { virReportError(VIR_ERR_XML_ERROR, _("Only one element is allowed in in " " in network %s"), networkName); goto cleanup; } else if (nNatPorts == 1) { if (virXPathUInt("string(./port[1]/@start)", ctxt, &def->port.start) < 0 || def->port.start > 65535) { virReportError(VIR_ERR_XML_DETAIL, _("Missing or invalid 'start' attribute in " "in in in network %s"), networkName); goto cleanup; } if (virXPathUInt("string(./port[1]/@end)", ctxt, &def->port.end) < 0 || def->port.end > 65535 || def->port.end < def->port.start) { virReportError(VIR_ERR_XML_DETAIL, _("Missing or invalid 'end' attribute in in " " in in network %s"), networkName); goto cleanup; } } ret = 0; cleanup: VIR_FREE(addrStart); VIR_FREE(addrEnd); VIR_FREE(natAddrNodes); VIR_FREE(natPortNodes); ctxt->node = save; return ret; } static int virNetworkForwardDefParseXML(const char *networkName, xmlNodePtr node, xmlXPathContextPtr ctxt, virNetworkForwardDefPtr def) { size_t i; int ret = -1; xmlNodePtr *forwardIfNodes = NULL; xmlNodePtr *forwardPfNodes = NULL; xmlNodePtr *forwardAddrNodes = NULL; xmlNodePtr *forwardNatNodes = NULL; int nForwardIfs, nForwardAddrs, nForwardPfs, nForwardNats; char *forwardDev = NULL; char *forwardManaged = NULL; char *forwardDriverName = NULL; char *type = NULL; xmlNodePtr save = ctxt->node; ctxt->node = node; if (!(type = virXPathString("string(./@mode)", ctxt))) { def->type = VIR_NETWORK_FORWARD_NAT; } else { if ((def->type = virNetworkForwardTypeFromString(type)) < 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("unknown forwarding type '%s'"), type); goto cleanup; } VIR_FREE(type); } forwardManaged = virXPathString("string(./@managed)", ctxt); if (forwardManaged != NULL && STRCASEEQ(forwardManaged, "yes")) { def->managed = true; } forwardDriverName = virXPathString("string(./driver/@name)", ctxt); if (forwardDriverName) { int driverName = virNetworkForwardDriverNameTypeFromString(forwardDriverName); if (driverName <= 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Unknown forward " "in network %s"), forwardDriverName, networkName); goto cleanup; } def->driverName = driverName; } /* bridge and hostdev modes can use a pool of physical interfaces */ nForwardIfs = virXPathNodeSet("./interface", ctxt, &forwardIfNodes); if (nForwardIfs < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid element found in of network %s"), networkName); goto cleanup; } nForwardAddrs = virXPathNodeSet("./address", ctxt, &forwardAddrNodes); if (nForwardAddrs < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid
element found in of network %s"), networkName); goto cleanup; } nForwardPfs = virXPathNodeSet("./pf", ctxt, &forwardPfNodes); if (nForwardPfs < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid element found in of network %s"), networkName); goto cleanup; } nForwardNats = virXPathNodeSet("./nat", ctxt, &forwardNatNodes); if (nForwardNats < 0) { virReportError(VIR_ERR_XML_ERROR, _("invalid element found in of network %s"), networkName); goto cleanup; } else if (nForwardNats > 1) { virReportError(VIR_ERR_XML_ERROR, _("Only one element is allowed in of network %s"), networkName); goto cleanup; } else if (nForwardNats == 1) { if (virNetworkForwardNatDefParseXML(networkName, *forwardNatNodes, ctxt, def) < 0) goto cleanup; } forwardDev = virXPathString("string(./@dev)", ctxt); if (forwardDev && (nForwardAddrs > 0 || nForwardPfs > 0)) { virReportError(VIR_ERR_XML_ERROR, "%s", _("the 'dev' attribute cannot be used when " "
or sub-elements are present " "in network %s")); goto cleanup; } if (nForwardIfs > 0 || forwardDev) { if (VIR_ALLOC_N(def->ifs, MAX(nForwardIfs, 1)) < 0) goto cleanup; if (forwardDev) { def->ifs[0].device.dev = forwardDev; def->ifs[0].type = VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV; forwardDev = NULL; def->nifs++; } /* parse each */ for (i = 0; i < nForwardIfs; i++) { forwardDev = virXMLPropString(forwardIfNodes[i], "dev"); if (!forwardDev) { virReportError(VIR_ERR_XML_ERROR, _("Missing required dev attribute in " " element of network %s"), networkName); goto cleanup; } if ((i == 0) && (def->nifs == 1)) { /* both and are * present. If they don't match, it's an error. */ if (STRNEQ(forwardDev, def->ifs[0].device.dev)) { virReportError(VIR_ERR_XML_ERROR, _(" must match first " " in network %s"), def->ifs[0].device.dev, forwardDev, networkName); goto cleanup; } VIR_FREE(forwardDev); continue; } def->ifs[i].device.dev = forwardDev; forwardDev = NULL; def->ifs[i].type = VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV; def->nifs++; } } else if (nForwardAddrs > 0) { if (VIR_ALLOC_N(def->ifs, nForwardAddrs) < 0) goto cleanup; for (i = 0; i < nForwardAddrs; i++) { if (!(type = virXMLPropString(forwardAddrNodes[i], "type"))) { virReportError(VIR_ERR_XML_ERROR, _("missing address type in network %s"), networkName); goto cleanup; } if ((def->ifs[i].type = virNetworkForwardHostdevDeviceTypeFromString(type)) < 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("unknown address type '%s' in network %s"), type, networkName); goto cleanup; } switch (def->ifs[i].type) { case VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_PCI: if (virDevicePCIAddressParseXML(forwardAddrNodes[i], &def->ifs[i].device.pci) < 0) { goto cleanup; } break; /* Add USB case here if we ever find a reason to support it */ default: virReportError(VIR_ERR_XML_ERROR, _("unsupported address type '%s' in network %s"), type, networkName); goto cleanup; } VIR_FREE(type); def->nifs++; } } else if (nForwardPfs > 1) { virReportError(VIR_ERR_XML_ERROR, _("Only one element is allowed in of network %s"), networkName); goto cleanup; } else if (nForwardPfs == 1) { if (VIR_ALLOC_N(def->pfs, nForwardPfs) < 0) goto cleanup; forwardDev = virXMLPropString(*forwardPfNodes, "dev"); if (!forwardDev) { virReportError(VIR_ERR_XML_ERROR, _("Missing required dev attribute " "in element of network '%s'"), networkName); goto cleanup; } def->pfs->dev = forwardDev; forwardDev = NULL; def->npfs++; } ret = 0; cleanup: VIR_FREE(type); VIR_FREE(forwardDev); VIR_FREE(forwardManaged); VIR_FREE(forwardDriverName); VIR_FREE(forwardPfNodes); VIR_FREE(forwardIfNodes); VIR_FREE(forwardAddrNodes); VIR_FREE(forwardNatNodes); ctxt->node = save; return ret; } static virNetworkDefPtr virNetworkDefParseXML(xmlXPathContextPtr ctxt) { virNetworkDefPtr def; char *tmp = NULL; char *stp = NULL; xmlNodePtr *ipNodes = NULL; xmlNodePtr *routeNodes = NULL; xmlNodePtr *portGroupNodes = NULL; int nIps, nPortGroups, nRoutes; xmlNodePtr dnsNode = NULL; xmlNodePtr virtPortNode = NULL; xmlNodePtr forwardNode = NULL; char *ipv6nogwStr = NULL; char *trustGuestRxFilters = NULL; xmlNodePtr save = ctxt->node; xmlNodePtr bandwidthNode = NULL; xmlNodePtr vlanNode; if (VIR_ALLOC(def) < 0) return NULL; /* Extract network name */ def->name = virXPathString("string(./name[1])", ctxt); if (!def->name) { virReportError(VIR_ERR_NO_NAME, NULL); goto error; } /* Extract network uuid */ tmp = virXPathString("string(./uuid[1])", ctxt); if (!tmp) { if (virUUIDGenerate(def->uuid)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to generate UUID")); goto error; } } else { if (virUUIDParse(tmp, def->uuid) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed uuid element")); goto error; } VIR_FREE(tmp); def->uuid_specified = true; } /* check if definitions with no IPv6 gateway addresses is to * allow guest-to-guest communications. */ ipv6nogwStr = virXPathString("string(./@ipv6)", ctxt); if (ipv6nogwStr) { if (STREQ(ipv6nogwStr, "yes")) { def->ipv6nogw = true; } else if (STRNEQ(ipv6nogwStr, "no")) { virReportError(VIR_ERR_XML_ERROR, _("Invalid ipv6 setting '%s' in network '%s'"), ipv6nogwStr, def->name); goto error; } VIR_FREE(ipv6nogwStr); } trustGuestRxFilters = virXPathString("string(./@trustGuestRxFilters)", ctxt); if (trustGuestRxFilters) { if ((def->trustGuestRxFilters = virTristateBoolTypeFromString(trustGuestRxFilters)) <= 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid trustGuestRxFilters setting '%s' " "in network '%s'"), trustGuestRxFilters, def->name); goto error; } VIR_FREE(trustGuestRxFilters); } /* Parse network domain information */ def->domain = virXPathString("string(./domain[1]/@name)", ctxt); tmp = virXPathString("string(./domain[1]/@localOnly)", ctxt); if (tmp) { def->domainLocalOnly = virTristateBoolTypeFromString(tmp); if (def->domainLocalOnly <= 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid domain localOnly setting '%s' " "in network '%s'"), tmp, def->name); goto error; } VIR_FREE(tmp); } if ((bandwidthNode = virXPathNode("./bandwidth", ctxt)) && virNetDevBandwidthParse(&def->bandwidth, bandwidthNode, -1) < 0) goto error; vlanNode = virXPathNode("./vlan", ctxt); if (vlanNode && virNetDevVlanParse(vlanNode, ctxt, &def->vlan) < 0) goto error; /* Parse bridge information */ def->bridge = virXPathString("string(./bridge[1]/@name)", ctxt); stp = virXPathString("string(./bridge[1]/@stp)", ctxt); def->stp = (stp && STREQ(stp, "off")) ? false : true; tmp = virXPathString("string(./bridge[1]/@delay)", ctxt); if (tmp) { if (virStrToLong_ulp(tmp, NULL, 10, &def->delay) < 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid delay value in network '%s'"), def->name); goto error; } } VIR_FREE(tmp); tmp = virXPathString("string(./bridge[1]/@macTableManager)", ctxt); if (tmp) { if ((def->macTableManager = virNetworkBridgeMACTableManagerTypeFromString(tmp)) <= 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid macTableManager setting '%s' " "in network '%s'"), tmp, def->name); goto error; } VIR_FREE(tmp); } tmp = virXPathString("string(./mac[1]/@address)", ctxt); if (tmp) { if (virMacAddrParse(tmp, &def->mac) < 0) { virReportError(VIR_ERR_XML_ERROR, _("Invalid bridge mac address '%s' in network '%s'"), tmp, def->name); goto error; } if (virMacAddrIsMulticast(&def->mac)) { virReportError(VIR_ERR_XML_ERROR, _("Invalid multicast bridge mac address '%s' in network '%s'"), tmp, def->name); goto error; } VIR_FREE(tmp); def->mac_specified = true; } dnsNode = virXPathNode("./dns", ctxt); if (dnsNode != NULL && virNetworkDNSDefParseXML(def->name, dnsNode, ctxt, &def->dns) < 0) { goto error; } virtPortNode = virXPathNode("./virtualport", ctxt); if (virtPortNode && (!(def->virtPortProfile = virNetDevVPortProfileParse(virtPortNode, VIR_VPORT_XML_REQUIRE_TYPE)))) { goto error; } nPortGroups = virXPathNodeSet("./portgroup", ctxt, &portGroupNodes); if (nPortGroups < 0) goto error; if (nPortGroups > 0) { size_t i; /* allocate array to hold all the portgroups */ if (VIR_ALLOC_N(def->portGroups, nPortGroups) < 0) goto error; /* parse each portgroup */ for (i = 0; i < nPortGroups; i++) { if (virNetworkPortGroupParseXML(&def->portGroups[i], portGroupNodes[i], ctxt) < 0) goto error; def->nPortGroups++; } } VIR_FREE(portGroupNodes); nIps = virXPathNodeSet("./ip", ctxt, &ipNodes); if (nIps < 0) goto error; if (nIps > 0) { size_t i; /* allocate array to hold all the addrs */ if (VIR_ALLOC_N(def->ips, nIps) < 0) goto error; /* parse each addr */ for (i = 0; i < nIps; i++) { if (virNetworkIPDefParseXML(def->name, ipNodes[i], ctxt, &def->ips[i]) < 0) goto error; def->nips++; } } VIR_FREE(ipNodes); nRoutes = virXPathNodeSet("./route", ctxt, &routeNodes); if (nRoutes < 0) goto error; if (nRoutes > 0) { size_t i; /* allocate array to hold all the route definitions */ if (VIR_ALLOC_N(def->routes, nRoutes) < 0) goto error; /* parse each definition */ for (i = 0; i < nRoutes; i++) { virNetworkRouteDefPtr route = NULL; if (!(route = virNetworkRouteDefParseXML(def->name, routeNodes[i], ctxt))) goto error; def->routes[i] = route; def->nroutes++; } /* now validate the correctness of any static route gateways specified * * note: the parameters within each definition are verified/assumed valid; * the question being asked and answered here is if the specified gateway * is directly reachable from this bridge. */ nRoutes = def->nroutes; nIps = def->nips; for (i = 0; i < nRoutes; i++) { size_t j; virSocketAddr testAddr, testGw; bool addrMatch; virNetworkRouteDefPtr gwdef = def->routes[i]; virSocketAddrPtr gateway = virNetworkRouteDefGetGateway(gwdef); addrMatch = false; for (j = 0; j < nIps; j++) { virNetworkIpDefPtr def2 = &def->ips[j]; if (VIR_SOCKET_ADDR_FAMILY(gateway) != VIR_SOCKET_ADDR_FAMILY(&def2->address)) { continue; } int prefix = virNetworkIpDefPrefix(def2); virSocketAddrMaskByPrefix(&def2->address, prefix, &testAddr); virSocketAddrMaskByPrefix(gateway, prefix, &testGw); if (VIR_SOCKET_ADDR_VALID(&testAddr) && VIR_SOCKET_ADDR_VALID(&testGw) && virSocketAddrEqual(&testAddr, &testGw)) { addrMatch = true; break; } } if (!addrMatch) { char *gw = virSocketAddrFormat(gateway); virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("unreachable static route gateway '%s' specified for network '%s'"), gw, def->name); VIR_FREE(gw); goto error; } } } VIR_FREE(routeNodes); forwardNode = virXPathNode("./forward", ctxt); if (forwardNode && virNetworkForwardDefParseXML(def->name, forwardNode, ctxt, &def->forward) < 0) { goto error; } /* Validate some items in the main NetworkDef that need to align * with the chosen forward mode. */ switch (def->forward.type) { case VIR_NETWORK_FORWARD_NONE: break; case VIR_NETWORK_FORWARD_ROUTE: case VIR_NETWORK_FORWARD_NAT: /* It's pointless to specify L3 forwarding without specifying * the network we're on. */ if (def->nips == 0) { virReportError(VIR_ERR_XML_ERROR, _("%s forwarding requested, " "but no IP address provided for network '%s'"), virNetworkForwardTypeToString(def->forward.type), def->name); goto error; } if (def->forward.nifs > 1) { virReportError(VIR_ERR_XML_ERROR, _("multiple forwarding interfaces specified " "for network '%s', only one is supported"), def->name); goto error; } break; case VIR_NETWORK_FORWARD_PRIVATE: case VIR_NETWORK_FORWARD_VEPA: case VIR_NETWORK_FORWARD_PASSTHROUGH: case VIR_NETWORK_FORWARD_HOSTDEV: if (def->bridge) { virReportError(VIR_ERR_XML_ERROR, _("bridge name not allowed in %s mode (network '%s')"), virNetworkForwardTypeToString(def->forward.type), def->name); goto error; } if (def->macTableManager) { virReportError(VIR_ERR_XML_ERROR, _("bridge macTableManager setting not allowed " "in %s mode (network '%s')"), virNetworkForwardTypeToString(def->forward.type), def->name); goto error; } /* fall through to next case */ case VIR_NETWORK_FORWARD_BRIDGE: if (def->delay || stp) { virReportError(VIR_ERR_XML_ERROR, _("bridge delay/stp options only allowed in route, nat, and isolated mode, not in %s (network '%s')"), virNetworkForwardTypeToString(def->forward.type), def->name); goto error; } if (def->bridge && (def->forward.nifs || def->forward.npfs)) { virReportError(VIR_ERR_XML_ERROR, _("A network with forward mode='%s' can specify " "a bridge name or a forward dev, but not " "both (network '%s')"), virNetworkForwardTypeToString(def->forward.type), def->name); goto error; } break; } VIR_FREE(stp); ctxt->node = save; return def; error: VIR_FREE(tmp); VIR_FREE(routeNodes); VIR_FREE(stp); virNetworkDefFree(def); VIR_FREE(ipNodes); VIR_FREE(portGroupNodes); VIR_FREE(ipv6nogwStr); VIR_FREE(trustGuestRxFilters); ctxt->node = save; return NULL; } static virNetworkDefPtr virNetworkDefParse(const char *xmlStr, const char *filename) { xmlDocPtr xml; virNetworkDefPtr def = NULL; if ((xml = virXMLParse(filename, xmlStr, _("(network_definition)")))) { def = virNetworkDefParseNode(xml, xmlDocGetRootElement(xml)); xmlFreeDoc(xml); } return def; } virNetworkDefPtr virNetworkDefParseString(const char *xmlStr) { return virNetworkDefParse(xmlStr, NULL); } virNetworkDefPtr virNetworkDefParseFile(const char *filename) { return virNetworkDefParse(NULL, filename); } virNetworkDefPtr virNetworkDefParseNode(xmlDocPtr xml, xmlNodePtr root) { xmlXPathContextPtr ctxt = NULL; virNetworkDefPtr def = NULL; if (!xmlStrEqual(root->name, BAD_CAST "network")) { virReportError(VIR_ERR_XML_ERROR, _("unexpected root element <%s>, " "expecting "), root->name); return NULL; } ctxt = xmlXPathNewContext(xml); if (ctxt == NULL) { virReportOOMError(); goto cleanup; } ctxt->node = root; def = virNetworkDefParseXML(ctxt); cleanup: xmlXPathFreeContext(ctxt); return def; } static int virNetworkDNSDefFormat(virBufferPtr buf, const virNetworkDNSDef *def) { size_t i, j; if (!(def->forwardPlainNames || def->nfwds || def->nhosts || def->nsrvs || def->ntxts)) return 0; virBufferAddLit(buf, "forwardPlainNames) { const char *fwd = virTristateBoolTypeToString(def->forwardPlainNames); if (!fwd) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown forwardPlainNames type %d in network"), def->forwardPlainNames); return -1; } virBufferAsprintf(buf, " forwardPlainNames='%s'", fwd); if (!(def->nfwds || def->nhosts || def->nsrvs || def->ntxts)) { virBufferAddLit(buf, "/>\n"); return 0; } } virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 2); for (i = 0; i < def->nfwds; i++) { virBufferAsprintf(buf, "\n", def->forwarders[i]); } for (i = 0; i < def->ntxts; i++) { virBufferAsprintf(buf, "\n", def->txts[i].name, def->txts[i].value); } for (i = 0; i < def->nsrvs; i++) { if (def->srvs[i].service && def->srvs[i].protocol) { virBufferAsprintf(buf, "srvs[i].service, def->srvs[i].protocol); if (def->srvs[i].domain) virBufferAsprintf(buf, " domain='%s'", def->srvs[i].domain); if (def->srvs[i].target) virBufferAsprintf(buf, " target='%s'", def->srvs[i].target); if (def->srvs[i].port) virBufferAsprintf(buf, " port='%d'", def->srvs[i].port); if (def->srvs[i].priority) virBufferAsprintf(buf, " priority='%d'", def->srvs[i].priority); if (def->srvs[i].weight) virBufferAsprintf(buf, " weight='%d'", def->srvs[i].weight); virBufferAddLit(buf, "/>\n"); } } if (def->nhosts) { for (i = 0; i < def->nhosts; i++) { char *ip = virSocketAddrFormat(&def->hosts[i].ip); virBufferAsprintf(buf, "\n", ip); virBufferAdjustIndent(buf, 2); for (j = 0; j < def->hosts[i].nnames; j++) virBufferAsprintf(buf, "%s\n", def->hosts[i].names[j]); virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); VIR_FREE(ip); } } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); return 0; } static int virNetworkIpDefFormat(virBufferPtr buf, const virNetworkIpDef *def) { int result = -1; virBufferAddLit(buf, "family) virBufferAsprintf(buf, " family='%s'", def->family); if (VIR_SOCKET_ADDR_VALID(&def->address)) { char *addr = virSocketAddrFormat(&def->address); if (!addr) goto error; virBufferAsprintf(buf, " address='%s'", addr); VIR_FREE(addr); } if (VIR_SOCKET_ADDR_VALID(&def->netmask)) { char *addr = virSocketAddrFormat(&def->netmask); if (!addr) goto error; virBufferAsprintf(buf, " netmask='%s'", addr); VIR_FREE(addr); } if (def->prefix > 0) virBufferAsprintf(buf, " prefix='%u'", def->prefix); virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 2); if (def->tftproot) { virBufferEscapeString(buf, "\n", def->tftproot); } if ((def->nranges || def->nhosts)) { size_t i; virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); for (i = 0; i < def->nranges; i++) { char *saddr = virSocketAddrFormat(&def->ranges[i].start); if (!saddr) goto error; char *eaddr = virSocketAddrFormat(&def->ranges[i].end); if (!eaddr) { VIR_FREE(saddr); goto error; } virBufferAsprintf(buf, "\n", saddr, eaddr); VIR_FREE(saddr); VIR_FREE(eaddr); } for (i = 0; i < def->nhosts; i++) { virBufferAddLit(buf, "hosts[i].mac) virBufferAsprintf(buf, " mac='%s'", def->hosts[i].mac); if (def->hosts[i].id) virBufferAsprintf(buf, " id='%s'", def->hosts[i].id); if (def->hosts[i].name) virBufferAsprintf(buf, " name='%s'", def->hosts[i].name); if (VIR_SOCKET_ADDR_VALID(&def->hosts[i].ip)) { char *ipaddr = virSocketAddrFormat(&def->hosts[i].ip); if (!ipaddr) goto error; virBufferAsprintf(buf, " ip='%s'", ipaddr); VIR_FREE(ipaddr); } virBufferAddLit(buf, "/>\n"); } if (def->bootfile) { virBufferEscapeString(buf, "bootfile); if (VIR_SOCKET_ADDR_VALID(&def->bootserver)) { char *ipaddr = virSocketAddrFormat(&def->bootserver); if (!ipaddr) goto error; virBufferEscapeString(buf, " server='%s'", ipaddr); VIR_FREE(ipaddr); } virBufferAddLit(buf, "/>\n"); } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); result = 0; error: return result; } static int virPortGroupDefFormat(virBufferPtr buf, const virPortGroupDef *def) { virBufferAsprintf(buf, "name); if (def->isDefault) virBufferAddLit(buf, " default='yes'"); if (def->trustGuestRxFilters) virBufferAsprintf(buf, " trustGuestRxFilters='%s'", virTristateBoolTypeToString(def->trustGuestRxFilters)); virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 2); if (virNetDevVlanFormat(&def->vlan, buf) < 0) return -1; if (virNetDevVPortProfileFormat(def->virtPortProfile, buf) < 0) return -1; virNetDevBandwidthFormat(def->bandwidth, buf); virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); return 0; } static int virNetworkForwardNatDefFormat(virBufferPtr buf, const virNetworkForwardDef *fwd) { char *addrStart = NULL; char *addrEnd = NULL; int ret = -1; if (VIR_SOCKET_ADDR_VALID(&fwd->addr.start)) { addrStart = virSocketAddrFormat(&fwd->addr.start); if (!addrStart) goto cleanup; } if (VIR_SOCKET_ADDR_VALID(&fwd->addr.end)) { addrEnd = virSocketAddrFormat(&fwd->addr.end); if (!addrEnd) goto cleanup; } if (!addrEnd && !addrStart && !fwd->port.start && !fwd->port.end) return 0; virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); if (addrStart) { virBufferAsprintf(buf, "
\n"); } if (fwd->port.start || fwd->port.end) { virBufferAsprintf(buf, "port.start); if (fwd->port.end) virBufferAsprintf(buf, " end='%d'", fwd->port.end); virBufferAddLit(buf, "/>\n"); } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); ret = 0; cleanup: VIR_FREE(addrStart); VIR_FREE(addrEnd); return ret; } int virNetworkDefFormatBuf(virBufferPtr buf, const virNetworkDef *def, unsigned int flags) { const unsigned char *uuid; char uuidstr[VIR_UUID_STRING_BUFLEN]; size_t i; bool shortforward; virBufferAddLit(buf, "connections > 0)) virBufferAsprintf(buf, " connections='%d'", def->connections); if (def->ipv6nogw) virBufferAddLit(buf, " ipv6='yes'"); if (def->trustGuestRxFilters) virBufferAsprintf(buf, " trustGuestRxFilters='%s'", virTristateBoolTypeToString(def->trustGuestRxFilters)); virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 2); virBufferEscapeString(buf, "%s\n", def->name); uuid = def->uuid; virUUIDFormat(uuid, uuidstr); virBufferAsprintf(buf, "%s\n", uuidstr); if (def->forward.type != VIR_NETWORK_FORWARD_NONE) { const char *dev = NULL; if (!def->forward.npfs) dev = virNetworkDefForwardIf(def, 0); const char *mode = virNetworkForwardTypeToString(def->forward.type); if (!mode) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown forward type %d in network '%s'"), def->forward.type, def->name); goto error; } virBufferAddLit(buf, "forward.type == VIR_NETWORK_FORWARD_HOSTDEV) { if (def->forward.managed) virBufferAddLit(buf, " managed='yes'"); else virBufferAddLit(buf, " managed='no'"); } shortforward = !(def->forward.nifs || def->forward.npfs || VIR_SOCKET_ADDR_VALID(&def->forward.addr.start) || VIR_SOCKET_ADDR_VALID(&def->forward.addr.end) || def->forward.port.start || def->forward.port.end || (def->forward.driverName != VIR_NETWORK_FORWARD_DRIVER_NAME_DEFAULT)); virBufferAsprintf(buf, "%s>\n", shortforward ? "/" : ""); virBufferAdjustIndent(buf, 2); if (def->forward.driverName != VIR_NETWORK_FORWARD_DRIVER_NAME_DEFAULT) { const char *driverName = virNetworkForwardDriverNameTypeToString(def->forward.driverName); if (!driverName) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected hostdev driver name type %d "), def->forward.driverName); goto error; } virBufferAsprintf(buf, "\n", driverName); } if (def->forward.type == VIR_NETWORK_FORWARD_NAT) { if (virNetworkForwardNatDefFormat(buf, &def->forward) < 0) goto error; } /* For now, hard-coded to at most 1 forward.pfs */ if (def->forward.npfs) virBufferEscapeString(buf, "\n", def->forward.pfs[0].dev); if (def->forward.nifs && (!def->forward.npfs || !(flags & VIR_NETWORK_XML_INACTIVE))) { for (i = 0; i < def->forward.nifs; i++) { if (def->forward.ifs[i].type == VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV) { virBufferEscapeString(buf, "forward.ifs[i].device.dev); if (!(flags & VIR_NETWORK_XML_INACTIVE) && (def->forward.ifs[i].connections > 0)) { virBufferAsprintf(buf, " connections='%d'", def->forward.ifs[i].connections); } virBufferAddLit(buf, "/>\n"); } else { if (def->forward.ifs[i].type == VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_PCI) { if (virDevicePCIAddressFormat(buf, def->forward.ifs[i].device.pci, true) < 0) goto error; } } } } virBufferAdjustIndent(buf, -2); if (!shortforward) virBufferAddLit(buf, "\n"); } if (def->forward.type == VIR_NETWORK_FORWARD_NONE || def->forward.type == VIR_NETWORK_FORWARD_NAT || def->forward.type == VIR_NETWORK_FORWARD_ROUTE || def->bridge || def->macTableManager) { virBufferAddLit(buf, "bridge); if (def->forward.type == VIR_NETWORK_FORWARD_NONE || def->forward.type == VIR_NETWORK_FORWARD_NAT || def->forward.type == VIR_NETWORK_FORWARD_ROUTE) { virBufferAsprintf(buf, " stp='%s' delay='%ld'", def->stp ? "on" : "off", def->delay); } if (def->macTableManager) { virBufferAsprintf(buf, " macTableManager='%s'", virNetworkBridgeMACTableManagerTypeToString(def->macTableManager)); } virBufferAddLit(buf, "/>\n"); } if (def->mac_specified) { char macaddr[VIR_MAC_STRING_BUFLEN]; virMacAddrFormat(&def->mac, macaddr); virBufferAsprintf(buf, "\n", macaddr); } if (def->domain) { virBufferAsprintf(buf, "domain); /* default to "no", but don't format it in the XML */ if (def->domainLocalOnly) { const char *local = virTristateBoolTypeToString(def->domainLocalOnly); if (!local) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown localOnly type %d in network"), def->domainLocalOnly); return -1; } virBufferAsprintf(buf, " localOnly='%s'", local); } virBufferAddLit(buf, "/>\n"); } if (virNetworkDNSDefFormat(buf, &def->dns) < 0) goto error; if (virNetDevVlanFormat(&def->vlan, buf) < 0) goto error; if (virNetDevBandwidthFormat(def->bandwidth, buf) < 0) goto error; for (i = 0; i < def->nips; i++) { if (virNetworkIpDefFormat(buf, &def->ips[i]) < 0) goto error; } for (i = 0; i < def->nroutes; i++) { if (virNetworkRouteDefFormat(buf, def->routes[i]) < 0) goto error; } if (virNetDevVPortProfileFormat(def->virtPortProfile, buf) < 0) goto error; for (i = 0; i < def->nPortGroups; i++) if (virPortGroupDefFormat(buf, &def->portGroups[i]) < 0) goto error; virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); return 0; error: return -1; } char * virNetworkDefFormat(const virNetworkDef *def, unsigned int flags) { virBuffer buf = VIR_BUFFER_INITIALIZER; if (virNetworkDefFormatBuf(&buf, def, flags) < 0) goto error; if (virBufferCheckError(&buf) < 0) goto error; return virBufferContentAndReset(&buf); error: virBufferFreeAndReset(&buf); return NULL; } static char * virNetworkObjFormat(virNetworkObjPtr net, unsigned int flags) { virBuffer buf = VIR_BUFFER_INITIALIZER; char *class_id = virBitmapFormat(net->class_id); size_t i; if (!class_id) goto error; virBufferAddLit(&buf, "\n"); virBufferAdjustIndent(&buf, 2); virBufferAsprintf(&buf, "\n", class_id); virBufferAsprintf(&buf, "\n", net->floor_sum); VIR_FREE(class_id); for (i = 0; i < VIR_NETWORK_TAINT_LAST; i++) { if (net->taint & (1 << i)) virBufferAsprintf(&buf, "\n", virNetworkTaintTypeToString(i)); } if (virNetworkDefFormatBuf(&buf, net->def, flags) < 0) goto error; virBufferAdjustIndent(&buf, -2); virBufferAddLit(&buf, ""); if (virBufferCheckError(&buf) < 0) goto error; return virBufferContentAndReset(&buf); error: virBufferFreeAndReset(&buf); return NULL; } virPortGroupDefPtr virPortGroupFindByName(virNetworkDefPtr net, const char *portgroup) { size_t i; for (i = 0; i < net->nPortGroups; i++) { if (portgroup) { if (STREQ(portgroup, net->portGroups[i].name)) return &net->portGroups[i]; } else { if (net->portGroups[i].isDefault) return &net->portGroups[i]; } } return NULL; } int virNetworkSaveXML(const char *configDir, virNetworkDefPtr def, const char *xml) { char uuidstr[VIR_UUID_STRING_BUFLEN]; char *configFile = NULL; int ret = -1; if ((configFile = virNetworkConfigFile(configDir, def->name)) == NULL) goto cleanup; if (virFileMakePath(configDir) < 0) { virReportSystemError(errno, _("cannot create config directory '%s'"), configDir); goto cleanup; } virUUIDFormat(def->uuid, uuidstr); ret = virXMLSaveFile(configFile, virXMLPickShellSafeComment(def->name, uuidstr), "net-edit", xml); cleanup: VIR_FREE(configFile); return ret; } int virNetworkSaveConfig(const char *configDir, virNetworkDefPtr def) { int ret = -1; char *xml; if (!(xml = virNetworkDefFormat(def, VIR_NETWORK_XML_INACTIVE))) goto cleanup; if (virNetworkSaveXML(configDir, def, xml)) goto cleanup; ret = 0; cleanup: VIR_FREE(xml); return ret; } int virNetworkSaveStatus(const char *statusDir, virNetworkObjPtr network) { int ret = -1; int flags = 0; char *xml; if (!(xml = virNetworkObjFormat(network, flags))) goto cleanup; if (virNetworkSaveXML(statusDir, network->def, xml)) goto cleanup; ret = 0; cleanup: VIR_FREE(xml); return ret; } virNetworkObjPtr virNetworkLoadState(virNetworkObjListPtr nets, const char *stateDir, const char *name) { char *configFile = NULL; virNetworkDefPtr def = NULL; virNetworkObjPtr net = NULL; xmlDocPtr xml = NULL; xmlNodePtr node = NULL, *nodes = NULL; xmlXPathContextPtr ctxt = NULL; virBitmapPtr class_id_map = NULL; unsigned long long floor_sum_val = 0; unsigned int taint = 0; int n; size_t i; if ((configFile = virNetworkConfigFile(stateDir, name)) == NULL) goto error; if (!(xml = virXMLParseCtxt(configFile, NULL, _("(network status)"), &ctxt))) goto error; if (!(node = virXPathNode("//network", ctxt))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not find any 'network' element in status file")); goto error; } /* parse the definition first */ ctxt->node = node; if (!(def = virNetworkDefParseXML(ctxt))) goto error; if (!STREQ(name, def->name)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Network config filename '%s'" " does not match network name '%s'"), configFile, def->name); goto error; } /* now parse possible status data */ node = xmlDocGetRootElement(xml); if (xmlStrEqual(node->name, BAD_CAST "networkstatus")) { /* Newer network status file. Contains useful * info which are not to be found in bare config XML */ char *class_id = NULL; char *floor_sum = NULL; ctxt->node = node; if ((class_id = virXPathString("string(./class_id[1]/@bitmap)", ctxt))) { if (virBitmapParse(class_id, 0, &class_id_map, CLASS_ID_BITMAP_SIZE) < 0) { VIR_FREE(class_id); goto error; } } VIR_FREE(class_id); floor_sum = virXPathString("string(./floor[1]/@sum)", ctxt); if (floor_sum && virStrToLong_ull(floor_sum, NULL, 10, &floor_sum_val) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Malformed 'floor_sum' attribute: %s"), floor_sum); VIR_FREE(floor_sum); goto error; } VIR_FREE(floor_sum); if ((n = virXPathNodeSet("./taint", ctxt, &nodes)) < 0) goto error; for (i = 0; i < n; i++) { char *str = virXMLPropString(nodes[i], "flag"); if (str) { int flag = virNetworkTaintTypeFromString(str); if (flag < 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Unknown taint flag %s"), str); VIR_FREE(str); goto error; } VIR_FREE(str); /* Compute taint mask here. The network object does not * exist yet, so we can't use virNetworkObjtTaint. */ taint |= (1 << flag); } } VIR_FREE(nodes); } /* create the object */ if (!(net = virNetworkAssignDef(nets, def, true))) goto error; /* do not put any "goto error" below this comment */ /* assign status data stored in the network object */ if (class_id_map) { virBitmapFree(net->class_id); net->class_id = class_id_map; } if (floor_sum_val > 0) net->floor_sum = floor_sum_val; net->taint = taint; net->active = 1; /* any network with a state file is by definition active */ cleanup: VIR_FREE(configFile); xmlFreeDoc(xml); xmlXPathFreeContext(ctxt); return net; error: VIR_FREE(nodes); virBitmapFree(class_id_map); virNetworkDefFree(def); goto cleanup; } virNetworkObjPtr virNetworkLoadConfig(virNetworkObjListPtr nets, const char *configDir, const char *autostartDir, const char *name) { char *configFile = NULL, *autostartLink = NULL; virNetworkDefPtr def = NULL; virNetworkObjPtr net; int autostart; if ((configFile = virNetworkConfigFile(configDir, name)) == NULL) goto error; if ((autostartLink = virNetworkConfigFile(autostartDir, name)) == NULL) goto error; if ((autostart = virFileLinkPointsTo(autostartLink, configFile)) < 0) goto error; if (!(def = virNetworkDefParseFile(configFile))) goto error; if (!STREQ(name, def->name)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Network config filename '%s'" " does not match network name '%s'"), configFile, def->name); goto error; } if (def->forward.type == VIR_NETWORK_FORWARD_NONE || def->forward.type == VIR_NETWORK_FORWARD_NAT || def->forward.type == VIR_NETWORK_FORWARD_ROUTE) { if (!def->mac_specified) { virNetworkSetBridgeMacAddr(def); virNetworkSaveConfig(configDir, def); } /* Generate a bridge if none is specified, but don't check for collisions * if a bridge is hardcoded, so the network is at least defined. */ if (virNetworkSetBridgeName(nets, def, 0)) goto error; } else { /* Throw away MAC address for other forward types, * which could have been generated by older libvirt RPMs */ def->mac_specified = false; } if (!(net = virNetworkAssignDef(nets, def, false))) goto error; net->autostart = autostart; VIR_FREE(configFile); VIR_FREE(autostartLink); return net; error: VIR_FREE(configFile); VIR_FREE(autostartLink); virNetworkDefFree(def); return NULL; } int virNetworkLoadAllState(virNetworkObjListPtr nets, const char *stateDir) { DIR *dir; struct dirent *entry; int ret = -1; if (!(dir = opendir(stateDir))) { if (errno == ENOENT) return 0; virReportSystemError(errno, _("Failed to open dir '%s'"), stateDir); return -1; } while ((ret = virDirRead(dir, &entry, stateDir)) > 0) { virNetworkObjPtr net; if (entry->d_name[0] == '.') continue; if (!virFileStripSuffix(entry->d_name, ".xml")) continue; if ((net = virNetworkLoadState(nets, stateDir, entry->d_name))) virNetworkObjUnlock(net); } closedir(dir); return ret; } int virNetworkLoadAllConfigs(virNetworkObjListPtr nets, const char *configDir, const char *autostartDir) { DIR *dir; struct dirent *entry; int ret = -1; if (!(dir = opendir(configDir))) { if (errno == ENOENT) return 0; virReportSystemError(errno, _("Failed to open dir '%s'"), configDir); return -1; } while ((ret = virDirRead(dir, &entry, configDir)) > 0) { virNetworkObjPtr net; if (entry->d_name[0] == '.') continue; if (!virFileStripSuffix(entry->d_name, ".xml")) continue; /* NB: ignoring errors, so one malformed config doesn't kill the whole process */ net = virNetworkLoadConfig(nets, configDir, autostartDir, entry->d_name); if (net) virNetworkObjUnlock(net); } closedir(dir); return ret; } int virNetworkDeleteConfig(const char *configDir, const char *autostartDir, virNetworkObjPtr net) { char *configFile = NULL; char *autostartLink = NULL; int ret = -1; if ((configFile = virNetworkConfigFile(configDir, net->def->name)) == NULL) goto error; if ((autostartLink = virNetworkConfigFile(autostartDir, net->def->name)) == NULL) goto error; /* Not fatal if this doesn't work */ unlink(autostartLink); if (unlink(configFile) < 0) { virReportSystemError(errno, _("cannot remove config file '%s'"), configFile); goto error; } ret = 0; error: VIR_FREE(configFile); VIR_FREE(autostartLink); return ret; } char *virNetworkConfigFile(const char *dir, const char *name) { char *ret = NULL; ignore_value(virAsprintf(&ret, "%s/%s.xml", dir, name)); return ret; } int virNetworkBridgeInUse(virNetworkObjListPtr nets, const char *bridge, const char *skipname) { size_t i; unsigned int ret = 0; for (i = 0; i < nets->count; i++) { virNetworkObjLock(nets->objs[i]); if (nets->objs[i]->def->bridge && STREQ(nets->objs[i]->def->bridge, bridge) && !(skipname && STREQ(nets->objs[i]->def->name, skipname))) ret = 1; virNetworkObjUnlock(nets->objs[i]); } return ret; } char *virNetworkAllocateBridge(virNetworkObjListPtr nets, const char *template) { int id = 0; char *newname; if (!template) template = "virbr%d"; do { if (virAsprintf(&newname, template, id) < 0) return NULL; if (!virNetworkBridgeInUse(nets, newname, NULL)) return newname; VIR_FREE(newname); id++; } while (id <= MAX_BRIDGE_ID); virReportError(VIR_ERR_INTERNAL_ERROR, _("Bridge generation exceeded max id %d"), MAX_BRIDGE_ID); return NULL; } int virNetworkSetBridgeName(virNetworkObjListPtr nets, virNetworkDefPtr def, int check_collision) { int ret = -1; if (def->bridge && !strstr(def->bridge, "%d")) { /* We may want to skip collision detection in this case (ex. when * loading configs at daemon startup, so the network is at least * defined. */ if (check_collision && virNetworkBridgeInUse(nets, def->bridge, def->name)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("bridge name '%s' already in use."), def->bridge); goto error; } } else { /* Allocate a bridge name */ if (!(def->bridge = virNetworkAllocateBridge(nets, def->bridge))) goto error; } ret = 0; error: return ret; } void virNetworkSetBridgeMacAddr(virNetworkDefPtr def) { if (!def->mac_specified) { /* if the bridge doesn't have a mac address explicitly defined, * autogenerate a random one. */ virMacAddrGenerate((unsigned char[]){ 0x52, 0x54, 0 }, &def->mac); def->mac_specified = true; } } /* NetworkObj backend of the virNetworkUpdate API */ static void virNetworkDefUpdateNoSupport(virNetworkDefPtr def, const char *section) { virReportError(VIR_ERR_NO_SUPPORT, _("can't update '%s' section of network '%s'"), section, def->name); } static void virNetworkDefUpdateUnknownCommand(unsigned int command) { virReportError(VIR_ERR_NO_SUPPORT, _("unrecognized network update command code %d"), command); } static int virNetworkDefUpdateCheckElementName(virNetworkDefPtr def, xmlNodePtr node, const char *section) { if (!xmlStrEqual(node->name, BAD_CAST section)) { virReportError(VIR_ERR_XML_ERROR, _("unexpected element <%s>, expecting <%s>, " "while updating network '%s'"), node->name, section, def->name); return -1; } return 0; } static int virNetworkDefUpdateBridge(virNetworkDefPtr def, unsigned int command ATTRIBUTE_UNUSED, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { virNetworkDefUpdateNoSupport(def, "bridge"); return -1; } static int virNetworkDefUpdateDomain(virNetworkDefPtr def, unsigned int command ATTRIBUTE_UNUSED, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { virNetworkDefUpdateNoSupport(def, "domain"); return -1; } static int virNetworkDefUpdateIP(virNetworkDefPtr def, unsigned int command ATTRIBUTE_UNUSED, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { virNetworkDefUpdateNoSupport(def, "ip"); return -1; } static virNetworkIpDefPtr virNetworkIpDefByIndex(virNetworkDefPtr def, int parentIndex) { virNetworkIpDefPtr ipdef = NULL; size_t i; /* first find which ip element's dhcp host list to work on */ if (parentIndex >= 0) { ipdef = virNetworkDefGetIpByIndex(def, AF_UNSPEC, parentIndex); if (!(ipdef)) { virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't update dhcp host entry - no " "element found at index %d in network '%s'"), parentIndex, def->name); } return ipdef; } /* -1 means "find the most appropriate", which in this case * means the one and only that has element */ for (i = 0; (ipdef = virNetworkDefGetIpByIndex(def, AF_UNSPEC, i)); i++) { if (ipdef->nranges || ipdef->nhosts) break; } if (!ipdef) { ipdef = virNetworkDefGetIpByIndex(def, AF_INET, 0); if (!ipdef) ipdef = virNetworkDefGetIpByIndex(def, AF_INET6, 0); } if (!ipdef) { virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't update dhcp host entry - no " "element found in network '%s'"), def->name); } return ipdef; } static int virNetworkDefUpdateCheckMultiDHCP(virNetworkDefPtr def, virNetworkIpDefPtr ipdef) { int family = VIR_SOCKET_ADDR_FAMILY(&ipdef->address); size_t i; virNetworkIpDefPtr ip; for (i = 0; (ip = virNetworkDefGetIpByIndex(def, family, i)); i++) { if (ip != ipdef) { if (ip->nranges || ip->nhosts) { virReportError(VIR_ERR_OPERATION_INVALID, _("dhcp is supported only for a " "single %s address on each network"), (family == AF_INET) ? "IPv4" : "IPv6"); return -1; } } } return 0; } static int virNetworkDefUpdateIPDHCPHost(virNetworkDefPtr def, unsigned int command, int parentIndex, xmlXPathContextPtr ctxt, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { size_t i; int ret = -1; virNetworkIpDefPtr ipdef = virNetworkIpDefByIndex(def, parentIndex); virNetworkDHCPHostDef host; bool partialOkay = (command == VIR_NETWORK_UPDATE_COMMAND_DELETE); memset(&host, 0, sizeof(host)); if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "host") < 0) goto cleanup; /* ipdef is the ip element that needs its host array updated */ if (!ipdef) goto cleanup; if (virNetworkDHCPHostDefParseXML(def->name, ipdef, ctxt->node, &host, partialOkay) < 0) goto cleanup; if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) { /* search for the entry with this (ip|mac|name), * and update the IP+(mac|name) */ for (i = 0; i < ipdef->nhosts; i++) { if ((host.mac && ipdef->hosts[i].mac && !virMacAddrCompare(host.mac, ipdef->hosts[i].mac)) || (VIR_SOCKET_ADDR_VALID(&host.ip) && virSocketAddrEqual(&host.ip, &ipdef->hosts[i].ip)) || (host.name && STREQ_NULLABLE(host.name, ipdef->hosts[i].name))) { break; } } if (i == ipdef->nhosts) { char *ip = virSocketAddrFormat(&host.ip); virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't locate an existing dhcp host entry with " "\"mac='%s'\" \"name='%s'\" \"ip='%s'\" in" " network '%s'"), host.mac ? host.mac : _("unknown"), host.name, ip ? ip : _("unknown"), def->name); VIR_FREE(ip); goto cleanup; } /* clear the existing hosts entry, move the new one in its place, * then clear out the extra copy to get rid of the duplicate pointers * to its data (mac and name strings). */ virNetworkDHCPHostDefClear(&ipdef->hosts[i]); ipdef->hosts[i] = host; memset(&host, 0, sizeof(host)); } else if ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) || (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST)) { if (virNetworkDefUpdateCheckMultiDHCP(def, ipdef) < 0) goto cleanup; /* log error if an entry with same name/address/ip already exists */ for (i = 0; i < ipdef->nhosts; i++) { if ((host.mac && ipdef->hosts[i].mac && !virMacAddrCompare(host.mac, ipdef->hosts[i].mac)) || (host.name && STREQ_NULLABLE(host.name, ipdef->hosts[i].name)) || (VIR_SOCKET_ADDR_VALID(&host.ip) && virSocketAddrEqual(&host.ip, &ipdef->hosts[i].ip))) { char *ip = virSocketAddrFormat(&host.ip); virReportError(VIR_ERR_OPERATION_INVALID, _("there is an existing dhcp host entry in " "network '%s' that matches " "\"\""), def->name, host.mac ? host.mac : _("unknown"), host.name, ip ? ip : _("unknown")); VIR_FREE(ip); goto cleanup; } } /* add to beginning/end of list */ if (VIR_INSERT_ELEMENT(ipdef->hosts, command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ? 0 : ipdef->nhosts, ipdef->nhosts, host) < 0) goto cleanup; } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) { /* find matching entry - all specified attributes must match */ for (i = 0; i < ipdef->nhosts; i++) { if ((!host.mac || !ipdef->hosts[i].mac || !virMacAddrCompare(host.mac, ipdef->hosts[i].mac)) && (!host.name || STREQ_NULLABLE(host.name, ipdef->hosts[i].name)) && (!VIR_SOCKET_ADDR_VALID(&host.ip) || virSocketAddrEqual(&host.ip, &ipdef->hosts[i].ip))) { break; } } if (i == ipdef->nhosts) { virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't locate a matching dhcp host entry " "in network '%s'"), def->name); goto cleanup; } /* remove it */ virNetworkDHCPHostDefClear(&ipdef->hosts[i]); VIR_DELETE_ELEMENT(ipdef->hosts, i, ipdef->nhosts); } else { virNetworkDefUpdateUnknownCommand(command); goto cleanup; } ret = 0; cleanup: virNetworkDHCPHostDefClear(&host); return ret; } static int virNetworkDefUpdateIPDHCPRange(virNetworkDefPtr def, unsigned int command, int parentIndex, xmlXPathContextPtr ctxt, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { size_t i; int ret = -1; virNetworkIpDefPtr ipdef = virNetworkIpDefByIndex(def, parentIndex); virSocketAddrRange range; memset(&range, 0, sizeof(range)); if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "range") < 0) goto cleanup; /* ipdef is the ip element that needs its range array updated */ if (!ipdef) goto cleanup; /* parse the xml into a virSocketAddrRange */ if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) { virReportError(VIR_ERR_NO_SUPPORT, "%s", _("dhcp ranges cannot be modified, " "only added or deleted")); goto cleanup; } if (virSocketAddrRangeParseXML(def->name, ctxt->node, &range) < 0) goto cleanup; /* check if an entry with same name/address/ip already exists */ for (i = 0; i < ipdef->nranges; i++) { if (virSocketAddrEqual(&range.start, &ipdef->ranges[i].start) && virSocketAddrEqual(&range.end, &ipdef->ranges[i].end)) { break; } } if ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) || (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST)) { if (virNetworkDefUpdateCheckMultiDHCP(def, ipdef) < 0) goto cleanup; if (i < ipdef->nranges) { char *startip = virSocketAddrFormat(&range.start); char *endip = virSocketAddrFormat(&range.end); virReportError(VIR_ERR_OPERATION_INVALID, _("there is an existing dhcp range entry in " "network '%s' that matches " "\"\""), def->name, startip ? startip : "unknown", endip ? endip : "unknown"); VIR_FREE(startip); VIR_FREE(endip); goto cleanup; } /* add to beginning/end of list */ if (VIR_INSERT_ELEMENT(ipdef->ranges, command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ? 0 : ipdef->nranges, ipdef->nranges, range) < 0) goto cleanup; } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) { if (i == ipdef->nranges) { virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't locate a matching dhcp range entry " "in network '%s'"), def->name); goto cleanup; } /* remove it */ /* NB: nothing to clear from a RangeDef that's being freed */ VIR_DELETE_ELEMENT(ipdef->ranges, i, ipdef->nranges); } else { virNetworkDefUpdateUnknownCommand(command); goto cleanup; } ret = 0; cleanup: return ret; } static int virNetworkDefUpdateForward(virNetworkDefPtr def, unsigned int command ATTRIBUTE_UNUSED, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { virNetworkDefUpdateNoSupport(def, "forward"); return -1; } static int virNetworkDefUpdateForwardInterface(virNetworkDefPtr def, unsigned int command, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { size_t i; int ret = -1; virNetworkForwardIfDef iface; memset(&iface, 0, sizeof(iface)); if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "interface") < 0) goto cleanup; if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) { virReportError(VIR_ERR_NO_SUPPORT, "%s", _("forward interface entries cannot be modified, " "only added or deleted")); goto cleanup; } /* parsing this is so simple that it doesn't have its own function */ iface.type = VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV; if (!(iface.device.dev = virXMLPropString(ctxt->node, "dev"))) { virReportError(VIR_ERR_XML_ERROR, "%s", _("missing dev attribute in element")); goto cleanup; } /* check if an with same dev name already exists */ for (i = 0; i < def->forward.nifs; i++) { if (def->forward.ifs[i].type == VIR_NETWORK_FORWARD_HOSTDEV_DEVICE_NETDEV && STREQ(iface.device.dev, def->forward.ifs[i].device.dev)) break; } if ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) || (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST)) { if (i < def->forward.nifs) { virReportError(VIR_ERR_OPERATION_INVALID, _("there is an existing interface entry " "in network '%s' that matches " "\"\""), def->name, iface.device.dev); goto cleanup; } /* add to beginning/end of list */ if (VIR_INSERT_ELEMENT(def->forward.ifs, command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ? 0 : def->forward.nifs, def->forward.nifs, iface) < 0) goto cleanup; } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) { if (i == def->forward.nifs) { virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't find an interface entry " "in network '%s' matching "), def->name, iface.device.dev); goto cleanup; } /* fail if the interface is being used */ if (def->forward.ifs[i].connections > 0) { virReportError(VIR_ERR_OPERATION_INVALID, _("unable to delete interface '%s' " "in network '%s'. It is currently being used " " by %d domains."), iface.device.dev, def->name, def->forward.ifs[i].connections); goto cleanup; } /* remove it */ virNetworkForwardIfDefClear(&def->forward.ifs[i]); VIR_DELETE_ELEMENT(def->forward.ifs, i, def->forward.nifs); } else { virNetworkDefUpdateUnknownCommand(command); goto cleanup; } ret = 0; cleanup: virNetworkForwardIfDefClear(&iface); return ret; } static int virNetworkDefUpdateForwardPF(virNetworkDefPtr def, unsigned int command ATTRIBUTE_UNUSED, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { virNetworkDefUpdateNoSupport(def, "forward pf"); return -1; } static int virNetworkDefUpdatePortGroup(virNetworkDefPtr def, unsigned int command, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { size_t i; int foundName = -1, foundDefault = -1; int ret = -1; virPortGroupDef portgroup; memset(&portgroup, 0, sizeof(portgroup)); if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "portgroup") < 0) goto cleanup; if (virNetworkPortGroupParseXML(&portgroup, ctxt->node, ctxt) < 0) goto cleanup; /* check if a portgroup with same name already exists */ for (i = 0; i < def->nPortGroups; i++) { if (STREQ(portgroup.name, def->portGroups[i].name)) foundName = i; if (def->portGroups[i].isDefault) foundDefault = i; } if (foundName == -1 && ((command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) || (command == VIR_NETWORK_UPDATE_COMMAND_DELETE))) { virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't find a portgroup entry " "in network '%s' matching "), def->name, portgroup.name); goto cleanup; } else if (foundName >= 0 && ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) || (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST))) { virReportError(VIR_ERR_OPERATION_INVALID, _("there is an existing portgroup entry in " "network '%s' that matches " "\"\""), def->name, portgroup.name); goto cleanup; } /* if there is already a different default, we can't make this * one the default. */ if (command != VIR_NETWORK_UPDATE_COMMAND_DELETE && portgroup.isDefault && foundDefault >= 0 && foundDefault != foundName) { virReportError(VIR_ERR_OPERATION_INVALID, _("a different portgroup entry in " "network '%s' is already set as the default. " "Only one default is allowed."), def->name); goto cleanup; } if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) { /* replace existing entry */ virPortGroupDefClear(&def->portGroups[foundName]); def->portGroups[foundName] = portgroup; memset(&portgroup, 0, sizeof(portgroup)); } else if ((command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST) || (command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST)) { /* add to beginning/end of list */ if (VIR_INSERT_ELEMENT(def->portGroups, command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ? 0 : def->nPortGroups, def->nPortGroups, portgroup) < 0) goto cleanup; } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) { /* remove it */ virPortGroupDefClear(&def->portGroups[foundName]); VIR_DELETE_ELEMENT(def->portGroups, foundName, def->nPortGroups); } else { virNetworkDefUpdateUnknownCommand(command); goto cleanup; } ret = 0; cleanup: virPortGroupDefClear(&portgroup); return ret; } static int virNetworkDefUpdateDNSHost(virNetworkDefPtr def, unsigned int command ATTRIBUTE_UNUSED, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { size_t i, j, k; int foundIdx = -1, ret = -1; virNetworkDNSDefPtr dns = &def->dns; virNetworkDNSHostDef host; bool isAdd = (command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST || command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST); int foundCt = 0; memset(&host, 0, sizeof(host)); if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) { virReportError(VIR_ERR_NO_SUPPORT, "%s", _("DNS HOST records cannot be modified, " "only added or deleted")); goto cleanup; } if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "host") < 0) goto cleanup; if (virNetworkDNSHostDefParseXML(def->name, ctxt->node, &host, !isAdd) < 0) goto cleanup; for (i = 0; i < dns->nhosts; i++) { bool foundThisTime = false; if (virSocketAddrEqual(&host.ip, &dns->hosts[i].ip)) foundThisTime = true; for (j = 0; j < host.nnames && !foundThisTime; j++) { for (k = 0; k < dns->hosts[i].nnames && !foundThisTime; k++) { if (STREQ(host.names[j], dns->hosts[i].names[k])) foundThisTime = true; } } if (foundThisTime) { foundCt++; foundIdx = i; } } if (isAdd) { if (foundCt > 0) { virReportError(VIR_ERR_OPERATION_INVALID, _("there is already at least one DNS HOST " "record with a matching field in network %s"), def->name); goto cleanup; } /* add to beginning/end of list */ if (VIR_INSERT_ELEMENT(dns->hosts, command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ? 0 : dns->nhosts, dns->nhosts, host) < 0) goto cleanup; } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) { if (foundCt == 0) { virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't locate a matching DNS HOST " "record in network %s"), def->name); goto cleanup; } if (foundCt > 1) { virReportError(VIR_ERR_OPERATION_INVALID, _("multiple matching DNS HOST records were " "found in network %s"), def->name); goto cleanup; } /* remove it */ virNetworkDNSHostDefClear(&dns->hosts[foundIdx]); VIR_DELETE_ELEMENT(dns->hosts, foundIdx, dns->nhosts); } else { virNetworkDefUpdateUnknownCommand(command); goto cleanup; } ret = 0; cleanup: virNetworkDNSHostDefClear(&host); return ret; } static int virNetworkDefUpdateDNSSrv(virNetworkDefPtr def, unsigned int command ATTRIBUTE_UNUSED, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { size_t i; int foundIdx = -1, ret = -1; virNetworkDNSDefPtr dns = &def->dns; virNetworkDNSSrvDef srv; bool isAdd = (command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST || command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST); int foundCt = 0; memset(&srv, 0, sizeof(srv)); if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) { virReportError(VIR_ERR_NO_SUPPORT, "%s", _("DNS SRV records cannot be modified, " "only added or deleted")); goto cleanup; } if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "srv") < 0) goto cleanup; if (virNetworkDNSSrvDefParseXML(def->name, ctxt->node, ctxt, &srv, !isAdd) < 0) goto cleanup; for (i = 0; i < dns->nsrvs; i++) { if ((!srv.domain || STREQ_NULLABLE(srv.domain, dns->srvs[i].domain)) && (!srv.service || STREQ_NULLABLE(srv.service, dns->srvs[i].service)) && (!srv.protocol || STREQ_NULLABLE(srv.protocol, dns->srvs[i].protocol)) && (!srv.target || STREQ_NULLABLE(srv.target, dns->srvs[i].target))) { foundCt++; foundIdx = i; } } if (isAdd) { if (foundCt > 0) { virReportError(VIR_ERR_OPERATION_INVALID, _("there is already at least one DNS SRV " "record matching all specified fields in network %s"), def->name); goto cleanup; } /* add to beginning/end of list */ if (VIR_INSERT_ELEMENT(dns->srvs, command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ? 0 : dns->nsrvs, dns->nsrvs, srv) < 0) goto cleanup; } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) { if (foundCt == 0) { virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't locate a matching DNS SRV " "record in network %s"), def->name); goto cleanup; } if (foundCt > 1) { virReportError(VIR_ERR_OPERATION_INVALID, _("multiple DNS SRV records matching all specified " "fields were found in network %s"), def->name); goto cleanup; } /* remove it */ virNetworkDNSSrvDefClear(&dns->srvs[foundIdx]); VIR_DELETE_ELEMENT(dns->srvs, foundIdx, dns->nsrvs); } else { virNetworkDefUpdateUnknownCommand(command); goto cleanup; } ret = 0; cleanup: virNetworkDNSSrvDefClear(&srv); return ret; } static int virNetworkDefUpdateDNSTxt(virNetworkDefPtr def, unsigned int command ATTRIBUTE_UNUSED, int parentIndex ATTRIBUTE_UNUSED, xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, /* virNetworkUpdateFlags */ unsigned int fflags ATTRIBUTE_UNUSED) { int foundIdx, ret = -1; virNetworkDNSDefPtr dns = &def->dns; virNetworkDNSTxtDef txt; bool isAdd = (command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST || command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST); memset(&txt, 0, sizeof(txt)); if (command == VIR_NETWORK_UPDATE_COMMAND_MODIFY) { virReportError(VIR_ERR_NO_SUPPORT, "%s", _("DNS TXT records cannot be modified, " "only added or deleted")); goto cleanup; } if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "txt") < 0) goto cleanup; if (virNetworkDNSTxtDefParseXML(def->name, ctxt->node, &txt, !isAdd) < 0) goto cleanup; for (foundIdx = 0; foundIdx < dns->ntxts; foundIdx++) { if (STREQ(txt.name, dns->txts[foundIdx].name)) break; } if (isAdd) { if (foundIdx < dns->ntxts) { virReportError(VIR_ERR_OPERATION_INVALID, _("there is already a DNS TXT record " "with name '%s' in network %s"), txt.name, def->name); goto cleanup; } /* add to beginning/end of list */ if (VIR_INSERT_ELEMENT(dns->txts, command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST ? 0 : dns->ntxts, dns->ntxts, txt) < 0) goto cleanup; } else if (command == VIR_NETWORK_UPDATE_COMMAND_DELETE) { if (foundIdx == dns->ntxts) { virReportError(VIR_ERR_OPERATION_INVALID, _("couldn't locate a matching DNS TXT " "record in network %s"), def->name); goto cleanup; } /* remove it */ virNetworkDNSTxtDefClear(&dns->txts[foundIdx]); VIR_DELETE_ELEMENT(dns->txts, foundIdx, dns->ntxts); } else { virNetworkDefUpdateUnknownCommand(command); goto cleanup; } ret = 0; cleanup: virNetworkDNSTxtDefClear(&txt); return ret; } int virNetworkDefUpdateSection(virNetworkDefPtr def, unsigned int command, /* virNetworkUpdateCommand */ unsigned int section, /* virNetworkUpdateSection */ int parentIndex, const char *xml, unsigned int flags) /* virNetworkUpdateFlags */ { int ret = -1; xmlDocPtr doc; xmlXPathContextPtr ctxt = NULL; if (!(doc = virXMLParseStringCtxt(xml, _("network_update_xml"), &ctxt))) goto cleanup; switch (section) { case VIR_NETWORK_SECTION_BRIDGE: ret = virNetworkDefUpdateBridge(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_DOMAIN: ret = virNetworkDefUpdateDomain(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_IP: ret = virNetworkDefUpdateIP(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_IP_DHCP_HOST: ret = virNetworkDefUpdateIPDHCPHost(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_IP_DHCP_RANGE: ret = virNetworkDefUpdateIPDHCPRange(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_FORWARD: ret = virNetworkDefUpdateForward(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_FORWARD_INTERFACE: ret = virNetworkDefUpdateForwardInterface(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_FORWARD_PF: ret = virNetworkDefUpdateForwardPF(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_PORTGROUP: ret = virNetworkDefUpdatePortGroup(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_DNS_HOST: ret = virNetworkDefUpdateDNSHost(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_DNS_TXT: ret = virNetworkDefUpdateDNSTxt(def, command, parentIndex, ctxt, flags); break; case VIR_NETWORK_SECTION_DNS_SRV: ret = virNetworkDefUpdateDNSSrv(def, command, parentIndex, ctxt, flags); break; default: virReportError(VIR_ERR_NO_SUPPORT, "%s", _("can't update unrecognized section of network")); break; } cleanup: xmlFreeDoc(doc); xmlXPathFreeContext(ctxt); return ret; } /* * virNetworkObjUpdate: * * Apply the supplied update to the given virNetworkObj. Except for * @network pointing to an actual network object rather than the * opaque virNetworkPtr, parameters are identical to the public API * virNetworkUpdate. * * The original virNetworkDefs are copied, and all modifications made * to these copies. The originals are replaced with the copies only * after success has been guaranteed. * * Returns: -1 on error, 0 on success. */ int virNetworkObjUpdate(virNetworkObjPtr network, unsigned int command, /* virNetworkUpdateCommand */ unsigned int section, /* virNetworkUpdateSection */ int parentIndex, const char *xml, unsigned int flags) /* virNetworkUpdateFlags */ { int ret = -1; virNetworkDefPtr livedef = NULL, configdef = NULL; /* normalize config data, and check for common invalid requests. */ if (virNetworkConfigChangeSetup(network, flags) < 0) goto cleanup; if (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE) { virNetworkDefPtr checkdef; /* work on a copy of the def */ if (!(livedef = virNetworkDefCopy(network->def, 0))) goto cleanup; if (virNetworkDefUpdateSection(livedef, command, section, parentIndex, xml, flags) < 0) { goto cleanup; } /* run a final format/parse cycle to make sure we didn't * add anything illegal to the def */ if (!(checkdef = virNetworkDefCopy(livedef, 0))) goto cleanup; virNetworkDefFree(checkdef); } if (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) { virNetworkDefPtr checkdef; /* work on a copy of the def */ if (!(configdef = virNetworkDefCopy(virNetworkObjGetPersistentDef(network), VIR_NETWORK_XML_INACTIVE))) { goto cleanup; } if (virNetworkDefUpdateSection(configdef, command, section, parentIndex, xml, flags) < 0) { goto cleanup; } if (!(checkdef = virNetworkDefCopy(configdef, VIR_NETWORK_XML_INACTIVE))) { goto cleanup; } virNetworkDefFree(checkdef); } if (configdef) { /* successfully modified copy, now replace original */ if (virNetworkObjReplacePersistentDef(network, configdef) < 0) goto cleanup; configdef = NULL; } if (livedef) { /* successfully modified copy, now replace original */ virNetworkDefFree(network->def); network->def = livedef; livedef = NULL; } ret = 0; cleanup: virNetworkDefFree(livedef); virNetworkDefFree(configdef); return ret; } /* * virNetworkObjIsDuplicate: * @doms : virNetworkObjListPtr to search * @def : virNetworkDefPtr definition of network to lookup * @check_active: If true, ensure that network is not active * * Returns: -1 on error * 0 if network is new * 1 if network is a duplicate */ int virNetworkObjIsDuplicate(virNetworkObjListPtr doms, virNetworkDefPtr def, bool check_active) { int ret = -1; virNetworkObjPtr vm = NULL; /* See if a VM with matching UUID already exists */ vm = virNetworkFindByUUID(doms, def->uuid); if (vm) { /* UUID matches, but if names don't match, refuse it */ if (STRNEQ(vm->def->name, def->name)) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(vm->def->uuid, uuidstr); virReportError(VIR_ERR_OPERATION_FAILED, _("network '%s' is already defined with uuid %s"), vm->def->name, uuidstr); goto cleanup; } if (check_active) { /* UUID & name match, but if VM is already active, refuse it */ if (virNetworkObjIsActive(vm)) { virReportError(VIR_ERR_OPERATION_INVALID, _("network is already active as '%s'"), vm->def->name); goto cleanup; } } ret = 1; } else { /* UUID does not match, but if a name matches, refuse it */ vm = virNetworkFindByName(doms, def->name); if (vm) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(vm->def->uuid, uuidstr); virReportError(VIR_ERR_OPERATION_FAILED, _("network '%s' already exists with uuid %s"), def->name, uuidstr); goto cleanup; } ret = 0; } cleanup: if (vm) virNetworkObjUnlock(vm); return ret; } void virNetworkObjLock(virNetworkObjPtr obj) { virMutexLock(&obj->lock); } void virNetworkObjUnlock(virNetworkObjPtr obj) { virMutexUnlock(&obj->lock); } #define MATCH(FLAG) (flags & (FLAG)) static bool virNetworkMatch(virNetworkObjPtr netobj, unsigned int flags) { /* filter by active state */ if (MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_ACTIVE) && !((MATCH(VIR_CONNECT_LIST_NETWORKS_ACTIVE) && virNetworkObjIsActive(netobj)) || (MATCH(VIR_CONNECT_LIST_NETWORKS_INACTIVE) && !virNetworkObjIsActive(netobj)))) return false; /* filter by persistence */ if (MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_PERSISTENT) && !((MATCH(VIR_CONNECT_LIST_NETWORKS_PERSISTENT) && netobj->persistent) || (MATCH(VIR_CONNECT_LIST_NETWORKS_TRANSIENT) && !netobj->persistent))) return false; /* filter by autostart option */ if (MATCH(VIR_CONNECT_LIST_NETWORKS_FILTERS_AUTOSTART) && !((MATCH(VIR_CONNECT_LIST_NETWORKS_AUTOSTART) && netobj->autostart) || (MATCH(VIR_CONNECT_LIST_NETWORKS_NO_AUTOSTART) && !netobj->autostart))) return false; return true; } #undef MATCH int virNetworkObjListExport(virConnectPtr conn, virNetworkObjList netobjs, virNetworkPtr **nets, virNetworkObjListFilter filter, unsigned int flags) { virNetworkPtr *tmp_nets = NULL; virNetworkPtr net = NULL; int nnets = 0; int ret = -1; size_t i; if (nets && VIR_ALLOC_N(tmp_nets, netobjs.count + 1) < 0) goto cleanup; for (i = 0; i < netobjs.count; i++) { virNetworkObjPtr netobj = netobjs.objs[i]; virNetworkObjLock(netobj); if ((!filter || filter(conn, netobj->def)) && virNetworkMatch(netobj, flags)) { if (nets) { if (!(net = virGetNetwork(conn, netobj->def->name, netobj->def->uuid))) { virNetworkObjUnlock(netobj); goto cleanup; } tmp_nets[nnets] = net; } nnets++; } virNetworkObjUnlock(netobj); } if (tmp_nets) { /* trim the array to the final size */ ignore_value(VIR_REALLOC_N(tmp_nets, nnets + 1)); *nets = tmp_nets; tmp_nets = NULL; } ret = nnets; cleanup: if (tmp_nets) { for (i = 0; i < nnets; i++) virObjectUnref(tmp_nets[i]); } VIR_FREE(tmp_nets); return ret; }