summaryrefslogtreecommitdiffstats
path: root/avahi-core/iface-linux.c
blob: 98fdea561cba6a2975974f61f0032c0b28cb66e6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
/* $Id$ */

/***
  This file is part of avahi.

  avahi is free software; you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation; either version 2.1 of the
  License, or (at your option) any later version.

  avahi 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 avahi; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <net/if.h>
#include <errno.h>
#include <string.h>

#include <avahi-common/malloc.h>

#include "log.h"
#include "iface.h"
#include "iface-linux.h"

#ifndef IFLA_RTA
#include <linux/if_addr.h>
#define IFLA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
#endif

#ifndef IFA_RTA
#include <linux/if_addr.h>
#define IFA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
#endif

static int netlink_list_items(AvahiNetlink *nl, uint16_t type, unsigned *ret_seq) {
    struct nlmsghdr *n;
    struct rtgenmsg *gen;
    uint8_t req[1024];

    /* Issue a wild dump NETLINK request */

    memset(&req, 0, sizeof(req));
    n = (struct nlmsghdr*) req;
    n->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
    n->nlmsg_type = type;
    n->nlmsg_flags = NLM_F_ROOT|NLM_F_REQUEST;
    n->nlmsg_pid = 0;

    gen = NLMSG_DATA(n);
    memset(gen, 0, sizeof(struct rtgenmsg));
    gen->rtgen_family = AF_UNSPEC;

    return avahi_netlink_send(nl, n, ret_seq);
}

