diff options
Diffstat (limited to 'src/resolve/resolved-mdns.c')
| -rw-r--r-- | src/resolve/resolved-mdns.c | 288 | 
1 files changed, 288 insertions, 0 deletions
| diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c new file mode 100644 index 0000000000..096a4b1fe5 --- /dev/null +++ b/src/resolve/resolved-mdns.c @@ -0,0 +1,288 @@ +/*-*- 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) { +                unsigned i; + +                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); + +                for (i = 0; i < p->answer->n_rrs; i++) { +                        DnsResourceRecord *rr; +                        DnsTransaction *t; + +                        rr = p->answer->items[i].rr; + +                        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, +                              p->answer->n_rrs, 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)  { +                r = -errno; +                goto fail; +        } + +        return m->mdns_ipv6_fd; + +fail: +        m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); +        return r; +} | 
