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