]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: implement client-side DNAME resolution
authorLennart Poettering <lennart@poettering.net>
Mon, 23 Nov 2015 23:18:49 +0000 (00:18 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 23 Nov 2015 23:20:39 +0000 (00:20 +0100)
Most servers apparently always implicitly convert DNAME to CNAME, but
some servers don't, hence implement this properly, as this is required
by edns0.

src/resolve/resolved-dns-cache.c
src/resolve/resolved-dns-question.c
src/resolve/resolved-dns-rr.c
src/resolve/resolved-dns-rr.h
src/shared/dns-domain.c
src/shared/dns-domain.h
src/test/test-dns-domain.c

index 04f64022e01c4444e404e8f6e6f9c598afff04a5..d963ce6e00357574f9e4f146884439a1bc54cb6d 100644 (file)
 ***/
 
 #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) {
index 75f2ed6042056f104cf8a15942264b614779c418..9fb3038381751e5fd4ddee1801d6bbbd9c73ab90 100644 (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;
 
index 636e07dea91d945c64a103373300f9599c0d8816..9b264900fcd9d50676d73dc458f939c72e42b6b8 100644 (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) {
index c02f2ec00f16c7ec17336f0c5cedddc5fa94a97b..c1601fb69480fc013f2ceadc3b620a5f7c4253d2 100644 (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);
index e08143c3be1e44c0d6e83e644b05dabdb3040af7..e6aad39c74532bfddc0afbf0ad03cb8ee2536aaf 100644 (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;
 
index c6d7623e09dd4c0301ade3f1b2226aa1bbecc13e..a68df330e6f5c7b0bdb93037d889534596e4155a 100644 (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);
index a664e1c79cf60a1443eb7487cf9bb1c89d4e2cc3..b76a31a5492de65a7a6a6dfc861e5171fd63b20a 100644 (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;
 }