/* * 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 defined(WITH_LIBNL) # include # include # include # include "virnetlink.h" # include "virfile.h" # include "virlog.h" # include "virnetdev.h" # include "virsocket.h" VIR_LOG_INIT("util.netdevvportprofile"); # define MICROSEC_PER_SEC (1000 * 1000) # 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; } virNetDevVPortProfile * virNetDevVPortProfileCopy(const virNetDevVPortProfile *src) { virNetDevVPortProfile *ret = NULL; if (!src) return NULL; ret = g_new0(virNetDevVPortProfile, 1); memcpy(ret, src, sizeof(*ret)); return ret; } /* 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(virNetDevVPortProfile *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 %1$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 %1$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(virNetDevVPortProfile *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 (%1$s and %2$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 (%1$d and %2$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 (%1$d and %2$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 (%1$d and %2$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 ('%1$s' and '%2$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 ('%1$s' and '%2$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 ('%1$s' and '%2$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(virNetDevVPortProfile **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 */ *result = g_new0(virNetDevVPortProfile, 1); /* 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 defined(WITH_LIBNL) 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 %1$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) { 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")); return -1; } } else { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IFLA_PORT_SELF is missing")); return -1; } } 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")); return -1; } 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")); return -1; } /* 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 %1$u/%2$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")); return -1; } 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); } return -1; } } else { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IFLA_VF_PORTS is missing")); return -1; } } if (tb_port[IFLA_PORT_RESPONSE]) { *status = *(uint16_t *)RTA_DATA(tb_port[IFLA_PORT_RESPONSE]); } else { if (is8021Qbg) { /* no in-progress here; may be missing */ *status = PORT_PROFILE_RESPONSE_INPROGRESS; } else { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no IFLA_PORT_RESPONSE found in netlink message")); return -1; } } return 0; } 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 = virNetlinkMsgNew(RTM_SETLINK, NLM_F_REQUEST); 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 %1$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 %1$d during port-profile setlink on interface %2$s (%3$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 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) { return -1; } 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 %1$d not supported"), virtPortOp); return -1; } return virNetDevVPortProfileOpCommon(physdev_ifname, physdev_ifindex, nltarget_kernel, macaddr, vlanid, NULL, &portVsi, virtPort->instanceID, NULL, vf, op, setlink_only); } /* 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 %1$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_LIBNL */ 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_LIBNL */