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