chiark / gitweb /
resolved: add 100ms initial jitter to all LLMNR requests
[elogind.git] / src / resolve / resolved-dns-scope.c
index 42a2fc4c774c3fbc923acc6f8075626c3901d703..40c326a81d249913f741fc68cefc8711ff34e22d 100644 (file)
@@ -28,6 +28,9 @@
 #include "resolved-dns-domain.h"
 #include "resolved-dns-scope.h"
 
+#define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC)
+#define MULTICAST_RATELIMIT_BURST 1000
+
 int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int family) {
         DnsScope *s;
 
@@ -49,6 +52,9 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int
 
         log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family));
 
+        /* Enforce ratelimiting for the multicast protocols */
+        RATELIMIT_INIT(s->ratelimit, MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST);
+
         *ret = s;
         return 0;
 }
@@ -85,7 +91,7 @@ DnsScope* dns_scope_free(DnsScope *s) {
         return NULL;
 }
 
-DnsServer *dns_scope_get_server(DnsScope *s) {
+DnsServer *dns_scope_get_dns_server(DnsScope *s) {
         assert(s);
 
         if (s->protocol != DNS_PROTOCOL_DNS)
@@ -133,7 +139,7 @@ int dns_scope_send(DnsScope *s, DnsPacket *p) {
                 if (DNS_PACKET_QDCOUNT(p) > 1)
                         return -ENOTSUP;
 
-                srv = dns_scope_get_server(s);
+                srv = dns_scope_get_dns_server(s);
                 if (!srv)
                         return -ESRCH;
 
@@ -161,6 +167,9 @@ int dns_scope_send(DnsScope *s, DnsPacket *p) {
                 if (DNS_PACKET_QDCOUNT(p) > 1)
                         return -ENOTSUP;
 
+                if (!ratelimit_test(&s->ratelimit))
+                        return -EBUSY;
+
                 family = s->family;
                 port = 5355;
 
@@ -197,7 +206,7 @@ int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *add
         if (family == AF_UNSPEC) {
                 DnsServer *srv;
 
-                srv = dns_scope_get_server(s);
+                srv = dns_scope_get_dns_server(s);
                 if (!srv)
                         return -ESRCH;
 
@@ -353,6 +362,12 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) {
                 if (fd < 0)
                         return fd;
 
+                /* Always first try to drop membership before we add
+                 * one. This is necessary on some devices, such as
+                 * veth. */
+                if (b)
+                        setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn));
+
                 if (setsockopt(fd, IPPROTO_IP, b ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0)
                         return -errno;
 
@@ -366,6 +381,9 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) {
                 if (fd < 0)
                         return fd;
 
+                if (b)
+                        setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
+
                 if (setsockopt(fd, IPPROTO_IPV6, b ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
                         return -errno;
         } else
@@ -384,7 +402,7 @@ int dns_scope_good_dns_server(DnsScope *s, int family, const union in_addr_union
         if (s->link)
                 return !!link_find_dns_server(s->link,  family, address);
         else
-                return manager_known_dns_server(s->manager, family, address);
+                return !!manager_find_dns_server(s->manager, family, address);
 }
 
 static int dns_scope_make_reply_packet(
@@ -403,7 +421,9 @@ static int dns_scope_make_reply_packet(
 
         assert(s);
 
-        if (q->n_keys <= 0 && answer->n_rrs <= 0 && soa->n_rrs <= 0)
+        if ((!q || q->n_keys <= 0)
+            && (!answer || answer->n_rrs <= 0)
+            && (!soa || soa->n_rrs <= 0))
                 return -EINVAL;
 
         r = dns_packet_new(&p, s->protocol, 0);
@@ -513,6 +533,9 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
         if (stream)
                 r = dns_stream_write_packet(stream, reply);
         else {
+                if (!ratelimit_test(&s->ratelimit))
+                        return;
+
                 if (p->family == AF_INET)
                         fd = manager_llmnr_ipv4_udp_fd(s->manager);
                 else if (p->family == AF_INET6)
@@ -526,6 +549,11 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
                         return;
                 }
 
+                /* Note that we always immediately reply to all LLMNR
+                 * requests, and do not wait any time, since we
+                 * verified uniqueness for all records. Also see RFC
+                 * 4795, Section 2.7 */
+
                 r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, reply);
         }
 
@@ -535,7 +563,7 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
         }
 }
 
-DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsQuestion *question) {
+DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsQuestion *question, bool cache_ok) {
         DnsTransaction *t;
 
         assert(scope);
@@ -544,9 +572,19 @@ DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsQuestion *questio
         /* Try to find an ongoing transaction that is a equal or a
          * superset of the specified question */
 
-        LIST_FOREACH(transactions_by_scope, t, scope->transactions)
+        LIST_FOREACH(transactions_by_scope, t, scope->transactions) {
+
+                /* Refuse reusing transactions that completed based on
+                 * cached data instead of a real packet, if that's
+                 * requested. */
+                if (!cache_ok &&
+                    IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE) &&
+                    !t->received)
+                        continue;
+
                 if (dns_question_is_superset(t->question, question) > 0)
                         return t;
+        }
 
         return NULL;
 }