From: "D. Herrendoerfer" <d.herrendoerfer@xxxxxxxxxxxxxxxxxx> 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@xxxxxxxxxxxxxxxxxx> --- 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list