X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Fresolve%2Fresolved-dns-query.c;h=ace768b824f001834c11da01a28a4c3607d1a7de;hb=1716f6dcf54d4c181c2e2558e3d5414f54c8d9ca;hp=5bd59202544920c619c813e739df54a1db632e79;hpb=ad867662936a4c7ab2c7116d804c272338801231;p=elogind.git diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 5bd592025..ace768b82 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -25,6 +25,10 @@ #define TRANSACTION_TIMEOUT_USEC (5 * USEC_PER_SEC) #define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) #define ATTEMPTS_MAX 8 +#define CNAME_MAX 8 +#define QUERIES_MAX 2048 + +static int dns_query_transaction_go(DnsQueryTransaction *t); DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t) { if (!t) @@ -35,6 +39,8 @@ DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t) { dns_packet_unref(t->sent); dns_packet_unref(t->received); + dns_resource_record_freev(t->cached_rrs, t->n_cached_rrs); + sd_event_source_unref(t->tcp_event_source); safe_close(t->tcp_fd); @@ -102,18 +108,19 @@ static void dns_query_transaction_stop(DnsQueryTransaction *t) { t->tcp_fd = safe_close(t->tcp_fd); } -static void dns_query_transaction_set_state(DnsQueryTransaction *t, DnsQueryState state) { +static void dns_query_transaction_complete(DnsQueryTransaction *t, DnsQueryState state) { assert(t); + assert(!IN_SET(state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); + assert(IN_SET(t->state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); - if (t->state == state) - return; + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ t->state = state; - if (state != DNS_QUERY_SENT) { - dns_query_transaction_stop(t); - dns_query_finish(t->query); - } + dns_query_transaction_stop(t); + dns_query_finish(t->query); } static int on_tcp_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -139,7 +146,7 @@ static int on_tcp_ready(sd_event_source *s, int fd, uint32_t revents, void *user ss = writev(fd, iov, 2); if (ss < 0) { if (errno != EINTR && errno != EAGAIN) { - dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); return -errno; } } else @@ -149,7 +156,7 @@ static int on_tcp_ready(sd_event_source *s, int fd, uint32_t revents, void *user if (t->tcp_written >= sizeof(sz) + t->sent->size) { r = sd_event_source_set_io_events(s, EPOLLIN); if (r < 0) { - dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); return r; } } @@ -163,11 +170,11 @@ static int on_tcp_ready(sd_event_source *s, int fd, uint32_t revents, void *user ss = read(fd, (uint8_t*) &t->tcp_read_size + t->tcp_read, sizeof(t->tcp_read_size) - t->tcp_read); if (ss < 0) { if (errno != EINTR && errno != EAGAIN) { - dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); return -errno; } } else if (ss == 0) { - dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); return -EIO; } else t->tcp_read += ss; @@ -176,7 +183,7 @@ static int on_tcp_ready(sd_event_source *s, int fd, uint32_t revents, void *user if (t->tcp_read >= sizeof(t->tcp_read_size)) { if (be16toh(t->tcp_read_size) < DNS_PACKET_HEADER_SIZE) { - dns_query_transaction_set_state(t, DNS_QUERY_INVALID_REPLY); + dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY); return -EBADMSG; } @@ -184,9 +191,9 @@ static int on_tcp_ready(sd_event_source *s, int fd, uint32_t revents, void *user ssize_t ss; if (!t->received) { - r = dns_packet_new(&t->received, be16toh(t->tcp_read_size)); + r = dns_packet_new(&t->received, t->scope->protocol, be16toh(t->tcp_read_size)); if (r < 0) { - dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); return r; } } @@ -196,11 +203,11 @@ static int on_tcp_ready(sd_event_source *s, int fd, uint32_t revents, void *user sizeof(t->tcp_read_size) + be16toh(t->tcp_read_size) - t->tcp_read); if (ss < 0) { if (errno != EINTR && errno != EAGAIN) { - dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); return -errno; } } else if (ss == 0) { - dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); return -EIO; } else t->tcp_read += ss; @@ -217,11 +224,14 @@ static int on_tcp_ready(sd_event_source *s, int fd, uint32_t revents, void *user return 0; } -static int dns_query_transaction_start_tcp(DnsQueryTransaction *t) { +static int dns_query_transaction_open_tcp(DnsQueryTransaction *t) { int r; assert(t); + if (t->scope->protocol == DNS_PROTOCOL_DNS) + return -ENOTSUP; + if (t->tcp_fd >= 0) return 0; @@ -247,9 +257,11 @@ void dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p) { assert(t); assert(p); + assert(t->state == DNS_QUERY_PENDING); - if (t->state != DNS_QUERY_SENT) - return; + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ if (t->received != p) { dns_packet_unref(t->received); @@ -259,30 +271,51 @@ void dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p) { if (t->tcp_fd >= 0) { if (DNS_PACKET_TC(p)) { /* Truncated via TCP? Somebody must be fucking with us */ - dns_query_transaction_set_state(t, DNS_QUERY_INVALID_REPLY); + dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY); return; } if (DNS_PACKET_ID(p) != t->id) { /* Not the reply to our query? Somebody must be fucking with us */ - dns_query_transaction_set_state(t, DNS_QUERY_INVALID_REPLY); + dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY); return; } } if (DNS_PACKET_TC(p)) { /* Response was truncated, let's try again with good old TCP */ - r = dns_query_transaction_start_tcp(t); + r = dns_query_transaction_open_tcp(t); + if (r == -ESRCH) { + /* No servers found? Damn! */ + dns_query_transaction_complete(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_go(t); + if (r < 0) { + dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); + return; + } + return; } } + /* Parse and update the cache */ + r = dns_packet_extract_rrs(p); + if (r < 0) { + dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY); + return; + } else if (r > 0) + dns_cache_put_rrs(&t->scope->cache, p->rrs, r, 0); + if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) - dns_query_transaction_set_state(t, DNS_QUERY_SUCCESS); + dns_query_transaction_complete(t, DNS_QUERY_SUCCESS); else - dns_query_transaction_set_state(t, DNS_QUERY_FAILURE); + dns_query_transaction_complete(t, DNS_QUERY_FAILURE); } static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { @@ -295,16 +328,16 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat /* Timeout reached? Try again, with a new server */ dns_scope_next_dns_server(t->scope); - r = dns_query_transaction_start(t); + r = dns_query_transaction_go(t); if (r < 0) - dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); return 0; } static int dns_query_make_packet(DnsQueryTransaction *t) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - unsigned n; + unsigned n, added = 0; int r; assert(t); @@ -312,17 +345,28 @@ static int dns_query_make_packet(DnsQueryTransaction *t) { if (t->sent) return 0; - r = dns_packet_new_query(&p, 0); + r = dns_packet_new_query(&p, t->scope->protocol, 0); if (r < 0) return r; for (n = 0; n < t->query->n_keys; n++) { + r = dns_scope_good_key(t->scope, &t->query->keys[n]); + if (r < 0) + return r; + if (r == 0) + continue; + r = dns_packet_append_key(p, &t->query->keys[n], NULL); if (r < 0) return r; + + added++; } - DNS_PACKET_HEADER(p)->qdcount = htobe16(t->query->n_keys); + if (added <= 0) + return -EDOM; + + DNS_PACKET_HEADER(p)->qdcount = htobe16(added); DNS_PACKET_HEADER(p)->id = t->id; t->sent = p; @@ -331,7 +375,7 @@ static int dns_query_make_packet(DnsQueryTransaction *t) { return 0; } -int dns_query_transaction_start(DnsQueryTransaction *t) { +static int dns_query_transaction_go(DnsQueryTransaction *t) { int r; assert(t); @@ -339,36 +383,59 @@ int dns_query_transaction_start(DnsQueryTransaction *t) { dns_query_transaction_stop(t); if (t->n_attempts >= ATTEMPTS_MAX) { - dns_query_transaction_set_state(t, DNS_QUERY_ATTEMPTS_MAX); + dns_query_transaction_complete(t, DNS_QUERY_ATTEMPTS_MAX); return 0; } + t->n_attempts++; + t->received = dns_packet_unref(t->received); + t->cached_rrs = dns_resource_record_freev(t->cached_rrs, t->n_cached_rrs); + t->n_cached_rrs = 0; + /* First, let's try the cache */ + dns_cache_prune(&t->scope->cache); + r = dns_cache_lookup_many(&t->scope->cache, t->query->keys, t->query->n_keys, &t->cached_rrs); + if (r < 0) + return r; + if (r > 0) { + t->n_cached_rrs = r; + dns_query_transaction_complete(t, DNS_QUERY_SUCCESS); + return 0; + } + + /* Otherwise, we need to ask the network */ r = dns_query_make_packet(t); + if (r == -EDOM) { + /* Not the right request to make on this network? + * (i.e. an A request made on IPv6 or an AAAA request + * made on IPv4, on LLMNR or mDNS.) */ + dns_query_transaction_complete(t, DNS_QUERY_NO_SERVERS); + return 0; + } if (r < 0) return r; /* 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) - r = dns_query_transaction_start_tcp(t); - + r = dns_query_transaction_open_tcp(t); if (r == -ESRCH) { - dns_query_transaction_set_state(t, DNS_QUERY_NO_SERVERS); + /* No servers to send this to? */ + dns_query_transaction_complete(t, DNS_QUERY_NO_SERVERS); return 0; } if (r < 0) { /* Couldn't send? Try immediately again, with a new server */ dns_scope_next_dns_server(t->scope); - return dns_query_transaction_start(t); + return dns_query_transaction_go(t); } r = sd_event_add_time(t->query->manager->event, &t->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + TRANSACTION_TIMEOUT_USEC, 0, on_transaction_timeout, t); if (r < 0) return r; - dns_query_transaction_set_state(t, DNS_QUERY_SENT); + t->state = DNS_QUERY_PENDING; return 1; } @@ -380,13 +447,18 @@ DnsQuery *dns_query_free(DnsQuery *q) { sd_bus_message_unref(q->request); dns_packet_unref(q->received); + + dns_resource_record_freev(q->cached_rrs, q->n_cached_rrs); + sd_event_source_unref(q->timeout_event_source); while (q->transactions) dns_query_transaction_free(q->transactions); - if (q->manager) + if (q->manager) { LIST_REMOVE(queries, q->manager->dns_queries, q); + q->manager->n_dns_queries--; + } for (n = 0; n < q->n_keys; n++) free(q->keys[n].name); @@ -398,16 +470,16 @@ 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); if (n_keys <= 0 || n_keys >= 65535) return -EINVAL; + if (m->n_dns_queries >= QUERIES_MAX) + return -EBUSY; + assert(keys); q = new0(DnsQuery, 1); @@ -429,15 +501,76 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsResourceKey *keys, unsigned n_k name = q->keys[q->n_keys].name; else if (!dns_name_equal(name, q->keys[q->n_keys].name)) return -EINVAL; + + log_debug("Looking up RR for %s %s %s", + strna(dns_class_to_string(keys[q->n_keys].class)), + strna(dns_type_to_string(keys[q->n_keys].type)), + keys[q->n_keys].name); } LIST_PREPEND(queries, m->dns_queries, q); + m->n_dns_queries++; 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_complete(DnsQuery *q, DnsQueryState state) { + assert(q); + assert(!IN_SET(state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); + assert(IN_SET(q->state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ + + q->state = state; + + dns_query_stop(q); + 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_complete(q, DNS_QUERY_TIMEOUT); + return 0; +} + +int dns_query_go(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_good_domain(s, q->keys[0].name); if (match < 0) return match; @@ -458,17 +591,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_good_domain(s, q->keys[0].name); if (match < 0) return match; @@ -478,79 +610,30 @@ 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); + q->state = DNS_QUERY_PENDING; + q->block_finish++; LIST_FOREACH(transactions_by_query, t, q->transactions) { - - r = dns_query_transaction_start(t); + r = dns_query_transaction_go(t); if (r < 0) goto fail; - - if (q->state != DNS_QUERY_SENT) - break; } - return 0; + q->block_finish--; + dns_query_finish(q); -fail: - while (q->transactions) - dns_query_transaction_free(q->transactions); + return 1; +fail: + dns_query_stop(q); return r; } @@ -560,20 +643,34 @@ void dns_query_finish(DnsQuery *q) { DnsPacket *received = NULL; assert(q); + assert(IN_SET(q->state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); - if (q->state != DNS_QUERY_SENT) + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function, unless the block_finish + * counter was explicitly bumped before doing so. */ + + if (q->block_finish > 0) 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 */ + /* One of the transactions is successful, let's use + * it, and copy its data out */ if (t->state == DNS_QUERY_SUCCESS) { q->received = dns_packet_ref(t->received); - dns_query_set_state(q, DNS_QUERY_SUCCESS); + + /* We simply steal the cached RRs array */ + q->cached_rrs = t->cached_rrs; + q->n_cached_rrs = t->n_cached_rrs; + t->cached_rrs = NULL; + t->n_cached_rrs = 0; + + dns_query_complete(q, DNS_QUERY_SUCCESS); return; } @@ -593,5 +690,146 @@ void dns_query_finish(DnsQuery *q) { if (state == DNS_QUERY_FAILURE) q->received = dns_packet_ref(received); - dns_query_set_state(q, state); + dns_query_complete(q, state); +} + +int dns_query_cname_redirect(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_stop(q); + q->state = 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; +} + +int dns_query_get_rrs(DnsQuery *q, DnsResourceRecord ***rrs) { + int r; + + assert(q); + assert(rrs); + + if (IN_SET(q->state, DNS_QUERY_NULL, DNS_QUERY_PENDING)) + return -EBUSY; + + if (q->received) { + r = dns_packet_extract_rrs(q->received); + if (r < 0) + return r; + if (r == 0) { + *rrs = NULL; + return r; + } + + *rrs = q->received->rrs; + return r; + } + + if (q->cached_rrs) { + *rrs = q->cached_rrs; + return q->n_cached_rrs; + } + + return -ESRCH; +} + +int dns_query_get_rcode(DnsQuery *q) { + assert(q); + + if (IN_SET(q->state, DNS_QUERY_NULL, DNS_QUERY_PENDING)) + return -EBUSY; + + if (!q->received) + return -ESRCH; + + return DNS_PACKET_RCODE(q->received); +} + +int dns_query_get_ifindex(DnsQuery *q) { + assert(q); + + if (IN_SET(q->state, DNS_QUERY_NULL, DNS_QUERY_PENDING)) + return -EBUSY; + + if (!q->received) + return -ESRCH; + + return q->received->ifindex; }