/* * * Connection Manager * * Copyright (C) 2011-2012 Intel Corporation. All rights reserved. * Copyright (C) 2013-2014 BMW Car IT 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 #endif #include #include #include #include #include #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; }