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 <stdlib.h>
27
28#include <avahi-common/timeval.h>
29#include <avahi-common/malloc.h>
30
31#include "announce.h"
32#include "log.h"
33#include "rr-util.h"
34
35#define AVAHI_ANNOUNCEMENT_JITTER_MSEC 250
36#define AVAHI_PROBE_JITTER_MSEC 250
37#define AVAHI_PROBE_INTERVAL_MSEC 250
38
39static void remove_announcer(AvahiServer *s, AvahiAnnouncer *a) {
40    assert(s);
41    assert(a);
42
43    if (a->time_event)
44        avahi_time_event_free(a->time_event);
45
46    AVAHI_LLIST_REMOVE(AvahiAnnouncer, by_interface, a->interface->announcers, a);
47    AVAHI_LLIST_REMOVE(AvahiAnnouncer, by_entry, a->entry->announcers, a);
48
49    avahi_free(a);
50}
51
52static void elapse_announce(AvahiTimeEvent *e, void *userdata);
53
54static void set_timeout(AvahiAnnouncer *a, const struct timeval *tv) {
55    assert(a);
56
57    if (!tv) {
58        if (a->time_event) {
59            avahi_time_event_free(a->time_event);
60            a->time_event = NULL;
61        }
62    } else {
63
64        if (a->time_event)
65            avahi_time_event_update(a->time_event, tv);
66        else
67            a->time_event = avahi_time_event_new(a->server->time_event_queue, tv, elapse_announce, a);
68    }
69}
70
71static void next_state(AvahiAnnouncer *a);
72
73void avahi_s_entry_group_check_probed(AvahiSEntryGroup *g, int immediately) {
74    AvahiEntry *e;
75    assert(g);
76    assert(!g->dead);
77
78    /* Check whether all group members have been probed */
79
80    if (g->state != AVAHI_ENTRY_GROUP_REGISTERING || g->n_probing > 0)
81        return;
82
83    avahi_s_entry_group_change_state(g, AVAHI_ENTRY_GROUP_ESTABLISHED);
84
85    if (g->dead)
86        return;
87
88    for (e = g->entries; e; e = e->by_group_next) {
89        AvahiAnnouncer *a;
90
91        for (a = e->announcers; a; a = a->by_entry_next) {
92
93            if (a->state != AVAHI_WAITING)
94                continue;
95
96            a->state = AVAHI_ANNOUNCING;
97
98            if (immediately) {
99                /* Shortcut */
100
101                a->n_iteration = 1;
102                next_state(a);
103            } else {
104                struct timeval tv;
105                a->n_iteration = 0;
106                avahi_elapse_time(&tv, 0, AVAHI_ANNOUNCEMENT_JITTER_MSEC);
107                set_timeout(a, &tv);
108            }
109        }
110    }
111}
112
113static void next_state(AvahiAnnouncer *a) {
114    assert(a);
115
116    if (a->state == AVAHI_WAITING) {
117
118        assert(a->entry->group);
119
120        avahi_s_entry_group_check_probed(a->entry->group, 1);
121
122    } else if (a->state == AVAHI_PROBING) {
123
124        if (a->n_iteration >= 4) {
125            /* Probing done */
126
127            if (a->entry->group) {
128                assert(a->entry->group->n_probing);
129                a->entry->group->n_probing--;
130            }
131
132            if (a->entry->group && a->entry->group->state == AVAHI_ENTRY_GROUP_REGISTERING)
133                a->state = AVAHI_WAITING;
134            else {
135                a->state = AVAHI_ANNOUNCING;
136                a->n_iteration = 1;
137            }
138
139            set_timeout(a, NULL);
140            next_state(a);
141        } else {
142            struct timeval tv;
143
144            avahi_interface_post_probe(a->interface, a->entry->record, 0);
145
146            avahi_elapse_time(&tv, AVAHI_PROBE_INTERVAL_MSEC, 0);
147            set_timeout(a, &tv);
148
149            a->n_iteration++;
150        }
151
152    } else if (a->state == AVAHI_ANNOUNCING) {
153
154        if (a->entry->flags & AVAHI_PUBLISH_UNIQUE)
155            /* Send the whole rrset at once */
156            avahi_server_prepare_matching_responses(a->server, a->interface, a->entry->record->key, 0);
157        else
158            avahi_server_prepare_response(a->server, a->interface, a->entry, 0, 0);
159
160        avahi_server_generate_response(a->server, a->interface, NULL, NULL, 0, 0, 0);
161
162        if (++a->n_iteration >= 4) {
163            /* Announcing done */
164
165            a->state = AVAHI_ESTABLISHED;
166
167            set_timeout(a, NULL);
168        } else {
169            struct timeval tv;
170            avahi_elapse_time(&tv, a->sec_delay*1000, AVAHI_ANNOUNCEMENT_JITTER_MSEC);
171
172            if (a->n_iteration < 10)
173                a->sec_delay *= 2;
174
175            set_timeout(a, &tv);
176        }
177    }
178}
179
180static void elapse_announce(AvahiTimeEvent *e, void *userdata) {
181    assert(e);
182
183    next_state(userdata);
184}
185
186static AvahiAnnouncer *get_announcer(AvahiServer *s, AvahiEntry *e, AvahiInterface *i) {
187    AvahiAnnouncer *a;
188
189    assert(s);
190    assert(e);
191    assert(i);
192
193    for (a = e->announcers; a; a = a->by_entry_next)
194        if (a->interface == i)
195            return a;
196
197    return NULL;
198}
199
200static void go_to_initial_state(AvahiAnnouncer *a) {
201    AvahiEntry *e;
202    struct timeval tv;
203
204    assert(a);
205    e = a->entry;
206
207    if ((e->flags & AVAHI_PUBLISH_UNIQUE) && !(e->flags & AVAHI_PUBLISH_NO_PROBE))
208        a->state = AVAHI_PROBING;
209    else if (!(e->flags & AVAHI_PUBLISH_NO_ANNOUNCE)) {
210
211        if (!e->group || e->group->state == AVAHI_ENTRY_GROUP_ESTABLISHED)
212            a->state = AVAHI_ANNOUNCING;
213        else
214            a->state = AVAHI_WAITING;
215
216    } else
217        a->state = AVAHI_ESTABLISHED;
218
219    a->n_iteration = 1;
220    a->sec_delay = 1;
221
222    if (a->state == AVAHI_PROBING && e->group)
223        e->group->n_probing++;
224
225    if (a->state == AVAHI_PROBING)
226        set_timeout(a, avahi_elapse_time(&tv, 0, AVAHI_PROBE_JITTER_MSEC));
227    else if (a->state == AVAHI_ANNOUNCING)
228        set_timeout(a, avahi_elapse_time(&tv, 0, AVAHI_ANNOUNCEMENT_JITTER_MSEC));
229    else
230        set_timeout(a, NULL);
231}
232
233static void new_announcer(AvahiServer *s, AvahiInterface *i, AvahiEntry *e) {
234    AvahiAnnouncer *a;
235
236    assert(s);
237    assert(i);
238    assert(e);
239    assert(!e->dead);
240
241    if (!avahi_interface_match(i, e->interface, e->protocol) || !i->announcing || !avahi_entry_is_commited(e))
242        return;
243
244    /* We don't want duplicate announcers */
245    if (get_announcer(s, e, i))
246        return;
247
248    if ((!(a = avahi_new(AvahiAnnouncer, 1)))) {
249        avahi_log_error(__FILE__": Out of memory.");
250        return;
251    }
252
253    a->server = s;
254    a->interface = i;
255    a->entry = e;
256    a->time_event = NULL;
257
258    AVAHI_LLIST_PREPEND(AvahiAnnouncer, by_interface, i->announcers, a);
259    AVAHI_LLIST_PREPEND(AvahiAnnouncer, by_entry, e->announcers, a);
260
261    go_to_initial_state(a);
262}
263
264void avahi_announce_interface(AvahiServer *s, AvahiInterface *i) {
265    AvahiEntry *e;
266
267    assert(s);
268    assert(i);
269
270    if (!i->announcing)
271        return;
272
273    for (e = s->entries; e; e = e->entries_next)
274        if (!e->dead)
275            new_announcer(s, i, e);
276}
277
278static void announce_walk_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) {
279    AvahiEntry *e = userdata;
280
281    assert(m);
282    assert(i);
283    assert(e);
284    assert(!e->dead);
285
286    new_announcer(m->server, i, e);
287}
288
289void avahi_announce_entry(AvahiServer *s, AvahiEntry *e) {
290    assert(s);
291    assert(e);
292    assert(!e->dead);
293
294    avahi_interface_monitor_walk(s->monitor, e->interface, e->protocol, announce_walk_callback, e);
295}
296
297void avahi_announce_group(AvahiServer *s, AvahiSEntryGroup *g) {
298    AvahiEntry *e;
299
300    assert(s);
301    assert(g);
302
303    for (e = g->entries; e; e = e->by_group_next)
304        if (!e->dead)
305            avahi_announce_entry(s, e);
306}
307
308int avahi_entry_is_registered(AvahiServer *s, AvahiEntry *e, AvahiInterface *i) {
309    AvahiAnnouncer *a;
310
311    assert(s);
312    assert(e);
313    assert(i);
314    assert(!e->dead);
315
316    if (!(a = get_announcer(s, e, i)))
317        return 0;
318
319    return
320        a->state == AVAHI_ANNOUNCING ||
321        a->state == AVAHI_ESTABLISHED ||
322        (a->state == AVAHI_WAITING && !(e->flags & AVAHI_PUBLISH_UNIQUE));
323}
324
325int avahi_entry_is_probing(AvahiServer *s, AvahiEntry *e, AvahiInterface *i) {
326    AvahiAnnouncer *a;
327
328    assert(s);
329    assert(e);
330    assert(i);
331    assert(!e->dead);
332
333    if (!(a = get_announcer(s, e, i)))
334        return 0;
335
336    return
337        a->state == AVAHI_PROBING ||
338        (a->state == AVAHI_WAITING && (e->flags & AVAHI_PUBLISH_UNIQUE));
339}
340
341void avahi_entry_return_to_initial_state(AvahiServer *s, AvahiEntry *e, AvahiInterface *i) {
342    AvahiAnnouncer *a;
343
344    assert(s);
345    assert(e);
346    assert(i);
347
348    if (!(a = get_announcer(s, e, i)))
349        return;
350
351    if (a->state == AVAHI_PROBING && a->entry->group)
352        a->entry->group->n_probing--;
353
354    go_to_initial_state(a);
355}
356
357static AvahiRecord *make_goodbye_record(AvahiRecord *r) {
358    AvahiRecord *g;
359
360    assert(r);
361
362    if (!(g = avahi_record_copy(r)))
363        return NULL; /* OOM */
364
365    assert(g->ref == 1);
366    g->ttl = 0;
367
368    return g;
369}
370
371static int is_duplicate_entry(AvahiServer *s, AvahiEntry *e) {
372    AvahiEntry *i;
373
374    assert(s);
375    assert(e);
376
377    for (i = avahi_hashmap_lookup(s->entries_by_key, e->record->key); i; i = i->by_key_next) {
378
379        if (i == e)
380            continue;
381
382        if (!avahi_record_equal_no_ttl(i->record, e->record))
383            continue;
384
385        return 1;
386    }
387
388    return 0;
389}
390
391static void send_goodbye_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) {
392    AvahiEntry *e = userdata;
393    AvahiRecord *g;
394
395    assert(m);
396    assert(i);
397    assert(e);
398    assert(!e->dead);
399
400    if (!avahi_interface_match(i, e->interface, e->protocol))
401        return;
402
403    if (e->flags & AVAHI_PUBLISH_NO_ANNOUNCE)
404        return;
405
406    if (!avahi_entry_is_registered(m->server, e, i))
407        return;
408
409    if (is_duplicate_entry(m->server, e))
410        return;
411
412    if (!(g = make_goodbye_record(e->record)))
413        return; /* OOM */
414
415    avahi_interface_post_response(i, g, e->flags & AVAHI_PUBLISH_UNIQUE, NULL, 1);
416    avahi_record_unref(g);
417}
418
419static void reannounce(AvahiAnnouncer *a) {
420    AvahiEntry *e;
421    struct timeval tv;
422
423    assert(a);
424    e = a->entry;
425
426    /* If the group this entry belongs to is not even commited, there's nothing to reannounce */
427    if (e->group && (e->group->state == AVAHI_ENTRY_GROUP_UNCOMMITED || e->group->state == AVAHI_ENTRY_GROUP_COLLISION))
428        return;
429
430    /* Because we might change state we decrease the probing counter first */
431    if (a->state == AVAHI_PROBING && a->entry->group)
432        a->entry->group->n_probing--;
433
434    if (a->state == AVAHI_PROBING ||
435        (a->state == AVAHI_WAITING && (e->flags & AVAHI_PUBLISH_UNIQUE) && !(e->flags & AVAHI_PUBLISH_NO_PROBE)))
436
437        /* We were probing or waiting after probe, so we restart probing from the beginning here */
438
439        a->state = AVAHI_PROBING;
440    else if (a->state == AVAHI_WAITING)
441
442        /* We were waiting, but were not probing before, so we continue waiting  */
443        a->state = AVAHI_WAITING;
444
445    else if (e->flags & AVAHI_PUBLISH_NO_ANNOUNCE)
446
447        /* No announcer needed */
448        a->state = AVAHI_ESTABLISHED;
449
450    else {
451
452        /* Ok, let's restart announcing */
453        a->state = AVAHI_ANNOUNCING;
454    }
455
456    /* Now let's increase the probing counter again */
457    if (a->state == AVAHI_PROBING && e->group)
458        e->group->n_probing++;
459
460    a->n_iteration = 1;
461    a->sec_delay = 1;
462
463    if (a->state == AVAHI_PROBING)
464        set_timeout(a, avahi_elapse_time(&tv, 0, AVAHI_PROBE_JITTER_MSEC));
465    else if (a->state == AVAHI_ANNOUNCING)
466        set_timeout(a, avahi_elapse_time(&tv, 0, AVAHI_ANNOUNCEMENT_JITTER_MSEC));
467    else
468        set_timeout(a, NULL);
469}
470
471
472static void reannounce_walk_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) {
473    AvahiEntry *e = userdata;
474    AvahiAnnouncer *a;
475
476    assert(m);
477    assert(i);
478    assert(e);
479    assert(!e->dead);
480
481    if (!(a = get_announcer(m->server, e, i)))
482        return;
483
484    reannounce(a);
485}
486
487void avahi_reannounce_entry(AvahiServer *s, AvahiEntry *e) {
488
489    assert(s);
490    assert(e);
491    assert(!e->dead);
492
493    avahi_interface_monitor_walk(s->monitor, e->interface, e->protocol, reannounce_walk_callback, e);
494}
495
496void avahi_goodbye_interface(AvahiServer *s, AvahiInterface *i, int send_goodbye, int remove) {
497    assert(s);
498    assert(i);
499
500    if (send_goodbye)
501        if (i->announcing) {
502            AvahiEntry *e;
503
504            for (e = s->entries; e; e = e->entries_next)
505                if (!e->dead)
506                    send_goodbye_callback(s->monitor, i, e);
507        }
508
509    if (remove)
510        while (i->announcers)
511            remove_announcer(s, i->announcers);
512}
513
514void avahi_goodbye_entry(AvahiServer *s, AvahiEntry *e, int send_goodbye, int remove) {
515    assert(s);
516    assert(e);
517
518    if (send_goodbye)
519        if (!e->dead)
520            avahi_interface_monitor_walk(s->monitor, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, send_goodbye_callback, e);
521
522    if (remove)
523        while (e->announcers)
524            remove_announcer(s, e->announcers);
525}
526
527