/*
* 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 "virnetdev.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"))
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, SYSFS_NET_DIR "%s/brport/bridge", 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, SYSFS_NET_DIR "%s/brport/bridge/address",
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 int
parallelsLoadNetwork(parallelsConnPtr privconn, virJSONValuePtr jobj)
{
int ret = -1;
virNetworkObjPtr net = NULL;
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, PARALLELS_BRIDGED_NETWORK_TYPE)) {
def->forward.type = VIR_NETWORK_FORWARD_BRIDGE;
if (parallelsGetBridgedNetInfo(def, jobj) < 0) {
/* Only mandatory networks are required to be configured completely */
if (STRNEQ(def->name, PARALLELS_REQUIRED_BRIDGED_NETWORK))
ret = 0;
goto cleanup;
}
} else if (STREQ(tmp, PARALLELS_HOSTONLY_NETWORK_TYPE)) {
def->forward.type = VIR_NETWORK_FORWARD_NONE;
if (parallelsGetHostOnlyNetInfo(def, def->name) < 0) {
/* Only mandatory networks are required to be configured completely */
if (STRNEQ(def->name, PARALLELS_REQUIRED_HOSTONLY_NETWORK))
ret = 0;
goto cleanup;
}
} else {
parallelsParseError();
goto cleanup;
}
if (!(net = virNetworkAssignDef(privconn->networks, def, 0)))
goto cleanup;
def = NULL;
net->active = 1;
net->autostart = 1;
ret = 0;
cleanup:
virNetworkObjEndAPI(&net);
virNetworkDefFree(def);
return ret;
}
static int
parallelsAddRoutedNetwork(parallelsConnPtr privconn)
{
virNetworkObjPtr net = NULL;
virNetworkDefPtr def;
if (VIR_ALLOC(def) < 0)
goto cleanup;
def->forward.type = VIR_NETWORK_FORWARD_ROUTE;
if (VIR_STRDUP(def->name, PARALLELS_DOMAIN_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, 0)))
goto cleanup;
net->active = 1;
net->autostart = 1;
virNetworkObjEndAPI(&net);
return 0;
cleanup:
virNetworkDefFree(def);
return -1;
}
static int parallelsLoadNetworks(parallelsConnPtr privconn)
{
virJSONValuePtr jobj, jobj2;
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;
}
if (parallelsLoadNetwork(privconn, jobj2) < 0)
goto cleanup;
}
if (parallelsAddRoutedNetwork(privconn) < 0)
goto cleanup;
ret = 0;
cleanup:
virJSONValueFree(jobj);
return ret;
}
virDrvOpenStatus
parallelsNetworkOpen(virConnectPtr conn,
unsigned int flags)
{
parallelsConnPtr privconn = conn->privateData;
virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR);
if (STRNEQ(conn->driver->name, "Parallels"))
return VIR_DRV_OPEN_DECLINED;
if (!(privconn->networks = virNetworkObjListNew()))
goto error;
if (parallelsLoadNetworks(conn->privateData) < 0)
goto error;
return VIR_DRV_OPEN_SUCCESS;
error:
virObjectUnref(privconn->networks);
privconn->networks = NULL;
return VIR_DRV_OPEN_ERROR;
}
int parallelsNetworkClose(virConnectPtr conn)
{
parallelsConnPtr privconn = conn->privateData;
if (!privconn)
return 0;
virObjectUnref(privconn->networks);
return 0;
}
static int parallelsConnectNumOfNetworks(virConnectPtr conn)
{
int nactive;
parallelsConnPtr privconn = conn->privateData;
nactive = virNetworkObjListNumOfNetworks(privconn->networks,
true, NULL, conn);
return nactive;
}
static int parallelsConnectListNetworks(virConnectPtr conn,
char **const names,
int nnames)
{
parallelsConnPtr privconn = conn->privateData;
int got;
got = virNetworkObjListGetNames(privconn->networks,
true, names, nnames, NULL, conn);
return got;
}
static int parallelsConnectNumOfDefinedNetworks(virConnectPtr conn)
{
int ninactive;
parallelsConnPtr privconn = conn->privateData;
ninactive = virNetworkObjListNumOfNetworks(privconn->networks,
false, NULL, conn);
return ninactive;
}
static int parallelsConnectListDefinedNetworks(virConnectPtr conn,
char **const names,
int nnames)
{
parallelsConnPtr privconn = conn->privateData;
int got;
got = virNetworkObjListGetNames(privconn->networks,
false, names, nnames, NULL, conn);
return got;
}
static int parallelsConnectListAllNetworks(virConnectPtr conn,
virNetworkPtr **nets,
unsigned int flags)
{
parallelsConnPtr privconn = conn->privateData;
virCheckFlags(VIR_CONNECT_LIST_NETWORKS_FILTERS_ALL, -1);
return virNetworkObjListExport(conn, privconn->networks, nets, NULL, flags);
}
static virNetworkPtr parallelsNetworkLookupByUUID(virConnectPtr conn,
const unsigned char *uuid)
{
parallelsConnPtr privconn = conn->privateData;
virNetworkObjPtr network;
virNetworkPtr ret = NULL;
network = virNetworkObjFindByUUID(privconn->networks, uuid);
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:
virNetworkObjEndAPI(&network);
return ret;
}
static virNetworkPtr parallelsNetworkLookupByName(virConnectPtr conn,
const char *name)
{
parallelsConnPtr privconn = conn->privateData;
virNetworkObjPtr network;
virNetworkPtr ret = NULL;
network = virNetworkObjFindByName(privconn->networks, name);
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:
virNetworkObjEndAPI(&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);
network = virNetworkObjFindByUUID(privconn->networks, net->uuid);
if (!network) {
virReportError(VIR_ERR_NO_NETWORK,
"%s", _("no network with matching uuid"));
goto cleanup;
}
ret = virNetworkDefFormat(network->def, flags);
cleanup:
virNetworkObjEndAPI(&network);
return ret;
}
static int parallelsNetworkIsActive(virNetworkPtr net)
{
parallelsConnPtr privconn = net->conn->privateData;
virNetworkObjPtr obj;
int ret = -1;
obj = virNetworkObjFindByUUID(privconn->networks, net->uuid);
if (!obj) {
virReportError(VIR_ERR_NO_NETWORK, NULL);
goto cleanup;
}
ret = virNetworkObjIsActive(obj);
cleanup:
virNetworkObjEndAPI(&obj);
return ret;
}
static int parallelsNetworkIsPersistent(virNetworkPtr net)
{
parallelsConnPtr privconn = net->conn->privateData;
virNetworkObjPtr obj;
int ret = -1;
obj = virNetworkObjFindByUUID(privconn->networks, net->uuid);
if (!obj) {
virReportError(VIR_ERR_NO_NETWORK, NULL);
goto cleanup;
}
ret = obj->persistent;
cleanup:
virNetworkObjEndAPI(&obj);
return ret;
}
static int parallelsNetworkGetAutostart(virNetworkPtr net,
int *autostart)
{
parallelsConnPtr privconn = net->conn->privateData;
virNetworkObjPtr network;
int ret = -1;
network = virNetworkObjFindByUUID(privconn->networks, net->uuid);
if (!network) {
virReportError(VIR_ERR_NO_NETWORK,
"%s", _("no network with matching uuid"));
goto cleanup;
}
*autostart = network->autostart;
ret = 0;
cleanup:
virNetworkObjEndAPI(&network);
return ret;
}
virNetworkDriver parallelsNetworkDriver = {
.name = "Parallels",
.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 */
};