From: "D. Herrendoerfer" <d.herrendoerfer(a)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(a)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