First - thanks for taking this on, and doing it the right way! (i.e.
refactoring my two changes down into a common set of functions to be
reused, rather than duplicating the code). I can't really say why I
didn't go the extra bit when I made the changes to virnetdevtap.c and
virnetdevmacvlan.c - I really should have cleaned it up as you've done
in patches 1 & 2 here.
On 12/4/20 2:01 AM, Shi Lei wrote:
Extract ReserveName/GenerateName from netdevtap and netdevmacvlan as
common helper functions. Along with them, export Lock/Unlock functions
to protect these processes.
Signed-off-by: Shi Lei <shi_lei(a)massclouds.com>
---
src/libvirt_private.syms | 4 ++
src/util/virnetdev.c | 140 +++++++++++++++++++++++++++++++++++++++
src/util/virnetdev.h | 33 +++++++++
3 files changed, 177 insertions(+)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 2f640ef1..be3148c9 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2551,6 +2551,7 @@ virNetDevDelMulti;
virNetDevExists;
virNetDevFeatureTypeFromString;
virNetDevFeatureTypeToString;
+virNetDevGenerateName;
virNetDevGetFeatures;
virNetDevGetIndex;
virNetDevGetLinkInfo;
@@ -2572,8 +2573,10 @@ virNetDevGetVLanID;
virNetDevIfStateTypeFromString;
virNetDevIfStateTypeToString;
virNetDevIsVirtualFunction;
+virNetDevLockGenName;
virNetDevPFGetVF;
virNetDevReadNetConfig;
+virNetDevReserveName;
virNetDevRunEthernetScript;
virNetDevRxFilterFree;
virNetDevRxFilterModeTypeFromString;
@@ -2594,6 +2597,7 @@ virNetDevSetRcvMulti;
virNetDevSetRootQDisc;
virNetDevSetupControl;
virNetDevSysfsFile;
+virNetDevUnlockGenName;
virNetDevValidateConfig;
virNetDevVFInterfaceStats;
diff --git a/src/util/virnetdev.c b/src/util/virnetdev.c
index 5104bbe7..5ff8e35f 100644
--- a/src/util/virnetdev.c
+++ b/src/util/virnetdev.c
@@ -17,6 +17,7 @@
*/
#include <config.h>
+#include <math.h>
#include "virnetdev.h"
#include "viralloc.h"
@@ -95,6 +96,22 @@ VIR_LOG_INIT("util.netdev");
(FEATURE_WORD(blocks, index, field) & FEATURE_FIELD_FLAG(index))
#endif
+VIR_ENUM_IMPL(virNetDevGenNameType,
+ VIR_NET_DEV_GEN_NAME_LAST,
+ "none",
+ "tap",
+ "macvtap",
+ "macvlan",
+);
You've added VIR_ENUM_IMPL and VIR_ENUM_DECL, but haven't actually used
the corresponding virNetDevGenNameTypeToString() and
virNetDevGenNameTypeFromString() functions anywhere. Unless it will be
used, I'd say it would be just as well to leave it out. (and if you do
define it, probably use "vnet" for the tap device entry, since it's not
really important that the device is a tap device, just that it's a
network device whose name starts with "vnet").
+static virNetDevGenName
+virNetDevGenNames[VIR_NET_DEV_GEN_NAME_LAST] = {
+ {0, NULL, VIR_MUTEX_INITIALIZER},
So you have this extra entry in the table at [0] because the first enum
value is VIR_NET_DEV_GEN_NAME_NONE. Since that value would never be
possible, I think you should just make the first value in the enum be
VIR_NET_DEV_GEN_NAME_VNET (instead of ..._TAP), and then you don't need
the unused entry in the table.
+ {-1, VIR_NET_GENERATED_TAP_PREFIX, VIR_MUTEX_INITIALIZER},
+ {-1, VIR_NET_GENERATED_MACVTAP_PREFIX, VIR_MUTEX_INITIALIZER},
+ {-1, VIR_NET_GENERATED_MACVLAN_PREFIX, VIR_MUTEX_INITIALIZER},
+};
+
typedef enum {
VIR_MCAST_TYPE_INDEX_TOKEN,
VIR_MCAST_TYPE_NAME_TOKEN,
@@ -3516,3 +3533,126 @@ virNetDevSetRootQDisc(const char *ifname,
return 0;
}
+
+
+/**
+ * virNetDevReserveName:
+ * @name: name of an existing network device
+ * @type: type of the network device
+ * @locked: whether this process is locked by the internal mutex
+ *
+ * Reserve a network device name, so that any new network device
+ * created with an autogenerated name will use a number higher
+ * than the number in the given device name.
+ *
+ * Returns nothing.
+ */
+void
+virNetDevReserveName(const char *name,
+ virNetDevGenNameType type,
+ bool locked)
+{
+ unsigned int id;
+ const char *idstr = NULL;
+
+ if (type && STRPREFIX(name, virNetDevGenNames[type].prefix)) {
If you're going to worry about range-checking type, make sure that it's
< ..._LAST (0 will be a valid value if you follow my recommendation
above to remove ..._NONE)
+
+ VIR_INFO("marking device in use: '%s'", name);
+
+ idstr = name + strlen(virNetDevGenNames[type].prefix);
+
+ if (virStrToLong_ui(idstr, NULL, 10, &id) >= 0) {
+ if (locked)
+ virMutexLock(&virNetDevGenNames[type].mutex);
+
+ if (virNetDevGenNames[type].lastID < (int)id)
+ virNetDevGenNames[type].lastID = id;
+
+ if (locked)
+ virMutexUnlock(&virNetDevGenNames[type].mutex);
+ }
+ }
+}
+
+
+/**
+ * virNetDevGenerateName:
+ * @ifname: pointer to pointer to string containing template
+ *
+ * generate a new (currently unused) name for a new network device based
+ * on the templace string in @ifname - replace %d with the reserved id,
+ * and keep trying new values until one is found
+ * that doesn't already exist, or we've tried 10000 different
+ * names. Once a usable name is found, replace the template with the
+ * actual name.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+virNetDevGenerateName(char **ifname, virNetDevGenNameType type)
+{
+ int id;
+ const char *prefix = virNetDevGenNames[type].prefix;
+ double maxIDd = pow(10, IFNAMSIZ - 1 - strlen(prefix));
+ int maxID = INT_MAX;
+ int attempts = 0;
+
+ if (maxIDd <= (double)INT_MAX)
+ maxID = (int)maxIDd;
+
+ do {
+ g_autofree char *try = NULL;
+
+ id = ++virNetDevGenNames[type].lastID;
+
+ /* reset before overflow */
+ if (virNetDevGenNames[type].lastID >= maxID)
+ virNetDevGenNames[type].lastID = -1;
+
+ if (*ifname)
+ try = g_strdup_printf(*ifname, id);
An interesting twist! I like the direction of this - instead of relying
on the kernel to generate the names in the case of someone defining
their device with, e.g. <target dev='myprefix%d'/>, you send it through
our generator. Sending a user-provided string to printf as the format
string makes me uneasy though. Also, what if ifname is an
unparameterized name (e.g. "mytap"), and there is already a device named
"mytap"? In that case, it will spin through the loop 10000 times trying
the same device name, although we know from the start that it's not
going to work.
How about if, instead of blindly doing this, we check to see if the
device name has a single "%" that is followed by "d"? If it's
anything
other than that (or NULL) then we just return with the string unchanged.
+ else
+ try = g_strdup_printf("%s%d", prefix, id);
+
+ if (!virNetDevExists(try)) {
+ g_free(*ifname);
+ *ifname = g_steal_pointer(&try);
+ return 0;
+ }
+ } while (++attempts < 10000);
+
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("no unused %s names available"),
+ prefix);
+ return -1;
+}
+
+
+/**
+ * virNetDevLockGenName:
+ * @type: type of the network device
+ *
+ * Lock the internal mutex for creation for this network device type.
+ *
+ * Returns nothing.
+ */
+void
+virNetDevLockGenName(virNetDevGenNameType type)
+{
+ virMutexLock(&virNetDevGenNames[type].mutex);
+}
+
+
+/**
+ * virNetDevUnlockGenName:
+ * @type: type of the network device
+ *
+ * Unlock the internal mutex for creation for this network device type.
+ *
+ * Returns nothing.
+ */
+void
+virNetDevUnlockGenName(virNetDevGenNameType type)
+{
+ virMutexUnlock(&virNetDevGenNames[type].mutex);
+}
diff --git a/src/util/virnetdev.h b/src/util/virnetdev.h
index 53e606c6..19f37b61 100644
--- a/src/util/virnetdev.h
+++ b/src/util/virnetdev.h
@@ -40,6 +40,12 @@ typedef void virIfreq;
*/
#define VIR_NET_GENERATED_TAP_PREFIX "vnet"
+/* libvirt will start macvtap/macvlan interface names with one of
+ * these prefixes when it auto-generates the name
+ */
+#define VIR_NET_GENERATED_MACVTAP_PREFIX "macvtap"
+#define VIR_NET_GENERATED_MACVLAN_PREFIX "macvlan"
+
typedef enum {
VIR_NETDEV_RX_FILTER_MODE_NONE = 0,
VIR_NETDEV_RX_FILTER_MODE_NORMAL,
@@ -145,6 +151,24 @@ struct _virNetDevCoalesce {
uint32_t rate_sample_interval;
};
+typedef enum {
+ VIR_NET_DEV_GEN_NAME_NONE = 0,
See comment above about removing ..._NONE
+ VIR_NET_DEV_GEN_NAME_TAP,
+ VIR_NET_DEV_GEN_NAME_MACVTAP,
+ VIR_NET_DEV_GEN_NAME_MACVLAN,
+ VIR_NET_DEV_GEN_NAME_LAST
+} virNetDevGenNameType;
+
+VIR_ENUM_DECL(virNetDevGenNameType);
Also about removing VIR_ENUM_DECL() (unless we end up with a reason for
virNetDevGenNameType(To|From)String())
+
+typedef struct _virNetDevGenName virNetDevGenName;
+typedef virNetDevGenName *virNetDevGenNamePtr;
+struct _virNetDevGenName {
+ int lastID; /* not "unsigned" because callers use %d */
+ const char *prefix;
+ virMutex mutex;
+};
+
int virNetDevSetupControl(const char *ifname,
virIfreq *ifr)
@@ -321,3 +345,12 @@ int virNetDevVFInterfaceStats(virPCIDeviceAddressPtr vfAddr,
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNetDevRxFilter, virNetDevRxFilterFree);
+
+void virNetDevReserveName(const char *name,
+ virNetDevGenNameType type,
+ bool locked);
+
+int virNetDevGenerateName(char **ifname, virNetDevGenNameType type);
+
+void virNetDevLockGenName(virNetDevGenNameType type);
+void virNetDevUnlockGenName(virNetDevGenNameType type);
Otherwise this looks good!