libvirt/src/conf/network_conf.c
Laine Stump 5754dbd56d Give each virtual network bridge its own fixed MAC address
This fixes https://bugzilla.redhat.com/show_bug.cgi?id=609463

The problem was that, since a bridge always acquires the MAC address
of the connected interface with the numerically lowest MAC, as guests
are started and stopped, it was possible for the MAC address to change
over time, and this change in the network was being detected by
Windows 7 (it sees the MAC of the default route change), so on each
reboot it would bring up a dialog box asking about this "new network".

The solution is to create a dummy tap interface with a MAC guaranteed
to be lower than any guest interface's MAC, and attach that tap to the
bridge as soon as it's created. Since all guest MAC addresses start
with 0xFE, we can just generate a MAC with the standard "0x52, 0x54,
0" prefix, and it's guaranteed to always win (physical interfaces are
never connected to these bridges, so we don't need to worry about
competing numerically with them).

Note that the dummy tap is never set to IFF_UP state - that's not
necessary in order for the bridge to take its MAC, and not setting it
to UP eliminates the clutter of having an (eg) "virbr0-nic" displayed
in the output of the ifconfig command.

I chose to not auto-generate the MAC address in the network XML
parser, as there are likely to be consumers of that API that don't
need or want to have a MAC address associated with the
bridge.

Instead, in bridge_driver.c when the network is being defined, if
there is no MAC, one is generated. To account for virtual network
configs that already exist when upgrading from an older version of
libvirt, I've added a %post script to the specfile that searches for
all network definitions in both the config directory
(/etc/libvirt/qemu/networks) and the state directory
(/var/lib/libvirt/network) that are missing a mac address, generates a
random address, and adds it to the config (and a matching address to
the state file, if there is one).

docs/formatnetwork.html.in: document <mac address.../>
docs/schemas/network.rng: add nac address to schema
libvirt.spec.in: %post script to update existing networks
src/conf/network_conf.[ch]: parse and format <mac address.../>
src/libvirt_private.syms: export a couple private symbols we need
src/network/bridge_driver.c:
    auto-generate mac address when needed,
    create dummy interface if mac address is present.
tests/networkxml2xmlin/isolated-network.xml
tests/networkxml2xmlin/routed-network.xml
tests/networkxml2xmlout/isolated-network.xml
tests/networkxml2xmlout/routed-network.xml: add mac address to some tests
2011-02-17 13:36:32 -05:00

1271 lines
37 KiB
C

