/* * network_conf.c: network XML handling * * Copyright (C) 2006-2008 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 */ #include #include #include #include #include #include #include "internal.h" #include "network_conf.h" #include "memory.h" #include "xml.h" #include "uuid.h" #include "util.h" #include "buf.h" VIR_ENUM_DECL(virNetworkForward) VIR_ENUM_IMPL(virNetworkForward, VIR_NETWORK_FORWARD_LAST, "none", "nat", "route" ) static void virNetworkReportError(virConnectPtr conn, int code, const char *fmt, ...) { va_list args; char errorMessage[1024]; const char *virerr; if (fmt) { va_start(args, fmt); vsnprintf(errorMessage, sizeof(errorMessage)-1, fmt, args); va_end(args); } else { errorMessage[0] = '\0'; } virerr = __virErrorMsg(code, (errorMessage[0] ? errorMessage : NULL)); __virRaiseError(conn, NULL, NULL, VIR_FROM_NETWORK, code, VIR_ERR_ERROR, virerr, errorMessage, NULL, -1, -1, virerr, errorMessage); } virNetworkObjPtr virNetworkFindByUUID(const virNetworkObjPtr nets, const unsigned char *uuid) { virNetworkObjPtr net = nets; while (net) { if (!memcmp(net->def->uuid, uuid, VIR_UUID_BUFLEN)) return net; net = net->next; } return NULL; } virNetworkObjPtr virNetworkFindByName(const virNetworkObjPtr nets, const char *name) { virNetworkObjPtr net = nets; while (net) { if (STREQ(net->def->name, name)) return net; net = net->next; } return NULL; } void virNetworkDefFree(virNetworkDefPtr def) { int i; if (!def) return; VIR_FREE(def->name); VIR_FREE(def->bridge); VIR_FREE(def->forwardDev); VIR_FREE(def->ipAddress); VIR_FREE(def->network); VIR_FREE(def->netmask); for (i = 0 ; i < def->nranges && def->ranges ; i++) { VIR_FREE(def->ranges[i].start); VIR_FREE(def->ranges[i].end); } VIR_FREE(def->ranges); VIR_FREE(def); } void virNetworkObjFree(virNetworkObjPtr net) { if (!net) return; virNetworkDefFree(net->def); virNetworkDefFree(net->newDef); VIR_FREE(net->configFile); VIR_FREE(net->autostartLink); VIR_FREE(net); } virNetworkObjPtr virNetworkAssignDef(virConnectPtr conn, virNetworkObjPtr *nets, const virNetworkDefPtr def) { virNetworkObjPtr network; if ((network = virNetworkFindByName(*nets, def->name))) { if (!virNetworkIsActive(network)) { virNetworkDefFree(network->def); network->def = def; } else { if (network->newDef) virNetworkDefFree(network->newDef); network->newDef = def; } return network; } if (VIR_ALLOC(network) < 0) { virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); return NULL; } network->def = def; network->next = *nets; *nets = network; return network; } void virNetworkRemoveInactive(virNetworkObjPtr *nets, const virNetworkObjPtr net) { virNetworkObjPtr prev = NULL; virNetworkObjPtr curr = *nets; while (curr && curr != net) { prev = curr; curr = curr->next; } if (curr) { if (prev) prev->next = curr->next; else *nets = curr->next; } virNetworkObjFree(net); } static int virNetworkDHCPRangeDefParseXML(virConnectPtr conn, virNetworkDefPtr def, xmlNodePtr node) { xmlNodePtr cur; cur = node->children; while (cur != NULL) { xmlChar *start, *end; if (cur->type != XML_ELEMENT_NODE || !xmlStrEqual(cur->name, BAD_CAST "range")) { cur = cur->next; continue; } if (!(start = xmlGetProp(cur, BAD_CAST "start"))) { cur = cur->next; continue; } if (!(end = xmlGetProp(cur, BAD_CAST "end"))) { cur = cur->next; xmlFree(start); continue; } if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0) { xmlFree(start); xmlFree(end); virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); return -1; } def->ranges[def->nranges].start = (char *)start; def->ranges[def->nranges].end = (char *)end; def->nranges++; cur = cur->next; } return 0; } static virNetworkDefPtr virNetworkDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt) { virNetworkDefPtr def; char *tmp; if (VIR_ALLOC(def) < 0) { virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); return NULL; } /* Extract network name */ def->name = virXPathString(conn, "string(./name[1])", ctxt); if (!def->name) { virNetworkReportError(conn, VIR_ERR_NO_NAME, NULL); goto error; } /* Extract network uuid */ tmp = virXPathString(conn, "string(./uuid[1])", ctxt); if (!tmp) { int err; if ((err = virUUIDGenerate(def->uuid))) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("Failed to generate UUID: %s"), strerror(err)); goto error; } } else { if (virUUIDParse(tmp, def->uuid) < 0) { VIR_FREE(tmp); virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("malformed uuid element")); goto error; } VIR_FREE(tmp); } /* Parse bridge information */ def->bridge = virXPathString(conn, "string(./bridge[1]/@name)", ctxt); tmp = virXPathString(conn, "string(./bridge[1]/@stp)", ctxt); def->stp = (tmp && STREQ(tmp, "off")) ? 0 : 1; VIR_FREE(tmp); if (virXPathULong(conn, "string(./bridge[1]/@delay)", ctxt, &def->delay) < 0) def->delay = 0; def->ipAddress = virXPathString(conn, "string(./ip[1]/@address)", ctxt); def->netmask = virXPathString(conn, "string(./ip[1]/@netmask)", ctxt); if (def->ipAddress && def->netmask) { /* XXX someday we want IPv6 too, so inet_aton won't work there */ struct in_addr inaddress, innetmask; char *netaddr; xmlNodePtr dhcp; if (inet_pton(AF_INET, def->ipAddress, &inaddress) <= 0) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot parse IP address '%s'"), def->ipAddress); goto error; } if (inet_pton(AF_INET, def->netmask, &innetmask) <= 0) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot parse netmask '%s'"), def->netmask); goto error; } inaddress.s_addr &= innetmask.s_addr; netaddr = inet_ntoa(inaddress); if (asprintf(&def->network, "%s/%s", netaddr, def->netmask) < 0) { virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); goto error; } if ((dhcp = virXPathNode(conn, "./ip[1]/dhcp[1]", ctxt)) && virNetworkDHCPRangeDefParseXML(conn, def, dhcp) < 0) goto error; } /* IPv4 forwarding setup */ if (virXPathBoolean(conn, "count(./forward) > 0", ctxt)) { if (!def->ipAddress || !def->netmask) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("Forwarding requested, but no IPv4 address/netmask provided")); goto error; } tmp = virXPathString(conn, "string(./forward[1]/@mode)", ctxt); if (tmp) { if ((def->forwardType = virNetworkForwardTypeFromString(tmp)) < 0) { virNetworkReportError(conn, 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(conn, "string(./forward[1]/@dev)", ctxt); } else { def->forwardType = VIR_NETWORK_FORWARD_NONE; } return def; error: virNetworkDefFree(def); return NULL; } /* Called from SAX on parsing errors in the XML. */ static void catchXMLError (void *ctx, const char *msg ATTRIBUTE_UNUSED, ...) { xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) ctx; if (ctxt) { virConnectPtr conn = ctxt->_private; if (conn && conn->err.code == VIR_ERR_NONE && ctxt->lastError.level == XML_ERR_FATAL && ctxt->lastError.message != NULL) { virNetworkReportError (conn, VIR_ERR_XML_DETAIL, _("at line %d: %s"), ctxt->lastError.line, ctxt->lastError.message); } } } virNetworkDefPtr virNetworkDefParseString(virConnectPtr conn, const char *xmlStr) { xmlParserCtxtPtr pctxt; xmlDocPtr xml = NULL; xmlNodePtr root; virNetworkDefPtr def = NULL; /* Set up a parser context so we can catch the details of XML errors. */ pctxt = xmlNewParserCtxt (); if (!pctxt || !pctxt->sax) goto cleanup; pctxt->sax->error = catchXMLError; pctxt->_private = conn; if (conn) virResetError (&conn->err); xml = xmlCtxtReadDoc (pctxt, BAD_CAST xmlStr, "network.xml", NULL, XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_NOWARNING); if (!xml) { if (conn && conn->err.code == VIR_ERR_NONE) virNetworkReportError(conn, VIR_ERR_XML_ERROR, _("failed to parse xml document")); goto cleanup; } if ((root = xmlDocGetRootElement(xml)) == NULL) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing root element")); goto cleanup; } def = virNetworkDefParseNode(conn, xml, root); cleanup: xmlFreeParserCtxt (pctxt); xmlFreeDoc (xml); return def; } virNetworkDefPtr virNetworkDefParseFile(virConnectPtr conn, const char *filename) { xmlParserCtxtPtr pctxt; xmlDocPtr xml = NULL; xmlNodePtr root; virNetworkDefPtr def = NULL; /* Set up a parser context so we can catch the details of XML errors. */ pctxt = xmlNewParserCtxt (); if (!pctxt || !pctxt->sax) goto cleanup; pctxt->sax->error = catchXMLError; pctxt->_private = conn; if (conn) virResetError (&conn->err); xml = xmlCtxtReadFile (pctxt, filename, NULL, XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_NOWARNING); if (!xml) { if (conn && conn->err.code == VIR_ERR_NONE) virNetworkReportError(conn, VIR_ERR_XML_ERROR, _("failed to parse xml document")); goto cleanup; } if ((root = xmlDocGetRootElement(xml)) == NULL) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing root element")); goto cleanup; } def = virNetworkDefParseNode(conn, xml, root); cleanup: xmlFreeParserCtxt (pctxt); xmlFreeDoc (xml); return def; } virNetworkDefPtr virNetworkDefParseNode(virConnectPtr conn, xmlDocPtr xml, xmlNodePtr root) { xmlXPathContextPtr ctxt = NULL; virNetworkDefPtr def = NULL; if (!xmlStrEqual(root->name, BAD_CAST "network")) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("incorrect root element")); return NULL; } ctxt = xmlXPathNewContext(xml); if (ctxt == NULL) { virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); goto cleanup; } ctxt->node = root; def = virNetworkDefParseXML(conn, ctxt); cleanup: xmlXPathFreeContext(ctxt); return def; } char *virNetworkDefFormat(virConnectPtr conn, const virNetworkDefPtr def) { virBuffer buf = VIR_BUFFER_INITIALIZER; unsigned char *uuid; char *tmp; char uuidstr[VIR_UUID_STRING_BUFLEN]; virBufferAddLit(&buf, "\n"); virBufferEscapeString(&buf, " %s\n", def->name); uuid = def->uuid; virUUIDFormat(uuid, uuidstr); virBufferVSprintf(&buf, " %s\n", uuidstr); if (def->forwardType != VIR_NETWORK_FORWARD_NONE) { const char *mode = virNetworkForwardTypeToString(def->forwardType); if (mode) { if (def->forwardDev) { virBufferEscapeString(&buf, " forwardDev); } else { virBufferAddLit(&buf, " \n", mode); } } virBufferAddLit(&buf, " bridge) virBufferEscapeString(&buf, " name='%s'", def->bridge); virBufferVSprintf(&buf, " stp='%s' forwardDelay='%ld' />\n", def->stp ? "on" : "off", def->delay); if (def->ipAddress || def->netmask) { virBufferAddLit(&buf, " ipAddress) virBufferVSprintf(&buf, " address='%s'", def->ipAddress); if (def->netmask) virBufferVSprintf(&buf, " netmask='%s'", def->netmask); virBufferAddLit(&buf, ">\n"); if (def->nranges) { int i; virBufferAddLit(&buf, " \n"); for (i = 0 ; i < def->nranges ; i++) virBufferVSprintf(&buf, " \n", def->ranges[i].start, def->ranges[i].end); virBufferAddLit(&buf, " \n"); } virBufferAddLit(&buf, " \n"); } virBufferAddLit(&buf, "\n"); if (virBufferError(&buf)) goto no_memory; return virBufferContentAndReset(&buf); no_memory: virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); tmp = virBufferContentAndReset(&buf); VIR_FREE(tmp); return NULL; } int virNetworkSaveConfig(virConnectPtr conn, const char *configDir, const char *autostartDir, virNetworkObjPtr net) { char *xml; int fd = -1, ret = -1; size_t towrite; int err; if (!net->configFile && asprintf(&net->configFile, "%s/%s.xml", configDir, net->def->name) < 0) { net->configFile = NULL; virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); goto cleanup; } if (!net->autostartLink && asprintf(&net->autostartLink, "%s/%s.xml", autostartDir, net->def->name) < 0) { net->autostartLink = NULL; virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); goto cleanup; } if (!(xml = virNetworkDefFormat(conn, net->newDef ? net->newDef : net->def))) goto cleanup; if ((err = virFileMakePath(configDir))) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot create config directory %s: %s"), configDir, strerror(err)); goto cleanup; } if ((err = virFileMakePath(autostartDir))) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot create autostart directory %s: %s"), autostartDir, strerror(err)); goto cleanup; } if ((fd = open(net->configFile, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR )) < 0) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot create config file %s: %s"), net->configFile, strerror(errno)); goto cleanup; } towrite = strlen(xml); if (safewrite(fd, xml, towrite) < 0) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot write config file %s: %s"), net->configFile, strerror(errno)); goto cleanup; } if (close(fd) < 0) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot save config file %s: %s"), net->configFile, strerror(errno)); goto cleanup; } ret = 0; cleanup: VIR_FREE(xml); if (fd != -1) close(fd); return ret; } virNetworkObjPtr virNetworkLoadConfig(virConnectPtr conn, virNetworkObjPtr *nets, const char *configDir, const char *autostartDir, const char *file) { char *configFile = NULL, *autostartLink = NULL; virNetworkDefPtr def = NULL; virNetworkObjPtr net; int autostart; if (asprintf(&configFile, "%s/%s", configDir, file) < 0) { configFile = NULL; virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); goto error; } if (asprintf(&autostartLink, "%s/%s", autostartDir, file) < 0) { autostartLink = NULL; virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); goto error; } if ((autostart = virFileLinkPointsTo(autostartLink, configFile)) < 0) goto error; if (!(def = virNetworkDefParseFile(conn, configFile))) goto error; if (!virFileMatchesNameSuffix(file, def->name, ".xml")) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("Network config filename '%s'" " does not match network name '%s'"), configFile, def->name); goto error; } if (!(net = virNetworkAssignDef(conn, nets, def))) goto error; net->configFile = configFile; net->autostartLink = autostartLink; net->autostart = autostart; return net; error: VIR_FREE(configFile); VIR_FREE(autostartLink); virNetworkDefFree(def); return NULL; } int virNetworkLoadAllConfigs(virConnectPtr conn, virNetworkObjPtr *nets, const char *configDir, const char *autostartDir) { DIR *dir; struct dirent *entry; if (!(dir = opendir(configDir))) { if (errno == ENOENT) return 0; virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("Failed to open dir '%s': %s"), configDir, strerror(errno)); return -1; } while ((entry = readdir(dir))) { if (entry->d_name[0] == '.') continue; if (!virFileHasSuffix(entry->d_name, ".xml")) continue; /* NB: ignoring errors, so one malformed config doesn't kill the whole process */ virNetworkLoadConfig(conn, nets, configDir, autostartDir, entry->d_name); } closedir(dir); return 0; } int virNetworkDeleteConfig(virConnectPtr conn, virNetworkObjPtr net) { if (!net->configFile || !net->autostartLink) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("no config file for %s"), net->def->name); return -1; } /* Not fatal if this doesn't work */ unlink(net->autostartLink); if (unlink(net->configFile) < 0) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot remove config for %s: %s"), net->def->name, strerror(errno)); return -1; } return 0; }