resolved: implement client-side DNAME resolution

Most servers apparently always implicitly convert DNAME to CNAME, but
some servers don't, hence implement this properly, as this is required
by edns0.
This commit is contained in:
Lennart Poettering 2015-11-24 00:18:49 +01:00
parent 5ce1946f4d
commit 58db254ade
7 changed files with 175 additions and 12 deletions

View File

@ -20,8 +20,10 @@
***/
#include "alloc-util.h"
#include "dns-domain.h"
#include "resolved-dns-cache.h"
#include "resolved-dns-packet.h"
#include "string-util.h"
/* Never cache more than 1K entries */
#define CACHE_MAX 1024
@ -521,25 +523,53 @@ fail:
static DnsCacheItem *dns_cache_get_by_key_follow_cname(DnsCache *c, DnsResourceKey *k) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *cname_key = NULL;
DnsCacheItem *i, *j;
DnsCacheItem *i;
const char *n;
int r;
assert(c);
assert(k);
/* If we hit some OOM error, or suchlike, we don't care too
* much, after all this is just a cache */
i = hashmap_get(c->by_key, k);
if (i || k->type == DNS_TYPE_CNAME)
if (i || k->type == DNS_TYPE_CNAME || k->type == DNS_TYPE_DNAME)
return i;
/* check if we have a CNAME record instead */
/* Check if we have a CNAME record instead */
cname_key = dns_resource_key_new_cname(k);
if (!cname_key)
return NULL;
j = hashmap_get(c->by_key, cname_key);
if (j)
return j;
i = hashmap_get(c->by_key, cname_key);
if (i)
return i;
return i;
/* OK, let's look for cached DNAME records. */
n = DNS_RESOURCE_KEY_NAME(k);
for (;;) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *dname_key = NULL;
char label[DNS_LABEL_MAX];
if (isempty(n))
return NULL;
dname_key = dns_resource_key_new(k->class, DNS_TYPE_DNAME, n);
if (!dname_key)
return NULL;
i = hashmap_get(c->by_key, dname_key);
if (i)
return i;
/* Jump one label ahead */
r = dns_label_unescape(&n, label, sizeof(label));
if (r <= 0)
return NULL;
}
return NULL;
}
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret) {

View File

@ -207,6 +207,7 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname,
assert(cname);
assert(ret);
assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME));
if (!q) {
n = dns_question_new(0);
@ -219,7 +220,22 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname,
}
for (i = 0; i < q->n_keys; i++) {
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), cname->cname.name);
_cleanup_free_ char *destination = NULL;
const char *d;
if (cname->key->type == DNS_TYPE_CNAME)
d = cname->cname.name;
else {
r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(q->keys[i]), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination);
if (r < 0)
return r;
if (r == 0)
continue;
d = destination;
}
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), d);
if (r < 0)
return r;

View File

@ -57,10 +57,33 @@ DnsResourceKey* dns_resource_key_new_cname(const DnsResourceKey *key) {
}
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) {
int r;
assert(key);
assert(cname);
return dns_resource_key_new(key->class, key->type, cname->cname.name);
assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME));
if (cname->key->type == DNS_TYPE_CNAME)
return dns_resource_key_new(key->class, key->type, cname->cname.name);
else {
DnsResourceKey *k;
char *destination = NULL;
r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination);
if (r < 0)
return NULL;
if (r == 0)
return dns_resource_key_ref((DnsResourceKey*) key);
k = dns_resource_key_new_consume(key->class, key->type, destination);
if (!k) {
free(destination);
return NULL;
}
return k;
}
}
DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) {
@ -142,10 +165,12 @@ int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRec
if (rr->key->class != key->class && key->class != DNS_CLASS_ANY)
return 0;
if (rr->key->type != DNS_TYPE_CNAME)
if (rr->key->type == DNS_TYPE_CNAME)
return dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key));
else if (rr->key->type == DNS_TYPE_DNAME)
return dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key));
else
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
}
static void dns_resource_key_hash_func(const void *i, struct siphash *state) {

View File

@ -186,6 +186,7 @@ static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) {
DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name);
DnsResourceKey* dns_resource_key_new_cname(const DnsResourceKey *key);
DnsResourceKey* dns_resource_key_new_dname(const DnsResourceKey *key);
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname);
DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name);
DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key);

