1/***
2  This file is part of avahi.
3
4  avahi is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Lesser General Public License as
6  published by the Free Software Foundation; either version 2.1 of the
7  License, or (at your option) any later version.
8
9  avahi is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
12  Public License for more details.
13
14  You should have received a copy of the GNU Lesser General Public
15  License along with avahi; if not, write to the Free Software
16  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17  USA.
18***/
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include <string.h>
25#include <net/if.h>
26#include <errno.h>
27#include <string.h>
28
29#include <avahi-common/malloc.h>
30
31#include "log.h"
32#include "iface.h"
33#include "iface-linux.h"
34
35#ifndef IFLA_RTA
36#include <linux/if_addr.h>
37#define IFLA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
38#endif
39
40#ifndef IFA_RTA
41#include <linux/if_addr.h>
42#define IFA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
43#endif
44
45static int netlink_list_items(AvahiNetlink *nl, uint16_t type, unsigned *ret_seq) {
46    struct nlmsghdr *n;
47    struct rtgenmsg *gen;
48    uint8_t req[1024];
49
50    /* Issue a wild dump NETLINK request */
51
52    memset(&req, 0, sizeof(req));
53    n = (struct nlmsghdr*) req;
54    n->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
55    n->nlmsg_type = type;
56    n->nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP;
57    n->nlmsg_pid = 0;
58
59    gen = NLMSG_DATA(n);
60    memset(gen, 0, sizeof(struct rtgenmsg));
61    gen->rtgen_family = AF_UNSPEC;
62
63    return avahi_netlink_send(nl, n, ret_seq);
64}
65
66static void netlink_callback(AvahiNetlink *nl, struct nlmsghdr *n, void* userdata) {
67    AvahiInterfaceMonitor *m = userdata;
68
69    /* This routine is called for every RTNETLINK response packet */
70
71    assert(m);
72    assert(n);
73    assert(m->osdep.netlink == nl);
74
75    if (n->nlmsg_type == RTM_NEWLINK) {
76
77        /* A new interface appeared or an existing one has been modified */
78
79        struct ifinfomsg *ifinfomsg = NLMSG_DATA(n);
80        AvahiHwInterface *hw;
81        struct rtattr *a = NULL;
82        size_t l;
83
84        /* A (superfluous?) sanity check */
85        if (ifinfomsg->ifi_family != AF_UNSPEC)
86            return;
87
88        /* Check whether there already is an AvahiHwInterface object
89         * for this link, so that we can update its data. Note that
90         * Netlink sends us an RTM_NEWLINK not only when a new
91         * interface appears, but when it changes, too */
92
93        if (!(hw = avahi_interface_monitor_get_hw_interface(m, ifinfomsg->ifi_index)))
94
95            /* No object found, so let's create a new
96             * one. avahi_hw_interface_new() will call
97             * avahi_interface_new() internally twice for IPv4 and
98             * IPv6, so there is no need for us to do that
99             * ourselves */
100            if (!(hw = avahi_hw_interface_new(m, (AvahiIfIndex) ifinfomsg->ifi_index)))
101                return; /* OOM */
102
103        /* Check whether the flags of this interface are OK for us */
104        hw->flags_ok =
105            (ifinfomsg->ifi_flags & IFF_UP) &&
106            (!m->server->config.use_iff_running || (ifinfomsg->ifi_flags & IFF_RUNNING)) &&
107            !(ifinfomsg->ifi_flags & IFF_LOOPBACK) &&
108            (ifinfomsg->ifi_flags & IFF_MULTICAST) &&
109            (m->server->config.allow_point_to_point || !(ifinfomsg->ifi_flags & IFF_POINTOPOINT));
110
111        /* Handle interface attributes */
112        l = NLMSG_PAYLOAD(n, sizeof(struct ifinfomsg));
113        a = IFLA_RTA(ifinfomsg);
114
115        while (RTA_OK(a, l)) {
116            switch(a->rta_type) {
117                case IFLA_IFNAME:
118
119                    /* Fill in interface name */
120                    avahi_free(hw->name);
121                    hw->name = avahi_strndup(RTA_DATA(a), RTA_PAYLOAD(a));
122                    break;
123
124                case IFLA_MTU:
125
126                    /* Fill in MTU */
127                    assert(RTA_PAYLOAD(a) == sizeof(unsigned int));
128                    hw->mtu = *((unsigned int*) RTA_DATA(a));
129                    break;
130
131                case IFLA_ADDRESS:
132
133                    /* Fill in hardware (MAC) address */
134                    hw->mac_address_size = RTA_PAYLOAD(a);
135                    if (hw->mac_address_size > AVAHI_MAC_ADDRESS_MAX)
136                        hw->mac_address_size = AVAHI_MAC_ADDRESS_MAX;
137
138                    memcpy(hw->mac_address, RTA_DATA(a), hw->mac_address_size);
139                    break;
140
141                default:
142                    ;
143            }
144
145            a = RTA_NEXT(a, l);
146        }
147
148        /* Check whether this interface is now "relevant" for us. If
149         * it is Avahi will start to announce its records on this
150         * interface and send out queries for subscribed records on
151         * it */
152        avahi_hw_interface_check_relevant(hw, AVAHI_MDNS);
153        avahi_hw_interface_check_relevant(hw, AVAHI_LLMNR);
154
155        /* Update any associated RRs of this interface. (i.e. the
156         * _workstation._tcp record containing the MAC address) */
157        avahi_hw_interface_update_rrs(hw, 0);
158
159    } else if (n->nlmsg_type == RTM_DELLINK) {
160
161        /* An interface has been removed */
162
163        struct ifinfomsg *ifinfomsg = NLMSG_DATA(n);
164        AvahiHwInterface *hw;
165
166        /* A (superfluous?) sanity check */
167        if (ifinfomsg->ifi_family != AF_UNSPEC)
168            return;
169
170        /* Get a reference to our AvahiHwInterface object of this interface */
171        if (!(hw = avahi_interface_monitor_get_hw_interface(m, (AvahiIfIndex) ifinfomsg->ifi_index)))
172            return;
173
174        /* Free our object */
175        avahi_hw_interface_free(hw, 0);
176
177    } else if (n->nlmsg_type == RTM_NEWADDR || n->nlmsg_type == RTM_DELADDR) {
178
179        /* An address has been added, modified or removed */
180
181        struct ifaddrmsg *ifaddrmsg = NLMSG_DATA(n);
182        AvahiInterface *i;
183        struct rtattr *a = NULL;
184        size_t l;
185        AvahiAddress raddr, rlocal, *r;
186        int raddr_valid = 0, rlocal_valid = 0;
187
188        /* We are only interested in IPv4 and IPv6 */
189        if (ifaddrmsg->ifa_family != AF_INET && ifaddrmsg->ifa_family != AF_INET6)
190            return;
191
192        /* Try to get a reference to our AvahiInterface object for the
193         * interface this address is assigned to. If ther is no object
194         * for this interface, we ignore this address. */
195        if (!(i = avahi_interface_monitor_get_interface(m, (AvahiIfIndex) ifaddrmsg->ifa_index, avahi_af_to_proto(ifaddrmsg->ifa_family))))
196            return;
197
198        /* Fill in address family for our new address */
199        rlocal.proto = raddr.proto = avahi_af_to_proto(ifaddrmsg->ifa_family);
200
201        l = NLMSG_PAYLOAD(n, sizeof(struct ifaddrmsg));
202        a = IFA_RTA(ifaddrmsg);
203
204        while (RTA_OK(a, l)) {
205
206            switch(a->rta_type) {
207
208                case IFA_ADDRESS:
209
210                    if ((rlocal.proto == AVAHI_PROTO_INET6 && RTA_PAYLOAD(a) != 16) ||
211                        (rlocal.proto == AVAHI_PROTO_INET && RTA_PAYLOAD(a) != 4))
212                        return;
213
214                    memcpy(rlocal.data.data, RTA_DATA(a), RTA_PAYLOAD(a));
215                    rlocal_valid = 1;
216
217                    break;
218
219                case IFA_LOCAL:
220
221                    /* Fill in local address data. Usually this is
222                     * preferable over IFA_ADDRESS if both are set,
223                     * since this refers to the local address of a PPP
224                     * link while IFA_ADDRESS refers to the other
225                     * end. */
226
227                    if ((raddr.proto == AVAHI_PROTO_INET6 && RTA_PAYLOAD(a) != 16) ||
228                        (raddr.proto == AVAHI_PROTO_INET && RTA_PAYLOAD(a) != 4))
229                        return;
230
231                    memcpy(raddr.data.data, RTA_DATA(a), RTA_PAYLOAD(a));
232                    raddr_valid = 1;
233
234                    break;
235
236                default:
237                    ;
238            }
239
240            a = RTA_NEXT(a, l);
241        }
242
243        /* If there was no adress attached to this message, let's quit. */
244        if (rlocal_valid)
245            r = &rlocal;
246        else if (raddr_valid)
247            r = &raddr;
248        else
249            return;
250
251        if (n->nlmsg_type == RTM_NEWADDR) {
252            AvahiInterfaceAddress *addr;
253
254            /* This address is new or has been modified, so let's get an object for it */
255            if (!(addr = avahi_interface_monitor_get_address(m, i, r)))
256
257                /* Mmm, no object existing yet, so let's create a new one */
258                if (!(addr = avahi_interface_address_new(m, i, r, ifaddrmsg->ifa_prefixlen)))
259                    return; /* OOM */
260
261            /* Update the scope field for the address */
262            addr->global_scope = ifaddrmsg->ifa_scope == RT_SCOPE_UNIVERSE || ifaddrmsg->ifa_scope == RT_SCOPE_SITE;
263            addr->deprecated = !!(ifaddrmsg->ifa_flags & IFA_F_DEPRECATED);
264        } else {
265            AvahiInterfaceAddress *addr;
266            assert(n->nlmsg_type == RTM_DELADDR);
267
268            /* Try to get a reference to our AvahiInterfaceAddress object for this address */
269            if (!(addr = avahi_interface_monitor_get_address(m, i, r)))
270                return;
271
272            /* And free it */
273            avahi_interface_address_free(addr);
274        }
275
276        /* Avahi only considers interfaces with at least one address
277         * attached relevant. Since we migh have added or removed an
278         * address, let's have it check again whether the interface is
279         * now relevant */
280        avahi_interface_check_relevant(i, AVAHI_MDNS);
281        avahi_interface_check_relevant(i, AVAHI_LLMNR);
282
283        /* Update any associated RRs, like A or AAAA for our new/removed address */
284        avahi_interface_update_rrs(i, 0);
285
286    } else if (n->nlmsg_type == NLMSG_DONE) {
287
288        /* This wild dump request ended, so let's see what we do next */
289
290        if (m->osdep.list == LIST_IFACE) {
291
292            /* Mmmm, interfaces have been wild dumped already, so
293             * let's go on with wild dumping the addresses */
294
295            if (netlink_list_items(m->osdep.netlink, RTM_GETADDR, &m->osdep.query_addr_seq) < 0) {
296                avahi_log_warn("NETLINK: Failed to list addrs: %s", strerror(errno));
297                m->osdep.list = LIST_DONE;
298            } else
299
300                /* Update state information */
301                m->osdep.list = LIST_ADDR;
302
303        } else
304            /* We're done. Tell avahi_interface_monitor_sync() to finish. */
305            m->osdep.list = LIST_DONE;
306
307        if (m->osdep.list == LIST_DONE) {
308
309            /* Only after this boolean variable has been set, Avahi
310             * will start to announce or browse on all interfaces. It
311             * is originaly set to 0, which means that relevancy
312             * checks and RR updates are disabled during the wild
313             * dumps. */
314            m->list_complete = 1;
315
316            /* So let's check if any interfaces are relevant now */
317            avahi_interface_monitor_check_relevant(m, AVAHI_MDNS);
318            avahi_interface_monitor_check_relevant(m, AVAHI_LLMNR);
319
320            /* And update all RRs attached to any interface */
321            avahi_interface_monitor_update_rrs(m, 0);
322
323            /* Tell the user that the wild dump is complete */
324            avahi_log_info("Network interface enumeration completed.");
325        }
326
327    } else if (n->nlmsg_type == NLMSG_ERROR &&
328               (n->nlmsg_seq == m->osdep.query_link_seq || n->nlmsg_seq == m->osdep.query_addr_seq)) {
329        struct nlmsgerr *e = NLMSG_DATA (n);
330
331        /* Some kind of error happened. Let's just tell the user and
332         * ignore it otherwise */
333
334        if (e->error)
335            avahi_log_warn("NETLINK: Failed to browse: %s", strerror(-e->error));
336    }
337}
338
339int avahi_interface_monitor_init_osdep(AvahiInterfaceMonitor *m) {
340    assert(m);
341
342    /* Initialize our own data */
343
344    m->osdep.netlink = NULL;
345    m->osdep.query_addr_seq = m->osdep.query_link_seq = 0;
346
347    /* Create a netlink object for us. It abstracts some things and
348     * makes netlink easier to use. It will attach to the main loop
349     * for us and call netlink_callback() whenever an event
350     * happens. */
351    if (!(m->osdep.netlink = avahi_netlink_new(m->server->poll_api, RTMGRP_LINK|RTMGRP_IPV4_IFADDR|RTMGRP_IPV6_IFADDR, netlink_callback, m)))
352        goto fail;
353
354    /* Set the initial state. */
355    m->osdep.list = LIST_IFACE;
356
357    /* Start the wild dump for the interfaces */
358    if (netlink_list_items(m->osdep.netlink, RTM_GETLINK, &m->osdep.query_link_seq) < 0)
359        goto fail;
360
361    return 0;
362
363fail:
364
365    if (m->osdep.netlink) {
366        avahi_netlink_free(m->osdep.netlink);
367        m->osdep.netlink = NULL;
368    }
369
370    return -1;
371}
372
373void avahi_interface_monitor_free_osdep(AvahiInterfaceMonitor *m) {
374    assert(m);
375
376    if (m->osdep.netlink) {
377        avahi_netlink_free(m->osdep.netlink);
378        m->osdep.netlink = NULL;
379    }
380}
381
382void avahi_interface_monitor_sync(AvahiInterfaceMonitor *m) {
383    assert(m);
384
385    /* Let's handle netlink events until we are done with wild
386     * dumping */
387
388    while (!m->list_complete)
389        if (!avahi_netlink_work(m->osdep.netlink, 1) == 0)
390            break;
391
392    /* At this point Avahi knows about all local interfaces and
393     * addresses in existance. */
394}
395