libvirt/src/util/virnetdevvportprofile.c
Antoni Segura Puimedon a9fbe3b157 docs: schema and docs for the midonet virtualport type
Midonet is an opensource virtual networking that over lays the IP
network between hypervisors. Currently, such networks can be made
with the openvswitch virtualport type.

This patch, defines the schema and documentation that will serve
as basis for the follow up patches that will add support to libvirt
for using Midonet virtual ports for its interfaces. The schema
definition requires that the port profile expresses its interfaceid
as part of the port profile. For that reason, this is part of the
patch too.

Signed-off-by: Antoni Segura Puimedon <toni+libvirt@midokura.com>
2015-03-17 13:09:05 -04:00

1285 lines
42 KiB
C

/*
* Copyright (C) 2009-2014 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
* <http://www.gnu.org/licenses/>.
*
* Authors:
* Stefan Berger <stefanb@us.ibm.com>
* Daniel P. Berrange <berrange@redhat.com>
*/
#include <config.h>
#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 <stdint.h>
# include <stdio.h>
# include <errno.h>
# include <fcntl.h>
# include <c-ctype.h>
# include <sys/socket.h>
# include <sys/ioctl.h>
# include <net/if.h>
# include <linux/if_tun.h>
# 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(virNetDevVPortProfilePtr a, virNetDevVPortProfilePtr 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;
}
/* 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 <virtualport type='%s'>"), 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(virNetDevVPortProfilePtr 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 <virtualport type='%s'>"),
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,
virNetDevVPortProfilePtr 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) == NULL) {
/* 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,
virNetDevVPortProfilePtr fromInterface,
virNetDevVPortProfilePtr fromNetwork,
virNetDevVPortProfilePtr 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' || c_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;
}
if (instanceId &&
tb_port[IFLA_PORT_INSTANCE_UUID] &&
!memcmp(instanceId,
(unsigned char *)
RTA_DATA(tb_port[IFLA_PORT_INSTANCE_UUID]),
VIR_UUID_BUFLEN) &&
tb_port[IFLA_PORT_VF] &&
vf == *(uint32_t *)RTA_DATA(tb_port[IFLA_PORT_VF])) {
found = true;
break;
}
}
if (!found) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Could not find netlink response with "
"expected parameters"));
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;
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 = virNetDevLinkDump(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)) {
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 = virNetDevLinkDump(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;
}
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_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 {
if (VIR_STRDUP(physfndev, ifname) < 0) {
rc = -1;
goto cleanup;
}
}
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;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("operation type %d not supported"), virtPortOp);
rc = -1;
}
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;
VIR_DEBUG("Associating port profile '%p' on link device '%s'",
virtPort, (macvtap_ifname ? macvtap_ifname : linkdev));
VIR_DEBUG("%s: VM OPERATION: %s", __FUNCTION__, virNetDevVPortProfileOpTypeToString(vmOp));
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;
VIR_DEBUG("Disassociating port profile id '%p' on link device '%s' ",
virtPort, macvtap_ifname);
VIR_DEBUG("%s: VM OPERATION: %s", __FUNCTION__, virNetDevVPortProfileOpTypeToString(vmOp));
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 ATTRIBUTE_UNUSED,
const virNetDevVPortProfile *virtPort ATTRIBUTE_UNUSED,
const virMacAddr *macvtap_macaddr ATTRIBUTE_UNUSED,
const char *linkdev ATTRIBUTE_UNUSED,
int vf ATTRIBUTE_UNUSED,
const unsigned char *vmuuid ATTRIBUTE_UNUSED,
virNetDevVPortProfileOp vmOp ATTRIBUTE_UNUSED,
bool setlink_only ATTRIBUTE_UNUSED)
{
virReportSystemError(ENOSYS, "%s",
_("Virtual port profile association not supported on this platform"));
return -1;
}
int virNetDevVPortProfileDisassociate(const char *macvtap_ifname ATTRIBUTE_UNUSED,
const virNetDevVPortProfile *virtPort ATTRIBUTE_UNUSED,
const virMacAddr *macvtap_macaddr ATTRIBUTE_UNUSED,
const char *linkdev ATTRIBUTE_UNUSED,
int vf ATTRIBUTE_UNUSED,
virNetDevVPortProfileOp vmOp ATTRIBUTE_UNUSED)
{
virReportSystemError(ENOSYS, "%s",
_("Virtual port profile association not supported on this platform"));
return -1;
}
#endif /* ! WITH_VIRTUALPORT */