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