chiark / gitweb /
resolved: implement full LLMNR conflict detection logic
authorLennart Poettering <lennart@poettering.net>
Wed, 6 Aug 2014 14:15:35 +0000 (16:15 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 11 Aug 2014 13:06:22 +0000 (15:06 +0200)
src/resolve/resolved-dns-cache.c
src/resolve/resolved-dns-cache.h
src/resolve/resolved-dns-packet.c
src/resolve/resolved-dns-packet.h
src/resolve/resolved-dns-scope.c
src/resolve/resolved-dns-scope.h
src/resolve/resolved-dns-transaction.c
src/resolve/resolved-dns-zone.c
src/resolve/resolved-dns-zone.h
src/resolve/resolved-manager.c
src/resolve/resolved-manager.h

index 696eb9d..733ef63 100644 (file)
@@ -43,6 +43,8 @@ struct DnsCacheItem {
         usec_t until;
         DnsCacheItemType type;
         unsigned prioq_idx;
+        int owner_family;
+        union in_addr_union owner_address;
         LIST_FIELDS(DnsCacheItem, by_key);
 };
 
@@ -256,13 +258,20 @@ static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsReso
         prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
 }
 
-static int dns_cache_put_positive(DnsCache *c, DnsResourceRecord *rr, usec_t timestamp) {
+static int dns_cache_put_positive(
+                DnsCache *c,
+                DnsResourceRecord *rr,
+                usec_t timestamp,
+                int owner_family,
+                const union in_addr_union *owner_address) {
+
         _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
         DnsCacheItem *existing;
         int r;
 
         assert(c);
         assert(rr);
+        assert(owner_address);
 
         /* New TTL is 0? Delete the entry... */
         if (rr->ttl <= 0) {
@@ -298,6 +307,8 @@ static int dns_cache_put_positive(DnsCache *c, DnsResourceRecord *rr, usec_t tim
         i->rr = dns_resource_record_ref(rr);
         i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
         i->prioq_idx = PRIOQ_IDX_NULL;
+        i->owner_family = owner_family;
+        i->owner_address = *owner_address;
 
         r = dns_cache_link_item(c, i);
         if (r < 0)
@@ -307,12 +318,21 @@ static int dns_cache_put_positive(DnsCache *c, DnsResourceRecord *rr, usec_t tim
         return 0;
 }
 
-static int dns_cache_put_negative(DnsCache *c, DnsResourceKey *key, int rcode, usec_t timestamp, uint32_t soa_ttl) {
+static int dns_cache_put_negative(
+                DnsCache *c,
+                DnsResourceKey *key,
+                int rcode,
+                usec_t timestamp,
+                uint32_t soa_ttl,
+                int owner_family,
+                const union in_addr_union *owner_address) {
+
         _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
         int r;
 
         assert(c);
         assert(key);
+        assert(owner_address);
 
         dns_cache_remove(c, key);
 
@@ -340,6 +360,8 @@ static int dns_cache_put_negative(DnsCache *c, DnsResourceKey *key, int rcode, u
         i->key = dns_resource_key_ref(key);
         i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
         i->prioq_idx = PRIOQ_IDX_NULL;
+        i->owner_family = owner_family;
+        i->owner_address = *owner_address;
 
         r = dns_cache_link_item(c, i);
         if (r < 0)
@@ -349,7 +371,16 @@ static int dns_cache_put_negative(DnsCache *c, DnsResourceKey *key, int rcode, u
         return 0;
 }
 
-int dns_cache_put(DnsCache *c, DnsQuestion *q, int rcode, DnsAnswer *answer, unsigned max_rrs, usec_t timestamp) {
+int dns_cache_put(
+                DnsCache *c,
+                DnsQuestion *q,
+                int rcode,
+                DnsAnswer *answer,
+                unsigned max_rrs,
+                usec_t timestamp,
+                int owner_family,
+                const union in_addr_union *owner_address) {
+
         unsigned i;
         int r;
 
@@ -382,7 +413,7 @@ int dns_cache_put(DnsCache *c, DnsQuestion *q, int rcode, DnsAnswer *answer, uns
 
         /* Second, add in positive entries for all contained RRs */
         for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) {
-                r = dns_cache_put_positive(c, answer->rrs[i], timestamp);
+                r = dns_cache_put_positive(c, answer->rrs[i], timestamp, owner_family, owner_address);
                 if (r < 0)
                         goto fail;
         }
@@ -403,7 +434,7 @@ int dns_cache_put(DnsCache *c, DnsQuestion *q, int rcode, DnsAnswer *answer, uns
                 if (r == 0)
                         continue;
 
-                r = dns_cache_put_negative(c, q->keys[i], rcode, timestamp, MIN(soa->soa.minimum, soa->ttl));
+                r = dns_cache_put_negative(c, q->keys[i], rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
                 if (r < 0)
                         goto fail;
         }
@@ -495,3 +526,39 @@ int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **ret) {
 
         return n;
 }
+
+int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {
+        DnsCacheItem *i, *first;
+        bool same_owner = true;
+
+        assert(cache);
+        assert(rr);
+
+        dns_cache_prune(cache);
+
+        /* See if there's a cache entry for the same key. If there
+         * isn't there's no conflict */
+        first = hashmap_get(cache->by_key, rr->key);
+        if (!first)
+                return 0;
+
+        /* See if the RR key is owned by the same owner, if so, there
+         * isn't a conflict either */
+        LIST_FOREACH(by_key, i, first) {
+                if (i->owner_family != owner_family ||
+                    !in_addr_equal(owner_family, &i->owner_address, owner_address)) {
+                        same_owner = false;
+                        break;
+                }
+        }
+        if (same_owner)
+                return 0;
+
+        /* See if there's the exact same RR in the cache. If yes, then
+         * there's no conflict. */
+        if (dns_cache_get(cache, rr))
+                return 0;
+
+        /* There's a conflict */
+        return 1;
+}
index d88d1d0..e92280c 100644 (file)
@@ -40,5 +40,7 @@ typedef struct DnsCache {
 void dns_cache_flush(DnsCache *c);
 void dns_cache_prune(DnsCache *c);
 
-int dns_cache_put(DnsCache *c, DnsQuestion *q, int rcode, DnsAnswer *answer, unsigned max_rrs, usec_t timestamp);
+int dns_cache_put(DnsCache *c, DnsQuestion *q, int rcode, DnsAnswer *answer, unsigned max_rrs, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
 int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **answer);
+
+int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address);
index 4f95038..0d276df 100644 (file)
@@ -1358,6 +1358,9 @@ int dns_packet_extract(DnsPacket *p) {
         unsigned n, i;
         int r;
 
+        if (p->extracted)
+                return 0;
+
         saved_rindex = p->rindex;
         dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE);
 
@@ -1409,6 +1412,8 @@ int dns_packet_extract(DnsPacket *p) {
         p->answer = answer;
         answer = NULL;
 
+        p->extracted = true;
+
         r = 0;
 
 finish:
index 26a2e76..6a865a2 100644 (file)
@@ -81,6 +81,8 @@ struct DnsPacket {
         union in_addr_union sender, destination;
         uint16_t sender_port, destination_port;
         uint32_t ttl;
+
+        bool extracted;
 };
 
 static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) {
index 40c326a..174249a 100644 (file)
@@ -61,6 +61,7 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int
 
 DnsScope* dns_scope_free(DnsScope *s) {
         DnsTransaction *t;
+        DnsResourceRecord *rr;
 
         if (!s)
                 return NULL;
@@ -81,6 +82,12 @@ DnsScope* dns_scope_free(DnsScope *s) {
                 dns_transaction_free(t);
         }
 
+        while ((rr = hashmap_steal_first(s->conflict_queue)))
+                dns_resource_record_unref(rr);
+
+        hashmap_free(s->conflict_queue);
+        sd_event_source_unref(s->conflict_event_source);
+
         dns_cache_flush(&s->cache);
         dns_zone_flush(&s->zone);
 
@@ -115,7 +122,7 @@ void dns_scope_next_dns_server(DnsScope *s) {
                 manager_next_dns_server(s->manager);
 }
 
-int dns_scope_send(DnsScope *s, DnsPacket *p) {
+int dns_scope_emit(DnsScope *s, DnsPacket *p) {
         union in_addr_union addr;
         int ifindex = 0, r;
         int family;
@@ -420,6 +427,7 @@ static int dns_scope_make_reply_packet(
         int r;
 
         assert(s);
+        assert(ret);
 
         if ((!q || q->n_keys <= 0)
             && (!answer || answer->n_rrs <= 0)
@@ -478,6 +486,20 @@ static int dns_scope_make_reply_packet(
         return 0;
 }
 
+static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) {
+        unsigned n;
+
+        assert(s);
+        assert(p);
+
+        if (p->question)
+                for (n = 0; n < p->question->n_keys; n++)
+                        dns_zone_verify_conflicts(&s->zone, p->question->keys[n]);
+        if (p->answer)
+                for (n = 0; n < p->answer->n_rrs; n++)
+                        dns_zone_verify_conflicts(&s->zone, p->answer->rrs[n]->key);
+}
+
 void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
         _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
@@ -509,7 +531,8 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
         }
 
         if (DNS_PACKET_C(p)) {
-                /* FIXME: Somebody notified us about a likely conflict */
+                /* Somebody notified us about a possible conflict */
+                dns_scope_verify_conflicts(s, p);
                 return;
         }
 
@@ -588,3 +611,174 @@ DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsQuestion *questio
 
         return NULL;
 }
+
+static int dns_scope_make_conflict_packet(
+                DnsScope *s,
+                DnsResourceRecord *rr,
+                DnsPacket **ret) {
+
+        _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+        int r;
+
+        assert(s);
+        assert(rr);
+        assert(ret);
+
+        r = dns_packet_new(&p, s->protocol, 0);
+        if (r < 0)
+                return r;
+
+        DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
+                                                              0 /* qr */,
+                                                              0 /* opcode */,
+                                                              1 /* conflict */,
+                                                              0 /* tc */,
+                                                              0 /* t */,
+                                                              0 /* (ra) */,
+                                                              0 /* (ad) */,
+                                                              0 /* (cd) */,
+                                                              0));
+        random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t));
+        DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
+        DNS_PACKET_HEADER(p)->arcount = htobe16(1);
+
+        r = dns_packet_append_key(p, rr->key, NULL);
+        if (r < 0)
+                return r;
+
+        r = dns_packet_append_rr(p, rr, NULL);
+        if (r < 0)
+                return r;
+
+        *ret = p;
+        p = NULL;
+
+        return 0;
+}
+
+static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata) {
+        DnsScope *scope = userdata;
+        int r;
+
+        assert(es);
+        assert(scope);
+
+        scope->conflict_event_source = sd_event_source_unref(scope->conflict_event_source);
+
+        for (;;) {
+                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+                _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+
+                rr = hashmap_steal_first(scope->conflict_queue);
+                if (!rr)
+                        break;
+
+                r = dns_scope_make_conflict_packet(scope, rr, &p);
+                if (r < 0) {
+                        log_error("Failed to make conflict packet: %s", strerror(-r));
+                        return 0;
+                }
+
+                r = dns_scope_emit(scope, p);
+                if (r < 0)
+                        log_debug("Failed to send conflict packet: %s", strerror(-r));
+        }
+
+        return 0;
+}
+
+int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) {
+        usec_t jitter;
+        int r;
+
+        assert(scope);
+        assert(rr);
+
+        /* We don't send these queries immediately. Instead, we queue
+         * them, and send them after some jitter delay. */
+        r = hashmap_ensure_allocated(&scope->conflict_queue, dns_resource_key_hash_func, dns_resource_key_compare_func);
+        if (r < 0) {
+                log_oom();
+                return r;
+        }
+
+        /* We only place one RR per key in the conflict
+         * messages, not all of them. That should be enough to
+         * indicate where there might be a conflict */
+        r = hashmap_put(scope->conflict_queue, rr->key, rr);
+        if (r == -EEXIST || r == 0)
+                return 0;
+        if (r < 0) {
+                log_debug("Failed to queue conflicting RR: %s", strerror(-r));
+                return r;
+        }
+
+        dns_resource_record_ref(rr);
+
+        if (scope->conflict_event_source)
+                return 0;
+
+        random_bytes(&jitter, sizeof(jitter));
+        jitter %= LLMNR_JITTER_INTERVAL_USEC;
+
+        r = sd_event_add_time(scope->manager->event,
+                              &scope->conflict_event_source,
+                              clock_boottime_or_monotonic(),
+                              now(clock_boottime_or_monotonic()) + jitter,
+                              LLMNR_JITTER_INTERVAL_USEC,
+                              on_conflict_dispatch, scope);
+        if (r < 0) {
+                log_debug("Failed to add conflict dispatch event: %s", strerror(-r));
+                return r;
+        }
+
+        return 0;
+}
+
+void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) {
+        unsigned i;
+        int r;
+
+        assert(scope);
+        assert(p);
+
+        if (p->protocol != DNS_PROTOCOL_LLMNR)
+                return;
+
+        if (DNS_PACKET_RRCOUNT(p) <= 0)
+                return;
+
+        if (DNS_PACKET_C(p) != 0)
+                return;
+
+        if (DNS_PACKET_T(p) != 0)
+                return;
+
+        if (manager_our_packet(scope->manager, p))
+                return;
+
+        r = dns_packet_extract(p);
+        if (r < 0) {
+                log_debug("Failed to extract packet: %s", strerror(-r));
+                return;
+        }
+
+        log_debug("Checking for conflicts...");
+
+        for (i = 0; i < p->answer->n_rrs; i++) {
+
+                /* Check for conflicts against the local zone. If we
+                 * found one, we won't check any further */
+                r = dns_zone_check_conflicts(&scope->zone, p->answer->rrs[i]);
+                if (r != 0)
+                        continue;
+
+                /* Check for conflicts against the local cache. If so,
+                 * send out an advisory query, to inform everybody */
+                r = dns_cache_check_conflicts(&scope->cache, p->answer->rrs[i], p->family, &p->sender);
+                if (r <= 0)
+                        continue;
+
+                dns_scope_notify_conflict(scope, p->answer->rrs[i]);
+        }
+}
index ae9469a..6ba5ef2 100644 (file)
@@ -55,6 +55,9 @@ struct DnsScope {
         DnsCache cache;
         DnsZone zone;
 
+        Hashmap *conflict_queue;
+        sd_event_source *conflict_event_source;
+
         RateLimit ratelimit;
 
         LIST_HEAD(DnsTransaction, transactions);
@@ -65,7 +68,7 @@ struct DnsScope {
 int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family);
 DnsScope* dns_scope_free(DnsScope *s);
 
-int dns_scope_send(DnsScope *s, DnsPacket *p);
+int dns_scope_emit(DnsScope *s, DnsPacket *p);
 int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port);
 
 DnsScopeMatch dns_scope_good_domain(DnsScope *s, const char *domain);
@@ -80,3 +83,6 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b);
 void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p);
 
 DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsQuestion *question, bool cache_ok);
+
+int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr);
+void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p);
index a2e4f2c..e76940e 100644 (file)
@@ -132,6 +132,15 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
                   t->scope->link ? t->scope->link->name : "*",
                   t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family));
 
