gupnp-linux-context-manager.c 40 KB
Newer Older
Jens Georg's avatar
Jens Georg committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
 * Copyright (C) 2011 Jens Georg
 *
 * Author: Jens Georg <mail@jensge.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
Jens Georg's avatar
Jens Georg committed
18
19
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
Jens Georg's avatar
Jens Georg committed
20
21
22
23
24
25
 */

/**
 * SECTION:gupnp-linux-context-manager
 * @short_description: Linux-specific implementation of #GUPnPContextManager
 *
Jens Georg's avatar
Jens Georg committed
26
27
28
29
30
31
32
33
34
35
36
37
38
 * This is a Linux-specific context manager which uses <ulink
 * url="http://www.linuxfoundation.org/collaborate/workgroups/networking/netlink">Netlink</ulink>
 * to detect the changes in network interface configurations, such as
 * added or removed interfaces, network addresses, ...
 *
 * The context manager works in two phase.
 *
 * Phase one is the "bootstrapping" phase where we query all currently
 * configured interfaces and addresses.
 *
 * Phase two is the "listening" phase where we just listen to the netlink
 * messages that are happening and create or destroy #GUPnPContext<!-- -->s
 * accordingly.
Jens Georg's avatar
Jens Georg committed
39
40
 */

Jens Georg's avatar
Jens Georg committed
41
42
#define G_LOG_DOMAIN "GUPnP-ContextManager-Linux"

Jens Georg's avatar
Jens Georg committed
43
44
#include <config.h>

Jens Georg's avatar
Jens Georg committed
45
#include <arpa/inet.h>
Jens Georg's avatar
Jens Georg committed
46
47
48
49
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
50
#include <net/if.h>
Jens Georg's avatar
Jens Georg committed
51
52
53
54
55
56
57
#ifdef HAVE_LINUX_WIRELESS_H
#include <linux/wireless.h>
#endif
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>

Jens Georg's avatar
Jens Georg committed
58
#include <glib.h>
59
60
#include <gio/gio.h>

Jens Georg's avatar
Jens Georg committed
61
62
63
64
#include "gupnp-linux-context-manager.h"
#include "gupnp-context.h"

struct _GUPnPLinuxContextManagerPrivate {
Jens Georg's avatar
Jens Georg committed
65
        /* Socket used for IOCTL calls */
Jens Georg's avatar
Jens Georg committed
66
        int fd;
Jens Georg's avatar
Jens Georg committed
67
68

        /* Netlink sequence number; nl_seq > 1 means bootstrapping done */
Jens Georg's avatar
Jens Georg committed
69
        int nl_seq;
Jens Georg's avatar
Jens Georg committed
70
71

        /* Socket used to do netlink communication */
Jens Georg's avatar
Jens Georg committed
72
        GSocket *netlink_socket;
Jens Georg's avatar
Jens Georg committed
73
74

        /* Socket source used for normal netlink communication */
Jens Georg's avatar
Jens Georg committed
75
        GSource *netlink_socket_source;
Jens Georg's avatar
Jens Georg committed
76
77

        /* Idle source used during bootstrap */
Jens Georg's avatar
Jens Georg committed
78
79
        GSource *bootstrap_source;

Jens Georg's avatar
Jens Georg committed
80
81
        /* A hash table mapping system interface indices to a NetworkInterface
         * structure */
Jens Georg's avatar
Jens Georg committed
82
        GHashTable *interfaces;
Jens Georg's avatar
Jens Georg committed
83

84
85
86
        /* Receive buffer for netlink messages. */
        char recvbuf[8196];

Jens Georg's avatar
Jens Georg committed
87
        gboolean dump_netlink_packets;
Jens Georg's avatar
Jens Georg committed
88
};
Jens Georg's avatar
Jens Georg committed
89
90
91
92
93
94
95
96
97
98
typedef struct _GUPnPLinuxContextManagerPrivate GUPnPLinuxContextManagerPrivate;


struct _GUPnPLinuxContextManager {
        GUPnPContextManager parent;
};

G_DEFINE_TYPE_WITH_PRIVATE (GUPnPLinuxContextManager,
                            gupnp_linux_context_manager,
                            GUPNP_TYPE_CONTEXT_MANAGER);
Jens Georg's avatar
Jens Georg committed
99
100
101
102
103
104
105
106
107
108
109
110

typedef enum {
        /* Interface is up */
        NETWORK_INTERFACE_UP = 1 << 0,

        /* Interface doesn't support multicast or is P-t-P */
        NETWORK_INTERFACE_IGNORE = 1 << 1,

        /* Interface is down but has an address set */
        NETWORK_INTERFACE_PRECONFIGURED = 1 << 2
} NetworkInterfaceFlags;

