[libvirt] [PATCH v4 1/1] util: Add netlink event handling to virnetlink.c

D. Herrendoerfer d.herrendoerfer at herrendoerfer.name
Thu Feb 9 15:36:37 UTC 2012


From: "D. Herrendoerfer" <d.herrendoerfer at herrendoerfer.name>

This code adds a netlink event interface to libvirt.
It is based upon the event_poll code and makes use of
it. An event is generated for each netlink message sent
to the libvirt pid.

Signed-off-by: D. Herrendoerfer <d.herrendoerfer at herrendoerfer.name>
---
 daemon/libvirtd.c        |    8 +
 src/libvirt_private.syms |    6 +
 src/util/virnetlink.c    |  436 +++++++++++++++++++++++++++++++++++++++++++++-
 src/util/virnetlink.h    |   29 +++
 4 files changed, 478 insertions(+), 1 deletions(-)

diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c
index b1b542b..ca8074d 100644
--- a/daemon/libvirtd.c
+++ b/daemon/libvirtd.c
@@ -47,6 +47,7 @@
 #include "conf.h"
 #include "memory.h"
 #include "conf.h"
+#include "virnetlink.h"
 #include "virnetserver.h"
 #include "threads.h"
 #include "remote.h"
@@ -1598,6 +1599,12 @@ int main(int argc, char **argv) {
         goto cleanup;
     }
 
+    /* Register the netlink event service */
+    if (virNetlinkEventServiceStart() < 0) {
+        ret = VIR_DAEMON_ERR_NETWORK;
+        goto cleanup;
+    }
+
     /* Run event loop. */
     virNetServerRun(srv);
 
@@ -1607,6 +1614,7 @@ int main(int argc, char **argv) {
                 0, "shutdown", NULL);
 
 cleanup:
+    virNetlinkEventServiceStop();
     virNetServerProgramFree(remoteProgram);
     virNetServerProgramFree(qemuProgram);
     virNetServerClose(srv);
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index d6ad36c..008470e 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1258,6 +1258,12 @@ virNetDevVPortProfileOpTypeToString;
 
 #virnetlink.h
 virNetlinkCommand;
+virNetlinkEventServiceIsRunning;
+virNetlinkEventServiceStop;
+virNetlinkEventServiceStart;
+virNetlinkEventAddClient;
+virNetlinkEventRemoveClient;
+
 
 
 # virnetmessage.h
diff --git a/src/util/virnetlink.c b/src/util/virnetlink.c
index d03d171..7e70251 100644
--- a/src/util/virnetlink.c
+++ b/src/util/virnetlink.c
@@ -35,7 +35,10 @@
 #include <sys/types.h>
 
 #include "virnetlink.h"
+#include "logging.h"
 #include "memory.h"
+#include "threads.h"
+#include "virmacaddr.h"
 #include "virterror_internal.h"
 
 #define VIR_FROM_THIS VIR_FROM_NET
@@ -46,6 +49,54 @@
 
 #define NETLINK_ACK_TIMEOUT_S  2
 
+#if defined(__linux__) && defined(HAVE_LIBNL)
+/* State for a single netlink event handle */
+struct virNetlinkEventHandle {
+    int watch;
+    virNetlinkEventHandleCallback cb;
+    void *opaque;
+    unsigned char macaddr[VIR_MAC_BUFLEN];
+    int deleted;
+};
+
+/* State for the main netlink event loop */
+struct virNetlinkEventLoop {
+    virMutex lock;
+    int handled;
+    size_t handlesCount;
+    size_t handlesAlloc;
+    struct virNetlinkEventHandle *handles;
+};
+
+typedef struct _virNetlinkEventSrvPrivate virNetlinkEventSrvPrivate;
+typedef virNetlinkEventSrvPrivate *virNetlinkEventSrvPrivatePtr;
+struct _virNetlinkEventSrvPrivate {
+    virMutex lock;
+    int eventwatch;
+    int netlinkfd;
+    struct nl_handle *netlinknh;
+};
+
+enum virNetlinkDeleteMode {
+    VIR_NETLINK_HANDLE_VALID,
+    VIR_NETLINK_HANDLE_DELETE,
+    VIR_NETLINK_HANDLE_FREE,
+};
+
+/* Only have one event loop */
+static struct virNetlinkEventLoop eventLoop;
+
+/* Unique ID for the next netlink watch to be registered */
+static int nextWatch = 1;
+
+/* Allocate extra slots for virEventPollHandle/virEventPollTimeout
+ records in this multiple */
+#define NETLINK_EVENT_ALLOC_EXTENT 10
+
+static virNetlinkEventSrvPrivatePtr server = 0;
+
+/* Function definitions */
+
 /**
  * virNetlinkCommand:
  * @nlmsg: pointer to netlink message
@@ -58,7 +109,6 @@
  * Returns 0 on success, -1 on error. In case of error, no response
  * buffer will be returned.
  */
