mirror of
https://github.com/systemd/systemd.git
synced 2025-03-21 02:50:18 +03:00
resolved: implement full LLMNR conflict detection logic
This commit is contained in:
parent
3ef77d0476
commit
a407657425
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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) {
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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. */
|
||||
|
||||
dns_zone_item_probe_stop(i);
|
||||
i->state = DNS_ZONE_ITEM_ESTABLISHED;
|
||||
} else
|
||||
dns_zone_item_conflict(i);
|
||||
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);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
||||
dns_scope_check_conflicts(scope, p);
|
||||
|
||||
t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p)));
|
||||
if (!t)
|
||||
return 0;
|
||||
if (t)
|
||||
dns_transaction_process_reply(t, p);
|
||||
|
||||
dns_transaction_process_reply(t, p);
|
||||
} else if (dns_packet_validate_query(p) > 0) {
|
||||
log_debug("Got query packet for id %u", DNS_PACKET_ID(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;
|
||||
|
||||
if (p->family == AF_INET)
|
||||
scope = l->llmnr_ipv4_scope;
|
||||
else if (p->family == AF_INET6)
|
||||
scope = l->llmnr_ipv6_scope;
|
||||
|
||||
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,30 +1409,27 @@ fail:
|
||||
}
|
||||
|
||||
static int on_llmnr_stream_packet(DnsStream *s) {
|
||||
DnsScope *scope;
|
||||
|
||||
assert(s);
|
||||
|
||||
if (dns_packet_validate_query(s->read_packet) > 0) {
|
||||
Link *l;
|
||||
|
||||
l = hashmap_get(s->manager->links, INT_TO_PTR(s->read_packet->ifindex));
|
||||
if (l) {
|
||||
DnsScope *scope = NULL;
|
||||
|
||||
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 (scope) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
scope = manager_find_scope(s->manager, s->read_packet);
|
||||
if (!scope) {
|
||||
log_warning("Got LLMNR TCP packet on unknown scope. Ignroing.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (dns_packet_validate_query(s->read_packet) > 0) {
|
||||
log_debug("Got query packet for id %u", DNS_PACKET_ID(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;
|
||||
} 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",
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user