+        /* RFC 4795, Section 4.1 says that the peer with the
+         * lexicographically smaller IP address loses */
+        if (memcmp(&p->sender, &p->destination, FAMILY_ADDRESS_SIZE(p->family)) < 0) {
+                log_debug("Peer has lexicographically smaller IP address and thus lost in the conflict.");
+                return;
+        }
+
+        log_debug("We have the lexicographically smaller IP address and thus lost in the conflict.");
+
         t->block_gc++;
         SET_FOREACH(z, t->zone_items, i)
                 dns_zone_item_conflict(z);
@@ -196,6 +205,14 @@ static int on_stream_complete(DnsStream *s, int error) {
                 return 0;
         }
 
+        if (dns_packet_validate_reply(p) <= 0) {
+                log_debug("Invalid LLMNR TCP packet.");
+                dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
+                return 0;
+        }
+
+        dns_scope_check_conflicts(t->scope, p);
+
         t->block_gc++;
         dns_transaction_process_reply(t, p);
         t->block_gc--;
@@ -370,7 +387,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
         }
 
         /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */
-        dns_cache_put(&t->scope->cache, p->question, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0);
+        dns_cache_put(&t->scope->cache, p->question, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender);
 
         if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
                 dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
@@ -507,7 +524,8 @@ int dns_transaction_go(DnsTransaction *t) {
                                 t->scope->manager->event,
                                 &t->timeout_event_source,
                                 clock_boottime_or_monotonic(),
-                                now(clock_boottime_or_monotonic()) + jitter, LLMNR_JITTER_INTERVAL_USEC,
+                                now(clock_boottime_or_monotonic()) + jitter,
+                                LLMNR_JITTER_INTERVAL_USEC,
                                 on_transaction_timeout, t);
                 if (r < 0)
                         return r;
@@ -542,7 +560,7 @@ int dns_transaction_go(DnsTransaction *t) {
                 r = dns_transaction_open_tcp(t);
         } else {
                 /* Try via UDP, and if that fails due to large size try via TCP */
-                r = dns_scope_send(t->scope, t->sent);
+                r = dns_scope_emit(t->scope, t->sent);
                 if (r == -EMSGSIZE)
                         r = dns_transaction_open_tcp(t);
         }
index ed47759..d96ddd2 100644 (file)
@@ -493,6 +493,9 @@ void dns_zone_item_conflict(DnsZoneItem *i) {
 
         assert(i);
 
+        if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
+                return;
+
         dns_resource_record_to_string(i->rr, &pretty);
         log_info("Detected conflict on %s", strna(pretty));
 
@@ -507,6 +510,8 @@ void dns_zone_item_conflict(DnsZoneItem *i) {
 }
 
 void dns_zone_item_ready(DnsZoneItem *i) {
+        _cleanup_free_ char *pretty = NULL;
+
         assert(i);
         assert(i->probe_transaction);
 
@@ -516,14 +521,107 @@ void dns_zone_item_ready(DnsZoneItem *i) {
         if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
                 return;
 
-        if (i->probe_transaction->state != DNS_TRANSACTION_SUCCESS) {
-                _cleanup_free_ char *pretty = NULL;
+        if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
+                bool we_lost = false;
 
-                dns_resource_record_to_string(i->rr, &pretty);
-                log_debug("Record %s successfully probed.", strna(pretty));
+                /* The probe got a successful reply. If we so far
+                 * weren't established we just give up. If we already
+                 * were established, and the peer has the
+                 * lexicographically smaller IP address we continue
+                 * and defend it. */
+
+                if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
+                        we_lost = true;
+                else {
+                        assert(i->probe_transaction->received);
+                        we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) > 0;
+                }
+
+                if (we_lost) {
+                        dns_zone_item_conflict(i);
+                        return;
+                }
+
+                log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
+        }
+
+        dns_resource_record_to_string(i->rr, &pretty);
+        log_debug("Record %s successfully probed.", strna(pretty));
 
-                dns_zone_item_probe_stop(i);
+        dns_zone_item_probe_stop(i);
+        i->state = DNS_ZONE_ITEM_ESTABLISHED;
+}
+
+static int dns_zone_item_verify(DnsZoneItem *i) {
+        int r;
+
+        assert(i);
+
+        if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
+                return 0;
+
+        i->state = DNS_ZONE_ITEM_VERIFYING;
+        r = dns_zone_item_probe_start(i);
+        if (r < 0) {
+                log_error("Failed to start probing for verifying RR: %s", strerror(-r));
                 i->state = DNS_ZONE_ITEM_ESTABLISHED;
-        } else
-                dns_zone_item_conflict(i);
+                return r;
+        }
+
+        return 0;
+}
+
+int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
+        DnsZoneItem *i, *first;
+        int c;
+
+        assert(zone);
+        assert(rr);
+
+        /* This checks whether a response RR we received from somebody
+         * else is one that we actually thought was uniquely ours. If
+         * so, we'll verify our RRs. */
+
+        /* No conflict if we don't have the name at all. */
+        first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key));
+        if (!first)
+                return 0;
+
+        /* No conflict if we have the exact same RR */
+        if (dns_zone_get(zone, rr))
+                return 0;
+
+        /* OK, somebody else has RRs for the same name. Yuck! Let's
+         * start probing again */
+
+        LIST_FOREACH(by_name, i, first) {
+                if (dns_resource_record_equal(i->rr, rr))
+                        continue;
+
+                dns_zone_item_verify(i);
+                c++;
+        }
+
+        return c;
+}
+
+int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
+        DnsZoneItem *i, *first;
+        int c;
+
+        assert(zone);
+
+        /* Somebody else notified us about a possible conflict. Let's
+         * verify if that's true. */
+
+        first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key));
+        if (!first)
+                return 0;
+
+        LIST_FOREACH(by_name, i, first) {
+                dns_zone_item_verify(i);
+                c++;
+        }
+
+        return c;
 }
