diff options
-rw-r--r-- | src/resolve/resolved-dns-scope.c | 12 | ||||
-rw-r--r-- | src/resolve/resolved-mdns.c | 102 |
2 files changed, 107 insertions, 7 deletions
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 09f25a65b9..81c62bdca6 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -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; diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index 4b6df10614..0d19d08455 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -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"); |