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