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