index bf93ab4..37fdafe 100644 (file)
@@ -71,3 +71,6 @@ int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **answer, DnsAnswer **
 
 void dns_zone_item_conflict(DnsZoneItem *i);
 void dns_zone_item_ready(DnsZoneItem *i);
+
+int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr);
+int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key);
index 1288395..a93f4a5 100644 (file)
@@ -1219,38 +1219,34 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u
         _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
         DnsTransaction *t = NULL;
         Manager *m = userdata;
+        DnsScope *scope;
         int r;
 
         r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p);
         if (r <= 0)
                 return r;
 
+        scope = manager_find_scope(m, p);
+        if (!scope) {
+                log_warning("Got LLMNR UDP packet on unknown scope. Ignoring.");
+                return 0;
+        }
+
         if (dns_packet_validate_reply(p) > 0) {
                 log_debug("Got reply packet for id %u", DNS_PACKET_ID(p));
 
-                t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p)));
-                if (!t)
-                        return 0;
-
-                dns_transaction_process_reply(t, p);
+                dns_scope_check_conflicts(scope, p);
 
-        } else if (dns_packet_validate_query(p) > 0) {
-                Link *l;
-
-                l = hashmap_get(m->links, INT_TO_PTR(p->ifindex));
-                if (l) {
-                        DnsScope *scope = NULL;
+                t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p)));
+                if (t)
+                        dns_transaction_process_reply(t, p);
 
