/* * Copyright (C) 2010-2016 Red Hat, Inc. * Copyright (C) 2010-2012, 2016 IBM Corporation * * 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 * . * * Authors: * Stefan Berger * Dirk Herrendoerfer * * Notes: * netlink: http://lovezutto.googlepages.com/netlink.pdf * iproute2 package * * 2012/02: Renamed from netlink.[ch] to virnetlink.[ch] * */ #include #include #include #include #include "virnetlink.h" #include "virnetdev.h" #include "virlog.h" #include "virthread.h" #include "virmacaddr.h" #include "virerror.h" #define VIR_FROM_THIS VIR_FROM_NET VIR_LOG_INIT("util.netlink"); #define NETLINK_ACK_TIMEOUT_S (2*1000) #if defined(__linux__) && defined(HAVE_LIBNL) /* State for a single netlink event handle */ struct virNetlinkEventHandle { int watch; virNetlinkEventHandleCallback handleCB; virNetlinkEventRemoveCallback removeCB; void *opaque; virMacAddr macaddr; int deleted; }; # ifdef HAVE_LIBNL1 # define virNetlinkAlloc nl_handle_alloc # define virNetlinkSetBufferSize nl_set_buffer_size # define virNetlinkFree nl_handle_destroy typedef struct nl_handle virNetlinkHandle; # else # define virNetlinkAlloc nl_socket_alloc # define virNetlinkSetBufferSize nl_socket_set_buffer_size # define virNetlinkFree nl_socket_free typedef struct nl_sock virNetlinkHandle; # endif VIR_DEFINE_AUTOPTR_FUNC(virNetlinkHandle, virNetlinkFree) typedef struct _virNetlinkEventSrvPrivate virNetlinkEventSrvPrivate; typedef virNetlinkEventSrvPrivate *virNetlinkEventSrvPrivatePtr; struct _virNetlinkEventSrvPrivate { /*Server*/ virMutex lock; int eventwatch; int netlinkfd; virNetlinkHandle *netlinknh; /*Events*/ int handled; size_t handlesCount; size_t handlesAlloc; struct virNetlinkEventHandle *handles; }; enum virNetlinkDeleteMode { VIR_NETLINK_HANDLE_VALID, VIR_NETLINK_HANDLE_DELETED, }; /* 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 /* Linux kernel supports up to MAX_LINKS (32 at the time) individual * netlink protocols. */ static virNetlinkEventSrvPrivatePtr server[MAX_LINKS] = {NULL}; static virNetlinkHandle *placeholder_nlhandle; /* Function definitions */ /** * virNetlinkStartup: * * Perform any initialization that needs to take place before the * program starts up worker threads. This is currently used to assure * that an nl_handle is allocated prior to any attempts to bind a * netlink socket. For a discussion of why this is necessary, please * see the following email message: * * https://www.redhat.com/archives/libvir-list/2012-May/msg00202.html * * The short version is that, without this placeholder allocation of * an nl_handle that is never used, it is possible for nl_connect() in * one thread to collide with a direct bind() of a netlink socket in * another thread, leading to failure of the operation (which could * lead to failure of libvirtd to start). Since getaddrinfo() (used by * libvirtd in virSocketAddrParse, which is called quite frequently * during startup) directly calls bind() on a netlink socket, this is * actually a very common occurrence (15-20% failure rate on some * hardware). * * Returns 0 on success, -1 on failure. */ int virNetlinkStartup(void) { if (placeholder_nlhandle) return 0; VIR_DEBUG("Running global netlink initialization"); placeholder_nlhandle = virNetlinkAlloc(); if (!placeholder_nlhandle) { virReportSystemError(errno, "%s", _("cannot allocate placeholder nlhandle for netlink")); return -1; } return 0; } /** * virNetlinkShutdown: * * Undo any initialization done by virNetlinkStartup. This currently * destroys the placeholder nl_handle. */ void virNetlinkShutdown(void) { if (placeholder_nlhandle) { virNetlinkFree(placeholder_nlhandle); placeholder_nlhandle = NULL; } } /** * virNetLinkCreateSocket: * * @protocol: which protocol to connect to (e.g. NETLINK_ROUTE, * * Create a netlink socket, set its buffer size, and turn on message * peeking (so the buffer size can be dynamically increased if * needed). * * Returns a handle to the new netlink socket, or 0 if there was a failure. * */ static virNetlinkHandle * virNetlinkCreateSocket(int protocol) { virNetlinkHandle *nlhandle = NULL; if (!(nlhandle = virNetlinkAlloc())) { virReportSystemError(errno, "%s", _("cannot allocate nlhandle for netlink")); goto error; } if (nl_connect(nlhandle, protocol) < 0) { virReportSystemError(errno, _("cannot connect to netlink socket " "with protocol %d"), protocol); goto error; } if (virNetlinkSetBufferSize(nlhandle, 131702, 0) < 0) { virReportSystemError(errno, "%s", _("cannot set netlink socket buffer " "size to 128k")); goto error; } nl_socket_enable_msg_peek(nlhandle); cleanup: return nlhandle; error: if (nlhandle) { nl_close(nlhandle); virNetlinkFree(nlhandle); nlhandle = NULL; } goto cleanup; } static virNetlinkHandle * virNetlinkSendRequest(struct nl_msg *nl_msg, uint32_t src_pid, struct sockaddr_nl nladdr, unsigned int protocol, unsigned int groups) { ssize_t nbytes; int fd; int n; virNetlinkHandle *nlhandle = NULL; struct pollfd fds[1]; struct nlmsghdr *nlmsg = nlmsg_hdr(nl_msg); if (protocol >= MAX_LINKS) { virReportSystemError(EINVAL, _("invalid protocol argument: %d"), protocol); goto error; } if (!(nlhandle = virNetlinkCreateSocket(protocol))) goto error; fd = nl_socket_get_fd(nlhandle); if (fd < 0) { virReportSystemError(errno, "%s", _("cannot get netlink socket fd")); goto error; } if (groups && nl_socket_add_membership(nlhandle, groups) < 0) { virReportSystemError(errno, "%s", _("cannot add netlink membership")); goto error; } nlmsg_set_dst(nl_msg, &nladdr); nlmsg->nlmsg_pid = src_pid ? src_pid : getpid(); nbytes = nl_send_auto_complete(nlhandle, nl_msg); if (nbytes < 0) { virReportSystemError(errno, "%s", _("cannot send to netlink socket")); goto error; } memset(fds, 0, sizeof(fds)); fds[0].fd = fd; fds[0].events = POLLIN; n = poll(fds, ARRAY_CARDINALITY(fds), NETLINK_ACK_TIMEOUT_S); if (n <= 0) { if (n < 0) virReportSystemError(errno, "%s", _("error in poll call")); if (n == 0) virReportSystemError(ETIMEDOUT, "%s", _("no valid netlink response was received")); } return nlhandle; error: virNetlinkFree(nlhandle); return NULL; } /** * virNetlinkCommand: * @nl_msg: pointer to netlink message * @resp: pointer to pointer where response buffer will be allocated * @respbuflen: pointer to integer holding the size of the response buffer * on return of the function. * @src_pid: the pid of the process to send a message * @dst_pid: the pid of the process to talk to, i.e., pid = 0 for kernel * @protocol: netlink protocol * @groups: the group identifier * * Send the given message to the netlink layer and receive response. * Returns 0 on success, -1 on error. In case of error, no response * buffer will be returned. */ int virNetlinkCommand(struct nl_msg *nl_msg, struct nlmsghdr **resp, unsigned int *respbuflen, uint32_t src_pid, uint32_t dst_pid, unsigned int protocol, unsigned int groups) { struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK, .nl_pid = dst_pid, .nl_groups = 0, }; struct pollfd fds[1]; VIR_AUTOFREE(struct nlmsghdr *) temp_resp = NULL; VIR_AUTOPTR(virNetlinkHandle) nlhandle = NULL; int len = 0; memset(fds, 0, sizeof(fds)); if (!(nlhandle = virNetlinkSendRequest(nl_msg, src_pid, nladdr, protocol, groups))) return -1; len = nl_recv(nlhandle, &nladdr, (unsigned char **)&temp_resp, NULL); if (len == 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("nl_recv failed - returned 0 bytes")); return -1; } if (len < 0) { virReportSystemError(errno, "%s", _("nl_recv failed")); return -1; } VIR_STEAL_PTR(*resp, temp_resp); *respbuflen = len; return 0; } int virNetlinkDumpCommand(struct nl_msg *nl_msg, virNetlinkDumpCallback callback, uint32_t src_pid, uint32_t dst_pid, unsigned int protocol, unsigned int groups, void *opaque) { bool end = false; int len = 0; struct nlmsghdr *msg = NULL; struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK, .nl_pid = dst_pid, .nl_groups = 0, }; VIR_AUTOPTR(virNetlinkHandle) nlhandle = NULL; if (!(nlhandle = virNetlinkSendRequest(nl_msg, src_pid, nladdr, protocol, groups))) return -1; while (!end) { VIR_AUTOFREE(struct nlmsghdr *) resp = NULL; len = nl_recv(nlhandle, &nladdr, (unsigned char **)&resp, NULL); VIR_WARNINGS_NO_CAST_ALIGN for (msg = resp; NLMSG_OK(msg, len); msg = NLMSG_NEXT(msg, len)) { VIR_WARNINGS_RESET if (msg->nlmsg_type == NLMSG_DONE) end = true; if (virNetlinkGetErrorCode(msg, len) < 0) return -1; if (callback(msg, opaque) < 0) return -1; } } return 0; } /** * virNetlinkDumpLink: * * @ifname: The name of the interface; only use if ifindex <= 0 * @ifindex: The interface index; may be <= 0 if ifname is given * @nlData: Gets a pointer to the raw data from netlink. MUST BE FREED BY CALLER! * @tb: Pointer to a pointer of netlink attributes that will contain * the results * @src_pid: pid used for nl_pid of the local end of the netlink message * (0 == "use getpid()") * @dst_pid: pid of destination nl_pid if the kernel * is not the target of the netlink message but it is to be * sent to another process (0 if sending to the kernel) * * Get information from netlink about an interface given its name or index. * * Returns 0 on success, -1 on fatal error. */ int virNetlinkDumpLink(const char *ifname, int ifindex, void **nlData, struct nlattr **tb, uint32_t src_pid, uint32_t dst_pid) { int rc = -1; struct nlmsgerr *err; struct ifinfomsg ifinfo = { .ifi_family = AF_UNSPEC, .ifi_index = ifindex }; unsigned int recvbuflen; VIR_AUTOPTR(virNetlinkMsg) nl_msg = NULL; VIR_AUTOFREE(struct nlmsghdr *) resp = NULL; if (ifname && ifindex <= 0 && virNetDevGetIndex(ifname, &ifindex) < 0) return -1; ifinfo.ifi_index = ifindex; nl_msg = nlmsg_alloc_simple(RTM_GETLINK, NLM_F_REQUEST); if (!nl_msg) { virReportOOMError(); return -1; } if (nlmsg_append(nl_msg, &ifinfo, sizeof(ifinfo), NLMSG_ALIGNTO) < 0) goto buffer_too_small; if (ifname) { if (nla_put(nl_msg, IFLA_IFNAME, strlen(ifname)+1, ifname) < 0) goto buffer_too_small; } # ifdef RTEXT_FILTER_VF /* if this filter exists in the kernel's netlink implementation, * we need to set it, otherwise the response message will not * contain the IFLA_VFINFO_LIST that we're looking for. */ { uint32_t ifla_ext_mask = RTEXT_FILTER_VF; if (nla_put(nl_msg, IFLA_EXT_MASK, sizeof(ifla_ext_mask), &ifla_ext_mask) < 0) { goto buffer_too_small; } } # endif if (virNetlinkCommand(nl_msg, &resp, &recvbuflen, src_pid, dst_pid, NETLINK_ROUTE, 0) < 0) return -1; if (recvbuflen < NLMSG_LENGTH(0) || resp == NULL) goto malformed_resp; switch (resp->nlmsg_type) { case NLMSG_ERROR: err = (struct nlmsgerr *)NLMSG_DATA(resp); if (resp->nlmsg_len < NLMSG_LENGTH(sizeof(*err))) goto malformed_resp; if (err->error) { virReportSystemError(-err->error, _("error dumping %s (%d) interface"), ifname, ifindex); return -1; } break; case GENL_ID_CTRL: case NLMSG_DONE: rc = nlmsg_parse(resp, sizeof(struct ifinfomsg), tb, IFLA_MAX, NULL); if (rc < 0) goto malformed_resp; break; default: goto malformed_resp; } VIR_STEAL_PTR(*nlData, resp); return 0; malformed_resp: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed netlink response message")); return rc; buffer_too_small: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("allocated netlink buffer is too small")); return rc; } /** * virNetlinkNewLink: * * @ifname: name of the link * @type: the type of the device, i.e. "bridge", "macvtap", "macvlan" * @extra_args: the extra args for creating the netlink interface * @error: netlink error code * * A generic wrapper to create a network link. * * Returns 0 on success, -1 on error. Additionally, if the @error is * non-zero, then a failure occurred during virNetlinkCommand, but * no error message is generated leaving it up to the caller to handle * the condition. */ int virNetlinkNewLink(const char *ifname, const char *type, virNetlinkNewLinkDataPtr extra_args, int *error) { struct nlmsgerr *err; struct nlattr *linkinfo = NULL; struct nlattr *infodata = NULL; unsigned int buflen; struct ifinfomsg ifinfo = { .ifi_family = AF_UNSPEC }; VIR_AUTOPTR(virNetlinkMsg) nl_msg = NULL; VIR_AUTOFREE(struct nlmsghdr *) resp = NULL; *error = 0; VIR_DEBUG("Creating %s interface '%s'", type, ifname); if (!ifname || !type) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("both interface name and type must not be NULL")); return -1; } nl_msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL); if (!nl_msg) { virReportOOMError(); return -1; } if (nlmsg_append(nl_msg, &ifinfo, sizeof(ifinfo), NLMSG_ALIGNTO) < 0) goto buffer_too_small; NETLINK_MSG_PUT(nl_msg, IFLA_IFNAME, (strlen(ifname) + 1), ifname); NETLINK_MSG_NEST_START(nl_msg, linkinfo, IFLA_LINKINFO); NETLINK_MSG_PUT(nl_msg, IFLA_INFO_KIND, (strlen(type) + 1), type); if ((STREQ(type, "macvtap") || STREQ(type, "macvlan")) && extra_args && extra_args->macvlan_mode && *extra_args->macvlan_mode > 0) { NETLINK_MSG_NEST_START(nl_msg, infodata, IFLA_INFO_DATA); NETLINK_MSG_PUT(nl_msg, IFLA_MACVLAN_MODE, sizeof(uint32_t), extra_args->macvlan_mode); NETLINK_MSG_NEST_END(nl_msg, infodata); } NETLINK_MSG_NEST_END(nl_msg, linkinfo); if (extra_args) { NETLINK_MSG_PUT(nl_msg, IFLA_LINK, sizeof(uint32_t), extra_args->ifindex); NETLINK_MSG_PUT(nl_msg, IFLA_ADDRESS, VIR_MAC_BUFLEN, extra_args->mac); } if (virNetlinkCommand(nl_msg, &resp, &buflen, 0, 0, NETLINK_ROUTE, 0) < 0) return -1; if (buflen < NLMSG_LENGTH(0) || resp == NULL) goto malformed_resp; switch (resp->nlmsg_type) { case NLMSG_ERROR: err = (struct nlmsgerr *)NLMSG_DATA(resp); if (resp->nlmsg_len < NLMSG_LENGTH(sizeof(*err))) goto malformed_resp; if (err->error < 0) { *error = err->error; return -1; } break; case NLMSG_DONE: break; default: goto malformed_resp; } return 0; malformed_resp: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed netlink response message")); return -1; buffer_too_small: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("allocated netlink buffer is too small")); return -1; } /** * virNetlinkDelLink: * * @ifname: Name of the link * @fallback: pointer to an alternate function that will * be called to perform the delete if RTM_DELLINK fails * with EOPNOTSUPP (any other error will simply be treated * as an error). * * delete a network "link" (aka interface aka device) with the given * name. This works for many different types of network devices, * including macvtap and bridges. * * Returns 0 on success, -1 on fatal error. */ int virNetlinkDelLink(const char *ifname, virNetlinkDelLinkFallback fallback) { struct nlmsgerr *err; struct ifinfomsg ifinfo = { .ifi_family = AF_UNSPEC }; unsigned int recvbuflen; VIR_AUTOPTR(virNetlinkMsg) nl_msg = NULL; VIR_AUTOFREE(struct nlmsghdr *) resp = NULL; nl_msg = nlmsg_alloc_simple(RTM_DELLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL); if (!nl_msg) { virReportOOMError(); return -1; } if (nlmsg_append(nl_msg, &ifinfo, sizeof(ifinfo), NLMSG_ALIGNTO) < 0) goto buffer_too_small; if (nla_put(nl_msg, IFLA_IFNAME, strlen(ifname)+1, ifname) < 0) goto buffer_too_small; if (virNetlinkCommand(nl_msg, &resp, &recvbuflen, 0, 0, NETLINK_ROUTE, 0) < 0) { return -1; } if (recvbuflen < NLMSG_LENGTH(0) || resp == NULL) goto malformed_resp; switch (resp->nlmsg_type) { case NLMSG_ERROR: err = (struct nlmsgerr *)NLMSG_DATA(resp); if (resp->nlmsg_len < NLMSG_LENGTH(sizeof(*err))) goto malformed_resp; if (-err->error == EOPNOTSUPP && fallback) return fallback(ifname); if (err->error) { virReportSystemError(-err->error, _("error destroying network device %s"), ifname); return -1; } break; case NLMSG_DONE: break; default: goto malformed_resp; } return 0; malformed_resp: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed netlink response message")); return -1; buffer_too_small: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("allocated netlink buffer is too small")); return -1; } /** * virNetlinkGetNeighbor: * * @nlData: Gets a pointer to the raw data from netlink. MUST BE FREED BY CALLER! * @src_pid: pid used for nl_pid of the local end of the netlink message * (0 == "use getpid()") * @dst_pid: pid of destination nl_pid if the kernel * is not the target of the netlink message but it is to be * sent to another process (0 if sending to the kernel) * * Get neighbor table entry from netlink. * * Returns 0 on success, -1 on fatal error. */ int virNetlinkGetNeighbor(void **nlData, uint32_t src_pid, uint32_t dst_pid) { struct nlmsgerr *err; struct ndmsg ndinfo = { .ndm_family = AF_UNSPEC, }; unsigned int recvbuflen; VIR_AUTOPTR(virNetlinkMsg) nl_msg = NULL; VIR_AUTOFREE(struct nlmsghdr *) resp = NULL; nl_msg = nlmsg_alloc_simple(RTM_GETNEIGH, NLM_F_DUMP | NLM_F_REQUEST); if (!nl_msg) { virReportOOMError(); return -1; } if (nlmsg_append(nl_msg, &ndinfo, sizeof(ndinfo), NLMSG_ALIGNTO) < 0) goto buffer_too_small; if (virNetlinkCommand(nl_msg, &resp, &recvbuflen, src_pid, dst_pid, NETLINK_ROUTE, 0) < 0) return -1; if (recvbuflen < NLMSG_LENGTH(0) || resp == NULL) goto malformed_resp; switch (resp->nlmsg_type) { case NLMSG_ERROR: err = (struct nlmsgerr *)NLMSG_DATA(resp); if (resp->nlmsg_len < NLMSG_LENGTH(sizeof(*err))) goto malformed_resp; if (err->error) { virReportSystemError(-err->error, "%s", _("error dumping")); return -1; } break; case RTM_NEWNEIGH: break; default: goto malformed_resp; } VIR_STEAL_PTR(*nlData, resp); return recvbuflen; malformed_resp: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed netlink response message")); return -1; buffer_too_small: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("allocated netlink buffer is too small")); return -1; } int virNetlinkGetErrorCode(struct nlmsghdr *resp, unsigned int recvbuflen) { struct nlmsgerr *err; int result = 0; if (recvbuflen < NLMSG_LENGTH(0) || resp == NULL) goto malformed_resp; switch (resp->nlmsg_type) { case NLMSG_ERROR: err = (struct nlmsgerr *)NLMSG_DATA(resp); if (resp->nlmsg_len < NLMSG_LENGTH(sizeof(*err))) goto malformed_resp; switch (err->error) { case 0: /* ACK */ break; default: result = err->error; } break; case NLMSG_DONE: break; default: /* We allow multipart messages. */ if (!(resp->nlmsg_flags & NLM_F_MULTI)) goto malformed_resp; } return result; malformed_resp: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed netlink response message")); return -EINVAL; } static void virNetlinkEventServerLock(virNetlinkEventSrvPrivatePtr driver) { virMutexLock(&driver->lock); } static void virNetlinkEventServerUnlock(virNetlinkEventSrvPrivatePtr driver) { virMutexUnlock(&driver->lock); } /** * virNetlinkEventRemoveClientPrimitive: * * @i: index of the client to remove from the table * @protocol: netlink protocol * * This static function does the low level removal of a client from * the table once its index is known, including calling the remove * callback (which usually will free resources required by the * handler). The event server lock *must* be locked before calling * this function. * * assumes success, returns nothing. */ static int virNetlinkEventRemoveClientPrimitive(size_t i, unsigned int protocol) { if (protocol >= MAX_LINKS) return -EINVAL; virNetlinkEventRemoveCallback removeCB = server[protocol]->handles[i].removeCB; if (removeCB) { (removeCB)(server[protocol]->handles[i].watch, &server[protocol]->handles[i].macaddr, server[protocol]->handles[i].opaque); } server[protocol]->handles[i].deleted = VIR_NETLINK_HANDLE_DELETED; return 0; } static void virNetlinkEventCallback(int watch, int fd ATTRIBUTE_UNUSED, int events ATTRIBUTE_UNUSED, void *opaque) { virNetlinkEventSrvPrivatePtr srv = opaque; struct sockaddr_nl peer; struct ucred *creds = NULL; size_t i; int length; bool handled = false; VIR_AUTOFREE(struct nlmsghdr *) msg = NULL; length = nl_recv(srv->netlinknh, &peer, (unsigned char **)&msg, &creds); if (length == 0) return; if (length < 0) { virReportSystemError(errno, "%s", _("nl_recv returned with error")); return; } virNetlinkEventServerLock(srv); VIR_DEBUG("dispatching to max %d clients, called from event watch %d", (int)srv->handlesCount, watch); for (i = 0; i < srv->handlesCount; i++) { if (srv->handles[i].deleted != VIR_NETLINK_HANDLE_VALID) continue; VIR_DEBUG("dispatching client %zu.", i); (srv->handles[i].handleCB)(msg, length, &peer, &handled, srv->handles[i].opaque); } if (!handled) VIR_DEBUG("event not handled."); virNetlinkEventServerUnlock(srv); } /** * virNetlinkEventServiceStop: * * stop the monitor to receive netlink messages for libvirtd. * This removes the netlink socket fd from the event handler. * * @protocol: netlink protocol * * Returns -1 if the monitor cannot be unregistered, 0 upon success */ int virNetlinkEventServiceStop(unsigned int protocol) { if (protocol >= MAX_LINKS) return -EINVAL; virNetlinkEventSrvPrivatePtr srv = server[protocol]; size_t i; VIR_INFO("stopping netlink event service"); if (!server[protocol]) return 0; virNetlinkEventServerLock(srv); nl_close(srv->netlinknh); virNetlinkFree(srv->netlinknh); virEventRemoveHandle(srv->eventwatch); /* free any remaining clients on the list */ for (i = 0; i < srv->handlesCount; i++) { if (srv->handles[i].deleted == VIR_NETLINK_HANDLE_VALID) virNetlinkEventRemoveClientPrimitive(i, protocol); } server[protocol] = NULL; VIR_FREE(srv->handles); virNetlinkEventServerUnlock(srv); virMutexDestroy(&srv->lock); VIR_FREE(srv); return 0; } /** * virNetlinkEventServiceStopAll: * * Stop all the monitors to receive netlink messages for libvirtd. * * Returns -1 if any monitor cannot be unregistered, 0 upon success */ int virNetlinkEventServiceStopAll(void) { size_t i; VIR_INFO("stopping all netlink event services"); for (i = 0; i < MAX_LINKS; i++) virNetlinkEventServiceStop(i); return 0; } /** * virNetlinkEventServiceIsRunning: * * Returns if the netlink event service is running. * * @protocol: netlink protocol * * Returns 'true' if the service is running, 'false' if stopped. */ bool virNetlinkEventServiceIsRunning(unsigned int protocol) { if (protocol >= MAX_LINKS) { virReportSystemError(EINVAL, _("invalid protocol argument: %d"), protocol); return false; } return server[protocol] != NULL; } /** * virNetlinkEventServiceLocalPid: * * @protocol: netlink protocol * * Returns the nl_pid value that was used to bind() the netlink socket * used by the netlink event service, or -1 on error (netlink * guarantees that this value will always be > 0). */ int virNetlinkEventServiceLocalPid(unsigned int protocol) { if (protocol >= MAX_LINKS) return -EINVAL; if (!(server[protocol] && server[protocol]->netlinknh)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("netlink event service not running")); return -1; } return (int)nl_socket_get_local_port(server[protocol]->netlinknh); } /** * virNetlinkEventServiceStart: * * start a monitor to receive netlink messages for libvirtd. * This registers a netlink socket with the event interface. * * @protocol: netlink protocol * @groups: broadcast groups to join in * * Returns -1 if the monitor cannot be registered, 0 upon success */ int virNetlinkEventServiceStart(unsigned int protocol, unsigned int groups) { virNetlinkEventSrvPrivatePtr srv; int fd; int ret = -1; if (protocol >= MAX_LINKS) { virReportSystemError(EINVAL, _("invalid protocol argument: %d"), protocol); return -EINVAL; } if (server[protocol]) return 0; VIR_INFO("starting netlink event service with protocol %d", protocol); if (VIR_ALLOC(srv) < 0) return -1; if (virMutexInit(&srv->lock) < 0) { VIR_FREE(srv); return -1; } virNetlinkEventServerLock(srv); /* Allocate a new socket and get fd */ if (!(srv->netlinknh = virNetlinkCreateSocket(protocol))) goto error_locked; fd = nl_socket_get_fd(srv->netlinknh); if (fd < 0) { virReportSystemError(errno, "%s", _("cannot get netlink socket fd")); goto error_server; } if (groups && nl_socket_add_membership(srv->netlinknh, groups) < 0) { virReportSystemError(errno, "%s", _("cannot add netlink membership")); goto error_server; } if (nl_socket_set_nonblocking(srv->netlinknh)) { virReportSystemError(errno, "%s", _("cannot set netlink socket nonblocking")); goto error_server; } if ((srv->eventwatch = virEventAddHandle(fd, VIR_EVENT_HANDLE_READABLE, virNetlinkEventCallback, srv, NULL)) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to add netlink event handle watch")); goto error_server; } srv->netlinkfd = fd; VIR_DEBUG("netlink event listener on fd: %i running", fd); ret = 0; server[protocol] = srv; error_server: if (ret < 0) { nl_close(srv->netlinknh); virNetlinkFree(srv->netlinknh); } error_locked: virNetlinkEventServerUnlock(srv); if (ret < 0) { virMutexDestroy(&srv->lock); VIR_FREE(srv); } return ret; } /** * virNetlinkEventAddClient: * * @handleCB: callback to invoke when an event occurs * @removeCB: callback to invoke when removing a client * @opaque: user data to pass to callback * @macaddr: macaddr to store with the data. Used to identify callers. * May be null. * @protocol: netlink protocol * * 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 handleCB, virNetlinkEventRemoveCallback removeCB, void *opaque, const virMacAddr *macaddr, unsigned int protocol) { size_t i; int r, ret = -1; virNetlinkEventSrvPrivatePtr srv = NULL; if (protocol >= MAX_LINKS) return -EINVAL; srv = server[protocol]; if (handleCB == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid NULL callback provided")); return -1; } virNetlinkEventServerLock(srv); VIR_DEBUG("adding client: %d.", nextWatch); r = 0; /* first try to re-use deleted free slots */ for (i = 0; i < srv->handlesCount; i++) { if (srv->handles[i].deleted == VIR_NETLINK_HANDLE_DELETED) { r = i; goto addentry; } } /* Resize the eventLoop array if needed */ if (srv->handlesCount == srv->handlesAlloc) { VIR_DEBUG("Used %zu handle slots, adding at least %d more", srv->handlesAlloc, NETLINK_EVENT_ALLOC_EXTENT); if (VIR_RESIZE_N(srv->handles, srv->handlesAlloc, srv->handlesCount, NETLINK_EVENT_ALLOC_EXTENT) < 0) goto error; } r = srv->handlesCount++; addentry: srv->handles[r].watch = nextWatch; srv->handles[r].handleCB = handleCB; srv->handles[r].removeCB = removeCB; srv->handles[r].opaque = opaque; srv->handles[r].deleted = VIR_NETLINK_HANDLE_VALID; if (macaddr) virMacAddrSet(&srv->handles[r].macaddr, macaddr); else virMacAddrSetRaw(&srv->handles[r].macaddr, (unsigned char[VIR_MAC_BUFLEN]){0, 0, 0, 0, 0, 0}); VIR_DEBUG("added client to loop slot: %d. with macaddr ptr=%p", r, macaddr); ret = nextWatch++; error: virNetlinkEventServerUnlock(srv); return ret; } /** * virNetlinkEventRemoveClient: * * @watch: watch whose handle to remove * @macaddr: macaddr whose handle to remove * @protocol: netlink protocol * * 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 virMacAddr *macaddr, unsigned int protocol) { size_t i; int ret = -1; virNetlinkEventSrvPrivatePtr srv = NULL; if (protocol >= MAX_LINKS) return -EINVAL; srv = server[protocol]; VIR_DEBUG("removing client watch=%d, mac=%p.", watch, macaddr); if (watch <= 0 && !macaddr) { VIR_WARN("Ignoring invalid netlink client id: %d", watch); return -1; } virNetlinkEventServerLock(srv); for (i = 0; i < srv->handlesCount; i++) { if (srv->handles[i].deleted != VIR_NETLINK_HANDLE_VALID) continue; if ((watch && srv->handles[i].watch == watch) || (!watch && virMacAddrCmp(macaddr, &srv->handles[i].macaddr) == 0)) { VIR_DEBUG("removed client: %d by %s.", srv->handles[i].watch, watch ? "index" : "mac"); virNetlinkEventRemoveClientPrimitive(i, protocol); ret = 0; goto cleanup; } } VIR_DEBUG("no client found to remove."); cleanup: virNetlinkEventServerUnlock(srv); return ret; } #else # if defined(__linux) static const char *unsupported = N_("libnl was not available at build time"); # else static const char *unsupported = N_("not supported on non-linux platforms"); # endif int virNetlinkStartup(void) { return 0; } void virNetlinkShutdown(void) { return; } int virNetlinkCommand(struct nl_msg *nl_msg ATTRIBUTE_UNUSED, struct nlmsghdr **resp ATTRIBUTE_UNUSED, unsigned int *respbuflen ATTRIBUTE_UNUSED, uint32_t src_pid ATTRIBUTE_UNUSED, uint32_t dst_pid ATTRIBUTE_UNUSED, unsigned int protocol ATTRIBUTE_UNUSED, unsigned int groups ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -1; } int virNetlinkDumpCommand(struct nl_msg *nl_msg ATTRIBUTE_UNUSED, virNetlinkDumpCallback callback ATTRIBUTE_UNUSED, uint32_t src_pid ATTRIBUTE_UNUSED, uint32_t dst_pid ATTRIBUTE_UNUSED, unsigned int protocol ATTRIBUTE_UNUSED, unsigned int groups ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -1; } int virNetlinkDumpLink(const char *ifname ATTRIBUTE_UNUSED, int ifindex ATTRIBUTE_UNUSED, void **nlData ATTRIBUTE_UNUSED, struct nlattr **tb ATTRIBUTE_UNUSED, uint32_t src_pid ATTRIBUTE_UNUSED, uint32_t dst_pid ATTRIBUTE_UNUSED) { virReportSystemError(ENOSYS, "%s", _("Unable to dump link info on this platform")); return -1; } int virNetlinkDelLink(const char *ifname ATTRIBUTE_UNUSED, virNetlinkDelLinkFallback fallback ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -1; } int virNetlinkNewLink(const char *ifname ATTRIBUTE_UNUSED, const char *type ATTRIBUTE_UNUSED, virNetlinkNewLinkDataPtr extra_args ATTRIBUTE_UNUSED, int *error ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -1; } int virNetlinkGetNeighbor(void **nlData ATTRIBUTE_UNUSED, uint32_t src_pid ATTRIBUTE_UNUSED, uint32_t dst_pid ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -1; } /** * stopNetlinkEventServer: stop the monitor to receive netlink * messages for libvirtd */ int virNetlinkEventServiceStop(unsigned int protocol ATTRIBUTE_UNUSED) { VIR_DEBUG("%s", _(unsupported)); return 0; } /** * stopNetlinkEventServerAll: stop all the monitors to receive netlink * messages for libvirtd */ int virNetlinkEventServiceStopAll(void) { VIR_DEBUG("%s", _(unsupported)); return 0; } /** * startNetlinkEventServer: start a monitor to receive netlink * messages for libvirtd */ int virNetlinkEventServiceStart(unsigned int protocol ATTRIBUTE_UNUSED, unsigned int groups ATTRIBUTE_UNUSED) { VIR_DEBUG("%s", _(unsupported)); return 0; } /** * virNetlinkEventServiceIsRunning: returns if the netlink event * service is running. */ bool virNetlinkEventServiceIsRunning(unsigned int protocol ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return 0; } int virNetlinkEventServiceLocalPid(unsigned int protocol ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -1; } /** * virNetlinkEventAddClient: register a callback for handling of * netlink messages */ int virNetlinkEventAddClient(virNetlinkEventHandleCallback handleCB ATTRIBUTE_UNUSED, virNetlinkEventRemoveCallback removeCB ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED, const virMacAddr *macaddr ATTRIBUTE_UNUSED, unsigned int protocol ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -1; } /** * virNetlinkEventRemoveClient: unregister a callback from a netlink monitor */ int virNetlinkEventRemoveClient(int watch ATTRIBUTE_UNUSED, const virMacAddr *macaddr ATTRIBUTE_UNUSED, unsigned int protocol ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -1; } int virNetlinkGetErrorCode(struct nlmsghdr *resp ATTRIBUTE_UNUSED, unsigned int recvbuflen ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -EINVAL; } #endif /* __linux__ */