From b914e211f3a40f507b3cdc572838ec7f3fd5e4cf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 29 Jul 2014 19:49:45 +0200 Subject: [PATCH] resolved: when resolving an address PTR record via llmnr, make a tcp connection by default --- src/resolve/resolved-dns-domain.c | 88 +++++- src/resolve/resolved-dns-domain.h | 1 + src/resolve/resolved-dns-query.c | 50 +++- src/resolve/resolved-dns-question.c | 35 +++ src/resolve/resolved-dns-question.h | 3 + src/resolve/resolved-dns-scope.c | 4 +- src/resolve/resolved-dns-stream.c | 397 +++++++++++++++------------- src/resolve/resolved-dns-stream.h | 1 + src/resolve/test-dns-domain.c | 19 ++ src/shared/util.c | 20 ++ src/shared/util.h | 2 + 11 files changed, 421 insertions(+), 199 deletions(-) diff --git a/src/resolve/resolved-dns-domain.c b/src/resolve/resolved-dns-domain.c index eea73f6d547..f3e7df7a1e8 100644 --- a/src/resolve/resolved-dns-domain.c +++ b/src/resolve/resolved-dns-domain.c @@ -360,6 +360,93 @@ int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { return 0; } +int dns_name_address(const char *p, int *family, union in_addr_union *address) { + int r; + + assert(p); + assert(family); + assert(address); + + r = dns_name_endswith(p, "in-addr.arpa"); + if (r < 0) + return r; + if (r > 0) { + uint8_t a[4]; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a); i++) { + char label[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (r > 3) + return -EINVAL; + + r = safe_atou8(label, &a[i]); + if (r < 0) + return r; + } + + r = dns_name_equal(p, "in-addr.arpa"); + if (r <= 0) + return r; + + *family = AF_INET; + address->in.s_addr = htobe32(((uint32_t) a[3] << 24) | + ((uint32_t) a[2] << 16) | + ((uint32_t) a[1] << 8) | + (uint32_t) a[0]); + + return 1; + } + + r = dns_name_endswith(p, "ip6.arpa"); + if (r < 0) + return r; + if (r > 0) { + struct in6_addr a; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) { + char label[DNS_LABEL_MAX+1]; + int x, y; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + x = unhexchar(label[0]); + if (x < 0) + return -EINVAL; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + y = unhexchar(label[0]); + if (y < 0) + return -EINVAL; + + a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x; + } + + r = dns_name_equal(p, "ip6.arpa"); + if (r <= 0) + return r; + + *family = AF_INET6; + address->in6 = a; + return 1; + } + + return 0; +} + int dns_name_root(const char *name) { char label[DNS_LABEL_MAX+1]; int r; @@ -382,7 +469,6 @@ int dns_name_single_label(const char *name) { r = dns_label_unescape(&name, label, sizeof(label)); if (r < 0) return r; - if (r == 0) return 0; diff --git a/src/resolve/resolved-dns-domain.h b/src/resolve/resolved-dns-domain.h index 809c4daacc2..16372fd9407 100644 --- a/src/resolve/resolved-dns-domain.h +++ b/src/resolve/resolved-dns-domain.h @@ -39,6 +39,7 @@ int dns_name_equal(const char *x, const char *y); int dns_name_endswith(const char *name, const char *suffix); int dns_name_reverse(int family, const union in_addr_union *a, char **ret); +int dns_name_address(const char *p, int *family, union in_addr_union *a); int dns_name_root(const char *name); int dns_name_single_label(const char *name); diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 32448c5822f..42f4f23cb98 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -165,7 +165,14 @@ static int on_stream_complete(DnsStream *s, int error) { return 0; } + t->block_gc++; dns_query_transaction_process_reply(t, p); + t->block_gc--; + + /* If the response wasn't useful, then complete the transition now */ + if (t->state == DNS_QUERY_PENDING) + dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY); + return 0; } @@ -181,10 +188,25 @@ static int dns_query_transaction_open_tcp(DnsQueryTransaction *t) { if (t->scope->protocol == DNS_PROTOCOL_DNS) fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53); else if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { - if (!t->received) - return -EINVAL; - fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port); + /* 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); + else { + union in_addr_union address; + int family; + + /* Otherwise, try to talk to the owner of a + * the IP address, in case this is a reverse + * PTR lookup */ + r = dns_question_extract_reverse_address(t->question, &family, &address); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + fd = dns_scope_tcp_socket(t->scope, family, &address, 5355); + } } else return -EAFNOSUPPORT; @@ -205,6 +227,13 @@ static int dns_query_transaction_open_tcp(DnsQueryTransaction *t) { t->received = dns_packet_unref(t->received); t->stream->complete = on_stream_complete; + t->stream->transaction = t; + + /* The interface index is difficult to determine if we are + * connecting to the local host, hence fill this in right away + * instead of determining it from the socket */ + if (t->scope->link) + t->stream->ifindex = t->scope->link->ifindex; return 0; } @@ -416,10 +445,19 @@ static int dns_query_transaction_go(DnsQueryTransaction *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) + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && + (dns_question_endswith(t->question, "in-addr.arpa") > 0 || + dns_question_endswith(t->question, "ip6.arpa") > 0)) { + + /* RFC 4795, Section 2.4. says reverse lookups shall + * always be made via TCP on LLMNR */ r = dns_query_transaction_open_tcp(t); + } else { + /* 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_open_tcp(t); + } if (r == -ESRCH) { /* No servers to send this to? */ dns_query_transaction_complete(t, DNS_QUERY_NO_SERVERS); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index 056bd6eb649..66017e82cd7 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -235,3 +235,38 @@ int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion ** return 1; } + +int dns_question_endswith(DnsQuestion *q, const char *suffix) { + unsigned i; + + assert(q); + assert(suffix); + + for (i = 0; i < q->n_keys; i++) { + int k; + + k = dns_name_endswith(DNS_RESOURCE_KEY_NAME(q->keys[i]), suffix); + if (k <= 0) + return k; + } + + return 1; +} + +int dns_question_extract_reverse_address(DnsQuestion *q, int *family, union in_addr_union *address) { + unsigned i; + + assert(q); + assert(family); + assert(address); + + for (i = 0; i < q->n_keys; i++) { + int k; + + k = dns_name_address(DNS_RESOURCE_KEY_NAME(q->keys[i]), family, address); + if (k != 0) + return k; + } + + return 0; +} diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h index 7da627fdca1..4ba2fe9f0ed 100644 --- a/src/resolve/resolved-dns-question.h +++ b/src/resolve/resolved-dns-question.h @@ -46,4 +46,7 @@ int dns_question_is_superset(DnsQuestion *q, DnsQuestion *other); int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion **ret); +int dns_question_endswith(DnsQuestion *q, const char *suffix); +int dns_question_extract_reverse_address(DnsQuestion *q, int *family, union in_addr_union *address); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 9a636b179c8..5d2edbae47f 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -312,8 +312,8 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, const char *domain) { } if (s->protocol == DNS_PROTOCOL_LLMNR) { - if (dns_name_endswith(domain, "254.169.in-addr.arpa") > 0 || - dns_name_endswith(domain, "0.8.e.f.ip6.arpa") > 0 || + if (dns_name_endswith(domain, "in-addr.arpa") > 0 || + dns_name_endswith(domain, "ip6.arpa") > 0 || dns_name_single_label(domain) > 0) return DNS_SCOPE_MAYBE; diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index 24a2288428b..47130c42319 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -48,7 +48,7 @@ static int dns_stream_update_io(DnsStream *s) { return sd_event_source_set_io_events(s->io_event_source, f); } -static int stream_complete(DnsStream *s, int error) { +static int dns_stream_complete(DnsStream *s, int error) { assert(s); dns_stream_stop(s); @@ -61,161 +61,7 @@ static int stream_complete(DnsStream *s, int error) { return 0; } -static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { - DnsStream *s = userdata; - - assert(s); - - return stream_complete(s, ETIMEDOUT); -} - -static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - DnsStream *s = userdata; - int r; - - assert(s); - - if ((revents & EPOLLOUT) && - s->write_packet && - s->n_written < sizeof(s->write_size) + s->write_packet->size) { - - struct iovec iov[2]; - ssize_t ss; - - iov[0].iov_base = &s->write_size; - iov[0].iov_len = sizeof(s->write_size); - iov[1].iov_base = DNS_PACKET_DATA(s->write_packet); - iov[1].iov_len = s->write_packet->size; - - IOVEC_INCREMENT(iov, 2, s->n_written); - - ss = writev(fd, iov, 2); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return stream_complete(s, errno); - } else - s->n_written += ss; - - /* Are we done? If so, disable the event source for EPOLLOUT */ - if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) { - r = dns_stream_update_io(s); - if (r < 0) - return stream_complete(s, -r); - } - } - - if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) && - (!s->read_packet || - s->n_read < sizeof(s->read_size) + s->read_packet->size)) { - - if (s->n_read < sizeof(s->read_size)) { - ssize_t ss; - - ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return stream_complete(s, errno); - } else if (ss == 0) - return stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - if (s->n_read >= sizeof(s->read_size)) { - - if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) - return stream_complete(s, EBADMSG); - - if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { - ssize_t ss; - - if (!s->read_packet) { - r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size)); - if (r < 0) - return stream_complete(s, -r); - - s->read_packet->size = be16toh(s->read_size); - s->read_packet->ipproto = IPPROTO_TCP; - s->read_packet->family = s->peer.sa.sa_family; - s->read_packet->ttl = s->ttl; - s->read_packet->ifindex = s->ifindex; - - if (s->read_packet->family == AF_INET) { - s->read_packet->sender.in = s->peer.in.sin_addr; - s->read_packet->sender_port = be16toh(s->peer.in.sin_port); - s->read_packet->destination.in = s->local.in.sin_addr; - s->read_packet->destination_port = be16toh(s->local.in.sin_port); - } else { - assert(s->read_packet->family == AF_INET6); - s->read_packet->sender.in6 = s->peer.in6.sin6_addr; - s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port); - s->read_packet->destination.in6 = s->local.in6.sin6_addr; - s->read_packet->destination_port = be16toh(s->local.in6.sin6_port); - - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->peer.in6.sin6_scope_id; - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->local.in6.sin6_scope_id; - } - } - - ss = read(fd, - (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size), - sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return stream_complete(s, errno); - } else if (ss == 0) - return stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - /* Are we done? If so, disable the event source for EPOLLIN */ - if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) { - r = dns_stream_update_io(s); - if (r < 0) - return stream_complete(s, -r); - - /* If there's a packet handler - * installed, call that. Note that - * this is optional... */ - if (s->on_packet) - return s->on_packet(s); - } - } - } - - if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) && - (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size)) - return stream_complete(s, 0); - - return 0; -} - -DnsStream *dns_stream_free(DnsStream *s) { - if (!s) - return NULL; - - dns_stream_stop(s); - - if (s->manager) { - LIST_REMOVE(streams, s->manager->dns_streams, s); - s->manager->n_dns_streams--; - } - - dns_packet_unref(s->write_packet); - dns_packet_unref(s->read_packet); - - free(s); - - return 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); - -int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { - static const int one = 1; +static int dns_stream_identify(DnsStream *s) { union { struct cmsghdr header; /* For alignment */ uint8_t buffer[CMSG_SPACE(MAX(sizeof(struct in_pktinfo), sizeof(struct in6_pktinfo))) @@ -223,39 +69,30 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { } control; struct msghdr mh = {}; struct cmsghdr *cmsg; - _cleanup_(dns_stream_freep) DnsStream *s = NULL; socklen_t sl; int r; - assert(m); - assert(fd >= 0); + assert(s); - if (m->n_dns_streams > DNS_STREAMS_MAX) - return -EBUSY; - - s = new0(DnsStream, 1); - if (!s) - return -ENOMEM; - - s->fd = -1; - s->protocol = protocol; - - /* Query the remote side */ - s->peer_salen = sizeof(s->peer); - r = getpeername(fd, &s->peer.sa, &s->peer_salen); - if (r < 0) - return -errno; - if (s->peer.sa.sa_family == AF_INET6) - s->ifindex = s->peer.in6.sin6_scope_id; + if (s->identified) + return 0; /* Query the local side */ s->local_salen = sizeof(s->local); - r = getsockname(fd, &s->local.sa, &s->local_salen); + r = getsockname(s->fd, &s->local.sa, &s->local_salen); if (r < 0) return -errno; if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0) s->ifindex = s->local.in6.sin6_scope_id; + /* Query the remote side */ + s->peer_salen = sizeof(s->peer); + r = getpeername(s->fd, &s->peer.sa, &s->peer_salen); + if (r < 0) + return -errno; + if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0) + s->ifindex = s->peer.in6.sin6_scope_id; + /* Check consistency */ assert(s->peer.sa.sa_family == s->local.sa.sa_family); assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); @@ -263,16 +100,16 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { /* Query connection meta information */ sl = sizeof(control); if (s->peer.sa.sa_family == AF_INET) { - r = getsockopt(fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); + r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); if (r < 0) return -errno; - } else { - assert(s->peer.sa.sa_family == AF_INET6); + } else if (s->peer.sa.sa_family == AF_INET6) { - r = getsockopt(fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); + r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); if (r < 0) return -errno; - } + } else + return -EAFNOSUPPORT; mh.msg_control = &control; mh.msg_controllen = sl; @@ -320,33 +157,213 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { * device if the connection came from the local host since it * avoids the routing table in such a case. Let's unset the * interface index in such a case. */ - if (s->ifindex > 0 && manager_ifindex_is_loopback(m, s->ifindex) != 0) + if (s->ifindex > 0 && manager_ifindex_is_loopback(s->manager, s->ifindex) != 0) s->ifindex = 0; /* If we don't know the interface index still, we look for the * first local interface with a matching address. Yuck! */ if (s->ifindex <= 0) - s->ifindex = manager_find_ifindex(m, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr); - - r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); - if (r < 0) - return -errno; + s->ifindex = manager_find_ifindex(s->manager, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr); if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) { uint32_t ifindex = htobe32(s->ifindex); /* Make sure all packets for this connection are sent on the same interface */ if (s->local.sa.sa_family == AF_INET) { - r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); + r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); if (r < 0) return -errno; } else if (s->local.sa.sa_family == AF_INET6) { - r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); + r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); if (r < 0) return -errno; } } + s->identified = true; + + return 0; +} + +static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { + DnsStream *s = userdata; + + assert(s); + + return dns_stream_complete(s, ETIMEDOUT); +} + +static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + DnsStream *s = userdata; + int r; + + assert(s); + + r = dns_stream_identify(s); + if (r < 0) + return dns_stream_complete(s, -r); + + if ((revents & EPOLLOUT) && + s->write_packet && + s->n_written < sizeof(s->write_size) + s->write_packet->size) { + + struct iovec iov[2]; + ssize_t ss; + + iov[0].iov_base = &s->write_size; + iov[0].iov_len = sizeof(s->write_size); + iov[1].iov_base = DNS_PACKET_DATA(s->write_packet); + iov[1].iov_len = s->write_packet->size; + + IOVEC_INCREMENT(iov, 2, s->n_written); + + ss = writev(fd, iov, 2); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else + s->n_written += ss; + + /* Are we done? If so, disable the event source for EPOLLOUT */ + if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) { + r = dns_stream_update_io(s); + if (r < 0) + return dns_stream_complete(s, -r); + } + } + + if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) && + (!s->read_packet || + s->n_read < sizeof(s->read_size) + s->read_packet->size)) { + + if (s->n_read < sizeof(s->read_size)) { + ssize_t ss; + + ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else if (ss == 0) + return dns_stream_complete(s, ECONNRESET); + else + s->n_read += ss; + } + + if (s->n_read >= sizeof(s->read_size)) { + + if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) + return dns_stream_complete(s, EBADMSG); + + if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { + ssize_t ss; + + if (!s->read_packet) { + r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size)); + if (r < 0) + return dns_stream_complete(s, -r); + + s->read_packet->size = be16toh(s->read_size); + s->read_packet->ipproto = IPPROTO_TCP; + s->read_packet->family = s->peer.sa.sa_family; + s->read_packet->ttl = s->ttl; + s->read_packet->ifindex = s->ifindex; + + if (s->read_packet->family == AF_INET) { + s->read_packet->sender.in = s->peer.in.sin_addr; + s->read_packet->sender_port = be16toh(s->peer.in.sin_port); + s->read_packet->destination.in = s->local.in.sin_addr; + s->read_packet->destination_port = be16toh(s->local.in.sin_port); + } else { + assert(s->read_packet->family == AF_INET6); + s->read_packet->sender.in6 = s->peer.in6.sin6_addr; + s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port); + s->read_packet->destination.in6 = s->local.in6.sin6_addr; + s->read_packet->destination_port = be16toh(s->local.in6.sin6_port); + + if (s->read_packet->ifindex == 0) + s->read_packet->ifindex = s->peer.in6.sin6_scope_id; + if (s->read_packet->ifindex == 0) + s->read_packet->ifindex = s->local.in6.sin6_scope_id; + } + } + + ss = read(fd, + (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size), + sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else if (ss == 0) + return dns_stream_complete(s, ECONNRESET); + else + s->n_read += ss; + } + + /* Are we done? If so, disable the event source for EPOLLIN */ + if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) { + r = dns_stream_update_io(s); + if (r < 0) + return dns_stream_complete(s, -r); + + /* If there's a packet handler + * installed, call that. Note that + * this is optional... */ + if (s->on_packet) + return s->on_packet(s); + } + } + } + + if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) && + (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size)) + return dns_stream_complete(s, 0); + + return 0; +} + +DnsStream *dns_stream_free(DnsStream *s) { + if (!s) + return NULL; + + dns_stream_stop(s); + + if (s->manager) { + LIST_REMOVE(streams, s->manager->dns_streams, s); + s->manager->n_dns_streams--; + } + + dns_packet_unref(s->write_packet); + dns_packet_unref(s->read_packet); + + free(s); + + return 0; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); + +int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { + static const int one = 1; + _cleanup_(dns_stream_freep) DnsStream *s = NULL; + int r; + + assert(m); + assert(fd >= 0); + + if (m->n_dns_streams > DNS_STREAMS_MAX) + return -EBUSY; + + s = new0(DnsStream, 1); + if (!s) + return -ENOMEM; + + s->fd = -1; + s->protocol = protocol; + + r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + if (r < 0) + return -errno; + r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h index db456580cbb..5509f7528b1 100644 --- a/src/resolve/resolved-dns-stream.h +++ b/src/resolve/resolved-dns-stream.h @@ -39,6 +39,7 @@ struct DnsStream { socklen_t local_salen; int ifindex; uint32_t ttl; + bool identified; sd_event_source *io_event_source; sd_event_source *timeout_event_source; diff --git a/src/resolve/test-dns-domain.c b/src/resolve/test-dns-domain.c index bd53402be89..dfe2a44eaec 100644 --- a/src/resolve/test-dns-domain.c +++ b/src/resolve/test-dns-domain.c @@ -159,6 +159,24 @@ static void test_dns_name_single_label(void) { assert_se(dns_name_single_label("xx.yy") == false); } +static void test_dns_name_reverse_one(const char *address, const char *name) { + _cleanup_free_ char *p = NULL; + union in_addr_union a, b; + int familya, familyb; + + assert_se(in_addr_from_string_auto(address, &familya, &a) >= 0); + assert_se(dns_name_reverse(familya, &a, &p) >= 0); + assert_se(streq(p, name)); + assert_se(dns_name_address(p, &familyb, &b) > 0); + assert_se(familya == familyb); + assert_se(in_addr_equal(familya, &a, &b)); +} + +static void test_dns_name_reverse(void) { + test_dns_name_reverse_one("47.11.8.15", "15.8.11.47.in-addr.arpa"); + test_dns_name_reverse_one("fe80::47", "7.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa"); +} + int main(int argc, char *argv[]) { test_dns_label_unescape(); @@ -168,6 +186,7 @@ int main(int argc, char *argv[]) { test_dns_name_endswith(); test_dns_name_root(); test_dns_name_single_label(); + test_dns_name_reverse(); return 0; } diff --git a/src/shared/util.c b/src/shared/util.c index b1689e651f7..d8a75bdc6a5 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -332,6 +332,26 @@ int safe_atoi(const char *s, int *ret_i) { return 0; } +int safe_atou8(const char *s, uint8_t *ret) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret); + + errno = 0; + l = strtoul(s, &x, 0); + + if (!x || x == s || *x || errno) + return errno > 0 ? -errno : -EINVAL; + + if ((unsigned long) (uint8_t) l != l) + return -ERANGE; + + *ret = (uint8_t) l; + return 0; +} + int safe_atollu(const char *s, long long unsigned *ret_llu) { char *x = NULL; unsigned long long l; diff --git a/src/shared/util.h b/src/shared/util.h index d9d525e8a51..81da59b20cf 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -192,6 +192,8 @@ int safe_atolli(const char *s, long long int *ret_i); int safe_atod(const char *s, double *ret_d); +int safe_atou8(const char *s, uint8_t *ret); + #if __WORDSIZE == 32 static inline int safe_atolu(const char *s, unsigned long *ret_u) { assert_cc(sizeof(unsigned long) == sizeof(unsigned));