avahi_client_set_errno(client, AVAHI_ERR_DISCONNECTED);
goto fail;
- } if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
+ } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameAcquired")) {
+
+ /* Ignore this message */
+
+ } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
char *name, *old, *new;
if (!dbus_message_get_args(
} else if (!avahi_client_is_connected(client)) {
- /* Ignore messages, we get in unconnected state */
+ /* Ignore messages we get in unconnected state */
} else if (dbus_message_is_signal (message, AVAHI_DBUS_INTERFACE_SERVER, "StateChanged")) {
int32_t state;
assert(percent > 0);
- /* Request a cache update, if we are subscribed to this entry */
- if (avahi_querier_exists(e->cache->interface, e->record->key)) {
-/* avahi_log_debug("Requesting cache entry update at %i%% for %s.", percent, txt); */
- avahi_interface_post_query(e->cache->interface, e->record->key, 1);
- }
+ /* Request a cache update if we are subscribed to this entry */
+ if (avahi_querier_shall_refresh_cache(e->cache->interface, e->record->key))
+ avahi_interface_post_query(e->cache->interface, e->record->key, 0, NULL);
/* Check again later */
next_expiry(e->cache, e, percent);
avahi_interface_send_packet_unicast(i, p, NULL, 0);
}
-int avahi_interface_post_query(AvahiInterface *i, AvahiKey *key, int immediately) {
+int avahi_interface_post_query(AvahiInterface *i, AvahiKey *key, int immediately, unsigned *ret_id) {
assert(i);
assert(key);
if (avahi_interface_is_relevant(i))
- return avahi_query_scheduler_post(i->query_scheduler, key, immediately);
+ return avahi_query_scheduler_post(i->query_scheduler, key, immediately, ret_id);
return 0;
}
+int avahi_interface_withraw_query(AvahiInterface *i, unsigned id) {
+
+ return avahi_query_scheduler_withdraw_by_id(i->query_scheduler, id);
+}
+
int avahi_interface_post_response(AvahiInterface *i, AvahiRecord *record, int flush_cache, const AvahiAddress *querier, int immediately) {
assert(i);
assert(record);
void avahi_interface_send_packet(AvahiInterface *i, AvahiDnsPacket *p);
void avahi_interface_send_packet_unicast(AvahiInterface *i, AvahiDnsPacket *p, const AvahiAddress *a, uint16_t port);
-int avahi_interface_post_query(AvahiInterface *i, AvahiKey *k, int immediately);
+int avahi_interface_post_query(AvahiInterface *i, AvahiKey *k, int immediately, unsigned *ret_id);
+int avahi_interface_withraw_query(AvahiInterface *i, unsigned id);
int avahi_interface_post_response(AvahiInterface *i, AvahiRecord *record, int flush_cache, const AvahiAddress *querier, int immediately);
int avahi_interface_post_probe(AvahiInterface *i, AvahiRecord *p, int immediately);
avahi_querier_add_for_all(e->server, interface, protocol, l->key, &tv);
l->queriers_added = 1;
- /* add a second */
+ /* Add a second */
avahi_timeval_add(&tv, 1000000);
+ /* Issue the ALL_FOR_NOW event one second after the querier was initially created */
l->all_for_now_event = avahi_time_event_new(e->server->time_event_queue, &tv, all_for_now_callback, l);
return l;
AvahiTimeEvent *time_event;
struct timeval creation_time;
+
+ unsigned post_id;
+ int post_id_valid;
AVAHI_LLIST_FIELDS(AvahiQuerier, queriers);
};
assert(q);
- avahi_interface_post_query(q->interface, q->key, 0);
+ if (q->n_used <= 0) {
+
+ /* We are not referenced by anyone anymore, so let's free
+ * ourselves. We should not send out any further queries from
+ * this querier object anymore. */
+
+ avahi_querier_free(q);
+ return;
+ }
+
+ if (avahi_interface_post_query(q->interface, q->key, 0, &q->post_id)) {
+
+ /* The queue accepted our query. We store the query id here,
+ * that allows us to drop the query at a later point if the
+ * query is very short-lived. */
+
+ q->post_id_valid = 1;
+ }
q->sec_delay *= 2;
assert(key);
if ((q = avahi_hashmap_lookup(i->queriers_by_key, key))) {
+
/* Someone is already browsing for records of this RR key */
q->n_used++;
- /* Return the creation time */
+ /* Return the creation time. This is used for generating the
+ * ALL_FOR_NOW event one second after the querier was
+ * initially created. */
if (ret_ctime)
*ret_ctime = q->creation_time;
return;
q->interface = i;
q->n_used = 1;
q->sec_delay = 1;
+ q->post_id_valid = 0;
gettimeofday(&q->creation_time, NULL);
/* Do the initial query */
- avahi_interface_post_query(i, key, 0);
+ if (avahi_interface_post_query(i, key, 0, &q->post_id))
+ q->post_id_valid = 1;
/* Schedule next queries */
q->time_event = avahi_time_event_new(i->monitor->server->time_event_queue, avahi_elapse_time(&tv, q->sec_delay*1000, 0), querier_elapse_callback, q);
AVAHI_LLIST_PREPEND(AvahiQuerier, queriers, i->queriers, q);
avahi_hashmap_insert(i->queriers_by_key, q->key, q);
- /* Return the creation time */
+ /* Return the creation time. This is used for generating the
+ * ALL_FOR_NOW event one second after the querier was initially
+ * created. */
if (ret_ctime)
*ret_ctime = q->creation_time;
}
void avahi_querier_remove(AvahiInterface *i, AvahiKey *key) {
AvahiQuerier *q;
- if (!(q = avahi_hashmap_lookup(i->queriers_by_key, key))) {
- /* The was no querier for this RR key */
- avahi_log_warn(__FILE__": querier_remove() called but no querier to remove");
+ if (!(q = avahi_hashmap_lookup(i->queriers_by_key, key)) || q->n_used <= 0) {
+ /* There was no querier for this RR key, or it wasn't referenced by anyone */
+ avahi_log_warn(__FILE__": querier_remove() called but no querier to remove.");
return;
}
- assert(q->n_used >= 1);
+ if ((--q->n_used) <= 0) {
- if ((--q->n_used) <= 0)
- avahi_querier_free(q);
+ /* Nobody references us anymore. */
+
+ if (q->post_id_valid && avahi_interface_withraw_query(i, q->post_id)) {
+
+ /* We succeeded in withdrawing our query from the queue,
+ * so let's drop dead. */
+
+ avahi_querier_free(q);
+ }
+
+ /* If we failed to withdraw our query from the queue, we stay
+ * alive, in case someone else might recycle our querier at a
+ * later point. We are freed at our next expiry, in case
+ * nobody recycled us. */
+ }
}
static void remove_querier_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) {
avahi_interface_monitor_walk(s->monitor, idx, protocol, add_querier_callback, &cbdata);
}
-int avahi_querier_exists(AvahiInterface *i, AvahiKey *key) {
+int avahi_querier_shall_refresh_cache(AvahiInterface *i, AvahiKey *key) {
+ AvahiQuerier *q;
+
assert(i);
assert(key);
- if (avahi_hashmap_lookup(i->queriers_by_key, key))
- return 1;
+ /* Called by the cache maintainer */
- return 0;
+ if (!(q = avahi_hashmap_lookup(i->queriers_by_key, key)))
+ /* This key is currently not subscribed at all, so no cache
+ * refresh is needed */
+ return 0;
+
+ if (q->n_used <= 0) {
+
+ /* If this is an entry nobody references right now, don't
+ * consider it "existing". */
+
+ /* Remove this querier since it is referenced by nobody
+ * and the cached data will soon be out of date */
+ avahi_querier_free(q);
+
+ /* Tell the cache that no refresh is needed */
+ return 0;
+
+ } else {
+ struct timeval tv;
+
+ /* We can defer our query a little, since the cache will now
+ * issue a refresh query anyway. */
+ avahi_elapse_time(&tv, q->sec_delay*1000, 0);
+ avahi_time_event_update(q->time_event, &tv);
+
+ /* Tell the cache that a refresh should be issued */
+ return 1;
+ }
}
void avahi_querier_free_all(AvahiInterface *i) {
void avahi_querier_free_all(AvahiInterface *i);
/** Return 1 if there is a querier for the specified key on the specified interface */
-int avahi_querier_exists(AvahiInterface *i, AvahiKey *key);
+int avahi_querier_shall_refresh_cache(AvahiInterface *i, AvahiKey *key);
#endif
typedef struct AvahiKnownAnswer AvahiKnownAnswer;
struct AvahiQueryJob {
+ unsigned id;
+ int n_posted;
+
AvahiQueryScheduler *scheduler;
AvahiTimeEvent *time_event;
AvahiKey *key;
+ /* Jobs are stored in a simple linked list. It might turn out in
+ * the future that this list grows too long and we must switch to
+ * some other kind of data structure. This needs further
+ * investigation. I expect the list to be very short (< 20
+ * entries) most of the time, but this might be a wrong
+ * assumption, especially on setups where traffic reflection is
+ * involved. */
+
AVAHI_LLIST_FIELDS(AvahiQueryJob, jobs);
};
AvahiInterface *interface;
AvahiTimeEventQueue *time_event_queue;
+ unsigned next_id;
+
AVAHI_LLIST_HEAD(AvahiQueryJob, jobs);
AVAHI_LLIST_HEAD(AvahiQueryJob, history);
AVAHI_LLIST_HEAD(AvahiKnownAnswer, known_answers);
qj->scheduler = s;
qj->key = avahi_key_ref(key);
qj->time_event = NULL;
+ qj->n_posted = 1;
+ qj->id = s->next_id++;
if ((qj->done = done))
AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->history, qj);
s->interface = i;
s->time_event_queue = i->monitor->server->time_event_queue;
+ s->next_id = 0;
AVAHI_LLIST_HEAD_INIT(AvahiQueryJob, s->jobs);
AVAHI_LLIST_HEAD_INIT(AvahiQueryJob, s->history);
return NULL;
}
-int avahi_query_scheduler_post(AvahiQueryScheduler *s, AvahiKey *key, int immediately) {
+int avahi_query_scheduler_post(AvahiQueryScheduler *s, AvahiKey *key, int immediately, unsigned *ret_id) {
struct timeval tv;
AvahiQueryJob *qj;
assert(s);
assert(key);
- if ((qj = find_history_job(s, key))) {
-/* avahi_log_debug("Query suppressed by local duplicate suppression (history)"); */
+ if ((qj = find_history_job(s, key)))
return 0;
- }
avahi_elapse_time(&tv, immediately ? 0 : AVAHI_QUERY_DEFER_MSEC, 0);
if ((qj = find_scheduled_job(s, key))) {
/* Duplicate questions suppression */
-/* avahi_log_debug("Query suppressed by local duplicate suppression (scheduled)"); */
-
if (avahi_timeval_compare(&tv, &qj->delivery) < 0) {
/* If the new entry should be scheduled earlier,
* update the old entry */
avahi_time_event_update(qj->time_event, &qj->delivery);
}
- return 1;
+ qj->n_posted++;
+
} else {
-/* avahi_log_debug("Accepted new query job."); */
if (!(qj = job_new(s, key, 0)))
return 0; /* OOM */
qj->delivery = tv;
qj->time_event = avahi_time_event_new(s->time_event_queue, &qj->delivery, elapse_callback, qj);
-
- return 1;
}
+
+ if (ret_id)
+ *ret_id = qj->id;
+
+ return 1;
}
void avahi_query_scheduler_incoming(AvahiQueryScheduler *s, AvahiKey *key) {
assert(key);
/* This function is called whenever an incoming query was
- * receieved. We drop scheduled queries that match. The keyword is
+ * received. We drop scheduled queries that match. The keyword is
* "DUPLICATE QUESTION SUPPRESION". */
if ((qj = find_scheduled_job(s, key))) {
-/* avahi_log_debug("Query suppressed by distributed duplicate suppression"); */
job_mark_done(s, qj);
return;
}
-
- if (!(qj = job_new(s, key, 1)))
- return; /* OOM */
+
+ /* Look if there's a history job for this key. If there is, just
+ * update the elapse time */
+ if (!(qj = find_history_job(s, key)))
+ if (!(qj = job_new(s, key, 1)))
+ return; /* OOM */
gettimeofday(&qj->delivery, NULL);
job_set_elapse_time(s, qj, AVAHI_QUERY_HISTORY_MSEC, 0);
}
+int avahi_query_scheduler_withdraw_by_id(AvahiQueryScheduler *s, unsigned id) {
+ AvahiQueryJob *qj;
+
+ assert(s);
+
+ /* Very short lived queries can withdraw an already scheduled item
+ * from the queue using this function, simply by passing the id
+ * returned by avahi_query_scheduler_post(). */
+
+ for (qj = s->jobs; qj; qj = qj->jobs_next) {
+ assert(!qj->done);
+
+ if (qj->id == id) {
+ /* Entry found */
+
+ assert(qj->n_posted >= 1);
+
+ if (--qj->n_posted <= 0) {
+
+ /* We withdraw this job only if the calling object was
+ * the only remaining poster. (Usually this is the
+ * case since there should exist only one querier per
+ * key, but there are exceptions, notably reflected
+ * traffic.) */
+
+ job_free(s, qj);
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
void avahi_query_scheduler_free(AvahiQueryScheduler *s);
void avahi_query_scheduler_clear(AvahiQueryScheduler *s);
-int avahi_query_scheduler_post(AvahiQueryScheduler *s, AvahiKey *key, int immediately);
+int avahi_query_scheduler_post(AvahiQueryScheduler *s, AvahiKey *key, int immediately, unsigned *ret_id);
+int avahi_query_scheduler_withdraw_by_id(AvahiQueryScheduler *s, unsigned id);
void avahi_query_scheduler_incoming(AvahiQueryScheduler *s, AvahiKey *key);
#endif
AVAHI_LLIST_FIELDS(AvahiRecordListItem, items);
};
-
struct AvahiRecordList {
AVAHI_LLIST_HEAD(AvahiRecordListItem, read);
AVAHI_LLIST_HEAD(AvahiRecordListItem, unread);
+
+ int all_flush_cache;
};
AvahiRecordList *avahi_record_list_new(void) {
AVAHI_LLIST_HEAD_INIT(AvahiRecordListItem, l->read);
AVAHI_LLIST_HEAD_INIT(AvahiRecordListItem, l->unread);
+
+ l->all_flush_cache = 1;
return l;
}
item_free(l, l->read);
while (l->unread)
item_free(l, l->unread);
+
+ l->all_flush_cache = 1;
}
-AvahiRecord* avahi_record_list_next(AvahiRecordList *l, int *flush_cache, int *unicast_response, int *auxiliary) {
+AvahiRecord* avahi_record_list_next(AvahiRecordList *l, int *ret_flush_cache, int *ret_unicast_response, int *ret_auxiliary) {
AvahiRecord *r;
AvahiRecordListItem *i;
assert(!i->read);
r = avahi_record_ref(i->record);
- if (unicast_response)
- *unicast_response = i->unicast_response;
- if (flush_cache)
- *flush_cache = i->flush_cache;
- if (auxiliary)
- *auxiliary = i->auxiliary;
+ if (ret_unicast_response)
+ *ret_unicast_response = i->unicast_response;
+ if (ret_flush_cache)
+ *ret_flush_cache = i->flush_cache;
+ if (ret_auxiliary)
+ *ret_auxiliary = i->auxiliary;
AVAHI_LLIST_REMOVE(AvahiRecordListItem, items, l->unread, i);
AVAHI_LLIST_PREPEND(AvahiRecordListItem, items, l->read, i);
i->record = avahi_record_ref(r);
i->read = 0;
+ l->all_flush_cache = l->all_flush_cache && flush_cache;
+
AVAHI_LLIST_PREPEND(AvahiRecordListItem, items, l->unread, i);
}
return !l->unread && !l->read;
}
+
+int avahi_record_list_all_flush_cache(AvahiRecordList *l) {
+ assert(l);
+
+ /* Return TRUE if all entries in this list have flush_cache set */
+
+ return l->all_flush_cache;
+}
void avahi_record_list_free(AvahiRecordList *l);
void avahi_record_list_flush(AvahiRecordList *l);
-AvahiRecord* avahi_record_list_next(AvahiRecordList *l, int *flush_cache, int *unicast_response, int *auxiliary);
+AvahiRecord* avahi_record_list_next(AvahiRecordList *l, int *ret_flush_cache, int *ret_unicast_response, int *ret_auxiliary);
void avahi_record_list_push(AvahiRecordList *l, AvahiRecord *r, int flush_cache, int unicast_response, int auxiliary);
void avahi_record_list_drop(AvahiRecordList *l, AvahiRecord *r);
+int avahi_record_list_all_flush_cache(AvahiRecordList *l);
+
int avahi_record_list_is_empty(AvahiRecordList *l);
#endif
int tc = p && !!(avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) & AVAHI_DNS_FLAG_TC);
while ((r = avahi_record_list_next(s->record_list, &flush_cache, &unicast_response, &auxiliary))) {
-
- if (!avahi_interface_post_response(i, r, flush_cache, a, immediately || (flush_cache && !tc && !auxiliary)) && unicast_response) {
- append_aux_records_to_list(s, i, r, unicast_response);
-
+ int im = immediately;
+
+ /* Only send the response immediately if it contains a
+ * unique entry AND it is not in reply to a truncated
+ * packet AND it is not an auxiliary record AND all other
+ * responses for this record are unique too. */
+
+ if (flush_cache && !tc && !auxiliary && avahi_record_list_all_flush_cache(s->record_list))
+ im = 1;
+
+ if (!avahi_interface_post_response(i, r, flush_cache, a, im) && unicast_response) {
+
/* Due to some reasons the record has not been scheduled.
* The client requested an unicast response in that
* case. Therefore we prepare such a response */
+ append_aux_records_to_list(s, i, r, unicast_response);
+
for (;;) {
if (!reply) {
for (j = s->monitor->interfaces; j; j = j->interface_next)
if (j != i && (s->config.reflect_ipv || j->protocol == i->protocol)) {
/* Post the query to other networks */
- avahi_interface_post_query(j, k, 1);
+ avahi_interface_post_query(j, k, 1, NULL);
/* Reply from caches of other network. This is needed to
* "work around" known answer suppression. */