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