View File

@ -547,6 +547,73 @@ int dns_name_endswith(const char *name, const char *suffix) {
}
}
int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) {
const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix;
int r, q, k, w;
assert(name);
assert(old_suffix);
assert(new_suffix);
assert(ret);
n = name;
s = old_suffix;
for (;;) {
char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
if (!saved_before)
saved_before = n;
r = dns_label_unescape(&n, ln, sizeof(ln));
if (r < 0)
return r;
k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
if (k < 0)
return k;
if (k > 0)
r = k;
if (!saved_after)
saved_after = n;
q = dns_label_unescape(&s, ls, sizeof(ls));
if (q < 0)
return q;
w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
if (w < 0)
return w;
if (w > 0)
q = w;
if (r == 0 && q == 0)
break;
if (r == 0 && saved_after == n) {
*ret = NULL; /* doesn't match */
return 0;
}
ln[r] = ls[q] = 0;
if (r != q || strcasecmp(ln, ls)) {
/* Not the same, let's jump back, and try with the next label again */
s = old_suffix;
n = saved_after;
saved_after = saved_before = NULL;
}
}
/* Found it! Now generate the new name */
prefix = strndupa(name, saved_before - name);
r = dns_name_concat(prefix, new_suffix, ret);
if (r < 0)
return r;
return 1;
}
int dns_name_between(const char *a, const char *b, const char *c) {
int n;

View File

@ -62,6 +62,8 @@ int dns_name_between(const char *a, const char *b, const char *c);
int dns_name_equal(const char *x, const char *y);
int dns_name_endswith(const char *name, const char *suffix);
int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret);
int dns_name_reverse(int family, const union in_addr_union *a, char **ret);
int dns_name_address(const char *p, int *family, union in_addr_union *a);
@ -76,3 +78,5 @@ bool dns_service_name_is_valid(const char *name);
int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
int dns_service_split(const char *joined, char **name, char **type, char **domain);
int dns_name_replace_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret);

View File

@ -408,6 +408,25 @@ static void test_dns_service_split(void) {
test_dns_service_split_one("Wuff\\032Wuff._foo._bar.waldo.com", "Wuff Wuff", "_foo._bar", "waldo.com", 0);
}
static void test_dns_name_change_suffix_one(const char *name, const char *old_suffix, const char *new_suffix, int r, const char *result) {
_cleanup_free_ char *s = NULL;
assert_se(dns_name_change_suffix(name, old_suffix, new_suffix, &s) == r);
assert_se(streq_ptr(s, result));
}
static void test_dns_name_change_suffix(void) {
test_dns_name_change_suffix_one("foo.bar", "bar", "waldo", 1, "foo.waldo");
test_dns_name_change_suffix_one("foo.bar.waldi.quux", "foo.bar.waldi.quux", "piff.paff", 1, "piff.paff");
test_dns_name_change_suffix_one("foo.bar.waldi.quux", "bar.waldi.quux", "piff.paff", 1, "foo.piff.paff");
test_dns_name_change_suffix_one("foo.bar.waldi.quux", "waldi.quux", "piff.paff", 1, "foo.bar.piff.paff");
test_dns_name_change_suffix_one("foo.bar.waldi.quux", "quux", "piff.paff", 1, "foo.bar.waldi.piff.paff");
test_dns_name_change_suffix_one("foo.bar.waldi.quux", "", "piff.paff", 1, "foo.bar.waldi.quux.piff.paff");
test_dns_name_change_suffix_one("", "", "piff.paff", 1, "piff.paff");
test_dns_name_change_suffix_one("", "", "", 1, "");
test_dns_name_change_suffix_one("a", "b", "c", 0, NULL);
}
int main(int argc, char *argv[]) {
test_dns_label_unescape();
@ -427,6 +446,7 @@ int main(int argc, char *argv[]) {
test_dns_srv_type_verify();
test_dns_service_join();
test_dns_service_split();
test_dns_name_change_suffix();
return 0;
}