/* * parallels_network.c: core privconn functions for managing * Parallels Cloud Server hosts * * Copyright (C) 2013-2014 Red Hat, Inc. * Copyright (C) 2012 Parallels, Inc. * * 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 * . * */ #include #include "datatypes.h" #include "dirname.h" #include "viralloc.h" #include "virerror.h" #include "virfile.h" #include "md5.h" #include "parallels_utils.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_PARALLELS #define PARALLELS_ROUTED_NETWORK_UUID "eb593dd1-6846-45b0-84a0-de0729286982" #define parallelsParseError() \ virReportErrorHelper(VIR_FROM_TEST, VIR_ERR_OPERATION_FAILED, __FILE__, \ __FUNCTION__, __LINE__, _("Can't parse prlctl output")) #define SYSFS_NET_DIR "/sys/class/net" static int parallelsGetBridgedNetInfo(virNetworkDefPtr def, virJSONValuePtr jobj) { const char *ifname; char *bridgeLink = NULL; char *bridgePath = NULL; char *bridgeAddressPath = NULL; char *bridgeAddress = NULL; int len = 0; int ret = -1; if (!(ifname = virJSONValueObjectGetString(jobj, "Bound To"))) { parallelsParseError(); goto cleanup; } if (virAsprintf(&bridgeLink, "%s/%s/brport/bridge", SYSFS_NET_DIR, ifname) < 0) goto cleanup; if (virFileResolveLink(bridgeLink, &bridgePath) < 0) { virReportSystemError(errno, _("cannot read link '%s'"), bridgeLink); goto cleanup; } if (VIR_STRDUP(def->bridge, last_component(bridgePath)) < 0) goto cleanup; if (virAsprintf(&bridgeAddressPath, "%s/%s/brport/bridge/address", SYSFS_NET_DIR, ifname) < 0) goto cleanup; if ((len = virFileReadAll(bridgeAddressPath, 18, &bridgeAddress)) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Error reading file '%s'"), bridgeAddressPath); goto cleanup; } if (len < VIR_MAC_STRING_BUFLEN) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Error reading MAC from '%s'"), bridgeAddressPath); } bridgeAddress[VIR_MAC_STRING_BUFLEN - 1] = '\0'; if (virMacAddrParse(bridgeAddress, &def->mac) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Can't parse MAC '%s'"), bridgeAddress); goto cleanup; } def->mac_specified = 1; ret = 0; cleanup: VIR_FREE(bridgeLink); VIR_FREE(bridgePath); VIR_FREE(bridgeAddress); VIR_FREE(bridgeAddressPath); return ret; } static int parallelsGetHostOnlyNetInfo(virNetworkDefPtr def, const char *name) { const char *tmp; virJSONValuePtr jobj = NULL, jobj2; int ret = -1; if (VIR_EXPAND_N(def->ips, def->nips, 1) < 0) goto cleanup; jobj = parallelsParseOutput("prlsrvctl", "net", "info", "-j", name, NULL); if (!jobj) { parallelsParseError(); goto cleanup; } if (!(jobj2 = virJSONValueObjectGet(jobj, "Parallels adapter"))) { parallelsParseError(); goto cleanup; } if (VIR_STRDUP(def->ips[0].family, "ipv4") < 0) goto cleanup; if (!(tmp = virJSONValueObjectGetString(jobj2, "IP address"))) { parallelsParseError(); goto cleanup; } if (virSocketAddrParseIPv4(&def->ips[0].address, tmp) < 0) { parallelsParseError(); goto cleanup; } if (!(tmp = virJSONValueObjectGetString(jobj2, "Subnet mask"))) { parallelsParseError(); goto cleanup; } if (virSocketAddrParseIPv4(&def->ips[0].netmask, tmp) < 0) { parallelsParseError(); goto cleanup; } if (!(jobj2 = virJSONValueObjectGet(jobj, "DHCPv4 server"))) { parallelsParseError(); goto cleanup; } if (VIR_EXPAND_N(def->ips[0].ranges, def->ips[0].nranges, 1) < 0) goto cleanup; if (!(tmp = virJSONValueObjectGetString(jobj2, "IP scope start address"))) { parallelsParseError(); goto cleanup; } if (virSocketAddrParseIPv4(&def->ips[0].ranges[0].start, tmp) < 0) { parallelsParseError(); goto cleanup; } if (!(tmp = virJSONValueObjectGetString(jobj2, "IP scope end address"))) { parallelsParseError(); goto cleanup; } if (virSocketAddrParseIPv4(&def->ips[0].ranges[0].end, tmp) < 0) { parallelsParseError(); goto cleanup; } ret = 0; cleanup: virJSONValueFree(jobj); return ret; } static virNetworkObjPtr parallelsLoadNetwork(parallelsConnPtr privconn, virJSONValuePtr jobj) { virNetworkObjPtr net; virNetworkDefPtr def; const char *tmp; /* MD5_DIGEST_SIZE = VIR_UUID_BUFLEN = 16 */ unsigned char md5[MD5_DIGEST_SIZE]; if (VIR_ALLOC(def) < 0) goto cleanup; if (!(tmp = virJSONValueObjectGetString(jobj, "Network ID"))) { parallelsParseError(); goto cleanup; } if (VIR_STRDUP(def->name, tmp) < 0) goto cleanup; /* Network names are unique in Parallels Cloud Server, so we can make * a UUID from it */ md5_buffer(tmp, strlen(tmp), md5); memcpy(def->uuid, md5, VIR_UUID_BUFLEN); def->uuid_specified = 1; if (!(tmp = virJSONValueObjectGetString(jobj, "Type"))) { parallelsParseError(); goto cleanup; } if (STREQ(tmp, "bridged")) { def->forward.type = VIR_NETWORK_FORWARD_BRIDGE; if (parallelsGetBridgedNetInfo(def, jobj) < 0) goto cleanup; } else if (STREQ(tmp, "host-only")) { def->forward.type = VIR_NETWORK_FORWARD_NONE; if (parallelsGetHostOnlyNetInfo(def, def->name) < 0) goto cleanup; } else { parallelsParseError(); goto cleanup; } if (!(net = virNetworkAssignDef(&privconn->networks, def, false))) goto cleanup; net->active = 1; net->autostart = 1; virNetworkObjUnlock(net); return net; cleanup: virNetworkDefFree(def); return NULL; } static virNetworkObjPtr parallelsAddRoutedNetwork(parallelsConnPtr privconn) { virNetworkObjPtr net; virNetworkDefPtr def; if (VIR_ALLOC(def) < 0) goto cleanup; def->forward.type = VIR_NETWORK_FORWARD_ROUTE; if (VIR_STRDUP(def->name, PARALLELS_ROUTED_NETWORK_NAME) < 0) goto cleanup; if (virUUIDParse(PARALLELS_ROUTED_NETWORK_UUID, def->uuid) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Can't parse UUID")); goto cleanup; } def->uuid_specified = 1; if (!(net = virNetworkAssignDef(&privconn->networks, def, false))) { virNetworkDefFree(def); goto cleanup; } net->active = 1; net->autostart = 1; virNetworkObjUnlock(net); return net; cleanup: virNetworkDefFree(def); return NULL; } static int parallelsLoadNetworks(parallelsConnPtr privconn) { virJSONValuePtr jobj, jobj2; virNetworkObjPtr net; int ret = -1; int count; size_t i; jobj = parallelsParseOutput("prlsrvctl", "net", "list", "-j", NULL); if (!jobj) { parallelsParseError(); goto cleanup; } count = virJSONValueArraySize(jobj); if (count < 0) { parallelsParseError(); goto cleanup; } for (i = 0; i < count; i++) { jobj2 = virJSONValueArrayGet(jobj, i); if (!jobj2) { parallelsParseError(); goto cleanup; } net = parallelsLoadNetwork(privconn, jobj2); if (!net) goto cleanup; } if (!parallelsAddRoutedNetwork(privconn)) goto cleanup; ret = 0; cleanup: virJSONValueFree(jobj); return ret; } static virDrvOpenStatus parallelsNetworkOpen(virConnectPtr conn, virConnectAuthPtr auth ATTRIBUTE_UNUSED, unsigned int flags) { virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR); if (STRNEQ(conn->driver->name, "Parallels")) return VIR_DRV_OPEN_DECLINED; conn->networkPrivateData = conn->privateData; if (parallelsLoadNetworks(conn->privateData) < 0) return VIR_DRV_OPEN_DECLINED; return VIR_DRV_OPEN_SUCCESS; } static int parallelsNetworkClose(virConnectPtr conn) { parallelsConnPtr privconn = conn->privateData; parallelsDriverLock(privconn); virNetworkObjListFree(&privconn->networks); parallelsDriverUnlock(privconn); return 0; } static int parallelsConnectNumOfNetworks(virConnectPtr conn) { int nactive = 0; size_t i; parallelsConnPtr privconn = conn->privateData; parallelsDriverLock(privconn); for (i = 0; i < privconn->networks.count; i++) { virNetworkObjLock(privconn->networks.objs[i]); if (virNetworkObjIsActive(privconn->networks.objs[i])) nactive++; virNetworkObjUnlock(privconn->networks.objs[i]); } parallelsDriverUnlock(privconn); return nactive; } static int parallelsConnectListNetworks(virConnectPtr conn, char **const names, int nnames) { parallelsConnPtr privconn = conn->privateData; int got = 0; size_t i; parallelsDriverLock(privconn); for (i = 0; i < privconn->networks.count && got < nnames; i++) { virNetworkObjLock(privconn->networks.objs[i]); if (virNetworkObjIsActive(privconn->networks.objs[i])) { if (VIR_STRDUP(names[got], privconn->networks.objs[i]->def->name) < 0) { virNetworkObjUnlock(privconn->networks.objs[i]); goto cleanup; } got++; } virNetworkObjUnlock(privconn->networks.objs[i]); } parallelsDriverUnlock(privconn); return got; cleanup: parallelsDriverUnlock(privconn); for (i = 0; i < got; i++) VIR_FREE(names[i]); return -1; } static int parallelsConnectNumOfDefinedNetworks(virConnectPtr conn) { int ninactive = 0; size_t i; parallelsConnPtr privconn = conn->privateData; parallelsDriverLock(privconn); for (i = 0; i < privconn->networks.count; i++) { virNetworkObjLock(privconn->networks.objs[i]); if (!virNetworkObjIsActive(privconn->networks.objs[i])) ninactive++; virNetworkObjUnlock(privconn->networks.objs[i]); } parallelsDriverUnlock(privconn); return ninactive; } static int parallelsConnectListDefinedNetworks(virConnectPtr conn, char **const names, int nnames) { parallelsConnPtr privconn = conn->privateData; int got = 0; size_t i; parallelsDriverLock(privconn); for (i = 0; i < privconn->networks.count && got < nnames; i++) { virNetworkObjLock(privconn->networks.objs[i]); if (!virNetworkObjIsActive(privconn->networks.objs[i])) { if (VIR_STRDUP(names[got], privconn->networks.objs[i]->def->name) < 0) { virNetworkObjUnlock(privconn->networks.objs[i]); goto cleanup; } got++; } virNetworkObjUnlock(privconn->networks.objs[i]); } parallelsDriverUnlock(privconn); return got; cleanup: parallelsDriverUnlock(privconn); for (i = 0; i < got; i++) VIR_FREE(names[i]); return -1; } static int parallelsConnectListAllNetworks(virConnectPtr conn, virNetworkPtr **nets, unsigned int flags) { parallelsConnPtr privconn = conn->privateData; int ret = -1; virCheckFlags(VIR_CONNECT_LIST_NETWORKS_FILTERS_ALL, -1); parallelsDriverLock(privconn); ret = virNetworkObjListExport(conn, privconn->networks, nets, NULL, flags); parallelsDriverUnlock(privconn); return ret; } static virNetworkPtr parallelsNetworkLookupByUUID(virConnectPtr conn, const unsigned char *uuid) { parallelsConnPtr privconn = conn->privateData; virNetworkObjPtr network; virNetworkPtr ret = NULL; parallelsDriverLock(privconn); network = virNetworkFindByUUID(&privconn->networks, uuid); parallelsDriverUnlock(privconn); if (!network) { virReportError(VIR_ERR_NO_NETWORK, "%s", _("no network with matching uuid")); goto cleanup; } ret = virGetNetwork(conn, network->def->name, network->def->uuid); cleanup: if (network) virNetworkObjUnlock(network); return ret; } static virNetworkPtr parallelsNetworkLookupByName(virConnectPtr conn, const char *name) { parallelsConnPtr privconn = conn->privateData; virNetworkObjPtr network; virNetworkPtr ret = NULL; parallelsDriverLock(privconn); network = virNetworkFindByName(&privconn->networks, name); parallelsDriverUnlock(privconn); if (!network) { virReportError(VIR_ERR_NO_NETWORK, _("no network with matching name '%s'"), name); goto cleanup; } ret = virGetNetwork(conn, network->def->name, network->def->uuid); cleanup: if (network) virNetworkObjUnlock(network); return ret; } static char *parallelsNetworkGetXMLDesc(virNetworkPtr net, unsigned int flags) { parallelsConnPtr privconn = net->conn->privateData; virNetworkObjPtr network; char *ret = NULL; virCheckFlags(VIR_NETWORK_XML_INACTIVE, NULL); parallelsDriverLock(privconn); network = virNetworkFindByUUID(&privconn->networks, net->uuid); parallelsDriverUnlock(privconn); if (!network) { virReportError(VIR_ERR_NO_NETWORK, "%s", _("no network with matching uuid")); goto cleanup; } ret = virNetworkDefFormat(network->def, flags); cleanup: if (network) virNetworkObjUnlock(network); return ret; } static int parallelsNetworkIsActive(virNetworkPtr net) { parallelsConnPtr privconn = net->conn->privateData; virNetworkObjPtr obj; int ret = -1; parallelsDriverLock(privconn); obj = virNetworkFindByUUID(&privconn->networks, net->uuid); parallelsDriverUnlock(privconn); if (!obj) { virReportError(VIR_ERR_NO_NETWORK, NULL); goto cleanup; } ret = virNetworkObjIsActive(obj); cleanup: if (obj) virNetworkObjUnlock(obj); return ret; } static int parallelsNetworkIsPersistent(virNetworkPtr net) { parallelsConnPtr privconn = net->conn->privateData; virNetworkObjPtr obj; int ret = -1; parallelsDriverLock(privconn); obj = virNetworkFindByUUID(&privconn->networks, net->uuid); parallelsDriverUnlock(privconn); if (!obj) { virReportError(VIR_ERR_NO_NETWORK, NULL); goto cleanup; } ret = obj->persistent; cleanup: if (obj) virNetworkObjUnlock(obj); return ret; } static int parallelsNetworkGetAutostart(virNetworkPtr net, int *autostart) { parallelsConnPtr privconn = net->conn->privateData; virNetworkObjPtr network; int ret = -1; parallelsDriverLock(privconn); network = virNetworkFindByUUID(&privconn->networks, net->uuid); parallelsDriverUnlock(privconn); if (!network) { virReportError(VIR_ERR_NO_NETWORK, "%s", _("no network with matching uuid")); goto cleanup; } *autostart = network->autostart; ret = 0; cleanup: if (network) virNetworkObjUnlock(network); return ret; } static virNetworkDriver parallelsNetworkDriver = { "Parallels", .networkOpen = parallelsNetworkOpen, /* 1.0.1 */ .networkClose = parallelsNetworkClose, /* 1.0.1 */ .connectNumOfNetworks = parallelsConnectNumOfNetworks, /* 1.0.1 */ .connectListNetworks = parallelsConnectListNetworks, /* 1.0.1 */ .connectNumOfDefinedNetworks = parallelsConnectNumOfDefinedNetworks, /* 1.0.1 */ .connectListDefinedNetworks = parallelsConnectListDefinedNetworks, /* 1.0.1 */ .connectListAllNetworks = parallelsConnectListAllNetworks, /* 1.0.1 */ .networkLookupByUUID = parallelsNetworkLookupByUUID, /* 1.0.1 */ .networkLookupByName = parallelsNetworkLookupByName, /* 1.0.1 */ .networkGetXMLDesc = parallelsNetworkGetXMLDesc, /* 1.0.1 */ .networkGetAutostart = parallelsNetworkGetAutostart, /* 1.0.1 */ .networkIsActive = parallelsNetworkIsActive, /* 1.0.1 */ .networkIsPersistent = parallelsNetworkIsPersistent, /* 1.0.1 */ }; int parallelsNetworkRegister(void) { if (virRegisterNetworkDriver(¶llelsNetworkDriver) < 0) return -1; return 0; }