iface.c revision 602a2b6481587b7da2594db39151ec9380f276df
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 <sys/socket.h>
28#include <asm/types.h>
29#include <linux/netlink.h>
30#include <linux/rtnetlink.h>
31#include <errno.h>
32#include <net/if.h>
33
34#include "iface.h"
35#include "netlink.h"
36#include "dns.h"
37#include "socket.h"
38#include "announce.h"
39
40static void update_address_rr(AvahiInterfaceMonitor *m, AvahiInterfaceAddress *a, int remove) {
41    g_assert(m);
42    g_assert(a);
43
44    if (!avahi_interface_address_relevant(a) || remove) {
45        if (a->entry_group) {
46            avahi_entry_group_free(a->entry_group);
47            a->entry_group = NULL;
48        }
49    } else {
50        if (!a->entry_group) {
51            a->entry_group = avahi_entry_group_new(m->server, NULL, NULL);
52            avahi_server_add_address(m->server, a->entry_group, a->interface->hardware->index, AF_UNSPEC, 0, NULL, &a->address);
53            avahi_entry_group_commit(a->entry_group);
54        }
55    }
56}
57
58static void update_interface_rr(AvahiInterfaceMonitor *m, AvahiInterface *i, int remove) {
59    AvahiInterfaceAddress *a;
60    g_assert(m);
61    g_assert(i);
62
63    for (a = i->addresses; a; a = a->address_next)
64        update_address_rr(m, a, remove);
65}
66
67static void update_hw_interface_rr(AvahiInterfaceMonitor *m, AvahiHwInterface *hw, int remove) {
68    AvahiInterface *i;
69
70    g_assert(m);
71    g_assert(hw);
72
73    for (i = hw->interfaces; i; i = i->by_hardware_next)
74        update_interface_rr(m, i, remove);
75}
76
77static void free_address(AvahiInterfaceMonitor *m, AvahiInterfaceAddress *a) {
78    g_assert(m);
79    g_assert(a);
80    g_assert(a->interface);
81
82    AVAHI_LLIST_REMOVE(AvahiInterfaceAddress, address, a->interface->addresses, a);
83
84    if (a->entry_group)
85        avahi_entry_group_free(a->entry_group);
86
87    g_free(a);
88}
89
90static void free_interface(AvahiInterfaceMonitor *m, AvahiInterface *i, gboolean send_goodbye) {
91    g_assert(m);
92    g_assert(i);
93
94    g_message("removing interface %s.%i", i->hardware->name, i->protocol);
95    avahi_goodbye_interface(m->server, i, send_goodbye);
96    g_message("flushing...");
97    avahi_packet_scheduler_flush_responses(i->scheduler);
98    g_message("done");
99
100    g_assert(!i->announcements);
101
102    while (i->addresses)
103        free_address(m, i->addresses);
104
105    avahi_packet_scheduler_free(i->scheduler);
106    avahi_cache_free(i->cache);
107
108    AVAHI_LLIST_REMOVE(AvahiInterface, interface, m->interfaces, i);
109    AVAHI_LLIST_REMOVE(AvahiInterface, by_hardware, i->hardware->interfaces, i);
110
111    g_free(i);
112}
113
114static void free_hw_interface(AvahiInterfaceMonitor *m, AvahiHwInterface *hw, gboolean send_goodbye) {
115    g_assert(m);
116    g_assert(hw);
117
118    while (hw->interfaces)
119        free_interface(m, hw->interfaces, send_goodbye);
120
121    AVAHI_LLIST_REMOVE(AvahiHwInterface, hardware, m->hw_interfaces, hw);
122    g_hash_table_remove(m->hash_table, &hw->index);
123
124    g_free(hw->name);
125    g_free(hw);
126}
127
128static AvahiInterfaceAddress* get_address(AvahiInterfaceMonitor *m, AvahiInterface *i, const AvahiAddress *raddr) {
129    AvahiInterfaceAddress *ia;
130
131    g_assert(m);
132    g_assert(i);
133    g_assert(raddr);
134
135    for (ia = i->addresses; ia; ia = ia->address_next)
136        if (avahi_address_cmp(&ia->address, raddr) == 0)
137            return ia;
138
139    return NULL;
140}
141
142static int netlink_list_items(AvahiNetlink *nl, guint16 type, guint *ret_seq) {
143    struct nlmsghdr *n;
144    struct rtgenmsg *gen;
145    guint8 req[1024];
146
147    memset(&req, 0, sizeof(req));
148    n = (struct nlmsghdr*) req;
149    n->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
150    n->nlmsg_type = type;
151    n->nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
152    n->nlmsg_pid = 0;
153
154    gen = NLMSG_DATA(n);
155    memset(gen, 0, sizeof(struct rtgenmsg));
156    gen->rtgen_family = AF_UNSPEC;
157
158    return avahi_netlink_send(nl, n, ret_seq);
159}
160
161static void new_interface(AvahiInterfaceMonitor *m, AvahiHwInterface *hw, guchar protocol) {
162    AvahiInterface *i;
163
164    g_assert(m);
165    g_assert(hw);
166    g_assert(protocol != AF_UNSPEC);
167
168    i = g_new(AvahiInterface, 1);
169    i->monitor = m;
170    i->hardware = hw;
171    i->protocol = protocol;
172    i->announcing = FALSE;
173
174    AVAHI_LLIST_HEAD_INIT(AvahiInterfaceAddress, i->addresses);
175    AVAHI_LLIST_HEAD_INIT(AvahiAnnouncement, i->announcements);
176
177    i->cache = avahi_cache_new(m->server, i);
178    i->scheduler = avahi_packet_scheduler_new(m->server, i);
179
180    AVAHI_LLIST_PREPEND(AvahiInterface, by_hardware, hw->interfaces, i);
181    AVAHI_LLIST_PREPEND(AvahiInterface, interface, m->interfaces, i);
182}
183
184static void check_interface_relevant(AvahiInterfaceMonitor *m, AvahiInterface *i) {
185    gboolean b;
186
187    g_assert(m);
188    g_assert(i);
189
190    b = avahi_interface_relevant(i);
191
192    if (b && !i->announcing) {
193        g_message("New relevant interface %s.%i", i->hardware->name, i->protocol);
194
195        if (i->protocol == AF_INET)
196            avahi_mdns_mcast_join_ipv4 (i->hardware->index, m->server->fd_ipv4);
197        if (i->protocol == AF_INET6)
198            avahi_mdns_mcast_join_ipv6 (i->hardware->index, m->server->fd_ipv6);
199
200        i->announcing = TRUE;
201        avahi_announce_interface(m->server, i);
202    } else if (!b && i->announcing) {
203        g_message("Interface %s.%i no longer relevant", i->hardware->name, i->protocol);
204
205        avahi_goodbye_interface(m->server, i, FALSE);
206
207        if (i->protocol == AF_INET)
208            avahi_mdns_mcast_leave_ipv4 (i->hardware->index, m->server->fd_ipv4);
209        if (i->protocol == AF_INET6)
210            avahi_mdns_mcast_leave_ipv6 (i->hardware->index, m->server->fd_ipv6);
211
212        i->announcing = FALSE;
213    }
214}
215
216static void check_hw_interface_relevant(AvahiInterfaceMonitor *m, AvahiHwInterface *hw) {
217    AvahiInterface *i;
218
219    g_assert(m);
220    g_assert(hw);
221
222    for (i = hw->interfaces; i; i = i->by_hardware_next)
223        check_interface_relevant(m, i);
224}
225
226static void callback(AvahiNetlink *nl, struct nlmsghdr *n, gpointer userdata) {
227    AvahiInterfaceMonitor *m = userdata;
228
229    g_assert(m);
230    g_assert(n);
231    g_assert(m->netlink == nl);
232
233    if (n->nlmsg_type == RTM_NEWLINK) {
234        struct ifinfomsg *ifinfomsg = NLMSG_DATA(n);
235        AvahiHwInterface *hw;
236        struct rtattr *a = NULL;
237        size_t l;
238
239        if (ifinfomsg->ifi_family != AF_UNSPEC)
240            return;
241
242        if (!(hw = g_hash_table_lookup(m->hash_table, &ifinfomsg->ifi_index))) {
243            hw = g_new(AvahiHwInterface, 1);
244            hw->monitor = m;
245            hw->name = NULL;
246            hw->flags = 0;
247            hw->mtu = 1500;
248            hw->index = ifinfomsg->ifi_index;
249
250            AVAHI_LLIST_HEAD_INIT(AvahiInterface, hw->interfaces);
251            AVAHI_LLIST_PREPEND(AvahiHwInterface, hardware, m->hw_interfaces, hw);
252
253            g_hash_table_insert(m->hash_table, &hw->index, hw);
254
255            if (m->server->fd_ipv4 >= 0)
256                new_interface(m, hw, AF_INET);
257            if (m->server->fd_ipv6 >= 0)
258                new_interface(m, hw, AF_INET6);
259        }
260
261        hw->flags = ifinfomsg->ifi_flags;
262
263        l = NLMSG_PAYLOAD(n, sizeof(struct ifinfomsg));
264        a = IFLA_RTA(ifinfomsg);
265
266        while (RTA_OK(a, l)) {
267            switch(a->rta_type) {
268                case IFLA_IFNAME:
269                    g_free(hw->name);
270                    hw->name = g_strndup(RTA_DATA(a), RTA_PAYLOAD(a));
271                    break;
272
273                case IFLA_MTU:
274                    g_assert(RTA_PAYLOAD(a) == sizeof(unsigned int));
275                    hw->mtu = *((unsigned int*) RTA_DATA(a));
276                    break;
277
278                default:
279                    ;
280            }
281
282            a = RTA_NEXT(a, l);
283        }
284
285        update_hw_interface_rr(m, hw, FALSE);
286        check_hw_interface_relevant(m, hw);
287
288    } else if (n->nlmsg_type == RTM_DELLINK) {
289        struct ifinfomsg *ifinfomsg = NLMSG_DATA(n);
290        AvahiHwInterface *hw;
291
292        if (ifinfomsg->ifi_family != AF_UNSPEC)
293            return;
294
295        if (!(hw = avahi_interface_monitor_get_hw_interface(m, ifinfomsg->ifi_index)))
296            return;
297
298        update_hw_interface_rr(m, hw, TRUE);
299        free_hw_interface(m, hw, FALSE);
300
301    } else if (n->nlmsg_type == RTM_NEWADDR || n->nlmsg_type == RTM_DELADDR) {
302
303        struct ifaddrmsg *ifaddrmsg = NLMSG_DATA(n);
304        AvahiInterface *i;
305        struct rtattr *a = NULL;
306        size_t l;
307        AvahiAddress raddr;
308        int raddr_valid = 0;
309
310        if (ifaddrmsg->ifa_family != AF_INET && ifaddrmsg->ifa_family != AF_INET6)
311            return;
312
313        if (!(i = (AvahiInterface*) avahi_interface_monitor_get_interface(m, ifaddrmsg->ifa_index, ifaddrmsg->ifa_family)))
314            return;
315
316        raddr.family = ifaddrmsg->ifa_family;
317
318        l = NLMSG_PAYLOAD(n, sizeof(struct ifinfomsg));
319        a = IFA_RTA(ifaddrmsg);
320
321        while (RTA_OK(a, l)) {
322            switch(a->rta_type) {
323                case IFA_ADDRESS:
324                    if ((raddr.family == AF_INET6 && RTA_PAYLOAD(a) != 16) ||
325                        (raddr.family == AF_INET && RTA_PAYLOAD(a) != 4))
326                        return;
327
328                    memcpy(raddr.data.data, RTA_DATA(a), RTA_PAYLOAD(a));
329                    raddr_valid = 1;
330
331                    break;
332
333                default:
334                    ;
335            }
336
337            a = RTA_NEXT(a, l);
338        }
339
340
341        if (!raddr_valid)
342            return;
343
344        if (n->nlmsg_type == RTM_NEWADDR) {
345            AvahiInterfaceAddress *addr;
346
347            if (!(addr = get_address(m, i, &raddr))) {
348                addr = g_new(AvahiInterfaceAddress, 1);
349                addr->monitor = m;
350                addr->address = raddr;
351                addr->interface = i;
352                addr->entry_group = NULL;
353
354                AVAHI_LLIST_PREPEND(AvahiInterfaceAddress, address, i->addresses, addr);
355            }
356
357            addr->flags = ifaddrmsg->ifa_flags;
358            addr->scope = ifaddrmsg->ifa_scope;
359
360            update_address_rr(m, addr, FALSE);
361            check_interface_relevant(m, i);
362        } else {
363            AvahiInterfaceAddress *addr;
364
365            if (!(addr = get_address(m, i, &raddr)))
366                return;
367
368            update_address_rr(m, addr, TRUE);
369            free_address(m, addr);
370
371            check_interface_relevant(m, i);
372        }
373
374    } else if (n->nlmsg_type == NLMSG_DONE) {
375
376        if (m->list == LIST_IFACE) {
377            m->list = LIST_DONE;
378
379            if (netlink_list_items(m->netlink, RTM_GETADDR, &m->query_addr_seq) < 0)
380                g_warning("NETLINK: Failed to list addrs: %s", strerror(errno));
381            else
382                m->list = LIST_ADDR;
383        } else {
384            m->list = LIST_DONE;
385            g_message("Enumeration complete");
386        }
387
388    } else if (n->nlmsg_type == NLMSG_ERROR && (n->nlmsg_seq == m->query_link_seq || n->nlmsg_seq == m->query_addr_seq)) {
389        struct nlmsgerr *e = NLMSG_DATA (n);
390
391        if (e->error)
392            g_warning("NETLINK: Failed to browse: %s", strerror(-e->error));
393    }
394}
395
396AvahiInterfaceMonitor *avahi_interface_monitor_new(AvahiServer *s) {
397    AvahiInterfaceMonitor *m = NULL;
398
399    m = g_new0(AvahiInterfaceMonitor, 1);
400    m->server = s;
401    if (!(m->netlink = avahi_netlink_new(s->context, G_PRIORITY_DEFAULT-10, RTMGRP_LINK|RTMGRP_IPV4_IFADDR|RTMGRP_IPV6_IFADDR, callback, m)))
402        goto fail;
403
404    m->hash_table = g_hash_table_new(g_int_hash, g_int_equal);
405
406    AVAHI_LLIST_HEAD_INIT(AvahiInterface, m->interfaces);
407    AVAHI_LLIST_HEAD_INIT(AvahiHwInterface, m->hw_interfaces);
408
409    if (netlink_list_items(m->netlink, RTM_GETLINK, &m->query_link_seq) < 0)
410        goto fail;
411
412    m->list = LIST_IFACE;
413
414    return m;
415
416fail:
417    avahi_interface_monitor_free(m);
418    return NULL;
419}
420
421void avahi_interface_monitor_sync(AvahiInterfaceMonitor *m) {
422    g_assert(m);
423
424    while (m->list != LIST_DONE) {
425        if (!avahi_netlink_work(m->netlink, TRUE))
426            break;
427    }
428}
429
430void avahi_interface_monitor_free(AvahiInterfaceMonitor *m) {
431    g_assert(m);
432
433    while (m->hw_interfaces)
434        free_hw_interface(m, m->hw_interfaces, TRUE);
435
436    g_assert(!m->interfaces);
437
438
439    if (m->netlink)
440        avahi_netlink_free(m->netlink);
441
442    if (m->hash_table)
443        g_hash_table_destroy(m->hash_table);
444
445    g_free(m);
446}
447
448
449AvahiInterface* avahi_interface_monitor_get_interface(AvahiInterfaceMonitor *m, gint index, guchar protocol) {
450    AvahiHwInterface *hw;
451    AvahiInterface *i;
452
453    g_assert(m);
454    g_assert(index > 0);
455    g_assert(protocol != AF_UNSPEC);
456
457    if (!(hw = avahi_interface_monitor_get_hw_interface(m, index)))
458        return NULL;
459
460    for (i = hw->interfaces; i; i = i->by_hardware_next)
461        if (i->protocol == protocol)
462            return i;
463
464    return NULL;
465}
466
467AvahiHwInterface* avahi_interface_monitor_get_hw_interface(AvahiInterfaceMonitor *m, gint index) {
468    g_assert(m);
469    g_assert(index > 0);
470
471    return g_hash_table_lookup(m->hash_table, &index);
472}
473
474
475void avahi_interface_send_packet_unicast(AvahiInterface *i, AvahiDnsPacket *p, const AvahiAddress *a, guint16 port) {
476    g_assert(i);
477    g_assert(p);
478    char t[64];
479
480    if (!avahi_interface_relevant(i))
481        return;
482
483    g_assert(!a || a->family == i->protocol);
484
485    if (a)
486        g_message("unicast sending on '%s.%i' to %s:%u", i->hardware->name, i->protocol, avahi_address_snprint(t, sizeof(t), a), port);
487    else
488        g_message("multicast sending on '%s.%i'", i->hardware->name, i->protocol);
489
490    if (i->protocol == AF_INET && i->monitor->server->fd_ipv4 >= 0)
491        avahi_send_dns_packet_ipv4(i->monitor->server->fd_ipv4, i->hardware->index, p, a ? &a->data.ipv4 : NULL, port);
492    else if (i->protocol == AF_INET6 && i->monitor->server->fd_ipv6 >= 0)
493        avahi_send_dns_packet_ipv6(i->monitor->server->fd_ipv6, i->hardware->index, p, a ? &a->data.ipv6 : NULL, port);
494}
495
496void avahi_interface_send_packet(AvahiInterface *i, AvahiDnsPacket *p) {
497    g_assert(i);
498    g_assert(p);
499
500    avahi_interface_send_packet_unicast(i, p, NULL, 0);
501}
502
503gboolean avahi_interface_post_query(AvahiInterface *i, AvahiKey *key, gboolean immediately) {
504    g_assert(i);
505    g_assert(key);
506
507    if (avahi_interface_relevant(i))
508        return avahi_packet_scheduler_post_query(i->scheduler, key, immediately);
509
510    return FALSE;
511}
512
513gboolean avahi_interface_post_response(AvahiInterface *i, const AvahiAddress *a, AvahiRecord *record, gboolean flush_cache, gboolean immediately) {
514    g_assert(i);
515    g_assert(record);
516
517    if (avahi_interface_relevant(i))
518        return avahi_packet_scheduler_post_response(i->scheduler, a, record, flush_cache, immediately);
519
520    return FALSE;
521}
522
523gboolean avahi_interface_post_probe(AvahiInterface *i, AvahiRecord *record, gboolean immediately) {
524    g_assert(i);
525    g_assert(record);
526
527    if (avahi_interface_relevant(i))
528        return avahi_packet_scheduler_post_probe(i->scheduler, record, immediately);
529
530    return FALSE;
531}
532
533void avahi_dump_caches(AvahiInterfaceMonitor *m, FILE *f) {
534    AvahiInterface *i;
535    g_assert(m);
536
537    for (i = m->interfaces; i; i = i->interface_next) {
538        if (avahi_interface_relevant(i)) {
539            fprintf(f, "\n;;; INTERFACE %s.%i ;;;\n", i->hardware->name, i->protocol);
540            avahi_cache_dump(i->cache, f);
541        }
542    }
543    fprintf(f, "\n");
544}
545
546gboolean avahi_interface_relevant(AvahiInterface *i) {
547    g_assert(i);
548
549    return
550        (i->hardware->flags & IFF_UP) &&
551        (i->hardware->flags & IFF_RUNNING) &&
552        !(i->hardware->flags & IFF_LOOPBACK) &&
553        (i->hardware->flags & IFF_MULTICAST) &&
554        i->addresses;
555}
556
557gboolean avahi_interface_address_relevant(AvahiInterfaceAddress *a) {
558    g_assert(a);
559
560    return a->scope == RT_SCOPE_UNIVERSE;
561}
562
563
564gboolean avahi_interface_match(AvahiInterface *i, gint index, guchar protocol) {
565    g_assert(i);
566
567    if (index > 0 && index != i->hardware->index)
568        return FALSE;
569
570    if (protocol != AF_UNSPEC && protocol != i->protocol)
571        return FALSE;
572
573    return TRUE;
574}
575
576
577void avahi_interface_monitor_walk(AvahiInterfaceMonitor *m, gint interface, guchar protocol, AvahiInterfaceMonitorWalkCallback callback, gpointer userdata) {
578    g_assert(m);
579    g_assert(callback);
580
581    if (interface > 0) {
582        if (protocol != AF_UNSPEC) {
583            AvahiInterface *i;
584
585            if ((i = avahi_interface_monitor_get_interface(m, interface, protocol)))
586                callback(m, i, userdata);
587
588        } else {
589            AvahiHwInterface *hw;
590            AvahiInterface *i;
591
592            if ((hw = avahi_interface_monitor_get_hw_interface(m, interface)))
593                for (i = hw->interfaces; i; i = i->by_hardware_next)
594                    if (avahi_interface_match(i, interface, protocol))
595                        callback(m, i, userdata);
596        }
597
598    } else {
599        AvahiInterface *i;
600
601        for (i = m->interfaces; i; i = i->interface_next)
602            if (avahi_interface_match(i, interface, protocol))
603                callback(m, i, userdata);
604    }
605}
606