Jens Georg's avatar
Jens Georg committed
111
static void
Jens Georg's avatar
Jens Georg committed
112
dump_rta_attr (sa_family_t family, struct rtattr *rt_attr)
Jens Georg's avatar
Jens Georg committed
113
114
115
116
117
118
119
120
121
{
        const char *data = NULL;
        const char *label = NULL;
        char buf[INET6_ADDRSTRLEN];

        if (rt_attr->rta_type == IFA_ADDRESS ||
                        rt_attr->rta_type == IFA_LOCAL ||
                        rt_attr->rta_type == IFA_BROADCAST ||
                        rt_attr->rta_type == IFA_ANYCAST) {
Jens Georg's avatar
Jens Georg committed
122
                data = inet_ntop (family,
Jens Georg's avatar
Jens Georg committed
123
124
125
                                  RTA_DATA (rt_attr),
                                  buf,
                                  sizeof (buf));
Jens Georg's avatar
Jens Georg committed
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
        } else if (rt_attr->rta_type == IFA_LABEL) {
                data = (const char *) RTA_DATA (rt_attr);
        } else {
                data = "Unknown";
        }

        switch (rt_attr->rta_type) {
                case IFA_UNSPEC: label = "IFA_UNSPEC"; break;
                case IFA_ADDRESS: label = "IFA_ADDRESS"; break;
                case IFA_LOCAL: label = "IFA_LOCAL"; break;
                case IFA_LABEL: label = "IFA_LABEL"; break;
                case IFA_BROADCAST: label = "IFA_BROADCAST"; break;
                case IFA_ANYCAST: label = "IFA_ANYCAST"; break;
                case IFA_CACHEINFO: label = "IFA_CACHEINFO"; break;
                case IFA_MULTICAST: label = "IFA_MULTICAST"; break;
Jens Georg's avatar
Jens Georg committed
141
#if defined(IFA_FLAGS)
Jens Georg's avatar
Jens Georg committed
142
                case IFA_FLAGS: label = "IFA_FLAGS"; break;
Jens Georg's avatar
Jens Georg committed
143
#endif
Jens Georg's avatar
Jens Georg committed
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
                default: label = "Unknown"; break;
        }

        g_debug ("  %s: %s", label, data);
}

static void
gupnp_linux_context_manager_hexdump (const guint8 * const buffer,
                                     size_t               length,
                                     GString             *hexdump)
{
        size_t offset = 0;
        char ascii[17] = { 0 };
        char padding[49] = { 0 };
        uint8_t modulus = 0;

        for (offset = 0; offset < length; offset++) {
                modulus = (offset % 16);
                if (modulus == 0) {
                        g_string_append_printf (hexdump,
                                                "%04zx: ", offset);
                }

                g_string_append_printf (hexdump, "%02x ", buffer[offset]);

                if (g_ascii_isprint (buffer[offset])) {
                        ascii[modulus] = buffer[offset];
                } else {
                        ascii[modulus] = '.';
                }

                /* end of line, dump ASCII */
                if (modulus == 15) {
                        g_string_append_printf (hexdump, "  %s\n", ascii);
                        memset (ascii, 0, sizeof (ascii));
                }
        }

        if (modulus != 15) {
                memset (padding, ' ', 3 * (15 - modulus));
                g_string_append_printf (hexdump, "%s  %s\n", padding, ascii);
        }
}

Jens Georg's avatar
Jens Georg committed
188
189
190
191
192
193
194
195
196
197
198
/* struct representing a network interface */
struct _NetworkInterface {
        /* Weak pointer to context manager associated with this interface */
        GUPnPLinuxContextManager *manager;

        /* Name of the interface (eth0 etc.) */
        char *name;

        /* ESSID for wireless interfaces */
        char *essid;

199
200
201
        /* interface id of the device */
        int index;

Jens Georg's avatar
Jens Georg committed
202
203
204
        /* States of the interface */
        NetworkInterfaceFlags flags;

Jens Georg's avatar
Jens Georg committed
205
206
207
        /* UPnP contexts associated with this interface. Can be more than one
         * with alias addresses like eth0:1 etc. */
        GHashTable *contexts;
Jens Georg's avatar
Jens Georg committed
208
209
210
211
212
213
214
};

typedef struct _NetworkInterface NetworkInterface;

/* Create a new network interface struct and query the device name */
static NetworkInterface *
network_device_new (GUPnPLinuxContextManager *manager,
215
                    char                     *name,
Jens Georg's avatar
Jens Georg committed
216
217
218
219
220
221
                    int                       index)
{
        NetworkInterface *device;

        device = g_slice_new0 (NetworkInterface);
        device->manager = manager;
222
223
        device->name = name;
        device->index = index;
Jens Georg's avatar
Jens Georg committed
224

Jens Georg's avatar
Jens Georg committed
225
226
227
228
229
        device->contexts = g_hash_table_new_full (g_str_hash,
                                                  g_str_equal,
                                                  g_free,
                                                  g_object_unref);

Jens Georg's avatar
Jens Georg committed
230
231
232
        return device;
}

Jens Georg's avatar
Jens Georg committed
233
234
235
236
237
238
239
240
241
242
static int
get_ioctl_fd (GUPnPLinuxContextManager *self)
{
    GUPnPLinuxContextManagerPrivate *priv;

    priv = gupnp_linux_context_manager_get_instance_private (self);

    return priv->fd;
}

Jens Georg's avatar
Jens Georg committed
243
/* Try to update the ESSID of a network interface. */
244
static void
Jens Georg's avatar
Jens Georg committed
245
246
247
248
249
250
251
252
253
254
255
network_device_update_essid (NetworkInterface *device)
{
        char *old_essid = device->essid;
#ifdef HAVE_LINUX_WIRELESS_H
        char essid[IW_ESSID_MAX_SIZE + 1];
        struct iwreq iwr;
        int ret;

        /* Query essid */
        memset (&iwr, 0, sizeof (struct iwreq));
        memset (essid, 0, IW_ESSID_MAX_SIZE + 1);
256
        strncpy (iwr.ifr_name, device->name, IFNAMSIZ - 1);
Jens Georg's avatar
Jens Georg committed
257
258
        iwr.u.essid.pointer = (caddr_t) essid;
        iwr.u.essid.length = IW_ESSID_MAX_SIZE;
Jens Georg's avatar
Jens Georg committed
259
        ret = ioctl (get_ioctl_fd (device->manager), SIOCGIWESSID, &iwr);
Jens Georg's avatar
Jens Georg committed
260
261
262
263
264
265

        if ((ret == 0 && essid[0] != '\0') &&
            (!device->essid || strcmp (device->essid, essid)))
                device->essid = g_strdup (essid);
        else
                old_essid = NULL;
266
267
#else
        old_essid = NULL;
Jens Georg's avatar
Jens Georg committed
268
#endif
Jens Georg's avatar
Jens Georg committed
269
        g_free (old_essid);
Jens Georg's avatar
Jens Georg committed
270
271
272
}

