From 8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 16 Jul 2014 22:09:00 +0200 Subject: [PATCH] resolved: add CNAME lookup support --- src/resolve/resolved-bus.c | 152 ++++++++++++------ src/resolve/resolved-dns-packet.c | 2 +- src/resolve/resolved-dns-packet.h | 2 + src/resolve/resolved-dns-query.c | 247 +++++++++++++++++++++--------- src/resolve/resolved-dns-query.h | 12 +- src/shared/bus-errors.h | 1 + 6 files changed, 298 insertions(+), 118 deletions(-) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 64bb9ac8894..ebb97d7fd34 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -82,15 +82,55 @@ static int reply_query_state(DnsQuery *q) { } case DNS_QUERY_NULL: - case DNS_QUERY_SENT: + case DNS_QUERY_PENDING: case DNS_QUERY_SUCCESS: + default: assert_not_reached("Impossible state"); } } +static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) { + int r; + + assert(reply); + assert(rr); + + r = sd_bus_message_open_container(reply, 'r', "yayi"); + if (r < 0) + return r; + + if (rr->key.type == DNS_TYPE_A) { + r = sd_bus_message_append(reply, "y", AF_INET); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); + } else { + r = sd_bus_message_append(reply, "y", AF_INET6); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); + } + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", ifindex); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 0; +} + static void bus_method_resolve_hostname_complete(DnsQuery *q) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; unsigned i, n, added = 0; + size_t answer_rindex; int r; assert(q); @@ -106,6 +146,8 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { if (r < 0) goto parse_fail; + answer_rindex = q->received->rindex; + r = sd_bus_message_new_method_return(q->request, &reply); if (r < 0) goto finish; @@ -117,6 +159,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { n = DNS_PACKET_ANCOUNT(q->received) + DNS_PACKET_NSCOUNT(q->received) + DNS_PACKET_ARCOUNT(q->received); + for (i = 0; i < n; i++) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; @@ -124,41 +167,22 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { if (r < 0) goto parse_fail; - if (rr->key.class != DNS_CLASS_IN) - continue; - - if (!(q->request_family != AF_INET6 && rr->key.type == DNS_TYPE_A) && - !(q->request_family != AF_INET && rr->key.type == DNS_TYPE_AAAA)) - continue; - - if (!dns_name_equal(rr->key.name, q->request_hostname)) - continue; - - r = sd_bus_message_open_container(reply, 'r', "yayi"); + r = dns_query_matches_rr(q, rr); if (r < 0) - goto finish; - - if (rr->key.type == DNS_TYPE_A) { - r = sd_bus_message_append(reply, "y", AF_INET); + goto parse_fail; + if (r == 0) { + /* Hmm, if this is not an address record, + maybe it's a cname? If so, remember this */ + r = dns_query_matches_cname(q, rr); if (r < 0) - goto finish; + goto parse_fail; + if (r > 0) + cname = dns_resource_record_ref(rr); - r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); - } else { - r = sd_bus_message_append(reply, "y", AF_INET6); - if (r < 0) - goto finish; - - r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); + continue; } - if (r < 0) - goto finish; - r = sd_bus_message_append(reply, "i", q->received->ifindex); - if (r < 0) - goto finish; - - r = sd_bus_message_close_container(reply); + r = append_address(reply, rr, q->received->ifindex); if (r < 0) goto finish; @@ -166,8 +190,56 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { } if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have RR of this type", q->request_hostname); - goto finish; + if (!cname) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have RR of this type", q->request_hostname); + goto finish; + } + + /* This has a cname? Then update the query with the + * new cname. */ + r = dns_query_follow_cname(q, cname->cname.name); + if (r < 0) { + if (r == -ELOOP) + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop on '%s'", q->request_hostname); + else + r = sd_bus_reply_method_errno(q->request, -r, NULL); + + goto finish; + } + + /* Before we restart the query, let's see if any of + * the RRs we already got already answers our query */ + dns_packet_rewind(q->received, answer_rindex); + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_packet_read_rr(q->received, &rr, NULL); + if (r < 0) + goto parse_fail; + + r = dns_query_matches_rr(q, rr); + if (r < 0) + goto parse_fail; + if (r == 0) + continue; + + r = append_address(reply, rr, q->received->ifindex); + if (r < 0) + goto finish; + + added++; + } + + /* If we didn't find anything, then let's restart the + * query, this time with the cname */ + if (added <= 0) { + r = dns_query_start(q); + if (r < 0) { + r = sd_bus_reply_method_errno(q->request, -r, NULL); + goto finish; + } + return; + } } r = sd_bus_message_close_container(reply); @@ -245,7 +317,6 @@ static int bus_method_resolve_hostname(sd_bus *bus, sd_bus_message *message, voi static void bus_method_resolve_address_complete(DnsQuery *q) { _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; unsigned i, n, added = 0; - _cleanup_free_ char *reverse = NULL; int r; assert(q); @@ -257,10 +328,6 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { assert(q->received); - r = dns_name_reverse(q->request_family, &q->request_address, &reverse); - if (r < 0) - goto finish; - r = dns_packet_skip_question(q->received); if (r < 0) goto parse_fail; @@ -284,11 +351,10 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { if (r < 0) goto parse_fail; - if (rr->key.class != DNS_CLASS_IN) - continue; - if (rr->key.type != DNS_TYPE_PTR) - continue; - if (!dns_name_equal(rr->key.name, reverse)) + r = dns_query_matches_rr(q, rr); + if (r < 0) + goto parse_fail; + if (r == 0) continue; r = sd_bus_message_append(reply, "s", rr->ptr.name); diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index af296f63acb..5597ffd9690 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -395,7 +395,7 @@ int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { return 0; } -static void dns_packet_rewind(DnsPacket *p, size_t idx) { +void dns_packet_rewind(DnsPacket *p, size_t idx) { assert(p); assert(idx <= p->size); assert(idx >= DNS_PACKET_HEADER_SIZE); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index d4c9c475f5b..99f60a15569 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -116,6 +116,8 @@ int dns_packet_read_name(DnsPacket *p, char **ret, size_t *start); int dns_packet_read_key(DnsPacket *p, DnsResourceKey *ret, size_t *start); int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start); +void dns_packet_rewind(DnsPacket *p, size_t idx); + int dns_packet_skip_question(DnsPacket *p); enum { diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 5bd59202544..6e04324da5d 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -25,6 +25,9 @@ #define TRANSACTION_TIMEOUT_USEC (5 * USEC_PER_SEC) #define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) #define ATTEMPTS_MAX 8 +#define CNAME_MAX 8 + +static int dns_query_transaction_start(DnsQueryTransaction *t); DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t) { if (!t) @@ -110,7 +113,7 @@ static void dns_query_transaction_set_state(DnsQueryTransaction *t, DnsQueryStat t->state = state; - if (state != DNS_QUERY_SENT) { + if (state != DNS_QUERY_PENDING) { dns_query_transaction_stop(t); dns_query_finish(t->query); } @@ -248,7 +251,7 @@ void dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p) { assert(t); assert(p); - if (t->state != DNS_QUERY_SENT) + if (t->state != DNS_QUERY_PENDING) return; if (t->received != p) { @@ -273,8 +276,21 @@ void dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p) { if (DNS_PACKET_TC(p)) { /* Response was truncated, let's try again with good old TCP */ r = dns_query_transaction_start_tcp(t); + if (r == -ESRCH) { + /* No servers found? Damn! */ + dns_query_transaction_set_state(t, DNS_QUERY_NO_SERVERS); + return; + } if (r < 0) { - dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + /* Couldn't send? Try immediately again, with a new server */ + dns_scope_next_dns_server(t->scope); + + r = dns_query_transaction_start(t); + if (r < 0) { + dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + return; + } + return; } } @@ -331,7 +347,7 @@ static int dns_query_make_packet(DnsQueryTransaction *t) { return 0; } -int dns_query_transaction_start(DnsQueryTransaction *t) { +static int dns_query_transaction_start(DnsQueryTransaction *t) { int r; assert(t); @@ -342,12 +358,14 @@ int dns_query_transaction_start(DnsQueryTransaction *t) { dns_query_transaction_set_state(t, DNS_QUERY_ATTEMPTS_MAX); return 0; } - t->n_attempts++; r = dns_query_make_packet(t); if (r < 0) return r; + t->n_attempts++; + t->received = dns_packet_unref(t->received); + /* Try via UDP, and if that fails due to large size try via TCP */ r = dns_scope_send(t->scope, t->sent); if (r == -EMSGSIZE) @@ -368,7 +386,7 @@ int dns_query_transaction_start(DnsQueryTransaction *t) { if (r < 0) return r; - dns_query_transaction_set_state(t, DNS_QUERY_SENT); + dns_query_transaction_set_state(t, DNS_QUERY_PENDING); return 1; } @@ -398,10 +416,7 @@ DnsQuery *dns_query_free(DnsQuery *q) { int dns_query_new(Manager *m, DnsQuery **ret, DnsResourceKey *keys, unsigned n_keys) { _cleanup_(dns_query_freep) DnsQuery *q = NULL; - DnsScope *s, *first = NULL; - DnsScopeMatch found = DNS_SCOPE_NO; const char *name = NULL; - int n, r; assert(m); @@ -434,10 +449,67 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsResourceKey *keys, unsigned n_k LIST_PREPEND(queries, m->dns_queries, q); q->manager = m; - LIST_FOREACH(scopes, s, m->dns_scopes) { + if (ret) + *ret = q; + q = NULL; + + return 0; +} + +static void dns_query_stop(DnsQuery *q) { + assert(q); + + q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); + + while (q->transactions) + dns_query_transaction_free(q->transactions); +} + +static void dns_query_set_state(DnsQuery *q, DnsQueryState state) { + DnsQueryState old_state; + assert(q); + + if (q->state == state) + return; + + old_state = q->state; + q->state = state; + + if (!IN_SET(state, DNS_QUERY_NULL, DNS_QUERY_PENDING)) { + dns_query_stop(q); + + if (old_state == DNS_QUERY_PENDING && q->complete) + q->complete(q); + } +} + +static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsQuery *q = userdata; + + assert(s); + assert(q); + + dns_query_set_state(q, DNS_QUERY_TIMEOUT); + return 0; +} + +int dns_query_start(DnsQuery *q) { + DnsScopeMatch found = DNS_SCOPE_NO; + DnsScope *s, *first = NULL; + DnsQueryTransaction *t; + int r; + + assert(q); + + if (q->state != DNS_QUERY_NULL) + return 0; + + assert(q->n_keys > 0); + + LIST_FOREACH(scopes, s, q->manager->dns_scopes) { DnsScopeMatch match; - match = dns_scope_test(s, name); + match = dns_scope_test(s, q->keys[0].name); if (match < 0) return match; @@ -458,17 +530,16 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsResourceKey *keys, unsigned n_k } if (found == DNS_SCOPE_NO) - return -ENETDOWN; + return -ESRCH; r = dns_query_transaction_new(q, NULL, first); if (r < 0) return r; - n = 1; LIST_FOREACH(scopes, s, first->scopes_next) { DnsScopeMatch match; - match = dns_scope_test(s, name); + match = dns_scope_test(s, q->keys[0].name); if (match < 0) return match; @@ -478,62 +549,15 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsResourceKey *keys, unsigned n_k r = dns_query_transaction_new(q, NULL, s); if (r < 0) return r; - - n++; } - if (ret) - *ret = q; - q = NULL; - - return n; -} - -static void dns_query_set_state(DnsQuery *q, DnsQueryState state) { - assert(q); - - if (q->state == state) - return; - - q->state = state; - - if (state == DNS_QUERY_SENT) - return; - - q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); - - while (q->transactions) - dns_query_transaction_free(q->transactions); - - if (q->complete) - q->complete(q); -} - -static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { - DnsQuery *q = userdata; - - assert(s); - assert(q); - - dns_query_set_state(q, DNS_QUERY_TIMEOUT); - return 0; -} - -int dns_query_start(DnsQuery *q) { - DnsQueryTransaction *t; - int r; - - assert(q); - assert(q->state == DNS_QUERY_NULL); - - if (!q->transactions) - return -ENETDOWN; + q->received = dns_packet_unref(q->received); r = sd_event_add_time(q->manager->event, &q->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + QUERY_TIMEOUT_USEC, 0, on_query_timeout, q); if (r < 0) goto fail; - dns_query_set_state(q, DNS_QUERY_SENT); + dns_query_set_state(q, DNS_QUERY_PENDING); LIST_FOREACH(transactions_by_query, t, q->transactions) { @@ -541,16 +565,14 @@ int dns_query_start(DnsQuery *q) { if (r < 0) goto fail; - if (q->state != DNS_QUERY_SENT) + if (q->state != DNS_QUERY_PENDING) break; } - return 0; + return 1; fail: - while (q->transactions) - dns_query_transaction_free(q->transactions); - + dns_query_stop(q); return r; } @@ -561,13 +583,13 @@ void dns_query_finish(DnsQuery *q) { assert(q); - if (q->state != DNS_QUERY_SENT) + if (q->state != DNS_QUERY_PENDING) return; LIST_FOREACH(transactions_by_query, t, q->transactions) { /* One of the transactions is still going on, let's wait for it */ - if (t->state == DNS_QUERY_SENT || t->state == DNS_QUERY_NULL) + if (t->state == DNS_QUERY_PENDING || t->state == DNS_QUERY_NULL) return; /* One of the transactions is successful, let's use it */ @@ -595,3 +617,88 @@ void dns_query_finish(DnsQuery *q) { dns_query_set_state(q, state); } + +int dns_query_follow_cname(DnsQuery *q, const char *name) { + DnsResourceKey *keys; + unsigned i; + + assert(q); + + if (q->n_cname > CNAME_MAX) + return -ELOOP; + + keys = new(DnsResourceKey, q->n_keys); + if (!keys) + return -ENOMEM; + + for (i = 0; i < q->n_keys; i++) { + keys[i].class = q->keys[i].class; + keys[i].type = q->keys[i].type; + keys[i].name = strdup(name); + if (!keys[i].name) { + + for (; i > 0; i--) + free(keys[i-1].name); + free(keys); + return -ENOMEM; + } + } + + for (i = 0; i < q->n_keys; i++) + free(q->keys[i].name); + free(q->keys); + + q->keys = keys; + + q->n_cname++; + + dns_query_set_state(q, DNS_QUERY_NULL); + return 0; +} + +int dns_query_matches_rr(DnsQuery *q, DnsResourceRecord *rr) { + unsigned i; + int r; + + assert(q); + assert(rr); + + for (i = 0; i < q->n_keys; i++) { + + if (rr->key.class != q->keys[i].class) + continue; + + if (rr->key.type != q->keys[i].type && + q->keys[i].type != DNS_TYPE_ANY) + continue; + + r = dns_name_equal(rr->key.name, q->keys[i].name); + if (r != 0) + return r; + } + + return 0; +} + +int dns_query_matches_cname(DnsQuery *q, DnsResourceRecord *rr) { + unsigned i; + int r; + + assert(q); + assert(rr); + + for (i = 0; i < q->n_keys; i++) { + + if (rr->key.class != q->keys[i].class) + continue; + + if (rr->key.type != DNS_TYPE_CNAME) + continue; + + r = dns_name_equal(rr->key.name, q->keys[i].name); + if (r != 0) + return r; + } + + return 0; +} diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index d5bc08dc30b..864036acc73 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -36,14 +36,14 @@ typedef struct DnsQueryTransaction DnsQueryTransaction; typedef enum DnsQueryState { DNS_QUERY_NULL, - DNS_QUERY_SENT, + DNS_QUERY_PENDING, DNS_QUERY_FAILURE, DNS_QUERY_SUCCESS, DNS_QUERY_NO_SERVERS, DNS_QUERY_TIMEOUT, DNS_QUERY_ATTEMPTS_MAX, DNS_QUERY_INVALID_REPLY, - DNS_QUERY_RESOURCES, + DNS_QUERY_RESOURCES } DnsQueryState; struct DnsQueryTransaction { @@ -75,11 +75,13 @@ struct DnsQuery { unsigned n_keys; DnsQueryState state; + unsigned n_cname; sd_event_source *timeout_event_source; DnsPacket *received; + /* Bus client information */ sd_bus_message *request; unsigned char request_family; const char *request_hostname; @@ -94,10 +96,12 @@ struct DnsQuery { int dns_query_new(Manager *m, DnsQuery **q, DnsResourceKey *keys, unsigned n_keys); DnsQuery *dns_query_free(DnsQuery *q); int dns_query_start(DnsQuery *q); -void dns_query_finish(DnsQuery *q); +int dns_query_follow_cname(DnsQuery *q, const char *name); +int dns_query_matches_rr(DnsQuery *q, DnsResourceRecord *rr); +int dns_query_matches_cname(DnsQuery *q, DnsResourceRecord *rr); DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t); -int dns_query_transaction_start(DnsQueryTransaction *t); void dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p); +void dns_query_finish(DnsQuery *q); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/shared/bus-errors.h b/src/shared/bus-errors.h index f3d973507e6..e4d0d435a5d 100644 --- a/src/shared/bus-errors.h +++ b/src/shared/bus-errors.h @@ -64,4 +64,5 @@ #define BUS_ERROR_INVALID_REPLY "org.freedesktop.resolve1.InvalidReply" #define BUS_ERROR_NO_SUCH_RR "org.freedesktop.resolve1.NoSuchRR" #define BUS_ERROR_NO_RESOURCES "org.freedesktop.resolve1.NoResources" +#define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop" #define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError."