summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2015-11-24 00:18:49 +0100
committerLennart Poettering <lennart@poettering.net>2015-11-24 00:20:39 +0100
commit58db254ade4fb2ef77de68f28c4f13814819f6a1 (patch)
tree597cc7eb7ec7822f62b91e25558e3e9d490b655c
parent5ce1946f4def03e0ab7127dd5729949a32e060ac (diff)
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.
-rw-r--r--src/resolve/resolved-dns-cache.c44
-rw-r--r--src/resolve/resolved-dns-question.c18
-rw-r--r--src/resolve/resolved-dns-rr.c33
-rw-r--r--src/resolve/resolved-dns-rr.h1
-rw-r--r--src/shared/dns-domain.c67
-rw-r--r--src/shared/dns-domain.h4
-rw-r--r--src/test/test-dns-domain.c20
7 files changed, 175 insertions, 12 deletions
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index 04f64022e0..d963ce6e00 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -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;
+
+ /* 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;
- return i;
+ 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) {
diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c
index 75f2ed6042..9fb3038381 100644
--- a/src/resolve/resolved-dns-question.c
+++ b/src/resolve/resolved-dns-question.c
@@ -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;
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index 636e07dea9..9b264900fc 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -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) {
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index c02f2ec00f..c1601fb694 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -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);
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c
index e08143c3be..e6aad39c74 100644
--- a/src/shared/dns-domain.c
+++ b/src/shared/dns-domain.c
@@ -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;
diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h
index c6d7623e09..a68df330e6 100644
--- a/src/shared/dns-domain.h
+++ b/src/shared/dns-domain.h
@@ -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);
diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c
index a664e1c79c..b76a31a549 100644
--- a/src/test/test-dns-domain.c
+++ b/src/test/test-dns-domain.c
@@ -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;
}