static void
273
network_device_create_context (NetworkInterface *device,
274
275
                               const char       *address,
                               const char       *label,
276
277
                               const char       *mask,
                               GInetAddressMask *host_mask)
Jens Georg's avatar
Jens Georg committed
278
279
280
{
        guint port;
        GError *error = NULL;
Jens Georg's avatar
Jens Georg committed
281
        GUPnPContext *context;
Jens Georg's avatar
Jens Georg committed
282

283
284
285
286
        if (g_hash_table_contains (device->contexts, address)) {
                g_debug ("Context for address %s on %s already exists",
                         address,
                         label);
Jens Georg's avatar
Jens Georg committed
287
288
289
290

                return;
        }

Jens Georg's avatar
Jens Georg committed
291
        g_object_get (device->manager, "port", &port, NULL);
Jens Georg's avatar
Jens Georg committed
292
293

        network_device_update_essid (device);
Jens Georg's avatar
Jens Georg committed
294

Jens Georg's avatar
Jens Georg committed
295
296
297
        context = g_initable_new (GUPNP_TYPE_CONTEXT,
                                  NULL,
                                  &error,
298
                                  "host-ip", address,
Jens Georg's avatar
Jens Georg committed
299
                                  "interface", label,
300
301
                                  "network", device->essid ? device->essid
                                                           : mask,
302
                                  "host-mask", host_mask,
Jens Georg's avatar
Jens Georg committed
303
304
                                  "port", port,
                                  NULL);
Jens Georg's avatar
Jens Georg committed
305
306
307
308
309
310
311
312

        if (error) {
                g_warning ("Error creating GUPnP context: %s",
                           error->message);
                g_error_free (error);

                return;
        }
313
        g_hash_table_insert (device->contexts, g_strdup (address), context);
Jens Georg's avatar
Jens Georg committed
314

315
316
317
318
319
        if (device->flags & NETWORK_INTERFACE_UP) {
                g_signal_emit_by_name (device->manager,
                                       "context-available",
                                       context);
        }
Jens Georg's avatar
Jens Georg committed
320
321
322
}

static void
323
324
325
context_signal_up (G_GNUC_UNUSED gpointer key,
                   gpointer               value,
                   gpointer               user_data)
Jens Georg's avatar
Jens Georg committed
326
{
Jens Georg's avatar
Jens Georg committed
327
        g_signal_emit_by_name (user_data, "context-available", value);
Jens Georg's avatar
Jens Georg committed
328
329
330
}

static void
331
332
333
context_signal_down (G_GNUC_UNUSED gpointer key,
                     gpointer               value,
                     gpointer               user_data)
Jens Georg's avatar
Jens Georg committed
334
{
Jens Georg's avatar
Jens Georg committed
335
        g_signal_emit_by_name (user_data, "context-unavailable", value);
Jens Georg's avatar
Jens Georg committed
336
337
338
339
340
341
342
343
344
345
}

static void
network_device_up (NetworkInterface *device)
{
        if (device->flags & NETWORK_INTERFACE_UP)
                return;

        device->flags |= NETWORK_INTERFACE_UP;

Jens Georg's avatar
Jens Georg committed
346
347
348
349
        if (g_hash_table_size (device->contexts) > 0)
                g_hash_table_foreach (device->contexts,
                                      context_signal_up,
                                      device->manager);
Jens Georg's avatar
Jens Georg committed
350
351
352
353
354
}

static void
network_device_down (NetworkInterface *device)
{
Jens Georg's avatar
Jens Georg committed
355
        if (!(device->flags & NETWORK_INTERFACE_UP))
Jens Georg's avatar
Jens Georg committed
356
357
358
359
                return;

        device->flags &= ~NETWORK_INTERFACE_UP;

Jens Georg's avatar
Jens Georg committed
360
        if (device->contexts != NULL)
Jens Georg's avatar
Jens Georg committed
361
362
363
                g_hash_table_foreach (device->contexts,
                                      context_signal_down,
                                      device->manager);
Jens Georg's avatar
Jens Georg committed
364
365
366
367
368
}

static void
network_device_free (NetworkInterface *device)
{
Jens Georg's avatar
Jens Georg committed
369
370
        g_free (device->name);
        g_free (device->essid);
Jens Georg's avatar
Jens Georg committed
371

Jens Georg's avatar
Jens Georg committed
372
373
374
375
376
377
378
379
380
        if (device->contexts != NULL) {
                GHashTableIter iter;
                char *key;
                GUPnPContext *value;

                g_hash_table_iter_init (&iter, device->contexts);
                while (g_hash_table_iter_next (&iter,
                                               (gpointer *) &key,
                                               (gpointer *) &value)) {
Jens Georg's avatar
Jens Georg committed
381
382
383
384
                        g_signal_emit_by_name (device->manager,
                                               "context-unavailable",
                                               value);
                        g_hash_table_iter_remove (&iter);
Jens Georg's avatar
Jens Georg committed
385
                }
Jens Georg's avatar
Jens Georg committed
386
        }
Jens Georg's avatar
Jens Georg committed
387
388
389

        g_hash_table_unref (device->contexts);
        device->contexts = NULL;
390
391

        g_slice_free (NetworkInterface, device);
Jens Georg's avatar
Jens Georg committed
392
393
394
395
396
397
398
399
}


static void query_all_network_interfaces (GUPnPLinuxContextManager *self);
static void query_all_addresses (GUPnPLinuxContextManager *self);
static void receive_netlink_message (GUPnPLinuxContextManager  *self,
                                     GError                   **error);
