4 This file is part of avahi.
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.
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.
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
26 #include "query-sched.h"
29 #define AVAHI_QUERY_HISTORY_MSEC 100
30 #define AVAHI_QUERY_DEFER_MSEC 100
32 typedef struct AvahiQueryJob AvahiQueryJob;
33 typedef struct AvahiKnownAnswer AvahiKnownAnswer;
35 struct AvahiQueryJob {
36 AvahiQueryScheduler *scheduler;
37 AvahiTimeEvent *time_event;
44 AVAHI_LLIST_FIELDS(AvahiQueryJob, jobs);
47 struct AvahiKnownAnswer {
48 AvahiQueryScheduler *scheduler;
51 AVAHI_LLIST_FIELDS(AvahiKnownAnswer, known_answer);
54 struct AvahiQueryScheduler {
55 AvahiInterface *interface;
56 AvahiTimeEventQueue *time_event_queue;
58 AVAHI_LLIST_HEAD(AvahiQueryJob, jobs);
59 AVAHI_LLIST_HEAD(AvahiQueryJob, history);
60 AVAHI_LLIST_HEAD(AvahiKnownAnswer, known_answers);
63 static AvahiQueryJob* job_new(AvahiQueryScheduler *s, AvahiKey *key, gboolean done) {
69 qj = g_new(AvahiQueryJob, 1);
71 qj->key = avahi_key_ref(key);
72 qj->time_event = NULL;
74 if ((qj->done = done))
75 AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->history, qj);
77 AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->jobs, qj);
82 static void job_free(AvahiQueryScheduler *s, AvahiQueryJob *qj) {
87 avahi_time_event_queue_remove(s->time_event_queue, qj->time_event);
90 AVAHI_LLIST_REMOVE(AvahiQueryJob, jobs, s->history, qj);
92 AVAHI_LLIST_REMOVE(AvahiQueryJob, jobs, s->jobs, qj);
94 avahi_key_unref(qj->key);
98 static void elapse_callback(AvahiTimeEvent *e, gpointer data);
100 static void job_set_elapse_time(AvahiQueryScheduler *s, AvahiQueryJob *qj, guint msec, guint jitter) {
106 avahi_elapse_time(&tv, msec, jitter);
109 avahi_time_event_queue_update(s->time_event_queue, qj->time_event, &tv);
111 qj->time_event = avahi_time_event_queue_add(s->time_event_queue, &tv, elapse_callback, qj);
114 static void job_mark_done(AvahiQueryScheduler *s, AvahiQueryJob *qj) {
120 AVAHI_LLIST_REMOVE(AvahiQueryJob, jobs, s->jobs, qj);
121 AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->history, qj);
125 job_set_elapse_time(s, qj, AVAHI_QUERY_HISTORY_MSEC, 0);
126 g_get_current_time(&qj->delivery);
129 AvahiQueryScheduler *avahi_query_scheduler_new(AvahiInterface *i) {
130 AvahiQueryScheduler *s;
133 s = g_new(AvahiQueryScheduler, 1);
135 s->time_event_queue = i->monitor->server->time_event_queue;
137 AVAHI_LLIST_HEAD_INIT(AvahiQueryJob, s->jobs);
138 AVAHI_LLIST_HEAD_INIT(AvahiQueryJob, s->history);
139 AVAHI_LLIST_HEAD_INIT(AvahiKnownAnswer, s->known_answers);
144 void avahi_query_scheduler_free(AvahiQueryScheduler *s) {
147 g_assert(!s->known_answers);
148 avahi_query_scheduler_clear(s);
152 void avahi_query_scheduler_clear(AvahiQueryScheduler *s) {
156 job_free(s, s->jobs);
158 job_free(s, s->history);
161 static gpointer known_answer_walk_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, gpointer userdata) {
162 AvahiQueryScheduler *s = userdata;
163 AvahiKnownAnswer *ka;
170 if (avahi_cache_entry_half_ttl(c, e))
173 ka = g_new0(AvahiKnownAnswer, 1);
175 ka->record = avahi_record_ref(e->record);
177 AVAHI_LLIST_PREPEND(AvahiKnownAnswer, known_answer, s->known_answers, ka);
181 static gboolean packet_add_query_job(AvahiQueryScheduler *s, AvahiDnsPacket *p, AvahiQueryJob *qj) {
186 if (!avahi_dns_packet_append_key(p, qj->key, FALSE))
189 /* Add all matching known answers to the list */
190 avahi_cache_walk(s->interface->cache, qj->key, known_answer_walk_callback, s);
192 job_mark_done(s, qj);
197 static void append_known_answers_and_send(AvahiQueryScheduler *s, AvahiDnsPacket *p) {
198 AvahiKnownAnswer *ka;
205 while ((ka = s->known_answers)) {
206 gboolean too_large = FALSE;
208 while (!avahi_dns_packet_append_record(p, ka->record, FALSE, 0)) {
210 if (avahi_dns_packet_is_empty(p)) {
211 /* The record is too large to fit into one packet, so
212 there's no point in sending it. Better is letting
213 the owner of the record send it as a response. This
214 has the advantage of a cache refresh. */
220 avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_FLAGS, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) | AVAHI_DNS_FLAG_TC);
221 avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ANCOUNT, n);
222 avahi_interface_send_packet(s->interface, p);
223 avahi_dns_packet_free(p);
225 p = avahi_dns_packet_new_query(s->interface->hardware->mtu);
229 AVAHI_LLIST_REMOVE(AvahiKnownAnswer, known_answer, s->known_answers, ka);
230 avahi_record_unref(ka->record);
237 avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ANCOUNT, n);
238 avahi_interface_send_packet(s->interface, p);
239 avahi_dns_packet_free(p);
242 static void elapse_callback(AvahiTimeEvent *e, gpointer data) {
243 AvahiQueryJob *qj = data;
244 AvahiQueryScheduler *s;
253 /* Lets remove it from the history */
258 g_assert(!s->known_answers);
260 p = avahi_dns_packet_new_query(s->interface->hardware->mtu);
261 b = packet_add_query_job(s, p, qj);
262 g_assert(b); /* An query must always fit in */
265 /* Try to fill up packet with more queries, if available */
268 if (!packet_add_query_job(s, p, s->jobs))
274 avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_QDCOUNT, n);
276 /* Now add known answers */
277 append_known_answers_and_send(s, p);
280 static AvahiQueryJob* find_scheduled_job(AvahiQueryScheduler *s, AvahiKey *key) {
286 for (qj = s->jobs; qj; qj = qj->jobs_next) {
289 if (avahi_key_equal(qj->key, key))
296 static AvahiQueryJob* find_history_job(AvahiQueryScheduler *s, AvahiKey *key) {
302 for (qj = s->history; qj; qj = qj->jobs_next) {
305 if (avahi_key_equal(qj->key, key)) {
306 /* Check whether this entry is outdated */
308 if (avahi_age(&qj->delivery) > AVAHI_QUERY_HISTORY_MSEC*1000) {
309 /* it is outdated, so let's remove it */
321 gboolean avahi_query_scheduler_post(AvahiQueryScheduler *s, AvahiKey *key, gboolean immediately) {
328 if ((qj = find_history_job(s, key))) {
329 g_message("Query suppressed by local duplicate suppression (history)");
333 avahi_elapse_time(&tv, immediately ? 0 : AVAHI_QUERY_DEFER_MSEC, 0);
335 if ((qj = find_scheduled_job(s, key))) {
336 /* Duplicate questions suppression */
338 g_message("Query suppressed by local duplicate suppression (scheduled)");
340 if (avahi_timeval_compare(&tv, &qj->delivery) < 0) {
341 /* If the new entry should be scheduled earlier,
342 * update the old entry */
344 avahi_time_event_queue_update(s->time_event_queue, qj->time_event, &qj->delivery);
349 g_message("Accepted new query job.\n");
351 qj = job_new(s, key, FALSE);
353 qj->time_event = avahi_time_event_queue_add(s->time_event_queue, &qj->delivery, elapse_callback, qj);
359 void avahi_query_scheduler_incoming(AvahiQueryScheduler *s, AvahiKey *key) {
365 /* This function is called whenever an incoming query was
366 * receieved. We drop scheduled queries that match. The keyword is
367 * "DUPLICATE QUESTION SUPPRESION". */
369 if ((qj = find_scheduled_job(s, key))) {
370 g_message("Query suppressed by distributed duplicate suppression");
371 job_mark_done(s, qj);
375 qj = job_new(s, key, TRUE);
376 g_get_current_time(&qj->delivery);
377 job_set_elapse_time(s, qj, AVAHI_QUERY_HISTORY_MSEC, 0);