Home | History | Annotate | Download | only in avahi-core
      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 <stdlib.h>
     25 
     26 #include <avahi-common/domain.h>
     27 #include <avahi-common/timeval.h>
     28 #include "avahi-common/avahi-malloc.h"
     29 
     30 #include "probe-sched.h"
     31 #include "log.h"
     32 #include "rr-util.h"
     33 
     34 #define AVAHI_PROBE_HISTORY_MSEC 150
     35 #define AVAHI_PROBE_DEFER_MSEC 50
     36 
     37 typedef struct AvahiProbeJob AvahiProbeJob;
     38 
     39 struct AvahiProbeJob {
     40     AvahiProbeScheduler *scheduler;
     41     AvahiTimeEvent *time_event;
     42 
     43     int chosen; /* Use for packet assembling */
     44     int done;
     45     struct timeval delivery;
     46 
     47     AvahiRecord *record;
     48 
     49     AVAHI_LLIST_FIELDS(AvahiProbeJob, jobs);
     50 };
     51 
     52 struct AvahiProbeScheduler {
     53     AvahiInterface *interface;
     54     AvahiTimeEventQueue *time_event_queue;
     55 
     56     AVAHI_LLIST_HEAD(AvahiProbeJob, jobs);
     57     AVAHI_LLIST_HEAD(AvahiProbeJob, history);
     58 };
     59 
     60 static AvahiProbeJob* job_new(AvahiProbeScheduler *s, AvahiRecord *record, int done) {
     61     AvahiProbeJob *pj;
     62 
     63     assert(s);
     64     assert(record);
     65 
     66     if (!(pj = avahi_new(AvahiProbeJob, 1))) {
     67         avahi_log_error(__FILE__": Out of memory");
     68         return NULL; /* OOM */
     69     }
     70 
     71     pj->scheduler = s;
     72     pj->record = avahi_record_ref(record);
     73     pj->time_event = NULL;
     74     pj->chosen = 0;
     75 
     76     if ((pj->done = done))
     77         AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->history, pj);
     78     else
     79         AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->jobs, pj);
     80 
     81     return pj;
     82 }
     83 
     84 static void job_free(AvahiProbeScheduler *s, AvahiProbeJob *pj) {
     85     assert(pj);
     86 
     87     if (pj->time_event)
     88         avahi_time_event_free(pj->time_event);
     89 
     90     if (pj->done)
     91         AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->history, pj);
     92     else
     93         AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->jobs, pj);
     94 
     95     avahi_record_unref(pj->record);
     96     avahi_free(pj);
     97 }
     98 
     99 static void elapse_callback(AvahiTimeEvent *e, void* data);
    100 
    101 static void job_set_elapse_time(AvahiProbeScheduler *s, AvahiProbeJob *pj, unsigned msec, unsigned jitter) {
    102     struct timeval tv;
    103 
    104     assert(s);
    105     assert(pj);
    106 
    107     avahi_elapse_time(&tv, msec, jitter);
    108 
    109     if (pj->time_event)
    110         avahi_time_event_update(pj->time_event, &tv);
    111     else
    112         pj->time_event = avahi_time_event_new(s->time_event_queue, &tv, elapse_callback, pj);
    113 }
    114 
    115 static void job_mark_done(AvahiProbeScheduler *s, AvahiProbeJob *pj) {
    116     assert(s);
    117     assert(pj);
    118 
    119     assert(!pj->done);
    120 
    121     AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->jobs, pj);
    122     AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->history, pj);
    123 
    124     pj->done = 1;
    125 
    126     job_set_elapse_time(s, pj, AVAHI_PROBE_HISTORY_MSEC, 0);
    127     gettimeofday(&pj->delivery, NULL);
    128 }
    129 
    130 AvahiProbeScheduler *avahi_probe_scheduler_new(AvahiInterface *i) {
    131     AvahiProbeScheduler *s;
    132 
    133     assert(i);
    134 
    135     if (!(s = avahi_new(AvahiProbeScheduler, 1))) {
    136         avahi_log_error(__FILE__": Out of memory");
    137         return NULL;
    138     }
    139 
    140     s->interface = i;
    141     s->time_event_queue = i->monitor->server->time_event_queue;
    142 
    143     AVAHI_LLIST_HEAD_INIT(AvahiProbeJob, s->jobs);
    144     AVAHI_LLIST_HEAD_INIT(AvahiProbeJob, s->history);
    145 
    146     return s;
    147 }
    148 
    149 void avahi_probe_scheduler_free(AvahiProbeScheduler *s) {
    150     assert(s);
    151 
    152     avahi_probe_scheduler_clear(s);
    153     avahi_free(s);
    154 }
    155 
    156 void avahi_probe_scheduler_clear(AvahiProbeScheduler *s) {
    157     assert(s);
    158 
    159     while (s->jobs)
    160         job_free(s, s->jobs);
    161     while (s->history)
    162         job_free(s, s->history);
    163 }
    164 
    165 static int packet_add_probe_query(AvahiProbeScheduler *s, AvahiDnsPacket *p, AvahiProbeJob *pj) {
    166     size_t size;
    167     AvahiKey *k;
    168     int b;
    169 
    170     assert(s);
    171     assert(p);
    172     assert(pj);
    173 
    174     assert(!pj->chosen);
    175 
    176     /* Estimate the size for this record */
    177     size =
    178         avahi_key_get_estimate_size(pj->record->key) +
    179         avahi_record_get_estimate_size(pj->record);
    180 
    181     /* Too large */
    182     if (size > avahi_dns_packet_space(p))
    183         return 0;
    184 
    185     /* Create the probe query */
    186     if (!(k = avahi_key_new(pj->record->key->name, pj->record->key->clazz, AVAHI_DNS_TYPE_ANY)))
    187         return 0; /* OOM */
    188 
    189     b = !!avahi_dns_packet_append_key(p, k, 0);
    190     assert(b);
    191 
    192     /* Mark this job for addition to the packet */
    193     pj->chosen = 1;
    194 
    195     /* Scan for more jobs whith matching key pattern */
    196     for (pj = s->jobs; pj; pj = pj->jobs_next) {
    197         if (pj->chosen)
    198             continue;
    199 
    200         /* Does the record match the probe? */
    201         if (k->clazz != pj->record->key->clazz || !avahi_domain_equal(k->name, pj->record->key->name))
    202             continue;
    203 
    204         /* This job wouldn't fit in */
    205         if (avahi_record_get_estimate_size(pj->record) > avahi_dns_packet_space(p))
    206             break;
    207 
    208         /* Mark this job for addition to the packet */
    209         pj->chosen = 1;
    210     }
    211 
    212     avahi_key_unref(k);
    213 
    214     return 1;
    215 }
    216 
    217 static void elapse_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void* data) {
    218     AvahiProbeJob *pj = data, *next;
    219     AvahiProbeScheduler *s;
    220     AvahiDnsPacket *p;
    221     unsigned n;
    222 
    223     assert(pj);
    224     s = pj->scheduler;
    225 
    226     if (pj->done) {
    227         /* Lets remove it  from the history */
    228         job_free(s, pj);
    229         return;
    230     }
    231 
    232     if (!(p = avahi_dns_packet_new_query(s->interface->hardware->mtu)))
    233         return; /* OOM */
    234     n = 1;
    235 
    236     /* Add the import probe */
    237     if (!packet_add_probe_query(s, p, pj)) {
    238         size_t size;
    239         AvahiKey *k;
    240         int b;
    241 
    242         avahi_dns_packet_free(p);
    243 
    244         /* The probe didn't fit in the package, so let's allocate a larger one */
    245 
    246         size =
    247             avahi_key_get_estimate_size(pj->record->key) +
    248             avahi_record_get_estimate_size(pj->record) +
    249             AVAHI_DNS_PACKET_HEADER_SIZE;
    250 
    251         if (!(p = avahi_dns_packet_new_query(size + AVAHI_DNS_PACKET_EXTRA_SIZE)))
    252             return; /* OOM */
    253 
    254         if (!(k = avahi_key_new(pj->record->key->name, pj->record->key->clazz, AVAHI_DNS_TYPE_ANY))) {
    255             avahi_dns_packet_free(p);
    256             return;  /* OOM */
    257         }
    258 
    259         b = avahi_dns_packet_append_key(p, k, 0) && avahi_dns_packet_append_record(p, pj->record, 0, 0);
    260         avahi_key_unref(k);
    261 
    262         if (b) {
    263             avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_NSCOUNT, 1);
    264             avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_QDCOUNT, 1);
    265             avahi_interface_send_packet(s->interface, p);
    266         } else
    267             avahi_log_warn("Probe record too large, cannot send");
    268 
    269         avahi_dns_packet_free(p);
    270         job_mark_done(s, pj);
    271 
    272         return;
    273     }
    274 
    275     /* Try to fill up packet with more probes, if available */
    276     for (pj = s->jobs; pj; pj = pj->jobs_next) {
    277 
    278         if (pj->chosen)
    279             continue;
    280 
    281         if (!packet_add_probe_query(s, p, pj))
    282             break;
    283 
    284         n++;
    285     }
    286 
    287     avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_QDCOUNT, n);
    288 
    289     n = 0;
    290 
    291     /* Now add the chosen records to the authorative section */
    292     for (pj = s->jobs; pj; pj = next) {
    293 
    294         next = pj->jobs_next;
    295 
    296         if (!pj->chosen)
    297             continue;
    298 
    299         if (!avahi_dns_packet_append_record(p, pj->record, 0, 0)) {
    300 /*             avahi_log_warn("Bad probe size estimate!"); */
    301 
    302             /* Unmark all following jobs */
    303             for (; pj; pj = pj->jobs_next)
    304                 pj->chosen = 0;
    305 
    306             break;
    307         }
    308 
    309         job_mark_done(s, pj);
    310 
    311         n ++;
    312     }
    313 
    314     avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_NSCOUNT, n);
    315 
    316     /* Send it now */
    317     avahi_interface_send_packet(s->interface, p);
    318     avahi_dns_packet_free(p);
    319 }
    320 
    321 static AvahiProbeJob* find_scheduled_job(AvahiProbeScheduler *s, AvahiRecord *record) {
    322     AvahiProbeJob *pj;
    323 
    324     assert(s);
    325     assert(record);
    326 
    327     for (pj = s->jobs; pj; pj = pj->jobs_next) {
    328         assert(!pj->done);
    329 
    330         if (avahi_record_equal_no_ttl(pj->record, record))
    331             return pj;
    332     }
    333 
    334     return NULL;
    335 }
    336 
    337 static AvahiProbeJob* find_history_job(AvahiProbeScheduler *s, AvahiRecord *record) {
    338     AvahiProbeJob *pj;
    339 
    340     assert(s);
    341     assert(record);
    342 
    343     for (pj = s->history; pj; pj = pj->jobs_next) {
    344         assert(pj->done);
    345 
    346         if (avahi_record_equal_no_ttl(pj->record, record)) {
    347             /* Check whether this entry is outdated */
    348 
    349             if (avahi_age(&pj->delivery) > AVAHI_PROBE_HISTORY_MSEC*1000) {
    350                 /* it is outdated, so let's remove it */
    351                 job_free(s, pj);
    352                 return NULL;
    353             }
    354 
    355             return pj;
    356         }
    357     }
    358 
    359     return NULL;
    360 }
    361 
    362 int avahi_probe_scheduler_post(AvahiProbeScheduler *s, AvahiRecord *record, int immediately) {
    363     AvahiProbeJob *pj;
    364     struct timeval tv;
    365 
    366     assert(s);
    367     assert(record);
    368     assert(!avahi_key_is_pattern(record->key));
    369 
    370     if ((pj = find_history_job(s, record)))
    371         return 0;
    372 
    373     avahi_elapse_time(&tv, immediately ? 0 : AVAHI_PROBE_DEFER_MSEC, 0);
    374 
    375     if ((pj = find_scheduled_job(s, record))) {
    376 
    377         if (avahi_timeval_compare(&tv, &pj->delivery) < 0) {
    378             /* If the new entry should be scheduled earlier, update the old entry */
    379             pj->delivery = tv;
    380             avahi_time_event_update(pj->time_event, &pj->delivery);
    381         }
    382 
    383         return 1;
    384     } else {
    385         /* Create a new job and schedule it */
    386         if (!(pj = job_new(s, record, 0)))
    387             return 0; /* OOM */
    388 
    389         pj->delivery = tv;
    390         pj->time_event = avahi_time_event_new(s->time_event_queue, &pj->delivery, elapse_callback, pj);
    391 
    392 
    393 /*     avahi_log_debug("Accepted new probe job."); */
    394 
    395         return 1;
    396     }
    397 }
    398