[libvirt] [PATCHv5] interface: add udev based backend for virInterface

Add a read-only udev based backend for virInterface. Useful for distros that do not have netcf support yet. Multiple libvirt based utilities use a HAL based fallback when virInterface is not available which is less than ideal. This implements: * virConnectNumOfInterfaces() * virConnectListInterfaces() * virConnectNumOfDefinedInterfaces() * virConnectListDefinedInterfaces() * virConnectInterfaceLookupByName() * virConnectInterfaceLookupByMACString() --- Change from v4: * Fix up udev reference counting Change from v3: * fix make syntax-check issues * check strdup() and handle that case * simplify configure check Change from v2: * rebase from master Change from v1: * rebase from master configure.ac | 18 +- po/POTFILES.in | 1 + src/Makefile.am | 12 +- src/interface/interface_backend_udev.c | 438 ++++++++++++++++++++++++++++++++ tools/virsh.c | 2 + 5 files changed, 458 insertions(+), 13 deletions(-) create mode 100644 src/interface/interface_backend_udev.c diff --git a/configure.ac b/configure.ac index 13967e9..eee87ae 100644 --- a/configure.ac +++ b/configure.ac @@ -2799,18 +2799,12 @@ if test "$with_libvirtd" = "no" ; then with_interface=no fi -dnl The interface driver depends on the netcf library -if test "$with_interface:$with_netcf" = "check:yes" ; then - with_interface=yes -fi - -if test "$with_interface:$with_netcf" = "check:no" ; then - with_interface=no -fi - -if test "$with_interface:$with_netcf" = "yes:no" ; then - AC_MSG_ERROR([Requested the Interface driver without netcf support]) -fi +dnl The interface driver depends on the netcf library or udev library +case $with_interface:$with_netcf:$with_udev in + check:*yes*) with_interface=yes ;; + check:no:no) with_interface=no ;; + yes:no:no) AC_MSG_ERROR([Requested the Interface driver without netcf or udev support]) ;; +esac if test "$with_interface" = "yes" ; then AC_DEFINE_UNQUOTED([WITH_INTERFACE], [1], diff --git a/po/POTFILES.in b/po/POTFILES.in index 74d8dcc..2538225 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -41,6 +41,7 @@ src/hyperv/hyperv_driver.c src/hyperv/hyperv_util.c src/hyperv/hyperv_wmi.c src/interface/interface_backend_netcf.c +src/interface/interface_backend_udev.c src/internal.h src/libvirt.c src/libvirt-qemu.c diff --git a/src/Makefile.am b/src/Makefile.am index ae3d491..98448c1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -558,11 +558,16 @@ INTERFACE_DRIVER_SOURCES = if WITH_INTERFACE INTERFACE_DRIVER_SOURCES += \ interface/interface_driver.h -endif if WITH_NETCF INTERFACE_DRIVER_SOURCES += \ interface/interface_backend_netcf.c +else +if HAVE_UDEV +INTERFACE_DRIVER_SOURCES += \ + interface/interface_backend_udev.c +endif +endif endif SECRET_DRIVER_SOURCES = \ @@ -1045,6 +1050,11 @@ libvirt_driver_interface_la_LIBADD = if WITH_NETCF libvirt_driver_interface_la_CFLAGS += $(NETCF_CFLAGS) libvirt_driver_interface_la_LIBADD += $(NETCF_LIBS) +else +if HAVE_UDEV +libvirt_driver_interface_la_CFLAGS += $(UDEV_CFLAGS) +libvirt_driver_interface_la_LIBADD += $(UDEV_LIBS) +endif endif if WITH_DRIVER_MODULES libvirt_driver_interface_la_LIBADD += ../gnulib/lib/libgnu.la diff --git a/src/interface/interface_backend_udev.c b/src/interface/interface_backend_udev.c new file mode 100644 index 0000000..b6e9f55 --- /dev/null +++ b/src/interface/interface_backend_udev.c @@ -0,0 +1,438 @@ +/* + * interface_backend_udev.c: udev backend for virInterface + * + * Copyright (C) 2012 Doug Goldstein <cardoe@cardoe.com> + * + * 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 <libudev.h> + +#include "virterror_internal.h" +#include "datatypes.h" +#include "interface_driver.h" +#include "interface_conf.h" +#include "memory.h" + +#define VIR_FROM_THIS VIR_FROM_INTERFACE + +struct udev_iface_driver { + struct udev *udev; +}; + +enum udev_status { UDEV_IFACE_ACTIVE, UDEV_IFACE_INACTIVE, UDEV_IFACE_ALL }; + +static struct udev_enumerate * +udevIfaceGetDevices(struct udev *udev, enum udev_status status) +{ + struct udev_enumerate *enumerate; + + /* Check input params for sanity */ + if (!udev) + return NULL; + + /* Create a new enumeration to create a list */ + enumerate = udev_enumerate_new(udev); + + if (!enumerate) + return NULL; + + /* Enumerate all network subsystem devices */ + udev_enumerate_add_match_subsystem(enumerate, "net"); + + /* Ignore devices that are part of a bridge */ + udev_enumerate_add_nomatch_sysattr(enumerate, "brport/state", NULL); + + /* State of the device */ + switch (status) { + case UDEV_IFACE_ACTIVE: + udev_enumerate_add_match_sysattr(enumerate, "operstate", "up"); + break; + + case UDEV_IFACE_INACTIVE: + udev_enumerate_add_match_sysattr(enumerate, "operstate", "down"); + break; + + case UDEV_IFACE_ALL: + break; + } + + /* We don't want to see the TUN devices that QEMU creates for other gets + * running on this machine. By saying nomatch NULL, we just are getting + * devices without the tun_flags sysattr. + */ + udev_enumerate_add_nomatch_sysattr(enumerate, "tun_flags", NULL); + + return enumerate; +} + +static virDrvOpenStatus +udevIfaceOpenInterface(virConnectPtr conn, + virConnectAuthPtr auth ATTRIBUTE_UNUSED, + unsigned int flags) +{ + struct udev_iface_driver *driverState = NULL; + + virCheckFlags(0, VIR_DRV_OPEN_ERROR); + + if (VIR_ALLOC(driverState) < 0) { + virReportOOMError(); + goto err; + } + + driverState->udev = udev_new(); + if (!driverState->udev) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to create udev context")); + goto err; + } + + conn->interfacePrivateData = driverState; + + return VIR_DRV_OPEN_SUCCESS; + +err: + VIR_FREE(driverState); + + return VIR_DRV_OPEN_ERROR; +} + +static int +udevIfaceCloseInterface(virConnectPtr conn) +{ + struct udev_iface_driver *driverState; + + if (conn->interfacePrivateData != NULL) { + driverState = conn->interfacePrivateData; + + udev_unref(driverState->udev); + + VIR_FREE(driverState); + } + + conn->interfacePrivateData = NULL; + return 0; +} + +static int +udevIfaceNumOfInterfaces(virConnectPtr conn) +{ + struct udev_iface_driver *driverState = conn->interfacePrivateData; + struct udev *udev = udev_ref(driverState->udev); + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *devices; + struct udev_list_entry *dev_entry; + int count = 0; + + enumerate = udevIfaceGetDevices(udev, UDEV_IFACE_ACTIVE); + + if (!enumerate) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to get number of active interfaces on host")); + count = -1; + goto err; + } + + /* Do the scan to load up the enumeration */ + udev_enumerate_scan_devices(enumerate); + + /* Get a list we can walk */ + devices = udev_enumerate_get_list_entry(enumerate); + + /* For each item so we can count */ + udev_list_entry_foreach(dev_entry, devices) { + count++; + } + +err: + if (enumerate) + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return count; +} + +static int +udevIfaceListInterfaces(virConnectPtr conn, char **const names, int names_len) +{ + struct udev_iface_driver *driverState = conn->interfacePrivateData; + struct udev *udev = udev_ref(driverState->udev); + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *devices; + struct udev_list_entry *dev_entry; + int count = 0; + + enumerate = udevIfaceGetDevices(udev, UDEV_IFACE_ACTIVE); + + if (!enumerate) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to get list of active interfaces on host")); + goto err; + } + + /* Do the scan to load up the enumeration */ + udev_enumerate_scan_devices(enumerate); + + /* Get a list we can walk */ + devices = udev_enumerate_get_list_entry(enumerate); + + /* For each item so we can count */ + udev_list_entry_foreach(dev_entry, devices) { + struct udev_device *dev; + const char *path; + + /* Ensure we won't exceed the size of our array */ + if (count > names_len) + break; + + path = udev_list_entry_get_name(dev_entry); + dev = udev_device_new_from_syspath(udev, path); + names[count] = strdup(udev_device_get_sysname(dev)); + udev_device_unref(dev); + + /* If strdup() failed, we are out of memory */ + if (!names[count]) { + virReportOOMError(); + goto err; + } + + count++; + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return count; + +err: + if (enumerate) + udev_enumerate_unref(enumerate); + udev_unref(udev); + + for (names_len = 0; names_len < count; names_len++) + VIR_FREE(names[names_len]); + + return -1; +} + +static int +udevIfaceNumOfDefinedInterfaces(virConnectPtr conn) +{ + struct udev_iface_driver *driverState = conn->interfacePrivateData; + struct udev *udev = udev_ref(driverState->udev); + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *devices; + struct udev_list_entry *dev_entry; + int count = 0; + + enumerate = udevIfaceGetDevices(udev, UDEV_IFACE_INACTIVE); + + if (!enumerate) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to get number of defined interfaces on host")); + count = -1; + goto err; + } + + /* Do the scan to load up the enumeration */ + udev_enumerate_scan_devices(enumerate); + + /* Get a list we can walk */ + devices = udev_enumerate_get_list_entry(enumerate); + + /* For each item so we can count */ + udev_list_entry_foreach(dev_entry, devices) { + count++; + } + +err: + if (enumerate) + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return count; +} + +static int +udevIfaceListDefinedInterfaces(virConnectPtr conn, + char **const names, + int names_len) +{ + struct udev_iface_driver *driverState = conn->interfacePrivateData; + struct udev *udev = udev_ref(driverState->udev); + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *devices; + struct udev_list_entry *dev_entry; + int count = 0; + + enumerate = udevIfaceGetDevices(udev, UDEV_IFACE_INACTIVE); + + if (!enumerate) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to get list of defined interfaces on host")); + goto err; + } + + /* Do the scan to load up the enumeration */ + udev_enumerate_scan_devices(enumerate); + + /* Get a list we can walk */ + devices = udev_enumerate_get_list_entry(enumerate); + + /* For each item so we can count */ + udev_list_entry_foreach(dev_entry, devices) { + struct udev_device *dev; + const char *path; + + /* Ensure we won't exceed the size of our array */ + if (count > names_len) + break; + + path = udev_list_entry_get_name(dev_entry); + dev = udev_device_new_from_syspath(udev, path); + names[count] = strdup(udev_device_get_sysname(dev)); + udev_device_unref(dev); + + /* If strdup() failed, we are out of memory */ + if (!names[count]) { + virReportOOMError(); + goto err; + } + + count++; + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return count; + +err: + if (enumerate) + udev_enumerate_unref(enumerate); + udev_unref(udev); + + for (names_len = 0; names_len < count; names_len++) + VIR_FREE(names[names_len]); + + return -1; +} + +static virInterfacePtr +udevIfaceLookupByName(virConnectPtr conn, const char *name) +{ + struct udev_iface_driver *driverState = conn->interfacePrivateData; + struct udev *udev = udev_ref(driverState->udev); + struct udev_device *dev; + const char *macaddr; + virInterfacePtr ret = NULL; + + /* get a device reference based on the device name */ + dev = udev_device_new_from_subsystem_sysname(udev, "net", name); + if (!dev) { + virReportError(VIR_ERR_NO_INTERFACE, + _("couldn't find interface named '%s'"), + name); + goto err; + } + + macaddr = udev_device_get_sysattr_value(dev, "address"); + ret = virGetInterface(conn, name, macaddr); + udev_device_unref(dev); + +err: + udev_unref(udev); + + return ret; +} + +static virInterfacePtr +udevIfaceLookupByMACString(virConnectPtr conn, const char *macstr) +{ + struct udev_iface_driver *driverState = conn->interfacePrivateData; + struct udev *udev = udev_ref(driverState->udev); + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *dev_entry; + struct udev_device *dev; + const char *name; + virInterfacePtr ret = NULL; + + enumerate = udevIfaceGetDevices(udev, UDEV_IFACE_ALL); + + if (!enumerate) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("failed to lookup interface with MAC address '%s'"), + macstr); + goto err; + } + + /* Match on MAC */ + udev_enumerate_add_match_sysattr(enumerate, "address", macstr); + + /* Do the scan to load up the enumeration */ + udev_enumerate_scan_devices(enumerate); + + /* Get a list we can walk */ + dev_entry = udev_enumerate_get_list_entry(enumerate); + + /* Check that we got something back */ + if (!dev_entry) { + virReportError(VIR_ERR_NO_INTERFACE, + _("couldn't find interface with MAC address '%s'"), + macstr); + goto err; + } + + /* Check that we didn't get multiple items back */ + if (udev_list_entry_get_next(dev_entry)) { + virReportError(VIR_ERR_MULTIPLE_INTERFACES, + _("the MAC address '%s' matches multiple interfaces"), + macstr); + goto err; + } + + dev = udev_device_new_from_syspath(udev, udev_list_entry_get_name(dev_entry)); + name = udev_device_get_sysname(dev); + ret = virGetInterface(conn, name, macstr); + udev_device_unref(dev); + +err: + if (enumerate) + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return ret; +} + +static virInterfaceDriver udevIfaceDriver = { + "Interface", + .open = udevIfaceOpenInterface, /* 0.10.3 */ + .close = udevIfaceCloseInterface, /* 0.10.3 */ + .numOfInterfaces = udevIfaceNumOfInterfaces, /* 0.10.3 */ + .listInterfaces = udevIfaceListInterfaces, /* 0.10.3 */ + .numOfDefinedInterfaces = udevIfaceNumOfDefinedInterfaces, /* 0.10.3 */ + .listDefinedInterfaces = udevIfaceListDefinedInterfaces, /* 0.10.3 */ + .interfaceLookupByName = udevIfaceLookupByName, /* 0.10.3 */ + .interfaceLookupByMACString = udevIfaceLookupByMACString, /* 0.10.3 */ +}; + +int +interfaceRegister(void) { + if (virRegisterInterfaceDriver(&udevIfaceDriver) < 0) + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to register udev interface driver")); + return 0; +} diff --git a/tools/virsh.c b/tools/virsh.c index b32ee8b..f0ec625 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -2708,6 +2708,8 @@ vshShowVersion(vshControl *ctl ATTRIBUTE_UNUSED) vshPrint(ctl, " Interface"); # if defined(WITH_NETCF) vshPrint(ctl, " netcf"); +# elif defined(HAVE_UDEV) + vshPrint(ctl, " udev"); # endif #endif #ifdef WITH_NWFILTER -- 1.7.8.6