static void create_context (GUPnPLinuxContextManager *self,
Jens Georg's avatar
Jens Georg committed
400
                            const char               *label,
401
                            const char               *address,
402
                            const char               *mask,
403
404
                            struct ifaddrmsg         *ifa,
                            GInetAddressMask         *host_mask);
Jens Georg's avatar
Jens Georg committed
405
static void remove_context (GUPnPLinuxContextManager *self,
406
                            const char               *address,
Jens Georg's avatar
Jens Georg committed
407
                            const char               *label,
Jens Georg's avatar
Jens Georg committed
408
409
410
                            struct ifaddrmsg         *ifa);

static gboolean
411
412
413
on_netlink_message_available (G_GNUC_UNUSED GSocket     *socket,
                              G_GNUC_UNUSED GIOCondition condition,
                              gpointer                   user_data)
Jens Georg's avatar
Jens Georg committed
414
415
416
417
418
419
420
421
422
423
424
425
426
{
        GUPnPLinuxContextManager *self;

        self = GUPNP_LINUX_CONTEXT_MANAGER (user_data);

        receive_netlink_message (self, NULL);

        return TRUE;
}

#define RT_ATTR_OK(a,l) \
        ((l > 0) && RTA_OK (a, l))

Jens Georg's avatar
Jens Georg committed
427
static void
428
429
extract_info (struct nlmsghdr *header,
              gboolean         dump,
Jens Georg's avatar
Jens Georg committed
430
              sa_family_t      family,
431
              guint8           prefixlen,
432
              char           **address,
433
434
              char           **label,
              char           **mask)
Jens Georg's avatar
Jens Georg committed
435
{
Jens Georg's avatar
Jens Georg committed
436
437
        int rt_attr_len;
        struct rtattr *rt_attr;
Jens Georg's avatar
Jens Georg committed
438
        char buf[INET6_ADDRSTRLEN];
Jens Georg's avatar
Jens Georg committed
439

440
441
        rt_attr = IFA_RTA (NLMSG_DATA (header));
        rt_attr_len = IFA_PAYLOAD (header);
Jens Georg's avatar
Jens Georg committed
442
        while (RT_ATTR_OK (rt_attr, rt_attr_len)) {
Jens Georg's avatar
Jens Georg committed
443
                if (dump) {
Jens Georg's avatar
Jens Georg committed
444
                        dump_rta_attr (family, rt_attr);
Jens Georg's avatar
Jens Georg committed
445
446
                }

Jens Georg's avatar
Jens Georg committed
447
448
                if (rt_attr->rta_type == IFA_LABEL) {
                        *label = g_strdup ((char *) RTA_DATA (rt_attr));
Jens Georg's avatar
Jens Georg committed
449
                } else if (rt_attr->rta_type == IFA_ADDRESS) {
450
                        if (address != NULL) {
451
452
453
454
455
456
                                GInetAddress *addr;

                                *address = NULL;
                                addr = g_inet_address_new_from_bytes (RTA_DATA (rt_attr), family);
                                if (family == AF_INET) {
                                        *address = g_inet_address_to_string (addr);
Jens Georg's avatar
Jens Georg committed
457
                                } else {
458
459
460
461
462
                                        if (g_inet_address_get_is_link_local (addr) ||
                                            g_inet_address_get_is_loopback (addr) ||
                                            g_inet_address_get_is_site_local (addr)) {
                                                *address = g_inet_address_to_string (addr);
                                        }
Jens Georg's avatar
Jens Georg committed
463
                                }
464
                                g_object_unref (addr);
465
                        }
466
467

                        if (mask != NULL) {
Jens Georg's avatar
Jens Georg committed
468
469
470
471
                                struct in6_addr addr = { 0 }, *data;
                                int i = 0, bits = prefixlen;
                                guint8 *outbuf = (guint8 *)&addr.s6_addr;
                                guint8 *inbuf;
472
473

                                data = RTA_DATA (rt_attr);
Jens Georg's avatar
Jens Georg committed
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
                                inbuf = (guint8 *)&data->s6_addr;

                                for (i = 0; i < (family == AF_INET ? 4 : 16); i++) {
                                        if (bits > 8) {
                                                bits -= 8;
                                                outbuf[i] = inbuf[i] & 0xff;
                                        } else {
                                                static const guint8 bits_a[] =
                                                { 0x00, 0x08, 0x0C, 0x0E, 0x0F };

                                                if (bits >= 4) {
                                                        outbuf[i] = inbuf[i] & 0xf0;
                                                        bits -= 4;
                                                }
                                                outbuf[i] = outbuf[i] |
                                                            (inbuf[i] & bits_a[bits]);
                                                break;
                                        }
                                }
493

Jens Georg's avatar
Jens Georg committed
494
                                inet_ntop (family,
495
496
497
498
499
500
                                           &addr,
                                           buf,
                                           sizeof (buf));
                                *mask = g_strdup (buf);

                        }
Jens Georg's avatar
Jens Georg committed
501
502
                }
                rt_attr = RTA_NEXT (rt_attr, rt_attr_len);
Jens Georg's avatar
Jens Georg committed
503
504
505
        }
}

506
507
508
509
static void
extract_link_message_info (struct nlmsghdr *header,
                           char           **ifname,
                           gboolean        *is_wifi)
Jens Georg's avatar
Jens Georg committed
510
511
512
{
        int rt_attr_len;
        struct rtattr *rt_attr;
513
514
        *ifname = NULL;
        *is_wifi = FALSE;
Jens Georg's avatar
Jens Georg committed
515
516
517
518

        rt_attr = IFLA_RTA (NLMSG_DATA (header));
        rt_attr_len = IFLA_PAYLOAD (header);
        while (RT_ATTR_OK (rt_attr, rt_attr_len)) {
519
520
521
522
523
524
525
526
527
528
529
                switch (rt_attr->rta_type)
                {
                case IFLA_WIRELESS:
                        *is_wifi = TRUE;
                        break;
                case IFLA_IFNAME:
                        *ifname = g_strdup ((const char *) RTA_DATA(rt_attr));
                        break;
                default:
                        break;
                }
Jens Georg's avatar
Jens Georg committed
530
531
532
533
534

                rt_attr = RTA_NEXT (rt_attr, rt_attr_len);
        }
}

