blob: 2802cee2bbc7d3d0378cc03af5121b06737ddbd8 [file] [log] [blame]
From: Pranav Batra <batrapranav@chromium.org>
Subject: Cache mDNS host name records (IPv4, record A) for 12-24 hours after expiration.
By default, the address record for a .local mDNS domain name is removed from the cache after 2 minutes.
When resolving a host name, if the address record is not found in the cache, avahi attempts to query the
network for the record. If a response is not received within 5 seconds (see TIMEOUT_MSEC in
avahi-core/resolve_service.c), avahi will return an error.
With this patch, instead of returning an error, avahi will attempt to query a backup resource record
cache that holds IPv4 address records (A) for an additional 12-24 hours and if a record is found,
return that record instead of an error.
Note that as the code block that adds entries to the cache and the code block that queries the cache
both run under the same process id and thread id, no mutex is necessary.
Also note that avahi is configured with IPv6 support disabled, so there is no need
to store AAAA records in the backup resource record cache.
--- a/avahi-core/browse.h
+++ b/avahi-core/browse.h
@@ -20,6 +20,8 @@
USA.
***/
+#include <time.h>
+
#include <avahi-common/llist.h>
#include "core.h"
@@ -28,6 +30,17 @@
#include "dns.h"
#include "lookup.h"
+extern AvahiHashmap *host_cache_archive;
+extern AvahiHashmap *host_cache_current;
+extern AvahiIfIndex host_interface;
+
+typedef struct HostCacheRecord HostCacheRecord;
+
+struct HostCacheRecord {
+ AvahiRecord *record;
+ time_t time;
+};
+
typedef struct AvahiSRBLookup AvahiSRBLookup;
struct AvahiSRecordBrowser {
--- a/avahi-core/resolve-host-name.c
+++ b/avahi-core/resolve-host-name.c
@@ -84,17 +84,59 @@ static void finish(AvahiSHostNameResolver *r, AvahiResolverEvent event) {
}
char addr[AVAHI_ADDRESS_STR_MAX];
avahi_address_snprint(addr, sizeof(addr), &a);
- avahi_log_info("Resolved hostname %s: %s", r->host_name, addr);
-
+ avahi_log_info("Resolved hostname %s (%s): %s (interface %d, protocol %d, flags %d, IPv4 %s, IPv6 %s, ttl %d)",
+ r->host_name,
+ r->address_record->key->name,
+ addr,
+ r->interface,
+ r->protocol,
+ r->flags,
+ r->record_browser_a?"yes":"no",
+ r->record_browser_aaaa?"yes":"no",
+ r->address_record->ttl);
r->callback(r, r->interface, r->protocol, AVAHI_RESOLVER_FOUND, r->address_record->key->name, &a, r->flags, r->userdata);
break;
}
- case AVAHI_RESOLVER_FAILURE:
- avahi_log_warn("Failed to resolve hostname %s", r->host_name);
- r->callback(r, r->interface, r->protocol, event, r->host_name, NULL, r->flags, r->userdata);
+ case AVAHI_RESOLVER_FAILURE: {
+ HostCacheRecord *record = NULL;
+ if (host_cache_current)
+ record = avahi_hashmap_lookup(host_cache_current, r->host_name);
+ if (!record && host_cache_archive)
+ record = avahi_hashmap_lookup(host_cache_archive, r->host_name);
+ if (!record ||
+ !r->record_browser_a ||
+ (r->protocol != AVAHI_PROTO_INET && r->protocol != AVAHI_PROTO_UNSPEC) ||
+ r->address_record) {
+ // Although avahi is configured to only use the IPv4 mDNS broadcast address (224.0.0.251),
+ // it turns out IPv6 host address records (AAAA) can be sent via IPv4.
+ // r->protocol is based on the mDNS broadcast address used and will generally always be
+ // -1 (any protocol) or 0 (IPv4; AVAHI_PROTO_INET).
+ // If an IPv4 address is requested (avahi-resolve-host-name -4), r->record_browser_a will be initialized.
+ // If an IPv6 address is requested (avahi-resolve-host-name -6), r->record_browser_aaaa will be initialized.
+ // Both data structures will be initialized if the address type aprotocol is unspecified.
+ avahi_log_warn("Failed to resolve hostname %s (interface %d, protocol %d, flags %d, IPv4 %s, IPv6 %s)",
+ r->host_name,
+ r->interface,
+ r->protocol,
+ r->flags,
+ r->record_browser_a?"yes":"no",
+ r->record_browser_aaaa?"yes":"no");
+ if (r->address_record)
+ avahi_log_error("Assertion failed: !r->address_record");
+ r->callback(r, r->interface, r->protocol, event, r->host_name, NULL, r->flags, r->userdata);
+ break;
+ }
+ avahi_log_warn("Using stale mDNS A record for %s (%d seconds old)", r->host_name, time(NULL) - record->time);
+ r->flags = AVAHI_LOOKUP_RESULT_CACHED | AVAHI_LOOKUP_RESULT_MULTICAST; // 5
+ r->protocol = AVAHI_PROTO_INET;
+ if (r->interface <= 0)
+ r->interface = host_interface;
+ r->address_record = avahi_record_ref(record->record);
+ finish(r, AVAHI_RESOLVER_FOUND);
break;
+ }
}
}
--- a/avahi-core/server.c
+++ b/avahi-core/server.c
@@ -26,12 +26,14 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
+#include <strings.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
+#include <ctype.h>
#include <avahi-common/domain.h>
#include <avahi-common/timeval.h>
@@ -51,6 +53,11 @@
#define AVAHI_DEFAULT_CACHE_ENTRIES_MAX 4096
+AvahiHashmap *host_cache_archive = NULL;
+AvahiHashmap *host_cache_current = NULL;
+AvahiIfIndex host_interface = AVAHI_IF_UNSPEC;
+time_t host_time = 0;
+
static void enum_aux_records(AvahiServer *s, AvahiInterface *i, const char *name, uint16_t type, void (*callback)(AvahiServer *s, AvahiRecord *r, int flush_cache, void* userdata), void* userdata) {
assert(s);
assert(i);
@@ -655,6 +662,45 @@ fail:
avahi_record_list_flush(s->record_list);
}
+static unsigned string_case_hash(const void *data) {
+ const char *p = data;
+ unsigned hash = 0;
+
+ assert(p);
+
+ for (; *p; p++)
+ hash = 31 * hash + tolower(*p);
+
+ return hash;
+}
+
+static int string_case_equal(const void *a, const void *b) {
+ const char *p = a, *q = b;
+
+ assert(p);
+ assert(q);
+
+ return strcasecmp(p, q) == 0;
+}
+
+static void free_host_record(void* p) {
+ HostCacheRecord *r = p;
+ assert(r);
+ avahi_record_unref(r->record);
+ avahi_free(r);
+}
+
+static AvahiHashmap *new_host_hashmap() {
+ return avahi_hashmap_new(string_case_hash, string_case_equal, avahi_free, free_host_record);
+}
+
+static HostCacheRecord *new_host_record(AvahiRecord *r, time_t t) {
+ HostCacheRecord *record = avahi_new(HostCacheRecord, 1);
+ record->record = avahi_record_ref(r);
+ record->time = t;
+ return record;
+}
+
static void handle_response_packet(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a, int from_local_iface) {
unsigned n;
@@ -680,6 +726,23 @@ static void handle_response_packet(AvahiServer *s, AvahiDnsPacket *p, AvahiInter
reflect_response(s, i, record, cache_flush);
avahi_cache_update(i->cache, record, cache_flush, a);
avahi_response_scheduler_incoming(i->response_scheduler, record, cache_flush);
+ if (record->key->type == AVAHI_DNS_TYPE_A) {
+ host_interface = i->hardware->index;
+ // This is always called before we resolve an address if we've seen valid records.
+ if (!host_cache_archive)
+ host_cache_archive = new_host_hashmap();
+ if (!host_cache_current)
+ host_cache_current = new_host_hashmap();
+ time_t current_time = time(NULL);
+ if (current_time > host_time + 12*3600) { // Rotate the cache every 12 hours.
+ host_time = current_time;
+ avahi_hashmap_free(host_cache_archive);
+ host_cache_archive = host_cache_current;
+ host_cache_current = new_host_hashmap();
+ }
+ avahi_hashmap_remove(host_cache_archive, record->key->name);
+ avahi_hashmap_replace(host_cache_current, avahi_strdup(record->key->name), new_host_record(record, current_time));
+ }
}
}