chiark / gitweb /
resolved: most DNS servers can't handle more than one question per packet, hence...
authorLennart Poettering <lennart@poettering.net>
Tue, 22 Jul 2014 23:59:36 +0000 (01:59 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 23 Jul 2014 00:00:40 +0000 (02:00 +0200)
src/resolve/resolved-dns-answer.c
src/resolve/resolved-dns-answer.h
src/resolve/resolved-dns-query.c
src/resolve/resolved-dns-query.h

index 34c854c..d907664 100644 (file)
@@ -138,3 +138,40 @@ int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **r
 
         return 0;
 }
+
+DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b) {
+        _cleanup_(dns_answer_unrefp) DnsAnswer *ret = NULL;
+        DnsAnswer *k;
+        unsigned i;
+        int r;
+
+        if (a && (!b || b->n_rrs <= 0))
+                return dns_answer_ref(a);
+        if ((!a || a->n_rrs <= 0) && b)
+                return dns_answer_ref(b);
+
+        ret = dns_answer_new((a ? a->n_rrs : 0) + (b ? b->n_rrs : 0));
+        if (!ret)
+                return NULL;
+
+        if (a) {
+                for (i = 0; i < a->n_rrs; i++) {
+                        r = dns_answer_add(ret, a->rrs[i]);
+                        if (r < 0)
+                                return NULL;
+                }
+        }
+
+        if (b) {
+                for (i = 0; i < b->n_rrs; i++) {
+                        r = dns_answer_add(ret, b->rrs[i]);
+                        if (r < 0)
+                                return NULL;
+                }
+        }
+
+        k = ret;
+        ret = NULL;
+
+        return k;
+}
index 249383b..135a421 100644 (file)
@@ -41,4 +41,6 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr);
 int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key);
 int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret);
 
+DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b);
+
 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
index a56e295..8b4aa3b 100644 (file)
@@ -335,8 +335,9 @@ void dns_query_transaction_process_reply(DnsQueryTransaction *t, DnsPacket *p) {
         if (r < 0) {
                 dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY);
                 return;
-        } else
-                dns_cache_put(&t->scope->cache, p->question, DNS_PACKET_RCODE(p), p->answer, 0);
+        }
+
+        dns_cache_put(&t->scope->cache, p->question, DNS_PACKET_RCODE(p), p->answer, 0);
 
         if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
                 dns_query_transaction_complete(t, DNS_QUERY_SUCCESS);
@@ -575,7 +576,8 @@ static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) {
         return 0;
 }
 