535
static void
Jens Georg's avatar
Jens Georg committed
536
create_context (GUPnPLinuxContextManager *self,
537
                const char               *address,
Jens Georg's avatar
Jens Georg committed
538
                const char               *label,
539
                const char               *mask,
540
541
                struct ifaddrmsg         *ifa,
                GInetAddressMask         *host_mask)
Jens Georg's avatar
Jens Georg committed
542
543
{
        NetworkInterface *device;
Jens Georg's avatar
Jens Georg committed
544
        GUPnPLinuxContextManagerPrivate *priv;
Jens Georg's avatar
Jens Georg committed
545

Jens Georg's avatar
Jens Georg committed
546
547
        priv = gupnp_linux_context_manager_get_instance_private (self);
        device = g_hash_table_lookup (priv->interfaces,
Jens Georg's avatar
Jens Georg committed
548
                                      GINT_TO_POINTER (ifa->ifa_index));
Jens Georg's avatar
Jens Georg committed
549

Jens Georg's avatar
Jens Georg committed
550
551
552
553
554
555
556
557
558
        if (!device) {
                g_warning ("Got new address for device %d but device is"
                           " not active",
                           ifa->ifa_index);

                return;
        }

        /* If device isn't one we consider, silently skip address */
Jens Georg's avatar
Jens Georg committed
559
        if (device->flags & NETWORK_INTERFACE_IGNORE)
Jens Georg's avatar
Jens Georg committed
560
561
                return;

562
563
564
        network_device_create_context (device,
                                       address,
                                       label,
565
566
                                       mask,
                                       host_mask);
Jens Georg's avatar
Jens Georg committed
567
568
}

569
static void
Jens Georg's avatar
Jens Georg committed
570
remove_context (GUPnPLinuxContextManager *self,
571
                const char               *address,
Jens Georg's avatar
Jens Georg committed
572
573
                const char               *label,
                struct ifaddrmsg         *ifa)
Jens Georg's avatar
Jens Georg committed
574
575
{
        NetworkInterface *device;
Jens Georg's avatar
Jens Georg committed
576
        GUPnPContext *context;
Jens Georg's avatar
Jens Georg committed
577
        GUPnPLinuxContextManagerPrivate *priv;
Jens Georg's avatar
Jens Georg committed
578

Jens Georg's avatar
Jens Georg committed
579
580
        priv = gupnp_linux_context_manager_get_instance_private (self);
        device = g_hash_table_lookup (priv->interfaces,
Jens Georg's avatar
Jens Georg committed
581
582
                                      GINT_TO_POINTER (ifa->ifa_index));

583
584
585
586
        if (!device) {
                g_debug ("Device with index %d not found, ignoring",
                         ifa->ifa_index);

587
                return;
588
        }
589

590
        context = g_hash_table_lookup (device->contexts, address);
Jens Georg's avatar
Jens Georg committed
591
592
        if (context) {
                if (device->flags & NETWORK_INTERFACE_UP) {
Jens Georg's avatar
Jens Georg committed
593
594
                        g_signal_emit_by_name (self,
                                               "context-unavailable",
Jens Georg's avatar
Jens Georg committed
595
596
                                               context);
                }
597
598
599
600
                g_hash_table_remove (device->contexts, address);
        } else {
                g_debug ("Failed to find context with address %s",
                         address);
Jens Georg's avatar
Jens Georg committed
601
602
        }

Jens Georg's avatar
Jens Georg committed
603
604
        if (g_hash_table_size (device->contexts) == 0)
                device->flags &= ~NETWORK_INTERFACE_PRECONFIGURED;
Jens Georg's avatar
Jens Georg committed
605
606
607
608
609
610
611
612
613
614
615
616
617
}

/* Idle-handler for initial interface and address bootstrapping.
 *
 * We cannot send the RTM_GETADDR message until we processed all packets of
 * the RTM_GETLINK message. So on the first call this idle handler processes
 * all answers of RTM_GETLINK on the second call all answers of RTM_GETADDR
 * and on the third call it creates the regular socket source for listening on
 * the netlink socket, detaching itself from the idle source afterwards.
 */
static gboolean
on_bootstrap (GUPnPLinuxContextManager *self)
{
Jens Georg's avatar
Jens Georg committed
618
619
620
621
        GUPnPLinuxContextManagerPrivate *priv;

        priv = gupnp_linux_context_manager_get_instance_private (self);
        if (priv->nl_seq == 0) {
Jens Georg's avatar
Jens Georg committed
622
623
624
                query_all_network_interfaces (self);

                return TRUE;
Jens Georg's avatar
Jens Georg committed
625
        } else if (priv->nl_seq == 1) {
Jens Georg's avatar
Jens Georg committed
626
627
628
629
                query_all_addresses (self);

                return TRUE;
        } else {
Jens Georg's avatar
Jens Georg committed
630
631
                priv->netlink_socket_source = g_socket_create_source
                                                (priv->netlink_socket,
Jens Georg's avatar
Jens Georg committed
632
633
634
                                                 G_IO_IN | G_IO_PRI,
                                                 NULL);

Jens Georg's avatar
Jens Georg committed
635
                g_source_attach (priv->netlink_socket_source,
Jens Georg's avatar
Jens Georg committed
636
                                 g_main_context_get_thread_default ());
Jens Georg's avatar
Jens Georg committed
637

Jens Georg's avatar
Jens Georg committed
638
                g_source_set_callback (priv->netlink_socket_source,
Jens Georg's avatar
Jens Georg committed
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
                                       (GSourceFunc)
                                       on_netlink_message_available,
                                       self,
                                       NULL);
        }

        return FALSE;
}

