/*
 * 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
 * <http://www.gnu.org/licenses/>.
 */

#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 defined(WITH_LIBNL)

# include <fcntl.h>

# include <net/if.h>
# include <linux/if_tun.h>

# 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;
}


int virNetDevVPortProfileCopy(virNetDevVPortProfilePtr *dst, const virNetDevVPortProfile *src)
{
    if (!src) {
        *dst = NULL;
        return 0;
    }

    *dst = g_new0(virNetDevVPortProfile, 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 <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(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 <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,
                           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 */
    *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 %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 %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"));
                        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 = 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 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 %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 %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 */
