5c27b6
From 1a69b4e4b8f0eed78951464a8916356961203687 Mon Sep 17 00:00:00 2001
5c27b6
Message-Id: <1a69b4e4b8f0eed78951464a8916356961203687@dist-git>
5c27b6
From: Laine Stump <laine@laine.org>
5c27b6
Date: Thu, 13 Apr 2017 14:29:29 -0400
5c27b6
Subject: [PATCH] util: new functions virNetDev(Save|Read|Set)NetConfig()
5c27b6
5c27b6
These three functions are destined to replace
5c27b6
virNetDev(Replace|Restore)NetConfig() and
5c27b6
virNetDev(Replace|Restore)MacAddress(), which both do the save and set
5c27b6
together as a single step. We need to separate the save, read, and set
5c27b6
steps because there will be situations where we need to do something
5c27b6
else in between (in particular, we will need to rebind a VF's driver
5c27b6
after save but before set).
5c27b6
5c27b6
This patch creates the new functions, but doesn't call them - that
5c27b6
will come in a subsequent patch. Note that the new functions to
5c27b6
read/write the file that stores the original network config now uses
5c27b6
JSON rather than plaintext (it still recognizes the old format as well
5c27b6
though, so it won't get confused during an upgrade).
5c27b6
5c27b6
Resolves: https://bugzilla.redhat.com/1442040 (RHEL 7.3.z)
5c27b6
Resolves: https://bugzilla.redhat.com/1415609 (RHEL 7.4)
5c27b6
5c27b6
(cherry picked from commit 26694daf0997290e4a02fd10b1c2e24678ca8653)
5c27b6
---
5c27b6
 src/libvirt_private.syms |   3 +
5c27b6
 src/util/virnetdev.c     | 590 ++++++++++++++++++++++++++++++++++++++++++++++-
5c27b6
 src/util/virnetdev.h     |  22 ++
5c27b6
 3 files changed, 613 insertions(+), 2 deletions(-)
5c27b6
5c27b6
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
5c27b6
index f91703f2e..9b0bc0100 100644
5c27b6
--- a/src/libvirt_private.syms
5c27b6
+++ b/src/libvirt_private.syms
5c27b6
@@ -1900,6 +1900,7 @@ virNetDevIfStateTypeFromString;
5c27b6
 virNetDevIfStateTypeToString;
5c27b6
 virNetDevIsVirtualFunction;
5c27b6
 virNetDevPFGetVF;
5c27b6
+virNetDevReadNetConfig;
5c27b6
 virNetDevReplaceMacAddress;
5c27b6
 virNetDevReplaceNetConfig;
5c27b6
 virNetDevRestoreMacAddress;
5c27b6
@@ -1909,11 +1910,13 @@ virNetDevRxFilterFree;
5c27b6
 virNetDevRxFilterModeTypeFromString;
5c27b6
 virNetDevRxFilterModeTypeToString;
5c27b6
 virNetDevRxFilterNew;
5c27b6
+virNetDevSaveNetConfig;
5c27b6
 virNetDevSetMAC;
5c27b6
 virNetDevSetMTU;
5c27b6
 virNetDevSetMTUFromDevice;
5c27b6
 virNetDevSetName;
5c27b6
 virNetDevSetNamespace;
5c27b6
+virNetDevSetNetConfig;
5c27b6
 virNetDevSetOnline;
5c27b6
 virNetDevSetPromiscuous;
5c27b6
 virNetDevSetRcvAllMulti;
5c27b6
diff --git a/src/util/virnetdev.c b/src/util/virnetdev.c
5c27b6
index 807b67697..b61ed0f0b 100644
5c27b6
--- a/src/util/virnetdev.c
5c27b6
+++ b/src/util/virnetdev.c
5c27b6
@@ -33,6 +33,7 @@
5c27b6
 #include "virlog.h"
5c27b6
 #include "virstring.h"
5c27b6
 #include "virutil.h"
5c27b6
+#include "virjson.h"
5c27b6
 
5c27b6
 #include <sys/ioctl.h>
5c27b6
 #include <net/if.h>
5c27b6
@@ -1902,7 +1903,551 @@ virNetDevRestoreNetConfig(const char *linkdev, int vf, const char *stateDir)
5c27b6
     return ret;
5c27b6
 }
5c27b6
 
