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
30 #include <sys/types.h>
31 #include <sys/socket.h>
32 #include <linux/netlink.h>
33 #include <linux/rtnetlink.h>
34 #ifdef HAVE_LINUX_WIRELESS_H
35 #include <linux/wireless.h>
37 #include <sys/ioctl.h>
41 #include "gupnp-linux-context-manager.h"
42 #include "gupnp-context.h"
44 G_DEFINE_TYPE (GUPnPLinuxContextManager,
45 gupnp_linux_context_manager,
46 GUPNP_TYPE_CONTEXT_MANAGER);
48 struct _GUPnPLinuxContextManagerPrivate {
51 GSocket *netlink_socket;
52 GSource *netlink_socket_source;
53 GSource *bootstrap_source;
55 GHashTable *interfaces;
60 NETWORK_INTERFACE_UP = 1 << 0,
62 /* Interface doesn't support multicast or is P-t-P */
63 NETWORK_INTERFACE_IGNORE = 1 << 1,
65 /* Interface is down but has an address set */
66 NETWORK_INTERFACE_PRECONFIGURED = 1 << 2
67 } NetworkInterfaceFlags;
69 /* struct representing a network interface */
70 struct _NetworkInterface {
71 /* Weak pointer to context manager associated with this interface */
72 GUPnPLinuxContextManager *manager;
74 /* Name of the interface (eth0 etc.) */
77 /* ESSID for wireless interfaces */
80 /* States of the interface */
81 NetworkInterfaceFlags flags;
83 /* UPnP contexts associated with this interface. Can be more than one
84 * with alias addresses like eth0:1 etc. */
88 typedef struct _NetworkInterface NetworkInterface;
90 /* Create a new network interface struct and query the device name */
91 static NetworkInterface *
92 network_device_new (GUPnPLinuxContextManager *manager,
95 NetworkInterface *device;
99 /* Query interface name */
100 memset (&ifr, 0, sizeof (struct ifreq));
101 ifr.ifr_ifindex = index;
102 ret = ioctl (manager->priv->fd, SIOCGIFNAME, &ifr);
105 g_warning ("Could not get interface name for index %d",
111 device = g_slice_new0 (NetworkInterface);
112 device->manager = manager;
113 device->name = g_strdup (ifr.ifr_name);
115 device->contexts = g_hash_table_new_full (g_str_hash,
123 /* Try to update the ESSID of a network interface. */
125 network_device_update_essid (NetworkInterface *device)
127 char *old_essid = device->essid;
128 #ifdef HAVE_LINUX_WIRELESS_H
129 char essid[IW_ESSID_MAX_SIZE + 1];
134 memset (&iwr, 0, sizeof (struct iwreq));
135 memset (essid, 0, IW_ESSID_MAX_SIZE + 1);
136 strncpy (iwr.ifr_name, device->name, IFNAMSIZ);
137 iwr.u.essid.pointer = (caddr_t) essid;
138 iwr.u.essid.length = IW_ESSID_MAX_SIZE;
139 ret = ioctl (device->manager->priv->fd, SIOCGIWESSID, &iwr);
141 if ((ret == 0 && essid[0] != '\0') &&
142 (!device->essid || strcmp (device->essid, essid)))
143 device->essid = g_strdup (essid);
152 network_device_create_context (NetworkInterface *device, const char *label)
155 GError *error = NULL;
156 GUPnPContext *context;
158 /* We cannot create a context yet. But it may be that there will not
159 * be a RTM_NEWADDR message for this device if the IP address does not
160 * change so we mark this device as preconfigured and will create the
161 * context if the device comes up. If the address changes, we'll get a
162 * RTM_DELADDR before the next RTM_NEWADDR. */
163 if (!device->flags & NETWORK_INTERFACE_UP) {
164 device->flags |= NETWORK_INTERFACE_PRECONFIGURED;
169 device->flags &= ~NETWORK_INTERFACE_PRECONFIGURED;
171 g_object_get (device->manager,
175 network_device_update_essid (device);
176 context = g_initable_new (GUPNP_TYPE_CONTEXT,
180 "network", device->essid,
185 g_warning ("Error creating GUPnP context: %s",
187 g_error_free (error);
191 g_hash_table_insert (device->contexts, g_strdup (label), context);
193 g_signal_emit_by_name (device->manager,
199 context_signal_up (G_GNUC_UNUSED gpointer key,
203 g_signal_emit_by_name (user_data, "context-available", value);
207 context_signal_down (G_GNUC_UNUSED gpointer key,
211 g_signal_emit_by_name (user_data, "context-unavailable", value);
215 network_device_up (NetworkInterface *device)
217 if (device->flags & NETWORK_INTERFACE_UP)
220 device->flags |= NETWORK_INTERFACE_UP;
222 if (g_hash_table_size (device->contexts) > 0)
223 g_hash_table_foreach (device->contexts,
226 else if (device->flags & NETWORK_INTERFACE_PRECONFIGURED)
227 network_device_create_context (device, device->name);
231 network_device_down (NetworkInterface *device)
233 if (!device->flags & NETWORK_INTERFACE_UP)
236 device->flags &= ~NETWORK_INTERFACE_UP;
238 if (device->contexts)
239 g_hash_table_foreach (device->contexts,
245 network_device_free (NetworkInterface *device)
247 if (device->name != NULL)
248 g_free (device->name);
249 if (device->essid != NULL)
250 g_free (device->essid);
252 if (device->contexts != NULL) {
257 g_hash_table_iter_init (&iter, device->contexts);
258 while (g_hash_table_iter_next (&iter,
260 (gpointer *) &value)) {
261 g_signal_emit_by_name (device->manager,
262 "context-unavailable",
264 g_hash_table_iter_remove (&iter);
268 g_hash_table_unref (device->contexts);
269 device->contexts = NULL;
273 static void query_all_network_interfaces (GUPnPLinuxContextManager *self);
274 static void query_all_addresses (GUPnPLinuxContextManager *self);
275 static void receive_netlink_message (GUPnPLinuxContextManager *self,
277 static void create_context (GUPnPLinuxContextManager *self,
279 struct ifaddrmsg *ifa);
280 static void remove_context (GUPnPLinuxContextManager *self,
282 struct ifaddrmsg *ifa);
285 on_netlink_message_available (G_GNUC_UNUSED GSocket *socket,
286 G_GNUC_UNUSED GIOCondition condition,
289 GUPnPLinuxContextManager *self;
291 self = GUPNP_LINUX_CONTEXT_MANAGER (user_data);
293 receive_netlink_message (self, NULL);
298 #define RT_ATTR_OK(a,l) \
299 ((l > 0) && RTA_OK (a, l))
302 extract_info (struct nlmsghdr *header, char **label)
305 struct rtattr *rt_attr;
307 rt_attr = IFLA_RTA (NLMSG_DATA (header));
308 rt_attr_len = IFLA_PAYLOAD (header);
309 while (RT_ATTR_OK (rt_attr, rt_attr_len)) {
310 if (rt_attr->rta_type == IFA_LABEL) {
311 *label = g_strdup ((char *) RTA_DATA (rt_attr));
314 rt_attr = RTA_NEXT (rt_attr, rt_attr_len);
319 is_wireless_status_message (struct nlmsghdr *header)
322 struct rtattr *rt_attr;
324 rt_attr = IFLA_RTA (NLMSG_DATA (header));
325 rt_attr_len = IFLA_PAYLOAD (header);
326 while (RT_ATTR_OK (rt_attr, rt_attr_len)) {
327 if (rt_attr->rta_type == IFLA_WIRELESS)
330 rt_attr = RTA_NEXT (rt_attr, rt_attr_len);
337 create_context (GUPnPLinuxContextManager *self,
339 struct ifaddrmsg *ifa)
341 NetworkInterface *device;
343 remove_context (self, label, ifa);
345 device = g_hash_table_lookup (self->priv->interfaces,
346 GINT_TO_POINTER (ifa->ifa_index));
349 g_warning ("Got new address for device %d but device is"
356 /* If device isn't one we consider, silently skip address */
357 if (device->flags & NETWORK_INTERFACE_IGNORE) {
361 network_device_create_context (device, label);
365 remove_context (GUPnPLinuxContextManager *self,
367 struct ifaddrmsg *ifa)
369 NetworkInterface *device;
370 GUPnPContext *context;
372 device = g_hash_table_lookup (self->priv->interfaces,
373 GINT_TO_POINTER (ifa->ifa_index));
378 context = g_hash_table_lookup (device->contexts, label);
380 if (device->flags & NETWORK_INTERFACE_UP) {
381 g_signal_emit_by_name (self,
382 "context-unavailable",
385 g_hash_table_remove (device->contexts, label);
388 if (g_hash_table_size (device->contexts) == 0)
389 device->flags &= ~NETWORK_INTERFACE_PRECONFIGURED;
392 /* Idle-handler for initial interface and address bootstrapping.
394 * We cannot send the RTM_GETADDR message until we processed all packets of
395 * the RTM_GETLINK message. So on the first call this idle handler processes
396 * all answers of RTM_GETLINK on the second call all answers of RTM_GETADDR
397 * and on the third call it creates the regular socket source for listening on
398 * the netlink socket, detaching itself from the idle source afterwards.
401 on_bootstrap (GUPnPLinuxContextManager *self)
403 if (self->priv->nl_seq == 0) {
404 query_all_network_interfaces (self);
407 } else if (self->priv->nl_seq == 1) {
408 query_all_addresses (self);
412 self->priv->netlink_socket_source = g_socket_create_source
413 (self->priv->netlink_socket,
417 g_source_attach (self->priv->netlink_socket_source,
418 g_main_context_get_thread_default ());
420 g_source_set_callback (self->priv->netlink_socket_source,
422 on_netlink_message_available,
436 send_netlink_request (GUPnPLinuxContextManager *self,
437 guint netlink_message,
441 struct sockaddr_nl dest;
446 memset (&req, 0, sizeof (req));
447 memset (&dest, 0, sizeof (dest));
448 memset (&msg, 0, sizeof (msg));
450 dest.nl_family = AF_NETLINK;
451 req.hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtgenmsg));
452 req.hdr.nlmsg_seq = self->priv->nl_seq++;
453 req.hdr.nlmsg_type = netlink_message;
454 req.hdr.nlmsg_flags = NLM_F_REQUEST | flags;
455 req.gen.rtgen_family = AF_INET;
458 io.iov_len = req.hdr.nlmsg_len;
462 msg.msg_name = &dest;
463 msg.msg_namelen = sizeof (dest);
465 fd = g_socket_get_fd (self->priv->netlink_socket);
466 if (sendmsg (fd, (struct msghdr *) &msg, 0) < 0)
467 g_warning ("Could not send netlink message: %s",
472 query_all_network_interfaces (GUPnPLinuxContextManager *self)
474 GError *error = NULL;
476 send_netlink_request (self, RTM_GETLINK, NLM_F_DUMP);
478 receive_netlink_message (self, &error);
479 } while (error == NULL);
481 g_error_free (error);
485 query_all_addresses (GUPnPLinuxContextManager *self)
487 send_netlink_request (self,
489 NLM_F_ROOT | NLM_F_MATCH | NLM_F_ACK);
492 /* Ignore non-multicast device, except loop-back and P-t-P devices */
493 #define INTERFACE_IS_VALID(ifi) \
494 (((ifi)->ifi_flags & (IFF_MULTICAST | IFF_LOOPBACK)) && \
495 !((ifi)->ifi_flags & IFF_POINTOPOINT))
498 handle_device_status_change (GUPnPLinuxContextManager *self,
499 struct ifinfomsg *ifi)
502 NetworkInterface *device;
504 key = GINT_TO_POINTER (ifi->ifi_index);
505 device = g_hash_table_lookup (self->priv->interfaces,
508 if (device != NULL) {
509 if (ifi->ifi_flags & IFF_UP)
510 network_device_up (device);
512 network_device_down (device);
517 device = network_device_new (self, ifi->ifi_index);
519 if (!INTERFACE_IS_VALID (ifi))
520 device->flags |= NETWORK_INTERFACE_IGNORE;
521 if (ifi->ifi_flags & IFF_UP)
522 device->flags |= NETWORK_INTERFACE_UP;
524 g_hash_table_insert (self->priv->interfaces,
531 remove_device (GUPnPLinuxContextManager *self,
532 struct ifinfomsg *ifi)
534 g_hash_table_remove (self->priv->interfaces,
535 GINT_TO_POINTER (ifi->ifi_index));
538 #define NLMSG_IS_VALID(msg,len) \
539 (NLMSG_OK(msg,len) && (msg->nlmsg_type != NLMSG_DONE))
542 receive_netlink_message (GUPnPLinuxContextManager *self, GError **error)
544 static char buf[4096];
545 static const int bufsize = 4096;
548 GError *inner_error = NULL;
549 struct nlmsghdr *header = (struct nlmsghdr *) buf;
550 struct ifinfomsg *ifi;
551 struct ifaddrmsg *ifa;
554 len = g_socket_receive (self->priv->netlink_socket,
560 if (inner_error->code != G_IO_ERROR_WOULD_BLOCK)
561 g_warning ("Error receiving netlink message: %s",
562 inner_error->message);
563 g_propagate_error (error, inner_error);
568 for (;NLMSG_IS_VALID (header, len); header = NLMSG_NEXT (header,len)) {
569 switch (header->nlmsg_type) {
570 /* RTM_NEWADDR and RTM_DELADDR are sent on real address
572 * RTM_NEWLINK is sent on varous occations:
573 * - Creation of a new device
574 * - Device goes up/down
575 * - Wireless status changes
576 * RTM_DELLINK is sent only if device is removed, like
577 * openvpn --rmtun /dev/tun0, NOT on ifconfig down. */
582 ifa = NLMSG_DATA (header);
583 extract_info (header, &label);
584 create_context (self, label, ifa);
592 ifa = NLMSG_DATA (header);
593 extract_info (header, &label);
594 remove_context (self, label, ifa);
599 ifi = NLMSG_DATA (header);
601 /* Check if wireless is up for chit-chat */
602 if (is_wireless_status_message (header))
604 handle_device_status_change (self, ifi);
607 ifi = NLMSG_DATA (header);
608 remove_device (self, ifi);
618 /* Create INET socket used for SIOCGIFNAME and SIOCGIWESSID ioctl
621 create_ioctl_socket (GUPnPLinuxContextManager *self, GError **error)
623 self->priv->fd = socket (AF_INET, SOCK_DGRAM, 0);
625 if (self->priv->fd < 0) {
628 g_set_error_literal (error,
630 g_io_error_from_errno (errno),
631 "Failed to setup socket for ioctl");
639 /* Create a netlink socket, bind to it and wrap it in a GSocket */
641 create_netlink_socket (GUPnPLinuxContextManager *self, GError **error)
643 struct sockaddr_nl sa;
650 fd = socket (PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
652 g_set_error_literal (error,
654 g_io_error_from_errno (errno),
655 "Failed to bind to netlink socket");
659 memset (&sa, 0, sizeof (sa));
660 sa.nl_family = AF_NETLINK;
661 /* Listen for interface changes and IP address changes */
662 sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
664 status = bind (fd, (struct sockaddr *) &sa, sizeof (sa));
666 g_set_error_literal (error,
668 g_io_error_from_errno (errno),
669 "Failed to bind to netlink socket");
675 sock = g_socket_new_from_fd (fd, &inner_error);
678 g_propagate_prefixed_error (error,
680 "Failed to create GSocket from "
686 g_socket_set_blocking (sock, FALSE);
688 self->priv->netlink_socket = sock;
694 gupnp_linux_context_manager_is_available (void)
698 fd = socket (PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
709 gupnp_linux_context_manager_init (GUPnPLinuxContextManager *self)
712 G_TYPE_INSTANCE_GET_PRIVATE (self,
713 GUPNP_TYPE_LINUX_CONTEXT_MANAGER,
714 GUPnPLinuxContextManagerPrivate);
716 self->priv->nl_seq = 0;
718 self->priv->interfaces =
719 g_hash_table_new_full (g_direct_hash,
722 (GDestroyNotify) network_device_free);
726 gupnp_linux_context_manager_constructed (GObject *object)
728 GObjectClass *parent_class;
729 GUPnPLinuxContextManager *self;
730 GError *error = NULL;
732 self = GUPNP_LINUX_CONTEXT_MANAGER (object);
734 if (!create_ioctl_socket (self, &error))
737 if (!create_netlink_socket (self, &error))
740 self->priv->bootstrap_source =
741 g_idle_source_new ();
742 g_source_attach (self->priv->bootstrap_source,
743 g_main_context_get_thread_default ());
744 g_source_set_callback (self->priv->bootstrap_source,
745 (GSourceFunc) on_bootstrap,
750 if (self->priv->fd > 0)
751 close (self->priv->fd);
753 g_warning ("Failed to setup Linux context manager: %s",
756 g_error_free (error);
760 parent_class = G_OBJECT_CLASS (gupnp_linux_context_manager_parent_class);
761 if (parent_class->constructed)
762 parent_class->constructed (object);
766 gupnp_linux_context_manager_dispose (GObject *object)
768 GUPnPLinuxContextManager *self;
769 GObjectClass *parent_class;
771 self = GUPNP_LINUX_CONTEXT_MANAGER (object);
773 if (self->priv->bootstrap_source != NULL) {
774 g_source_destroy (self->priv->bootstrap_source);
775 g_source_unref (self->priv->bootstrap_source);
776 self->priv->bootstrap_source = NULL;
779 if (self->priv->netlink_socket_source != NULL) {
780 g_source_destroy (self->priv->netlink_socket_source);
781 g_source_unref (self->priv->netlink_socket_source);
782 self->priv->netlink_socket_source = NULL;
785 if (self->priv->netlink_socket != NULL) {
786 g_object_unref (self->priv->netlink_socket);
787 self->priv->netlink_socket = NULL;
790 if (self->priv->fd != 0) {
791 close (self->priv->fd);
795 if (self->priv->interfaces) {
796 g_hash_table_destroy (self->priv->interfaces);
797 self->priv->interfaces = NULL;
801 parent_class = G_OBJECT_CLASS (gupnp_linux_context_manager_parent_class);
802 parent_class->dispose (object);
806 gupnp_linux_context_manager_class_init (GUPnPLinuxContextManagerClass *klass)
808 GObjectClass *object_class;
810 object_class = G_OBJECT_CLASS (klass);
812 object_class->constructed = gupnp_linux_context_manager_constructed;
813 object_class->dispose = gupnp_linux_context_manager_dispose;
815 g_type_class_add_private (klass,
816 sizeof (GUPnPLinuxContextManagerPrivate));