summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/resolve/resolved-dns-cache.c77
-rw-r--r--src/resolve/resolved-dns-cache.h4
-rw-r--r--src/resolve/resolved-dns-packet.c5
-rw-r--r--src/resolve/resolved-dns-packet.h2
-rw-r--r--src/resolve/resolved-dns-scope.c198
-rw-r--r--src/resolve/resolved-dns-scope.h8
-rw-r--r--src/resolve/resolved-dns-transaction.c24
-rw-r--r--src/resolve/resolved-dns-zone.c112
-rw-r--r--src/resolve/resolved-dns-zone.h3
-rw-r--r--src/resolve/resolved-manager.c89
-rw-r--r--src/resolve/resolved-manager.h3
11 files changed, 467 insertions, 58 deletions
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index 696eb9d523..733ef6348d 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -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;
+}
diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h
index d88d1d0e15..e92280c319 100644
--- a/src/resolve/resolved-dns-cache.h
+++ b/src/resolve/resolved-dns-cache.h
@@ -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);
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index 4f9503803b..0d276df8c9 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -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:
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index 26a2e7646a..6a865a2d5b 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -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) {
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index 40c326a81d..174249a9f7 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -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]);
+ }
+}
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index ae9469a39f..6ba5ef2419 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -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);
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index a2e4f2ce95..e76940e18e 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -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);
}
diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c
index ed47759106..d96ddd270c 100644
--- a/src/resolve/resolved-dns-zone.c
+++ b/src/resolve/resolved-dns-zone.c
@@ -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;
}
diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h
index bf93ab43fb..37fdafe040 100644
--- a/src/resolve/resolved-dns-zone.h
+++ b/src/resolve/resolved-dns-zone.h
@@ -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);
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index 12883952b1..a93f4a597f 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -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",
diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h
index 9d824e1751..f960bc2f1e 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -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);