struct nl_req_s {
        struct nlmsghdr hdr;
        struct rtgenmsg gen;
};

static void
send_netlink_request (GUPnPLinuxContextManager *self,
                      guint netlink_message,
                      guint flags)
{
        struct nl_req_s req;
        struct sockaddr_nl dest;
        struct msghdr msg;
        struct iovec io;
        int fd;
Jens Georg's avatar
Jens Georg committed
663
664
665
        GUPnPLinuxContextManagerPrivate *priv;

        priv = gupnp_linux_context_manager_get_instance_private (self);
Jens Georg's avatar
Jens Georg committed
666
667
668
669
670
671
672

        memset (&req, 0, sizeof (req));
        memset (&dest, 0, sizeof (dest));
        memset (&msg, 0, sizeof (msg));

        dest.nl_family = AF_NETLINK;
        req.hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtgenmsg));
Jens Georg's avatar
Jens Georg committed
673
        req.hdr.nlmsg_seq = priv->nl_seq++;
Jens Georg's avatar
Jens Georg committed
674
675
        req.hdr.nlmsg_type = netlink_message;
        req.hdr.nlmsg_flags = NLM_F_REQUEST | flags;
676
        req.gen.rtgen_family = gupnp_context_manager_get_socket_family (GUPNP_CONTEXT_MANAGER (self));
Jens Georg's avatar
Jens Georg committed
677
678
679
680
681
682
683
684
685

        io.iov_base = &req;
        io.iov_len = req.hdr.nlmsg_len;

        msg.msg_iov = &io;
        msg.msg_iovlen = 1;
        msg.msg_name = &dest;
        msg.msg_namelen = sizeof (dest);

Jens Georg's avatar
Jens Georg committed
686
        fd = g_socket_get_fd (priv->netlink_socket);
Jens Georg's avatar
Jens Georg committed
687
688
689
690
691
        if (sendmsg (fd, (struct msghdr *) &msg, 0) < 0)
                g_warning ("Could not send netlink message: %s",
                           strerror (errno));
}

Jens Georg's avatar
Jens Georg committed
692
693
/* Query all available interfaces and immediately process all answers. We need
 * to do this to be able to send RTM_GETADDR in the next step */
Jens Georg's avatar
Jens Georg committed
694
695
696
697
698
static void
query_all_network_interfaces (GUPnPLinuxContextManager *self)
{
        GError *error = NULL;

Jens Georg's avatar
Jens Georg committed
699
700
        g_debug ("Bootstrap: Querying all interfaces");

Jens Georg's avatar
Jens Georg committed
701
702
703
704
        send_netlink_request (self, RTM_GETLINK, NLM_F_DUMP);
        do {
                receive_netlink_message (self, &error);
        } while (error == NULL);
705
706

        g_error_free (error);
Jens Georg's avatar
Jens Georg committed
707
708
}

Jens Georg's avatar
Jens Georg committed
709
710
/* Start query of all currenly available network addresses. The answer will be
 * processed by the normal netlink socket source call-back. */
Jens Georg's avatar
Jens Georg committed
711
712
713
static void
query_all_addresses (GUPnPLinuxContextManager *self)
{
Jens Georg's avatar
Jens Georg committed
714
        g_debug ("Bootstrap: Querying all addresses");
Jens Georg's avatar
Jens Georg committed
715
716
        send_netlink_request (self,
                              RTM_GETADDR,
Jens Georg's avatar
Jens Georg committed
717
                              NLM_F_DUMP);
Jens Georg's avatar
Jens Georg committed
718
719
720
721
722
723
724
}

/* Ignore non-multicast device, except loop-back and P-t-P devices */
#define INTERFACE_IS_VALID(ifi) \
        (((ifi)->ifi_flags & (IFF_MULTICAST | IFF_LOOPBACK)) && \
         !((ifi)->ifi_flags & IFF_POINTOPOINT))

Jens Georg's avatar
Jens Georg committed
725
/* Handle status changes (up, down, new address, ...) on network interfaces */
Jens Georg's avatar
Jens Georg committed
726
727
static void
handle_device_status_change (GUPnPLinuxContextManager *self,
728
                             char                     *name,
Jens Georg's avatar
Jens Georg committed
729
730
731
732
                             struct ifinfomsg         *ifi)
{
        gpointer key;
        NetworkInterface *device;
Jens Georg's avatar
Jens Georg committed
733
734
735
        GUPnPLinuxContextManagerPrivate *priv;

        priv = gupnp_linux_context_manager_get_instance_private (self);
Jens Georg's avatar
Jens Georg committed
736
737

        key = GINT_TO_POINTER (ifi->ifi_index);
Jens Georg's avatar
Jens Georg committed
738
        device = g_hash_table_lookup (priv->interfaces,
Jens Georg's avatar
Jens Georg committed
739
740
741
742
743
744
745
746
747
748
749
                                      key);

        if (device != NULL) {
                if (ifi->ifi_flags & IFF_UP)
                        network_device_up (device);
                else
                        network_device_down (device);

                return;
        }

750
        device = network_device_new (self, name, ifi->ifi_index);
Jens Georg's avatar
Jens Georg committed
751
752
753
754
755
756
        if (device) {
                if (!INTERFACE_IS_VALID (ifi))
                        device->flags |= NETWORK_INTERFACE_IGNORE;
                if (ifi->ifi_flags & IFF_UP)
                        device->flags |= NETWORK_INTERFACE_UP;

Jens Georg's avatar
Jens Georg committed
757
                g_hash_table_insert (priv->interfaces,
Jens Georg's avatar
Jens Georg committed
758
759
760
761
762
763
764
765
766
                                     key,
                                     device);
        }
}

