diff options
Diffstat (limited to 'src/shared/netlink.c')
-rw-r--r-- | src/shared/netlink.c | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/src/shared/netlink.c b/src/shared/netlink.c new file mode 100644 index 00000000..d72294cc --- /dev/null +++ b/src/shared/netlink.c @@ -0,0 +1,666 @@ +/* + * + * Connection Manager + * + * Copyright (C) 2011-2012 Intel Corporation. All rights reserved. + * Copyright (C) 2013 BWM CarIT GmbH. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* + * This file is a copy from ELL which has been ported to use GLib's + * data structures. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <linux/netlink.h> + +#include <gdbus.h> + +#include "src/shared/util.h" +#include "src/shared/netlink.h" + +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +struct command { + unsigned int id; + uint32_t seq; + uint32_t len; + netlink_command_func_t handler; + netlink_destroy_func_t destroy; + void *user_data; +}; + +struct notify { + uint32_t group; + netlink_notify_func_t handler; + netlink_destroy_func_t destroy; + void *user_data; +}; + +struct netlink_info { + uint32_t pid; + GIOChannel *channel; + uint32_t next_seq; + GQueue *command_queue; + GHashTable *command_pending; + GHashTable *command_lookup; + unsigned int next_command_id; + GHashTable *notify_groups; + GHashTable *notify_lookup; + unsigned int next_notify_id; + netlink_debug_func_t debug_handler; + netlink_destroy_func_t debug_destroy; + void *debug_data; +}; + + +static void destroy_command(struct command *command) +{ + if (command->destroy) + command->destroy(command->user_data); + + g_free(command); +} + +static void destroy_notify(struct notify *notify) +{ + if (notify->destroy) + notify->destroy(notify->user_data); + + g_free(notify); +} + +static gboolean can_write_data(GIOChannel *chan, + GIOCondition cond, gpointer user_data) +{ + struct netlink_info *netlink = user_data; + struct command *command; + struct sockaddr_nl addr; + const void *data; + ssize_t written; + int sk; + + command = g_queue_pop_head(netlink->command_queue); + if (!command) + return FALSE; + + sk = g_io_channel_unix_get_fd(chan); + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_pid = 0; + + data = ((void *) command) + NLMSG_ALIGN(sizeof(struct command)); + + written = sendto(sk, data, command->len, 0, + (struct sockaddr *) &addr, sizeof(addr)); + if (written < 0 || (uint32_t) written != command->len) { + g_hash_table_remove(netlink->command_lookup, + GUINT_TO_POINTER(command->id)); + destroy_command(command); + return FALSE; + } + + util_hexdump('<', data, command->len, + netlink->debug_handler, netlink->debug_data); + + g_hash_table_replace(netlink->command_pending, + GUINT_TO_POINTER(command->seq), command); + + return g_queue_get_length(netlink->command_queue) > 0; +} + +static void do_notify(gpointer key, gpointer value, gpointer user_data) +{ + struct nlmsghdr *nlmsg = user_data; + struct notify *notify = value; + + if (notify->handler) { + notify->handler(nlmsg->nlmsg_type, NLMSG_DATA(nlmsg), + nlmsg->nlmsg_len - NLMSG_HDRLEN, notify->user_data); + } +} + +static void process_broadcast(struct netlink_info *netlink, uint32_t group, + struct nlmsghdr *nlmsg) +{ + GHashTable *notify_list; + + notify_list = g_hash_table_lookup(netlink->notify_groups, + GUINT_TO_POINTER(group)); + if (!notify_list) + return; + + g_hash_table_foreach(notify_list, do_notify, nlmsg); +} + +static void process_message(struct netlink_info *netlink, + struct nlmsghdr *nlmsg) +{ + const void *data = nlmsg; + struct command *command; + + command = g_hash_table_lookup(netlink->command_pending, + GUINT_TO_POINTER(nlmsg->nlmsg_seq)); + if (!command) + return; + + g_hash_table_remove(netlink->command_pending, + GUINT_TO_POINTER(nlmsg->nlmsg_seq)); + + if (!command->handler) + goto done; + + if (nlmsg->nlmsg_type < NLMSG_MIN_TYPE) { + const struct nlmsgerr *err; + + switch (nlmsg->nlmsg_type) { + case NLMSG_ERROR: + err = data + NLMSG_HDRLEN; + + command->handler(-err->error, 0, NULL, 0, + command->user_data); + break; + } + } else { + command->handler(0, nlmsg->nlmsg_type, data + NLMSG_HDRLEN, + nlmsg->nlmsg_len - NLMSG_HDRLEN, + command->user_data); + } + +done: + g_hash_table_remove(netlink->command_lookup, + GUINT_TO_POINTER(command->id)); + + destroy_command(command); +} + +static void process_multi(struct netlink_info *netlink, struct nlmsghdr *nlmsg) +{ + const void *data = nlmsg; + struct command *command; + + command = g_hash_table_lookup(netlink->command_pending, + GUINT_TO_POINTER(nlmsg->nlmsg_seq)); + if (!command) + return; + + if (!command->handler) + goto done; + + if (nlmsg->nlmsg_type < NLMSG_MIN_TYPE) { + const struct nlmsgerr *err; + + switch (nlmsg->nlmsg_type) { + case NLMSG_DONE: + case NLMSG_ERROR: + err = data + NLMSG_HDRLEN; + + command->handler(-err->error, 0, NULL, 0, + command->user_data); + break; + } + } else { + command->handler(0, nlmsg->nlmsg_type, data + NLMSG_HDRLEN, + nlmsg->nlmsg_len - NLMSG_HDRLEN, + command->user_data); + return; + } + +done: + g_hash_table_remove(netlink->command_pending, + GUINT_TO_POINTER(nlmsg->nlmsg_seq)); + + g_hash_table_remove(netlink->command_lookup, + GUINT_TO_POINTER(command->id)); + + destroy_command(command); +} + +static gboolean can_read_data(GIOChannel *chan, + GIOCondition cond, gpointer data) +{ + struct netlink_info *netlink = data; + struct cmsghdr *cmsg; + struct msghdr msg; + struct iovec iov; + struct nlmsghdr *nlmsg; + unsigned char buffer[4096]; + unsigned char control[32]; + uint32_t group = 0; + ssize_t len; + int sk; + + sk = g_io_channel_unix_get_fd(chan); + + iov.iov_base = buffer; + iov.iov_len = sizeof(buffer); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + len = recvmsg(sk, &msg, 0); + if (len < 0) + return FALSE; + + util_hexdump('>', buffer, len, netlink->debug_handler, + netlink->debug_data); + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + struct nl_pktinfo *pktinfo; + + if (cmsg->cmsg_level != SOL_NETLINK) + continue; + + if (cmsg->cmsg_type != NETLINK_PKTINFO) + continue; + + pktinfo = (void *) CMSG_DATA(cmsg); + + group = pktinfo->group; + } + + for (nlmsg = iov.iov_base; NLMSG_OK(nlmsg, (uint32_t) len); + nlmsg = NLMSG_NEXT(nlmsg, len)) { + if (group > 0 && nlmsg->nlmsg_seq == 0) { + process_broadcast(netlink, group, nlmsg); + continue; + } + + if (nlmsg->nlmsg_pid != netlink->pid) + continue; + + if (nlmsg->nlmsg_flags & NLM_F_MULTI) + process_multi(netlink, nlmsg); + else + process_message(netlink, nlmsg); + } + + return TRUE; +} + +static gboolean netlink_event(GIOChannel *chan, + GIOCondition cond, gpointer data) +{ + if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) + return FALSE; + + return TRUE; +} + +static int create_netlink_socket(int protocol, uint32_t *pid) +{ + struct sockaddr_nl addr; + socklen_t addrlen = sizeof(addr); + int sk, pktinfo = 1; + + sk = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + protocol); + if (sk < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + if (getsockname(sk, (struct sockaddr *) &addr, &addrlen) < 0) { + close(sk); + return -1; + } + + if (setsockopt(sk, SOL_NETLINK, NETLINK_PKTINFO, + &pktinfo, sizeof(pktinfo)) < 0) { + close(sk); + return -1; + } + + if (pid) + *pid = addr.nl_pid; + + return sk; +} + +struct netlink_info *netlink_new(int protocol) +{ + struct netlink_info *netlink; + int sk; + + netlink = g_try_new0(struct netlink_info, 1); + if (!netlink) + return NULL; + + netlink->next_seq = 1; + netlink->next_command_id = 1; + netlink->next_notify_id = 1; + + sk = create_netlink_socket(protocol, &netlink->pid); + if (sk < 0) { + g_free(netlink); + return NULL; + } + + netlink->channel = g_io_channel_unix_new(sk); + g_io_channel_set_close_on_unref(netlink->channel, TRUE); + + g_io_channel_set_encoding(netlink->channel, NULL, NULL); + g_io_channel_set_buffered(netlink->channel, FALSE); + + g_io_add_watch(netlink->channel, G_IO_IN, can_read_data, netlink); + g_io_add_watch(netlink->channel, G_IO_NVAL | G_IO_HUP | G_IO_ERR, + netlink_event, netlink); + + netlink->command_queue = g_queue_new(); + netlink->command_pending = g_hash_table_new(g_direct_hash, + g_direct_equal); + netlink->command_lookup = g_hash_table_new(g_direct_hash, + g_direct_equal); + + netlink->notify_groups = g_hash_table_new(g_direct_hash, + g_direct_equal); + netlink->notify_lookup = g_hash_table_new(g_direct_hash, + g_direct_equal); + + return netlink; +} + +static gboolean cleanup_notify(gpointer key, gpointer value, gpointer user_data) +{ + struct notify *notify = value; + + destroy_notify(notify); + + return TRUE; + +} + +static gboolean cleanup_notify_group(gpointer key, gpointer value, + gpointer user_data) +{ + GHashTable *notify_list = value; + + g_hash_table_foreach_remove(notify_list, cleanup_notify, user_data); + g_hash_table_destroy(notify_list); + + return TRUE; +} + +static gboolean cleanup_command(gpointer key, gpointer value, + gpointer user_data) +{ + struct command *command = value; + + destroy_command(command); + + return TRUE; +} + +void netlink_destroy(struct netlink_info *netlink) +{ + g_hash_table_destroy(netlink->notify_lookup); + + g_hash_table_foreach_remove(netlink->notify_groups, + cleanup_notify_group, NULL); + g_hash_table_destroy(netlink->notify_groups); + + g_queue_free(netlink->command_queue); + + g_hash_table_destroy(netlink->command_pending); + + g_hash_table_foreach_remove(netlink->command_lookup, + cleanup_command, NULL); + g_hash_table_destroy(netlink->command_lookup); + + g_io_channel_shutdown(netlink->channel, TRUE, NULL); + g_io_channel_unref(netlink->channel); + + g_free(netlink); +} + +unsigned int netlink_send(struct netlink_info *netlink, + uint16_t type, uint16_t flags, const void *data, + uint32_t len, netlink_command_func_t function, + void *user_data, netlink_destroy_func_t destroy) +{ + struct command *command; + struct nlmsghdr *nlmsg; + size_t size; + + if (!netlink) + return 0; + + if (!netlink->command_queue || + !netlink->command_pending || + !netlink->command_lookup) + return 0; + + size = NLMSG_ALIGN(sizeof(struct command)) + + NLMSG_HDRLEN + NLMSG_ALIGN(len); + + command = g_try_malloc0(size); + if (!command) + return 0; + + command->handler = function; + command->destroy = destroy; + command->user_data = user_data; + + command->id = netlink->next_command_id; + + g_hash_table_replace(netlink->command_lookup, + GUINT_TO_POINTER(command->id), command); + + command->seq = netlink->next_seq++; + command->len = NLMSG_HDRLEN + NLMSG_ALIGN(len); + + nlmsg = ((void *) command) + NLMSG_ALIGN(sizeof(struct command)); + + nlmsg->nlmsg_len = command->len; + nlmsg->nlmsg_type = type; + nlmsg->nlmsg_flags = NLM_F_REQUEST | flags; + nlmsg->nlmsg_seq = command->seq; + nlmsg->nlmsg_pid = netlink->pid; + + if (data && len > 0) + memcpy(((void *) nlmsg) + NLMSG_HDRLEN, data, len); + + g_queue_push_tail(netlink->command_queue, command); + + netlink->next_command_id++; + + /* Arm IOChannel to call can_write_data in case it is not armed yet. */ + if (g_queue_get_length(netlink->command_queue) == 1) + g_io_add_watch(netlink->channel, G_IO_OUT, can_write_data, + netlink); + + return command->id; +} + +bool netlink_cancel(struct netlink_info *netlink, unsigned int id) +{ + struct command *command; + + if (!netlink || id == 0) + return false; + + if (!netlink->command_queue || + !netlink->command_pending || + !netlink->command_lookup) + return false; + + command = g_hash_table_lookup(netlink->command_lookup, + GUINT_TO_POINTER(id)); + if (!command) + return false; + + g_hash_table_remove(netlink->command_lookup, GUINT_TO_POINTER(id)); + + if (!g_queue_remove(netlink->command_queue, command)) { + g_hash_table_remove(netlink->command_pending, + GUINT_TO_POINTER(command->seq)); + } + + destroy_command(command); + + return true; +} + +static bool add_membership(struct netlink_info *netlink, uint32_t group) +{ + int sk, value = group; + + sk = g_io_channel_unix_get_fd(netlink->channel); + + if (setsockopt(sk, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, + &value, sizeof(value)) < 0) + return false; + + return true; +} + +static bool drop_membership(struct netlink_info *netlink, uint32_t group) +{ + int sk, value = group; + + sk = g_io_channel_unix_get_fd(netlink->channel); + + if (setsockopt(sk, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, + &value, sizeof(value)) < 0) + return false; + + return true; +} + +unsigned int netlink_register(struct netlink_info *netlink, + uint32_t group, netlink_notify_func_t function, + void *user_data, netlink_destroy_func_t destroy) +{ + GHashTable *notify_list; + struct notify *notify; + unsigned int id; + + if (!netlink) + return 0; + + if (!netlink->notify_groups || !netlink->notify_lookup) + return 0; + + notify_list = g_hash_table_lookup(netlink->notify_groups, + GUINT_TO_POINTER(group)); + if (!notify_list) { + notify_list = g_hash_table_new(g_direct_hash, g_direct_equal); + if (!notify_list) + return 0; + + g_hash_table_replace(netlink->notify_groups, + GUINT_TO_POINTER(group), notify_list); + } + + notify = g_new(struct notify, 1); + + notify->group = group; + notify->handler = function; + notify->destroy = destroy; + notify->user_data = user_data; + + id = netlink->next_notify_id; + + g_hash_table_replace(netlink->notify_lookup, + GUINT_TO_POINTER(id), notify_list); + g_hash_table_replace(notify_list, GUINT_TO_POINTER(id), notify); + + if (g_hash_table_size(notify_list) == 1) { + if (add_membership(netlink, notify->group) == false) + goto remove_notify; + } + + netlink->next_notify_id++; + + return id; + +remove_notify: + g_hash_table_remove(notify_list, GUINT_TO_POINTER(id)); + g_hash_table_remove(netlink->notify_lookup, GUINT_TO_POINTER(id)); + g_free(notify); + + return 0; +} + +bool netlink_unregister(struct netlink_info *netlink, unsigned int id) +{ + GHashTable *notify_list; + struct notify *notify; + + if (!netlink || id == 0) + return false; + + if (!netlink->notify_groups || !netlink->notify_lookup) + return false; + + notify_list = g_hash_table_lookup(netlink->notify_lookup, + GUINT_TO_POINTER(id)); + + if (!notify_list) + return false; + + g_hash_table_remove(netlink->notify_lookup, GUINT_TO_POINTER(id)); + + notify = g_hash_table_lookup(notify_list, GUINT_TO_POINTER(id)); + if (!notify) + return false; + + g_hash_table_remove(notify_list, GUINT_TO_POINTER(id)); + + if (g_hash_table_size(notify_list) == 0) + drop_membership(netlink, notify->group); + + destroy_notify(notify); + + return true; +} + +bool netlink_set_debug(struct netlink_info *netlink, + netlink_debug_func_t function, + void *user_data, netlink_destroy_func_t destroy) +{ + if (!netlink) + return false; + + if (netlink->debug_destroy) + netlink->debug_destroy(netlink->debug_data); + + netlink->debug_handler = function; + netlink->debug_destroy = destroy; + netlink->debug_data = user_data; + + return true; +} |