diff options
Diffstat (limited to 'src/resolve/resolved-dns-transaction.c')
-rw-r--r-- | src/resolve/resolved-dns-transaction.c | 131 |
1 files changed, 121 insertions, 10 deletions
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 3f4673df7a..e468f245f7 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -39,6 +39,12 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { dns_packet_unref(t->received); dns_answer_unref(t->cached); + sd_event_source_unref(t->dns_ipv4_event_source); + sd_event_source_unref(t->dns_ipv6_event_source); + safe_close(t->dns_ipv4_fd); + safe_close(t->dns_ipv6_fd); + + dns_server_unref(t->server); dns_stream_free(t->stream); if (t->scope) { @@ -88,6 +94,8 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsQuestion *q) { if (!t) return -ENOMEM; + t->dns_ipv4_fd = t->dns_ipv6_fd = -1; + t->question = dns_question_ref(q); do @@ -237,6 +245,7 @@ static int on_stream_complete(DnsStream *s, int error) { } static int dns_transaction_open_tcp(DnsTransaction *t) { + _cleanup_(dns_server_unrefp) DnsServer *server = NULL; _cleanup_close_ int fd = -1; int r; @@ -246,12 +255,12 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { return 0; if (t->scope->protocol == DNS_PROTOCOL_DNS) - fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53); + fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53, &server); else if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { /* When we already received a query to this (but it was truncated), send to its sender address */ if (t->received) - fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port); + fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL); else { union in_addr_union address; int family = AF_UNSPEC; @@ -265,7 +274,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { if (r == 0) return -EINVAL; - fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT); + fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT, NULL); } } else return -EAFNOSUPPORT; @@ -285,6 +294,9 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { return r; } + + dns_server_unref(t->server); + t->server = dns_server_ref(server); t->received = dns_packet_unref(t->received); t->stream->complete = on_stream_complete; t->stream->transaction = t; @@ -333,10 +345,15 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { if (t->scope->protocol == DNS_PROTOCOL_DNS) { /* For DNS we are fine with accepting packets on any - * interface, but the source IP address must be one of - * a valid DNS server */ + * interface, but the source IP address must be the + * one of the DNS server we queried */ + + assert(t->server); - if (!dns_scope_good_dns_server(t->scope, p->family, &p->sender)) + if (t->server->family != p->family) + return; + + if (!in_addr_equal(p->family, &p->sender, &t->server->address)) return; if (p->sender_port != 53) @@ -398,6 +415,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { return; } + /* Only consider responses with equivalent query section to the request */ + if (!dns_question_is_superset(p->question, t->question) || + !dns_question_is_superset(t->question, p->question)) + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */ dns_cache_put(&t->scope->cache, p->question, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender); @@ -492,6 +514,7 @@ int dns_transaction_go(DnsTransaction *t) { } t->n_attempts++; + t->server = dns_server_unref(t->server); t->received = dns_packet_unref(t->received); t->cached = dns_answer_unref(t->cached); t->cached_rcode = 0; @@ -571,17 +594,20 @@ int dns_transaction_go(DnsTransaction *t) { * always be made via TCP on LLMNR */ r = dns_transaction_open_tcp(t); } else { + DnsServer *server; + /* Try via UDP, and if that fails due to large size try via TCP */ - r = dns_scope_emit(t->scope, t->sent); - if (r == -EMSGSIZE) + r = dns_scope_emit(t->scope, t, t->sent, &server); + if (r >= 0) + t->server = dns_server_ref(server); + else if (r == -EMSGSIZE) r = dns_transaction_open_tcp(t); } if (r == -ESRCH) { /* No servers to send this to? */ dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); return 0; - } - if (r < 0) { + } else if (r < 0) { if (t->scope->protocol != DNS_PROTOCOL_DNS) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return 0; @@ -606,6 +632,91 @@ int dns_transaction_go(DnsTransaction *t) { return 1; } +static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t = userdata; + int r; + + assert(t); + assert(t->scope); + + r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); + if (r <= 0) + return r; + + if (dns_packet_validate_reply(p) > 0 && + DNS_PACKET_ID(p) == t->id) { + dns_transaction_process_reply(t, p); + } else + log_debug("Invalid DNS packet."); + + return 0; +} + +int transaction_dns_ipv4_fd(DnsTransaction *t) { + const int one = 1; + int r; + + assert(t); + assert(t->scope); + assert(t->scope->manager); + + if (t->dns_ipv4_fd >= 0) + return t->dns_ipv4_fd; + + t->dns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (t->dns_ipv4_fd < 0) + return -errno; + + r = setsockopt(t->dns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(t->scope->manager->event, &t->dns_ipv4_event_source, t->dns_ipv4_fd, EPOLLIN, on_dns_packet, t); + if (r < 0) + goto fail; + + return t->dns_ipv4_fd; + +fail: + t->dns_ipv4_fd = safe_close(t->dns_ipv4_fd); + return r; +} + +int transaction_dns_ipv6_fd(DnsTransaction *t) { + const int one = 1; + int r; + + assert(t); + assert(t->scope); + assert(t->scope->manager); + + if (t->dns_ipv6_fd >= 0) + return t->dns_ipv6_fd; + + t->dns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (t->dns_ipv6_fd < 0) + return -errno; + + r = setsockopt(t->dns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(t->scope->manager->event, &t->dns_ipv6_event_source, t->dns_ipv6_fd, EPOLLIN, on_dns_packet, t); + if (r < 0) + goto fail; + + return t->dns_ipv6_fd; + +fail: + t->dns_ipv6_fd = safe_close(t->dns_ipv6_fd); + return r; +} + static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { [DNS_TRANSACTION_NULL] = "null", [DNS_TRANSACTION_PENDING] = "pending", |