static void
remove_device (GUPnPLinuxContextManager *self,
               struct ifinfomsg         *ifi)
{
Jens Georg's avatar
Jens Georg committed
767
768
769
770
771
        GUPnPLinuxContextManagerPrivate *priv;

        priv = gupnp_linux_context_manager_get_instance_private (self);

        g_hash_table_remove (priv->interfaces,
Jens Georg's avatar
Jens Georg committed
772
773
774
775
776
777
                             GINT_TO_POINTER (ifi->ifi_index));
}

#define NLMSG_IS_VALID(msg,len) \
        (NLMSG_OK(msg,len) && (msg->nlmsg_type != NLMSG_DONE))

Jens Georg's avatar
Jens Georg committed
778
779
/* Process the raw netlink message and dispatch to helper functions
 * accordingly */
Jens Georg's avatar
Jens Georg committed
780
781
782
static void
receive_netlink_message (GUPnPLinuxContextManager *self, GError **error)
{
783
        gssize len;
Jens Georg's avatar
Jens Georg committed
784
        GError *inner_error = NULL;
785
        struct nlmsghdr *header;
Jens Georg's avatar
Jens Georg committed
786
787
        struct ifinfomsg *ifi;
        struct ifaddrmsg *ifa;
Jens Georg's avatar
Jens Georg committed
788
789
790
        GUPnPLinuxContextManagerPrivate *priv;

        priv = gupnp_linux_context_manager_get_instance_private (self);
Jens Georg's avatar
Jens Georg committed
791

792
793
        header = (struct nlmsghdr *) priv->recvbuf;

Jens Georg's avatar
Jens Georg committed
794
        len = g_socket_receive (priv->netlink_socket,
795
796
                                priv->recvbuf,
                                sizeof (priv->recvbuf),
Jens Georg's avatar
Jens Georg committed
797
798
799
800
801
802
803
804
805
806
807
                                NULL,
                                &inner_error);
        if (len == -1) {
                if (inner_error->code != G_IO_ERROR_WOULD_BLOCK)
                        g_warning ("Error receiving netlink message: %s",
                                   inner_error->message);
                g_propagate_error (error, inner_error);

                return;
        }

Jens Georg's avatar
Jens Georg committed
808
        if (priv->dump_netlink_packets) {
Jens Georg's avatar
Jens Georg committed
809
810
811
812
                GString *hexdump = NULL;

                /* We should have at most len / 16 + 1 lines with 74 characters each */
                hexdump = g_string_new_len (NULL, ((len / 16) + 1) * 73);
813
814
                gupnp_linux_context_manager_hexdump ((guint8 *) priv->recvbuf,
                                                     len, hexdump);
Jens Georg's avatar
Jens Georg committed
815
816
817
818
819

                g_debug ("Netlink packet dump:\n%s", hexdump->str);
                g_string_free (hexdump, TRUE);
        }

Jens Georg's avatar
Jens Georg committed
820
821
822
823
824
825
826
827
828
829
830
        for (;NLMSG_IS_VALID (header, len); header = NLMSG_NEXT (header,len)) {
                switch (header->nlmsg_type) {
                        /* RTM_NEWADDR and RTM_DELADDR are sent on real address
                         * changes.
                         * RTM_NEWLINK is sent on varous occations:
                         *  - Creation of a new device
                         *  - Device goes up/down
                         *  - Wireless status changes
                         * RTM_DELLINK is sent only if device is removed, like
                         * openvpn --rmtun /dev/tun0, NOT on ifconfig down. */
                        case RTM_NEWADDR:
Jens Georg's avatar
Jens Georg committed
831
832
                            {
                                char *label = NULL;
833
                                char *address = NULL;
834
                                char *mask = NULL;
Jens Georg's avatar
Jens Georg committed
835

Jens Georg's avatar
Jens Georg committed
836
                                g_debug ("Received RTM_NEWADDR");
Jens Georg's avatar
Jens Georg committed
837
                                ifa = NLMSG_DATA (header);
Jens Georg's avatar
Jens Georg committed
838

Jens Georg's avatar
Jens Georg committed
839
                                extract_info (header,
Jens Georg's avatar
Jens Georg committed
840
                                              priv->dump_netlink_packets,
Jens Georg's avatar
Jens Georg committed
841
                                              ifa->ifa_family,
842
                                              ifa->ifa_prefixlen,
843
                                              &address,
844
845
                                              &label,
                                              &mask);
Jens Georg's avatar
Jens Georg committed
846

847
848
849
850
851
852
853
854
855
856
857
858
859
                                if (address != NULL) {
                                        GInetAddress *mask_addr;
                                        GInetAddressMask *host_mask;

                                        mask_addr = g_inet_address_new_from_string (mask);
                                        host_mask = g_inet_address_mask_new (mask_addr,
                                                                             ifa->ifa_prefixlen,
                                                                             NULL);
                                        create_context (self, address, label, mask, ifa, host_mask);

                                        g_object_unref (host_mask);
                                        g_object_unref (mask_addr);
                                }
Jens Georg's avatar
Jens Georg committed
860
                                g_free (label);
861
                                g_free (address);
862
                                g_free (mask);
Jens Georg's avatar
Jens Georg committed
863
864
                            }
                            break;
Jens Georg's avatar
Jens Georg committed
865
                        case RTM_DELADDR:
Jens Georg's avatar
Jens Georg committed
866
867
                            {
                                char *label = NULL;
868
                                char *address = NULL;
Jens Georg's avatar
Jens Georg committed
869

Jens Georg's avatar
Jens Georg committed
870
                                g_debug ("Received RTM_DELADDR");
Jens Georg's avatar
Jens Georg committed
871
                                ifa = NLMSG_DATA (header);
Jens Georg's avatar
Jens Georg committed
872
873
874
875
876
877
878

                                if (ifa->ifa_family == AF_INET6 &&
                                        (ifa->ifa_scope != RT_SCOPE_SITE &&
                                         ifa->ifa_scope != RT_SCOPE_LINK &&
                                         ifa->ifa_scope != RT_SCOPE_HOST))
                                        break;

Jens Georg's avatar
Jens Georg committed
879
                                extract_info (header,
Jens Georg's avatar
Jens Georg committed
880
                                              priv->dump_netlink_packets,
Jens Georg's avatar
Jens Georg committed
881
                                              ifa->ifa_family,
882
                                              ifa->ifa_prefixlen,
883
                                              &address,
884
885
                                              &label,
                                              NULL);
Jens Georg's avatar
Jens Georg committed
886
887
                                if (address != NULL)
                                        remove_context (self, address, label, ifa);
Jens Georg's avatar
Jens Georg committed
888
                                g_free (label);
889
                                g_free (address);
Jens Georg's avatar
Jens Georg committed
890
891
                            }
                            break;
892
893
894
895
                        case RTM_NEWLINK: {
                                char *name = NULL;
                                gboolean is_wireless_status_message = FALSE;

Jens Georg's avatar
Jens Georg committed
896
                                g_debug ("Received RTM_NEWLINK");
Jens Georg's avatar
Jens Georg committed
897
                                ifi = NLMSG_DATA (header);
898
899
900
                                extract_link_message_info (header,
                                                           &name,
                                                           &is_wireless_status_message);
Jens Georg's avatar
Jens Georg committed
901
902

                                /* Check if wireless is up for chit-chat */
903
904
905
                                if (is_wireless_status_message) {
                                        g_free (name);

Jens Georg's avatar
Jens Georg committed
906
                                        continue;
907
908
909
                                }

                                handle_device_status_change (self, name, ifi);
Jens Georg's avatar
Jens Georg committed
910
                                break;
911
                        }
Jens Georg's avatar
Jens Georg committed
912
                        case RTM_DELLINK:
Jens Georg's avatar
Jens Georg committed
913
                                g_debug ("Received RTM_DELLINK");
Jens Georg's avatar
Jens Georg committed
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
                                ifi = NLMSG_DATA (header);
                                remove_device (self, ifi);
                                break;
                        case NLMSG_ERROR:
                                break;
                        default:
                                break;
                }
        }
}

