From ad867662936a4c7ab2c7116d804c272338801231 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 16 Jul 2014 20:15:47 +0200 Subject: [PATCH] resolved: support for TCP DNS queries --- src/resolve/resolved-bus.c | 317 ++++++++++++++---------------- src/resolve/resolved-dns-packet.h | 1 + src/resolve/resolved-dns-query.c | 270 +++++++++++++++++++++---- src/resolve/resolved-dns-query.h | 16 +- src/resolve/resolved-dns-scope.c | 57 +++++- src/resolve/resolved-dns-scope.h | 2 + src/resolve/resolved-manager.c | 10 +- src/shared/bus-errors.h | 1 + 8 files changed, 455 insertions(+), 219 deletions(-) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 02e971dbf..64bb9ac88 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -25,141 +25,161 @@ #include "resolved.h" #include "resolved-dns-domain.h" -static void bus_method_resolve_hostname_complete(DnsQuery *q) { +static int reply_query_state(DnsQuery *q) { + _cleanup_free_ char *ip = NULL; + const char *name; int r; - assert(q); + if (q->request_hostname) + name = q->request_hostname; + else { + r = in_addr_to_string(q->request_family, &q->request_address, &ip); + if (r < 0) + return r; + + name = ip; + } - switch(q->state) { + switch (q->state) { - case DNS_QUERY_SKIPPED: - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "Not appropriate name servers or networks found"); - break; + case DNS_QUERY_NO_SERVERS: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "Not appropriate name servers or networks found"); case DNS_QUERY_TIMEOUT: - r = sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); - break; + return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); case DNS_QUERY_ATTEMPTS_MAX: - r = sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); - break; + return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); + + case DNS_QUERY_RESOURCES: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_RESOURCES, "Not enough resources"); + + case DNS_QUERY_INVALID_REPLY: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); case DNS_QUERY_FAILURE: { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - if (q->rcode == DNS_RCODE_NXDOMAIN) - sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "Hostname %s does not exist", q->request_hostname); + assert(q->received); + + if (DNS_PACKET_RCODE(q->received) == DNS_RCODE_NXDOMAIN) + sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", name); else { const char *rc, *n; - char p[DECIMAL_STR_MAX(q->rcode)]; + char p[3]; /* the rcode is 4 bits long */ - rc = dns_rcode_to_string(q->rcode); + rc = dns_rcode_to_string(DNS_PACKET_RCODE(q->received)); if (!rc) { - sprintf(p, "%i", q->rcode); + sprintf(p, "%i", DNS_PACKET_RCODE(q->received)); rc = p; } n = strappenda(_BUS_ERROR_DNS, rc); - - sd_bus_error_setf(&error, n, "Could not resolve hostname %s, server or network returned error %s", q->request_hostname, rc); + sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", name, rc); } - r = sd_bus_reply_method_error(q->request, &error); - break; + return sd_bus_reply_method_error(q->request, &error); } - case DNS_QUERY_SUCCESS: { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - unsigned i, n, added = 0; + case DNS_QUERY_NULL: + case DNS_QUERY_SENT: + case DNS_QUERY_SUCCESS: + assert_not_reached("Impossible state"); + } +} - assert(q->packet); +static void bus_method_resolve_hostname_complete(DnsQuery *q) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + unsigned i, n, added = 0; + int r; - r = dns_packet_skip_question(q->packet); - if (r < 0) - goto parse_fail; + assert(q); - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; + if (q->state != DNS_QUERY_SUCCESS) { + r = reply_query_state(q); + goto finish; + } - r = sd_bus_message_open_container(reply, 'a', "(yayi)"); - if (r < 0) - goto finish; + assert(q->received); - n = DNS_PACKET_ANCOUNT(q->packet) + - DNS_PACKET_NSCOUNT(q->packet) + - DNS_PACKET_ARCOUNT(q->packet); - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + r = dns_packet_skip_question(q->received); + if (r < 0) + goto parse_fail; - r = dns_packet_read_rr(q->packet, &rr, NULL); - if (r < 0) - goto parse_fail; + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; - if (rr->key.class != DNS_CLASS_IN) - continue; + r = sd_bus_message_open_container(reply, 'a', "(yayi)"); + if (r < 0) + goto finish; - if (!(q->request_family != AF_INET6 && rr->key.type == DNS_TYPE_A) && - !(q->request_family != AF_INET && rr->key.type == DNS_TYPE_AAAA)) - continue; + 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; - if (!dns_name_equal(rr->key.name, q->request_hostname)) - continue; + r = dns_packet_read_rr(q->received, &rr, NULL); + if (r < 0) + goto parse_fail; - r = sd_bus_message_open_container(reply, 'r', "yayi"); - if (r < 0) - goto finish; + if (rr->key.class != DNS_CLASS_IN) + continue; - if (rr->key.type == DNS_TYPE_A) { - r = sd_bus_message_append(reply, "y", AF_INET); - if (r < 0) - goto finish; + if (!(q->request_family != AF_INET6 && rr->key.type == DNS_TYPE_A) && + !(q->request_family != AF_INET && rr->key.type == DNS_TYPE_AAAA)) + continue; - 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; + if (!dns_name_equal(rr->key.name, q->request_hostname)) + continue; - r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); - } - if (r < 0) - goto finish; + r = sd_bus_message_open_container(reply, 'r', "yayi"); + if (r < 0) + goto finish; - r = sd_bus_message_append(reply, "i", q->packet->ifindex); + if (rr->key.type == DNS_TYPE_A) { + r = sd_bus_message_append(reply, "y", AF_INET); if (r < 0) goto finish; - r = sd_bus_message_close_container(reply); + 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; - added ++; + r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); } + if (r < 0) + goto finish; - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Hostname %s does not have RR of this type", q->request_hostname); - break; - } + r = sd_bus_message_append(reply, "i", q->received->ifindex); + if (r < 0) + goto finish; r = sd_bus_message_close_container(reply); if (r < 0) goto finish; - r = sd_bus_send(q->manager->bus, reply, NULL); - break; + added ++; } - parse_fail: - case DNS_QUERY_INVALID_REPLY: - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); - break; - - case DNS_QUERY_NULL: - case DNS_QUERY_SENT: - assert_not_reached("Unexpected query state"); + 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; } + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + goto finish; + +parse_fail: + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); + finish: if (r < 0) log_error("Failed to send bus reply: %s", strerror(-r)); @@ -223,120 +243,79 @@ static int bus_method_resolve_hostname(sd_bus *bus, sd_bus_message *message, voi } static void bus_method_resolve_address_complete(DnsQuery *q) { - _cleanup_free_ char *ip = NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + unsigned i, n, added = 0; + _cleanup_free_ char *reverse = NULL; int r; assert(q); - in_addr_to_string(q->request_family, &q->request_address, &ip); - - switch(q->state) { - - case DNS_QUERY_SKIPPED: - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "Not appropriate name servers or networks found"); - break; - - case DNS_QUERY_TIMEOUT: - r = sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); - break; - - case DNS_QUERY_ATTEMPTS_MAX: - r = sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); - break; - - case DNS_QUERY_FAILURE: { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - - if (q->rcode == DNS_RCODE_NXDOMAIN) - sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "No hostname known for address %s ", ip); - else { - const char *rc, *n; - char p[DECIMAL_STR_MAX(q->rcode)]; + if (q->state != DNS_QUERY_SUCCESS) { + r = reply_query_state(q); + goto finish; + } - rc = dns_rcode_to_string(q->rcode); - if (!rc) { - sprintf(p, "%i", q->rcode); - rc = p; - } + assert(q->received); - n = strappenda(_BUS_ERROR_DNS, rc); + r = dns_name_reverse(q->request_family, &q->request_address, &reverse); + if (r < 0) + goto finish; - sd_bus_error_setf(&error, n, "Could not resolve address %s, server or network returned error %s", ip, rc); - } + r = dns_packet_skip_question(q->received); + if (r < 0) + goto parse_fail; - r = sd_bus_reply_method_error(q->request, &error); - break; - } + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; - case DNS_QUERY_SUCCESS: { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - unsigned i, n, added = 0; - _cleanup_free_ char *reverse = NULL; + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + goto finish; - assert(q->packet); + n = DNS_PACKET_ANCOUNT(q->received) + + DNS_PACKET_NSCOUNT(q->received) + + DNS_PACKET_ARCOUNT(q->received); - r = dns_name_reverse(q->request_family, &q->request_address, &reverse); - if (r < 0) - goto finish; + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - r = dns_packet_skip_question(q->packet); + r = dns_packet_read_rr(q->received, &rr, NULL); if (r < 0) goto parse_fail; - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; + if (rr->key.class != DNS_CLASS_IN) + continue; + if (rr->key.type != DNS_TYPE_PTR) + continue; + if (!dns_name_equal(rr->key.name, reverse)) + continue; - r = sd_bus_message_open_container(reply, 'a', "s"); + r = sd_bus_message_append(reply, "s", rr->ptr.name); if (r < 0) goto finish; - n = DNS_PACKET_ANCOUNT(q->packet) + - DNS_PACKET_NSCOUNT(q->packet) + - DNS_PACKET_ARCOUNT(q->packet); - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - r = dns_packet_read_rr(q->packet, &rr, NULL); - 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)) - continue; - - r = sd_bus_message_append(reply, "s", rr->ptr.name); - if (r < 0) - goto finish; - - added ++; - } + added ++; + } - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address %s does not have RR of this type", ip); - break; - } + if (added <= 0) { + _cleanup_free_ char *ip = NULL; - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; + in_addr_to_string(q->request_family, &q->request_address, &ip); - r = sd_bus_send(q->manager->bus, reply, NULL); - break; + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address '%s' does not have RR of this type", ip); + goto finish; } - parse_fail: - case DNS_QUERY_INVALID_REPLY: - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); - break; + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; - case DNS_QUERY_NULL: - case DNS_QUERY_SENT: - assert_not_reached("Unexpected query state"); - } + r = sd_bus_send(q->manager->bus, reply, NULL); + goto finish; + +parse_fail: + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); finish: if (r < 0) diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 10f8f9691..d4c9c475f 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -71,6 +71,7 @@ static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { #define DNS_PACKET_QR(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 15) & 1) #define DNS_PACKET_OPCODE(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 11) & 15) #define DNS_PACKET_RCODE(p) (be16toh(DNS_PACKET_HEADER(p)->flags) & 15) +#define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) #define DNS_PACKET_QDCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->qdcount) #define DNS_PACKET_ANCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->ancount) #define DNS_PACKET_NSCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->nscount) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index dd812150c..5bd592025 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -31,7 +31,12 @@ DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t) { return NULL; sd_event_source_unref(t->timeout_event_source); - dns_packet_unref(t->packet); + + dns_packet_unref(t->sent); + dns_packet_unref(t->received); + + sd_event_source_unref(t->tcp_event_source); + safe_close(t->tcp_fd); if (t->query) { LIST_REMOVE(transactions_by_query, t->query->transactions, t); @@ -62,6 +67,8 @@ static int dns_query_transaction_new(DnsQuery *q, DnsQueryTransaction **ret, Dns if (!t) return -ENOMEM; + t->tcp_fd = -1; + do random_bytes(&t->id, sizeof(t->id)); while (t->id == 0 || @@ -87,6 +94,14 @@ static int dns_query_transaction_new(DnsQuery *q, DnsQueryTransaction **ret, Dns return 0; } +static void dns_query_transaction_stop(DnsQueryTransaction *t) { + assert(t); + + t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); + t->tcp_event_source = sd_event_source_unref(t->tcp_event_source); + t->tcp_fd = safe_close(t->tcp_fd); +} + static void dns_query_transaction_set_state(DnsQueryTransaction *t, DnsQueryState state) { assert(t); @@ -95,24 +110,179 @@ static void dns_query_transaction_set_state(DnsQueryTransaction *t, DnsQueryStat t->state = state; - if (state != DNS_QUERY_SENT) - t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); + if (state != DNS_QUERY_SENT) { + 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) { + DnsQueryTransaction *t = userdata; + int r; + + assert(t); + + if (revents & EPOLLOUT) { + struct iovec iov[2]; + be16_t sz; + ssize_t ss; + + sz = htobe16(t->sent->size); + + iov[0].iov_base = &sz; + iov[0].iov_len = sizeof(sz); + iov[1].iov_base = DNS_PACKET_DATA(t->sent); + iov[1].iov_len = t->sent->size; + + IOVEC_INCREMENT(iov, 2, t->tcp_written); + + ss = writev(fd, iov, 2); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) { + dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + return -errno; + } + } else + t->tcp_written += ss; + + /* Are we done? If so, disable the event source for EPOLLOUT */ + 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); + return r; + } + } + } + + if (revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) { + + if (t->tcp_read < sizeof(t->tcp_read_size)) { + ssize_t ss; + + 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); + return -errno; + } + } else if (ss == 0) { + dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + return -EIO; + } else + t->tcp_read += ss; + } + + 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); + return -EBADMSG; + } + + if (t->tcp_read < sizeof(t->tcp_read_size) + be16toh(t->tcp_read_size)) { + ssize_t ss; + + if (!t->received) { + r = dns_packet_new(&t->received, be16toh(t->tcp_read_size)); + if (r < 0) { + dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + return r; + } + } + + ss = read(fd, + (uint8_t*) DNS_PACKET_DATA(t->received) + t->tcp_read - sizeof(t->tcp_read_size), + 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); + return -errno; + } + } else if (ss == 0) { + dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + return -EIO; + } else + t->tcp_read += ss; + } + + if (t->tcp_read >= sizeof(t->tcp_read_size) + be16toh(t->tcp_read_size)) { + t->received->size = be16toh(t->tcp_read_size); + dns_query_transaction_reply(t, t->received); + return 0; + } + } + } + + return 0; +} + +static int dns_query_transaction_start_tcp(DnsQueryTransaction *t) { + int r; + + assert(t); + + if (t->tcp_fd >= 0) + return 0; + + t->tcp_written = 0; + t->tcp_read = 0; + t->received = dns_packet_unref(t->received); + + t->tcp_fd = dns_scope_tcp_socket(t->scope); + if (t->tcp_fd < 0) + return t->tcp_fd; + + r = sd_event_add_io(t->query->manager->event, &t->tcp_event_source, t->tcp_fd, EPOLLIN|EPOLLOUT, on_tcp_ready, t); + if (r < 0) { + t->tcp_fd = safe_close(t->tcp_fd); + return r; + } - dns_query_finish(t->query); + return 0; } -int dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p) { +void dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p) { + int r; + assert(t); assert(p); - t->packet = dns_packet_ref(p); + if (t->state != DNS_QUERY_SENT) + return; + + if (t->received != p) { + dns_packet_unref(t->received); + t->received = dns_packet_ref(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); + 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); + return; + } + } + + 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 < 0) { + dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); + return; + } + } if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) dns_query_transaction_set_state(t, DNS_QUERY_SUCCESS); else dns_query_transaction_set_state(t, DNS_QUERY_FAILURE); - - return 0; } static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { @@ -127,26 +297,20 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat r = dns_query_transaction_start(t); if (r < 0) - dns_query_transaction_set_state(t, DNS_QUERY_FAILURE); + dns_query_transaction_set_state(t, DNS_QUERY_RESOURCES); return 0; } -int dns_query_transaction_start(DnsQueryTransaction *t) { +static int dns_query_make_packet(DnsQueryTransaction *t) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; unsigned n; int r; assert(t); - t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); - - if (t->n_attempts >= ATTEMPTS_MAX) { - dns_query_transaction_set_state(t, DNS_QUERY_ATTEMPTS_MAX); + if (t->sent) return 0; - } - - t->n_attempts++; r = dns_packet_new_query(&p, 0); if (r < 0) @@ -161,7 +325,38 @@ int dns_query_transaction_start(DnsQueryTransaction *t) { DNS_PACKET_HEADER(p)->qdcount = htobe16(t->query->n_keys); DNS_PACKET_HEADER(p)->id = t->id; - r = dns_scope_send(t->scope, p); + t->sent = p; + p = NULL; + + return 0; +} + +int dns_query_transaction_start(DnsQueryTransaction *t) { + int r; + + assert(t); + + dns_query_transaction_stop(t); + + if (t->n_attempts >= ATTEMPTS_MAX) { + 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; + + /* 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); + + if (r == -ESRCH) { + dns_query_transaction_set_state(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); @@ -169,18 +364,12 @@ int dns_query_transaction_start(DnsQueryTransaction *t) { return dns_query_transaction_start(t); } - if (r > 0) { - int q; - - q = 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 (q < 0) - return q; - - dns_query_transaction_set_state(t, DNS_QUERY_SENT); - } else - dns_query_transaction_set_state(t, DNS_QUERY_SKIPPED); + 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; - return r; + dns_query_transaction_set_state(t, DNS_QUERY_SENT); + return 1; } DnsQuery *dns_query_free(DnsQuery *q) { @@ -190,7 +379,7 @@ DnsQuery *dns_query_free(DnsQuery *q) { return NULL; sd_bus_message_unref(q->request); - dns_packet_unref(q->packet); + dns_packet_unref(q->received); sd_event_source_unref(q->timeout_event_source); while (q->transactions) @@ -367,8 +556,8 @@ fail: void dns_query_finish(DnsQuery *q) { DnsQueryTransaction *t; - DnsQueryState state = DNS_QUERY_SKIPPED; - uint16_t rcode = 0; + DnsQueryState state = DNS_QUERY_NO_SERVERS; + DnsPacket *received = NULL; assert(q); @@ -381,25 +570,28 @@ void dns_query_finish(DnsQuery *q) { if (t->state == DNS_QUERY_SENT || t->state == DNS_QUERY_NULL) return; - /* One of the transactions is sucecssful, let's use it */ + /* One of the transactions is successful, let's use it */ if (t->state == DNS_QUERY_SUCCESS) { - q->packet = dns_packet_ref(t->packet); + q->received = dns_packet_ref(t->received); dns_query_set_state(q, DNS_QUERY_SUCCESS); return; } + /* 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) { + received = t->received; state = DNS_QUERY_FAILURE; + continue; + } - if (rcode == 0 && t->packet) - rcode = DNS_PACKET_RCODE(t->packet); - - } else if (state == DNS_QUERY_SKIPPED && t->state != DNS_QUERY_SKIPPED) + if (state == DNS_QUERY_NO_SERVERS && t->state != DNS_QUERY_NO_SERVERS) state = t->state; } if (state == DNS_QUERY_FAILURE) - q->rcode = rcode; + q->received = dns_packet_ref(received); dns_query_set_state(q, state); } diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index a07f17470..d5bc08dc3 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -39,10 +39,11 @@ typedef enum DnsQueryState { DNS_QUERY_SENT, DNS_QUERY_FAILURE, DNS_QUERY_SUCCESS, - DNS_QUERY_SKIPPED, + DNS_QUERY_NO_SERVERS, DNS_QUERY_TIMEOUT, DNS_QUERY_ATTEMPTS_MAX, DNS_QUERY_INVALID_REPLY, + DNS_QUERY_RESOURCES, } DnsQueryState; struct DnsQueryTransaction { @@ -55,7 +56,13 @@ struct DnsQueryTransaction { sd_event_source *timeout_event_source; unsigned n_attempts; - DnsPacket *packet; + DnsPacket *sent, *received; + + /* TCP connection logic */ + int tcp_fd; + sd_event_source *tcp_event_source; + size_t tcp_written, tcp_read; + be16_t tcp_read_size; LIST_FIELDS(DnsQueryTransaction, transactions_by_query); LIST_FIELDS(DnsQueryTransaction, transactions_by_scope); @@ -71,8 +78,7 @@ struct DnsQuery { sd_event_source *timeout_event_source; - uint16_t rcode; - DnsPacket *packet; + DnsPacket *received; sd_bus_message *request; unsigned char request_family; @@ -92,6 +98,6 @@ void dns_query_finish(DnsQuery *q); DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t); int dns_query_transaction_start(DnsQueryTransaction *t); -int dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p); +void dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 4e0a74276..0a70cb1f0 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -19,7 +19,10 @@ along with systemd; If not, see . ***/ +#include + #include "strv.h" +#include "socket-util.h" #include "resolved-dns-domain.h" #include "resolved-dns-scope.h" @@ -92,10 +95,14 @@ int dns_scope_send(DnsScope *s, DnsPacket *p) { srv = dns_scope_get_server(s); if (!srv) - return 0; + return -ESRCH; + + if (s->link) { + if (p->size > s->link->mtu) + return -EMSGSIZE; - if (s->link) ifindex = s->link->ifindex; + } if (srv->family == AF_INET) r = manager_dns_ipv4_send(s->manager, srv, ifindex, p); @@ -110,6 +117,52 @@ int dns_scope_send(DnsScope *s, DnsPacket *p) { return 1; } +int dns_scope_tcp_socket(DnsScope *s) { + _cleanup_close_ int fd = -1; + union sockaddr_union sa = {}; + socklen_t salen; + int one, ifindex, ret; + DnsServer *srv; + int r; + + assert(s); + + srv = dns_scope_get_server(s); + if (!srv) + return -ESRCH; + + if (s->link) + ifindex = s->link->ifindex; + + sa.sa.sa_family = srv->family; + if (srv->family == AF_INET) { + sa.in.sin_port = htobe16(53); + sa.in.sin_addr = srv->address.in; + salen = sizeof(sa.in); + } else if (srv->family == AF_INET6) { + sa.in6.sin6_port = htobe16(53); + sa.in6.sin6_addr = srv->address.in6; + sa.in6.sin6_scope_id = ifindex; + salen = sizeof(sa.in6); + } else + return -EAFNOSUPPORT; + + fd = socket(srv->family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + one = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + + r = connect(fd, &sa.sa, salen); + if (r < 0 && errno != EINPROGRESS) + return -errno; + + ret = fd; + fd = -1; + return ret; +} + DnsScopeMatch dns_scope_test(DnsScope *s, const char *domain) { char **i; diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 50f1106bf..97544f9e4 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -63,6 +63,8 @@ int dns_scope_new(Manager *m, DnsScope **ret, DnsScopeType t); DnsScope* dns_scope_free(DnsScope *s); int dns_scope_send(DnsScope *s, DnsPacket *p); +int dns_scope_tcp_socket(DnsScope *s); + DnsScopeMatch dns_scope_test(DnsScope *s, const char *domain); DnsServer *dns_scope_get_server(DnsScope *s); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 09b758f3f..3d2979dbb 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -577,7 +577,7 @@ int manager_dns_ipv4_recv(Manager *m, DnsPacket **ret) { l = recvmsg(fd, &mh, 0); if (l < 0) { - if (errno == EAGAIN) + if (errno == EAGAIN || errno == EINTR) return 0; return -errno; @@ -626,7 +626,7 @@ int manager_dns_ipv6_recv(Manager *m, DnsPacket **ret) { l = recvmsg(fd, &mh, 0); if (l < 0) { - if (errno == EAGAIN) + if (errno == EAGAIN || errno == EINTR) return 0; return -errno; @@ -657,7 +657,8 @@ static int on_dns_ipv4_packet(sd_event_source *s, int fd, uint32_t revents, void if (!t) return 0; - return dns_query_transaction_reply(t, p); + dns_query_transaction_reply(t, p); + return 0; } static int on_dns_ipv6_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -674,7 +675,8 @@ static int on_dns_ipv6_packet(sd_event_source *s, int fd, uint32_t revents, void if (!t) return 0; - return dns_query_transaction_reply(t, p); + dns_query_transaction_reply(t, p); + return 0; } int manager_dns_ipv4_fd(Manager *m) { diff --git a/src/shared/bus-errors.h b/src/shared/bus-errors.h index 08a7acc99..f3d973507 100644 --- a/src/shared/bus-errors.h +++ b/src/shared/bus-errors.h @@ -63,4 +63,5 @@ #define BUS_ERROR_NO_NAME_SERVERS "org.freedesktop.resolve1.NoNameServers" #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_DNS "org.freedesktop.resolve1.DnsError." -- 2.30.2