-                        if (p->family == AF_INET)
-                                scope = l->llmnr_ipv4_scope;
-                        else if (p->family == AF_INET6)
-                                scope = l->llmnr_ipv6_scope;
+        } else if (dns_packet_validate_query(p) > 0)  {
+                log_debug("Got query packet for id %u", DNS_PACKET_ID(p));
 
-                        if (scope)
-                                dns_scope_process_query(scope, NULL, p);
-                }
+                dns_scope_process_query(scope, NULL, p);
         } else
-                log_debug("Invalid LLMNR packet.");
+                log_debug("Invalid LLMNR UDP packet.");
 
         return 0;
 }
@@ -1413,29 +1409,26 @@ fail:
 }
 
 static int on_llmnr_stream_packet(DnsStream *s) {
-        assert(s);
+        DnsScope *scope;
 
-        if (dns_packet_validate_query(s->read_packet) > 0) {
-                Link *l;
+        assert(s);
 
-                l = hashmap_get(s->manager->links, INT_TO_PTR(s->read_packet->ifindex));
-                if (l) {
-                        DnsScope *scope = NULL;
+        scope = manager_find_scope(s->manager, s->read_packet);
+        if (!scope) {
+                log_warning("Got LLMNR TCP packet on unknown scope. Ignroing.");
+                return 0;
+        }
 
-                        if (s->read_packet->family == AF_INET)
-                                scope = l->llmnr_ipv4_scope;
-                        else if (s->read_packet->family == AF_INET6)
-                                scope = l->llmnr_ipv6_scope;
+        if (dns_packet_validate_query(s->read_packet) > 0) {
+                log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet));
 
-                        if (scope) {
-                                dns_scope_process_query(scope, s, s->read_packet);
+                dns_scope_process_query(scope, s, s->read_packet);
 
-                                /* If no reply packet was set, we free the stream */
-                                if (s->write_packet)
-                                        return 0;
-                        }
-                }
-        }
+                /* If no reply packet was set, we free the stream */
+                if (s->write_packet)
+                        return 0;
+        } else
+                log_debug("Invalid LLMNR TCP packet.");
 
         dns_stream_free(s);
         return 0;