/*
* network_conf.c: network XML handling
*
* Copyright (C) 2006-2011 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#include <config.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <dirent.h>
#include "virterror_internal.h"
#include "datatypes.h"
#include "network_conf.h"
#include "network.h"
#include "memory.h"
#include "xml.h"
#include "uuid.h"
#include "util.h"
#include "buf.h"
#include "c-ctype.h"
#include "files.h"
#define MAX_BRIDGE_ID 256
#define VIR_FROM_THIS VIR_FROM_NETWORK
VIR_ENUM_DECL(virNetworkForward)
VIR_ENUM_IMPL(virNetworkForward,
VIR_NETWORK_FORWARD_LAST,
"none", "nat", "route" )
#define virNetworkReportError(code, ...) \
virReportErrorHelper(NULL, VIR_FROM_NETWORK, code, __FILE__, \
__FUNCTION__, __LINE__, __VA_ARGS__)
virNetworkObjPtr virNetworkFindByUUID(const virNetworkObjListPtr nets,
const unsigned char *uuid)
{
unsigned int 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(const virNetworkObjListPtr nets,
const char *name)
{
unsigned int 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 virNetworkIpDefClear(virNetworkIpDefPtr def)
{
int ii;
VIR_FREE(def->family);
VIR_FREE(def->ranges);
for (ii = 0 ; ii < def->nhosts && def->hosts ; ii++) {
VIR_FREE(def->hosts[ii].mac);
VIR_FREE(def->hosts[ii].name);
}
VIR_FREE(def->hosts);
VIR_FREE(def->tftproot);
VIR_FREE(def->bootfile);
}
void virNetworkDefFree(virNetworkDefPtr def)
{
int ii;
if (!def)
return;
VIR_FREE(def->name);
VIR_FREE(def->bridge);
VIR_FREE(def->forwardDev);
VIR_FREE(def->domain);
for (ii = 0 ; ii < def->nips && def->ips ; ii++) {
virNetworkIpDefClear(&def->ips[ii]);
}
VIR_FREE(def->ips);
VIR_FREE(def);
}
void virNetworkObjFree(virNetworkObjPtr net)
{
if (!net)
return;
virNetworkDefFree(net->def);
virNetworkDefFree(net->newDef);
virMutexDestroy(&net->lock);
VIR_FREE(net);
}
void virNetworkObjListFree(virNetworkObjListPtr nets)
{
unsigned int i;
for (i = 0 ; i < nets->count ; i++)
virNetworkObjFree(nets->objs[i]);
VIR_FREE(nets->objs);
nets->count = 0;
}
virNetworkObjPtr virNetworkAssignDef(virNetworkObjListPtr nets,
const virNetworkDefPtr def)
{
virNetworkObjPtr network;
if ((network = virNetworkFindByName(nets, def->name))) {
if (!virNetworkObjIsActive(network)) {
virNetworkDefFree(network->def);
network->def = def;
} else {
virNetworkDefFree(network->newDef);
network->newDef = def;
}
return network;
}
if (VIR_ALLOC(network) < 0) {
virReportOOMError();
return NULL;
}
if (virMutexInit(&network->lock) < 0) {
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("cannot initialize mutex"));
VIR_FREE(network);
return NULL;
}
virNetworkObjLock(network);
network->def = def;
if (VIR_REALLOC_N(nets->objs, nets->count + 1) < 0) {
virReportOOMError();
VIR_FREE(network);
return NULL;
}
nets->objs[nets->count] = network;
nets->count++;
return network;
}
void virNetworkRemoveInactive(virNetworkObjListPtr nets,
const virNetworkObjPtr net)
{
unsigned int 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]);
if (i < (nets->count - 1))
memmove(nets->objs + i, nets->objs + i + 1,
sizeof(*(nets->objs)) * (nets->count - (i + 1)));
if (VIR_REALLOC_N(nets->objs, nets->count - 1) < 0) {
; /* Failure to reduce memory allocation isn't fatal */
}
nets->count--;
break;
}
virNetworkObjUnlock(nets->objs[i]);
}
}
/* return ips[index], or NULL if there aren't enough ips */
virNetworkIpDefPtr
virNetworkDefGetIpByIndex(const virNetworkDefPtr def,
int family, size_t n)
{
int ii;
if (!def->ips || n >= def->nips)
return NULL;
if (family == AF_UNSPEC) {
return &def->ips[n];
}
/* find the nth ip of type "family" */
for (ii = 0; ii < def->nips; ii++) {
if (VIR_SOCKET_IS_FAMILY(&def->ips[ii].address, family)
&& (n-- <= 0)) {
return &def->ips[ii];
}
}
/* 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 virNetworkIpDefPtr def)
{
if (def->prefix > 0) {
return def->prefix;
} else if (VIR_SOCKET_HAS_ADDR(&def->netmask)) {
return virSocketGetNumNetmaskBits(&def->netmask);
} else if (VIR_SOCKET_IS_FAMILY(&def->address, AF_INET)) {
/* Return the natural prefix for the network's ip address.
* On Linux we could use the IN_CLASSx() macros, but those
* aren't guaranteed on all platforms, so we just deal with
* the bits ourselves.
*/
unsigned char octet
= ntohl(def->address.data.inet4.sin_addr.s_addr) >> 24;
if ((octet & 0x80) == 0) {
/* Class A network */
return 8;
} else if ((octet & 0xC0) == 0x80) {
/* Class B network */
return 16;
} else if ((octet & 0xE0) == 0xC0) {
/* Class C network */
return 24;
}
return -1;
} else if (VIR_SOCKET_IS_FAMILY(&def->address, AF_INET6)) {
return 64;
}
return -1;
}
/* 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 virNetworkIpDefPtr def,
virSocketAddrPtr netmask)
{
if (VIR_SOCKET_IS_FAMILY(&def->netmask, AF_INET)) {
*netmask = def->netmask;
return 0;
}
return virSocketAddrPrefixToNetmask(virNetworkIpDefPrefix(def), netmask,
VIR_SOCKET_FAMILY(&def->address));
}
static int
virNetworkDHCPRangeDefParseXML(const char *networkName,
virNetworkIpDefPtr def,
xmlNodePtr node)
{
xmlNodePtr cur;
cur = node->children;
while (cur != NULL) {
if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST "range")) {
char *start, *end;
virSocketAddr saddr, eaddr;
int range;
if (!(start = virXMLPropString(cur, "start"))) {
cur = cur->next;
continue;
}
if (!(end = virXMLPropString(cur, "end"))) {
VIR_FREE(start);
cur = cur->next;
continue;
}
if (virSocketParseAddr(start, &saddr, AF_UNSPEC) < 0) {
VIR_FREE(start);
VIR_FREE(end);
return -1;
}
if (virSocketParseAddr(end, &eaddr, AF_UNSPEC) < 0) {
VIR_FREE(start);
VIR_FREE(end);
return -1;
}
range = virSocketGetRange(&saddr, &eaddr);
if (range < 0) {
virNetworkReportError(VIR_ERR_XML_ERROR,
_("Invalid dhcp range '%s' to '%s' in network '%s'"),
start, end, networkName);
VIR_FREE(start);
VIR_FREE(end);
return -1;
}
VIR_FREE(start);
VIR_FREE(end);
if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0) {
virReportOOMError();
return -1;
}
def->ranges[def->nranges].start = saddr;
def->ranges[def->nranges].end = eaddr;
def->nranges++;
} else if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST "host")) {
char *mac, *name, *ip;
unsigned char addr[6];
virSocketAddr inaddr;
mac = virXMLPropString(cur, "mac");
if ((mac != NULL) &&
(virParseMacAddr(mac, &addr[0]) != 0)) {
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot parse MAC address '%s' in network '%s'"),
mac, networkName);
VIR_FREE(mac);
}
name = virXMLPropString(cur, "name");
if ((name != NULL) && (!c_isalpha(name[0]))) {
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot use name address '%s' in network '%s'"),
name, networkName);
VIR_FREE(name);
}
/*
* You need at least one MAC address or one host name
*/
if ((mac == NULL) && (name == NULL)) {
virNetworkReportError(VIR_ERR_XML_ERROR,
_("Static host definition in network '%s' must have mac or name attribute"),
networkName);
return -1;
}
ip = virXMLPropString(cur, "ip");
if ((ip == NULL) ||
(virSocketParseAddr(ip, &inaddr, AF_UNSPEC) < 0)) {
virNetworkReportError(VIR_ERR_XML_ERROR,
_("Missing IP address in static host definition for network '%s'"),
networkName);
VIR_FREE(ip);
VIR_FREE(mac);
VIR_FREE(name);
return -1;
}
VIR_FREE(ip);
if (VIR_REALLOC_N(def->hosts, def->nhosts + 1) < 0) {
VIR_FREE(mac);
VIR_FREE(name);
virReportOOMError();
return -1;
}
def->hosts[def->nhosts].mac = mac;
def->hosts[def->nhosts].name = name;
def->hosts[def->nhosts].ip = inaddr;
def->nhosts++;
} else if (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 &&
virSocketParseAddr(server, &inaddr, 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
virNetworkIPParseXML(const char *networkName,
virNetworkIpDefPtr def,
xmlNodePtr node,
xmlXPathContextPtr ctxt)
{
/*
* 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;
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 (virXPathULong("string(./@prefix)", ctxt, &prefix) < 0)
def->prefix = 0;
else
def->prefix = prefix;
netmask = virXPathString("string(./@netmask)", ctxt);
if (address) {
if (virSocketParseAddr(address, &def->address, AF_UNSPEC) < 0) {
virNetworkReportError(VIR_ERR_XML_ERROR,
_("Bad address '%s' in definition of network '%s'"),
address, networkName);
goto error;
}
}
/* validate family vs. address */
if (def->family == NULL) {
if (!(VIR_SOCKET_IS_FAMILY(&def->address, AF_INET) ||
VIR_SOCKET_IS_FAMILY(&def->address, AF_UNSPEC))) {
virNetworkReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("no family specified for non-IPv4 address address '%s' in network '%s'"),
address, networkName);
goto error;
}
} else if (STREQ(def->family, "ipv4")) {
if (!VIR_SOCKET_IS_FAMILY(&def->address, AF_INET)) {
virNetworkReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("family 'ipv4' specified for non-IPv4 address '%s' in network '%s'"),
address, networkName);
goto error;
}
} else if (STREQ(def->family, "ipv6")) {
if (!VIR_SOCKET_IS_FAMILY(&def->address, AF_INET6)) {
virNetworkReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("family 'ipv6' specified for non-IPv6 address '%s' in network '%s'"),
address, networkName);
goto error;
}
} else {
virNetworkReportError(VIR_ERR_XML_ERROR,
_("Unrecognized family '%s' in definition of network '%s'"),
def->family, networkName);
goto error;
}
/* parse/validate netmask */
if (netmask) {
if (address == NULL) {
/* netmask is meaningless without an address */
virNetworkReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("netmask specified without address in network '%s'"),
networkName);
goto error;
}
if (!VIR_SOCKET_IS_FAMILY(&def->address, AF_INET)) {
virNetworkReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("netmask not supported for address '%s' in network '%s' (IPv4 only)"),
address, networkName);
goto error;
}
if (def->prefix > 0) {
/* can't have both netmask and prefix at the same time */
virNetworkReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("network '%s' cannot have both prefix='%u' and a netmask"),
networkName, def->prefix);
goto error;
}
if (virSocketParseAddr(netmask, &def->netmask, AF_UNSPEC) < 0)
goto error;
if (!VIR_SOCKET_IS_FAMILY(&def->netmask, AF_INET)) {
virNetworkReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("network '%s' has invalid netmask '%s' for address '%s' (both must be IPv4)"),
networkName, netmask, address);
goto error;
}
}
if (VIR_SOCKET_IS_FAMILY(&def->address, AF_INET)) {
/* parse IPv4-related info */
cur = node->children;
while (cur != NULL) {
if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST "dhcp")) {
result = virNetworkDHCPRangeDefParseXML(networkName, def, cur);
if (result)
goto error;
} else if (cur->type == XML_ELEMENT_NODE &&
xmlStrEqual(cur->name, BAD_CAST "tftp")) {
char *root;
if (!(root = virXMLPropString(cur, "root"))) {
cur = cur->next;
continue;
}
def->tftproot = (char *)root;
}
cur = cur->next;
}
}
result = 0;
error:
if (result < 0) {
virNetworkIpDefClear(def);
}
VIR_FREE(address);
VIR_FREE(netmask);
ctxt->node = save;
return result;
}
static virNetworkDefPtr
virNetworkDefParseXML(xmlXPathContextPtr ctxt)
{
virNetworkDefPtr def;
char *tmp;
xmlNodePtr *ipNodes = NULL;
int nIps;
if (VIR_ALLOC(def) < 0) {
virReportOOMError();
return NULL;
}
/* Extract network name */
def->name = virXPathString("string(./name[1])", ctxt);
if (!def->name) {
virNetworkReportError(VIR_ERR_NO_NAME, NULL);
goto error;
}
/* Extract network uuid */
tmp = virXPathString("string(./uuid[1])", ctxt);
if (!tmp) {
if (virUUIDGenerate(def->uuid)) {
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("Failed to generate UUID"));
goto error;
}
} else {
if (virUUIDParse(tmp, def->uuid) < 0) {
VIR_FREE(tmp);
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("malformed uuid element"));
goto error;
}
VIR_FREE(tmp);
}
/* Parse network domain information */
def->domain = virXPathString("string(./domain[1]/@name)", ctxt);
/* Parse bridge information */
def->bridge = virXPathString("string(./bridge[1]/@name)", ctxt);
tmp = virXPathString("string(./bridge[1]/@stp)", ctxt);
def->stp = (tmp && STREQ(tmp, "off")) ? 0 : 1;
VIR_FREE(tmp);
if (virXPathULong("string(./bridge[1]/@delay)", ctxt, &def->delay) < 0)
def->delay = 0;
tmp = virXPathString("string(./mac[1]/@address)", ctxt);
if (tmp) {
if (virParseMacAddr(tmp, def->mac) < 0) {
virNetworkReportError(VIR_ERR_XML_ERROR,
_("Invalid bridge mac address '%s' in network '%s'"),
tmp, def->name);
VIR_FREE(tmp);
goto error;
}
VIR_FREE(tmp);
def->mac_specified = true;
}
nIps = virXPathNodeSet("./ip", ctxt, &ipNodes);
if (nIps > 0) {
int ii;
/* allocate array to hold all the addrs */
if (VIR_ALLOC_N(def->ips, nIps) < 0) {
virReportOOMError();
goto error;
}
/* parse each addr */
for (ii = 0; ii < nIps; ii++) {
int ret = virNetworkIPParseXML(def->name, &def->ips[ii],
ipNodes[ii], ctxt);
if (ret < 0)
goto error;
def->nips++;
}
}
VIR_FREE(ipNodes);
/* IPv4 forwarding setup */
if (virXPathBoolean("count(./forward) > 0", ctxt)) {
if (def->nips == 0) {
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("Forwarding requested, but no IP address provided"));
goto error;
}
tmp = virXPathString("string(./forward[1]/@mode)", ctxt);
if (tmp) {
if ((def->forwardType = virNetworkForwardTypeFromString(tmp)) < 0) {
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown forwarding type '%s'"), tmp);
VIR_FREE(tmp);
goto error;
}
VIR_FREE(tmp);
} else {
def->forwardType = VIR_NETWORK_FORWARD_NAT;
}
def->forwardDev = virXPathString("string(./forward[1]/@dev)", ctxt);
} else {
def->forwardType = VIR_NETWORK_FORWARD_NONE;
}
return def;
error:
virNetworkDefFree(def);
VIR_FREE(ipNodes);
return NULL;
}
static virNetworkDefPtr
virNetworkDefParse(const char *xmlStr,
const char *filename)
{
xmlDocPtr xml;
virNetworkDefPtr def = NULL;
if ((xml = virXMLParse(filename, xmlStr, "network.xml"))) {
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")) {
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("incorrect root element"));
return NULL;
}
ctxt = xmlXPathNewContext(xml);
if (ctxt == NULL) {
virReportOOMError();
goto cleanup;
}
ctxt->node = root;
def = virNetworkDefParseXML(ctxt);
cleanup:
xmlXPathFreeContext(ctxt);
return def;
}
static int
virNetworkIpDefFormat(virBufferPtr buf,
const virNetworkIpDefPtr def)
{
int result = -1;
virBufferAddLit(buf, " <ip");
if (def->family) {
virBufferVSprintf(buf, " family='%s'", def->family);
}
if (VIR_SOCKET_HAS_ADDR(&def->address)) {
char *addr = virSocketFormatAddr(&def->address);
if (!addr)
goto error;
virBufferVSprintf(buf, " address='%s'", addr);
VIR_FREE(addr);
}
if (VIR_SOCKET_HAS_ADDR(&def->netmask)) {
char *addr = virSocketFormatAddr(&def->netmask);
if (!addr)
goto error;
virBufferVSprintf(buf, " netmask='%s'", addr);
VIR_FREE(addr);
}
if (def->prefix > 0) {
virBufferVSprintf(buf," prefix='%u'", def->prefix);
}
virBufferAddLit(buf, ">\n");
if (def->tftproot) {
virBufferEscapeString(buf, " <tftp root='%s' />\n",
def->tftproot);
}
if ((def->nranges || def->nhosts)) {
int ii;
virBufferAddLit(buf, " <dhcp>\n");
for (ii = 0 ; ii < def->nranges ; ii++) {
char *saddr = virSocketFormatAddr(&def->ranges[ii].start);
if (!saddr)
goto error;
char *eaddr = virSocketFormatAddr(&def->ranges[ii].end);
if (!eaddr) {
VIR_FREE(saddr);
goto error;
}
virBufferVSprintf(buf, " <range start='%s' end='%s' />\n",
saddr, eaddr);
VIR_FREE(saddr);
VIR_FREE(eaddr);
}
for (ii = 0 ; ii < def->nhosts ; ii++) {
virBufferAddLit(buf, " <host ");
if (def->hosts[ii].mac)
virBufferVSprintf(buf, "mac='%s' ", def->hosts[ii].mac);
if (def->hosts[ii].name)
virBufferVSprintf(buf, "name='%s' ", def->hosts[ii].name);
if (VIR_SOCKET_HAS_ADDR(&def->hosts[ii].ip)) {
char *ipaddr = virSocketFormatAddr(&def->hosts[ii].ip);
if (!ipaddr)
goto error;
virBufferVSprintf(buf, "ip='%s' ", ipaddr);
VIR_FREE(ipaddr);
}
virBufferAddLit(buf, "/>\n");
}
if (def->bootfile) {
virBufferEscapeString(buf, " <bootp file='%s' ",
def->bootfile);
if (VIR_SOCKET_HAS_ADDR(&def->bootserver)) {
char *ipaddr = virSocketFormatAddr(&def->bootserver);
if (!ipaddr)
goto error;
virBufferEscapeString(buf, "server='%s' ", ipaddr);
VIR_FREE(ipaddr);
}
virBufferAddLit(buf, "/>\n");
}
virBufferAddLit(buf, " </dhcp>\n");
}
virBufferAddLit(buf, " </ip>\n");
result = 0;
error:
return result;
}
char *virNetworkDefFormat(const virNetworkDefPtr def)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
unsigned char *uuid;
char uuidstr[VIR_UUID_STRING_BUFLEN];
int ii;
virBufferAddLit(&buf, "<network>\n");
virBufferEscapeString(&buf, " <name>%s</name>\n", def->name);
uuid = def->uuid;
virUUIDFormat(uuid, uuidstr);
virBufferVSprintf(&buf, " <uuid>%s</uuid>\n", uuidstr);
if (def->forwardType != VIR_NETWORK_FORWARD_NONE) {
const char *mode = virNetworkForwardTypeToString(def->forwardType);
if (mode) {
if (def->forwardDev) {
virBufferEscapeString(&buf, " <forward dev='%s'",
def->forwardDev);
} else {
virBufferAddLit(&buf, " <forward");
}
virBufferVSprintf(&buf, " mode='%s'/>\n", mode);
}
}
virBufferAddLit(&buf, " <bridge");
if (def->bridge)
virBufferEscapeString(&buf, " name='%s'", def->bridge);
virBufferVSprintf(&buf, " stp='%s' delay='%ld' />\n",
def->stp ? "on" : "off",
def->delay);
if (def->mac_specified) {
char macaddr[VIR_MAC_STRING_BUFLEN];
virFormatMacAddr(def->mac, macaddr);
virBufferVSprintf(&buf, " <mac address='%s'/>\n", macaddr);
}
if (def->domain)
virBufferVSprintf(&buf, " <domain name='%s'/>\n", def->domain);
for (ii = 0; ii < def->nips; ii++) {
if (virNetworkIpDefFormat(&buf, &def->ips[ii]) < 0)
goto error;
}
virBufferAddLit(&buf, "</network>\n");
if (virBufferError(&buf))
goto no_memory;
return virBufferContentAndReset(&buf);
no_memory:
virReportOOMError();
error:
virBufferFreeAndReset(&buf);
return NULL;
}
int virNetworkSaveXML(const char *configDir,
virNetworkDefPtr def,
const char *xml)
{
char *configFile = NULL;
int fd = -1, ret = -1;
size_t towrite;
int err;
if ((configFile = virNetworkConfigFile(configDir, def->name)) == NULL)
goto cleanup;
if ((err = virFileMakePath(configDir))) {
virReportSystemError(err,
_("cannot create config directory '%s'"),
configDir);
goto cleanup;
}
if ((fd = open(configFile,
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR )) < 0) {
virReportSystemError(errno,
_("cannot create config file '%s'"),
configFile);
goto cleanup;
}
towrite = strlen(xml);
if (safewrite(fd, xml, towrite) < 0) {
virReportSystemError(errno,
_("cannot write config file '%s'"),
configFile);
goto cleanup;
}
if (VIR_CLOSE(fd) < 0) {
virReportSystemError(errno,
_("cannot save config file '%s'"),
configFile);
goto cleanup;
}
ret = 0;
cleanup:
VIR_FORCE_CLOSE(fd);
VIR_FREE(configFile);
return ret;
}
int virNetworkSaveConfig(const char *configDir,
virNetworkDefPtr def)
{
int ret = -1;
char *xml;
if (!(xml = virNetworkDefFormat(def)))
goto cleanup;
if (virNetworkSaveXML(configDir, def, xml))
goto cleanup;
ret = 0;
cleanup:
VIR_FREE(xml);
return ret;
}
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)) {
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
_("Network config filename '%s'"
" does not match network name '%s'"),
configFile, def->name);
goto error;
}
/* 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;
if (!(net = virNetworkAssignDef(nets, def)))
goto error;
net->autostart = autostart;
net->persistent = 1;
VIR_FREE(configFile);
VIR_FREE(autostartLink);
return net;
error:
VIR_FREE(configFile);
VIR_FREE(autostartLink);
virNetworkDefFree(def);
return NULL;
}
int virNetworkLoadAllConfigs(virNetworkObjListPtr nets,
const char *configDir,
const char *autostartDir)
{
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(configDir))) {
if (errno == ENOENT)
return 0;
virReportSystemError(errno,
_("Failed to open dir '%s'"),
configDir);
return -1;
}
while ((entry = readdir(dir))) {
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 0;
}
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;
if (virAsprintf(&ret, "%s/%s.xml", dir, name) < 0) {
virReportOOMError();
return NULL;
}
return ret;
}
int virNetworkBridgeInUse(const virNetworkObjListPtr nets,
const char *bridge,
const char *skipname)
{
unsigned int 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(const virNetworkObjListPtr nets,
const char *template)
{
int id = 0;
char *newname;
if (!template)
template = "virbr%d";
do {
if (virAsprintf(&newname, template, id) < 0) {
virReportOOMError();
return NULL;
}
if (!virNetworkBridgeInUse(nets, newname, NULL)) {
return newname;
}
VIR_FREE(newname);
id++;
} while (id <= MAX_BRIDGE_ID);
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
_("Bridge generation exceeded max id %d"),
MAX_BRIDGE_ID);
return NULL;
}
int virNetworkSetBridgeName(const 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)) {
virNetworkReportError(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.
*/
virGenerateMacAddr((unsigned char[]){ 0x52, 0x54, 0 },
def->mac);
def->mac_specified = true;
}
}
/*
* 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,
unsigned int check_active)
{
int ret = -1;
int dupVM = 0;
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);
virNetworkReportError(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)) {
virNetworkReportError(VIR_ERR_OPERATION_INVALID,
_("network is already active as '%s'"),
vm->def->name);
goto cleanup;
}
}
dupVM = 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);
virNetworkReportError(VIR_ERR_OPERATION_FAILED,
_("network '%s' already exists with uuid %s"),
def->name, uuidstr);
goto cleanup;
}
}
ret = dupVM;
cleanup:
if (vm)
virNetworkObjUnlock(vm);
return ret;
}
void virNetworkObjLock(virNetworkObjPtr obj)
{
virMutexLock(&obj->lock);
}
void virNetworkObjUnlock(virNetworkObjPtr obj)
{
virMutexUnlock(&obj->lock);
}