From: Lennart Poettering Date: Tue, 29 Jul 2014 17:49:45 +0000 (+0200) Subject: resolved: when resolving an address PTR record via llmnr, make a tcp connection by... X-Git-Tag: v216~397 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=b914e211f3a40f507b3cdc572838ec7f3fd5e4cf resolved: when resolving an address PTR record via llmnr, make a tcp connection by default --- diff --git a/src/resolve/resolved-dns-domain.c b/src/resolve/resolved-dns-domain.c index eea73f6d5..f3e7df7a1 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 809c4daac..16372fd94 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 32448c582..42f4f23cb 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 056bd6eb6..66017e82c 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 7da627fdc..4ba2fe9f0 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 9a636b179..5d2edbae4 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 24a228842..47130c423 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,12 +61,136 @@ static int stream_complete(DnsStream *s, int error) { return 0; } +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))) + + EXTRA_CMSG_SPACE /* kernel appears to require extra space */]; + } control; + struct msghdr mh = {}; + struct cmsghdr *cmsg; + socklen_t sl; + int r; + + assert(s); + + if (s->identified) + return 0; + + /* Query the local side */ + s->local_salen = sizeof(s->local); + 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)); + + /* Query connection meta information */ + sl = sizeof(control); + if (s->peer.sa.sa_family == AF_INET) { + r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); + if (r < 0) + return -errno; + } else if (s->peer.sa.sa_family == AF_INET6) { + + 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; + for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + assert(s->peer.sa.sa_family == AF_INET6); + + switch (cmsg->cmsg_type) { + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi6_ifindex; + break; + } + + case IPV6_HOPLIMIT: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(s->peer.sa.sa_family == AF_INET); + + switch (cmsg->cmsg_type) { + + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi_ifindex; + break; + } + + case IP_TTL: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + } + } + + /* The Linux kernel sets the interface index to the loopback + * 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(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(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(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(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 stream_complete(s, ETIMEDOUT); + return dns_stream_complete(s, ETIMEDOUT); } static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { @@ -75,6 +199,10 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use 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) { @@ -92,7 +220,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use ss = writev(fd, iov, 2); if (ss < 0) { if (errno != EINTR && errno != EAGAIN) - return stream_complete(s, errno); + return dns_stream_complete(s, errno); } else s->n_written += ss; @@ -100,7 +228,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use 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); + return dns_stream_complete(s, -r); } } @@ -114,9 +242,9 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use 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); + return dns_stream_complete(s, errno); } else if (ss == 0) - return stream_complete(s, ECONNRESET); + return dns_stream_complete(s, ECONNRESET); else s->n_read += ss; } @@ -124,7 +252,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use if (s->n_read >= sizeof(s->read_size)) { if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) - return stream_complete(s, EBADMSG); + return dns_stream_complete(s, EBADMSG); if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { ssize_t ss; @@ -132,7 +260,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use 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); + return dns_stream_complete(s, -r); s->read_packet->size = be16toh(s->read_size); s->read_packet->ipproto = IPPROTO_TCP; @@ -164,9 +292,9 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); if (ss < 0) { if (errno != EINTR && errno != EAGAIN) - return stream_complete(s, errno); + return dns_stream_complete(s, errno); } else if (ss == 0) - return stream_complete(s, ECONNRESET); + return dns_stream_complete(s, ECONNRESET); else s->n_read += ss; } @@ -175,7 +303,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use 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); + return dns_stream_complete(s, -r); /* If there's a packet handler * installed, call that. Note that @@ -188,7 +316,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use 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 dns_stream_complete(s, 0); return 0; } @@ -216,15 +344,7 @@ 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; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(MAX(sizeof(struct in_pktinfo), sizeof(struct in6_pktinfo))) - + EXTRA_CMSG_SPACE /* kernel appears to require extra space */]; - } control; - struct msghdr mh = {}; - struct cmsghdr *cmsg; _cleanup_(dns_stream_freep) DnsStream *s = NULL; - socklen_t sl; int r; assert(m); @@ -240,113 +360,10 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { 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; - - /* Query the local side */ - s->local_salen = sizeof(s->local); - r = getsockname(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; - - /* 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)); - - /* Query connection meta information */ - sl = sizeof(control); - if (s->peer.sa.sa_family == AF_INET) { - r = getsockopt(fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } else { - assert(s->peer.sa.sa_family == AF_INET6); - - r = getsockopt(fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } - - mh.msg_control = &control; - mh.msg_controllen = sl; - for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) { - - if (cmsg->cmsg_level == IPPROTO_IPV6) { - assert(s->peer.sa.sa_family == AF_INET6); - - switch (cmsg->cmsg_type) { - - case IPV6_PKTINFO: { - struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi6_ifindex; - break; - } - - case IPV6_HOPLIMIT: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - - } else if (cmsg->cmsg_level == IPPROTO_IP) { - assert(s->peer.sa.sa_family == AF_INET); - - switch (cmsg->cmsg_type) { - - case IP_PKTINFO: { - struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi_ifindex; - break; - } - - case IP_TTL: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - } - } - - /* The Linux kernel sets the interface index to the loopback - * 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) - 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; - 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)); - 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)); - 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 db456580c..5509f7528 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 bd53402be..dfe2a44ea 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 b1689e651..d8a75bdc6 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 d9d525e8a..81da59b20 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));