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