-#if defined(__linux__) && defined(HAVE_LIBNL)
 int virNetlinkCommand(struct nl_msg *nl_msg,
                       unsigned char **respbuf, unsigned int *respbuflen,
                       int nl_pid)
@@ -138,6 +188,325 @@ err_exit:
     return rc;
 }
 
+static void
+virNetlinkEventServerLock(virNetlinkEventSrvPrivatePtr driver) {
+    virMutexLock(&driver->lock);
+}
+
+static void
+virNetlinkEventServerUnlock(virNetlinkEventSrvPrivatePtr driver) {
+    virMutexUnlock(&driver->lock);
+}
+
+static void
+virNetlinkEventCallback(int watch,
+                        int fd ATTRIBUTE_UNUSED,
+                        int events ATTRIBUTE_UNUSED,
+                        void *opaque) {
+    virNetlinkEventSrvPrivatePtr srv = opaque;
+    unsigned char *msg;
+    struct sockaddr_nl peer;
+    struct ucred *creds = NULL;
+    int i, length, handled;
+
+    length = nl_recv(srv->netlinknh, &peer, &msg, &creds);
+
+    virNetlinkEventServerLock(srv);
+
+    handled=0;
+
+    virMutexLock(&eventLoop.lock);
+
+    VIR_DEBUG("dispatching to max %d clients, called from event watch %d",
+            (int)eventLoop.handlesCount, watch);
+
+    for (i = 0; i < eventLoop.handlesCount; i++) {
+        if (eventLoop.handles[i].deleted != VIR_NETLINK_HANDLE_VALID) {
+            continue;
+        }
+
+        VIR_DEBUG("dispatching client %d.",i);
+
+        virNetlinkEventHandleCallback cb = eventLoop.handles[i].cb;
+        void *cpopaque = eventLoop.handles[i].opaque;
+        (cb)( msg, length, &peer, &handled, cpopaque);
+    }
+
+    virMutexUnlock(&eventLoop.lock);
+
+    if (handled == 0) {
+        VIR_DEBUG("nobody cared.");
+    }
+
+    VIR_FREE(msg);
+
+    for (i = 0; i < eventLoop.handlesCount; i++) {
+        if (eventLoop.handles[i].deleted == VIR_NETLINK_HANDLE_DELETE) {
+            VIR_FREE(eventLoop.handles[i].opaque);
+            eventLoop.handles[i].deleted = VIR_NETLINK_HANDLE_FREE;
+        }
+    }
+    virNetlinkEventServerUnlock(srv);
+}
+
+static int
+setupNetlinkEventServer(virNetlinkEventSrvPrivatePtr srv) {
+    int fd;
+    int ret = -1;
+
+    /* Init the mutexes */
+    if ( virMutexInit(&srv->lock) < 0)
+        return -1;
+
+    if ( virMutexInit(&eventLoop.lock) < 0) {
+        virMutexDestroy(&srv->lock);
+        return -1;
+    }
+
+    virNetlinkEventServerLock(srv);
+
+    /* Allocate a new socket and get fd */
+    srv->netlinknh = nl_handle_alloc();
+
+    if (!srv->netlinknh) {
+        netlinkError(errno,
+                "%s", _("cannot allocate nlhandle for virNetlinkEvent server"));
+        goto exit;
+    }
+
+    if (nl_connect(srv->netlinknh, NETLINK_ROUTE) < 0) {
+        netlinkError(errno,
+                "%s", _("cannot connect to netlink socket"));
+        goto exit_cleanup;
+    }
+
+    fd = nl_socket_get_fd(srv->netlinknh);
+    nl_socket_set_nonblocking(srv->netlinknh);
+
+    if ((srv->eventwatch = virEventAddHandle(fd,
+                            VIR_EVENT_HANDLE_READABLE,
+                            virNetlinkEventCallback,
+                            srv, NULL)) < 0) {
+        netlinkError(VIR_ERR_INTERNAL_ERROR, "%s",
+                _("Failed to add netlink event handle watch"));
+
+        goto exit_cleanup;
+    }
+
+    srv->netlinkfd = fd;
+    VIR_DEBUG("netlink event listener on fd: %i",fd);
+
+    virNetlinkEventServerUnlock(srv);
+    ret = 0;
+    goto exit;
+
+exit_cleanup:
+    nl_close(srv->netlinknh);
+    nl_handle_destroy(srv->netlinknh);
+exit:
+    virNetlinkEventServerUnlock(srv);
+    return ret;
+}
+
+/**
+ * virNetlinkEventServiceStop:
+ *
+ * stop the monitor to receive netlink messages for libvirtd.
+ * This removes the netlink socket fd from the event handler.
+ *
+ * returns -1 if the monitor cannot be unregistered, 0 upon success
+ */
+int
+virNetlinkEventServiceStop(void) {
+    virNetlinkEventSrvPrivatePtr srv = server;
+
+    VIR_INFO("stopping netlink event service");
+
+    if (!server) {
+        return 0;
+    }
+
+    virNetlinkEventServerLock(srv);
+
+    nl_close(srv->netlinknh);
+    nl_handle_destroy(srv->netlinknh);
+
+    virEventRemoveHandle(srv->eventwatch);
+    server=0;
+
+    virNetlinkEventServerUnlock(srv);
+
+    virMutexDestroy(&srv->lock);
+    virMutexDestroy(&eventLoop.lock);
+
+    return 0;
+}
+
+/**
+ * virNetlinkEventServiceIsRunning:
+ *
+ * returns if the netlink event service is running.
+ *
+ * returns 1 if the service is running, 0 if stopped.
+ */
+int
+virNetlinkEventServiceIsRunning(void) {
+    if (server)
+        return 1;
+    return 0;
+}
+
+/**
+ * virNetlinkEventServiceStart:
+ *
+ * start a monitor to receive netlink messages for libvirtd.
+ * This registers a netlink socket with the event interface.
+ *
+ * returns -1 if the monitor cannot be registered, 0 upon success
+ */
+int
+virNetlinkEventServiceStart(void) {
+    virNetlinkEventSrvPrivatePtr srv;
+
+    if (server) {
+        return 0;
+    }
+
+    VIR_INFO("starting netlink event service");
+
+    if (VIR_ALLOC(srv) < 0)
+    goto no_memory;
+
+    if (setupNetlinkEventServer(srv)) {
+        VIR_FREE(srv);
+        goto error;
+    }
+
+    VIR_DEBUG("netlink event service running");
+
+    server=srv;
+    return 0;
+
+no_memory:
+    virReportOOMError();
+error:
+    return -1;
+}
+
+/**
+ * virNetlinkEventAddClient:
+ *
+ * @cb: callback to invoke when an event occurs
+ * @opaque: user data to pass to callback
+ * @macaddr: macaddr to store with the data. Used to identify callers. May be null.
+ *
+ * register a callback for handling of netlink messages. The
+ * registered function receives the entire netlink message and
+ * may choose to act upon it.
+ *
+ * returns -1 if the file handle cannot be registered, number of monitor upon success
+ */
+int
+virNetlinkEventAddClient(virNetlinkEventHandleCallback cb,
+                         void *opaque,
+                         const unsigned char *macaddr) {
+                         int i,r, result;
+
+    if ( cb == NULL )
+        return -1;
+
+    virMutexLock(&eventLoop.lock);
+
+    VIR_DEBUG("adding client: %d.",nextWatch);
+
+    r = 0;
+    /* first try to re-use deleted free slots */
+    for (i = 0; i < eventLoop.handlesCount; i++) {
+        if (eventLoop.handles[i].deleted == VIR_NETLINK_HANDLE_FREE) {
+            r = i;
+            goto addentry;
+        }
+    }
+    /* Resize the eventLoop array if needed */
+    if (eventLoop.handlesCount == eventLoop.handlesAlloc) {
+        VIR_DEBUG("Used %zu handle slots, adding at least %d more",
+                eventLoop.handlesAlloc, NETLINK_EVENT_ALLOC_EXTENT);
+        if (VIR_RESIZE_N(eventLoop.handles, eventLoop.handlesAlloc,
+                        eventLoop.handlesCount, NETLINK_EVENT_ALLOC_EXTENT) < 0) {
+            result = -1;
+            goto err_exit;
+        }
+    }
+    r = eventLoop.handlesCount++;
+
+    addentry:
+    eventLoop.handles[r].watch = nextWatch;
+    eventLoop.handles[r].cb = cb;
+    eventLoop.handles[r].opaque = opaque;
+    eventLoop.handles[r].deleted = VIR_NETLINK_HANDLE_VALID;
+    if (!macaddr)
+        memcpy(eventLoop.handles[r].macaddr, macaddr,VIR_MAC_BUFLEN);
+
+    VIR_DEBUG("added client to loop slot: %d.",r);
+
+    result = nextWatch++;
+
+err_exit:
+    virMutexUnlock(&eventLoop.lock);
+
+    return result;
+}
+
+/**
+ * virNetlinkEventRemoveClient:
+ *
+ * @watch: watch whose handle to remove
+ * @macaddr: macaddr whose handle to remove
+ *
+ * Unregister a callback from a netlink monitor.
+ * The handler function referenced will no longer receive netlink messages.
+ * Either watch or macaddr may be used, the other should be null.
+ *
+ * returns -1 if the file handle was not registered, 0 upon success
+ */
+int
+virNetlinkEventRemoveClient(int watch, const unsigned char *macaddr) {
+    int i;
+    int ret = -1;
+
+    if (watch <= 0 && macaddr == 0) {
+        VIR_WARN("Ignoring invalid netlink client id: %d", watch);
+        return -1;
+    }
+
+    virMutexLock(&eventLoop.lock);
+    for (i = 0; i < eventLoop.handlesCount; i++) {
+        if (eventLoop.handles[i].deleted != VIR_NETLINK_HANDLE_VALID)
+        continue;
+
+        if (watch != 0 && eventLoop.handles[i].watch == watch) {
+            eventLoop.handles[i].deleted = VIR_NETLINK_HANDLE_DELETE;
+            VIR_DEBUG("removed client: %d by index.",
+                    eventLoop.handles[i].watch);
+            ret = 0;
+            goto unlock_exit;
+        }
+        if (watch == 0 && memcmp(macaddr, eventLoop.handles[i].macaddr, VIR_MAC_BUFLEN)) {
+            eventLoop.handles[i].deleted = VIR_NETLINK_HANDLE_DELETE;
+            VIR_DEBUG("removed client: %d by mac.",
+                    eventLoop.handles[i].watch);
+            ret = 0;
+            goto unlock_exit;
+        }
+    }
+    VIR_DEBUG("client not found to remove.");
+
+unlock_exit:
+    virMutexUnlock(&eventLoop.lock);
+    return ret;
+}
+
+
 #else
 
 int virNetlinkCommand(struct nl_msg *nl_msg ATTRIBUTE_UNUSED,
@@ -154,4 +523,69 @@ int virNetlinkCommand(struct nl_msg *nl_msg ATTRIBUTE_UNUSED,
     return -1;
 }
 
+/**
+ * stopNetlinkEventServer: stop the monitor to receive netlink messages for libvirtd
+ */
+int virNetlinkEventServiceStop(void) {
+    netlinkError(VIR_ERR_INTERNAL_ERROR,
+                "%s",
+# if defined(__linux__) && !defined(HAVE_LIBNL)
+                _("virNetlinkEventServiceStop is not supported since libnl was not available"));
+# endif
+    return 0;
+}
+
+/**
+ * startNetlinkEventServer: start a monitor to receive netlink messages for libvirtd
+ */
+int virNetlinkEventServiceStart(void) {
+# if defined(__linux__) && !defined(HAVE_LIBNL)
+    netlinkError(VIR_ERR_INTERNAL_ERROR,
+                "%s",
+                _("virNetlinkEventServiceStart is not supported since libnl was not available"));
+# endif
+    return 0;
+}
+
+/**
+ * virNetlinkEventServiceIsRunning: returns if the netlink event service is running.
+ */
+int virNetlinkEventServiceIsRunning(void) {
+# if defined(__linux__) && !defined(HAVE_LIBNL)
+    netlinkError(VIR_ERR_INTERNAL_ERROR,
+                "%s",
+                _("virNetlinkEventServiceIsRunning is not supported since libnl was not available"));
+# endif
+    return 0;
+}
+
+/**
+ * virNetlinkEventAddClient: register a callback for handling of netlink messages
+ */
+int virNetlinkEventAddClient(virNetlinkEventHandleCallback cb, void *opaque,
+                             const unsigned char *macaddr) {
+    netlinkError(VIR_ERR_INTERNAL_ERROR,
+                "%s",
+# if defined(__linux__) && !defined(HAVE_LIBNL)
+                _("virNetlinkEventServiceAddClient is not supported since libnl was not available"));
+# else
+                _("virNetlinkEventServiceAddClient is not supported on non-linux platforms"));
+# endif
+    return -1;
+}
+
+/**
+ * virNetlinkEventRemoveClient: unregister a callback from a netlink monitor
+ */
+int virNetlinkEventRemoveClient(int watch, const unsigned char *macaddr) {
+    netlinkError(VIR_ERR_INTERNAL_ERROR,
+                "%s",
+# if defined(__linux__) && !defined(HAVE_LIBNL)
+                _("virNetlinkEventRemoveClient is not supported since libnl was not available"));
+# else
+                _("virNetlinkEventRemoveClient is not supported on non-linux platforms"));
+# endif
+    return -1;
+}
+
 #endif /* __linux__ */