/* Create INET socket used for SIOCGIFNAME and SIOCGIWESSID ioctl
 * calls */
static gboolean
create_ioctl_socket (GUPnPLinuxContextManager *self, GError **error)
{
Jens Georg's avatar
Jens Georg committed
930
        GUPnPLinuxContextManagerPrivate *priv;
Jens Georg's avatar
Jens Georg committed
931

Jens Georg's avatar
Jens Georg committed
932
933
934
935
936
937
        priv = gupnp_linux_context_manager_get_instance_private (self);

        priv->fd = socket (AF_INET, SOCK_DGRAM, 0);

        if (priv->fd < 0) {
                priv->fd = 0;
Jens Georg's avatar
Jens Georg committed
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957

                g_set_error_literal (error,
                                     G_IO_ERROR,
                                     g_io_error_from_errno (errno),
                                     "Failed to setup socket for ioctl");

                return FALSE;
        }

        return TRUE;
}

/* Create a netlink socket, bind to it and wrap it in a GSocket */
static gboolean
create_netlink_socket (GUPnPLinuxContextManager *self, GError **error)
{
        struct sockaddr_nl sa;
        int fd, status;
        GSocket *sock;
        GError *inner_error;
Jens Georg's avatar
Jens Georg committed
958
959
960
        GUPnPLinuxContextManagerPrivate *priv;

        priv = gupnp_linux_context_manager_get_instance_private (self);
Jens Georg's avatar
Jens Georg committed
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975

        inner_error = NULL;

        fd = socket (PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
        if (fd == -1) {
                g_set_error_literal (error,
                                     G_IO_ERROR,
                                     g_io_error_from_errno (errno),
                                     "Failed to bind to netlink socket");
                return FALSE;
        }

        memset (&sa, 0, sizeof (sa));
        sa.nl_family = AF_NETLINK;
        /* Listen for interface changes and IP address changes */
976
977
978
979
980
981
982
983
        sa.nl_groups = RTMGRP_LINK;
        if (gupnp_context_manager_get_socket_family (GUPNP_CONTEXT_MANAGER (self)) == G_SOCKET_FAMILY_INVALID) {
                sa.nl_groups = RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_IFADDR;
        } else if (gupnp_context_manager_get_socket_family (GUPNP_CONTEXT_MANAGER (self)) == G_SOCKET_FAMILY_IPV4) {
                sa.nl_groups = RTMGRP_IPV4_IFADDR;
        } else if (gupnp_context_manager_get_socket_family (GUPNP_CONTEXT_MANAGER (self)) == G_SOCKET_FAMILY_IPV6) {
                sa.nl_groups = RTMGRP_IPV6_IFADDR;
        }
Jens Georg's avatar
Jens Georg committed
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000

        status = bind (fd, (struct sockaddr *) &sa, sizeof (sa));
        if (status == -1) {
                g_set_error_literal (error,
                                     G_IO_ERROR,
                                     g_io_error_from_errno (errno),
                                     "Failed to bind to netlink socket");
                close (fd);

                return FALSE;
        }

        sock = g_socket_new_from_fd (fd, &inner_error);
        if (sock == NULL) {
                close (fd);
                g_propagate_prefixed_error (error,
                                            inner_error,