2 * Copyright (C) 2011 Jens Georg
4 * Author: Jens Georg <mail@jensge.org>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
23 * SECTION:gupnp-linux-context-manager
24 * @short_description: Linux-specific implementation of #GUPnPContextManager
26 * This is a Linux-specific context manager which uses <ulink
27 * url="http://www.linuxfoundation.org/collaborate/workgroups/networking/netlink">Netlink</ulink>
28 * to detect the changes in network interface configurations, such as
29 * added or removed interfaces, network addresses, ...
31 * The context manager works in two phase.
33 * Phase one is the "bootstrapping" phase where we query all currently
34 * configured interfaces and addresses.
36 * Phase two is the "listening" phase where we just listen to the netlink
37 * messages that are happening and create or destroy #GUPnPContext<!-- -->s
43 #include <sys/types.h>
44 #include <sys/socket.h>
45 #include <linux/netlink.h>
46 #include <linux/rtnetlink.h>
47 #ifdef HAVE_LINUX_WIRELESS_H
48 #include <linux/wireless.h>
50 #include <sys/ioctl.h>
57 #include "gupnp-linux-context-manager.h"
58 #include "gupnp-context.h"
60 G_DEFINE_TYPE (GUPnPLinuxContextManager,
61 gupnp_linux_context_manager,
62 GUPNP_TYPE_CONTEXT_MANAGER);
64 struct _GUPnPLinuxContextManagerPrivate {
65 /* Socket used for IOCTL calls */
68 /* Netlink sequence number; nl_seq > 1 means bootstrapping done */
71 /* Socket used to do netlink communication */
72 GSocket *netlink_socket;
74 /* Socket source used for normal netlink communication */
75 GSource *netlink_socket_source;
77 /* Idle source used during bootstrap */
78 GSource *bootstrap_source;
80 /* A hash table mapping system interface indices to a NetworkInterface
82 GHashTable *interfaces;
87 NETWORK_INTERFACE_UP = 1 << 0,
89 /* Interface doesn't support multicast or is P-t-P */
90 NETWORK_INTERFACE_IGNORE = 1 << 1,
92 /* Interface is down but has an address set */
93 NETWORK_INTERFACE_PRECONFIGURED = 1 << 2
94 } NetworkInterfaceFlags;
96 /* struct representing a network interface */
97 struct _NetworkInterface {
98 /* Weak pointer to context manager associated with this interface */
99 GUPnPLinuxContextManager *manager;
101 /* Name of the interface (eth0 etc.) */
104 /* ESSID for wireless interfaces */
107 /* States of the interface */
108 NetworkInterfaceFlags flags;
110 /* UPnP contexts associated with this interface. Can be more than one
111 * with alias addresses like eth0:1 etc. */
112 GHashTable *contexts;
115 typedef struct _NetworkInterface NetworkInterface;
117 /* Create a new network interface struct and query the device name */
118 static NetworkInterface *
119 network_device_new (GUPnPLinuxContextManager *manager,
122 NetworkInterface *device;
126 /* Query interface name */
127 memset (&ifr, 0, sizeof (struct ifreq));
128 ifr.ifr_ifindex = index;
129 ret = ioctl (manager->priv->fd, SIOCGIFNAME, &ifr);
132 g_warning ("Could not get interface name for index %d",
138 device = g_slice_new0 (NetworkInterface);
139 device->manager = manager;
140 device->name = g_strdup (ifr.ifr_name);
142 device->contexts = g_hash_table_new_full (g_str_hash,
150 /* Try to update the ESSID of a network interface. */
152 network_device_update_essid (NetworkInterface *device)
154 char *old_essid = device->essid;
155 #ifdef HAVE_LINUX_WIRELESS_H
156 char essid[IW_ESSID_MAX_SIZE + 1];
161 memset (&iwr, 0, sizeof (struct iwreq));
162 memset (essid, 0, IW_ESSID_MAX_SIZE + 1);
163 strncpy (iwr.ifr_name, device->name, IFNAMSIZ);
164 iwr.u.essid.pointer = (caddr_t) essid;
165 iwr.u.essid.length = IW_ESSID_MAX_SIZE;
166 ret = ioctl (device->manager->priv->fd, SIOCGIWESSID, &iwr);
168 if ((ret == 0 && essid[0] != '\0') &&
169 (!device->essid || strcmp (device->essid, essid)))
170 device->essid = g_strdup (essid);
178 network_device_create_context (NetworkInterface *device, const char *label)
181 GError *error = NULL;
182 GUPnPContext *context;
184 /* We cannot create a context yet. But it may be that there will not
185 * be a RTM_NEWADDR message for this device if the IP address does not
186 * change so we mark this device as preconfigured and will create the
187 * context if the device comes up. If the address changes, we'll get a
188 * RTM_DELADDR before the next RTM_NEWADDR. */
189 if (!device->flags & NETWORK_INTERFACE_UP) {
190 device->flags |= NETWORK_INTERFACE_PRECONFIGURED;
195 device->flags &= ~NETWORK_INTERFACE_PRECONFIGURED;
197 g_object_get (device->manager,
201 network_device_update_essid (device);
202 context = g_initable_new (GUPNP_TYPE_CONTEXT,
206 "network", device->essid,
211 g_warning ("Error creating GUPnP context: %s",
213 g_error_free (error);
217 g_hash_table_insert (device->contexts, g_strdup (label), context);
219 g_signal_emit_by_name (device->manager,
225 context_signal_up (G_GNUC_UNUSED gpointer key,
229 g_signal_emit_by_name (user_data, "context-available", value);
233 context_signal_down (G_GNUC_UNUSED gpointer key,
237 g_signal_emit_by_name (user_data, "context-unavailable", value);
241 network_device_up (NetworkInterface *device)
243 if (device->flags & NETWORK_INTERFACE_UP)
246 device->flags |= NETWORK_INTERFACE_UP;
248 if (g_hash_table_size (device->contexts) > 0)
249 g_hash_table_foreach (device->contexts,
252 else if (device->flags & NETWORK_INTERFACE_PRECONFIGURED)
253 network_device_create_context (device, device->name);
257 network_device_down (NetworkInterface *device)
259 if (!device->flags & NETWORK_INTERFACE_UP)
262 device->flags &= ~NETWORK_INTERFACE_UP;
264 if (device->contexts)
265 g_hash_table_foreach (device->contexts,
271 network_device_free (NetworkInterface *device)
273 g_free (device->name);
274 g_free (device->essid);
276 if (device->contexts != NULL) {
281 g_hash_table_iter_init (&iter, device->contexts);
282 while (g_hash_table_iter_next (&iter,
284 (gpointer *) &value)) {
285 g_signal_emit_by_name (device->manager,
286 "context-unavailable",
288 g_hash_table_iter_remove (&iter);
292 g_hash_table_unref (device->contexts);
293 device->contexts = NULL;
295 g_slice_free (NetworkInterface, device);
299 static void query_all_network_interfaces (GUPnPLinuxContextManager *self);
300 static void query_all_addresses (GUPnPLinuxContextManager *self);
301 static void receive_netlink_message (GUPnPLinuxContextManager *self,
303 static void create_context (GUPnPLinuxContextManager *self,
305 struct ifaddrmsg *ifa);
306 static void remove_context (GUPnPLinuxContextManager *self,
308 struct ifaddrmsg *ifa);
311 on_netlink_message_available (G_GNUC_UNUSED GSocket *socket,
312 G_GNUC_UNUSED GIOCondition condition,
315 GUPnPLinuxContextManager *self;
317 self = GUPNP_LINUX_CONTEXT_MANAGER (user_data);
319 receive_netlink_message (self, NULL);
324 #define RT_ATTR_OK(a,l) \
325 ((l > 0) && RTA_OK (a, l))
328 extract_info (struct nlmsghdr *header, char **label)
331 struct rtattr *rt_attr;
333 rt_attr = IFLA_RTA (NLMSG_DATA (header));
334 rt_attr_len = IFLA_PAYLOAD (header);
335 while (RT_ATTR_OK (rt_attr, rt_attr_len)) {
336 if (rt_attr->rta_type == IFA_LABEL) {
337 *label = g_strdup ((char *) RTA_DATA (rt_attr));
341 rt_attr = RTA_NEXT (rt_attr, rt_attr_len);
346 is_wireless_status_message (struct nlmsghdr *header)
349 struct rtattr *rt_attr;
351 rt_attr = IFLA_RTA (NLMSG_DATA (header));
352 rt_attr_len = IFLA_PAYLOAD (header);
353 while (RT_ATTR_OK (rt_attr, rt_attr_len)) {
354 if (rt_attr->rta_type == IFLA_WIRELESS)
357 rt_attr = RTA_NEXT (rt_attr, rt_attr_len);
364 create_context (GUPnPLinuxContextManager *self,
366 struct ifaddrmsg *ifa)
368 NetworkInterface *device;
370 remove_context (self, label, ifa);
372 device = g_hash_table_lookup (self->priv->interfaces,
373 GINT_TO_POINTER (ifa->ifa_index));
376 g_warning ("Got new address for device %d but device is"
383 /* If device isn't one we consider, silently skip address */
384 if (device->flags & NETWORK_INTERFACE_IGNORE)
387 network_device_create_context (device, label);
391 remove_context (GUPnPLinuxContextManager *self,
393 struct ifaddrmsg *ifa)
395 NetworkInterface *device;
396 GUPnPContext *context;
398 device = g_hash_table_lookup (self->priv->interfaces,
399 GINT_TO_POINTER (ifa->ifa_index));
404 context = g_hash_table_lookup (device->contexts, label);
406 if (device->flags & NETWORK_INTERFACE_UP) {
407 g_signal_emit_by_name (self,
408 "context-unavailable",
411 g_hash_table_remove (device->contexts, label);
414 if (g_hash_table_size (device->contexts) == 0)
415 device->flags &= ~NETWORK_INTERFACE_PRECONFIGURED;
418 /* Idle-handler for initial interface and address bootstrapping.
420 * We cannot send the RTM_GETADDR message until we processed all packets of
421 * the RTM_GETLINK message. So on the first call this idle handler processes
422 * all answers of RTM_GETLINK on the second call all answers of RTM_GETADDR
423 * and on the third call it creates the regular socket source for listening on
424 * the netlink socket, detaching itself from the idle source afterwards.
427 on_bootstrap (GUPnPLinuxContextManager *self)
429 if (self->priv->nl_seq == 0) {
430 query_all_network_interfaces (self);
433 } else if (self->priv->nl_seq == 1) {
434 query_all_addresses (self);
438 self->priv->netlink_socket_source = g_socket_create_source
439 (self->priv->netlink_socket,
443 g_source_attach (self->priv->netlink_socket_source,
444 g_main_context_get_thread_default ());
446 g_source_set_callback (self->priv->netlink_socket_source,
448 on_netlink_message_available,
462 send_netlink_request (GUPnPLinuxContextManager *self,
463 guint netlink_message,
467 struct sockaddr_nl dest;
472 memset (&req, 0, sizeof (req));
473 memset (&dest, 0, sizeof (dest));
474 memset (&msg, 0, sizeof (msg));
476 dest.nl_family = AF_NETLINK;
477 req.hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtgenmsg));
478 req.hdr.nlmsg_seq = self->priv->nl_seq++;
479 req.hdr.nlmsg_type = netlink_message;
480 req.hdr.nlmsg_flags = NLM_F_REQUEST | flags;
481 req.gen.rtgen_family = AF_INET;
484 io.iov_len = req.hdr.nlmsg_len;
488 msg.msg_name = &dest;
489 msg.msg_namelen = sizeof (dest);
491 fd = g_socket_get_fd (self->priv->netlink_socket);
492 if (sendmsg (fd, (struct msghdr *) &msg, 0) < 0)
493 g_warning ("Could not send netlink message: %s",
497 /* Query all available interfaces and immediately process all answers. We need
498 * to do this to be able to send RTM_GETADDR in the next step */
500 query_all_network_interfaces (GUPnPLinuxContextManager *self)
502 GError *error = NULL;
504 send_netlink_request (self, RTM_GETLINK, NLM_F_DUMP);
506 receive_netlink_message (self, &error);
507 } while (error == NULL);
509 g_error_free (error);
512 /* Start query of all currenly available network addresses. The answer will be
513 * processed by the normal netlink socket source call-back. */
515 query_all_addresses (GUPnPLinuxContextManager *self)
517 send_netlink_request (self,
519 NLM_F_ROOT | NLM_F_MATCH | NLM_F_ACK);
522 /* Ignore non-multicast device, except loop-back and P-t-P devices */
523 #define INTERFACE_IS_VALID(ifi) \
524 (((ifi)->ifi_flags & (IFF_MULTICAST | IFF_LOOPBACK)) && \
525 !((ifi)->ifi_flags & IFF_POINTOPOINT))
527 /* Handle status changes (up, down, new address, ...) on network interfaces */
529 handle_device_status_change (GUPnPLinuxContextManager *self,
530 struct ifinfomsg *ifi)
533 NetworkInterface *device;
535 key = GINT_TO_POINTER (ifi->ifi_index);
536 device = g_hash_table_lookup (self->priv->interfaces,
539 if (device != NULL) {
540 if (ifi->ifi_flags & IFF_UP)
541 network_device_up (device);
543 network_device_down (device);
548 device = network_device_new (self, ifi->ifi_index);
550 if (!INTERFACE_IS_VALID (ifi))
551 device->flags |= NETWORK_INTERFACE_IGNORE;
552 if (ifi->ifi_flags & IFF_UP)
553 device->flags |= NETWORK_INTERFACE_UP;
555 g_hash_table_insert (self->priv->interfaces,
562 remove_device (GUPnPLinuxContextManager *self,
563 struct ifinfomsg *ifi)
565 g_hash_table_remove (self->priv->interfaces,
566 GINT_TO_POINTER (ifi->ifi_index));
569 #define NLMSG_IS_VALID(msg,len) \
570 (NLMSG_OK(msg,len) && (msg->nlmsg_type != NLMSG_DONE))
572 /* Process the raw netlink message and dispatch to helper functions
575 receive_netlink_message (GUPnPLinuxContextManager *self, GError **error)
577 static char buf[4096];
578 static const int bufsize = 4096;
581 GError *inner_error = NULL;
582 struct nlmsghdr *header = (struct nlmsghdr *) buf;
583 struct ifinfomsg *ifi;
584 struct ifaddrmsg *ifa;
587 len = g_socket_receive (self->priv->netlink_socket,
593 if (inner_error->code != G_IO_ERROR_WOULD_BLOCK)
594 g_warning ("Error receiving netlink message: %s",
595 inner_error->message);
596 g_propagate_error (error, inner_error);
601 for (;NLMSG_IS_VALID (header, len); header = NLMSG_NEXT (header,len)) {
602 switch (header->nlmsg_type) {
603 /* RTM_NEWADDR and RTM_DELADDR are sent on real address
605 * RTM_NEWLINK is sent on varous occations:
606 * - Creation of a new device
607 * - Device goes up/down
608 * - Wireless status changes
609 * RTM_DELLINK is sent only if device is removed, like
610 * openvpn --rmtun /dev/tun0, NOT on ifconfig down. */
615 ifa = NLMSG_DATA (header);
616 extract_info (header, &label);
617 create_context (self, label, ifa);
625 ifa = NLMSG_DATA (header);
626 extract_info (header, &label);
627 remove_context (self, label, ifa);
632 ifi = NLMSG_DATA (header);
634 /* Check if wireless is up for chit-chat */
635 if (is_wireless_status_message (header))
637 handle_device_status_change (self, ifi);
640 ifi = NLMSG_DATA (header);
641 remove_device (self, ifi);
651 /* Create INET socket used for SIOCGIFNAME and SIOCGIWESSID ioctl
654 create_ioctl_socket (GUPnPLinuxContextManager *self, GError **error)
656 self->priv->fd = socket (AF_INET, SOCK_DGRAM, 0);
658 if (self->priv->fd < 0) {
661 g_set_error_literal (error,
663 g_io_error_from_errno (errno),
664 "Failed to setup socket for ioctl");
672 /* Create a netlink socket, bind to it and wrap it in a GSocket */
674 create_netlink_socket (GUPnPLinuxContextManager *self, GError **error)
676 struct sockaddr_nl sa;
683 fd = socket (PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
685 g_set_error_literal (error,
687 g_io_error_from_errno (errno),
688 "Failed to bind to netlink socket");
692 memset (&sa, 0, sizeof (sa));
693 sa.nl_family = AF_NETLINK;
694 /* Listen for interface changes and IP address changes */
695 sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
697 status = bind (fd, (struct sockaddr *) &sa, sizeof (sa));
699 g_set_error_literal (error,
701 g_io_error_from_errno (errno),
702 "Failed to bind to netlink socket");
708 sock = g_socket_new_from_fd (fd, &inner_error);
711 g_propagate_prefixed_error (error,
713 "Failed to create GSocket from "
719 g_socket_set_blocking (sock, FALSE);
721 self->priv->netlink_socket = sock;
726 /* public helper function to determine runtime-fallback depending on netlink
729 gupnp_linux_context_manager_is_available (void)
733 fd = socket (PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
743 /* GObject virtual functions */
746 gupnp_linux_context_manager_init (GUPnPLinuxContextManager *self)
749 G_TYPE_INSTANCE_GET_PRIVATE (self,
750 GUPNP_TYPE_LINUX_CONTEXT_MANAGER,
751 GUPnPLinuxContextManagerPrivate);
753 self->priv->nl_seq = 0;
755 self->priv->interfaces =
756 g_hash_table_new_full (g_direct_hash,
759 (GDestroyNotify) network_device_free);
762 /* Constructor, kicks off bootstrapping */
764 gupnp_linux_context_manager_constructed (GObject *object)
766 GObjectClass *parent_class;
767 GUPnPLinuxContextManager *self;
768 GError *error = NULL;
770 self = GUPNP_LINUX_CONTEXT_MANAGER (object);
772 if (!create_ioctl_socket (self, &error))
775 if (!create_netlink_socket (self, &error))
778 self->priv->bootstrap_source =
779 g_idle_source_new ();
780 g_source_attach (self->priv->bootstrap_source,
781 g_main_context_get_thread_default ());
782 g_source_set_callback (self->priv->bootstrap_source,
783 (GSourceFunc) on_bootstrap,
788 if (self->priv->fd > 0)
789 close (self->priv->fd);
791 g_warning ("Failed to setup Linux context manager: %s",
794 g_error_free (error);
798 parent_class = G_OBJECT_CLASS (gupnp_linux_context_manager_parent_class);
799 if (parent_class->constructed)
800 parent_class->constructed (object);
804 gupnp_linux_context_manager_dispose (GObject *object)
806 GUPnPLinuxContextManager *self;
807 GObjectClass *parent_class;
809 self = GUPNP_LINUX_CONTEXT_MANAGER (object);
811 if (self->priv->bootstrap_source != NULL) {
812 g_source_destroy (self->priv->bootstrap_source);
813 g_source_unref (self->priv->bootstrap_source);
814 self->priv->bootstrap_source = NULL;
817 if (self->priv->netlink_socket_source != NULL) {
818 g_source_destroy (self->priv->netlink_socket_source);
819 g_source_unref (self->priv->netlink_socket_source);
820 self->priv->netlink_socket_source = NULL;
823 if (self->priv->netlink_socket != NULL) {
824 g_object_unref (self->priv->netlink_socket);
825 self->priv->netlink_socket = NULL;
828 if (self->priv->fd != 0) {
829 close (self->priv->fd);
833 if (self->priv->interfaces) {
834 g_hash_table_destroy (self->priv->interfaces);
835 self->priv->interfaces = NULL;
839 parent_class = G_OBJECT_CLASS (gupnp_linux_context_manager_parent_class);
840 parent_class->dispose (object);
844 gupnp_linux_context_manager_class_init (GUPnPLinuxContextManagerClass *klass)
846 GObjectClass *object_class;
848 object_class = G_OBJECT_CLASS (klass);
850 object_class->constructed = gupnp_linux_context_manager_constructed;
851 object_class->dispose = gupnp_linux_context_manager_dispose;
853 g_type_class_add_private (klass,
854 sizeof (GUPnPLinuxContextManagerPrivate));