/*
* Copyright (C) 2009-2016 Red Hat, 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 "virnetdevvportprofile.h"
#include "virerror.h"
#include "viralloc.h"
#include "virstring.h"
#define VIR_FROM_THIS VIR_FROM_NET
VIR_ENUM_IMPL(virNetDevVPort,
VIR_NETDEV_VPORT_PROFILE_LAST,
"none",
"802.1Qbg",
"802.1Qbh",
"openvswitch",
"midonet",
);
VIR_ENUM_IMPL(virNetDevVPortProfileOp,
VIR_NETDEV_VPORT_PROFILE_OP_LAST,
"create",
"save",
"restore",
"destroy",
"migrate out",
"migrate in start",
"migrate in finish",
"no-op",
);
#if WITH_VIRTUALPORT
# include
# include
# include
# include
# include
# include "virnetlink.h"
# include "virfile.h"
# include "virlog.h"
# include "virnetdev.h"
VIR_LOG_INIT("util.netdevvportprofile");
# define MICROSEC_PER_SEC (1000 * 1000)
# define NLMSGBUF_SIZE 256
# define RATTBUF_SIZE 64
# define STATUS_POLL_TIMEOUT_USEC (10 * MICROSEC_PER_SEC)
# define STATUS_POLL_INTERVL_USEC (MICROSEC_PER_SEC / 8)
# define LLDPAD_PID_FILE "/var/run/lldpad.pid"
enum virNetDevVPortProfileLinkOp {
VIR_NETDEV_VPORT_PROFILE_LINK_OP_ASSOCIATE = 0x1,
VIR_NETDEV_VPORT_PROFILE_LINK_OP_DISASSOCIATE = 0x2,
VIR_NETDEV_VPORT_PROFILE_LINK_OP_PREASSOCIATE = 0x3,
VIR_NETDEV_VPORT_PROFILE_LINK_OP_PREASSOCIATE_RR = 0x4,
};
#endif
bool
virNetDevVPortProfileEqual(const virNetDevVPortProfile *a, const virNetDevVPortProfile *b)
{
/* NULL resistant */
if (!a && !b)
return true;
if (!a || !b)
return false;
if (a->virtPortType != b->virtPortType)
return false;
switch (a->virtPortType) {
case VIR_NETDEV_VPORT_PROFILE_NONE:
break;
case VIR_NETDEV_VPORT_PROFILE_8021QBG:
if (a->managerID != b->managerID ||
a->typeID != b->typeID ||
a->typeIDVersion != b->typeIDVersion ||
memcmp(a->instanceID, b->instanceID, VIR_UUID_BUFLEN) != 0)
return false;
break;
case VIR_NETDEV_VPORT_PROFILE_8021QBH:
if (STRNEQ(a->profileID, b->profileID))
return false;
break;
case VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH:
if (STRNEQ(a->profileID, b->profileID) ||
memcmp(a->interfaceID, b->interfaceID, VIR_UUID_BUFLEN) != 0)
return false;
break;
default:
break;
}
return true;
}
int virNetDevVPortProfileCopy(virNetDevVPortProfilePtr *dst, const virNetDevVPortProfile *src)
{
if (!src) {
*dst = NULL;
return 0;
}
if (VIR_ALLOC(*dst) < 0)
return -1;
memcpy(*dst, src, sizeof(*src));
return 0;
}
/* virNetDevVPortProfileCheckComplete() checks that all attributes
* required for the type of virtport are specified. When
* generateMissing is true, any missing attribute that can be
* autogenerated, will be (instanceid, interfaceid). If virtport ==
* NULL or virtPortType == NONE, then the result is always 0
* (success). If a required attribute is missing, an error is logged
* and -1 is returned.
*/
int
virNetDevVPortProfileCheckComplete(virNetDevVPortProfilePtr virtport,
bool generateMissing)
{
const char *missing = NULL;
if (!virtport || virtport->virtPortType == VIR_NETDEV_VPORT_PROFILE_NONE)
return 0;
switch (virtport->virtPortType) {
case VIR_NETDEV_VPORT_PROFILE_8021QBG:
if (!virtport->managerID_specified) {
missing = "managerid";
} else if (!virtport->typeID_specified) {
missing = "typeid";
} else if (!virtport->typeIDVersion_specified) {
missing = "typeidversion";
} else if (!virtport->instanceID_specified) {
if (generateMissing) {
if (virUUIDGenerate(virtport->instanceID) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot generate a random uuid for instanceid"));
return -1;
}
virtport->instanceID_specified = true;
} else {
missing = "instanceid";
}
}
break;
case VIR_NETDEV_VPORT_PROFILE_8021QBH:
if (!virtport->profileID[0])
missing = "profileid";
break;
case VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH:
/* profileid is optional for openvswitch */
if (!virtport->interfaceID_specified) {
if (generateMissing) {
if (virUUIDGenerate(virtport->interfaceID) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot generate a random uuid for interfaceid"));
return -1;
}
virtport->interfaceID_specified = true;
} else {
missing = "interfaceid";
}
}
break;
case VIR_NETDEV_VPORT_PROFILE_MIDONET:
if (!virtport->interfaceID_specified)
missing = "interfaceid";
break;
}
if (missing) {
virReportError(VIR_ERR_XML_ERROR,
_("missing %s in "), missing,
virNetDevVPortTypeToString(virtport->virtPortType));
return -1;
}
return 0;
}
/* virNetDevVPortProfileCheckNoExtras() checks that there are no
* attributes specified in this virtport that are inappropriate for
* the type. if virtport == NULL or virtPortType == NONE, then the
* result is always 0 (success). If an extra attribute is present,
* an error is logged and -1 is returned.
*/
int
virNetDevVPortProfileCheckNoExtras(const virNetDevVPortProfile *virtport)
{
const char *extra = NULL;
if (!virtport || virtport->virtPortType == VIR_NETDEV_VPORT_PROFILE_NONE)
return 0;
switch (virtport->virtPortType) {
case VIR_NETDEV_VPORT_PROFILE_8021QBG:
if (virtport->profileID[0])
extra = "profileid";
else if (virtport->interfaceID_specified)
extra = "interfaceid";
break;
case VIR_NETDEV_VPORT_PROFILE_8021QBH:
if (virtport->managerID_specified)
extra = "managerid";
else if (virtport->typeID_specified)
extra = "typeid";
else if (virtport->typeIDVersion_specified)
extra = "typeidversion";
else if (virtport->instanceID_specified)
extra = "instanceid";
else if (virtport->interfaceID_specified)
extra = "interfaceid";
break;
case VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH:
if (virtport->managerID_specified)
extra = "managerid";
else if (virtport->typeID_specified)
extra = "typeid";
else if (virtport->typeIDVersion_specified)
extra = "typeidversion";
else if (virtport->instanceID_specified)
extra = "instanceid";
break;
}
if (extra) {
virReportError(VIR_ERR_XML_ERROR,
_("extra %s unsupported in "),
extra,
virNetDevVPortTypeToString(virtport->virtPortType));
return -1;
}
return 0;
}
/* virNetDevVPortProfileMerge() - merge the attributes in mods into
* orig. If anything that is set in mods has already been set in orig
* *and doesn't match*, log an error and return -1, otherwise return 0.
*/
static int
virNetDevVPortProfileMerge(virNetDevVPortProfilePtr orig,
const virNetDevVPortProfile *mods)
{
enum virNetDevVPortProfile otype;
if (!orig || !mods)
return 0;
otype = orig->virtPortType;
if (mods->virtPortType != VIR_NETDEV_VPORT_PROFILE_NONE) {
if (otype != VIR_NETDEV_VPORT_PROFILE_NONE &&
otype != mods->virtPortType) {
virReportError(VIR_ERR_XML_ERROR,
_("attempt to merge virtualports "
"with mismatched types (%s and %s)"),
virNetDevVPortTypeToString(otype),
virNetDevVPortTypeToString(mods->virtPortType));
return -1;
}
otype = orig->virtPortType = mods->virtPortType;
}
if (mods->managerID_specified &&
(otype == VIR_NETDEV_VPORT_PROFILE_8021QBG ||
otype == VIR_NETDEV_VPORT_PROFILE_NONE)) {
if (orig->managerID_specified &&
(orig->managerID != mods->managerID)) {
virReportError(VIR_ERR_XML_ERROR,
_("attempt to merge virtualports "
"with mismatched managerids (%d and %d)"),
orig->managerID, mods->managerID);
return -1;
}
orig->managerID = mods->managerID;
orig->managerID_specified = true;
}
if (mods->typeID_specified &&
(otype == VIR_NETDEV_VPORT_PROFILE_8021QBG ||
otype == VIR_NETDEV_VPORT_PROFILE_NONE)) {
if (orig->typeID_specified &&
(orig->typeID != mods->typeID)) {
virReportError(VIR_ERR_XML_ERROR,
_("attempt to merge virtualports "
"with mismatched typeids (%d and %d)"),
orig->typeID, mods->typeID);
return -1;
}
orig->typeID = mods->typeID;
orig->typeID_specified = true;
}
if (mods->typeIDVersion_specified &&
(otype == VIR_NETDEV_VPORT_PROFILE_8021QBG ||
otype == VIR_NETDEV_VPORT_PROFILE_NONE)) {
if (orig->typeIDVersion_specified &&
(orig->typeIDVersion != mods->typeIDVersion)) {
virReportError(VIR_ERR_XML_ERROR,
_("attempt to merge virtualports with "
"mismatched typeidversions (%d and %d)"),
orig->typeIDVersion, mods->typeIDVersion);
return -1;
}
orig->typeIDVersion = mods->typeIDVersion;
orig->typeIDVersion_specified = true;
}
if (mods->instanceID_specified &&
(otype == VIR_NETDEV_VPORT_PROFILE_8021QBG ||
otype == VIR_NETDEV_VPORT_PROFILE_NONE)) {
if (orig->instanceID_specified &&
memcmp(orig->instanceID, mods->instanceID,
sizeof(orig->instanceID))) {
char origuuid[VIR_UUID_STRING_BUFLEN];
char modsuuid[VIR_UUID_STRING_BUFLEN];
virReportError(VIR_ERR_XML_ERROR,
_("attempt to merge virtualports with "
"mismatched instanceids ('%s' and '%s')"),
virUUIDFormat(orig->instanceID, origuuid),
virUUIDFormat(mods->instanceID, modsuuid));
return -1;
}
memcpy(orig->instanceID, mods->instanceID, sizeof(orig->instanceID));
orig->instanceID_specified = true;
}
if (mods->interfaceID_specified &&
(otype == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH ||
otype == VIR_NETDEV_VPORT_PROFILE_NONE)) {
if (orig->interfaceID_specified &&
memcmp(orig->interfaceID, mods->interfaceID,
sizeof(orig->interfaceID))) {
char origuuid[VIR_UUID_STRING_BUFLEN];
char modsuuid[VIR_UUID_STRING_BUFLEN];
virReportError(VIR_ERR_XML_ERROR,
_("attempt to merge virtualports with "
"mismatched interfaceids ('%s' and '%s')"),
virUUIDFormat(orig->interfaceID, origuuid),
virUUIDFormat(mods->interfaceID, modsuuid));
return -1;
}
memcpy(orig->interfaceID, mods->interfaceID, sizeof(orig->interfaceID));
orig->interfaceID_specified = true;
}
if (mods->profileID[0] &&
(otype == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH ||
otype == VIR_NETDEV_VPORT_PROFILE_8021QBH ||
otype == VIR_NETDEV_VPORT_PROFILE_NONE)) {
if (orig->profileID[0] &&
STRNEQ(orig->profileID, mods->profileID)) {
virReportError(VIR_ERR_XML_ERROR,
_("attempt to merge virtualports with "
"mismatched profileids ('%s' and '%s')"),
orig->profileID, mods->profileID);
return -1;
}
if (virStrcpyStatic(orig->profileID, mods->profileID) < 0) {
/* this should never happen - it indicates mods->profileID
* isn't properly null terminated. */
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("corrupted profileid string"));
return -1;
}
}
return 0;
}
/* virNetDevVPortProfileMerge3() - create a new virNetDevVPortProfile
* that is a combination of the three input profiles. fromInterface is
* highest priority and fromPortgroup is lowest. As lower priority
* objects' attributes are merged in, if the attribute is unset in the
* result object, it is set from the lower priority object, but if it
* is already set in the result and the lower priority object wants to
* change it, that is an error.
*/
int virNetDevVPortProfileMerge3(virNetDevVPortProfilePtr *result,
const virNetDevVPortProfile *fromInterface,
const virNetDevVPortProfile *fromNetwork,
const virNetDevVPortProfile *fromPortgroup)
{
int ret = -1;
*result = NULL;
if ((!fromInterface || (fromInterface->virtPortType == VIR_NETDEV_VPORT_PROFILE_NONE)) &&
(!fromNetwork || (fromNetwork->virtPortType == VIR_NETDEV_VPORT_PROFILE_NONE)) &&
(!fromPortgroup || (fromPortgroup->virtPortType == VIR_NETDEV_VPORT_PROFILE_NONE))) {
return 0;
}
/* at least one of the source profiles is non-empty */
if (VIR_ALLOC(*result) < 0)
return ret;
/* start with the interface's profile. There are no pointers in a
* virtualPortProfile, so a shallow copy is sufficient.
*/
if (fromInterface)
**result = *fromInterface;
if (virNetDevVPortProfileMerge(*result, fromNetwork) < 0)
goto error;
if (virNetDevVPortProfileMerge(*result, fromPortgroup) < 0)
goto error;
ret = 0;
error:
if (ret < 0)
VIR_FREE(*result);
return ret;
}
#if WITH_VIRTUALPORT
static struct nla_policy ifla_port_policy[IFLA_PORT_MAX + 1] =
{
[IFLA_PORT_RESPONSE] = { .type = NLA_U16 },
};
static uint32_t
virNetDevVPortProfileGetLldpadPid(void)
{
int fd;
uint32_t pid = 0;
fd = open(LLDPAD_PID_FILE, O_RDONLY);
if (fd >= 0) {
char buffer[10];
if (saferead(fd, buffer, sizeof(buffer)) <= sizeof(buffer)) {
unsigned int res;
char *endptr;
if (virStrToLong_ui(buffer, &endptr, 10, &res) == 0
&& (*endptr == '\0' || g_ascii_isspace(*endptr))
&& res != 0) {
pid = res;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("error parsing pid of lldpad"));
}
}
} else {
virReportSystemError(errno,
_("Error opening file %s"), LLDPAD_PID_FILE);
}
VIR_FORCE_CLOSE(fd);
return pid;
}
/**
* virNetDevVPortProfileGetStatus:
*
* tb: top level netlink response attributes + values
* vf: The virtual function used in the request
* instanceId: instanceId of the interface (vm uuid in case of 802.1Qbh)
* is8021Qbg: whether this function is call for 8021Qbg
* status: pointer to a uint16 where the status will be written into
*
* Get the status from the IFLA_PORT_RESPONSE field; Returns 0 in
* case of success, < 0 otherwise with error having been reported
*/
static int
virNetDevVPortProfileGetStatus(struct nlattr **tb, int32_t vf,
const unsigned char *instanceId,
bool nltarget_kernel,
bool is8021Qbg,
uint16_t *status)
{
int rc = -1;
struct nlattr *tb_port[IFLA_PORT_MAX + 1] = { NULL, };
if (vf == PORT_SELF_VF && nltarget_kernel) {
if (tb[IFLA_PORT_SELF]) {
if (nla_parse_nested(tb_port, IFLA_PORT_MAX, tb[IFLA_PORT_SELF],
ifla_port_policy)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("error parsing IFLA_PORT_SELF part"));
goto cleanup;
}
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("IFLA_PORT_SELF is missing"));
goto cleanup;
}
} else {
if (tb[IFLA_VF_PORTS]) {
int rem;
bool found = false;
struct nlattr *tb_vf_ports = { NULL, };
nla_for_each_nested(tb_vf_ports, tb[IFLA_VF_PORTS], rem) {
if (nla_type(tb_vf_ports) != IFLA_VF_PORT) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("error while iterating over "
"IFLA_VF_PORTS part"));
goto cleanup;
}
if (nla_parse_nested(tb_port, IFLA_PORT_MAX, tb_vf_ports,
ifla_port_policy)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("error parsing IFLA_VF_PORT part"));
goto cleanup;
}
/* This ensures that the given VF is present in the
* IFLA_VF_PORTS list, and that its uuid matches the
* instanceId (in case we've associated it). If no
* instanceId is sent from the caller, that means we've
* disassociated it from this instanceId, and the uuid
* will either be unset (if still not associated with
* anything) or will be set to a new and different uuid
*/
if ((tb_port[IFLA_PORT_VF] &&
vf == *(uint32_t *)RTA_DATA(tb_port[IFLA_PORT_VF])) &&
(!instanceId ||
(tb_port[IFLA_PORT_INSTANCE_UUID] &&
!memcmp(instanceId,
(unsigned char *)
RTA_DATA(tb_port[IFLA_PORT_INSTANCE_UUID]),
VIR_UUID_BUFLEN)))) {
found = true;
break;
}
}
if (!found) {
char instanceIdStr[VIR_UUID_STRING_BUFLEN] = "(none)";
if (instanceId)
virUUIDFormat(instanceId, instanceIdStr);
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Could not find vf/instanceId %u/%s "
" in netlink response"),
vf, instanceIdStr);
/* go through all the entries again. This seems tedious,
* but experience has shown the resulting log to be
* very useful.
*/
VIR_WARN("IFLA_VF_PORTS entries that were returned:");
nla_for_each_nested(tb_vf_ports, tb[IFLA_VF_PORTS], rem) {
char uuidstr[VIR_UUID_STRING_BUFLEN] = "(none)";
if (nla_parse_nested(tb_port, IFLA_PORT_MAX, tb_vf_ports,
ifla_port_policy)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("error parsing IFLA_VF_PORT "
"during error reporting"));
goto cleanup;
}
if (tb_port[IFLA_PORT_INSTANCE_UUID]) {
virUUIDFormat((unsigned char *)
RTA_DATA(tb_port[IFLA_PORT_INSTANCE_UUID]),
uuidstr);
}
VIR_WARN(" vf: %d uuid: %s",
tb_port[IFLA_PORT_VF] ?
*(uint32_t *)RTA_DATA(tb_port[IFLA_PORT_VF]) : -1,
uuidstr);
}
goto cleanup;
}
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("IFLA_VF_PORTS is missing"));
goto cleanup;
}
}
if (tb_port[IFLA_PORT_RESPONSE]) {
*status = *(uint16_t *)RTA_DATA(tb_port[IFLA_PORT_RESPONSE]);
rc = 0;
} else {
if (is8021Qbg) {
/* no in-progress here; may be missing */
*status = PORT_PROFILE_RESPONSE_INPROGRESS;
rc = 0;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("no IFLA_PORT_RESPONSE found in netlink message"));
goto cleanup;
}
}
cleanup:
return rc;
}
static int
virNetDevVPortProfileOpSetLink(const char *ifname, int ifindex,
bool nltarget_kernel,
const virMacAddr *macaddr,
int vlanid,
const char *profileId,
struct ifla_port_vsi *portVsi,
const unsigned char *instanceId,
const unsigned char *hostUUID,
int32_t vf,
uint8_t op)
{
int rc = -1;
struct nlmsghdr *resp = NULL;
struct nlmsgerr *err;
struct ifinfomsg ifinfo = {
.ifi_family = AF_UNSPEC,
.ifi_index = ifindex,
};
unsigned int recvbuflen = 0;
int src_pid = 0;
uint32_t dst_pid = 0;
struct nl_msg *nl_msg;
struct nlattr *vfports = NULL, *vfport;
char macStr[VIR_MAC_STRING_BUFLEN];
char hostUUIDStr[VIR_UUID_STRING_BUFLEN];
char instanceUUIDStr[VIR_UUID_STRING_BUFLEN];
const char *opName;
switch (op) {
case PORT_REQUEST_PREASSOCIATE:
opName = "PREASSOCIATE";
break;
case PORT_REQUEST_PREASSOCIATE_RR:
opName = "PREASSOCIATE_RR";
break;
case PORT_REQUEST_ASSOCIATE:
opName = "ASSOCIATE";
break;
case PORT_REQUEST_DISASSOCIATE:
opName = "DISASSOCIATE";
break;
default:
opName = "(unknown)";
break;
}
VIR_INFO("%s: ifname: %s ifindex: %d vf: %d vlanid: %d mac: %s "
"profileId: %s instanceId: %s hostUUID: %s",
opName, ifname ? ifname : "(unspecified)",
ifindex, vf, vlanid,
macaddr ? virMacAddrFormat(macaddr, macStr) : "(unspecified)",
profileId ? profileId : "(unspecified)",
(instanceId
? virUUIDFormat(instanceId, instanceUUIDStr)
: "(unspecified)"),
(hostUUID
? virUUIDFormat(hostUUID, hostUUIDStr)
: "(unspecified)"));
nl_msg = nlmsg_alloc_simple(RTM_SETLINK, NLM_F_REQUEST);
if (!nl_msg) {
virReportOOMError();
return rc;
}
if (nlmsg_append(nl_msg, &ifinfo, sizeof(ifinfo), NLMSG_ALIGNTO) < 0)
goto buffer_too_small;
if (ifname &&
nla_put(nl_msg, IFLA_IFNAME, strlen(ifname)+1, ifname) < 0)
goto buffer_too_small;
if (macaddr || vlanid >= 0) {
struct nlattr *vfinfolist, *vfinfo;
if (!(vfinfolist = nla_nest_start(nl_msg, IFLA_VFINFO_LIST)))
goto buffer_too_small;
if (!(vfinfo = nla_nest_start(nl_msg, IFLA_VF_INFO)))
goto buffer_too_small;
if (macaddr) {
struct ifla_vf_mac ifla_vf_mac = {
.vf = vf,
.mac = { 0, },
};
virMacAddrGetRaw(macaddr, ifla_vf_mac.mac);
if (nla_put(nl_msg, IFLA_VF_MAC, sizeof(ifla_vf_mac),
&ifla_vf_mac) < 0)
goto buffer_too_small;
}
if (vlanid >= 0) {
struct ifla_vf_vlan ifla_vf_vlan = {
.vf = vf,
.vlan = vlanid,
.qos = 0,
};
if (nla_put(nl_msg, IFLA_VF_VLAN, sizeof(ifla_vf_vlan),
&ifla_vf_vlan) < 0)
goto buffer_too_small;
}
nla_nest_end(nl_msg, vfinfo);
nla_nest_end(nl_msg, vfinfolist);
}
if (vf == PORT_SELF_VF && nltarget_kernel) {
if (!(vfport = nla_nest_start(nl_msg, IFLA_PORT_SELF)))
goto buffer_too_small;
} else {
if (!(vfports = nla_nest_start(nl_msg, IFLA_VF_PORTS)))
goto buffer_too_small;
/* begin nesting vfports */
if (!(vfport = nla_nest_start(nl_msg, IFLA_VF_PORT)))
goto buffer_too_small;
}
if (profileId) {
if (nla_put(nl_msg, IFLA_PORT_PROFILE, strlen(profileId) + 1,
profileId) < 0)
goto buffer_too_small;
}
if (portVsi) {
if (nla_put(nl_msg, IFLA_PORT_VSI_TYPE, sizeof(*portVsi),
portVsi) < 0)
goto buffer_too_small;
}
if (instanceId) {
if (nla_put(nl_msg, IFLA_PORT_INSTANCE_UUID, VIR_UUID_BUFLEN,
instanceId) < 0)
goto buffer_too_small;
}
if (hostUUID) {
if (nla_put(nl_msg, IFLA_PORT_HOST_UUID, VIR_UUID_BUFLEN,
hostUUID) < 0)
goto buffer_too_small;
}
if (vf != PORT_SELF_VF) {
if (nla_put(nl_msg, IFLA_PORT_VF, sizeof(vf), &vf) < 0)
goto buffer_too_small;
}
if (nla_put(nl_msg, IFLA_PORT_REQUEST, sizeof(op), &op) < 0)
goto buffer_too_small;
/* end nesting of vport */
nla_nest_end(nl_msg, vfport);
if (vfports) {
/* end nesting of vfports */
nla_nest_end(nl_msg, vfports);
}
if (!nltarget_kernel) {
if ((src_pid = virNetlinkEventServiceLocalPid(NETLINK_ROUTE)) < 0)
goto cleanup;
if ((dst_pid = virNetDevVPortProfileGetLldpadPid()) == 0)
goto cleanup;
}
if (virNetlinkCommand(nl_msg, &resp, &recvbuflen,
src_pid, dst_pid, NETLINK_ROUTE, 0) < 0)
goto cleanup;
if (recvbuflen < NLMSG_LENGTH(0) || resp == NULL)
goto malformed_resp;
switch (resp->nlmsg_type) {
case NLMSG_ERROR:
err = (struct nlmsgerr *)NLMSG_DATA(resp);
if (resp->nlmsg_len < NLMSG_LENGTH(sizeof(*err)))
goto malformed_resp;
if (err->error) {
virReportSystemError(-err->error,
_("error during virtual port configuration of ifindex %d"),
ifindex);
goto cleanup;
}
break;
case NLMSG_DONE:
break;
default:
goto malformed_resp;
}
rc = 0;
cleanup:
nlmsg_free(nl_msg);
VIR_FREE(resp);
return rc;
malformed_resp:
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("malformed netlink response message"));
goto cleanup;
buffer_too_small:
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("allocated netlink buffer is too small"));
goto cleanup;
}
/**
* virNetDevVPortProfileGetNthParent
*
* @ifname : the name of the interface; ignored if ifindex is valid
* @ifindex : the index of the interface or -1 if ifname is given
* @nthParent : the nth parent interface to get
* @parent_ifindex : pointer to int
* @parent_ifname : pointer to buffer of size IFNAMSIZ
* @nth : the nth parent that is actually returned; if for example eth0.100
* was given and the 100th parent is to be returned, then eth0 will
* most likely be returned with nth set to 1 since the chain does
* not have more interfaces
*
* Get the nth parent interface of the given interface. 0 is the interface
* itself.
*
* Return 0 on success, < 0 otherwise
*/
static int
virNetDevVPortProfileGetNthParent(const char *ifname, int ifindex, unsigned int nthParent,
int *parent_ifindex, char *parent_ifname,
unsigned int *nth)
{
int rc = -1;
void *nlData = NULL;
struct nlattr *tb[IFLA_MAX + 1] = { NULL, };
bool end = false;
size_t i = 0;
*nth = 0;
if (ifindex <= 0 && virNetDevGetIndex(ifname, &ifindex) < 0)
return -1;
while (!end && i <= nthParent) {
VIR_FREE(nlData);
rc = virNetlinkDumpLink(ifname, ifindex, &nlData, tb, 0, 0);
if (rc < 0)
break;
if (tb[IFLA_IFNAME]) {
if (virStrcpy(parent_ifname, (char*)RTA_DATA(tb[IFLA_IFNAME]),
IFNAMSIZ) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("buffer for root interface name is too small"));
rc = -1;
goto cleanup;
}
*parent_ifindex = ifindex;
}
if (tb[IFLA_LINK]) {
ifindex = *(int *)RTA_DATA(tb[IFLA_LINK]);
ifname = NULL;
} else {
end = true;
}
i++;
}
*nth = i - 1;
cleanup:
VIR_FREE(nlData);
return rc;
}
/* Returns 0 on success, -1 on general failure, and -2 on timeout */
static int
virNetDevVPortProfileOpCommon(const char *ifname, int ifindex,
bool nltarget_kernel,
const virMacAddr *macaddr,
int vlanid,
const char *profileId,
struct ifla_port_vsi *portVsi,
const unsigned char *instanceId,
const unsigned char *hostUUID,
int32_t vf,
uint8_t op,
bool setlink_only)
{
int rc;
int src_pid = 0;
uint32_t dst_pid = 0;
void *nlData = NULL;
struct nlattr *tb[IFLA_MAX + 1] = { NULL, };
int repeats = STATUS_POLL_TIMEOUT_USEC / STATUS_POLL_INTERVL_USEC;
uint16_t status = 0;
bool is8021Qbg = (profileId == NULL);
rc = virNetDevVPortProfileOpSetLink(ifname, ifindex,
nltarget_kernel,
macaddr,
vlanid,
profileId,
portVsi,
instanceId,
hostUUID,
vf,
op);
if (rc < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("sending of PortProfileRequest failed."));
return rc;
}
if (setlink_only) /*for re-associations on existing links*/
return 0;
if (!nltarget_kernel &&
(((src_pid = virNetlinkEventServiceLocalPid(NETLINK_ROUTE)) < 0) ||
((dst_pid = virNetDevVPortProfileGetLldpadPid()) == 0))) {
rc = -1;
goto cleanup;
}
while (--repeats >= 0) {
VIR_FREE(nlData);
rc = virNetlinkDumpLink(NULL, ifindex, &nlData, tb, src_pid, dst_pid);
if (rc < 0)
goto cleanup;
rc = virNetDevVPortProfileGetStatus(tb, vf, instanceId, nltarget_kernel,
is8021Qbg, &status);
if (rc < 0)
goto cleanup;
if (status == PORT_PROFILE_RESPONSE_SUCCESS ||
status == PORT_VDP_RESPONSE_SUCCESS) {
break;
} else if (status == PORT_PROFILE_RESPONSE_INPROGRESS) {
/* keep trying... */
} else {
virReportSystemError(EINVAL,
_("error %d during port-profile setlink on "
"interface %s (%d)"),
status, ifname, ifindex);
rc = -1;
break;
}
g_usleep(STATUS_POLL_INTERVL_USEC);
}
if (status == PORT_PROFILE_RESPONSE_INPROGRESS) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("port-profile setlink timed out"));
rc = -2;
}
cleanup:
VIR_FREE(nlData);
return rc;
}
static int
virNetDevVPortProfileGetPhysdevAndVlan(const char *ifname, int *root_ifindex, char *root_ifname,
int *vlanid)
{
int ret;
unsigned int nth;
int ifindex = -1;
*vlanid = -1;
while (1) {
if ((ret = virNetDevVPortProfileGetNthParent(ifname, ifindex, 1,
root_ifindex, root_ifname, &nth)) < 0)
return ret;
if (nth == 0)
break;
if (*vlanid == -1) {
if (virNetDevGetVLanID(root_ifname, vlanid) < 0) {
virResetLastError();
*vlanid = -1;
}
}
ifindex = *root_ifindex;
ifname = NULL;
}
return 0;
}
/* Returns 0 on success, -1 on general failure, and -2 on timeout */
static int
virNetDevVPortProfileOp8021Qbg(const char *ifname,
const virMacAddr *macaddr,
int vf,
const virNetDevVPortProfile *virtPort,
enum virNetDevVPortProfileLinkOp virtPortOp,
bool setlink_only)
{
int rc = -1;
int op = PORT_REQUEST_ASSOCIATE;
struct ifla_port_vsi portVsi = {
.vsi_mgr_id = virtPort->managerID,
.vsi_type_version = virtPort->typeIDVersion,
};
bool nltarget_kernel = false;
int vlanid;
int physdev_ifindex = 0;
char physdev_ifname[IFNAMSIZ] = { 0, };
if (!ifname)
return -1;
vf = PORT_SELF_VF;
if (virNetDevVPortProfileGetPhysdevAndVlan(ifname, &physdev_ifindex,
physdev_ifname, &vlanid) < 0) {
goto cleanup;
}
if (vlanid < 0)
vlanid = 0;
portVsi.vsi_type_id[2] = virtPort->typeID >> 16;
portVsi.vsi_type_id[1] = virtPort->typeID >> 8;
portVsi.vsi_type_id[0] = virtPort->typeID;
switch (virtPortOp) {
case VIR_NETDEV_VPORT_PROFILE_LINK_OP_PREASSOCIATE:
op = PORT_REQUEST_PREASSOCIATE;
break;
case VIR_NETDEV_VPORT_PROFILE_LINK_OP_PREASSOCIATE_RR:
op = PORT_REQUEST_PREASSOCIATE_RR;
break;
case VIR_NETDEV_VPORT_PROFILE_LINK_OP_ASSOCIATE:
op = PORT_REQUEST_ASSOCIATE;
break;
case VIR_NETDEV_VPORT_PROFILE_LINK_OP_DISASSOCIATE:
op = PORT_REQUEST_DISASSOCIATE;
break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("operation type %d not supported"), virtPortOp);
goto cleanup;
}
rc = virNetDevVPortProfileOpCommon(physdev_ifname, physdev_ifindex,
nltarget_kernel,
macaddr,
vlanid,
NULL,
&portVsi,
virtPort->instanceID,
NULL,
vf,
op,
setlink_only);
cleanup:
return rc;
}
/* Returns 0 on success, -1 on general failure, and -2 on timeout */
static int
virNetDevVPortProfileOp8021Qbh(const char *ifname,
const virMacAddr *macaddr,
int32_t vf,
const virNetDevVPortProfile *virtPort,
const unsigned char *vm_uuid,
enum virNetDevVPortProfileLinkOp virtPortOp)
{
int rc = 0;
char *physfndev = NULL;
unsigned char hostuuid[VIR_UUID_BUFLEN];
bool nltarget_kernel = true;
int ifindex;
int vlanid = -1;
bool is_vf = false;
if (vf == -1) {
int isvf_ret = virNetDevIsVirtualFunction(ifname);
if (isvf_ret == -1)
goto cleanup;
is_vf = !!isvf_ret;
}
if (is_vf) {
if (virNetDevGetVirtualFunctionInfo(ifname, &physfndev, &vf) < 0) {
rc = -1;
goto cleanup;
}
} else {
physfndev = g_strdup(ifname);
}
rc = virNetDevGetIndex(physfndev, &ifindex);
if (rc < 0)
goto cleanup;
switch (virtPortOp) {
case VIR_NETDEV_VPORT_PROFILE_LINK_OP_PREASSOCIATE_RR:
case VIR_NETDEV_VPORT_PROFILE_LINK_OP_ASSOCIATE:
errno = virGetHostUUID(hostuuid);
if (errno) {
rc = -1;
goto cleanup;
}
rc = virNetDevVPortProfileOpCommon(NULL, ifindex,
nltarget_kernel,
macaddr,
vlanid,
virtPort->profileID,
NULL,
vm_uuid,
hostuuid,
vf,
(virtPortOp ==
VIR_NETDEV_VPORT_PROFILE_LINK_OP_PREASSOCIATE_RR) ?
PORT_REQUEST_PREASSOCIATE_RR
: PORT_REQUEST_ASSOCIATE,
false);
if (rc == -2)
/* Association timed out, disassociate */
virNetDevVPortProfileOpCommon(NULL, ifindex,
nltarget_kernel,
NULL,
vlanid,
NULL,
NULL,
NULL,
NULL,
vf,
PORT_REQUEST_DISASSOCIATE,
false);
break;
case VIR_NETDEV_VPORT_PROFILE_LINK_OP_DISASSOCIATE:
rc = virNetDevVPortProfileOpCommon(NULL, ifindex,
nltarget_kernel,
NULL,
vlanid,
NULL,
NULL,
NULL,
NULL,
vf,
PORT_REQUEST_DISASSOCIATE,
false);
break;
case VIR_NETDEV_VPORT_PROFILE_LINK_OP_PREASSOCIATE:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("operation type %d not supported"), virtPortOp);
rc = -1;
break;
default:
virReportEnumRangeError(virNetDevVPortProfileType, virtPortOp);
rc = -1;
break;
}
cleanup:
VIR_FREE(physfndev);
return rc;
}
/**
* virNetDevVPortProfileAssociate:
*
* @macvtap_ifname: The name of the macvtap device
* @virtPort: pointer to the object holding port profile parameters
* @vmuuid : the UUID of the virtual machine
* @vmOp : The VM operation (i.e., create, no-op)
* @setlink_only : Only set the link - dont wait for the link to come up
*
* Associate a port on a switch with a profile. This function
* may notify a kernel driver or an external daemon to run
* the setup protocol. If profile parameters were not supplied
* by the user, then this function returns without doing
* anything.
*
* Returns 0 in case of success, < 0 otherwise with error
* having been reported.
*/
int
virNetDevVPortProfileAssociate(const char *macvtap_ifname,
const virNetDevVPortProfile *virtPort,
const virMacAddr *macvtap_macaddr,
const char *linkdev,
int vf,
const unsigned char *vmuuid,
virNetDevVPortProfileOp vmOp,
bool setlink_only)
{
int rc = 0;
char uuidStr[VIR_UUID_STRING_BUFLEN];
char macStr[VIR_MAC_STRING_BUFLEN];
VIR_DEBUG("profile:'%p' vmOp: %s device: %s@%s mac: %s uuid: %s",
virtPort, virNetDevVPortProfileOpTypeToString(vmOp),
NULLSTR_EMPTY(macvtap_ifname), linkdev,
(macvtap_macaddr
? virMacAddrFormat(macvtap_macaddr, macStr)
: "(unspecified)"),
vmuuid ? virUUIDFormat(vmuuid, uuidStr) : "(unspecified)");
if (!virtPort || vmOp == VIR_NETDEV_VPORT_PROFILE_OP_NO_OP)
return 0;
switch (virtPort->virtPortType) {
case VIR_NETDEV_VPORT_PROFILE_NONE:
case VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH:
case VIR_NETDEV_VPORT_PROFILE_LAST:
break;
case VIR_NETDEV_VPORT_PROFILE_8021QBG:
rc = virNetDevVPortProfileOp8021Qbg(macvtap_ifname, macvtap_macaddr,
vf, virtPort,
(vmOp == VIR_NETDEV_VPORT_PROFILE_OP_MIGRATE_IN_START)
? VIR_NETDEV_VPORT_PROFILE_LINK_OP_PREASSOCIATE
: VIR_NETDEV_VPORT_PROFILE_LINK_OP_ASSOCIATE,
setlink_only);
break;
case VIR_NETDEV_VPORT_PROFILE_8021QBH:
rc = virNetDevVPortProfileOp8021Qbh(linkdev, macvtap_macaddr, vf,
virtPort, vmuuid,
(vmOp == VIR_NETDEV_VPORT_PROFILE_OP_MIGRATE_IN_START)
? VIR_NETDEV_VPORT_PROFILE_LINK_OP_PREASSOCIATE_RR
: VIR_NETDEV_VPORT_PROFILE_LINK_OP_ASSOCIATE);
if (vmOp != VIR_NETDEV_VPORT_PROFILE_OP_MIGRATE_IN_START && !rc) {
/* XXX bogus error handling */
ignore_value(virNetDevSetOnline(linkdev, true));
}
break;
}
return rc;
}
/**
* virNetDevVPortProfileDisassociate:
*
* @macvtap_ifname: The name of the macvtap device
* @macvtap_macaddr : The MAC address of the macvtap
* @linkdev: The link device in case of macvtap
* @virtPort: point to object holding port profile parameters
*
* Returns 0 in case of success, != 0 otherwise with error
* having been reported.
*/
int
virNetDevVPortProfileDisassociate(const char *macvtap_ifname,
const virNetDevVPortProfile *virtPort,
const virMacAddr *macvtap_macaddr,
const char *linkdev,
int vf,
virNetDevVPortProfileOp vmOp)
{
int rc = 0;
char macStr[VIR_MAC_STRING_BUFLEN];
VIR_DEBUG("profile:'%p' vmOp: %s device: %s@%s mac: %s",
virtPort, virNetDevVPortProfileOpTypeToString(vmOp),
NULLSTR_EMPTY(macvtap_ifname), linkdev,
(macvtap_macaddr
? virMacAddrFormat(macvtap_macaddr, macStr)
: "(unspecified)"));
if (!virtPort)
return 0;
switch (virtPort->virtPortType) {
case VIR_NETDEV_VPORT_PROFILE_NONE:
case VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH:
case VIR_NETDEV_VPORT_PROFILE_LAST:
break;
case VIR_NETDEV_VPORT_PROFILE_8021QBG:
rc = virNetDevVPortProfileOp8021Qbg(macvtap_ifname, macvtap_macaddr, vf,
virtPort,
VIR_NETDEV_VPORT_PROFILE_LINK_OP_DISASSOCIATE, false);
break;
case VIR_NETDEV_VPORT_PROFILE_8021QBH:
/* avoid disassociating twice */
if (vmOp == VIR_NETDEV_VPORT_PROFILE_OP_MIGRATE_IN_FINISH)
break;
if (vf < 0)
ignore_value(virNetDevSetOnline(linkdev, false));
rc = virNetDevVPortProfileOp8021Qbh(linkdev, macvtap_macaddr, vf,
virtPort, NULL,
VIR_NETDEV_VPORT_PROFILE_LINK_OP_DISASSOCIATE);
break;
}
return rc;
}
#else /* ! WITH_VIRTUALPORT */
int virNetDevVPortProfileAssociate(const char *macvtap_ifname G_GNUC_UNUSED,
const virNetDevVPortProfile *virtPort G_GNUC_UNUSED,
const virMacAddr *macvtap_macaddr G_GNUC_UNUSED,
const char *linkdev G_GNUC_UNUSED,
int vf G_GNUC_UNUSED,
const unsigned char *vmuuid G_GNUC_UNUSED,
virNetDevVPortProfileOp vmOp G_GNUC_UNUSED,
bool setlink_only G_GNUC_UNUSED)
{
virReportSystemError(ENOSYS, "%s",
_("Virtual port profile association not supported on this platform"));
return -1;
}
int virNetDevVPortProfileDisassociate(const char *macvtap_ifname G_GNUC_UNUSED,
const virNetDevVPortProfile *virtPort G_GNUC_UNUSED,
const virMacAddr *macvtap_macaddr G_GNUC_UNUSED,
const char *linkdev G_GNUC_UNUSED,
int vf G_GNUC_UNUSED,
virNetDevVPortProfileOp vmOp G_GNUC_UNUSED)
{
virReportSystemError(ENOSYS, "%s",
_("Virtual port profile association not supported on this platform"));
return -1;
}
#endif /* ! WITH_VIRTUALPORT */