On 10/05/2012 12:43 PM, Doug Goldstein wrote:
Add a read-only udev based backend for virInterface. Useful for distros that do not have netcf support yet. Multiple libvirt based utilities use a HAL based fallback when virInterface is not available which is less than ideal. This implements: * virConnectNumOfInterfaces() * virConnectListInterfaces() * virConnectNumOfDefinedInterfaces() * virConnectListDefinedInterfaces() * virConnectInterfaceLookupByName() * virConnectInterfaceLookupByMACString() ---
We really should also have virConnectListAllInterfaces() supported as part of this patch.
+++ b/src/Makefile.am @@ -558,11 +558,16 @@ INTERFACE_DRIVER_SOURCES = if WITH_INTERFACE INTERFACE_DRIVER_SOURCES += \ interface/interface_driver.h -endif
if WITH_NETCF INTERFACE_DRIVER_SOURCES += \ interface/interface_backend_netcf.c +else +if HAVE_UDEV +INTERFACE_DRIVER_SOURCES += \ + interface/interface_backend_udev.c
Hmm, this means the file isn't compiled on machines that have both netcf and udev development support, which means it is prone to bit-rot. I'd feel a little bit more comfortable if we had a file interface_driver.c that implemented the real driver.h callbacks, and then further forwarded on to internal callbacks, so that both backend files can be compiled at once (even if the code in the udev backend is not used). [Or put another way, I don't want to have to configure --without-netcf just to test your new code, as that doubles maintenance burden.] However, as written, this part of your patch is okay, and we could save breaking things into a two-layer callback structure to ensure full compilation for a later patch, although I'd still like that patch to be present before pushing this one (so we don't forget the issue).
+++ b/src/interface/interface_backend_udev.c
+enum udev_status { UDEV_IFACE_ACTIVE, UDEV_IFACE_INACTIVE, UDEV_IFACE_ALL };
Are these enum names going to possibly conflict with udev namespace? You might want to name them VIR_UDEV_*, to ensure that we won't be setting ourselves up for conflicts. Also, I think the tag is an unusual name, I'd rather see: typedef enum { VIR_UDEV_..., } virUdevStatus;
+ +static struct udev_enumerate * +udevIfaceGetDevices(struct udev *udev, enum udev_status status)
and then here, you could just use (struct udev *udev, virUdevStatus status)
+{ + struct udev_enumerate *enumerate; + + /* Check input params for sanity */ + if (!udev) + return NULL; + + /* Create a new enumeration to create a list */ + enumerate = udev_enumerate_new(udev); + + if (!enumerate) + return NULL;
You are returning NULL for two different reasons; do you need to use virReportError() for better results back to the user?
+ + /* Enumerate all network subsystem devices */ + udev_enumerate_add_match_subsystem(enumerate, "net"); + + /* Ignore devices that are part of a bridge */ + udev_enumerate_add_nomatch_sysattr(enumerate, "brport/state", NULL); + + /* State of the device */ + switch (status) { + case UDEV_IFACE_ACTIVE: + udev_enumerate_add_match_sysattr(enumerate, "operstate", "up"); + break; + + case UDEV_IFACE_INACTIVE: + udev_enumerate_add_match_sysattr(enumerate, "operstate", "down"); + break; + + case UDEV_IFACE_ALL: + break; + } + + /* We don't want to see the TUN devices that QEMU creates for other gets
s/gets/guests/
+static int +udevIfaceNumOfDefinedInterfaces(virConnectPtr conn) +{ + struct udev_iface_driver *driverState = conn->interfacePrivateData; + struct udev *udev = udev_ref(driverState->udev); + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *devices; + struct udev_list_entry *dev_entry; + int count = 0; + + enumerate = udevIfaceGetDevices(udev, UDEV_IFACE_INACTIVE); + + if (!enumerate) {
NumOfInterfaces and NumOfDefinedInterfaces look very similar; to avoid code duplication, I'd consider a static helper function that takes one additional argument (UDEV_IFACE_ACTIVE or UDEV_IFACE_INACTIVE), since that's all the difference between them. Same thing for listing names. Overall looks relatively straightforward. The missing support for ListAllInterfaces is worth a v6, though. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Fri, Oct 5, 2012 at 6:28 PM, Eric Blake <eblake@redhat.com> wrote:
On 10/05/2012 12:43 PM, Doug Goldstein wrote:
Add a read-only udev based backend for virInterface. Useful for distros that do not have netcf support yet. Multiple libvirt based utilities use a HAL based fallback when virInterface is not available which is less than ideal. This implements: * virConnectNumOfInterfaces() * virConnectListInterfaces() * virConnectNumOfDefinedInterfaces() * virConnectListDefinedInterfaces() * virConnectInterfaceLookupByName() * virConnectInterfaceLookupByMACString() ---
We really should also have virConnectListAllInterfaces() supported as part of this patch.
This is actually 1/2 and there's a 2/2 that implements ListAllInterfaces() floating on the ML. I originally implemented the original patch before ListAllInterfaces hit the ML and submitted that as a follow on. I'll squash that patch into this patch and repost. -- Doug Goldstein
participants (2)
-
Doug Goldstein
-
Eric Blake