diff --git a/src/util/virnetlink.h b/src/util/virnetlink.h
index a70abb6..e76c624 100644
--- a/src/util/virnetlink.h
+++ b/src/util/virnetlink.h
@@ -21,6 +21,7 @@
 # define __VIR_NETLINK_H__
 
 # include "config.h"
+# include "internal.h"
 
 # if defined(__linux__) && defined(HAVE_LIBNL)
 
@@ -29,6 +30,7 @@
 # else
 
 struct nl_msg;
+struct sockaddr_nl;
 
 # endif /* __linux__ */
 
@@ -36,4 +38,31 @@ int virNetlinkCommand(struct nl_msg *nl_msg,
                       unsigned char **respbuf, unsigned int *respbuflen,
                       int nl_pid);
 
+typedef void (*virNetlinkEventHandleCallback)( unsigned char *msg, int length, struct sockaddr_nl *peer, int *handled, void *opaque);
+
+/**
+ * stopNetlinkEventServer: stop the monitor to receive netlink messages for libvirtd
+ */
+int virNetlinkEventServiceStop(void);
+
+/**
+ * startNetlinkEventServer: start a monitor to receive netlink messages for libvirtd
+ */
+int virNetlinkEventServiceStart(void);
+
+/**
+ * virNetlinkEventServiceIsRunning: returns if the netlink event service is running.
+ */
+int virNetlinkEventServiceIsRunning(void);
+
+/**
+ * virNetlinkEventAddClient: register a callback for handling of netlink messages
+ */
+int virNetlinkEventAddClient(virNetlinkEventHandleCallback cb, void *opaque, const unsigned char *macaddr);
+
+/**
+ * virNetlinkEventRemoveClient: unregister a callback from a netlink monitor
+ */
+int virNetlinkEventRemoveClient(int watch, const unsigned char *macaddr);
+
 #endif /* __VIR_NETLINK_H__ */
-- 
1.7.7.5




More information about the libvir-list mailing list