@@ -1702,13 +1695,33 @@ LinkAddress* manager_find_link_address(Manager *m, int family, const union in_ad
         return NULL;
 }
 
-int manager_our_packet(Manager *m, DnsPacket *p) {
+bool manager_our_packet(Manager *m, DnsPacket *p) {
         assert(m);
         assert(p);
 
         return !!manager_find_link_address(m, p->family, &p->sender);
 }
 
+DnsScope* manager_find_scope(Manager *m, DnsPacket *p) {
+        Link *l;
+
+        assert(m);
+        assert(p);
+
+        l = hashmap_get(m->links, INT_TO_PTR(p->ifindex));
+        if (!l)
+                return NULL;
+
+        if (p->protocol == DNS_PROTOCOL_LLMNR) {
+                if (p->family == AF_INET)
+                        return l->llmnr_ipv4_scope;
+                else if (p->family == AF_INET6)
+                        return l->llmnr_ipv6_scope;
+        }
+
+        return NULL;
+}
+
 static const char* const support_table[_SUPPORT_MAX] = {
         [SUPPORT_NO] = "no",
         [SUPPORT_YES] = "yes",
index 9d824e1..f960bc2 100644 (file)
@@ -143,7 +143,8 @@ LinkAddress* manager_find_link_address(Manager *m, int family, const union in_ad
 void manager_refresh_rrs(Manager *m);
 int manager_next_hostname(Manager *m);
 
-int manager_our_packet(Manager *m, DnsPacket *p);
+bool manager_our_packet(Manager *m, DnsPacket *p);
+DnsScope* manager_find_scope(Manager *m, DnsPacket *p);
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);