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