105e151299
This large patch adds a couple of mechanisms to ensure we get NSEC3 and proof-of-unsigned support into place. Specifically: - Each item in an DnsAnswer gets two bit flags now: DNS_ANSWER_AUTHENTICATED and DNS_ANSWER_CACHEABLE. The former is necessary since DNS responses might contain signed as well as unsigned RRsets in one, and we need to remember which ones are signed and which ones aren't. The latter is necessary, since not we need to keep track which RRsets may be cached and which ones may not be, even while manipulating DnsAnswer objects. - The .n_answer_cachable of DnsTransaction is dropped now (it used to store how many of the first DnsAnswer entries are cachable), and replaced by the DNS_ANSWER_CACHABLE flag instead. - NSEC3 proofs are implemented now (lacking support for the wildcard part, to be added in a later commit). - Support for the "AD" bit has been dropped. It's unsafe, and now that we have end-to-end authentication we don't need it anymore. - An auxiliary DnsTransaction of a DnsTransactions is now kept around as least as long as the latter stays around. We no longer remove the auxiliary DnsTransaction as soon as it completed. THis is necessary, as we now are interested not only in the RRsets it acquired but also in its authentication status.
290 lines
8.9 KiB
C
290 lines
8.9 KiB
C
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
|
|
|
/***
|
|
This file is part of systemd.
|
|
|
|
Copyright 2015 Daniel Mack
|
|
|
|
systemd is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
systemd is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
***/
|
|
|
|
#include <resolv.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "fd-util.h"
|
|
#include "resolved-manager.h"
|
|
#include "resolved-mdns.h"
|
|
|
|
void manager_mdns_stop(Manager *m) {
|
|
assert(m);
|
|
|
|
m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source);
|
|
m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd);
|
|
|
|
m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source);
|
|
m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd);
|
|
}
|
|
|
|
int manager_mdns_start(Manager *m) {
|
|
int r;
|
|
|
|
assert(m);
|
|
|
|
if (m->mdns_support == SUPPORT_NO)
|
|
return 0;
|
|
|
|
r = manager_mdns_ipv4_fd(m);
|
|
if (r == -EADDRINUSE)
|
|
goto eaddrinuse;
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (socket_ipv6_is_supported()) {
|
|
r = manager_mdns_ipv6_fd(m);
|
|
if (r == -EADDRINUSE)
|
|
goto eaddrinuse;
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
|
|
eaddrinuse:
|
|
log_warning("There appears to be another mDNS responder running. Turning off mDNS support.");
|
|
m->mdns_support = SUPPORT_NO;
|
|
manager_mdns_stop(m);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
|
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
|
|
Manager *m = userdata;
|
|
DnsScope *scope;
|
|
int r;
|
|
|
|
r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p);
|
|
if (r <= 0)
|
|
return r;
|
|
|
|
scope = manager_find_scope(m, p);
|
|
if (!scope) {
|
|
log_warning("Got mDNS UDP packet on unknown scope. Ignoring.");
|
|
return 0;
|
|
}
|
|
|
|
if (dns_packet_validate_reply(p) > 0) {
|
|
DnsResourceRecord *rr;
|
|
|
|
log_debug("Got mDNS reply packet");
|
|
|
|
/*
|
|
* mDNS is different from regular DNS and LLMNR with regard to handling responses.
|
|
* While on other protocols, we can ignore every answer that doesn't match a question
|
|
* we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all
|
|
* incoming information, regardless of the DNS packet ID.
|
|
*
|
|
* Hence, extract the packet here, and try to find a transaction for answer the we got
|
|
* and complete it. Also store the new information in scope's cache.
|
|
*/
|
|
r = dns_packet_extract(p);
|
|
if (r < 0) {
|
|
log_debug("mDNS packet extraction failed.");
|
|
return 0;
|
|
}
|
|
|
|
dns_scope_check_conflicts(scope, p);
|
|
|
|
DNS_ANSWER_FOREACH(rr, p->answer) {
|
|
const char *name = DNS_RESOURCE_KEY_NAME(rr->key);
|
|
DnsTransaction *t;
|
|
|
|
/* If the received reply packet contains ANY record that is not .local or .in-addr.arpa,
|
|
* we assume someone's playing tricks on us and discard the packet completely. */
|
|
if (!(dns_name_endswith(name, "in-addr.arpa") > 0 ||
|
|
dns_name_endswith(name, "local") > 0))
|
|
return 0;
|
|
|
|
t = dns_scope_find_transaction(scope, rr->key, false);
|
|
if (t)
|
|
dns_transaction_process_reply(t, p);
|
|
}
|
|
|
|
dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, 0, p->family, &p->sender);
|
|
|
|
} else if (dns_packet_validate_query(p) > 0) {
|
|
log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));
|
|
|
|
dns_scope_process_query(scope, NULL, p);
|
|
} else
|
|
log_debug("Invalid mDNS UDP packet.");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int manager_mdns_ipv4_fd(Manager *m) {
|
|
union sockaddr_union sa = {
|
|
.in.sin_family = AF_INET,
|
|
.in.sin_port = htobe16(MDNS_PORT),
|
|
};
|
|
static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255;
|
|
int r;
|
|
|
|
assert(m);
|
|
|
|
if (m->mdns_ipv4_fd >= 0)
|
|
return m->mdns_ipv4_fd;
|
|
|
|
m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
|
if (m->mdns_ipv4_fd < 0)
|
|
return -errno;
|
|
|
|
r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
/* Disable Don't-Fragment bit in the IP header */
|
|
r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m);
|
|
if (r < 0)
|
|
goto fail;
|
|
|
|
return m->mdns_ipv4_fd;
|
|
|
|
fail:
|
|
m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd);
|
|
return r;
|
|
}
|
|
|
|
int manager_mdns_ipv6_fd(Manager *m) {
|
|
union sockaddr_union sa = {
|
|
.in6.sin6_family = AF_INET6,
|
|
.in6.sin6_port = htobe16(MDNS_PORT),
|
|
};
|
|
static const int one = 1, ttl = 255;
|
|
int r;
|
|
|
|
assert(m);
|
|
|
|
if (m->mdns_ipv6_fd >= 0)
|
|
return m->mdns_ipv6_fd;
|
|
|
|
m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
|
if (m->mdns_ipv6_fd < 0)
|
|
return -errno;
|
|
|
|
r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
/* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
|
|
r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6));
|
|
if (r < 0) {
|
|
r = -errno;
|
|
goto fail;
|
|
}
|
|
|
|
r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m);
|
|
if (r < 0)
|
|
goto fail;
|
|
|
|
return m->mdns_ipv6_fd;
|
|
|
|
fail:
|
|
m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd);
|
|
return r;
|
|
}
|