diff options
Diffstat (limited to 'src/resolve/resolved-dns-stream.c')
-rw-r--r-- | src/resolve/resolved-dns-stream.c | 263 |
1 files changed, 140 insertions, 123 deletions
diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index 24a2288428..47130c4231 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; |