static void netlink_callback(AvahiNetlink *nl, struct nlmsghdr *n, void* userdata) {
    AvahiInterfaceMonitor *m = userdata;

    /* This routine is called for every RTNETLINK response packet */

    assert(m);
    assert(n);
    assert(m->osdep.netlink == nl);

    if (n->nlmsg_type == RTM_NEWLINK) {

        /* A new interface appeared or an existing one has been modified */

        struct ifinfomsg *ifinfomsg = NLMSG_DATA(n);
        AvahiHwInterface *hw;
        struct rtattr *a = NULL;
        size_t l;

        /* A (superfluous?) sanity check */
        if (ifinfomsg->ifi_family != AF_UNSPEC)
            return;

        /* Check whether there already is an AvahiHwInterface object
         * for this link, so that we can update its data. Note that
         * Netlink sends us an RTM_NEWLINK not only when a new
         * interface appears, but when it changes, too */

        if (!(hw = avahi_interface_monitor_get_hw_interface(m, ifinfomsg->ifi_index)))

            /* No object found, so let's create a new
             * one. avahi_hw_interface_new() will call
             * avahi_interface_new() internally twice for IPv4 and
             * IPv6, so there is no need for us to do that
             * ourselves */
            if (!(hw = avahi_hw_interface_new(m, (AvahiIfIndex) ifinfomsg->ifi_index)))
                return; /* OOM */

        /* Check whether the flags of this interface are OK for us */
        hw->flags_ok =
            (ifinfomsg->ifi_flags & IFF_UP) &&
            (!m->server->config.use_iff_running || (ifinfomsg->ifi_flags & IFF_RUNNING)) &&
            !(ifinfomsg->ifi_flags & IFF_LOOPBACK) &&
            (ifinfomsg->ifi_flags & IFF_MULTICAST) &&
            (m->server->config.allow_point_to_point || !(ifinfomsg->ifi_flags & IFF_POINTOPOINT));

        /* Handle interface attributes */
        l = NLMSG_PAYLOAD(n, sizeof(struct ifinfomsg));
        a = IFLA_RTA(ifinfomsg);

        while (RTA_OK(a, l)) {
            switch(a->rta_type) {
                case IFLA_IFNAME:

                    /* Fill in interface name */
                    avahi_free(hw->name);
                    hw->name = avahi_strndup(RTA_DATA(a), RTA_PAYLOAD(a));
                    break;

                case IFLA_MTU:

                    /* Fill in MTU */
                    assert(RTA_PAYLOAD(a) == sizeof(unsigned int));
                    hw->mtu = *((unsigned int*) RTA_DATA(a));
                    break;

                case IFLA_ADDRESS:

                    /* Fill in hardware (MAC) address */
                    hw->mac_address_size = RTA_PAYLOAD(a);
                    if (hw->mac_address_size > AVAHI_MAC_ADDRESS_MAX)
                        hw->mac_address_size = AVAHI_MAC_ADDRESS_MAX;

                    memcpy(hw->mac_address, RTA_DATA(a), hw->mac_address_size);
                    break;

                default:
                    ;
            }

            a = RTA_NEXT(a, l);
        }

        /* Check whether this interface is now "relevant" for us. If
         * it is Avahi will start to announce its records on this
         * interface and send out queries for subscribed records on
         * it */
        avahi_hw_interface_check_relevant(hw);

        /* Update any associated RRs of this interface. (i.e. the
         * _workstation._tcp record containing the MAC address) */
        avahi_hw_interface_update_rrs(hw, 0);

    } else if (n->nlmsg_type == RTM_DELLINK) {

        /* An interface has been removed */

        struct ifinfomsg *ifinfomsg = NLMSG_DATA(n);
        AvahiHwInterface *hw;

        /* A (superfluous?) sanity check */
        if (ifinfomsg->ifi_family != AF_UNSPEC)
            return;

        /* Get a reference to our AvahiHwInterface object of this interface */
        if (!(hw = avahi_interface_monitor_get_hw_interface(m, (AvahiIfIndex) ifinfomsg->ifi_index)))
            return;

        /* Free our object */
        avahi_hw_interface_free(hw, 0);

    } else if (n->nlmsg_type == RTM_NEWADDR || n->nlmsg_type == RTM_DELADDR) {

        /* An address has been added, modified or removed */

        struct ifaddrmsg *ifaddrmsg = NLMSG_DATA(n);
        AvahiInterface *i;
        struct rtattr *a = NULL;
        size_t l;
        AvahiAddress raddr;
        int raddr_valid = 0;

        /* We are only interested in IPv4 and IPv6 */
        if (ifaddrmsg->ifa_family != AF_INET && ifaddrmsg->ifa_family != AF_INET6)
            return;

        /* Try to get a reference to our AvahiInterface object for the
         * interface this address is assigned to. If ther is no object
         * for this interface, we ignore this address. */
        if (!(i = avahi_interface_monitor_get_interface(m, (AvahiIfIndex) ifaddrmsg->ifa_index, avahi_af_to_proto(ifaddrmsg->ifa_family))))
            return;

        /* Fill in address family for our new address */
        raddr.proto = avahi_af_to_proto(ifaddrmsg->ifa_family);

        l = NLMSG_PAYLOAD(n, sizeof(struct ifaddrmsg));
        a = IFA_RTA(ifaddrmsg);

        while (RTA_OK(a, l)) {

            switch(a->rta_type) {
                case IFA_ADDRESS:
                    /* Fill in address data */

                    if ((raddr.proto == AVAHI_PROTO_INET6 && RTA_PAYLOAD(a) != 16) ||
                        (raddr.proto == AVAHI_PROTO_INET && RTA_PAYLOAD(a) != 4))
                        return;

                    memcpy(raddr.data.data, RTA_DATA(a), RTA_PAYLOAD(a));
                    raddr_valid = 1;

                    break;

                default:
                    ;
            }

            a = RTA_NEXT(a, l);
        }

        /* If there was no adress attached to this message, let's quit. */
        if (!raddr_valid)
            return;

        if (n->nlmsg_type == RTM_NEWADDR) {
            AvahiInterfaceAddress *addr;

            /* This address is new or has been modified, so let's get an object for it */
            if (!(addr = avahi_interface_monitor_get_address(m, i, &raddr)))

                /* Mmm, no object existing yet, so let's create a new one */
                if (!(addr = avahi_interface_address_new(m, i, &raddr, ifaddrmsg->ifa_prefixlen)))
                    return; /* OOM */

            /* Update the scope field for the address */
            addr->global_scope = ifaddrmsg->ifa_scope == RT_SCOPE_UNIVERSE || ifaddrmsg->ifa_scope == RT_SCOPE_SITE;
        } else {
            AvahiInterfaceAddress *addr;
            assert(n->nlmsg_type == RTM_DELADDR);

            /* Try to get a reference to our AvahiInterfaceAddress object for this address */
            if (!(addr = avahi_interface_monitor_get_address(m, i, &raddr)))
                return;

            /* And free it */
            avahi_interface_address_free(addr);
        }

        /* Avahi only considers interfaces with at least one address
         * attached relevant. Since we migh have added or removed an
         * address, let's have it check again whether the interface is
         * now relevant */
        avahi_interface_check_relevant(i);

        /* Update any associated RRs, like A or AAAA for our new/removed address */
        avahi_interface_update_rrs(i, 0);

    } else if (n->nlmsg_type == NLMSG_DONE) {

        /* This wild dump request ended, so let's see what we do next */

        if (m->osdep.list == LIST_IFACE) {

            /* Mmmm, interfaces have been wild dumped already, so
             * let's go on with wild dumping the addresses */

            if (netlink_list_items(m->osdep.netlink, RTM_GETADDR, &m->osdep.query_addr_seq) < 0) {
                avahi_log_warn("NETLINK: Failed to list addrs: %s", strerror(errno));
                m->osdep.list = LIST_DONE;
            } else

                /* Update state information */
                m->osdep.list = LIST_ADDR;

        } else
            /* We're done. Tell avahi_interface_monitor_sync() to finish. */
            m->osdep.list = LIST_DONE;

        if (m->osdep.list == LIST_DONE) {

            /* Only after this boolean variable has been set, Avahi
             * will start to announce or browse on all interfaces. It
             * is originaly set to 0, which means that relevancy
             * checks and RR updates are disabled during the wild
             * dumps. */
            m->list_complete = 1;

            /* So let's check if any interfaces are relevant now */
            avahi_interface_monitor_check_relevant(m);

            /* And update all RRs attached to any interface */
            avahi_interface_monitor_update_rrs(m, 0);

            /* Tell the user that the wild dump is complete */
            avahi_log_info("Network interface enumeration completed.");
        }

    } else if (n->nlmsg_type == NLMSG_ERROR &&
               (n->nlmsg_seq == m->osdep.query_link_seq || n->nlmsg_seq == m->osdep.query_addr_seq)) {
        struct nlmsgerr *e = NLMSG_DATA (n);

        /* Some kind of error happened. Let's just tell the user and
         * ignore it otherwise */

        if (e->error)
            avahi_log_warn("NETLINK: Failed to browse: %s", strerror(-e->error));
    }
}