-static int dns_query_add_transaction(DnsQuery *q, DnsScope *s) {
+static int dns_query_add_transaction(DnsQuery *q, DnsScope *s, DnsResourceKey *key) {
+        _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
         DnsQueryTransaction *t;
         int r;
 
@@ -585,12 +587,23 @@ static int dns_query_add_transaction(DnsQuery *q, DnsScope *s) {
         if (r < 0)
                 return r;
 
+        if (key) {
+                question = dns_question_new(1);
+                if (!question)
+                        return -ENOMEM;
+
+                r = dns_question_add(question, key);
+                if (r < 0)
+                        return r;
+        } else
+                question = dns_question_ref(q->question);
+
         LIST_FOREACH(transactions_by_scope, t, s->transactions)
-                if (dns_question_is_superset(t->question, q->question))
+                if (dns_question_is_superset(t->question, question))
                         break;
 
         if (!t) {
-                r = dns_query_transaction_new(&t, s, q->question);
+                r = dns_query_transaction_new(&t, s, question);
                 if (r < 0)
                         return r;
         }
@@ -616,6 +629,33 @@ fail:
         return r;
 }
 
+static int dns_query_add_transaction_split(DnsQuery *q, DnsScope *s) {
+        int r;
+
+        assert(q);
+        assert(s);
+
+        if (s->protocol == DNS_PROTOCOL_MDNS) {
+                r = dns_query_add_transaction(q, s, NULL);
+                if (r < 0)
+                        return r;
+        } else {
+                unsigned i;
+
+                /* On DNS and LLMNR we can only send a single
+                 * question per datagram, hence issue multiple
+                 * transactions. */
+
+                for (i = 0; i < q->question->n_keys; i++) {
+                        r = dns_query_add_transaction(q, s, q->question->keys[i]);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        return 0;
+}
+
 int dns_query_go(DnsQuery *q) {
         DnsScopeMatch found = DNS_SCOPE_NO;
         DnsScope *s, *first = NULL;
@@ -660,7 +700,7 @@ int dns_query_go(DnsQuery *q) {
         if (found == DNS_SCOPE_NO)
                 return -ESRCH;
 
-        r = dns_query_add_transaction(q, first);
+        r = dns_query_add_transaction_split(q, first);
         if (r < 0)
                 return r;
 
@@ -674,7 +714,7 @@ int dns_query_go(DnsQuery *q) {
                 if (match != found)
                         continue;
 
-                r = dns_query_add_transaction(q, s);
+                r = dns_query_add_transaction_split(q, s);
                 if (r < 0)
                         return r;
         }
@@ -711,8 +751,9 @@ fail:
 void dns_query_ready(DnsQuery *q) {
         DnsQueryTransaction *t;
         DnsQueryState state = DNS_QUERY_NO_SERVERS;
-        DnsAnswer *failure_answer = NULL;
-        int failure_rcode = 0, failure_ifindex = 0;
+        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+        int rcode = 0;
+        DnsScope *scope = NULL;
         Iterator i;
 
         assert(q);
@@ -728,6 +769,10 @@ void dns_query_ready(DnsQuery *q) {
 
         SET_FOREACH(t, q->transactions, i) {
 
+                /* If we found a successful answer, ignore all answers from other scopes */
+                if (state == DNS_QUERY_SUCCESS && t->scope != scope)
+                        continue;
+
                 /* One of the transactions is still going on, let's wait for it */
                 if (t->state == DNS_QUERY_PENDING || t->state == DNS_QUERY_NULL)
                         return;
@@ -735,34 +780,55 @@ void dns_query_ready(DnsQuery *q) {
                 /* One of the transactions is successful, let's use
                  * it, and copy its data out */
                 if (t->state == DNS_QUERY_SUCCESS) {
+                        DnsAnswer *a;
+
                         if (t->received) {
-                                q->answer = dns_answer_ref(t->received->answer);
-                                q->answer_ifindex = t->received->ifindex;
-                                q->answer_rcode = DNS_PACKET_RCODE(t->received);
+                                rcode = DNS_PACKET_RCODE(t->received);
+                                a = t->received->answer;
                         } else {
-                                q->answer = dns_answer_ref(t->cached);
-                                q->answer_ifindex = t->scope->link ? t->scope->link->ifindex : 0;
-                                q->answer_rcode = t->cached_rcode;
+                                rcode = t->cached_rcode;
+                                a = t->cached;
                         }
 
-                        dns_query_complete(q, DNS_QUERY_SUCCESS);
-                        return;
+                        if (state == DNS_QUERY_SUCCESS) {
+                                DnsAnswer *merged;
+
+                                merged = dns_answer_merge(answer, a);
+                                if (!merged) {
+                                        dns_query_complete(q, DNS_QUERY_RESOURCES);
+                                        return;
+                                }
+
+                                dns_answer_unref(answer);
+                                answer = merged;
+                        } else {
+                                dns_answer_unref(answer);
+                                answer = dns_answer_ref(a);
+                        }
+
+                        scope = t->scope;
+                        state = DNS_QUERY_SUCCESS;
+                        continue;
                 }
 
                 /* One of the transactions has failed, let's see
                  * whether we find anything better, but if not, return
-                 * its response packet */
-                if (t->state == DNS_QUERY_FAILURE) {
+                 * its response data */
+                if (state != DNS_QUERY_SUCCESS && t->state == DNS_QUERY_FAILURE) {
+                        DnsAnswer *a;
+
                         if (t->received) {
-                                failure_answer = t->received->answer;
-                                failure_ifindex = t->received->ifindex;
-                                failure_rcode = DNS_PACKET_RCODE(t->received);
+                                rcode = DNS_PACKET_RCODE(t->received);
+                                a = t->received->answer;
                         } else {
-                                failure_answer = t->cached;
-                                failure_ifindex = t->scope->link ? t->scope->link->ifindex : 0;
-                                failure_rcode = t->cached_rcode;
+                                rcode = t->cached_rcode;
+                                a = t->cached;
                         }
 
+                        dns_answer_unref(answer);
+                        answer = dns_answer_ref(a);
+
+                        scope = t->scope;
                         state = DNS_QUERY_FAILURE;
                         continue;
                 }
@@ -771,10 +837,10 @@ void dns_query_ready(DnsQuery *q) {
                         state = t->state;
         }
 
-        if (state == DNS_QUERY_FAILURE) {
-                q->answer = dns_answer_ref(failure_answer);
-                q->answer_ifindex = failure_ifindex;
-                q->answer_rcode = failure_rcode;
+        if (IN_SET(state, DNS_QUERY_SUCCESS, DNS_QUERY_FAILURE)) {
+                q->answer = dns_answer_ref(answer);
+                q->answer_rcode = rcode;
+                q->answer_ifindex = (scope && scope->link) ? scope->link->ifindex : 0;
         }
 
         dns_query_complete(q, state);
index c26abb8..0b76564 100644 (file)
@@ -90,7 +90,6 @@ struct DnsQuery {
         sd_event_source *timeout_event_source;
 
         /* Discovered data */
-        DnsPacket *received;
         DnsAnswer *answer;
         int answer_ifindex;
         int answer_rcode;