1#include <stdlib.h> 2 3#include <avahi-common/malloc.h> 4#include <avahi-common/timeval.h> 5#include <avahi-common/defs.h> 6#include <avahi-common/error.h> 7 8#include "internal.h" 9#include "browse.h" 10#include "socket.h" 11#include "log.h" 12#include "hashmap.h" 13#include "rr-util.h" 14 15#include "llmnr-lookup.h" 16 17/* Lookups Function */ 18 19/* AvahiLLMNRQuery Callback */ 20static void query_callback (AvahiIfIndex idx, AvahiProtocol protocol, AvahiRecord *r, void *userdata); 21 22static void elapse_timeout_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void *userdata); 23 24/* New lookup */ 25AvahiLLMNRLookup* avahi_llmnr_lookup_new( 26 AvahiLLMNRLookupEngine *e, 27 AvahiIfIndex interface, 28 AvahiProtocol protocol, 29 AvahiKey *key, 30 AvahiLLMNRLookupCallback callback, 31 void *userdata) { 32 33 struct timeval tv; 34 AvahiLLMNRLookup *l, *t; 35 36 assert(e); 37 assert(AVAHI_IF_VALID(interface)); 38 assert(AVAHI_PROTO_VALID(protocol)); 39 assert(key); 40 assert(callback); 41 42 if (!(l = avahi_new(AvahiLLMNRLookup, 1))) 43 return NULL; 44 45 /* Initialize parameters */ 46 l->engine = e; 47 l->dead = 0; 48 l->key = avahi_key_ref(key); 49 /* TODO*/ 50 l->cname_key = avahi_key_new_cname(l->key); 51 l->interface = interface; 52 l->protocol = protocol; 53 l->callback = callback; 54 l->userdata = userdata; 55 l->queries_issued = 0; 56 l->time_event = NULL; 57 58 /* Prepend in list */ 59 AVAHI_LLIST_PREPEND(AvahiLLMNRLookup, lookups, e->lookups, l); 60 61 /* In hashmap */ 62 t = avahi_hashmap_lookup(e->lookups_by_key, key); 63 AVAHI_LLIST_PREPEND(AvahiLLMNRLookup, by_key, t, l); 64 avahi_hashmap_replace(e->lookups_by_key, avahi_key_ref(key), t); 65 66 /* Issue LLMNR Queries on all interfaces that match the id and protocol*/ 67 avahi_llmnr_query_add_for_all(e->s, interface, protocol, key, query_callback, l); 68 l->queries_issued = 1; 69 70 /* All queries are issued right now. To the max we get response from each interface maximum 71 after 3 seconds (3 queries). still we keep it 4 seconds .AVAHI_BROWSER_ALL_FOR_NOW event */ 72 gettimeofday(&tv, NULL); 73 avahi_timeval_add(&tv, 4000000); 74 75 l->time_event = avahi_time_event_new(e->s->time_event_queue, &tv, elapse_timeout_callback, l); 76 77 return l; 78} 79 80/* Stop lookup */ 81static void lookup_stop(AvahiLLMNRLookup *l) { 82 assert(l); 83 84 l->callback = NULL; 85 86 if (l->queries_issued) { 87 avahi_llmnr_query_remove_for_all(l->engine->s, l->interface, l->protocol, l->key); 88 l->queries_issued = 0; 89 } 90 91 if (l->time_event) { 92 avahi_time_event_free(l->time_event); 93 l->time_event = NULL; 94 } 95} 96 97/* Destroy Lookup */ 98static void lookup_destroy(AvahiLLMNRLookup *l) { 99 AvahiLLMNRLookup *t; 100 assert(l); 101 102 lookup_stop(l); 103 104 t = avahi_hashmap_lookup(l->engine->lookups_by_key, l->key); 105 AVAHI_LLIST_REMOVE(AvahiLLMNRLookup, by_key, t, l); 106 107 if (t) 108 avahi_hashmap_replace(l->engine->lookups_by_key, avahi_key_ref(l->key), t); 109 else 110 avahi_hashmap_remove(l->engine->lookups_by_key, l->key); 111 112 AVAHI_LLIST_REMOVE(AvahiLLMNRLookup, lookups, l->engine->lookups, l); 113 114 if (l->key) 115 avahi_key_unref(l->key); 116 117 118 /* What this CNAME key is for? :( */ 119 if (l->cname_key) 120 avahi_key_unref(l->cname_key); 121 122 avahi_free(l); 123} 124 125/* Free lookup (and stop) */ 126void avahi_llmnr_lookup_free(AvahiLLMNRLookup *l) { 127 assert(l); 128 129 if (l->dead) 130 return; 131 132 l->dead = 1; 133 l->engine->cleanup_dead = 1; 134 lookup_stop(l); 135} 136 137static void elapse_timeout_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void *userdata) { 138 AvahiLLMNRLookup *l = userdata; 139 140 l->callback(l->engine, l->interface, l->protocol, AVAHI_BROWSER_ALL_FOR_NOW, AVAHI_LOOKUP_RESULT_LLMNR, NULL, l->userdata); 141 142 if (l->time_event) { 143 avahi_time_event_free(l->time_event); 144 l->time_event = NULL; 145 } 146 147 lookup_stop(l); 148 149 return; 150} 151 152/* Cache Functions */ 153 154/* Run callbacks for all lookups belong to e and were issued for r->key, idx and proto */ 155static void run_callbacks(AvahiLLMNRLookupEngine *e, AvahiIfIndex idx, AvahiProtocol proto, AvahiRecord *r, AvahiBrowserEvent event) { 156 AvahiLLMNRLookup *l; 157 158 assert(e); 159 assert(r); 160 161 162 for (l = avahi_hashmap_lookup(e->lookups_by_key, r->key); l; l = l->by_key_next) 163 if ( !(l->dead) && 164 (l->callback) && 165 /* lookups can have UNSPEC iface or protocol */ 166 (l->interface < 0 || (l->interface == idx)) && 167 (l->protocol < 0 || (l->protocol == proto)) ) 168 169 /* Call callback funtion */ 170 l->callback(e, idx, proto, event, AVAHI_LOOKUP_RESULT_LLMNR, r, l->userdata); 171 172 173 if (r->key->clazz == AVAHI_DNS_CLASS_IN && r->key->type == AVAHI_DNS_TYPE_CNAME) { 174 175 for (l = e->lookups; l; l = l->lookups_next) { 176 AvahiKey *key; 177 178 if (l->dead || !l->callback) 179 continue; 180 181 if ((key = avahi_key_new_cname(l->key))) { 182 183 if (avahi_key_equal(r->key, key) && 184 (l->interface < 0 || l->interface == idx) && 185 (l->protocol < 0 || l->protocol == proto) ) 186 /* Call callback */ 187 l->callback(e, idx, proto, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_LLMNR, r, l->userdata); 188 avahi_key_unref(key); 189 } 190 } 191 } 192} 193 194/* find cache entry (idx, protocol, r)*/ 195static AvahiLLMNRCacheEntry *find_cache_entry(AvahiLLMNRLookupEngine *e, AvahiIfIndex idx, AvahiProtocol protocol, AvahiRecord *r) { 196 AvahiLLMNRCacheEntry *c; 197 198 assert(e); 199 assert(r); 200 201 assert(idx != AVAHI_IF_UNSPEC); 202 assert(protocol != AVAHI_PROTO_UNSPEC); 203 204 for (c = avahi_hashmap_lookup(e->cache_by_key, r->key); c; c = c->cache_next) { 205 206 if ( (c->interface == idx) && 207 (c->protocol == protocol) && 208 avahi_record_equal_no_ttl(c->record, r) ) 209 210 return c; 211 } 212 213 return NULL; 214} 215 216/* remove/destroy cache entry */ 217static void destroy_cache_entry(AvahiLLMNRCacheEntry *c) { 218 219 AvahiLLMNRCacheEntry *t; 220 assert(c); 221 222 if (c->time_event) 223 avahi_time_event_free(c->time_event); 224 225 AVAHI_LLIST_REMOVE(AvahiLLMNRCacheEntry, cache, c->engine->cache, c); 226 227 t = avahi_hashmap_lookup(c->engine->cache_by_key, c->record->key); 228 AVAHI_LLIST_REMOVE(AvahiLLMNRCacheEntry, by_key, t, c); 229 if (t) 230 avahi_hashmap_replace(c->engine->cache_by_key, avahi_key_ref(c->record->key), t); 231 else 232 avahi_hashmap_remove(c->engine->cache_by_key, c->record->key); 233 234 /* Run callbacks that entry has been deleted */ 235 /* run_callbacks(c->engine, c->interface, c->protocol, c->record, AVAHI_BROWSER_REMOVE); */ 236 237 assert(c->engine->n_cache_entries > 0); 238 c->engine->n_cache_entries--; 239 240 avahi_record_unref(c->record); 241 242 avahi_free(c); 243} 244 245/* callback funtion when cache entry timeout reaches */ 246static void cache_entry_timeout(AvahiTimeEvent *e, void *userdata) { 247 AvahiLLMNRCacheEntry *c = userdata; 248 249 assert(e); 250 assert(c); 251 252 destroy_cache_entry(c); 253} 254 255/* Update cache */ 256static void update_cache(AvahiLLMNRLookupEngine *e, AvahiIfIndex idx, AvahiProtocol protocol, AvahiRecord *r) { 257 AvahiLLMNRCacheEntry *c; 258 int new = 1; 259 260 assert(e); 261 assert(r); 262 263 if ((c = find_cache_entry(e, idx, protocol, r))) { 264/* new = 0;*/ 265 266 /* Update the existine entry. Remove c->record 267 point c->record to r*/ 268 avahi_record_unref(c->record); 269 } else { 270 AvahiLLMNRCacheEntry *t; 271 272/* new = 1;*/ 273 274 /* Check for the cache limit */ 275 if (e->n_cache_entries >= LLMNR_CACHE_ENTRIES_MAX) 276 goto finish; 277 278 c = avahi_new(AvahiLLMNRCacheEntry, 1); 279 c->engine = e; 280 c->interface = idx; 281 c->protocol = protocol; 282 c->time_event = NULL; 283 284 AVAHI_LLIST_PREPEND(AvahiLLMNRCacheEntry, cache, e->cache, c); 285 286 t = avahi_hashmap_lookup(e->cache_by_key, r->key); 287 AVAHI_LLIST_PREPEND(AvahiLLMNRCacheEntry, by_key, t, c); 288 avahi_hashmap_replace(e->cache_by_key, avahi_key_ref(r->key), t); 289 290 e->n_cache_entries++; 291 } 292 293 /* Now update record */ 294 c->record = avahi_record_ref(r); 295 296 /* Scedule the expiry time of this CacheEntry */ 297 gettimeofday(&c->timestamp, NULL); 298 c->expiry = c->timestamp; 299 avahi_timeval_add(&c->expiry, r->ttl * 1000000); 300 301 if (c->time_event) 302 avahi_time_event_update(c->time_event, &c->expiry); 303 else 304 c->time_event = avahi_time_event_new(e->s->time_event_queue, &c->expiry, cache_entry_timeout, c); 305 306 finish: 307 if (new) 308 /* Whether the record is new or old we run callbacks for every update */ 309 run_callbacks(e, idx, protocol, r, AVAHI_BROWSER_NEW); 310} 311 312/* Define callback function */ 313static void query_callback( 314 AvahiIfIndex idx, 315 AvahiProtocol protocol, 316 AvahiRecord *r, 317 void *userdata) { 318 319 AvahiLLMNRLookup *lookup = userdata; 320 assert(AVAHI_IF_VALID(idx) && idx != -1); 321 assert(AVAHI_PROTO_VALID(protocol) && (protocol != AVAHI_PROTO_UNSPEC)); 322 323 if (r) 324 /* Update cache */ 325 update_cache(lookup->engine, idx, protocol, r); 326 /*else 327 This query was issued by 'lookup'. So call lookup->callback specifying that 328 on specified interface and protocol we have no records available 329 lookup->callback(lookup->engine, idx, protocol, AVAHI_BROWSER_FAILURE, AVAHI_LOOKUP_RESULT_LLMNR, NULL, lookup->userdata); */ 330 331 /* We can't stop the lookup right away because we may have some more responses coming 332 up from more interfaces */ 333 return; 334} 335 336/* Scan LLMNR cache */ 337unsigned avahi_scan_llmnr_cache( 338 AvahiLLMNRLookupEngine *e, 339 AvahiIfIndex idx, 340 AvahiProtocol protocol, 341 AvahiKey *key, 342 AvahiLLMNRLookupCallback callback, 343 void *userdata) { 344 345 AvahiLLMNRCacheEntry *c; 346 AvahiKey *cname_key; 347 unsigned n = 0; 348 349 assert(e); 350 assert(key); 351 assert(callback); 352 353 assert(AVAHI_IF_VALID(idx)); 354 assert(AVAHI_PROTO_VALID(protocol)); 355 356 for (c = avahi_hashmap_lookup(e->cache_by_key, key); c; c = c->by_key_next) 357 if (((idx < 0) || (c->interface == idx)) && 358 ((protocol < 0) || (c->protocol == protocol)) ) { 359 /* Call Callback Function */ 360 callback(e, 361 c->interface, 362 c->protocol, 363 AVAHI_BROWSER_NEW, 364 AVAHI_LOOKUP_RESULT_CACHED | AVAHI_LOOKUP_RESULT_LLMNR, 365 c->record, 366 userdata); 367 /* Increase 'n'*/ 368 n++; 369 } 370 371 if ((cname_key = avahi_key_new_cname(key))) { 372 373 for (c = avahi_hashmap_lookup(e->cache_by_key, cname_key); c; c = c->by_key_next) 374 if ( ( (idx < 0) || (c->interface == idx) ) && 375 ( (protocol < 0) || (c->protocol == protocol) )) { 376 callback(e, c->interface, c->protocol, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_CACHED | AVAHI_LOOKUP_RESULT_LLMNR, c->record, userdata); 377 n++; 378 } 379 380 avahi_key_unref(cname_key); 381 } 382 383 return n; 384} 385 386/* Clear all cache entries belong to this interface */ 387void avahi_llmnr_clear_cache(AvahiLLMNRLookupEngine *e, AvahiInterface *i) { 388 AvahiLLMNRCacheEntry *c; 389 390 assert(e); 391 assert(i); 392 393 for (c = e->cache; c; c = c->cache_next) 394 if (avahi_interface_match(i, c->interface, c->protocol)) 395 /*Destroy this cache entry */ 396 destroy_cache_entry(c); 397} 398 399/* Cache dump */ 400void avahi_llmnr_cache_dump(AvahiLLMNRLookupEngine *e, AvahiDumpCallback callback, void *userdata) { 401 AvahiLLMNRCacheEntry *c; 402 403 assert(e); 404 assert(callback); 405 406 callback(";;; LLMNR CACHE ;;;", userdata); 407 408 for (c = e->cache; c; c = c->cache_next) { 409 if (avahi_interface_is_relevant(avahi_interface_monitor_get_interface(c->engine->s->monitor, c->interface, c->protocol))) { 410 char *t = avahi_record_to_string(c->record); 411 callback(t, userdata); 412 avahi_free(t); 413 } 414 } 415} 416 417/* LLMNRLookupEngine functions */ 418void avahi_llmnr_lookup_engine_cleanup(AvahiLLMNRLookupEngine *e) { 419 AvahiLLMNRLookup *l, *n; 420 assert(e); 421 422 while (e->cleanup_dead) { 423 e->cleanup_dead = 0; 424 425 for (l = e->lookups; l; l = n) { 426 n = l->lookups_next; 427 428 if (l->dead) 429 lookup_destroy(l); 430 } 431 } 432} 433 434/* The interface is new so issue query for all lookups those are not dead */ 435void avahi_llmnr_lookup_engine_new_interface(AvahiLLMNRLookupEngine *e, AvahiInterface *i) { 436 AvahiLLMNRLookup *l; 437 438 assert(e); 439 assert(i); 440 for (l = e->lookups; l; l = l->lookups_next) { 441 442 if (l->dead || !l->callback) 443 continue; 444 445 if (l->queries_issued && avahi_interface_match(i, l->interface, l->protocol)) 446 /* Issue LLMNR query */ 447 avahi_llmnr_query_add(i, l->key, AVAHI_LLMNR_SIMPLE_QUERY, query_callback, l); 448 } 449} 450 451/* New lookup engine */ 452AvahiLLMNRLookupEngine* avahi_llmnr_lookup_engine_new(AvahiServer *s) { 453 AvahiLLMNRLookupEngine *e; 454 455 assert(s); 456 457 e = avahi_new(AvahiLLMNRLookupEngine, 1); 458 e->s = s; 459 e->next_id = 0; 460 e->cleanup_dead = 0; 461 e->n_cache_entries = 0; 462 463 /* Queries and lookups */ 464 e->lookups_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, (AvahiFreeFunc) avahi_key_unref, NULL); 465 e->queries_by_id = avahi_hashmap_new((AvahiHashFunc) avahi_int_hash, (AvahiEqualFunc) avahi_int_equal, NULL, NULL); 466 AVAHI_LLIST_HEAD_INIT(AvahiLLMNRLookup, e->lookups); 467 468 /* Cache*/ 469 e->cache_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, (AvahiFreeFunc) avahi_key_unref, NULL); 470 AVAHI_LLIST_HEAD_INIT(AvahiLLMNRCacheEntry, e->cache); 471 472 return e; 473} 474 475void avahi_llmnr_lookup_engine_free(AvahiLLMNRLookupEngine *e) { 476 assert(e); 477 478 while (e->cache) 479 destroy_cache_entry(e->cache); 480 481 while (e->lookups) 482 lookup_destroy(e->lookups); 483 484 assert(e->n_cache_entries == 0); 485 486 /* Clear all hashmap's*/ 487 avahi_hashmap_free(e->lookups_by_key); 488 avahi_hashmap_free(e->queries_by_id); 489 avahi_hashmap_free(e->cache_by_key); 490 491 avahi_free(e); 492} 493