]> git.meshlink.io Git - catta/blob - avahi-core/wide-area.c
* correctly handle empty domain names (i.e. the root zone)
[catta] / avahi-core / wide-area.c
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 <errno.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30
31 #include <avahi-common/malloc.h>
32 #include <avahi-common/error.h>
33 #include <avahi-common/timeval.h>
34
35 #include "internal.h"
36 #include "browse.h"
37 #include "socket.h"
38 #include "log.h"
39 #include "hashmap.h"
40 #include "wide-area.h"
41
42 #define MAX_CACHE_ENTRIES 500
43
44 typedef struct AvahiWideAreaCacheEntry AvahiWideAreaCacheEntry;
45
46 struct AvahiWideAreaCacheEntry {
47     AvahiWideAreaLookupEngine *engine;
48     
49     AvahiRecord *record;
50     struct timeval timestamp;
51     struct timeval expiry;
52
53     AvahiTimeEvent *time_event;
54     
55     AVAHI_LLIST_FIELDS(AvahiWideAreaCacheEntry, by_key);
56     AVAHI_LLIST_FIELDS(AvahiWideAreaCacheEntry, cache);
57 };
58
59 struct AvahiWideAreaLookup {
60     AvahiWideAreaLookupEngine *engine;
61     int dead;
62     
63     uint32_t id;  /* effectively just an uint16_t, but we need it as an index for a hash table */
64     AvahiTimeEvent *time_event;
65
66     AvahiKey *key, *cname_key;
67     
68     int n_send;
69     AvahiDnsPacket *packet;
70
71     AvahiWideAreaLookupCallback callback;
72     void *userdata;
73
74     AvahiAddress dns_server_used;
75
76     AVAHI_LLIST_FIELDS(AvahiWideAreaLookup, lookups);
77     AVAHI_LLIST_FIELDS(AvahiWideAreaLookup, by_key);
78 };
79
80 struct AvahiWideAreaLookupEngine {
81     AvahiServer *server;
82
83     int fd_ipv4, fd_ipv6;
84     AvahiWatch *watch_ipv4, *watch_ipv6;
85
86     uint16_t next_id;
87     
88     /* Cache */
89     AVAHI_LLIST_HEAD(AvahiWideAreaCacheEntry, cache);
90     AvahiHashmap *cache_by_key;
91     unsigned cache_n_entries;
92
93     /* Lookups */
94     AVAHI_LLIST_HEAD(AvahiWideAreaLookup, lookups);
95     AvahiHashmap *lookups_by_id;
96     AvahiHashmap *lookups_by_key;
97
98     int cleanup_dead;
99
100     AvahiAddress dns_servers[AVAHI_MAX_WIDE_AREA_SERVERS];
101     unsigned n_dns_servers;
102     unsigned current_dns_server;
103 };
104
105 static AvahiWideAreaLookup* find_lookup(AvahiWideAreaLookupEngine *e, uint16_t id) {
106     AvahiWideAreaLookup *l;
107     int i = (int) id;
108     
109     assert(e);
110
111     if (!(l = avahi_hashmap_lookup(e->lookups_by_id, &i)))
112         return NULL;
113     
114     assert(l->id == id);
115
116     if (l->dead)
117         return NULL;
118     
119     return l;
120 }
121
122 static int send_to_dns_server(AvahiWideAreaLookup *l, AvahiDnsPacket *p) {
123     AvahiAddress *a;
124     
125     assert(l);
126     assert(p);
127
128     if (l->engine->n_dns_servers <= 0)
129         return -1;
130
131     assert(l->engine->current_dns_server < l->engine->n_dns_servers);
132
133     a = &l->engine->dns_servers[l->engine->current_dns_server];
134     l->dns_server_used = *a;
135     
136     if (a->proto == AVAHI_PROTO_INET) {
137
138         if (l->engine->fd_ipv4 < 0)
139             return -1;
140         
141         return avahi_send_dns_packet_ipv4(l->engine->fd_ipv4, AVAHI_IF_UNSPEC, p, &a->data.ipv4, AVAHI_DNS_PORT);
142         
143     } else {
144         assert(a->proto == AVAHI_PROTO_INET6);
145
146         if (l->engine->fd_ipv6 < 0)
147             return -1;
148         
149         return avahi_send_dns_packet_ipv6(l->engine->fd_ipv6, AVAHI_IF_UNSPEC, p, &a->data.ipv6, AVAHI_DNS_PORT);
150     }
151 }
152
153 static void next_dns_server(AvahiWideAreaLookupEngine *e) {
154     assert(e);
155
156     e->current_dns_server++;
157
158     if (e->current_dns_server >= e->n_dns_servers)
159         e->current_dns_server = 0;
160 }
161
162 static void sender_timeout_callback(AvahiTimeEvent *e, void *userdata) {
163     AvahiWideAreaLookup *l = userdata;
164     struct timeval tv;
165
166     assert(l);
167
168     /* Try another DNS server after three retries */
169     if (l->n_send >= 3 && avahi_address_cmp(&l->engine->dns_servers[l->engine->current_dns_server], &l->dns_server_used) == 0) {
170         next_dns_server(l->engine);
171
172         if (avahi_address_cmp(&l->engine->dns_servers[l->engine->current_dns_server], &l->dns_server_used) == 0)
173             /* There is no other DNS server, fail */
174             l->n_send = 1000;
175     }
176     
177     if (l->n_send >= 6) {
178         avahi_log_warn(__FILE__": Query timed out.");
179         avahi_server_set_errno(l->engine->server, AVAHI_ERR_TIMEOUT);
180         l->callback(l->engine, AVAHI_BROWSER_FAILURE, AVAHI_LOOKUP_RESULT_WIDE_AREA, NULL, l->userdata);
181         avahi_wide_area_lookup_free(l);
182         return;
183     }
184
185     assert(l->packet);
186     send_to_dns_server(l, l->packet);
187     l->n_send++;
188
189     avahi_time_event_update(e, avahi_elapse_time(&tv, 1000, 0));
190 }
191
192 AvahiWideAreaLookup *avahi_wide_area_lookup_new(
193     AvahiWideAreaLookupEngine *e,
194     AvahiKey *key,
195     AvahiWideAreaLookupCallback callback,
196     void *userdata) {
197     
198     struct timeval tv;
199     AvahiWideAreaLookup *l, *t;
200     uint8_t *p;
201
202     assert(e);
203     assert(key);
204     assert(callback);
205     assert(userdata);
206
207     l = avahi_new(AvahiWideAreaLookup, 1);
208     l->engine = e;
209     l->dead = 0;
210     l->key = avahi_key_ref(key);
211     l->cname_key = avahi_key_new_cname(l->key);
212     l->callback = callback;
213     l->userdata = userdata;
214
215     /* If more than 65K wide area quries are issued simultaneously,
216      * this will break. This should be limited by some higher level */
217
218     for (;; e->next_id++)
219         if (!find_lookup(e, e->next_id))
220             break; /* This ID is not yet used. */
221
222     l->id = e->next_id++;
223     
224     /* We keep the packet around in case we need to repeat our query */
225     l->packet = avahi_dns_packet_new(0);
226
227     avahi_dns_packet_set_field(l->packet, AVAHI_DNS_FIELD_ID, (uint16_t) l->id);
228     avahi_dns_packet_set_field(l->packet, AVAHI_DNS_FIELD_FLAGS, AVAHI_DNS_FLAGS(0, 0, 0, 0, 1, 0, 0, 0, 0, 0));
229
230     p = avahi_dns_packet_append_key(l->packet, key, 0);
231     assert(p);
232     
233     avahi_dns_packet_set_field(l->packet, AVAHI_DNS_FIELD_QDCOUNT, 1);
234
235     if (send_to_dns_server(l, l->packet) < 0) {
236         avahi_log_error(__FILE__": Failed to send packet.");
237         avahi_dns_packet_free(l->packet);
238         avahi_key_unref(l->key);
239         if (l->cname_key)
240             avahi_key_unref(l->cname_key);
241         avahi_free(l);
242         return NULL;
243     }
244
245     l->n_send = 1;
246     
247     l->time_event = avahi_time_event_new(e->server->time_event_queue, avahi_elapse_time(&tv, 500, 0), sender_timeout_callback, l);
248
249     avahi_hashmap_insert(e->lookups_by_id, &l->id, l);
250
251     t = avahi_hashmap_lookup(e->lookups_by_key, l->key);
252     AVAHI_LLIST_PREPEND(AvahiWideAreaLookup, by_key, t, l);
253     avahi_hashmap_replace(e->lookups_by_key, avahi_key_ref(l->key), t);
254
255     AVAHI_LLIST_PREPEND(AvahiWideAreaLookup, lookups, e->lookups, l);
256     
257     return l;
258 }
259
260 static void lookup_stop(AvahiWideAreaLookup *l) {
261     assert(l);
262     
263     l->callback = NULL;
264
265     if (l->time_event) {
266         avahi_time_event_free(l->time_event);
267         l->time_event = NULL;
268     }
269 }
270
271 static void lookup_destroy(AvahiWideAreaLookup *l) {
272     AvahiWideAreaLookup *t;
273     assert(l);
274     
275     lookup_stop(l);
276
277     t = avahi_hashmap_lookup(l->engine->lookups_by_key, l->key);
278     AVAHI_LLIST_REMOVE(AvahiWideAreaLookup, by_key, t, l);
279     if (t)
280         avahi_hashmap_replace(l->engine->lookups_by_key, avahi_key_ref(l->key), t);
281     else
282         avahi_hashmap_remove(l->engine->lookups_by_key, l->key);
283
284     AVAHI_LLIST_REMOVE(AvahiWideAreaLookup, lookups, l->engine->lookups, l);
285     
286     avahi_hashmap_remove(l->engine->lookups_by_id, &l->id);
287     avahi_dns_packet_free(l->packet);
288
289     if (l->key)
290         avahi_key_unref(l->key);
291
292     if (l->cname_key)
293         avahi_key_unref(l->cname_key);
294     
295     avahi_free(l);
296 }
297
298 void avahi_wide_area_lookup_free(AvahiWideAreaLookup *l) {
299     assert(l);
300
301     if (l->dead)
302         return;
303
304     l->dead = 1;
305     l->engine->cleanup_dead = 1;
306     lookup_stop(l);
307 }
308
309 void avahi_wide_area_cleanup(AvahiWideAreaLookupEngine *e) {
310     AvahiWideAreaLookup *l, *n;
311     assert(e);
312
313     while (e->cleanup_dead) {
314         e->cleanup_dead = 0;
315     
316         for (l = e->lookups; l; l = n) {
317             n = l->lookups_next;
318             
319             if (l->dead)
320                 lookup_destroy(l);
321         }
322     }
323 }
324
325 static void cache_entry_free(AvahiWideAreaCacheEntry *c) {
326     AvahiWideAreaCacheEntry *t;
327     assert(c);
328
329     if (c->time_event)
330         avahi_time_event_free(c->time_event);
331
332     AVAHI_LLIST_REMOVE(AvahiWideAreaCacheEntry, cache, c->engine->cache, c);
333
334     t = avahi_hashmap_lookup(c->engine->cache_by_key, c->record->key);
335     AVAHI_LLIST_REMOVE(AvahiWideAreaCacheEntry, by_key, t, c);
336     if (t)
337         avahi_hashmap_replace(c->engine->cache_by_key, avahi_key_ref(c->record->key), t);
338     else
339         avahi_hashmap_remove(c->engine->cache_by_key, c->record->key);
340
341     c->engine->cache_n_entries --;
342
343     avahi_record_unref(c->record);
344     avahi_free(c);
345 }
346
347 static void expiry_event(AvahiTimeEvent *te, void *userdata) {
348     AvahiWideAreaCacheEntry *e = userdata;
349     
350     assert(te);
351     assert(e);
352
353     cache_entry_free(e);
354 }
355
356 static AvahiWideAreaCacheEntry* find_record_in_cache(AvahiWideAreaLookupEngine *e, AvahiRecord *r) {
357     AvahiWideAreaCacheEntry *c;
358     
359     assert(e);
360     assert(r);
361
362     for (c = avahi_hashmap_lookup(e->cache_by_key, r->key); c; c = c->by_key_next)
363         if (avahi_record_equal_no_ttl(r, c->record))
364             return c;
365
366     return NULL;
367 }
368
369 static void run_callbacks(AvahiWideAreaLookupEngine *e, AvahiRecord *r) {
370     AvahiWideAreaLookup *l;
371     
372     assert(e);
373     assert(r);
374
375     for (l = avahi_hashmap_lookup(e->lookups_by_key, r->key); l; l = l->by_key_next) {
376         if (l->dead || !l->callback)
377             continue;
378         
379         l->callback(e, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_WIDE_AREA, r, l->userdata);
380     }
381     
382     if (r->key->clazz == AVAHI_DNS_CLASS_IN && r->key->type == AVAHI_DNS_TYPE_CNAME) {
383         /* It's a CNAME record, so we have to scan the all lookups to see if one matches */
384
385         for (l = e->lookups; l; l = l->lookups_next) {
386             AvahiKey *key;
387
388             if (l->dead || !l->callback)
389                 continue;
390             
391             if ((key = avahi_key_new_cname(l->key))) {
392                 if (avahi_key_equal(r->key, key))
393                     l->callback(e, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_WIDE_AREA, r, l->userdata);
394
395                 avahi_key_unref(key);
396             }
397         }
398     }
399 }
400
401 static void add_to_cache(AvahiWideAreaLookupEngine *e, AvahiRecord *r) {
402     AvahiWideAreaCacheEntry *c;
403     int is_new;
404     
405     assert(e);
406     assert(r);
407
408     if ((c = find_record_in_cache(e, r))) {
409         is_new = 0;
410
411         /* Update the existing entry */
412         avahi_record_unref(c->record);
413     } else {
414         AvahiWideAreaCacheEntry *t;
415
416         is_new = 1;
417
418         /* Enforce cache size */
419         if (e->cache_n_entries >= MAX_CACHE_ENTRIES)
420             /* Eventually we should improve the caching algorithm here */
421             goto finish;
422         
423         c = avahi_new(AvahiWideAreaCacheEntry, 1);
424         c->engine = e;
425         c->time_event = NULL;
426
427         AVAHI_LLIST_PREPEND(AvahiWideAreaCacheEntry, cache, e->cache, c);
428
429         /* Add the new entry to the cache entry hash table */
430         t = avahi_hashmap_lookup(e->cache_by_key, r->key);
431         AVAHI_LLIST_PREPEND(AvahiWideAreaCacheEntry, by_key, t, c);
432         avahi_hashmap_replace(e->cache_by_key, avahi_key_ref(r->key), t);
433
434         e->cache_n_entries ++;
435     }
436
437     c->record = avahi_record_ref(r);
438     
439     gettimeofday(&c->timestamp, NULL);
440     c->expiry = c->timestamp;
441     avahi_timeval_add(&c->expiry, r->ttl * 1000000);
442
443     if (c->time_event)
444         avahi_time_event_update(c->time_event, &c->expiry);
445     else
446         c->time_event = avahi_time_event_new(e->server->time_event_queue, &c->expiry, expiry_event, c);
447
448 finish:
449     
450     if (is_new)
451         run_callbacks(e, r);
452 }
453
454 static int map_dns_error(uint16_t error) {
455     static const int table[16] = {
456         AVAHI_OK,
457         AVAHI_ERR_DNS_FORMERR,
458         AVAHI_ERR_DNS_SERVFAIL,
459         AVAHI_ERR_DNS_NXDOMAIN,
460         AVAHI_ERR_DNS_NOTIMP,
461         AVAHI_ERR_DNS_REFUSED,
462         AVAHI_ERR_DNS_YXDOMAIN,
463         AVAHI_ERR_DNS_YXRRSET,
464         AVAHI_ERR_DNS_NXRRSET,
465         AVAHI_ERR_DNS_NOTAUTH,
466         AVAHI_ERR_DNS_NOTZONE,
467         AVAHI_ERR_INVALID_DNS_ERROR,
468         AVAHI_ERR_INVALID_DNS_ERROR,
469         AVAHI_ERR_INVALID_DNS_ERROR,
470         AVAHI_ERR_INVALID_DNS_ERROR,
471         AVAHI_ERR_INVALID_DNS_ERROR
472     };
473
474     assert(error <= 15);
475
476     return table[error];
477 }
478
479 static void handle_packet(AvahiWideAreaLookupEngine *e, AvahiDnsPacket *p, AvahiAddress *a) {
480     AvahiWideAreaLookup *l = NULL;
481     int i, r;
482
483     AvahiBrowserEvent final_event = AVAHI_BROWSER_ALL_FOR_NOW;
484     
485     assert(e);
486     assert(p);
487
488     /* Some superficial validity tests */
489     if (avahi_dns_packet_check_valid(p) < 0 || avahi_dns_packet_is_query(p)) {
490         avahi_log_warn(__FILE__": Ignoring invalid response for wide area datagram.");
491         goto finish;
492     }
493
494     /* Look for the lookup that issued this query */
495     if (!(l = find_lookup(e, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ID))) || l->dead)
496         goto finish;
497
498     /* Check whether this a packet indicating a failure */
499     if ((r = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) & 15) != 0 ||
500         avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) == 0) {
501
502         avahi_server_set_errno(e->server, r == 0 ? AVAHI_ERR_NOT_FOUND : map_dns_error(r));
503         /* Tell the user about the failure */
504         final_event = AVAHI_BROWSER_FAILURE;
505
506         /* We go on here, since some of the records contained in the
507            reply might be interesting in some way */
508     }
509
510     /* Skip over the question */
511     for (i = (int) avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT); i > 0; i--) {
512         AvahiKey *k;
513         
514         if (!(k = avahi_dns_packet_consume_key(p, NULL))) {
515             avahi_log_warn(__FILE__": Wide area response packet too short.");
516             avahi_server_set_errno(e->server, AVAHI_ERR_INVALID_PACKET);
517             final_event = AVAHI_BROWSER_FAILURE;
518             goto finish;
519         }
520
521         avahi_key_unref(k);
522     }
523
524     /* Process responses */
525     for (i = (int) avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) +
526              (int) avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) +
527              (int) avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ARCOUNT); i > 0; i--) {
528
529         AvahiRecord *rr;
530
531         if (!(rr = avahi_dns_packet_consume_record(p, NULL))) {
532             avahi_log_warn(__FILE__": Wide area response packet too short (2).");
533             avahi_server_set_errno(e->server, AVAHI_ERR_INVALID_PACKET);
534             final_event = AVAHI_BROWSER_FAILURE;
535             goto finish;
536         }
537
538         add_to_cache(e, rr);
539         avahi_record_unref(rr);
540     }
541
542 finish:
543     
544     if (l && !l->dead) {
545         if (l->callback)
546             l->callback(e, final_event, AVAHI_LOOKUP_RESULT_WIDE_AREA, NULL, l->userdata);
547
548         lookup_stop(l);
549     }
550 }
551
552 static void socket_event(AvahiWatch *w, int fd, AvahiWatchEvent events, void *userdata) {
553     AvahiWideAreaLookupEngine *e = userdata;
554     AvahiAddress a;
555     AvahiDnsPacket *p = NULL;
556     
557     if (fd == e->fd_ipv4) {
558         struct sockaddr_in sa;
559             
560         if ((p = avahi_recv_dns_packet_ipv4(e->fd_ipv4, &sa, NULL, NULL, NULL)))
561             avahi_address_from_sockaddr((struct sockaddr*) &sa, &a);
562             
563     } else if (fd == e->fd_ipv6) {
564         struct sockaddr_in6 sa6;
565         
566         if ((p = avahi_recv_dns_packet_ipv6(e->fd_ipv6, &sa6, NULL, NULL, NULL)))
567             avahi_address_from_sockaddr((struct sockaddr*) &sa6, &a);
568
569     }
570
571     if (p) {
572         handle_packet(e, p, &a);
573         avahi_dns_packet_free(p);
574     }
575 }
576
577 AvahiWideAreaLookupEngine *avahi_wide_area_engine_new(AvahiServer *s) {
578     AvahiWideAreaLookupEngine *e;
579     
580     assert(s);
581
582     e = avahi_new(AvahiWideAreaLookupEngine, 1);
583     e->server = s;
584     e->cleanup_dead = 0;
585
586     /* Create sockets */
587     e->fd_ipv4 = avahi_open_unicast_socket_ipv4();
588     e->fd_ipv6 = avahi_open_unicast_socket_ipv6();
589
590     if (e->fd_ipv4 < 0 && e->fd_ipv6 < 0) {
591         avahi_log_error(__FILE__": Failed to create wide area sockets: %s", strerror(errno));
592
593         if (e->fd_ipv6 >= 0)
594             close(e->fd_ipv6);
595
596         if (e->fd_ipv4 >= 0)
597             close(e->fd_ipv4);
598         
599         avahi_free(e);
600         return NULL;
601     }
602
603     /* Create watches */
604     if (e->fd_ipv4 >= 0)
605         e->watch_ipv4 = s->poll_api->watch_new(e->server->poll_api, e->fd_ipv4, AVAHI_WATCH_IN, socket_event, e);
606     if (e->fd_ipv6 >= 0)
607         e->watch_ipv6 = s->poll_api->watch_new(e->server->poll_api, e->fd_ipv6, AVAHI_WATCH_IN, socket_event, e);
608
609     e->n_dns_servers = e->current_dns_server = 0;
610     e->next_id = (uint16_t) rand();
611
612     /* Initialize cache */
613     AVAHI_LLIST_HEAD_INIT(AvahiWideAreaCacheEntry, e->cache);
614     e->cache_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, (AvahiFreeFunc) avahi_key_unref, NULL);
615     e->cache_n_entries = 0;
616
617     /* Initialize lookup list */
618     e->lookups_by_id = avahi_hashmap_new((AvahiHashFunc) avahi_int_hash, (AvahiEqualFunc) avahi_int_equal, NULL, NULL);
619     e->lookups_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, (AvahiFreeFunc) avahi_key_unref, NULL);
620     AVAHI_LLIST_HEAD_INIT(AvahiWideAreaLookup, e->lookups);
621
622     return e;
623 }
624
625 void avahi_wide_area_engine_free(AvahiWideAreaLookupEngine *e) {
626     assert(e);
627     
628     avahi_wide_area_clear_cache(e);
629
630     while (e->lookups)
631         lookup_destroy(e->lookups);
632     
633     avahi_hashmap_free(e->cache_by_key);
634     avahi_hashmap_free(e->lookups_by_id);
635     avahi_hashmap_free(e->lookups_by_key);
636
637     if (e->watch_ipv4)
638         e->server->poll_api->watch_free(e->watch_ipv4);
639
640     if (e->watch_ipv6)
641         e->server->poll_api->watch_free(e->watch_ipv6);
642
643     if (e->fd_ipv6 >= 0)
644         close(e->fd_ipv6);
645     
646     if (e->fd_ipv4 >= 0)
647         close(e->fd_ipv4);
648
649     avahi_free(e);
650 }
651
652 void avahi_wide_area_clear_cache(AvahiWideAreaLookupEngine *e) {
653     assert(e);
654
655     while (e->cache)
656         cache_entry_free(e->cache);
657
658     assert(e->cache_n_entries == 0);
659 }
660
661 void avahi_wide_area_set_servers(AvahiWideAreaLookupEngine *e, const AvahiAddress *a, unsigned n) {
662     assert(e);
663
664     if (a) {
665         for (e->n_dns_servers = 0; n > 0 && e->n_dns_servers < AVAHI_MAX_WIDE_AREA_SERVERS; a++, n--) 
666             if ((a->proto == AVAHI_PROTO_INET && e->fd_ipv4 >= 0) || (a->proto == AVAHI_PROTO_INET6 && e->fd_ipv6 >= 0))
667                 e->dns_servers[e->n_dns_servers++] = *a;
668     } else {
669         assert(n == 0);
670         e->n_dns_servers = 0;
671     }
672     
673     e->current_dns_server = 0;
674
675     avahi_wide_area_clear_cache(e);
676 }
677
678 void avahi_wide_area_cache_dump(AvahiWideAreaLookupEngine *e, AvahiDumpCallback callback, void* userdata) {
679     AvahiWideAreaCacheEntry *c;
680     
681     assert(e);
682     assert(callback);
683
684     callback(";; WIDE AREA CACHE ;;; ", userdata);
685     
686     for (c = e->cache; c; c = c->cache_next) {
687         char *t = avahi_record_to_string(c->record);
688         callback(t, userdata);
689         avahi_free(t);
690     }
691 }
692
693 unsigned avahi_wide_area_scan_cache(AvahiWideAreaLookupEngine *e, AvahiKey *key, AvahiWideAreaLookupCallback callback, void *userdata) {
694     AvahiWideAreaCacheEntry *c;
695     AvahiKey *cname_key;
696     unsigned n = 0;
697     
698     assert(e);
699     assert(key);
700     assert(callback);
701
702     for (c = avahi_hashmap_lookup(e->cache_by_key, key); c; c = c->by_key_next) {
703         callback(e, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_WIDE_AREA|AVAHI_LOOKUP_RESULT_CACHED, c->record, userdata);
704         n++;
705     }
706
707     if ((cname_key = avahi_key_new_cname(key))) {
708
709         for (c = avahi_hashmap_lookup(e->cache_by_key, cname_key); c; c = c->by_key_next) {
710             callback(e, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_WIDE_AREA|AVAHI_LOOKUP_RESULT_CACHED, c->record, userdata);
711             n++;
712         }
713         
714         avahi_key_unref(cname_key);
715     }
716
717     return n;
718 }
719
720 int avahi_wide_area_has_servers(AvahiWideAreaLookupEngine *e) {
721     assert(e);
722
723     return e->n_dns_servers > 0;
724 }
725
726
727