summaryrefslogtreecommitdiff
path: root/src/resolve
diff options
context:
space:
mode:
Diffstat (limited to 'src/resolve')
-rw-r--r--src/resolve/dns-type.c74
-rw-r--r--src/resolve/resolved-bus.c1
-rw-r--r--src/resolve/resolved-dns-dnssec.c201
-rw-r--r--src/resolve/resolved-dns-dnssec.h1
-rw-r--r--src/resolve/resolved-dns-query.c47
-rw-r--r--src/resolve/resolved-dns-query.h4
-rw-r--r--src/resolve/resolved-dns-scope.c27
-rw-r--r--src/resolve/resolved-dns-scope.h2
-rw-r--r--src/resolve/resolved-dns-transaction.c15
-rw-r--r--src/resolve/resolved-link.c21
-rw-r--r--src/resolve/resolved-link.h4
-rw-r--r--src/resolve/resolved-manager.c15
-rw-r--r--src/resolve/resolved-manager.h2
-rw-r--r--src/resolve/test-dnssec-complex.c2
14 files changed, 289 insertions, 127 deletions
diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c
index fc2f1826fd..49210b2ca9 100644
--- a/src/resolve/dns-type.c
+++ b/src/resolve/dns-type.c
@@ -240,31 +240,69 @@ int dns_class_from_string(const char *s) {
}
const char* tlsa_cert_usage_to_string(uint8_t cert_usage) {
- switch(cert_usage) {
- case 0: return "CA constraint";
- case 1: return "Service certificate constraint";
- case 2: return "Trust anchor assertion";
- case 3: return "Domain-issued certificate";
- case 4 ... 254: return "Unassigned";
- case 255: return "Private use";
+
+ switch (cert_usage) {
+
+ case 0:
+ return "CA constraint";
+
+ case 1:
+ return "Service certificate constraint";
+
+ case 2:
+ return "Trust anchor assertion";
+
+ case 3:
+ return "Domain-issued certificate";
+
+ case 4 ... 254:
+ return "Unassigned";
+
+ case 255:
+ return "Private use";
}
+
+ return NULL; /* clang cannot count that we covered everything */
}
const char* tlsa_selector_to_string(uint8_t selector) {
- switch(selector) {
- case 0: return "Full Certificate";
- case 1: return "SubjectPublicKeyInfo";
- case 2 ... 254: return "Unassigned";
- case 255: return "Private use";
+ switch (selector) {
+
+ case 0:
+ return "Full Certificate";
+
+ case 1:
+ return "SubjectPublicKeyInfo";
+
+ case 2 ... 254:
+ return "Unassigned";
+
+ case 255:
+ return "Private use";
}
+
+ return NULL;
}
const char* tlsa_matching_type_to_string(uint8_t selector) {
- switch(selector) {
- case 0: return "No hash used";
- case 1: return "SHA-256";
- case 2: return "SHA-512";
- case 3 ... 254: return "Unassigned";
- case 255: return "Private use";
+
+ switch (selector) {
+
+ case 0:
+ return "No hash used";
+
+ case 1:
+ return "SHA-256";
+
+ case 2:
+ return "SHA-512";
+
+ case 3 ... 254:
+ return "Unassigned";
+
+ case 255:
+ return "Private use";
}
+
+ return NULL;
}
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index 834ae837de..251e7a50a4 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -281,6 +281,7 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
q->request = sd_bus_message_ref(message);
q->request_family = family;
q->complete = bus_method_resolve_hostname_complete;
+ q->suppress_unroutable_family = family == AF_UNSPEC;
r = dns_query_bus_track(q, message);
if (r < 0)
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index 76c801cce8..21cf161494 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -19,7 +19,9 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#ifdef HAVE_GCRYPT
#include <gcrypt.h>
+#endif
#include "alloc-util.h"
#include "dns-domain.h"
@@ -48,19 +50,6 @@
* Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
*/
-static void initialize_libgcrypt(void) {
- const char *p;
-
- if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
- return;
-
- p = gcry_check_version("1.4.5");
- assert(p);
-
- gcry_control(GCRYCTL_DISABLE_SECMEM);
- gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
-}
-
uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
const uint8_t *p;
uint32_t sum, f;
@@ -88,6 +77,70 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
return sum & UINT32_C(0xFFFF);
}
+int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
+ size_t c = 0;
+ int r;
+
+ /* Converts the specified hostname into DNSSEC canonicalized
+ * form. */
+
+ if (buffer_max < 2)
+ return -ENOBUFS;
+
+ for (;;) {
+ r = dns_label_unescape(&n, buffer, buffer_max);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (buffer_max < (size_t) r + 2)
+ return -ENOBUFS;
+
+ /* The DNSSEC canonical form is not clear on what to
+ * do with dots appearing in labels, the way DNS-SD
+ * does it. Refuse it for now. */
+
+ if (memchr(buffer, '.', r))
+ return -EINVAL;
+
+ ascii_strlower_n(buffer, (size_t) r);
+ buffer[r] = '.';
+
+ buffer += r + 1;
+ c += r + 1;
+
+ buffer_max -= r + 1;
+ }
+
+ if (c <= 0) {
+ /* Not even a single label: this is the root domain name */
+
+ assert(buffer_max > 2);
+ buffer[0] = '.';
+ buffer[1] = 0;
+
+ return 1;
+ }
+
+ return (int) c;
+}
+
+#ifdef HAVE_GCRYPT
+
+static void initialize_libgcrypt(void) {
+ const char *p;
+
+ if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
+ return;
+
+ p = gcry_check_version("1.4.5");
+ assert(p);
+
+ gcry_control(GCRYCTL_DISABLE_SECMEM);
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+}
+
static int rr_compare(const void *a, const void *b) {
DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
size_t m;
@@ -971,55 +1024,6 @@ int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
return 0;
}
-int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
- size_t c = 0;
- int r;
-
- /* Converts the specified hostname into DNSSEC canonicalized
- * form. */
-
- if (buffer_max < 2)
- return -ENOBUFS;
-
- for (;;) {
- r = dns_label_unescape(&n, buffer, buffer_max);
- if (r < 0)
- return r;
- if (r == 0)
- break;
-
- if (buffer_max < (size_t) r + 2)
- return -ENOBUFS;
-
- /* The DNSSEC canonical form is not clear on what to
- * do with dots appearing in labels, the way DNS-SD
- * does it. Refuse it for now. */
-
- if (memchr(buffer, '.', r))
- return -EINVAL;
-
- ascii_strlower_n(buffer, (size_t) r);
- buffer[r] = '.';
-
- buffer += r + 1;
- c += r + 1;
-
- buffer_max -= r + 1;
- }
-
- if (c <= 0) {
- /* Not even a single label: this is the root domain name */
-
- assert(buffer_max > 2);
- buffer[0] = '.';
- buffer[1] = 0;
-
- return 1;
- }
-
- return (int) c;
-}
-
static int digest_to_gcrypt_md(uint8_t algorithm) {
/* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */
@@ -1882,7 +1886,7 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
return 0;
}
-int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) {
+static int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) {
DnsResourceRecord *rr;
DnsAnswerFlags flags;
int r;
@@ -2114,6 +2118,77 @@ int dnssec_test_positive_wildcard(
return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated);
}
+#else
+
+int dnssec_verify_rrset(
+ DnsAnswer *a,
+ const DnsResourceKey *key,
+ DnsResourceRecord *rrsig,
+ DnsResourceRecord *dnskey,
+ usec_t realtime,
+ DnssecResult *result) {
+
+ return -EOPNOTSUPP;
+}
+
+int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {
+
+ return -EOPNOTSUPP;
+}
+
+int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
+
+ return -EOPNOTSUPP;
+}
+
+int dnssec_verify_rrset_search(
+ DnsAnswer *a,
+ const DnsResourceKey *key,
+ DnsAnswer *validated_dnskeys,
+ usec_t realtime,
+ DnssecResult *result,
+ DnsResourceRecord **ret_rrsig) {
+
+ return -EOPNOTSUPP;
+}
+
+int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
+
+ return -EOPNOTSUPP;
+}
+
+int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
+
+ return -EOPNOTSUPP;
+}
+
+int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
+
+ return -EOPNOTSUPP;
+}
+
+int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
+
+ return -EOPNOTSUPP;
+}
+
+int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
+
+ return -EOPNOTSUPP;
+}
+
+int dnssec_test_positive_wildcard(
+ DnsAnswer *answer,
+ const char *name,
+ const char *source,
+ const char *zone,
+ bool *authenticated) {
+
+ return -EOPNOTSUPP;
+}
+
+#endif
+
static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_VALIDATED] = "validated",
[DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard",
diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h
index c99861b8e5..4542f0aa89 100644
--- a/src/resolve/resolved-dns-dnssec.h
+++ b/src/resolve/resolved-dns-dnssec.h
@@ -94,7 +94,6 @@ typedef enum DnssecNsecResult {
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl);
-int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated);
int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *source, const char *zone, bool *authenticated);
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index a00851658e..06d30d7863 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -21,6 +21,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
+#include "dns-type.h"
#include "hostname-util.h"
#include "local-addresses.h"
#include "resolved-dns-query.h"
@@ -161,6 +162,7 @@ static int dns_query_candidate_go(DnsQueryCandidate *c) {
DnsTransaction *t;
Iterator i;
int r;
+ unsigned n = 0;
assert(c);
@@ -172,8 +174,14 @@ static int dns_query_candidate_go(DnsQueryCandidate *c) {
r = dns_transaction_go(t);
if (r < 0)
return r;
+
+ n++;
}
+ /* If there was nothing to start, then let's proceed immediately */
+ if (n == 0)
+ dns_query_candidate_notify(c);
+
return 0;
}
@@ -222,6 +230,31 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) {
return state;
}
+static bool dns_query_candidate_is_routable(DnsQueryCandidate *c, uint16_t type) {
+ int family;
+
+ assert(c);
+
+ /* Checks whether the specified RR type matches an address family that is routable on the link(s) the scope of
+ * this candidate belongs to. Specifically, whether there's a routable IPv4 address on it if we query an A RR,
+ * or a routable IPv6 address if we query an AAAA RR. */
+
+ if (!c->query->suppress_unroutable_family)
+ return true;
+
+ if (c->scope->protocol != DNS_PROTOCOL_DNS)
+ return true;
+
+ family = dns_type_to_af(type);
+ if (family < 0)
+ return true;
+
+ if (c->scope->link)
+ return link_relevant(c->scope->link, family, false);
+ else
+ return manager_routable(c->scope->manager, family);
+}
+
static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
DnsQuestion *question;
DnsResourceKey *key;
@@ -236,14 +269,24 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
/* Create one transaction per question key */
DNS_QUESTION_FOREACH(key, question) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL;
+ DnsResourceKey *qkey;
+
+ if (!dns_query_candidate_is_routable(c, key->type))
+ continue;
if (c->search_domain) {
r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name);
if (r < 0)
goto fail;
- }
- r = dns_query_candidate_add_transaction(c, new_key ?: key);
+ qkey = new_key;
+ } else
+ qkey = key;
+
+ if (!dns_scope_good_key(c->scope, qkey))
+ continue;
+
+ r = dns_query_candidate_add_transaction(c, qkey);
if (r < 0)
goto fail;
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index c7e4ce9a00..75c2c14c1f 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -69,6 +69,10 @@ struct DnsQuery {
uint64_t flags;
int ifindex;
+ /* If true, A or AAAA RR lookups will be suppressed on links with no routable address of the matching address
+ * family */
+ bool suppress_unroutable_family;
+
DnsTransactionState state;
unsigned n_cname_redirects;
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index ac4887abea..03239794ee 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -490,7 +490,9 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
}
}
-int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) {
+bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) {
+ int key_family;
+
assert(s);
assert(key);
@@ -498,6 +500,9 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) {
* this scope. Note that this call assumes as fully qualified
* name, i.e. the search suffixes already appended. */
+ if (key->class != DNS_CLASS_IN)
+ return false;
+
if (s->protocol == DNS_PROTOCOL_DNS) {
/* On classic DNS, looking up non-address RRs is always
@@ -519,13 +524,11 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) {
/* On mDNS and LLMNR, send A and AAAA queries only on the
* respective scopes */
- if (s->family == AF_INET && key->class == DNS_CLASS_IN && key->type == DNS_TYPE_AAAA)
- return false;
-
- if (s->family == AF_INET6 && key->class == DNS_CLASS_IN && key->type == DNS_TYPE_A)
- return false;
+ key_family = dns_type_to_af(key->type);
+ if (key_family < 0)
+ return true;
- return true;
+ return key_family == s->family;
}
static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) {
@@ -1017,9 +1020,6 @@ bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) {
}
bool dns_scope_network_good(DnsScope *s) {
- Iterator i;
- Link *l;
-
/* Checks whether the network is in good state for lookups on this scope. For mDNS/LLMNR/Classic DNS scopes
* bound to links this is easy, as they don't even exist if the link isn't in a suitable state. For the global
* DNS scope we check whether there are any links that are up and have an address. */
@@ -1027,10 +1027,5 @@ bool dns_scope_network_good(DnsScope *s) {
if (s->link)
return true;
- HASHMAP_FOREACH(l, s->manager->links, i) {
- if (link_relevant(l, AF_UNSPEC, false))
- return true;
- }
-
- return false;
+ return manager_routable(s->manager, AF_UNSPEC);
}
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index f9b63d56d9..05b8d66de0 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -87,7 +87,7 @@ int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *add
int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port);
DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain);
-int dns_scope_good_key(DnsScope *s, DnsResourceKey *key);
+bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key);
DnsServer *dns_scope_get_dns_server(DnsScope *s);
void dns_scope_next_dns_server(DnsScope *s);
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 802ad860a4..501f13063e 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -1411,12 +1411,6 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
if (r < 0)
return r;
- r = dns_scope_good_key(t->scope, t->key);
- if (r < 0)
- return r;
- if (r == 0)
- return -EDOM;
-
r = dns_packet_append_key(p, t->key, NULL);
if (r < 0)
return r;
@@ -1498,13 +1492,6 @@ int dns_transaction_go(DnsTransaction *t) {
/* Otherwise, we need to ask the network */
r = dns_transaction_make_packet(t);
- if (r == -EDOM) {
- /* Not the right request to make on this network?
- * (i.e. an A request made on IPv6 or an AAAA request
- * made on IPv4, on LLMNR or mDNS.) */
- dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
- return 0;
- }
if (r < 0)
return r;
@@ -1538,7 +1525,7 @@ int dns_transaction_go(DnsTransaction *t) {
return 0;
}
if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(-r)) {
- /* On LLMNR, if we cannot connect to a host via TCP when doing revers lookups. This means we cannot
+ /* On LLMNR, if we cannot connect to a host via TCP when doing reverse lookups. This means we cannot
* answer this request with this protocol. */
dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND);
return 0;
diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c
index 37dd4a6e78..7412e64622 100644
--- a/src/resolve/resolved-link.c
+++ b/src/resolve/resolved-link.c
@@ -515,14 +515,17 @@ int link_update_monitor(Link *l) {
return 0;
}
-bool link_relevant(Link *l, int family, bool multicast) {
+bool link_relevant(Link *l, int family, bool local_multicast) {
_cleanup_free_ char *state = NULL;
LinkAddress *a;
assert(l);
- /* A link is relevant for multicast traffic if it isn't a loopback or pointopoint device, has a link beat, can
- * do multicast and has at least one relevant IP address */
+ /* A link is relevant for local multicast traffic if it isn't a loopback or pointopoint device, has a link
+ * beat, can do multicast and has at least one link-local (or better) IP address.
+ *
+ * A link is relevant for non-multicast traffic if it isn't a loopback device, has a link beat, and has at
+ * least one routable address.*/
if (l->flags & (IFF_LOOPBACK|IFF_DORMANT))
return false;
@@ -530,7 +533,7 @@ bool link_relevant(Link *l, int family, bool multicast) {
if ((l->flags & (IFF_UP|IFF_LOWER_UP)) != (IFF_UP|IFF_LOWER_UP))
return false;
- if (multicast) {
+ if (local_multicast) {
if (l->flags & IFF_POINTOPOINT)
return false;
@@ -548,7 +551,7 @@ bool link_relevant(Link *l, int family, bool multicast) {
return false;
LIST_FOREACH(addresses, a, l->addresses)
- if ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a))
+ if ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a, local_multicast))
return true;
return false;
@@ -692,7 +695,7 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) {
if (a->family == AF_INET) {
if (!force_remove &&
- link_address_relevant(a) &&
+ link_address_relevant(a, true) &&
a->link->llmnr_ipv4_scope &&
a->link->llmnr_support == RESOLVE_SUPPORT_YES &&
a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) {
@@ -749,7 +752,7 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) {
if (a->family == AF_INET6) {
if (!force_remove &&
- link_address_relevant(a) &&
+ link_address_relevant(a, true) &&
a->link->llmnr_ipv6_scope &&
a->link->llmnr_support == RESOLVE_SUPPORT_YES &&
a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) {
@@ -826,13 +829,13 @@ int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) {
return 0;
}
-bool link_address_relevant(LinkAddress *a) {
+bool link_address_relevant(LinkAddress *a, bool local_multicast) {
assert(a);
if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE))
return false;
- if (IN_SET(a->scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE))
+ if (a->scope >= (local_multicast ? RT_SCOPE_HOST : RT_SCOPE_LINK))
return false;
return true;
diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h
index 3b6aafb8f0..29e7b72247 100644
--- a/src/resolve/resolved-link.h
+++ b/src/resolve/resolved-link.h
@@ -89,7 +89,7 @@ int link_new(Manager *m, Link **ret, int ifindex);
Link *link_free(Link *l);
int link_update_rtnl(Link *l, sd_netlink_message *m);
int link_update_monitor(Link *l);
-bool link_relevant(Link *l, int family, bool multicast);
+bool link_relevant(Link *l, int family, bool local_multicast);
LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr);
void link_add_rrs(Link *l, bool force_remove);
@@ -107,7 +107,7 @@ bool link_dnssec_supported(Link *l);
int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr);
LinkAddress *link_address_free(LinkAddress *a);
int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m);
-bool link_address_relevant(LinkAddress *l);
+bool link_address_relevant(LinkAddress *l, bool local_multicast);
void link_address_add_rrs(LinkAddress *a, bool force_remove);
DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free);
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index fbd188c2ac..f1dbda1a6a 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -1226,3 +1226,18 @@ void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResource
m->n_dnssec_verdict[verdict]++;
}
+
+bool manager_routable(Manager *m, int family) {
+ Iterator i;
+ Link *l;
+
+ assert(m);
+
+ /* Returns true if the host has at least one interface with a routable address of the specified type */
+
+ HASHMAP_FOREACH(l, m->links, i)
+ if (link_relevant(l, family, false))
+ return true;
+
+ return false;
+}
diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h
index 1af49c8fb9..e2c539d3d2 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -169,3 +169,5 @@ DnssecMode manager_get_dnssec_mode(Manager *m);
bool manager_dnssec_supported(Manager *m);
void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key);
+
+bool manager_routable(Manager *m, int family);
diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c
index caac251e83..cde9741866 100644
--- a/src/resolve/test-dnssec-complex.c
+++ b/src/resolve/test-dnssec-complex.c
@@ -220,7 +220,7 @@ int main(int argc, char* argv[]) {
test_hostname_lookup(bus, "poettering.de", AF_INET, NULL);
test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL);
-#if HAVE_LIBIDN
+#ifdef HAVE_LIBIDN
/* Unsigned A with IDNA conversion necessary */
test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL);
test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL);