1 /***
2 This file is part of avahi.
3
4 avahi is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
8
9 avahi is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
12 Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with avahi; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17 USA.
18 ***/
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <string.h>
25 #include <stdlib.h>
26 #include <time.h>
27
28 #include <avahi-common/timeval.h>
29 #include "avahi-common/avahi-malloc.h"
30
31 #include "cache.h"
32 #include "log.h"
33 #include "rr-util.h"
34
remove_entry(AvahiCache * c,AvahiCacheEntry * e)35 static void remove_entry(AvahiCache *c, AvahiCacheEntry *e) {
36 AvahiCacheEntry *t;
37
38 assert(c);
39 assert(e);
40
41 /* avahi_log_debug("removing from cache: %p %p", c, e); */
42
43 /* Remove from hash table */
44 t = avahi_hashmap_lookup(c->hashmap, e->record->key);
45 AVAHI_LLIST_REMOVE(AvahiCacheEntry, by_key, t, e);
46 if (t)
47 avahi_hashmap_replace(c->hashmap, t->record->key, t);
48 else
49 avahi_hashmap_remove(c->hashmap, e->record->key);
50
51 /* Remove from linked list */
52 AVAHI_LLIST_REMOVE(AvahiCacheEntry, entry, c->entries, e);
53
54 if (e->time_event)
55 avahi_time_event_free(e->time_event);
56
57 avahi_multicast_lookup_engine_notify(c->server->multicast_lookup_engine, c->interface, e->record, AVAHI_BROWSER_REMOVE);
58
59 avahi_record_unref(e->record);
60
61 avahi_free(e);
62
63 assert(c->n_entries >= 1);
64 --c->n_entries;
65 }
66
avahi_cache_new(AvahiServer * server,AvahiInterface * iface)67 AvahiCache *avahi_cache_new(AvahiServer *server, AvahiInterface *iface) {
68 AvahiCache *c;
69 assert(server);
70
71 if (!(c = avahi_new(AvahiCache, 1))) {
72 avahi_log_error(__FILE__": Out of memory.");
73 return NULL; /* OOM */
74 }
75
76 c->server = server;
77 c->interface = iface;
78
79 if (!(c->hashmap = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, NULL, NULL))) {
80 avahi_log_error(__FILE__": Out of memory.");
81 avahi_free(c);
82 return NULL; /* OOM */
83 }
84
85 AVAHI_LLIST_HEAD_INIT(AvahiCacheEntry, c->entries);
86 c->n_entries = 0;
87
88 c->last_rand_timestamp = 0;
89
90 return c;
91 }
92
avahi_cache_free(AvahiCache * c)93 void avahi_cache_free(AvahiCache *c) {
94 assert(c);
95
96 while (c->entries)
97 remove_entry(c, c->entries);
98 assert(c->n_entries == 0);
99
100 avahi_hashmap_free(c->hashmap);
101
102 avahi_free(c);
103 }
104
lookup_key(AvahiCache * c,AvahiKey * k)105 static AvahiCacheEntry *lookup_key(AvahiCache *c, AvahiKey *k) {
106 assert(c);
107 assert(k);
108
109 assert(!avahi_key_is_pattern(k));
110
111 return avahi_hashmap_lookup(c->hashmap, k);
112 }
113
avahi_cache_walk(AvahiCache * c,AvahiKey * pattern,AvahiCacheWalkCallback cb,void * userdata)114 void* avahi_cache_walk(AvahiCache *c, AvahiKey *pattern, AvahiCacheWalkCallback cb, void* userdata) {
115 void* ret;
116
117 assert(c);
118 assert(pattern);
119 assert(cb);
120
121 if (avahi_key_is_pattern(pattern)) {
122 AvahiCacheEntry *e, *n;
123
124 for (e = c->entries; e; e = n) {
125 n = e->entry_next;
126
127 if (avahi_key_pattern_match(pattern, e->record->key))
128 if ((ret = cb(c, pattern, e, userdata)))
129 return ret;
130 }
131
132 } else {
133 AvahiCacheEntry *e, *n;
134
135 for (e = lookup_key(c, pattern); e; e = n) {
136 n = e->by_key_next;
137
138 if ((ret = cb(c, pattern, e, userdata)))
139 return ret;
140 }
141 }
142
143 return NULL;
144 }
145
lookup_record_callback(AvahiCache * c,AvahiKey * pattern,AvahiCacheEntry * e,void * userdata)146 static void* lookup_record_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void *userdata) {
147 assert(c);
148 assert(pattern);
149 assert(e);
150
151 if (avahi_record_equal_no_ttl(e->record, userdata))
152 return e;
153
154 return NULL;
155 }
156
lookup_record(AvahiCache * c,AvahiRecord * r)157 static AvahiCacheEntry *lookup_record(AvahiCache *c, AvahiRecord *r) {
158 assert(c);
159 assert(r);
160
161 return avahi_cache_walk(c, r->key, lookup_record_callback, r);
162 }
163
164 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, unsigned percent);
165
elapse_func(AvahiTimeEvent * t,void * userdata)166 static void elapse_func(AvahiTimeEvent *t, void *userdata) {
167 AvahiCacheEntry *e = userdata;
168 /* char *txt; */
169 unsigned percent = 0;
170
171 assert(t);
172 assert(e);
173
174 /* txt = avahi_record_to_string(e->record); */
175
176 switch (e->state) {
177
178 case AVAHI_CACHE_EXPIRY_FINAL:
179 case AVAHI_CACHE_POOF_FINAL:
180 case AVAHI_CACHE_GOODBYE_FINAL:
181 case AVAHI_CACHE_REPLACE_FINAL:
182
183 remove_entry(e->cache, e);
184
185 e = NULL;
186 /* avahi_log_debug("Removing entry from cache due to expiration (%s)", txt); */
187 break;
188
189 case AVAHI_CACHE_VALID:
190 case AVAHI_CACHE_POOF:
191 e->state = AVAHI_CACHE_EXPIRY1;
192 percent = 85;
193 break;
194
195 case AVAHI_CACHE_EXPIRY1:
196 e->state = AVAHI_CACHE_EXPIRY2;
197 percent = 90;
198 break;
199 case AVAHI_CACHE_EXPIRY2:
200 e->state = AVAHI_CACHE_EXPIRY3;
201 percent = 95;
202 break;
203
204 case AVAHI_CACHE_EXPIRY3:
205 e->state = AVAHI_CACHE_EXPIRY_FINAL;
206 percent = 100;
207 break;
208 }
209
210 if (e) {
211
212 assert(percent > 0);
213
214 /* Request a cache update if we are subscribed to this entry */
215 if (avahi_querier_shall_refresh_cache(e->cache->interface, e->record->key))
216 avahi_interface_post_query(e->cache->interface, e->record->key, 0, NULL);
217
218 /* Check again later */
219 next_expiry(e->cache, e, percent);
220
221 }
222
223 /* avahi_free(txt); */
224 }
225
update_time_event(AvahiCache * c,AvahiCacheEntry * e)226 static void update_time_event(AvahiCache *c, AvahiCacheEntry *e) {
227 assert(c);
228 assert(e);
229
230 if (e->time_event)
231 avahi_time_event_update(e->time_event, &e->expiry);
232 else
233 e->time_event = avahi_time_event_new(c->server->time_event_queue, &e->expiry, elapse_func, e);
234 }
235
next_expiry(AvahiCache * c,AvahiCacheEntry * e,unsigned percent)236 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, unsigned percent) {
237 AvahiUsec usec, left, right;
238 time_t now;
239
240 assert(c);
241 assert(e);
242 assert(percent > 0 && percent <= 100);
243
244 usec = (AvahiUsec) e->record->ttl * 10000;
245
246 left = usec * percent;
247 right = usec * (percent+2); /* 2% jitter */
248
249 now = time(NULL);
250
251 if (now >= c->last_rand_timestamp + 10) {
252 c->last_rand = rand();
253 c->last_rand_timestamp = now;
254 }
255
256 usec = left + (AvahiUsec) ((double) (right-left) * c->last_rand / (RAND_MAX+1.0));
257
258 e->expiry = e->timestamp;
259 avahi_timeval_add(&e->expiry, usec);
260
261 /* g_message("wake up in +%lu seconds", e->expiry.tv_sec - e->timestamp.tv_sec); */
262
263 update_time_event(c, e);
264 }
265
expire_in_one_second(AvahiCache * c,AvahiCacheEntry * e,AvahiCacheEntryState state)266 static void expire_in_one_second(AvahiCache *c, AvahiCacheEntry *e, AvahiCacheEntryState state) {
267 assert(c);
268 assert(e);
269
270 e->state = state;
271 gettimeofday(&e->expiry, NULL);
272 avahi_timeval_add(&e->expiry, 1000000); /* 1s */
273 update_time_event(c, e);
274 }
275
avahi_cache_update(AvahiCache * c,AvahiRecord * r,int cache_flush,const AvahiAddress * a)276 void avahi_cache_update(AvahiCache *c, AvahiRecord *r, int cache_flush, const AvahiAddress *a) {
277 /* char *txt; */
278
279 assert(c);
280 assert(r && r->ref >= 1);
281
282 /* txt = avahi_record_to_string(r); */
283
284 if (r->ttl == 0) {
285 /* This is a goodbye request */
286
287 AvahiCacheEntry *e;
288
289 if ((e = lookup_record(c, r)))
290 expire_in_one_second(c, e, AVAHI_CACHE_GOODBYE_FINAL);
291
292 } else {
293 AvahiCacheEntry *e = NULL, *first;
294 struct timeval now;
295
296 gettimeofday(&now, NULL);
297
298 /* This is an update request */
299
300 if ((first = lookup_key(c, r->key))) {
301
302 if (cache_flush) {
303
304 /* For unique entries drop all entries older than one second */
305 for (e = first; e; e = e->by_key_next) {
306 AvahiUsec t;
307
308 t = avahi_timeval_diff(&now, &e->timestamp);
309
310 if (t > 1000000)
311 expire_in_one_second(c, e, AVAHI_CACHE_REPLACE_FINAL);
312 }
313 }
314
315 /* Look for exactly the same entry */
316 for (e = first; e; e = e->by_key_next)
317 if (avahi_record_equal_no_ttl(e->record, r))
318 break;
319 }
320
321 if (e) {
322
323 /* avahi_log_debug("found matching cache entry"); */
324
325 /* We need to update the hash table key if we replace the
326 * record */
327 if (e->by_key_prev == NULL)
328 avahi_hashmap_replace(c->hashmap, r->key, e);
329
330 /* Update the record */
331 avahi_record_unref(e->record);
332 e->record = avahi_record_ref(r);
333
334 /* avahi_log_debug("cache: updating %s", txt); */
335
336 } else {
337 /* No entry found, therefore we create a new one */
338
339 /* avahi_log_debug("cache: couldn't find matching cache entry for %s", txt); */
340
341 if (c->n_entries >= c->server->config.n_cache_entries_max)
342 return;
343
344 if (!(e = avahi_new(AvahiCacheEntry, 1))) {
345 avahi_log_error(__FILE__": Out of memory");
346 return;
347 }
348
349 e->cache = c;
350 e->time_event = NULL;
351 e->record = avahi_record_ref(r);
352
353 /* Append to hash table */
354 AVAHI_LLIST_PREPEND(AvahiCacheEntry, by_key, first, e);
355 avahi_hashmap_replace(c->hashmap, e->record->key, first);
356
357 /* Append to linked list */
358 AVAHI_LLIST_PREPEND(AvahiCacheEntry, entry, c->entries, e);
359
360 c->n_entries++;
361
362 /* Notify subscribers */
363 avahi_multicast_lookup_engine_notify(c->server->multicast_lookup_engine, c->interface, e->record, AVAHI_BROWSER_NEW);
364 }
365
366 e->origin = *a;
367 e->timestamp = now;
368 next_expiry(c, e, 80);
369 e->state = AVAHI_CACHE_VALID;
370 e->cache_flush = cache_flush;
371 }
372
373 /* avahi_free(txt); */
374 }
375
376 struct dump_data {
377 AvahiDumpCallback callback;
378 void* userdata;
379 };
380
dump_callback(void * key,void * data,void * userdata)381 static void dump_callback(void* key, void* data, void* userdata) {
382 AvahiCacheEntry *e = data;
383 AvahiKey *k = key;
384 struct dump_data *dump_data = userdata;
385
386 assert(k);
387 assert(e);
388 assert(data);
389
390 for (; e; e = e->by_key_next) {
391 char *t;
392
393 if (!(t = avahi_record_to_string(e->record)))
394 continue; /* OOM */
395
396 dump_data->callback(t, dump_data->userdata);
397 avahi_free(t);
398 }
399 }
400
avahi_cache_dump(AvahiCache * c,AvahiDumpCallback callback,void * userdata)401 int avahi_cache_dump(AvahiCache *c, AvahiDumpCallback callback, void* userdata) {
402 struct dump_data data;
403
404 assert(c);
405 assert(callback);
406
407 callback(";;; CACHE DUMP FOLLOWS ;;;", userdata);
408
409 data.callback = callback;
410 data.userdata = userdata;
411
412 avahi_hashmap_foreach(c->hashmap, dump_callback, &data);
413
414 return 0;
415 }
416
avahi_cache_entry_half_ttl(AvahiCache * c,AvahiCacheEntry * e)417 int avahi_cache_entry_half_ttl(AvahiCache *c, AvahiCacheEntry *e) {
418 struct timeval now;
419 unsigned age;
420
421 assert(c);
422 assert(e);
423
424 gettimeofday(&now, NULL);
425
426 age = (unsigned) (avahi_timeval_diff(&now, &e->timestamp)/1000000);
427
428 /* avahi_log_debug("age: %lli, ttl/2: %u", age, e->record->ttl); */
429
430 return age >= e->record->ttl/2;
431 }
432
avahi_cache_flush(AvahiCache * c)433 void avahi_cache_flush(AvahiCache *c) {
434 assert(c);
435
436 while (c->entries)
437 remove_entry(c, c->entries);
438 }
439
440 /*** Passive observation of failure ***/
441
start_poof_callback(AvahiCache * c,AvahiKey * pattern,AvahiCacheEntry * e,void * userdata)442 static void* start_poof_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void *userdata) {
443 AvahiAddress *a = userdata;
444 struct timeval now;
445
446 assert(c);
447 assert(pattern);
448 assert(e);
449 assert(a);
450
451 gettimeofday(&now, NULL);
452
453 switch (e->state) {
454 case AVAHI_CACHE_VALID:
455
456 /* The entry was perfectly valid till, now, so let's enter
457 * POOF mode */
458
459 e->state = AVAHI_CACHE_POOF;
460 e->poof_address = *a;
461 e->poof_timestamp = now;
462 e->poof_num = 0;
463
464 break;
465
466 case AVAHI_CACHE_POOF:
467 if (avahi_timeval_diff(&now, &e->poof_timestamp) < 1000000)
468 break;
469
470 e->poof_timestamp = now;
471 e->poof_address = *a;
472 e->poof_num ++;
473
474 /* This is the 4th time we got no response, so let's
475 * fucking remove this entry. */
476 if (e->poof_num > 3)
477 expire_in_one_second(c, e, AVAHI_CACHE_POOF_FINAL);
478 break;
479
480 default:
481 ;
482 }
483
484 return NULL;
485 }
486
avahi_cache_start_poof(AvahiCache * c,AvahiKey * key,const AvahiAddress * a)487 void avahi_cache_start_poof(AvahiCache *c, AvahiKey *key, const AvahiAddress *a) {
488 assert(c);
489 assert(key);
490
491 avahi_cache_walk(c, key, start_poof_callback, (void*) a);
492 }
493
avahi_cache_stop_poof(AvahiCache * c,AvahiRecord * record,const AvahiAddress * a)494 void avahi_cache_stop_poof(AvahiCache *c, AvahiRecord *record, const AvahiAddress *a) {
495 AvahiCacheEntry *e;
496
497 assert(c);
498 assert(record);
499 assert(a);
500
501 if (!(e = lookup_record(c, record)))
502 return;
503
504 /* This function is called for each response suppression
505 record. If the matching cache entry is in POOF state and the
506 query address is the same, we put it back into valid mode */
507
508 if (e->state == AVAHI_CACHE_POOF || e->state == AVAHI_CACHE_POOF_FINAL)
509 if (avahi_address_cmp(a, &e->poof_address) == 0) {
510 e->state = AVAHI_CACHE_VALID;
511 next_expiry(c, e, 80);
512 }
513 }
514