5c27b6
-#else /* defined(__linux__) && defined(HAVE_LIBNL) */
5c27b6
+# define VIR_NETDEV_KEYNAME_ADMIN_MAC "adminMac"
5c27b6
+# define VIR_NETDEV_KEYNAME_VLAN_TAG "vlanTag"
5c27b6
+# define VIR_NETDEV_KEYNAME_MAC "mac"
5c27b6
+
5c27b6
+/**
5c27b6
+ * virNetDevSaveNetConfig:
5c27b6
+ * @linkdev: name of the interface
5c27b6
+ * @vf: vf index if linkdev is a pf
5c27b6
+ * @stateDir: directory to store old net config
5c27b6
+ * @saveVlan: false if we shouldn't attempt to save vlan tag info
5c27b6
+ *            (eg for interfaces using 802.1Qbg, since it handles
5c27b6
+ *            vlan tags internally)
5c27b6
+ *
5c27b6
+ * Save current MAC address and (if linkdev itself is a VF, or if @vf
5c27b6
+ * >= 0) the "admin MAC address" and vlan tag the device described by
5c27b6
+ * @linkdev:@vf to @stateDir. (the "admin MAC address" is stored in
5c27b6
+ * the PF, and is what the VF MAC will be initialized to the next time
5c27b6
+ * its driver is reloaded (either on host or guest).
5c27b6
+ *
5c27b6
+ * File Format:
5c27b6
+ *
5c27b6
+ * The file is in json format and will contain 1 or more of the
5c27b6
+ * following values:
5c27b6
+ *
5c27b6
+ *      "mac"      - VF MAC address (or missing if VF has no host net driver)
5c27b6
+ *      "vlanTag"  - a single vlan tag id
5c27b6
+ *      "adminMac" - admin MAC address (stored in the PF)
5c27b6
+ *
5c27b6
+ * For example:
5c27b6
+ *
5c27b6
+ *    {"mac": "9A:11:22:33:44:55",
5c27b6
+ *     "vlanTag": "42",
5c27b6
+ *     "adminMac": "00:00:00:00:00:00"
5c27b6
+ *    }
5c27b6
+ *
5c27b6
+ * File Name:
5c27b6
+ *
5c27b6
+ * If the device is a VF and we're allowed to save vlan tag info, the
5c27b6
+ * file will be named ${pfDevName_vf#{vf} (e.g. "enp2s0f0_vf5") and
5c27b6
+ * will contain at least "adminMac" and "vlanTag" (if the device was bound
5c27b6
+ * to a net driver on the host prior to use, it will also have "mac"..
5c27b6
+ * If the device isn't a VF, or we're not allowed to save vlan tag
5c27b6
+ * info, the file will be named ${linkdev} (e.g. "enp3s0f0") and will
5c27b6
+ * contain just linkdev's MAC address.
5c27b6
+ *
5c27b6
+ * Returns 0 on success, -1 on failure
5c27b6
+ *
5c27b6
+ */
5c27b6
+int
5c27b6
+virNetDevSaveNetConfig(const char *linkdev, int vf,
5c27b6
+                       const char *stateDir,
5c27b6
+                       bool saveVlan)
5c27b6
+{
5c27b6
+    int ret = -1;
5c27b6
+    const char *pfDevName = NULL;
5c27b6
+    char *pfDevOrig = NULL;
5c27b6
+    char *vfDevOrig = NULL;
5c27b6
+    virMacAddr oldMAC;
5c27b6
+    char MACStr[VIR_MAC_STRING_BUFLEN];
5c27b6
+    int oldVlanTag = -1;
5c27b6
+    char *filePath = NULL;
5c27b6
+    char *fileStr = NULL;
5c27b6
+    virJSONValuePtr configJSON = NULL;
5c27b6
+
5c27b6
+    if (vf >= 0) {
5c27b6
+        /* linkdev is the PF */
5c27b6
+        pfDevName = linkdev;
5c27b6
+
5c27b6
+        /* linkdev should get the VF's netdev name (or NULL if none) */
5c27b6
+        if (virNetDevPFGetVF(pfDevName, vf, &vfDevOrig) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        linkdev = vfDevOrig;
5c27b6
+
5c27b6
+    } else if (saveVlan && virNetDevIsVirtualFunction(linkdev) == 1) {
5c27b6
+        /* when vf is -1, linkdev might be a standard netdevice (not
5c27b6
+         * SRIOV), or it might be an SRIOV VF. If it's a VF, normalize
5c27b6
+         * it to PF + VFname
5c27b6
+         */
5c27b6
+
5c27b6
+        if (virNetDevGetPhysicalFunction(linkdev, &pfDevOrig) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        pfDevName = pfDevOrig;
5c27b6
+
5c27b6
+        if (virNetDevGetVirtualFunctionIndex(pfDevName, linkdev, &vf) < 0)
5c27b6
+            goto cleanup;
5c27b6
+    }
5c27b6
+
5c27b6
+    if (!(configJSON = virJSONValueNewObject()))
5c27b6
+        goto cleanup;
5c27b6
+
5c27b6
+    /* if there is a PF, it's now in pfDevName, and linkdev is either
5c27b6
+     * the VF's name, or NULL (if the VF isn't bound to a net driver
5c27b6
+     * on the host)
5c27b6
+     */
5c27b6
+
5c27b6
+    if (pfDevName) {
5c27b6
+        if (virAsprintf(&filePath, "%s/%s_vf%d", stateDir, pfDevName, vf) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        /* get admin MAC and vlan tag */
5c27b6
+        if (virNetDevGetVfConfig(pfDevName, vf, &oldMAC,
5c27b6
+                                 saveVlan ? &oldVlanTag : NULL) < 0) {
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+
5c27b6
+        if (virJSONValueObjectAppendString(configJSON,
5c27b6
+                                           VIR_NETDEV_KEYNAME_ADMIN_MAC,
5c27b6
+                                           virMacAddrFormat(&oldMAC, MACStr)) < 0 ||
5c27b6
+            virJSONValueObjectAppendNumberInt(configJSON,
5c27b6
+                                              VIR_NETDEV_KEYNAME_VLAN_TAG,
5c27b6
+                                              oldVlanTag) < 0) {
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+
5c27b6
+    } else {
5c27b6
+        if (virAsprintf(&filePath, "%s/%s", stateDir, linkdev) < 0)
5c27b6
+            goto cleanup;
5c27b6
+    }
5c27b6
+
5c27b6
+    if (linkdev) {
5c27b6
+        if (virNetDevGetMAC(linkdev, &oldMAC) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        /* for interfaces with no pfDevName (i.e. not a VF, this will
5c27b6
+         * be the only value in the file.
5c27b6
+         */
5c27b6
+        if (virJSONValueObjectAppendString(configJSON, VIR_NETDEV_KEYNAME_MAC,
5c27b6
+                                           virMacAddrFormat(&oldMAC, MACStr)) < 0)
5c27b6
+           goto cleanup;
5c27b6
+    }
5c27b6
+
5c27b6
+    if (!(fileStr = virJSONValueToString(configJSON, true)))
5c27b6
+        goto cleanup;
5c27b6
+
5c27b6
+    if (virFileWriteStr(filePath, fileStr, O_CREAT|O_TRUNC|O_WRONLY) < 0) {
5c27b6
+        virReportSystemError(errno, _("Unable to preserve mac/vlan tag "
5c27b6
+                                      "for device = %s, vf = %d"), linkdev, vf);
5c27b6
+        goto cleanup;
5c27b6
+    }
5c27b6
+
5c27b6
+    ret = 0;
5c27b6
+ cleanup:
5c27b6
+    VIR_FREE(pfDevOrig);
5c27b6
+    VIR_FREE(vfDevOrig);
5c27b6
+    VIR_FREE(filePath);
5c27b6
+    VIR_FREE(fileStr);
5c27b6
+    virJSONValueFree(configJSON);
5c27b6
+    return ret;
5c27b6
+}
5c27b6
+
5c27b6
+
5c27b6
+/**
5c27b6
+ * virNetDevReadNetConfig:
5c27b6
+ * @linkdev: name of the interface
5c27b6
+ * @vf: vf index if linkdev is a pf
5c27b6
+ * @stateDir: directory where net config is stored
5c27b6
+ * @adminMAC: returns admin MAC to store in the PF (if this is a VF)
5c27b6
+ * @MAC: returns MAC to set on device immediately
5c27b6
+ *
5c27b6
+ * Read saved MAC address and (if linkdev itself is a VF, or if @vf >=
5c27b6
+ * 0) "admin MAC address" and vlan tag of the device described by
5c27b6
+ * @linkdev:@vf from a file in @stateDir. (see virNetDevSaveNetConfig
5c27b6
+ * for details of file name and format).
5c27b6
+ *
5c27b6
+ * Returns 0 on success, -1 on failure.
5c27b6
+ *
5c27b6
+ * The caller MUST free adminMAC, vlan, and MAC when it is finished
5c27b6
+ * with them (they will be NULL if they weren't found in the file)
5c27b6
+ *
5c27b6
+ */
5c27b6
+int
5c27b6
+virNetDevReadNetConfig(const char *linkdev, int vf,
5c27b6
+                       const char *stateDir,
5c27b6
+                       virMacAddrPtr *adminMAC,
5c27b6
+                       virNetDevVlanPtr *vlan,
5c27b6
+                       virMacAddrPtr *MAC)
5c27b6
+{
5c27b6
+    int ret = -1;
5c27b6
+    const char *pfDevName = NULL;
5c27b6
+    char *pfDevOrig = NULL;
5c27b6
+    char *vfDevOrig = NULL;
5c27b6
+    char *filePath = NULL;
5c27b6
+    char *fileStr = NULL;
5c27b6
+    virJSONValuePtr configJSON = NULL;
5c27b6
+    const char *MACStr = NULL;
5c27b6
+    const char *adminMACStr = NULL;
5c27b6
+    int vlanTag = -1;
5c27b6
+
5c27b6
+    *adminMAC = NULL;
5c27b6
+    *vlan = NULL;
5c27b6
+    *MAC = NULL;
5c27b6
+
5c27b6
+    if (vf >= 0) {
5c27b6
+        /* linkdev is the PF */
5c27b6
+        pfDevName = linkdev;
5c27b6
+
5c27b6
+        /* linkdev should get the VF's netdev name (or NULL if none) */
5c27b6
+        if (virNetDevPFGetVF(pfDevName, vf, &vfDevOrig) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        linkdev = vfDevOrig;
5c27b6
+
5c27b6
+    } else if (virNetDevIsVirtualFunction(linkdev) == 1) {
5c27b6
+        /* when vf is -1, linkdev might be a standard netdevice (not
5c27b6
+         * SRIOV), or it might be an SRIOV VF. If it's a VF, normalize
5c27b6
+         * it to PF + VFname
5c27b6
+         */
5c27b6
+
5c27b6
+        if (virNetDevGetPhysicalFunction(linkdev, &pfDevOrig) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        pfDevName = pfDevOrig;
5c27b6
+
5c27b6
+        if (virNetDevGetVirtualFunctionIndex(pfDevName, linkdev, &vf) < 0)
5c27b6
+            goto cleanup;
5c27b6
+    }
5c27b6
+
5c27b6
+    /* if there is a PF, it's now in pfDevName, and linkdev is either
5c27b6
+     * the VF's name, or NULL (if the VF isn't bound to a net driver
5c27b6
+     * on the host)
5c27b6
+     */
5c27b6
+
5c27b6
+    if (pfDevName) {
5c27b6
+        if (virAsprintf(&filePath, "%s/%s_vf%d", stateDir, pfDevName, vf) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        if (linkdev && !virFileExists(filePath)) {
5c27b6
+            /* the device may have been stored in a file named for the
5c27b6
+             * VF due to saveVlan == false (or an older version of
5c27b6
+             * libvirt), so reset filePath so we'll try the other
5c27b6
+             * filename before failing.
5c27b6
+             */
5c27b6
+            VIR_FREE(filePath);
5c27b6
+            pfDevName = NULL;
5c27b6
+        }
5c27b6
+    }
5c27b6
+
5c27b6
+    if (!pfDevName) {
5c27b6
+        if (virAsprintf(&filePath, "%s/%s", stateDir, linkdev) < 0)
5c27b6
+            goto cleanup;
5c27b6
+    }
5c27b6
+
5c27b6
+    if (virFileReadAll(filePath, 128, &fileStr) < 0)
5c27b6
+        goto cleanup;
5c27b6
+
5c27b6
+    if (strchr("0123456789abcdefABCDEF", fileStr[0])) {
5c27b6
+        const char *vlanStr = NULL;
5c27b6
+
5c27b6
+        /* old version of file - just two lines of text. Line 1 is the
5c27b6
+         * MAC address (or if line 2 is present, line 1 is adminMAC),
5c27b6
+         * and line 2 (if present) is the vlan tag
5c27b6
+         */
5c27b6
+
5c27b6
+        if ((vlanStr = strchr(fileStr, '\n'))) {
5c27b6
+            char *endptr;
5c27b6
+
5c27b6
+            /* if there are 2 lines, the first is adminMAC */
5c27b6
+            adminMACStr = fileStr;
5c27b6
+            vlanStr++;
5c27b6
+
5c27b6
+            if ((virStrToLong_i(vlanStr, &endptr, 10, &vlanTag) < 0) ||
5c27b6
+                (endptr && *endptr != '\n' && *endptr != 0)) {
5c27b6
+                virReportError(VIR_ERR_INTERNAL_ERROR,
5c27b6
+                               _("cannot parse vlan tag '%s' from file '%s'"),
5c27b6
+                               vlanStr, filePath);
5c27b6
+                goto cleanup;
5c27b6
+            }
5c27b6
+        } else {
5c27b6
+            /* if there is only one line, it is MAC */
5c27b6
+            MACStr = fileStr;
5c27b6
+        }
5c27b6
+    } else {
5c27b6
+        /* if it doesn't start with a hex digit, it is a modern
5c27b6
+         * version of the config file - JSON format as described in
5c27b6
+         * preamble to virNetDevSaveNetConfig()
5c27b6
+         */
5c27b6
+        if (!(configJSON = virJSONValueFromString(fileStr))) {
5c27b6
+            virReportError(VIR_ERR_INTERNAL_ERROR,
5c27b6
+                           _("invalid json in net device saved "
5c27b6
+                             "config file '%s': '%.60s'"),
5c27b6
+                           filePath, fileStr);
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+
5c27b6
+        MACStr = virJSONValueObjectGetString(configJSON,
5c27b6
+                                             VIR_NETDEV_KEYNAME_MAC);
5c27b6
+        adminMACStr = virJSONValueObjectGetString(configJSON,
5c27b6
+                                                  VIR_NETDEV_KEYNAME_ADMIN_MAC);
5c27b6
+        ignore_value(virJSONValueObjectGetNumberInt(configJSON,
5c27b6
+                                                    VIR_NETDEV_KEYNAME_VLAN_TAG,
5c27b6
+                                                    &vlanTag));
5c27b6
+
5c27b6
+        if (!(MACStr || adminMACStr)) {
5c27b6
+            virReportError(VIR_ERR_INTERNAL_ERROR,
5c27b6
+                           _("network device saved config file '%s' "
5c27b6
+                             "has unexpected contents, missing both "
5c27b6
+                             "'MAC' and 'adminMAC': '%.60s'"),
5c27b6
+                           filePath, fileStr);
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+    }
5c27b6
+
5c27b6
+    if (MACStr) {
5c27b6
+        if (VIR_ALLOC(*MAC) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        if (virMacAddrParse(MACStr, *MAC) < 0) {
5c27b6
+            virReportError(VIR_ERR_INTERNAL_ERROR,
5c27b6
+                           _("cannot parse MAC address '%s' from file '%s'"),
5c27b6
+                           MACStr, filePath);
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+    }
5c27b6
+
5c27b6
+    if (adminMACStr) {
5c27b6
+        if (VIR_ALLOC(*adminMAC) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        if (virMacAddrParse(adminMACStr, *adminMAC) < 0) {
5c27b6
+            virReportError(VIR_ERR_INTERNAL_ERROR,
5c27b6
+                           _("cannot parse MAC address '%s' from file '%s'"),
5c27b6
+                           adminMACStr, filePath);
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+    }
5c27b6
+
5c27b6
+    if (vlanTag != -1) {
5c27b6
+        /* construct a simple virNetDevVlan object with a single tag */
5c27b6
+        if (VIR_ALLOC(*vlan) < 0)
5c27b6
+            goto cleanup;
5c27b6
+        if (VIR_ALLOC((*vlan)->tag) < 0)
5c27b6
+            goto cleanup;
5c27b6
+        (*vlan)->nTags = 1;
5c27b6
+        (*vlan)->tag[0] = vlanTag;
5c27b6
+    }
5c27b6
+
5c27b6
+    /* we won't need the file again */
5c27b6
+    ignore_value(unlink(filePath));
5c27b6
+
5c27b6
+    ret = 0;
5c27b6
+ cleanup:
5c27b6
+    if (ret < 0) {
5c27b6
+        VIR_FREE(*adminMAC);
5c27b6
+        VIR_FREE(*MAC);
5c27b6
+        VIR_FREE(*vlan);
5c27b6
+    }
5c27b6
+
5c27b6
+    VIR_FREE(pfDevOrig);
5c27b6
+    VIR_FREE(vfDevOrig);
5c27b6
+    VIR_FREE(filePath);
5c27b6
+    VIR_FREE(fileStr);
5c27b6
+    virJSONValueFree(configJSON);
5c27b6
+    return ret;
5c27b6
+}
5c27b6
+
5c27b6
+
5c27b6
+/**
5c27b6
+ * virNetDevSetNetConfig:
5c27b6
+ * @linkdev: name of the interface
5c27b6
+ * @vf: vf index if linkdev is a PF
5c27b6
+ * @adminMAC: new admin MAC address (will be stored in PF and
5c27b6
+ *            used for next initialization of VF driver)
5c27b6
+ * @vlan: new vlan tag info (or NULL)
5c27b6
+ * @MAC: new MAC address to set on the device immediately
5c27b6
+ * @setVlan: true to enable setting vlan tag (even if @vlan is NULL,
5c27b6
+ *           the interface vlan tag will be set to 0).
5c27b6
+ *
5c27b6
+ *
5c27b6
+ * Set new MAC address and (optionally) admin MAC and vlan tag of
5c27b6
+ * @linkdev VF# @vf.
5c27b6
+ *
5c27b6
+ * Returns 0 on success, -1 on failure
5c27b6
+ *
5c27b6
+ */
5c27b6
+int
5c27b6
+virNetDevSetNetConfig(const char *linkdev, int vf,
5c27b6
+                      const virMacAddr *adminMAC,
5c27b6
+                      virNetDevVlanPtr vlan,
5c27b6
+                      const virMacAddr *MAC,
5c27b6
+                      bool setVlan)
5c27b6
+{
5c27b6
+    int ret = -1;
5c27b6
+    char MACStr[VIR_MAC_STRING_BUFLEN];
5c27b6
+    const char *pfDevName = NULL;
5c27b6
+    char *pfDevOrig = NULL;
5c27b6
+    char *vfDevOrig = NULL;
5c27b6
+    int vlanTag = -1;
5c27b6
+
5c27b6
+    if (vf >= 0) {
5c27b6
+        /* linkdev is the PF */
5c27b6
+        pfDevName = linkdev;
5c27b6
+
5c27b6
+        /* linkdev should get the VF's netdev name (or NULL if none) */
5c27b6
+        if (virNetDevPFGetVF(pfDevName, vf, &vfDevOrig) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        linkdev = vfDevOrig;
5c27b6
+
5c27b6
+    } else if (virNetDevIsVirtualFunction(linkdev) == 1) {
5c27b6
+        /* when vf is -1, linkdev might be a standard netdevice (not
5c27b6
+         * SRIOV), or it might be an SRIOV VF. If it's a VF, normalize
5c27b6
+         * it to PF + VFname
5c27b6
+         */
5c27b6
+
5c27b6
+        if (virNetDevGetPhysicalFunction(linkdev, &pfDevOrig) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        pfDevName = pfDevOrig;
5c27b6
+
5c27b6
+        if (virNetDevGetVirtualFunctionIndex(pfDevName, linkdev, &vf) < 0)
5c27b6
+            goto cleanup;
5c27b6
+    }
5c27b6
+
5c27b6
+
5c27b6
+    if (!pfDevName) {
5c27b6
+        /* if it's not SRIOV, then we can't set the admin MAC address
5c27b6
+         * or vlan tag
5c27b6
+         */
5c27b6
+        if (adminMAC) {
5c27b6
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
5c27b6
+                           _("admin MAC can only be set for SR-IOV VFs, but "
5c27b6
+                             "%s is not a VF"), linkdev);
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+
5c27b6
+        if (vlan) {
5c27b6
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
5c27b6
+                           _("vlan can only be set for SR-IOV VFs, but "
5c27b6
+                             "%s is not a VF"), linkdev);
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+
5c27b6
+    } else {
5c27b6
+        bool pfIsOnline;
5c27b6
+
5c27b6
+        /* Assure that PF is online before trying to use it to set
5c27b6
+         * anything up for this VF. It *should* be online already,
5c27b6
+         * but if it isn't online the changes made to the VF via the
5c27b6
+         * PF won't take effect, yet there will be no error
5c27b6
+         * reported. In the case that the PF isn't online, we need to
5c27b6
+         * fail and report the error, rather than automatically
5c27b6
+         * setting it online, since setting an unconfigured interface
5c27b6
+         * online automatically turns on IPv6 autoconfig, which may
5c27b6
+         * not be what the admin expects, so we require them to
5c27b6
+         * explicitly enable the PF in the host system network config.
5c27b6
+         */
5c27b6
+        if (virNetDevGetOnline(pfDevName, &pfIsOnline) < 0)
5c27b6
+            goto cleanup;
5c27b6
+
5c27b6
+        if (!pfIsOnline) {
5c27b6
+            virReportError(VIR_ERR_INTERNAL_ERROR,
5c27b6
+                           _("Unable to configure VF %d of PF '%s' "
5c27b6
+                             "because the PF is not online. Please "
5c27b6
+                             "change host network config to put the "
5c27b6
+                             "PF online."),
5c27b6
+                           vf, pfDevName);
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+
5c27b6
+        if (vlan) {
5c27b6
+            if (vlan->nTags != 1 || vlan->trunk) {
5c27b6
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
5c27b6
+                               _("vlan trunking is not supported "
5c27b6
+                                 "by SR-IOV network devices"));
5c27b6
+                goto cleanup;
5c27b6
+            }
5c27b6
+
5c27b6
+            if (!setVlan) {
5c27b6
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
5c27b6
+                               _("vlan tag set for interface %s but "
5c27b6
+                                 "caller requested it not be set"));
5c27b6
+                goto cleanup;
5c27b6
+            }
5c27b6
+
5c27b6
+            vlanTag = vlan->tag[0];
5c27b6
+
5c27b6
+        } else if (setVlan) {
5c27b6
+            vlanTag = 0; /* assure any existing vlan tag is reset */
5c27b6
+        }
5c27b6
+    }
5c27b6
+
5c27b6
+    if (MAC) {
5c27b6
+        if (!linkdev) {
5c27b6
+            virReportError(VIR_ERR_INTERNAL_ERROR,
5c27b6
+                           _("VF %d of PF '%s' is not bound to a net driver, "
5c27b6
+                             "so its MAC address cannot be set to %s"),
5c27b6
+                           vf, pfDevName, virMacAddrFormat(MAC, MACStr));
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+
5c27b6
+        if (virNetDevSetMAC(linkdev, MAC) < 0) {
5c27b6
+            /* This may have failed due to the "administratively
5c27b6
+             * set" flag being set in the PF for this VF. For now
5c27b6
+             * we will just fail, but in the future we should
5c27b6
+             * attempt to set the VF MAC via the PF.
5c27b6
+             */
5c27b6
+            goto cleanup;
5c27b6
+        }
5c27b6
+        if (pfDevOrig) {
5c27b6
+            /* if pfDevOrig is set, it means that the caller was
5c27b6
+             * *really* only interested in setting the MAC of the VF
5c27b6
+             * itself, *not* the admin MAC via the PF. In those cases,
5c27b6
+             * the adminMAC was only provided in case we need to set
5c27b6
+             * the VF's MAC by temporarily unbinding/rebinding the
5c27b6
+             * VF's net driver with the admin MAC set to the desired
5c27b6
+             * MAC, and then want to restore the admin MAC to its
5c27b6
+             * original setting when we're finished. We would only
5c27b6
+             * need to do that if the virNetDevSetMAC() above had
5c27b6
+             * failed; since it didn't, we don't need to set the
5c27b6
+             * adminMAC, so we are NULLing it out here to avoid that
5c27b6
+             * below.
5c27b6
+
5c27b6
+             * (NB: since setting the admin MAC sets the
5c27b6
+             * "administratively set" flag for the VF in the PF's
5c27b6
+             * driver, which prevents any future changes to the VF's
5c27b6
+             * MAC address, we want to avoid setting the admin MAC as
5c27b6
+             * much as possible.)
5c27b6
+             */
5c27b6
+            adminMAC = NULL;
5c27b6
+        }
5c27b6
+    }
5c27b6
+
5c27b6
+    if (adminMAC || vlanTag >= 0) {
5c27b6
+        /* Set vlanTag and admin MAC using an RTM_SETLINK request sent to
5c27b6
+         * PFdevname+VF#, if mac != NULL this will set the "admin MAC" via
5c27b6
+         * the PF, *not* the actual VF MAC - the admin MAC only takes
5c27b6
+         * effect the next time the VF's driver is initialized (either in
5c27b6
+         * guest or host). if there is a vlanTag to set, it will take
5c27b6
+         * effect immediately though.
5c27b6
+         */
5c27b6
+        if (virNetDevSetVfConfig(pfDevName, vf, adminMAC, vlanTag) < 0)
5c27b6
+            goto cleanup;
5c27b6
+    }
5c27b6
+
5c27b6
+    ret = 0;
5c27b6
+ cleanup:
5c27b6
+    VIR_FREE(pfDevOrig);
5c27b6
+    VIR_FREE(vfDevOrig);
5c27b6
+    return ret;
5c27b6
+}
5c27b6
+
5c27b6
+
5c27b6
+#else /* defined(__linux__) && defined(HAVE_LIBNL)  && defined(IFLA_VF_MAX) */
5c27b6
 
5c27b6
 int
5c27b6
 virNetDevReplaceNetConfig(const char *linkdev ATTRIBUTE_UNUSED,
5c27b6
@@ -1927,7 +2472,48 @@ virNetDevRestoreNetConfig(const char *linkdev ATTRIBUTE_UNUSED,
5c27b6
     return -1;
5c27b6
 }
5c27b6
 
5c27b6
-#endif /* defined(__linux__) && defined(HAVE_LIBNL) */
5c27b6
+
5c27b6
+int
5c27b6
+virNetDevSaveNetConfig(const char *linkdev ATTRIBUTE_UNUSED,
5c27b6
+                       int vf ATTRIBUTE_UNUSED,
5c27b6
+                       const char *stateDir ATTRIBUTE_UNUSED,
5c27b6
+                       bool saveVlan ATTRIBUTE_UNUSED)
5c27b6
+{
5c27b6
+    virReportSystemError(ENOSYS, "%s",
5c27b6
+                         _("Unable to save net device config on this platform"));
5c27b6
+    return -1;
5c27b6
+}
5c27b6
+
5c27b6
+
5c27b6
+int
5c27b6
+virNetDevReadNetConfig(const char *linkdev ATTRIBUTE_UNUSED,
5c27b6
+                       int vf ATTRIBUTE_UNUSED,
5c27b6
+                       const char *stateDir ATTRIBUTE_UNUSED,
5c27b6
+                       virMacAddrPtr *adminMAC ATTRIBUTE_UNUSED,
5c27b6
+                       virNetDevVLanPtr *vlan ATTRIBUTE_UNUSED,
5c27b6
+                       virMacAddrPtr *MAC ATTRIBUTE_UNUSED)
5c27b6
+{
5c27b6
+    virReportSystemError(ENOSYS, "%s",
5c27b6
+                         _("Unable to read net device config on this platform"));
5c27b6
+    return -1;
5c27b6
+}
5c27b6
+
5c27b6
+
5c27b6
+int
5c27b6
+virNetDevSetNetConfig(const char *linkdev ATTRIBUTE_UNUSED,
5c27b6
+                      int vf ATTRIBUTE_UNUSED,
5c27b6
+                      const virMacAddr *adminMAC ATTRIBUTE_UNUSED,
5c27b6
+                      virNetDevVlanPtr vlan ATTRIBUTE_UNUSED,
5c27b6
+                      const virMacAddr *MAC ATTRIBUTE_UNUSED,
5c27b6
+                      bool setVlan ATTRIBUTE_UNUSED)
5c27b6
+{
5c27b6
+    virReportSystemError(ENOSYS, "%s",
5c27b6
+                         _("Unable to set net device config on this platform"));
5c27b6
+    return -1;
5c27b6
+}
5c27b6
+
5c27b6
+
5c27b6
+#endif /* defined(__linux__) && defined(HAVE_LIBNL) && defined(IFLA_VF_MAX) */
5c27b6
 
5c27b6
 VIR_ENUM_IMPL(virNetDevIfState,
5c27b6
               VIR_NETDEV_IF_STATE_LAST,
5c27b6
diff --git a/src/util/virnetdev.h b/src/util/virnetdev.h
5c27b6
index d534a3946..b5abf7a7b 100644
5c27b6
--- a/src/util/virnetdev.h
5c27b6
+++ b/src/util/virnetdev.h
5c27b6
@@ -187,6 +187,28 @@ int virNetDevGetVirtualFunctions(const char *pfname,
5c27b6
     ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
5c27b6
     ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5) ATTRIBUTE_RETURN_CHECK;
5c27b6
 
5c27b6
+int virNetDevSaveNetConfig(const char *linkdev, int vf,
5c27b6
+                           const char *stateDir,
5c27b6
+                           bool saveVlan)
5c27b6
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
5c27b6
+
5c27b6
+int
5c27b6
+virNetDevReadNetConfig(const char *linkdev, int vf,
5c27b6
+                       const char *stateDir,
5c27b6
+                       virMacAddrPtr *adminMAC,
5c27b6
+                       virNetDevVlanPtr *vlan,
5c27b6
+                       virMacAddrPtr *MAC)
5c27b6
+   ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4)
5c27b6
+   ATTRIBUTE_NONNULL(5) ATTRIBUTE_NONNULL(6) ATTRIBUTE_RETURN_CHECK;
5c27b6
+
5c27b6
+int
5c27b6
+virNetDevSetNetConfig(const char *linkdev, int vf,
5c27b6
+                      const virMacAddr *adminMAC,
5c27b6
+                      virNetDevVlanPtr vlan,
5c27b6
+                      const virMacAddr *MAC,
5c27b6
+                      bool setVLan)
5c27b6
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK;
5c27b6
+
5c27b6
 int virNetDevReplaceNetConfig(const char *linkdev, int vf,
5c27b6
                               const virMacAddr *macaddress,
5c27b6
                               virNetDevVlanPtr vlan,
5c27b6
-- 
5c27b6
2.12.2
5c27b6