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 <stdlib.h>
27
28#include <avahi-common/timeval.h>
29#include <avahi-common/malloc.h>
30#include <avahi-common/error.h>
31#include <avahi-common/domain.h>
32#include <avahi-common/rlist.h>
33#include <avahi-common/address.h>
34
35#include "browse.h"
36#include "log.h"
37#include "querier.h"
38#include "domain-util.h"
39#include "rr-util.h"
40
41#define AVAHI_LOOKUPS_PER_BROWSER_MAX 15
42
43struct AvahiSRBLookup {
44    AvahiSRecordBrowser *record_browser;
45
46    unsigned ref;
47
48    AvahiIfIndex interface;
49    AvahiProtocol protocol;
50    AvahiLookupFlags flags;
51
52    AvahiKey *key;
53
54    AvahiWideAreaLookup *wide_area;
55    AvahiMulticastLookup *multicast;
56
57    AvahiRList *cname_lookups;
58
59    AVAHI_LLIST_FIELDS(AvahiSRBLookup, lookups);
60};
61
62static void lookup_handle_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r);
63static void lookup_drop_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r);
64
65static void transport_flags_from_domain(AvahiServer *s, AvahiLookupFlags *flags, const char *domain) {
66    assert(flags);
67    assert(domain);
68
69    assert(!((*flags & AVAHI_LOOKUP_USE_MULTICAST) && (*flags & AVAHI_LOOKUP_USE_WIDE_AREA)));
70
71    if (*flags & (AVAHI_LOOKUP_USE_MULTICAST|AVAHI_LOOKUP_USE_WIDE_AREA))
72        return;
73
74    if (!s->wide_area_lookup_engine ||
75        !avahi_wide_area_has_servers(s->wide_area_lookup_engine) ||
76        avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_LOCAL) ||
77        avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_ADDR_IPV4) ||
78        avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_ADDR_IPV6))
79        *flags |= AVAHI_LOOKUP_USE_MULTICAST;
80    else
81        *flags |= AVAHI_LOOKUP_USE_WIDE_AREA;
82}
83
84static AvahiSRBLookup* lookup_new(
85    AvahiSRecordBrowser *b,
86    AvahiIfIndex interface,
87    AvahiProtocol protocol,
88    AvahiLookupFlags flags,
89    AvahiKey *key) {
90
91    AvahiSRBLookup *l;
92
93    assert(b);
94    assert(AVAHI_IF_VALID(interface));
95    assert(AVAHI_PROTO_VALID(protocol));
96
97    if (b->n_lookups >= AVAHI_LOOKUPS_PER_BROWSER_MAX)
98        /* We don't like cyclic CNAMEs */
99        return NULL;
100
101    if (!(l = avahi_new(AvahiSRBLookup, 1)))
102        return NULL;
103
104    l->ref = 1;
105    l->record_browser = b;
106    l->interface = interface;
107    l->protocol = protocol;
108    l->key = avahi_key_ref(key);
109    l->wide_area = NULL;
110    l->multicast = NULL;
111    l->cname_lookups = NULL;
112    l->flags = flags;
113
114    transport_flags_from_domain(b->server, &l->flags, key->name);
115
116    AVAHI_LLIST_PREPEND(AvahiSRBLookup, lookups, b->lookups, l);
117
118    b->n_lookups ++;
119
120    return l;
121}
122
123static void lookup_unref(AvahiSRBLookup *l) {
124    assert(l);
125    assert(l->ref >= 1);
126
127    if (--l->ref >= 1)
128        return;
129
130    AVAHI_LLIST_REMOVE(AvahiSRBLookup, lookups, l->record_browser->lookups, l);
131    l->record_browser->n_lookups --;
132
133    if (l->wide_area) {
134        avahi_wide_area_lookup_free(l->wide_area);
135        l->wide_area = NULL;
136    }
137
138    if (l->multicast) {
139        avahi_multicast_lookup_free(l->multicast);
140        l->multicast = NULL;
141    }
142
143    while (l->cname_lookups) {
144        lookup_unref(l->cname_lookups->data);
145        l->cname_lookups = avahi_rlist_remove_by_link(l->cname_lookups, l->cname_lookups);
146    }
147
148    avahi_key_unref(l->key);
149    avahi_free(l);
150}
151
152static AvahiSRBLookup* lookup_ref(AvahiSRBLookup *l) {
153    assert(l);
154    assert(l->ref >= 1);
155
156    l->ref++;
157    return l;
158}
159
160static AvahiSRBLookup *lookup_find(
161    AvahiSRecordBrowser *b,
162    AvahiIfIndex interface,
163    AvahiProtocol protocol,
164    AvahiLookupFlags flags,
165    AvahiKey *key) {
166
167    AvahiSRBLookup *l;
168
169    assert(b);
170
171    for (l = b->lookups; l; l = l->lookups_next) {
172
173        if ((l->interface == AVAHI_IF_UNSPEC || l->interface == interface) &&
174            (l->interface == AVAHI_PROTO_UNSPEC || l->protocol == protocol) &&
175            l->flags == flags &&
176            avahi_key_equal(l->key, key))
177
178            return l;
179    }
180
181    return NULL;
182}
183
184static void browser_cancel(AvahiSRecordBrowser *b) {
185    assert(b);
186
187    if (b->root_lookup) {
188        lookup_unref(b->root_lookup);
189        b->root_lookup = NULL;
190    }
191
192    if (b->defer_time_event) {
193        avahi_time_event_free(b->defer_time_event);
194        b->defer_time_event = NULL;
195    }
196}
197
198static void lookup_wide_area_callback(
199    AvahiWideAreaLookupEngine *e,
200    AvahiBrowserEvent event,
201    AvahiLookupResultFlags flags,
202    AvahiRecord *r,
203    void *userdata) {
204
205    AvahiSRBLookup *l = userdata;
206    AvahiSRecordBrowser *b;
207
208    assert(e);
209    assert(l);
210    assert(l->ref >= 1);
211
212    b = l->record_browser;
213
214    if (b->dead)
215        return;
216
217    lookup_ref(l);
218
219    switch (event) {
220        case AVAHI_BROWSER_NEW:
221            assert(r);
222
223            if (r->key->clazz == AVAHI_DNS_CLASS_IN &&
224                r->key->type == AVAHI_DNS_TYPE_CNAME)
225                /* It's a CNAME record, so let's follow it. We only follow it on wide area DNS! */
226                lookup_handle_cname(l, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_WIDE_AREA, r);
227            else {
228                /* It's a normal record, so let's call the user callback */
229                assert(avahi_key_equal(r->key, l->key));
230
231                b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, event, r, flags, b->userdata);
232            }
233            break;
234
235        case AVAHI_BROWSER_REMOVE:
236        case AVAHI_BROWSER_CACHE_EXHAUSTED:
237            /* Not defined for wide area DNS */
238            abort();
239
240        case AVAHI_BROWSER_ALL_FOR_NOW:
241        case AVAHI_BROWSER_FAILURE:
242
243            b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, event, NULL, flags, b->userdata);
244            break;
245    }
246
247    lookup_unref(l);
248
249}
250
251static void lookup_multicast_callback(
252    AvahiMulticastLookupEngine *e,
253    AvahiIfIndex interface,
254    AvahiProtocol protocol,
255    AvahiBrowserEvent event,
256    AvahiLookupResultFlags flags,
257    AvahiRecord *r,
258    void *userdata) {
259
260    AvahiSRBLookup *l = userdata;
261    AvahiSRecordBrowser *b;
262
263    assert(e);
264    assert(l);
265
266    b = l->record_browser;
267
268    if (b->dead)
269        return;
270
271    lookup_ref(l);
272
273    switch (event) {
274        case AVAHI_BROWSER_NEW:
275            assert(r);
276
277            if (r->key->clazz == AVAHI_DNS_CLASS_IN &&
278                r->key->type == AVAHI_DNS_TYPE_CNAME)
279                /* It's a CNAME record, so let's follow it. We allow browsing on both multicast and wide area. */
280                lookup_handle_cname(l, interface, protocol, b->flags, r);
281            else {
282                /* It's a normal record, so let's call the user callback */
283
284                if (avahi_server_is_record_local(b->server, interface, protocol, r))
285                    flags |= AVAHI_LOOKUP_RESULT_LOCAL;
286
287                b->callback(b, interface, protocol, event, r, flags, b->userdata);
288            }
289            break;
290
291        case AVAHI_BROWSER_REMOVE:
292            assert(r);
293
294            if (r->key->clazz == AVAHI_DNS_CLASS_IN &&
295                r->key->type == AVAHI_DNS_TYPE_CNAME)
296                /* It's a CNAME record, so let's drop that query! */
297                lookup_drop_cname(l, interface, protocol, 0, r);
298            else {
299                /* It's a normal record, so let's call the user callback */
300                assert(avahi_key_equal(b->key, l->key));
301
302                b->callback(b, interface, protocol, event, r, flags, b->userdata);
303            }
304            break;
305
306        case AVAHI_BROWSER_ALL_FOR_NOW:
307
308            b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, event, NULL, flags, b->userdata);
309            break;
310
311        case AVAHI_BROWSER_CACHE_EXHAUSTED:
312        case AVAHI_BROWSER_FAILURE:
313            /* Not defined for multicast DNS */
314            abort();
315
316    }
317
318    lookup_unref(l);
319}
320
321static int lookup_start(AvahiSRBLookup *l) {
322    assert(l);
323
324    assert(!(l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) != !(l->flags & AVAHI_LOOKUP_USE_MULTICAST));
325    assert(!l->wide_area && !l->multicast);
326
327    if (l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) {
328
329        if (!(l->wide_area = avahi_wide_area_lookup_new(l->record_browser->server->wide_area_lookup_engine, l->key, lookup_wide_area_callback, l)))
330            return -1;
331
332    } else {
333        assert(l->flags & AVAHI_LOOKUP_USE_MULTICAST);
334
335        if (!(l->multicast = avahi_multicast_lookup_new(l->record_browser->server->multicast_lookup_engine, l->interface, l->protocol, l->key, lookup_multicast_callback, l)))
336            return -1;
337    }
338
339    return 0;
340}
341
342static int lookup_scan_cache(AvahiSRBLookup *l) {
343    int n = 0;
344
345    assert(l);
346
347    assert(!(l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) != !(l->flags & AVAHI_LOOKUP_USE_MULTICAST));
348
349
350    if (l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) {
351        n = (int) avahi_wide_area_scan_cache(l->record_browser->server->wide_area_lookup_engine, l->key, lookup_wide_area_callback, l);
352
353    } else {
354        assert(l->flags & AVAHI_LOOKUP_USE_MULTICAST);
355        n = (int) avahi_multicast_lookup_engine_scan_cache(l->record_browser->server->multicast_lookup_engine, l->interface, l->protocol, l->key, lookup_multicast_callback, l);
356    }
357
358    return n;
359}
360
361static AvahiSRBLookup* lookup_add(AvahiSRecordBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiKey *key) {
362    AvahiSRBLookup *l;
363
364    assert(b);
365    assert(!b->dead);
366
367    if ((l = lookup_find(b, interface, protocol, flags, key)))
368        return lookup_ref(l);
369
370    if (!(l = lookup_new(b, interface, protocol, flags, key)))
371        return NULL;
372
373    return l;
374}
375
376static int lookup_go(AvahiSRBLookup *l) {
377    int n = 0;
378    assert(l);
379
380    if (l->record_browser->dead)
381        return 0;
382
383    lookup_ref(l);
384
385    /* Browse the cache for the root request */
386    n = lookup_scan_cache(l);
387
388    /* Start the lookup */
389    if (!l->record_browser->dead && l->ref > 1) {
390
391        if ((l->flags & AVAHI_LOOKUP_USE_MULTICAST) || n == 0)
392            /* We do no start a query if the cache contained entries and we're on wide area */
393
394            if (lookup_start(l) < 0)
395                n = -1;
396    }
397
398    lookup_unref(l);
399
400    return n;
401}
402
403static void lookup_handle_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r) {
404    AvahiKey *k;
405    AvahiSRBLookup *n;
406
407    assert(l);
408    assert(r);
409
410    assert(r->key->clazz == AVAHI_DNS_CLASS_IN);
411    assert(r->key->type == AVAHI_DNS_TYPE_CNAME);
412
413    k = avahi_key_new(r->data.ptr.name, l->record_browser->key->clazz, l->record_browser->key->type);
414    n = lookup_add(l->record_browser, interface, protocol, flags, k);
415    avahi_key_unref(k);
416
417    if (!n) {
418        avahi_log_debug(__FILE__": Failed to create SRBLookup.");
419        return;
420    }
421
422    l->cname_lookups = avahi_rlist_prepend(l->cname_lookups, lookup_ref(n));
423
424    lookup_go(n);
425    lookup_unref(n);
426}
427
428static void lookup_drop_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r) {
429    AvahiKey *k;
430    AvahiSRBLookup *n = NULL;
431    AvahiRList *rl;
432
433    assert(r->key->clazz == AVAHI_DNS_CLASS_IN);
434    assert(r->key->type == AVAHI_DNS_TYPE_CNAME);
435
436    k = avahi_key_new(r->data.ptr.name, l->record_browser->key->clazz, l->record_browser->key->type);
437
438    for (rl = l->cname_lookups; rl; rl = rl->rlist_next) {
439        n = rl->data;
440
441        assert(n);
442
443        if ((n->interface == AVAHI_IF_UNSPEC || n->interface == interface) &&
444            (n->interface == AVAHI_PROTO_UNSPEC || n->protocol == protocol) &&
445            n->flags == flags &&
446            avahi_key_equal(n->key, k))
447            break;
448    }
449
450    avahi_key_unref(k);
451
452    if (rl) {
453        l->cname_lookups = avahi_rlist_remove_by_link(l->cname_lookups, rl);
454        lookup_unref(n);
455    }
456}
457
458static void defer_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void *userdata) {
459    AvahiSRecordBrowser *b = userdata;
460    int n;
461
462    assert(b);
463    assert(!b->dead);
464
465    /* Remove the defer timeout */
466    if (b->defer_time_event) {
467        avahi_time_event_free(b->defer_time_event);
468        b->defer_time_event = NULL;
469    }
470
471    /* Create initial query */
472    assert(!b->root_lookup);
473    b->root_lookup = lookup_add(b, b->interface, b->protocol, b->flags, b->key);
474    assert(b->root_lookup);
475
476    n = lookup_go(b->root_lookup);
477
478    if (b->dead)
479        return;
480
481    if (n < 0) {
482        /* sending of the initial query failed */
483
484        avahi_server_set_errno(b->server, AVAHI_ERR_FAILURE);
485
486        b->callback(
487            b, b->interface, b->protocol, AVAHI_BROWSER_FAILURE, NULL,
488            b->flags & AVAHI_LOOKUP_USE_WIDE_AREA ? AVAHI_LOOKUP_RESULT_WIDE_AREA : AVAHI_LOOKUP_RESULT_MULTICAST,
489            b->userdata);
490
491        browser_cancel(b);
492        return;
493    }
494
495    /* Tell the client that we're done with the cache */
496    b->callback(
497        b, b->interface, b->protocol, AVAHI_BROWSER_CACHE_EXHAUSTED, NULL,
498        b->flags & AVAHI_LOOKUP_USE_WIDE_AREA ? AVAHI_LOOKUP_RESULT_WIDE_AREA : AVAHI_LOOKUP_RESULT_MULTICAST,
499        b->userdata);
500
501    if (!b->dead && b->root_lookup && b->root_lookup->flags & AVAHI_LOOKUP_USE_WIDE_AREA && n > 0) {
502
503        /* If we do wide area lookups and the the cache contained
504         * entries, we assume that it is complete, and tell the user
505         * so by firing ALL_FOR_NOW. */
506
507        b->callback(b, b->interface, b->protocol, AVAHI_BROWSER_ALL_FOR_NOW, NULL, AVAHI_LOOKUP_RESULT_WIDE_AREA, b->userdata);
508    }
509}
510
511void avahi_s_record_browser_restart(AvahiSRecordBrowser *b) {
512    assert(b);
513    assert(!b->dead);
514
515    browser_cancel(b);
516
517    /* Request a new iteration of the cache scanning */
518    if (!b->defer_time_event) {
519        b->defer_time_event = avahi_time_event_new(b->server->time_event_queue, NULL, defer_callback, b);
520        assert(b->defer_time_event);
521    }
522}
523
524AvahiSRecordBrowser *avahi_s_record_browser_new(
525    AvahiServer *server,
526    AvahiIfIndex interface,
527    AvahiProtocol protocol,
528    AvahiKey *key,
529    AvahiLookupFlags flags,
530    AvahiSRecordBrowserCallback callback,
531    void* userdata) {
532
533    AvahiSRecordBrowser *b;
534
535    assert(server);
536    assert(key);
537    assert(callback);
538
539    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE);
540    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL);
541    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !avahi_key_is_pattern(key), AVAHI_ERR_IS_PATTERN);
542    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, avahi_key_is_valid(key), AVAHI_ERR_INVALID_KEY);
543    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS);
544    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !(flags & AVAHI_LOOKUP_USE_WIDE_AREA) || !(flags & AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS);
545
546    if (!(b = avahi_new(AvahiSRecordBrowser, 1))) {
547        avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY);
548        return NULL;
549    }
550
551    b->dead = 0;
552    b->server = server;
553    b->interface = interface;
554    b->protocol = protocol;
555    b->key = avahi_key_ref(key);
556    b->flags = flags;
557    b->callback = callback;
558    b->userdata = userdata;
559    b->n_lookups = 0;
560    AVAHI_LLIST_HEAD_INIT(AvahiSRBLookup, b->lookups);
561    b->root_lookup = NULL;
562
563    AVAHI_LLIST_PREPEND(AvahiSRecordBrowser, browser, server->record_browsers, b);
564
565    /* The currently cached entries are scanned a bit later, and than we will start querying, too */
566    b->defer_time_event = avahi_time_event_new(server->time_event_queue, NULL, defer_callback, b);
567    assert(b->defer_time_event);
568
569    return b;
570}
571
572void avahi_s_record_browser_free(AvahiSRecordBrowser *b) {
573    assert(b);
574    assert(!b->dead);
575
576    b->dead = 1;
577    b->server->need_browser_cleanup = 1;
578
579    browser_cancel(b);
580}
581
582void avahi_s_record_browser_destroy(AvahiSRecordBrowser *b) {
583    assert(b);
584
585    browser_cancel(b);
586
587    AVAHI_LLIST_REMOVE(AvahiSRecordBrowser, browser, b->server->record_browsers, b);
588
589    avahi_key_unref(b->key);
590
591    avahi_free(b);
592}
593
594void avahi_browser_cleanup(AvahiServer *server) {
595    AvahiSRecordBrowser *b;
596    AvahiSRecordBrowser *n;
597
598    assert(server);
599
600    while (server->need_browser_cleanup) {
601        server->need_browser_cleanup = 0;
602
603        for (b = server->record_browsers; b; b = n) {
604            n = b->browser_next;
605
606            if (b->dead)
607                avahi_s_record_browser_destroy(b);
608        }
609    }
610
611    if (server->wide_area_lookup_engine)
612        avahi_wide_area_cleanup(server->wide_area_lookup_engine);
613    avahi_multicast_lookup_engine_cleanup(server->multicast_lookup_engine);
614}
615
616