resolved-dns-stream.c revision 56f64d95763a799ba4475daf44d8e9f72a1bd474
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen This file is part of systemd.
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen Copyright 2014 Lennart Poettering
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen systemd is free software; you can redistribute it and/or modify it
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen under the terms of the GNU Lesser General Public License as published by
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen the Free Software Foundation; either version 2.1 of the License, or
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen (at your option) any later version.
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen systemd is distributed in the hope that it will be useful, but
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen WITHOUT ANY WARRANTY; without even the implied warranty of
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen Lesser General Public License for more details.
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen You should have received a copy of the GNU Lesser General Public License
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen along with systemd; If not, see <http://www.gnu.org/licenses/>.
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC)
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen s->io_event_source = sd_event_source_unref(s->io_event_source);
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen s->timeout_event_source = sd_event_source_unref(s->timeout_event_source);
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersenstatic int dns_stream_update_io(DnsStream *s) {
02b59d57e0c08231645120077f651151f5bb2babTom Gundersen if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size)
02b59d57e0c08231645120077f651151f5bb2babTom Gundersen if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size)
02b59d57e0c08231645120077f651151f5bb2babTom Gundersen return sd_event_source_set_io_events(s->io_event_source, f);
02b59d57e0c08231645120077f651151f5bb2babTom Gundersenstatic int dns_stream_complete(DnsStream *s, int error) {
02b59d57e0c08231645120077f651151f5bb2babTom Gundersen uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo))
02b59d57e0c08231645120077f651151f5bb2babTom Gundersen + EXTRA_CMSG_SPACE /* kernel appears to require extra space */];
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen /* Query the local side */
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen r = getsockname(s->fd, &s->local.sa, &s->local_salen);
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0)
f5be560181d092c5f52a2b819aedcd48220f36abTom Gundersen /* Query the remote side */
f048a16b464295a4e0a4f4c1210f06343ad31231Tom Gundersen r = getpeername(s->fd, &s->peer.sa, &s->peer_salen);
6ae115c1fe95611b39d2f20cfcea3d385429f59eTom Gundersen if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0)
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen /* Check consistency */
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen assert(s->peer.sa.sa_family == s->local.sa.sa_family);
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6));
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen /* Query connection meta information */
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl);
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen } else if (s->peer.sa.sa_family == AF_INET6) {
8cd11a0f0f4ca05199e1166f6a07472b296f7455Tom Gundersen r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl);
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) {
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg);
f882c247ad59776c3a7753bb963c1f8e2386cb79Tom Gundersen struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg);
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen /* The Linux kernel sets the interface index to the loopback
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen * device if the connection came from the local host since it
f882c247ad59776c3a7753bb963c1f8e2386cb79Tom Gundersen * avoids the routing table in such a case. Let's unset the
f5be560181d092c5f52a2b819aedcd48220f36abTom Gundersen * interface index in such a case. */
f882c247ad59776c3a7753bb963c1f8e2386cb79Tom Gundersen /* If we don't know the interface index still, we look for the
f5be560181d092c5f52a2b819aedcd48220f36abTom Gundersen * first local interface with a matching address. Yuck! */
f5be560181d092c5f52a2b819aedcd48220f36abTom Gundersen 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);
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) {
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen /* Make sure all packets for this connection are sent on the same interface */
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex));
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m");
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen } else if (s->local.sa.sa_family == AF_INET6) {
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex));
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m");
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersenstatic int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) {
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersenstatic int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
02b59d57e0c08231645120077f651151f5bb2babTom Gundersen s->n_written < sizeof(s->write_size) + s->write_packet->size) {
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen iov[1].iov_base = DNS_PACKET_DATA(s->write_packet);
02b59d57e0c08231645120077f651151f5bb2babTom Gundersen /* Are we done? If so, disable the event source for EPOLLOUT */
02b59d57e0c08231645120077f651151f5bb2babTom Gundersen if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) {
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) &&
f048a16b464295a4e0a4f4c1210f06343ad31231Tom Gundersen s->n_read < sizeof(s->read_size) + s->read_packet->size)) {
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read);
71a6151083d842b2f5bf04e50239f0bf85d34d2eTom Gundersen } else if (ss == 0)
f048a16b464295a4e0a4f4c1210f06343ad31231Tom Gundersen if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE)
407fe036a24834203aca6c1eec7d74d9ad3e9ee0Tom Gundersen if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) {
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size));
6ae115c1fe95611b39d2f20cfcea3d385429f59eTom Gundersen s->read_packet->family = s->peer.sa.sa_family;
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen s->read_packet->sender.in = s->peer.in.sin_addr;
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen s->read_packet->sender_port = be16toh(s->peer.in.sin_port);
aa3437a573ed2a2b2a03c9649fe11d27b40a772bTom Gundersen s->read_packet->destination.in = s->local.in.sin_addr;
f882c247ad59776c3a7753bb963c1f8e2386cb79Tom Gundersen s->read_packet->destination_port = be16toh(s->local.in.sin_port);
dd3efc0993b6e95ff04775e9125e2fc9d30fe261Tom Gundersen s->read_packet->sender.in6 = s->peer.in6.sin6_addr;
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port);
f579559b3a14c1f1ef96c372e7626c4733e6ef7dTom Gundersen s->read_packet->destination.in6 = s->local.in6.sin6_addr;
if (ss < 0) {
} else if (ss == 0)
r = dns_stream_update_io(s);
return dns_stream_complete(s, -r);
if (s->on_packet)
return s->on_packet(s);
return dns_stream_complete(s, 0);
return NULL;
dns_stream_stop(s);
if (s->manager) {
free(s);
assert(m);
return -EBUSY;
return -ENOMEM;
return -errno;
r = sd_event_add_time(
m->event,
&s->timeout_event_source,
on_stream_timeout, s);
s->manager = m;
m->n_dns_streams++;
*ret = s;
s = NULL;
assert(s);
if (s->write_packet)
return -EBUSY;
s->n_written = 0;
return dns_stream_update_io(s);