int avahi_interface_monitor_init_osdep(AvahiInterfaceMonitor *m) {
    assert(m);

    /* Initialize our own data */

    m->osdep.netlink = NULL;
    m->osdep.query_addr_seq = m->osdep.query_link_seq = 0;

    /* Create a netlink object for us. It abstracts some things and
     * makes netlink easier to use. It will attach to the main loop
     * for us and call netlink_callback() whenever an event
     * happens. */
    if (!(m->osdep.netlink = avahi_netlink_new(m->server->poll_api, RTMGRP_LINK|RTMGRP_IPV4_IFADDR|RTMGRP_IPV6_IFADDR, netlink_callback, m)))
        goto fail;

    /* Set the initial state. */
    m->osdep.list = LIST_IFACE;

    /* Start the wild dump for the interfaces */
    if (netlink_list_items(m->osdep.netlink, RTM_GETLINK, &m->osdep.query_link_seq) < 0)
        goto fail;

    return 0;

fail:

    if (m->osdep.netlink) {
        avahi_netlink_free(m->osdep.netlink);
        m->osdep.netlink = NULL;
    }

    return -1;
}

void avahi_interface_monitor_free_osdep(AvahiInterfaceMonitor *m) {
    assert(m);

    if (m->osdep.netlink) {
        avahi_netlink_free(m->osdep.netlink);
        m->osdep.netlink = NULL;
    }
}

void avahi_interface_monitor_sync(AvahiInterfaceMonitor *m) {
    assert(m);

    /* Let's handle netlink events until we are done with wild
     * dumping */

    while (!m->list_complete)
        if (!avahi_netlink_work(m->osdep.netlink, 1) == 0)
            break;

    /* At this point Avahi knows about all local interfaces and
     * addresses in existance. */
}