]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: reply using unicast mDNS when appropriate 18701/head
authorSergey Bugaev <bugaevc@gmail.com>
Mon, 15 Feb 2021 17:18:34 +0000 (20:18 +0300)
committerSergey Bugaev <bugaevc@gmail.com>
Wed, 31 Mar 2021 09:54:08 +0000 (12:54 +0300)
Fixes https://github.com/systemd/systemd/issues/18434

src/resolve/resolved-dns-scope.c
src/resolve/resolved-mdns.c

index 09f25a65b9ea1fa064817edab21a1194735d9843..81c62bdca6b6213009004f96e69504859801da6f 100644 (file)
@@ -287,17 +287,23 @@ static int dns_scope_emit_one(DnsScope *s, int fd, int family, DnsPacket *p) {
                         return -EBUSY;
 
                 if (family == AF_INET) {
-                        addr.in = MDNS_MULTICAST_IPV4_ADDRESS;
+                        if (in4_addr_is_null(&p->destination.in))
+                                addr.in = MDNS_MULTICAST_IPV4_ADDRESS;
+                        else
+                                addr = p->destination;
                         fd = manager_mdns_ipv4_fd(s->manager);
                 } else if (family == AF_INET6) {
-                        addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS;
+                        if (in6_addr_is_null(&p->destination.in6))
+                                addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS;
+                        else
+                                addr = p->destination;
                         fd = manager_mdns_ipv6_fd(s->manager);
                 } else
                         return -EAFNOSUPPORT;
                 if (fd < 0)
                         return fd;
 
-                r = manager_send(s->manager, fd, s->link->ifindex, family, &addr, MDNS_PORT, NULL, p);
+                r = manager_send(s->manager, fd, s->link->ifindex, family, &addr, p->destination_port ?: MDNS_PORT, NULL, p);
                 if (r < 0)
                         return r;
 
index 4b6df10614050cd0022fd0ee3ef395a22245e713..0d19d084556a9dd06b2d533aa065da9bbb63787f 100644 (file)
@@ -173,12 +173,69 @@ static int mdns_do_tiebreak(DnsResourceKey *key, DnsAnswer *answer, DnsPacket *p
         return 0;
 }
 
+static bool mdns_should_reply_using_unicast(DnsPacket *p) {
+        DnsQuestionItem *item;
+
+        /* Work out if we should respond using multicast or unicast. */
+
+        /* The query was a legacy "one-shot mDNS query", RFC 6762, sections 5.1 and 6.7 */
+        if (p->sender_port != MDNS_PORT)
+                return true;
+
+        /* The query was a "direct unicast query", RFC 6762, section 5.5 */
+        switch (p->family) {
+        case AF_INET:
+                if (!in4_addr_equal(&p->destination.in, &MDNS_MULTICAST_IPV4_ADDRESS))
+                        return true;
+                break;
+        case AF_INET6:
+                if (!in6_addr_equal(&p->destination.in6, &MDNS_MULTICAST_IPV6_ADDRESS))
+                        return true;
+                break;
+        }
+
+        /* All the questions in the query had a QU bit set, RFC 6762, section 5.4 */
+        DNS_QUESTION_FOREACH_ITEM(item, p->question) {
+                if (!FLAGS_SET(item->flags, DNS_QUESTION_WANTS_UNICAST_REPLY))
+                        return false;
+        }
+        return true;
+}
+
+static bool sender_on_local_subnet(DnsScope *s, DnsPacket *p) {
+        LinkAddress *a;
+        int r;
+
+        /* Check whether the sender is on a local subnet. */
+
+        if (!s->link)
+                return false;
+
+        LIST_FOREACH(addresses, a, s->link->addresses) {
+                if (a->family != p->family)
+                        continue;
+                if (a->prefixlen == UCHAR_MAX) /* don't know subnet mask */
+                        continue;
+
+                r = in_addr_prefix_covers(a->family, &a->in_addr, a->prefixlen, &p->sender);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to determine whether link address covers sender address: %m");
+                if (r > 0)
+                        return true;
+        }
+
+        return false;
+}
+
+
 static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
         _cleanup_(dns_answer_unrefp) DnsAnswer *full_answer = NULL;
         _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
         DnsResourceKey *key = NULL;
         DnsResourceRecord *rr;
         bool tentative = false;
+        bool legacy_query = p->sender_port != MDNS_PORT;
+        bool unicast_reply;
         int r;
 
         assert(s);
@@ -190,8 +247,18 @@ static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
 
         assert_return((dns_question_size(p->question) > 0), -EINVAL);
 
+        unicast_reply = mdns_should_reply_using_unicast(p);
+        if (unicast_reply && !sender_on_local_subnet(s, p)) {
+                /* RFC 6762, section 5.5 recommends silently ignoring unicast queries
+                 * from senders outside the local network, so that we don't reveal our
+                 * internal network structure to outsiders. */
+                log_debug("Sender wants a unicast reply, but is not on a local subnet. Ignoring.");
+                return 0;
+        }
+
         DNS_QUESTION_FOREACH(key, p->question) {
                 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
+                DnsAnswerItem *item;
 
                 r = dns_zone_lookup(&s->zone, key, 0, &answer, &soa, &tentative);
                 if (r < 0)
@@ -222,21 +289,48 @@ static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
                         }
                 }
 
-                r = dns_answer_extend(&full_answer, answer);
-                if (r < 0)
-                        return log_debug_errno(r, "Failed to extend answer: %m");
+                if (dns_answer_isempty(answer))
+                        continue;
+
+                /* Copy answer items from full_answer to answer, tweaking them if needed. */
+                if (full_answer) {
+                        r = dns_answer_reserve(&full_answer, dns_answer_size(answer));
+                        if (r < 0)
+                                return log_debug_errno(r, "Failed to reserve space in answer");
+                } else {
+                        full_answer = dns_answer_new(dns_answer_size(answer));
+                        if (!full_answer)
+                                return log_oom();
+                }
+
+                DNS_ANSWER_FOREACH_ITEM(item, answer) {
+                        DnsAnswerFlags flags = item->flags;
+                        /* The cache-flush bit must not be set in legacy unicast responses.
+                         * See section 6.7 of RFC 6762. */
+                        if (legacy_query)
+                                flags &= ~DNS_ANSWER_CACHE_FLUSH;
+                        r = dns_answer_add(full_answer, item->rr, item->ifindex, flags, item->rrsig);
+                        if (r < 0)
+                                return log_debug_errno(r, "Failed to extend answer: %m");
+                }
         }
 
         if (dns_answer_isempty(full_answer))
                 return 0;
 
-        r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, NULL, full_answer, NULL, false, &reply);
+        r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS,
+                                        legacy_query ? p->question : NULL, full_answer,
+                                        NULL, false, &reply);
         if (r < 0)
                 return log_debug_errno(r, "Failed to build reply packet: %m");
 
         if (!ratelimit_below(&s->ratelimit))
                 return 0;
 
+        if (unicast_reply) {
+                reply->destination = p->sender;
+                reply->destination_port = p->sender_port;
+        }
         r = dns_scope_emit_udp(s, -1, AF_UNSPEC, reply);
         if (r < 0)
                 return log_debug_errno(r, "Failed to send reply packet: %m");