1
0
mirror of https://github.com/systemd/systemd synced 2025-09-30 17:24:46 +02:00

Compare commits

...

16 Commits

Author SHA1 Message Date
Vinnie Magro
f963f8953d networkd: add UseFQDN option for DHCPv6
Similar to DHCPv4's UseHostname option, add a UseFQDN config option in
[DHCPv6] to set the system's transient hostname if the FQDN option is
set in the DHCPv6 response from the server.
2021-02-11 13:30:04 +09:00
Zbigniew Jędrzejewski-Szmek
39755e0014
Merge pull request #17823 from poettering/resolved-just-bypass
resolved: just the dnssec bypass logic
2021-02-11 00:10:25 +01:00
Lennart Poettering
43475909ce resolved: drop timestamp parameter to dns_cache_put() we don't ever pass 2021-02-09 18:02:14 +01:00
Lennart Poettering
30ee707170 resolved: avoid NOTIMP error when looking up not supported requests
Some folks argue that NOTIMP should only be returned if a specific QTYPE
is not supported. While I don#t think the RFCs are too clear about that,
let's use REFUSED instead, which appears to be a less controversial
choice of error code.

Prompted-by: #17218
2021-02-09 17:58:30 +01:00
Lennart Poettering
775ae35403 resolved: add support for answering DNSSEC questions on the stub
This substantially beefs up the local DNS stub feature set in order to
allow local clients to do DNSSEC validation through the stub.

Previously we'd return NOTIMP if we'd get a DO or DO+CD lookup. With
this change we'll instead:

1. If we get DO+CD requests (i.e. DNSSEC with no local checking) we'll
   proxy DNS queries and response mostly unmodified to/from upstream DNS
   servers if possible (this is called "bypass" mode).  We will patch in
   new request IDs, (and patch them back out on reply), so that we can
   sanely keep track of things.  We'll also maintain a minimal local
   cache for such lookups, always keeping the whole DNS packets in it
   (if we reply from cache we'll patch the TTLs of all included RRs).

2. If we get DO requests without CD (i.e. DNSSEC with local checking)
   we'll resolve and validate locally. In this mode we will not proxy
   packets, but generate our own. We will however cache the combination
   of answer RRs (along with their packet section assignments) we got
   back in the cache, and use this information to generate reply packets
   from the DNS stub.

In both cases: if we determine a lookup is to be answered from LLMNR or
mDNS we'll always revert to non-DNSSEC, non-proxy operation as before.
Answers will lack the DO bit then, since the data cannot be validated
via DNSSEC by the clients.

To make this logic more debuggable, this also adds query flags for
turning off RR sources. i.e. cache/network/zone/trust anchor/local
synthesis may now be disabled individually for each lookup.

The cache is substantially updated to make all this work: in addition to
caching simple RRs for lookup RR keys, we'll now cache the whole packets
and the whole combination of RRs, so that we can answer DO and DO+CD
replies sensibly according to the rules described above. This sounds
wasteful, but given that the
DnsResourceRecord/DnsResourceKey/DnsAnswer/DnsPacket
objects are all ref-counted and we try to merge references the actual
additional memory used should be limited (but this might be something to
optimize further later on).

To implement classic RR key lookups and new-style packet proxy lookups
(i.e. the ones necessary for DO+CD packet proxying, as described above)
DnsTransaction and DnsQuery objects now always maintain either a
DnsResourceKey/DnsQuestion as lookup key or a DnsPacket for "bypass"
mode.

Fixes: #4621 #17218
2021-02-09 17:58:25 +01:00
Lennart Poettering
d8f3836ea9 resolved: DNS_CLASS_ANY lookups are OK too 2021-02-09 17:53:10 +01:00
Lennart Poettering
6f76e68a3c resolved: if dns_packet_append_answer() fails count how many RRs were successfully added
This is useful later when handling the truncation case: if we can't add
all RRs we'd like to add we need to set the TC bit, but still report the
number of RRs we added (and not the number of RRs we would have liked to
add) to the packet.
2021-02-09 17:53:04 +01:00
Lennart Poettering
04617bf83f resolved: add RRSIG field to DnsAnswerItem 2021-02-09 17:52:59 +01:00
Lennart Poettering
fa4e74b8ff resolved: add new DnsAnswerFlags indicating originating section when parsing
Let's beef up our parser a bit: let's store in the DnsAnswerFlags field
(that is stored as part of DnsAnswerItem) which DNS packet section (i.e.
answer, authoritative, additional) an RR originates from.

This is useful when propagating answers from an upstream DNS server
eventually, as we can place the data in the right sections downstream
too.
2021-02-09 17:52:53 +01:00
Lennart Poettering
9c5fcb8ac7 resolved: replace DNS_ANSWER_FOREACH_FULL() iterator macro with DNS_ANSWER_FOREACH_ITEM()
The more fields DnsAnswerItem gains the less sense it makes to pass
every field of it as separate parameter to an iterator macro. Let's
simplify things here, in preparation of adding more fields to the
structure later on: let's just return the structure itself in the loop,
rather than the individual fields.
2021-02-09 17:52:49 +01:00
Lennart Poettering
b17b6a7401 resolved: slight modernizations of resolved-dns-answer.c functions
Let's follow our own coding style an initialized return values on all
cases of "success".
2021-02-09 17:52:43 +01:00
Lennart Poettering
7d44b1986b resolved: add dns_answer_contains() helper 2021-02-09 17:52:36 +01:00
Lennart Poettering
81b4d94df5 resolved: add logic for patching TTLs of full packets 2021-02-09 17:52:30 +01:00
Lennart Poettering
93748b2686 resolved: add logic for patching OPT max udp size of existing packet 2021-02-09 17:52:24 +01:00
Lennart Poettering
42df953247 resolved: introduce dns_transaction_key() helper for getting RR key for transaction
This is a simple search&replace excercise: instead of accessing the
"key" field of the transaction directly, let's use a small inline helper
that does this for us.

This appears pointless for now, but this will become useful later when
we introduce "bypass" transactions, that reuse the original client DNS
packet for queries instead of synthesizing a packet of our own. In that
case transactions either have regular "key" field initialized as before,
or the "bypass" packet field instead. The new dns_transaction_key()
helper allows us to hide the differences for most cases as we can later
teach it to access the "bypass" packet's question key transparently.

No change in behaviour.
2021-02-09 17:52:19 +01:00
Lennart Poettering
1a6cd0206b resolved: add helper dns_packet_dup() for duplicating packets 2021-02-09 17:51:45 +01:00
30 changed files with 1717 additions and 637 deletions

View File

@ -1926,6 +1926,13 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>UseFQDN=</varname></term>
<listitem>
<para>Equivalent of <varname>UseHostname=</varname> in the [DHCPv4] section.</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><varname>RouteMetric=</varname></term> <term><varname>RouteMetric=</varname></term>
<listitem> <listitem>

View File

@ -75,6 +75,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY),
SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN), SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SOURCE, ESRCH),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DNSSD_SERVICE, ENOENT), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DNSSD_SERVICE, ENOENT),
SD_BUS_ERROR_MAP(BUS_ERROR_DNSSD_SERVICE_EXISTS, EEXIST), SD_BUS_ERROR_MAP(BUS_ERROR_DNSSD_SERVICE_EXISTS, EEXIST),

View File

@ -73,6 +73,7 @@
#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" #define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink"
#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" #define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy"
#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown" #define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown"
#define BUS_ERROR_NO_SOURCE "org.freedesktop.resolve1.NoSource"
#define BUS_ERROR_NO_SUCH_DNSSD_SERVICE "org.freedesktop.resolve1.NoSuchDnssdService" #define BUS_ERROR_NO_SUCH_DNSSD_SERVICE "org.freedesktop.resolve1.NoSuchDnssdService"
#define BUS_ERROR_DNSSD_SERVICE_EXISTS "org.freedesktop.resolve1.DnssdServiceExists" #define BUS_ERROR_DNSSD_SERVICE_EXISTS "org.freedesktop.resolve1.DnssdServiceExists"
#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." #define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError."

View File

@ -11,6 +11,7 @@
#include "escape.h" #include "escape.h"
#include "hashmap.h" #include "hashmap.h"
#include "hostname-setup.h"
#include "hostname-util.h" #include "hostname-util.h"
#include "missing_network.h" #include "missing_network.h"
#include "networkd-address.h" #include "networkd-address.h"
@ -1073,6 +1074,25 @@ static int dhcp6_address_acquired(Link *link) {
return r; return r;
} }
if (link->network->dhcp6_use_fqdn) {
const char *dhcpname = NULL;
_cleanup_free_ char *hostname = NULL;
(void) sd_dhcp6_lease_get_fqdn(link->dhcp6_lease, &dhcpname);
if (dhcpname) {
r = shorten_overlong(dhcpname, &hostname);
if (r < 0)
log_link_warning_errno(link, r, "Unable to shorten overlong DHCP hostname '%s', ignoring: %m", dhcpname);
if (r == 1)
log_link_notice(link, "Overlong DHCP hostname received, shortened from '%s' to '%s'", dhcpname, hostname);
}
if (hostname) {
r = manager_set_hostname(link->manager, hostname);
if (r < 0)
log_link_error_errno(link, r, "Failed to set transient hostname to '%s': %m", hostname);
}
}
return 0; return 0;
} }

View File

@ -223,6 +223,7 @@ DHCPv4.RouteMTUBytes, config_parse_mtu,
DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0 DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address) DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
DHCPv6.UseDNS, config_parse_dhcp_use_dns, 0, 0 DHCPv6.UseDNS, config_parse_dhcp_use_dns, 0, 0
DHCPv6.UseFQDN, config_parse_bool, 0, offsetof(Network, dhcp6_use_fqdn)
DHCPv6.UseNTP, config_parse_dhcp_use_ntp, 0, 0 DHCPv6.UseNTP, config_parse_dhcp_use_ntp, 0, 0
DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_rapid_commit) DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_rapid_commit)
DHCPv6.MUDURL, config_parse_dhcp6_mud_url, 0, 0 DHCPv6.MUDURL, config_parse_dhcp6_mud_url, 0, 0

View File

@ -374,6 +374,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp6_use_address = true, .dhcp6_use_address = true,
.dhcp6_use_dns = true, .dhcp6_use_dns = true,
.dhcp6_use_fqdn = true,
.dhcp6_use_ntp = true, .dhcp6_use_ntp = true,
.dhcp6_rapid_commit = true, .dhcp6_rapid_commit = true,
.dhcp6_route_metric = DHCP_ROUTE_METRIC, .dhcp6_route_metric = DHCP_ROUTE_METRIC,

View File

@ -159,6 +159,7 @@ struct Network {
bool dhcp6_use_address; bool dhcp6_use_address;
bool dhcp6_use_dns; bool dhcp6_use_dns;
bool dhcp6_use_dns_set; bool dhcp6_use_dns_set;
bool dhcp6_use_fqdn;
bool dhcp6_use_ntp; bool dhcp6_use_ntp;
bool dhcp6_use_ntp_set; bool dhcp6_use_ntp_set;
bool dhcp6_rapid_commit; bool dhcp6_rapid_commit;

View File

@ -101,6 +101,9 @@ static int reply_query_state(DnsQuery *q) {
* thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */
return sd_bus_reply_method_errorf(q->bus_request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); return sd_bus_reply_method_errorf(q->bus_request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q));
case DNS_TRANSACTION_NO_SOURCE:
return sd_bus_reply_method_errorf(q->bus_request, BUS_ERROR_NO_SOURCE, "All suitable resolution sources turned off");
case DNS_TRANSACTION_RCODE_FAILURE: { case DNS_TRANSACTION_RCODE_FAILURE: {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@ -274,8 +277,8 @@ static int validate_and_mangle_flags(
* *
* 1. Checks that the interface index is either 0 (meaning *all* interfaces) or positive * 1. Checks that the interface index is either 0 (meaning *all* interfaces) or positive
* *
* 2. Only the protocols flags and the NO_CNAME flag are set, at most. Plus additional flags specific * 2. Only the protocols flags and a bunch of NO_XYZ flags are set, at most. Plus additional flags
* to our method, passed in the "ok" parameter. * specific to our method, passed in the "ok" parameter.
* *
* 3. If zero protocol flags are specified it is automatically turned into *all* protocols. This way * 3. If zero protocol flags are specified it is automatically turned into *all* protocols. This way
* clients can simply pass 0 as flags and all will work as it should. They can also use this so * clients can simply pass 0 as flags and all will work as it should. They can also use this so
@ -283,7 +286,15 @@ static int validate_and_mangle_flags(
* to mean "all supported protocols". * to mean "all supported protocols".
*/ */
if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|
SD_RESOLVED_NO_CNAME|
SD_RESOLVED_NO_VALIDATE|
SD_RESOLVED_NO_SYNTHESIZE|
SD_RESOLVED_NO_CACHE|
SD_RESOLVED_NO_ZONE|
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK|
ok))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
@ -406,7 +417,7 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
if (r < 0 && r != -EALREADY) if (r < 0 && r != -EALREADY)
return r; return r;
r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, ifindex, flags); r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, ifindex, flags);
if (r < 0) if (r < 0)
return r; return r;
@ -548,7 +559,7 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s
if (r < 0) if (r < 0)
return r; return r;
r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0) if (r < 0)
return r; return r;
@ -724,14 +735,12 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (r < 0) if (r < 0)
return r; return r;
r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); /* Setting SD_RESOLVED_CLAMP_TTL: let's request that the TTL is fixed up for locally cached entries,
* after all we return it in the wire format blob. */
r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL);
if (r < 0) if (r < 0)
return r; return r;
/* Let's request that the TTL is fixed up for locally cached entries, after all we return it in the wire format
* blob */
q->clamp_ttl = true;
q->bus_request = sd_bus_message_ref(message); q->bus_request = sd_bus_message_ref(message);
q->complete = bus_method_resolve_record_complete; q->complete = bus_method_resolve_record_complete;
@ -1088,7 +1097,7 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin
if (r < 0) if (r < 0)
return r; return r;
r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
if (r < 0) if (r < 0)
return r; return r;
@ -1258,7 +1267,7 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s
if (r < 0) if (r < 0)
return r; return r;
r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); r = dns_query_new(m, &q, question_utf8, question_idna, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0) if (r < 0)
return r; return r;

View File

@ -27,6 +27,31 @@
/* Output: Result is authenticated */ /* Output: Result is authenticated */
#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) #define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9)
/* Input: Don't DNSSEC validate request */
#define SD_RESOLVED_NO_VALIDATE (UINT64_C(1) << 10)
/* Input: Don't answer request from locally synthesized records (which includes /etc/hosts) */
#define SD_RESOLVED_NO_SYNTHESIZE (UINT64_C(1) << 11)
/* Input: Don't answer request from cache */
#define SD_RESOLVED_NO_CACHE (UINT64_C(1) << 12)
/* Input: Don't answer request from locally registered public LLMNR/mDNS RRs */
#define SD_RESOLVED_NO_ZONE (UINT64_C(1) << 13)
/* Input: Don't answer request from locally registered public LLMNR/mDNS RRs */
#define SD_RESOLVED_NO_TRUST_ANCHOR (UINT64_C(1) << 14)
/* Input: Don't go to network for this request */
#define SD_RESOLVED_NO_NETWORK (UINT64_C(1) << 15)
/* Input: Require that request is answered from a "primary" answer, i.e. not from RRs acquired as
* side-effect of a previous transaction */
#define SD_RESOLVED_REQUIRE_PRIMARY (UINT64_C(1) << 16)
/* Input: If reply is answered from cache, the TTLs will be adjusted by age of cache entry */
#define SD_RESOLVED_CLAMP_TTL (UINT64_C(1) << 17)
#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) #define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6)
#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) #define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6)
#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) #define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)

View File

@ -55,20 +55,21 @@ DnsAnswer *dns_answer_new(size_t n) {
a->n_ref = 1; a->n_ref = 1;
a->n_allocated = n; a->n_allocated = n;
a->set_items = TAKE_PTR(s); a->set_items = TAKE_PTR(s);
return a; return a;
} }
static void dns_answer_flush(DnsAnswer *a) { static void dns_answer_flush(DnsAnswer *a) {
DnsResourceRecord *rr; DnsAnswerItem *item;
if (!a) if (!a)
return; return;
a->set_items = set_free(a->set_items); a->set_items = set_free(a->set_items);
DNS_ANSWER_FOREACH(rr, a) DNS_ANSWER_FOREACH_ITEM(item, a) {
dns_resource_record_unref(rr); dns_resource_record_unref(item->rr);
dns_resource_record_unref(item->rrsig);
}
a->n_rrs = 0; a->n_rrs = 0;
} }
@ -82,7 +83,13 @@ static DnsAnswer *dns_answer_free(DnsAnswer *a) {
DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsAnswer, dns_answer, dns_answer_free); DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsAnswer, dns_answer, dns_answer_free);
static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { static int dns_answer_add_raw(
DnsAnswer *a,
DnsResourceRecord *rr,
int ifindex,
DnsAnswerFlags flags,
DnsResourceRecord *rrsig) {
int r; int r;
assert(rr); assert(rr);
@ -97,6 +104,7 @@ static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex,
.rr = rr, .rr = rr,
.ifindex = ifindex, .ifindex = ifindex,
.flags = flags, .flags = flags,
.rrsig = dns_resource_record_ref(rrsig),
}; };
r = set_put(a->set_items, &a->items[a->n_rrs]); r = set_put(a->set_items, &a->items[a->n_rrs]);
@ -112,12 +120,16 @@ static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex,
} }
static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) {
DnsResourceRecord *rr; DnsAnswerItem *item;
DnsAnswerFlags flags; int r;
int ifindex, r;
DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) { DNS_ANSWER_FOREACH_ITEM(item, source) {
r = dns_answer_add_raw(a, rr, ifindex, flags); r = dns_answer_add_raw(
a,
item->rr,
item->ifindex,
item->flags,
item->rrsig);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -125,7 +137,13 @@ static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) {
return 0; return 0;
} }
int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { int dns_answer_add(
DnsAnswer *a,
DnsResourceRecord *rr,
int ifindex,
DnsAnswerFlags flags,
DnsResourceRecord *rrsig) {
DnsAnswerItem tmp, *exist; DnsAnswerItem tmp, *exist;
assert(rr); assert(rr);
@ -152,22 +170,28 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFl
if (rr->ttl > exist->rr->ttl) { if (rr->ttl > exist->rr->ttl) {
dns_resource_record_unref(exist->rr); dns_resource_record_unref(exist->rr);
exist->rr = dns_resource_record_ref(rr); exist->rr = dns_resource_record_ref(rr);
/* Update RRSIG and RR at the same time */
if (rrsig) {
dns_resource_record_ref(rrsig);
dns_resource_record_unref(exist->rrsig);
exist->rrsig = rrsig;
}
} }
exist->flags |= flags; exist->flags |= flags;
return 0; return 0;
} }
return dns_answer_add_raw(a, rr, ifindex, flags); return dns_answer_add_raw(a, rr, ifindex, flags, rrsig);
} }
static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) {
DnsResourceRecord *rr; DnsAnswerItem *item;
DnsAnswerFlags flags; int r;
int ifindex, r;
DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { DNS_ANSWER_FOREACH_ITEM(item, b) {
r = dns_answer_add(a, rr, ifindex, flags); r = dns_answer_add(a, item->rr, item->ifindex, item->flags, item->rrsig);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -175,7 +199,13 @@ static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) {
return 0; return 0;
} }
int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { int dns_answer_add_extend(
DnsAnswer **a,
DnsResourceRecord *rr,
int ifindex,
DnsAnswerFlags flags,
DnsResourceRecord *rrsig) {
int r; int r;
assert(a); assert(a);
@ -185,7 +215,7 @@ int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, Dns
if (r < 0) if (r < 0)
return r; return r;
return dns_answer_add(*a, rr, ifindex, flags); return dns_answer_add(*a, rr, ifindex, flags, rrsig);
} }
int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex) { int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex) {
@ -211,7 +241,7 @@ int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex
soa->soa.expire = 1; soa->soa.expire = 1;
soa->soa.minimum = ttl; soa->soa.minimum = ttl;
return dns_answer_add(a, soa, ifindex, DNS_ANSWER_AUTHENTICATED); return dns_answer_add(a, soa, ifindex, DNS_ANSWER_AUTHENTICATED, NULL);
} }
int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) {
@ -249,10 +279,9 @@ int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags
int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) {
DnsResourceRecord *i; DnsResourceRecord *i;
DNS_ANSWER_FOREACH(i, a) { DNS_ANSWER_FOREACH(i, a)
if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3))
return true; return true;
}
return false; return false;
} }
@ -284,7 +313,22 @@ int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) {
return false; return false;
} }
int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { int dns_answer_contains(DnsAnswer *answer, DnsResourceRecord *rr) {
DnsResourceRecord *i;
DNS_ANSWER_FOREACH(i, answer)
if (dns_resource_record_equal(i, rr))
return true;
return false;
}
int dns_answer_find_soa(
DnsAnswer *a,
const DnsResourceKey *key,
DnsResourceRecord **ret,
DnsAnswerFlags *ret_flags) {
DnsResourceRecord *rr, *soa = NULL; DnsResourceRecord *rr, *soa = NULL;
DnsAnswerFlags rr_flags, soa_flags = 0; DnsAnswerFlags rr_flags, soa_flags = 0;
int r; int r;
@ -293,7 +337,7 @@ int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceReco
/* For a SOA record we can never find a matching SOA record */ /* For a SOA record we can never find a matching SOA record */
if (key->type == DNS_TYPE_SOA) if (key->type == DNS_TYPE_SOA)
return 0; goto not_found;
DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) {
r = dns_resource_key_match_soa(key, rr->key); r = dns_resource_key_match_soa(key, rr->key);
@ -315,17 +359,30 @@ int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceReco
} }
if (!soa) if (!soa)
return 0; goto not_found;
if (ret) if (ret)
*ret = soa; *ret = soa;
if (flags) if (ret_flags)
*flags = soa_flags; *ret_flags = soa_flags;
return 1; return 1;
not_found:
if (ret)
*ret = NULL;
if (ret_flags)
*ret_flags = 0;
return 0;
} }
int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { int dns_answer_find_cname_or_dname(
DnsAnswer *a,
const DnsResourceKey *key,
DnsResourceRecord **ret,
DnsAnswerFlags *ret_flags) {
DnsResourceRecord *rr; DnsResourceRecord *rr;
DnsAnswerFlags rr_flags; DnsAnswerFlags rr_flags;
int r; int r;
@ -343,12 +400,17 @@ int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsR
if (r > 0) { if (r > 0) {
if (ret) if (ret)
*ret = rr; *ret = rr;
if (flags) if (ret_flags)
*flags = rr_flags; *ret_flags = rr_flags;
return 1; return 1;
} }
} }
if (ret)
*ret = NULL;
if (ret_flags)
*ret_flags = 0;
return 0; return 0;
} }
@ -440,21 +502,20 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) {
if ((*a)->n_ref > 1) { if ((*a)->n_ref > 1) {
_cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL;
DnsAnswerFlags flags; DnsAnswerItem *item;
int ifindex;
copy = dns_answer_new((*a)->n_rrs); copy = dns_answer_new((*a)->n_rrs);
if (!copy) if (!copy)
return -ENOMEM; return -ENOMEM;
DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { DNS_ANSWER_FOREACH_ITEM(item, *a) {
r = dns_resource_key_equal(rr->key, key); r = dns_resource_key_equal(item->rr->key, key);
if (r < 0) if (r < 0)
return r; return r;
if (r > 0) if (r > 0)
continue; continue;
r = dns_answer_add_raw(copy, rr, ifindex, flags); r = dns_answer_add_raw(copy, item->rr, item->ifindex, item->flags, item->rrsig);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -479,6 +540,8 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) {
/* Kill this entry */ /* Kill this entry */
dns_resource_record_unref((*a)->items[i].rr); dns_resource_record_unref((*a)->items[i].rr);
dns_resource_record_unref((*a)->items[i].rrsig);
memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1));
(*a)->n_rrs--; (*a)->n_rrs--;
continue; continue;
@ -525,21 +588,20 @@ int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) {
if ((*a)->n_ref > 1) { if ((*a)->n_ref > 1) {
_cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL;
DnsAnswerFlags flags; DnsAnswerItem *item;
int ifindex;
copy = dns_answer_new((*a)->n_rrs); copy = dns_answer_new((*a)->n_rrs);
if (!copy) if (!copy)
return -ENOMEM; return -ENOMEM;
DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { DNS_ANSWER_FOREACH_ITEM(item, *a) {
r = dns_resource_record_equal(rr, rm); r = dns_resource_record_equal(item->rr, rm);
if (r < 0) if (r < 0)
return r; return r;
if (r > 0) if (r > 0)
continue; continue;
r = dns_answer_add_raw(copy, rr, ifindex, flags); r = dns_answer_add_raw(copy, item->rr, item->ifindex, item->flags, item->rrsig);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -564,6 +626,7 @@ int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) {
/* Kill this entry */ /* Kill this entry */
dns_resource_record_unref((*a)->items[i].rr); dns_resource_record_unref((*a)->items[i].rr);
dns_resource_record_unref((*a)->items[i].rrsig);
memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1));
(*a)->n_rrs--; (*a)->n_rrs--;
continue; continue;
@ -576,19 +639,24 @@ int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) {
return 1; return 1;
} }
int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { int dns_answer_copy_by_key(
DnsResourceRecord *rr_source; DnsAnswer **a,
int ifindex_source, r; DnsAnswer *source,
DnsAnswerFlags flags_source; const DnsResourceKey *key,
DnsAnswerFlags or_flags,
DnsResourceRecord *rrsig) {
DnsAnswerItem *item;
int r;
assert(a); assert(a);
assert(key); assert(key);
/* Copy all RRs matching the specified key from source into *a */ /* Copy all RRs matching the specified key from source into *a */
DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) { DNS_ANSWER_FOREACH_ITEM(item, source) {
r = dns_resource_key_equal(rr_source->key, key); r = dns_resource_key_equal(item->rr->key, key);
if (r < 0) if (r < 0)
return r; return r;
if (r == 0) if (r == 0)
@ -599,7 +667,7 @@ int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKe
if (r < 0) if (r < 0)
return r; return r;
r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); r = dns_answer_add(*a, item->rr, item->ifindex, item->flags|or_flags, item->rrsig);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -607,14 +675,19 @@ int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKe
return 0; return 0;
} }
int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) { int dns_answer_move_by_key(
DnsAnswer **to,
DnsAnswer **from,
const DnsResourceKey *key,
DnsAnswerFlags or_flags,
DnsResourceRecord *rrsig) {
int r; int r;
assert(to); assert(to);
assert(from); assert(from);
assert(key); assert(key);
r = dns_answer_copy_by_key(to, *from, key, or_flags); r = dns_answer_copy_by_key(to, *from, key, or_flags, rrsig);
if (r < 0) if (r < 0)
return r; return r;
@ -744,19 +817,17 @@ int dns_answer_reserve_or_clone(DnsAnswer **a, size_t n_free) {
* This function is not used in the code base, but is useful when debugging. Do not delete. * This function is not used in the code base, but is useful when debugging. Do not delete.
*/ */
void dns_answer_dump(DnsAnswer *answer, FILE *f) { void dns_answer_dump(DnsAnswer *answer, FILE *f) {
DnsResourceRecord *rr; DnsAnswerItem *item;
DnsAnswerFlags flags;
int ifindex;
if (!f) if (!f)
f = stdout; f = stdout;
DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { DNS_ANSWER_FOREACH_ITEM(item, answer) {
const char *t; const char *t;
fputc('\t', f); fputc('\t', f);
t = dns_resource_record_to_string(rr); t = dns_resource_record_to_string(item->rr);
if (!t) { if (!t) {
log_oom(); log_oom();
continue; continue;
@ -764,21 +835,29 @@ void dns_answer_dump(DnsAnswer *answer, FILE *f) {
fputs(t, f); fputs(t, f);
if (ifindex != 0 || flags != 0) if (item->ifindex != 0 || item->rrsig || item->flags != 0)
fputs("\t;", f); fputs("\t;", f);
if (ifindex != 0) if (item->ifindex != 0)
fprintf(f, " ifindex=%i", ifindex); fprintf(f, " ifindex=%i", item->ifindex);
if (flags & DNS_ANSWER_AUTHENTICATED) if (item->rrsig)
fputs(" rrsig", f);
if (item->flags & DNS_ANSWER_AUTHENTICATED)
fputs(" authenticated", f); fputs(" authenticated", f);
if (flags & DNS_ANSWER_CACHEABLE) if (item->flags & DNS_ANSWER_CACHEABLE)
fputs(" cacheable", f); fputs(" cacheable", f);
if (flags & DNS_ANSWER_SHARED_OWNER) if (item->flags & DNS_ANSWER_SHARED_OWNER)
fputs(" shared-owner", f); fputs(" shared-owner", f);
if (flags & DNS_ANSWER_CACHE_FLUSH) if (item->flags & DNS_ANSWER_CACHE_FLUSH)
fputs(" cache-flush", f); fputs(" cache-flush", f);
if (flags & DNS_ANSWER_GOODBYE) if (item->flags & DNS_ANSWER_GOODBYE)
fputs(" goodbye", f); fputs(" goodbye", f);
if (item->flags & DNS_ANSWER_SECTION_ANSWER)
fputs(" section-answer", f);
if (item->flags & DNS_ANSWER_SECTION_AUTHORITY)
fputs(" section-authority", f);
if (item->flags & DNS_ANSWER_SECTION_ADDITIONAL)
fputs(" section-additional", f);
fputc('\n', f); fputc('\n', f);
} }

View File

@ -8,10 +8,8 @@ typedef struct DnsAnswerItem DnsAnswerItem;
#include "resolved-dns-rr.h" #include "resolved-dns-rr.h"
#include "set.h" #include "set.h"
/* A simple array of resource records. We keep track of the /* A simple array of resource records. We keep track of the originating ifindex for each RR where that makes
* originating ifindex for each RR where that makes sense, so that we * sense, so that we can qualify A and AAAA RRs referring to a local link with the right ifindex.
* can qualify A and AAAA RRs referring to a local link with the
* right ifindex.
* *
* Note that we usually encode the empty DnsAnswer object as a simple NULL. */ * Note that we usually encode the empty DnsAnswer object as a simple NULL. */
@ -21,10 +19,14 @@ typedef enum DnsAnswerFlags {
DNS_ANSWER_SHARED_OWNER = 1 << 2, /* For mDNS: RRset may be owner by multiple peers */ DNS_ANSWER_SHARED_OWNER = 1 << 2, /* For mDNS: RRset may be owner by multiple peers */
DNS_ANSWER_CACHE_FLUSH = 1 << 3, /* For mDNS: sets cache-flush bit in the rrclass of response records */ DNS_ANSWER_CACHE_FLUSH = 1 << 3, /* For mDNS: sets cache-flush bit in the rrclass of response records */
DNS_ANSWER_GOODBYE = 1 << 4, /* For mDNS: item is subject to disappear */ DNS_ANSWER_GOODBYE = 1 << 4, /* For mDNS: item is subject to disappear */
DNS_ANSWER_SECTION_ANSWER = 1 << 5, /* When parsing: RR originates from answer section */
DNS_ANSWER_SECTION_AUTHORITY = 1 << 6, /* When parsing: RR originates from authority section */
DNS_ANSWER_SECTION_ADDITIONAL = 1 << 7, /* When parsing: RR originates from additional section */
} DnsAnswerFlags; } DnsAnswerFlags;
struct DnsAnswerItem { struct DnsAnswerItem {
DnsResourceRecord *rr; DnsResourceRecord *rr;
DnsResourceRecord *rrsig; /* Optionally, also store RRSIG RR that successfully validates this item */
int ifindex; int ifindex;
DnsAnswerFlags flags; DnsAnswerFlags flags;
}; };
@ -40,16 +42,17 @@ DnsAnswer *dns_answer_new(size_t n);
DnsAnswer *dns_answer_ref(DnsAnswer *a); DnsAnswer *dns_answer_ref(DnsAnswer *a);
DnsAnswer *dns_answer_unref(DnsAnswer *a); DnsAnswer *dns_answer_unref(DnsAnswer *a);
int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig);
int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig);
int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex); int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex);
int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags);
int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a);
int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone); int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone);
int dns_answer_contains(DnsAnswer *answer, DnsResourceRecord *rr);
int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *ret_flags);
int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *ret_flags);
int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret);
int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); int dns_answer_extend(DnsAnswer **a, DnsAnswer *b);
@ -62,8 +65,8 @@ int dns_answer_reserve_or_clone(DnsAnswer **a, size_t n_free);
int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key);
int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr);
int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags, DnsResourceRecord *rrsig);
int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags, DnsResourceRecord *rrsig);
int dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); int dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname);
@ -115,17 +118,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
#define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a) #define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a)
#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \ #define _DNS_ANSWER_FOREACH_ITEM(q, item, a) \
for (size_t UNIQ_T(i, q) = ({ \ for (size_t UNIQ_T(i, q) = ({ \
(kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ (item) = dns_answer_isempty(a) ? NULL : (a)->items; \
(ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \
(fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \
0; \ 0; \
}); \ }); \
(a) && (UNIQ_T(i, q) < (a)->n_rrs); \ UNIQ_T(i, q) < dns_answer_size(a); \
UNIQ_T(i, q)++, \ UNIQ_T(i, q)++, \
(kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ (item) = ((UNIQ_T(i, q) < dns_answer_size(a)) ? (a)->items + UNIQ_T(i, q) : NULL))
(ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0), \
(fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0))
#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a) #define DNS_ANSWER_FOREACH_ITEM(item, a) _DNS_ANSWER_FOREACH_ITEM(UNIQ, item, a)

View File

@ -34,13 +34,16 @@ enum DnsCacheItemType {
struct DnsCacheItem { struct DnsCacheItem {
DnsCacheItemType type; DnsCacheItemType type;
DnsResourceKey *key; DnsResourceKey *key; /* The key for this item, i.e. the lookup key */
DnsResourceRecord *rr; DnsResourceRecord *rr; /* The RR for this item, i.e. the lookup value for positive queries */
DnsAnswer *answer; /* The full validated answer, if this is an RRset acquired via a "primary" lookup */
DnsPacket *full_packet; /* The full packet this information was acquired with */
int rcode; int rcode;
usec_t until; usec_t until;
bool authenticated:1; bool authenticated:1;
bool shared_owner:1; bool shared_owner:1;
DnssecResult dnssec_result;
int ifindex; int ifindex;
int owner_family; int owner_family;
@ -50,6 +53,12 @@ struct DnsCacheItem {
LIST_FIELDS(DnsCacheItem, by_key); LIST_FIELDS(DnsCacheItem, by_key);
}; };
/* Returns true if this is a cache item created as result of an explicit lookup, or created as "side-effect"
* of another request. "Primary" entries will carry the full answer data (with NSEC, ) that can aso prove
* wildcard expansion, non-existance and such, while entries that were created as "side-effect" just contain
* immediate RR data for the specified RR key, but nothing else. */
#define DNS_CACHE_ITEM_IS_PRIMARY(item) (!!(item)->answer)
static const char *dns_cache_item_type_to_string(DnsCacheItem *item) { static const char *dns_cache_item_type_to_string(DnsCacheItem *item) {
assert(item); assert(item);
@ -77,6 +86,8 @@ static void dns_cache_item_free(DnsCacheItem *i) {
dns_resource_record_unref(i->rr); dns_resource_record_unref(i->rr);
dns_resource_key_unref(i->key); dns_resource_key_unref(i->key);
dns_answer_unref(i->answer);
dns_packet_unref(i->full_packet);
free(i); free(i);
} }
@ -340,8 +351,11 @@ static void dns_cache_item_update_positive(
DnsCache *c, DnsCache *c,
DnsCacheItem *i, DnsCacheItem *i,
DnsResourceRecord *rr, DnsResourceRecord *rr,
DnsAnswer *answer,
DnsPacket *full_packet,
bool authenticated, bool authenticated,
bool shared_owner, bool shared_owner,
DnssecResult dnssec_result,
usec_t timestamp, usec_t timestamp,
int ifindex, int ifindex,
int owner_family, int owner_family,
@ -367,9 +381,18 @@ static void dns_cache_item_update_positive(
dns_resource_key_unref(i->key); dns_resource_key_unref(i->key);
i->key = dns_resource_key_ref(rr->key); i->key = dns_resource_key_ref(rr->key);
i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); dns_answer_ref(answer);
dns_answer_unref(i->answer);
i->answer = answer;
dns_packet_ref(full_packet);
dns_packet_unref(i->full_packet);
i->full_packet = full_packet;
i->until = calculate_until(rr, UINT32_MAX, timestamp, false);
i->authenticated = authenticated; i->authenticated = authenticated;
i->shared_owner = shared_owner; i->shared_owner = shared_owner;
i->dnssec_result = dnssec_result;
i->ifindex = ifindex; i->ifindex = ifindex;
@ -382,8 +405,11 @@ static void dns_cache_item_update_positive(
static int dns_cache_put_positive( static int dns_cache_put_positive(
DnsCache *c, DnsCache *c,
DnsResourceRecord *rr, DnsResourceRecord *rr,
DnsAnswer *answer,
DnsPacket *full_packet,
bool authenticated, bool authenticated,
bool shared_owner, bool shared_owner,
DnssecResult dnssec_result,
usec_t timestamp, usec_t timestamp,
int ifindex, int ifindex,
int owner_family, int owner_family,
@ -420,8 +446,11 @@ static int dns_cache_put_positive(
c, c,
existing, existing,
rr, rr,
answer,
full_packet,
authenticated, authenticated,
shared_owner, shared_owner,
dnssec_result,
timestamp, timestamp,
ifindex, ifindex,
owner_family, owner_family,
@ -444,9 +473,12 @@ static int dns_cache_put_positive(
.type = DNS_CACHE_POSITIVE, .type = DNS_CACHE_POSITIVE,
.key = dns_resource_key_ref(rr->key), .key = dns_resource_key_ref(rr->key),
.rr = dns_resource_record_ref(rr), .rr = dns_resource_record_ref(rr),
.answer = dns_answer_ref(answer),
.full_packet = dns_packet_ref(full_packet),
.until = calculate_until(rr, (uint32_t) -1, timestamp, false), .until = calculate_until(rr, (uint32_t) -1, timestamp, false),
.authenticated = authenticated, .authenticated = authenticated,
.shared_owner = shared_owner, .shared_owner = shared_owner,
.dnssec_result = dnssec_result,
.ifindex = ifindex, .ifindex = ifindex,
.owner_family = owner_family, .owner_family = owner_family,
.owner_address = *owner_address, .owner_address = *owner_address,
@ -481,7 +513,10 @@ static int dns_cache_put_negative(
DnsCache *c, DnsCache *c,
DnsResourceKey *key, DnsResourceKey *key,
int rcode, int rcode,
DnsAnswer *answer,
DnsPacket *full_packet,
bool authenticated, bool authenticated,
DnssecResult dnssec_result,
uint32_t nsec_ttl, uint32_t nsec_ttl,
usec_t timestamp, usec_t timestamp,
DnsResourceRecord *soa, DnsResourceRecord *soa,
@ -532,10 +567,13 @@ static int dns_cache_put_negative(
rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA :
rcode == DNS_RCODE_NXDOMAIN ? DNS_CACHE_NXDOMAIN : DNS_CACHE_RCODE, rcode == DNS_RCODE_NXDOMAIN ? DNS_CACHE_NXDOMAIN : DNS_CACHE_RCODE,
.authenticated = authenticated, .authenticated = authenticated,
.dnssec_result = dnssec_result,
.owner_family = owner_family, .owner_family = owner_family,
.owner_address = *owner_address, .owner_address = *owner_address,
.prioq_idx = PRIOQ_IDX_NULL, .prioq_idx = PRIOQ_IDX_NULL,
.rcode = rcode, .rcode = rcode,
.answer = dns_answer_ref(answer),
.full_packet = dns_packet_ref(full_packet),
}; };
i->until = i->until =
@ -630,17 +668,20 @@ int dns_cache_put(
DnsResourceKey *key, DnsResourceKey *key,
int rcode, int rcode,
DnsAnswer *answer, DnsAnswer *answer,
DnsPacket *full_packet,
bool authenticated, bool authenticated,
DnssecResult dnssec_result,
uint32_t nsec_ttl, uint32_t nsec_ttl,
usec_t timestamp,
int owner_family, int owner_family,
const union in_addr_union *owner_address) { const union in_addr_union *owner_address) {
DnsResourceRecord *soa = NULL, *rr; DnsResourceRecord *soa = NULL;
bool weird_rcode = false; bool weird_rcode = false;
DnsAnswerItem *item;
DnsAnswerFlags flags; DnsAnswerFlags flags;
unsigned cache_keys; unsigned cache_keys;
int r, ifindex; usec_t timestamp;
int r;
assert(c); assert(c);
assert(owner_address); assert(owner_address);
@ -660,6 +701,7 @@ int dns_cache_put(
log_debug("Not caching negative entry without a SOA record: %s", log_debug("Not caching negative entry without a SOA record: %s",
dns_resource_key_to_string(key, key_str, sizeof key_str)); dns_resource_key_to_string(key, key_str, sizeof key_str));
} }
return 0; return 0;
} }
@ -679,23 +721,53 @@ int dns_cache_put(
/* Make some space for our new entries */ /* Make some space for our new entries */
dns_cache_make_space(c, cache_keys); dns_cache_make_space(c, cache_keys);
if (timestamp <= 0)
timestamp = now(clock_boottime_or_monotonic()); timestamp = now(clock_boottime_or_monotonic());
/* Second, add in positive entries for all contained RRs */ /* Second, add in positive entries for all contained RRs */
DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { DNS_ANSWER_FOREACH_ITEM(item, answer) {
if ((flags & DNS_ANSWER_CACHEABLE) == 0 || int primary = false;
!rr_eligible(rr))
if (!FLAGS_SET(item->flags, DNS_ANSWER_CACHEABLE) ||
!rr_eligible(item->rr))
continue; continue;
if (key) {
/* We store the auxiliary RRs and packet data in the cache only if they were in
* direct response to the original query. If we cache an RR we also received, and
* that is just auxiliary information we can't use the data, hence don't. */
primary = dns_resource_key_match_rr(key, item->rr, NULL);
if (primary < 0)
return primary;
if (primary == 0) {
primary = dns_resource_key_match_cname_or_dname(key, item->rr->key, NULL);
if (primary < 0)
return primary;
}
}
if (!primary) {
DnsCacheItem *first;
/* Do not replace existing cache items for primary lookups with non-primary
* data. After all the primary lookup data is a lot more useful. */
first = hashmap_get(c->by_key, item->rr->key);
if (first && DNS_CACHE_ITEM_IS_PRIMARY(first))
return 0;
}
r = dns_cache_put_positive( r = dns_cache_put_positive(
c, c,
rr, item->rr,
flags & DNS_ANSWER_AUTHENTICATED, primary ? answer : NULL,
flags & DNS_ANSWER_SHARED_OWNER, primary ? full_packet : NULL,
item->flags & DNS_ANSWER_AUTHENTICATED,
item->flags & DNS_ANSWER_SHARED_OWNER,
dnssec_result,
timestamp, timestamp,
ifindex, item->ifindex,
owner_family, owner_address); owner_family,
owner_address);
if (r < 0) if (r < 0)
goto fail; goto fail;
} }
@ -745,7 +817,10 @@ int dns_cache_put(
c, c,
key, key,
rcode, rcode,
answer,
full_packet,
authenticated, authenticated,
dnssec_result,
nsec_ttl, nsec_ttl,
timestamp, timestamp,
soa, soa,
@ -762,11 +837,11 @@ fail:
if (key) if (key)
dns_cache_remove_by_key(c, key); dns_cache_remove_by_key(c, key);
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { DNS_ANSWER_FOREACH_ITEM(item, answer) {
if ((flags & DNS_ANSWER_CACHEABLE) == 0) if ((item->flags & DNS_ANSWER_CACHEABLE) == 0)
continue; continue;
dns_cache_remove_by_key(c, rr->key); dns_cache_remove_by_key(c, item->rr->key);
} }
return r; return r;
@ -827,7 +902,59 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
return NULL; return NULL;
} }
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **ret, bool *authenticated) { static int answer_add_clamp_ttl(
DnsAnswer **answer,
DnsResourceRecord *rr,
int ifindex,
DnsAnswerFlags answer_flags,
DnsResourceRecord *rrsig,
uint64_t query_flags,
usec_t until,
usec_t current) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *patched = NULL, *patched_rrsig = NULL;
int r;
assert(answer);
assert(rr);
if (FLAGS_SET(query_flags, SD_RESOLVED_CLAMP_TTL)) {
patched = dns_resource_record_ref(rr);
r = dns_resource_record_clamp_ttl(&patched, LESS_BY(until, current) / USEC_PER_SEC);
if (r < 0)
return r;
rr = patched;
if (rrsig) {
patched_rrsig = dns_resource_record_ref(rrsig);
r = dns_resource_record_clamp_ttl(&patched_rrsig, LESS_BY(until, current) / USEC_PER_SEC);
if (r < 0)
return r;
rrsig = patched_rrsig;
}
}
r = dns_answer_add_extend(answer, rr, ifindex, answer_flags, rrsig);
if (r < 0)
return r;
return 0;
}
int dns_cache_lookup(
DnsCache *c,
DnsResourceKey *key,
uint64_t query_flags,
int *ret_rcode,
DnsAnswer **ret_answer,
DnsPacket **ret_full_packet,
bool *ret_authenticated,
DnssecResult *ret_dnssec_result) {
_cleanup_(dns_packet_unrefp) DnsPacket *full_packet = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
char key_str[DNS_RESOURCE_KEY_STRING_MAX]; char key_str[DNS_RESOURCE_KEY_STRING_MAX];
unsigned n = 0; unsigned n = 0;
@ -837,27 +964,19 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod
bool have_authenticated = false, have_non_authenticated = false; bool have_authenticated = false, have_non_authenticated = false;
usec_t current; usec_t current;
int found_rcode = -1; int found_rcode = -1;
DnssecResult dnssec_result = -1;
int have_dnssec_result = -1;
assert(c); assert(c);
assert(key); assert(key);
assert(rcode);
assert(ret);
assert(authenticated);
if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
/* If we have ANY lookups we don't use the cache, so /* If we have ANY lookups we don't use the cache, so that the caller refreshes via the
* that the caller refreshes via the network. */ * network. */
log_debug("Ignoring cache for ANY lookup: %s", log_debug("Ignoring cache for ANY lookup: %s",
dns_resource_key_to_string(key, key_str, sizeof key_str)); dns_resource_key_to_string(key, key_str, sizeof key_str));
goto miss;
c->n_miss++;
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
*authenticated = false;
return 0;
} }
first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key);
@ -866,31 +985,80 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod
log_debug("Cache miss for %s", log_debug("Cache miss for %s",
dns_resource_key_to_string(key, key_str, sizeof key_str)); dns_resource_key_to_string(key, key_str, sizeof key_str));
goto miss;
c->n_miss++;
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
*authenticated = false;
return 0;
} }
if (FLAGS_SET(query_flags, SD_RESOLVED_CLAMP_TTL))
current = now(clock_boottime_or_monotonic());
LIST_FOREACH(by_key, j, first) { LIST_FOREACH(by_key, j, first) {
if (j->rr) { /* If the caller doesn't allow us to answer questions from cache data learned from
* "side-effect", skip this entry. */
if (FLAGS_SET(query_flags, SD_RESOLVED_REQUIRE_PRIMARY) &&
!DNS_CACHE_ITEM_IS_PRIMARY(j)) {
log_debug("Primary answer was requested for cache lookup for %s, which we don't have.",
dns_resource_key_to_string(key, key_str, sizeof key_str));
goto miss;
}
if (j->type == DNS_CACHE_NXDOMAIN)
nxdomain = true;
else if (j->type == DNS_CACHE_RCODE)
found_rcode = j->rcode;
else if (j->rr) {
if (j->rr->key->type == DNS_TYPE_NSEC) if (j->rr->key->type == DNS_TYPE_NSEC)
nsec = j; nsec = j;
n++; n++;
} else if (j->type == DNS_CACHE_NXDOMAIN) }
nxdomain = true;
else if (j->type == DNS_CACHE_RCODE)
found_rcode = j->rcode;
if (j->authenticated) if (j->authenticated)
have_authenticated = true; have_authenticated = true;
else else
have_non_authenticated = true; have_non_authenticated = true;
if (j->dnssec_result < 0) {
have_dnssec_result = false; /* an entry without dnssec result? then invalidate things for good */
dnssec_result = _DNSSEC_RESULT_INVALID;
} else if (have_dnssec_result < 0) {
have_dnssec_result = true; /* So far no result seen, let's pick this one up */
dnssec_result = j->dnssec_result;
} else if (have_dnssec_result > 0 && j->dnssec_result != dnssec_result) {
have_dnssec_result = false; /* conflicting result seen? then invalidate for good */
dnssec_result = _DNSSEC_RESULT_INVALID;
}
/* Append the answer RRs to our answer. Ideally we have the answer object, which we
* preferably use. But if the cached entry was generated as "side-effect" of a reply,
* i.e. from validated auxiliary records rather than from the main reply, then we use the
* individual RRs only instead. */
if (j->answer) {
/* Minor optimization, if the full answer object of this and the previous RR is the
* same, don't bother adding it again. Typically we store a full RRset here, hence
* that should be the case. */
if (!j->by_key_prev || j->answer != j->by_key_prev->answer) {
DnsAnswerItem *item;
DNS_ANSWER_FOREACH_ITEM(item, j->answer) {
r = answer_add_clamp_ttl(&answer, item->rr, item->ifindex, item->flags, item->rrsig, query_flags, j->until, current);
if (r < 0)
return r;
}
}
} else if (j->rr) {
r = answer_add_clamp_ttl(&answer, j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0, NULL, query_flags, j->until, current);
if (r < 0)
return r;
}
/* We'll return any packet we have for this. Typically all cache entries for the same key
* should come from the same packet anyway, hence it doesn't really matter which packet we
* return here, they should all resolve to the same anyway. */
if (!full_packet && j->full_packet)
full_packet = dns_packet_ref(j->full_packet);
} }
if (found_rcode >= 0) { if (found_rcode >= 0) {
@ -898,28 +1066,41 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod
dns_rcode_to_string(found_rcode), dns_rcode_to_string(found_rcode),
dns_resource_key_to_string(key, key_str, sizeof(key_str))); dns_resource_key_to_string(key, key_str, sizeof(key_str)));
*ret = NULL; if (ret_rcode)
*rcode = found_rcode; *ret_rcode = found_rcode;
*authenticated = false; if (ret_answer)
*ret_answer = TAKE_PTR(answer);
if (ret_full_packet)
*ret_full_packet = TAKE_PTR(full_packet);
if (ret_authenticated)
*ret_authenticated = false;
if (ret_dnssec_result)
*ret_dnssec_result = dnssec_result;
c->n_hit++; c->n_hit++;
return 1; return 1;
} }
if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) {
/* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC
* the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ * RRs from the lower-zone of a zone cut, but the DS RRs are on the upper zone. */
log_debug("NSEC NODATA cache hit for %s", log_debug("NSEC NODATA cache hit for %s",
dns_resource_key_to_string(key, key_str, sizeof key_str)); dns_resource_key_to_string(key, key_str, sizeof key_str));
/* We only found an NSEC record that matches our name. /* We only found an NSEC record that matches our name. If it says the type doesn't exist
* If it says the type doesn't exist report * report NODATA. Otherwise report a cache miss. */
* NODATA. Otherwise report a cache miss. */
*ret = NULL; if (ret_rcode)
*rcode = DNS_RCODE_SUCCESS; *ret_rcode = DNS_RCODE_SUCCESS;
*authenticated = nsec->authenticated; if (ret_answer)
*ret_answer = TAKE_PTR(answer);
if (ret_full_packet)
*ret_full_packet = TAKE_PTR(full_packet);
if (ret_authenticated)
*ret_authenticated = nsec->authenticated;
if (ret_dnssec_result)
*ret_dnssec_result = nsec->dnssec_result;
if (!bitmap_isset(nsec->rr->nsec.types, key->type) && if (!bitmap_isset(nsec->rr->nsec.types, key->type) &&
!bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) &&
@ -940,46 +1121,49 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod
if (n <= 0) { if (n <= 0) {
c->n_hit++; c->n_hit++;
*ret = NULL; if (ret_rcode)
*rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; *ret_rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
*authenticated = have_authenticated && !have_non_authenticated; if (ret_answer)
*ret_answer = TAKE_PTR(answer);
if (ret_full_packet)
*ret_full_packet = TAKE_PTR(full_packet);
if (ret_authenticated)
*ret_authenticated = have_authenticated && !have_non_authenticated;
if (ret_dnssec_result)
*ret_dnssec_result = dnssec_result;
return 1; return 1;
} }
answer = dns_answer_new(n);
if (!answer)
return -ENOMEM;
if (clamp_ttl)
current = now(clock_boottime_or_monotonic());
LIST_FOREACH(by_key, j, first) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
if (!j->rr)
continue;
if (clamp_ttl) {
rr = dns_resource_record_ref(j->rr);
r = dns_resource_record_clamp_ttl(&rr, LESS_BY(j->until, current) / USEC_PER_SEC);
if (r < 0)
return r;
}
r = dns_answer_add(answer, rr ?: j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0);
if (r < 0)
return r;
}
c->n_hit++; c->n_hit++;
*ret = answer; if (ret_rcode)
*rcode = DNS_RCODE_SUCCESS; *ret_rcode = DNS_RCODE_SUCCESS;
*authenticated = have_authenticated && !have_non_authenticated; if (ret_answer)
answer = NULL; *ret_answer = TAKE_PTR(answer);
if (ret_full_packet)
*ret_full_packet = TAKE_PTR(full_packet);
if (ret_authenticated)
*ret_authenticated = have_authenticated && !have_non_authenticated;
if (ret_dnssec_result)
*ret_dnssec_result = dnssec_result;
return n; return n;
miss:
if (ret_rcode)
*ret_rcode = DNS_RCODE_SUCCESS;
if (ret_answer)
*ret_answer = NULL;
if (ret_full_packet)
*ret_full_packet = NULL;
if (ret_authenticated)
*ret_authenticated = false;
if (ret_dnssec_result)
*ret_dnssec_result = _DNSSEC_RESULT_INVALID;
c->n_miss++;
return 0;
} }
int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {

View File

@ -5,6 +5,7 @@
#include "list.h" #include "list.h"
#include "prioq.h" #include "prioq.h"
#include "resolve-util.h" #include "resolve-util.h"
#include "resolved-dns-dnssec.h"
#include "time-util.h" #include "time-util.h"
typedef struct DnsCache { typedef struct DnsCache {
@ -22,8 +23,28 @@ typedef struct DnsCache {
void dns_cache_flush(DnsCache *c); void dns_cache_flush(DnsCache *c);
void dns_cache_prune(DnsCache *c); void dns_cache_prune(DnsCache *c);
int dns_cache_put(DnsCache *c, DnsCacheMode cache_mode, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); int dns_cache_put(
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **answer, bool *authenticated); DnsCache *c,
DnsCacheMode cache_mode,
DnsResourceKey *key,
int rcode,
DnsAnswer *answer,
DnsPacket *full_packet,
bool authenticated,
DnssecResult dnssec_result,
uint32_t nsec_ttl,
int owner_family,
const union in_addr_union *owner_address);
int dns_cache_lookup(
DnsCache *c,
DnsResourceKey *key,
uint64_t query_flags,
int *ret_rcode,
DnsAnswer **ret_answer,
DnsPacket **ret_full_packet,
bool *ret_authenticated,
DnssecResult *ret_dnssec_result);
int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address);

View File

@ -160,6 +160,38 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t min_alloc
return 0; return 0;
} }
int dns_packet_dup(DnsPacket **ret, DnsPacket *p) {
DnsPacket *c;
int r;
assert(ret);
assert(p);
r = dns_packet_validate(p);
if (r < 0)
return r;
c = malloc(ALIGN(sizeof(DnsPacket)) + p->size);
if (!c)
return -ENOMEM;
*c = (DnsPacket) {
.n_ref = 1,
.protocol = p->protocol,
.size = p->size,
.rindex = DNS_PACKET_HEADER_SIZE,
.allocated = p->size,
.max_size = p->max_size,
.opt_start = (size_t) -1,
.opt_size = (size_t) -1,
};
memcpy(DNS_PACKET_DATA(c), DNS_PACKET_DATA(p), p->size);
*ret = c;
return 0;
}
DnsPacket *dns_packet_ref(DnsPacket *p) { DnsPacket *dns_packet_ref(DnsPacket *p) {
if (!p) if (!p)
@ -1183,7 +1215,7 @@ int dns_packet_append_question(DnsPacket *p, DnsQuestion *q) {
return 0; return 0;
} }
int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a) { int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a, unsigned *completed) {
DnsResourceRecord *rr; DnsResourceRecord *rr;
DnsAnswerFlags flags; DnsAnswerFlags flags;
int r; int r;
@ -1194,6 +1226,9 @@ int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a) {
r = dns_packet_append_rr(p, rr, flags, NULL, NULL); r = dns_packet_append_rr(p, rr, flags, NULL, NULL);
if (r < 0) if (r < 0)
return r; return r;
if (completed)
(*completed)++;
} }
return 0; return 0;
@ -1298,6 +1333,7 @@ int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) {
if (r < 0) if (r < 0)
return r; return r;
if (ret)
*ret = unaligned_read_be16(d); *ret = unaligned_read_be16(d);
return 0; return 0;
@ -1384,19 +1420,19 @@ int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, siz
int dns_packet_read_name( int dns_packet_read_name(
DnsPacket *p, DnsPacket *p,
char **_ret, char **ret,
bool allow_compression, bool allow_compression,
size_t *start) { size_t *ret_start) {
_cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
size_t after_rindex = 0, jump_barrier; size_t after_rindex = 0, jump_barrier;
_cleanup_free_ char *ret = NULL; _cleanup_free_ char *name = NULL;
size_t n = 0, allocated = 0; size_t n = 0, allocated = 0;
bool first = true; bool first = true;
int r; int r;
assert(p); assert(p);
assert(_ret);
INIT_REWINDER(rewinder, p); INIT_REWINDER(rewinder, p);
jump_barrier = p->rindex; jump_barrier = p->rindex;
@ -1421,15 +1457,15 @@ int dns_packet_read_name(
if (r < 0) if (r < 0)
return r; return r;
if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) if (!GREEDY_REALLOC(name, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
return -ENOMEM; return -ENOMEM;
if (first) if (first)
first = false; first = false;
else else
ret[n++] = '.'; name[n++] = '.';
r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX);
if (r < 0) if (r < 0)
return r; return r;
@ -1457,18 +1493,19 @@ int dns_packet_read_name(
return -EBADMSG; return -EBADMSG;
} }
if (!GREEDY_REALLOC(ret, allocated, n + 1)) if (!GREEDY_REALLOC(name, allocated, n + 1))
return -ENOMEM; return -ENOMEM;
ret[n] = 0; name[n] = 0;
if (after_rindex != 0) if (after_rindex != 0)
p->rindex= after_rindex; p->rindex= after_rindex;
*_ret = TAKE_PTR(ret); if (ret)
*ret = TAKE_PTR(name);
if (ret_start)
*ret_start = rewinder.saved_rindex;
if (start)
*start = rewinder.saved_rindex;
CANCEL_REWINDER(rewinder); CANCEL_REWINDER(rewinder);
return 0; return 0;
@ -1570,16 +1607,19 @@ static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t siz
return 0; return 0;
} }
int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { int dns_packet_read_key(
DnsPacket *p,
DnsResourceKey **ret,
bool *ret_cache_flush,
size_t *ret_start) {
_cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
_cleanup_free_ char *name = NULL; _cleanup_free_ char *name = NULL;
bool cache_flush = false; bool cache_flush = false;
uint16_t class, type; uint16_t class, type;
DnsResourceKey *key;
int r; int r;
assert(p); assert(p);
assert(ret);
INIT_REWINDER(rewinder, p); INIT_REWINDER(rewinder, p);
r = dns_packet_read_name(p, &name, true, NULL); r = dns_packet_read_name(p, &name, true, NULL);
@ -1603,19 +1643,23 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flus
} }
} }
if (ret) {
DnsResourceKey *key;
key = dns_resource_key_new_consume(class, type, name); key = dns_resource_key_new_consume(class, type, name);
if (!key) if (!key)
return -ENOMEM; return -ENOMEM;
name = NULL; TAKE_PTR(name);
*ret = key; *ret = key;
}
if (ret_cache_flush) if (ret_cache_flush)
*ret_cache_flush = cache_flush; *ret_cache_flush = cache_flush;
if (start) if (ret_start)
*start = rewinder.saved_rindex; *ret_start = rewinder.saved_rindex;
CANCEL_REWINDER(rewinder);
CANCEL_REWINDER(rewinder);
return 0; return 0;
} }
@ -1625,7 +1669,12 @@ static bool loc_size_ok(uint8_t size) {
return m <= 9 && e <= 9 && (m > 0 || e == 0); return m <= 9 && e <= 9 && (m > 0 || e == 0);
} }
int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { int dns_packet_read_rr(
DnsPacket *p,
DnsResourceRecord **ret,
bool *ret_cache_flush,
size_t *ret_start) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
_cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
@ -1635,7 +1684,6 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
int r; int r;
assert(p); assert(p);
assert(ret);
INIT_REWINDER(rewinder, p); INIT_REWINDER(rewinder, p);
@ -2076,14 +2124,14 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
if (p->rindex != offset + rdlength) if (p->rindex != offset + rdlength)
return -EBADMSG; return -EBADMSG;
if (ret)
*ret = TAKE_PTR(rr); *ret = TAKE_PTR(rr);
if (ret_cache_flush) if (ret_cache_flush)
*ret_cache_flush = cache_flush; *ret_cache_flush = cache_flush;
if (start) if (ret_start)
*start = rewinder.saved_rindex; *ret_start = rewinder.saved_rindex;
CANCEL_REWINDER(rewinder);
CANCEL_REWINDER(rewinder);
return 0; return 0;
} }
@ -2203,8 +2251,9 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) {
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
bool cache_flush = false; bool cache_flush = false;
size_t start;
r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); r = dns_packet_read_rr(p, &rr, &cache_flush, &start);
if (r < 0) if (r < 0)
return r; return r;
@ -2272,15 +2321,26 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) {
} }
p->opt = dns_resource_record_ref(rr); p->opt = dns_resource_record_ref(rr);
p->opt_start = start;
assert(p->rindex >= start);
p->opt_size = p->rindex - start;
} else { } else {
/* According to RFC 4795, section 2.9. only the RRs from the Answer section DnsAnswerFlags flags = 0;
* shall be cached. Hence mark only those RRs as cacheable by default, but
* not the ones from the Additional or Authority sections. */
DnsAnswerFlags flags =
(i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) |
(p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0);
r = dns_answer_add(answer, rr, p->ifindex, flags); if (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush)
flags |= DNS_ANSWER_SHARED_OWNER;
/* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be
* cached. Hence mark only those RRs as cacheable by default, but not the ones from
* the Additional or Authority sections. */
if (i < DNS_PACKET_ANCOUNT(p))
flags |= DNS_ANSWER_CACHEABLE|DNS_ANSWER_SECTION_ANSWER;
else if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p))
flags |= DNS_ANSWER_SECTION_AUTHORITY;
else
flags |= DNS_ANSWER_SECTION_ADDITIONAL;
r = dns_answer_add(answer, rr, p->ifindex, flags, NULL);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -2356,6 +2416,106 @@ int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) {
return dns_resource_key_equal(p->question->keys[0], key); return dns_resource_key_equal(p->question->keys[0], key);
} }
int dns_packet_patch_max_udp_size(DnsPacket *p, uint16_t max_udp_size) {
assert(p);
assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX);
if (p->opt_start == (size_t) -1) /* No OPT section, nothing to patch */
return 0;
assert(p->opt_size != (size_t) -1);
assert(p->opt_size >= 5);
unaligned_write_be16(DNS_PACKET_DATA(p) + p->opt_start + 3, max_udp_size);
return 1;
}
static int patch_rr(DnsPacket *p, usec_t age) {
_cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
size_t ttl_index;
uint32_t ttl;
uint16_t type, rdlength;
int r;
INIT_REWINDER(rewinder, p);
/* Patches the RR at the current rindex, substracts the specified time from the TTL */
r = dns_packet_read_name(p, NULL, true, NULL);
if (r < 0)
return r;
r = dns_packet_read_uint16(p, &type, NULL);
if (r < 0)
return r;
r = dns_packet_read_uint16(p, NULL, NULL);
if (r < 0)
return r;
r = dns_packet_read_uint32(p, &ttl, &ttl_index);
if (r < 0)
return r;
if (type != DNS_TYPE_OPT) { /* The TTL of the OPT field is not actually a TTL, skip it */
ttl = LESS_BY(ttl * USEC_PER_SEC, age) / USEC_PER_SEC;
unaligned_write_be32(DNS_PACKET_DATA(p) + ttl_index, ttl);
}
r = dns_packet_read_uint16(p, &rdlength, NULL);
if (r < 0)
return r;
r = dns_packet_read(p, rdlength, NULL, NULL);
if (r < 0)
return r;
CANCEL_REWINDER(rewinder);
return 0;
}
int dns_packet_patch_ttls(DnsPacket *p, usec_t timestamp) {
_cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {};
unsigned i, n;
usec_t k;
int r;
assert(p);
assert(timestamp_is_set(timestamp));
/* Adjusts all TTLs in the packet by subtracting the time difference between now and the specified timestamp */
k = now(clock_boottime_or_monotonic());
assert(k >= timestamp);
k -= timestamp;
INIT_REWINDER(rewinder, p);
dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE);
n = DNS_PACKET_QDCOUNT(p);
for (i = 0; i < n; i++) {
r = dns_packet_read_key(p, NULL, NULL, NULL);
if (r < 0)
return r;
}
n = DNS_PACKET_RRCOUNT(p);
for (i = 0; i < n; i++) {
/* DNS servers suck, hence the RR count is in many servers off. If we reached the end
* prematurely, accept that, exit early */
if (p->rindex == p->size)
break;
r = patch_rr(p, k);
if (r < 0)
return r;
}
return 0;
}
static void dns_packet_hash_func(const DnsPacket *s, struct siphash *state) { static void dns_packet_hash_func(const DnsPacket *s, struct siphash *state) {
assert(s); assert(s);

View File

@ -176,6 +176,8 @@ static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) {
int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t min_alloc_dsize, size_t max_size); int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t min_alloc_dsize, size_t max_size);
int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t min_alloc_dsize, bool dnssec_checking_disabled); int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t min_alloc_dsize, bool dnssec_checking_disabled);
int dns_packet_dup(DnsPacket **ret, DnsPacket *p);
void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated);
DnsPacket *dns_packet_ref(DnsPacket *p); DnsPacket *dns_packet_ref(DnsPacket *p);
@ -201,7 +203,10 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, const DnsAnsw
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAnswerFlags flags, size_t *start, size_t *rdata_start); int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAnswerFlags flags, size_t *start, size_t *rdata_start);
int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, bool include_rfc6975, int rcode, size_t *start); int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, bool include_rfc6975, int rcode, size_t *start);
int dns_packet_append_question(DnsPacket *p, DnsQuestion *q); int dns_packet_append_question(DnsPacket *p, DnsQuestion *q);
int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a); int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a, unsigned *completed);
int dns_packet_patch_max_udp_size(DnsPacket *p, uint16_t max_udp_size);
int dns_packet_patch_ttls(DnsPacket *p, usec_t timestamp);
void dns_packet_truncate(DnsPacket *p, size_t sz); void dns_packet_truncate(DnsPacket *p, size_t sz);
int dns_packet_truncate_opt(DnsPacket *p); int dns_packet_truncate_opt(DnsPacket *p);

View File

@ -97,20 +97,35 @@ static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) {
return 1; return 1;
} }
static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { static int dns_query_candidate_add_transaction(
DnsQueryCandidate *c,
DnsResourceKey *key,
DnsPacket *bypass) {
_cleanup_(dns_transaction_gcp) DnsTransaction *t = NULL; _cleanup_(dns_transaction_gcp) DnsTransaction *t = NULL;
int r; int r;
assert(c); assert(c);
assert(key);
t = dns_scope_find_transaction(c->scope, key, true); if (key) {
/* Regular lookup with a resource key */
assert(!bypass);
t = dns_scope_find_transaction(c->scope, key, c->query->flags);
if (!t) { if (!t) {
r = dns_transaction_new(&t, c->scope, key); r = dns_transaction_new(&t, c->scope, key, NULL, c->query->flags);
if (r < 0) if (r < 0)
return r; return r;
} else if (set_contains(c->transactions, t)) } else if (set_contains(c->transactions, t))
return 0; return 0;
} else {
/* "Bypass" lookup with a query packet */
assert(bypass);
r = dns_transaction_new(&t, c->scope, NULL, bypass, c->query->flags);
if (r < 0)
return r;
}
r = set_ensure_allocated(&t->notify_query_candidates_done, NULL); r = set_ensure_allocated(&t->notify_query_candidates_done, NULL);
if (r < 0) if (r < 0)
@ -126,7 +141,6 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
return r; return r;
} }
t->clamp_ttl = c->query->clamp_ttl;
TAKE_PTR(t); TAKE_PTR(t);
return 1; return 1;
} }
@ -214,6 +228,21 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
dns_query_candidate_stop(c); dns_query_candidate_stop(c);
if (c->query->question_bypass) {
/* If this is a bypass query, then pass the original query packet along to the transaction */
assert(dns_question_size(c->query->question_bypass->question) == 1);
if (!dns_scope_good_key(c->scope, c->query->question_bypass->question->keys[0]))
return 0;
r = dns_query_candidate_add_transaction(c, NULL, c->query->question_bypass);
if (r < 0)
goto fail;
return 1;
}
question = dns_query_question_for_protocol(c->query, c->scope->protocol); question = dns_query_question_for_protocol(c->query, c->scope->protocol);
/* Create one transaction per question key */ /* Create one transaction per question key */
@ -233,7 +262,7 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
if (!dns_scope_good_key(c->scope, qkey)) if (!dns_scope_good_key(c->scope, qkey))
continue; continue;
r = dns_query_candidate_add_transaction(c, qkey); r = dns_query_candidate_add_transaction(c, qkey, NULL);
if (r < 0) if (r < 0)
goto fail; goto fail;
@ -321,6 +350,7 @@ static void dns_query_reset_answer(DnsQuery *q) {
q->answer_protocol = _DNS_PROTOCOL_INVALID; q->answer_protocol = _DNS_PROTOCOL_INVALID;
q->answer_family = AF_UNSPEC; q->answer_family = AF_UNSPEC;
q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain); q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain);
q->answer_full_packet = dns_packet_unref(q->answer_full_packet);
} }
DnsQuery *dns_query_free(DnsQuery *q) { DnsQuery *dns_query_free(DnsQuery *q) {
@ -340,6 +370,7 @@ DnsQuery *dns_query_free(DnsQuery *q) {
dns_question_unref(q->question_idna); dns_question_unref(q->question_idna);
dns_question_unref(q->question_utf8); dns_question_unref(q->question_utf8);
dns_packet_unref(q->question_bypass);
dns_query_reset_answer(q); dns_query_reset_answer(q);
@ -351,13 +382,15 @@ DnsQuery *dns_query_free(DnsQuery *q) {
varlink_unref(q->varlink_request); varlink_unref(q->varlink_request);
} }
dns_packet_unref(q->request_dns_packet); dns_packet_unref(q->request_packet);
dns_packet_unref(q->reply_dns_packet); dns_answer_unref(q->reply_answer);
dns_answer_unref(q->reply_authoritative);
dns_answer_unref(q->reply_additional);
if (q->request_dns_stream) { if (q->request_stream) {
/* Detach the stream from our query, in case something else keeps a reference to it. */ /* Detach the stream from our query, in case something else keeps a reference to it. */
(void) set_remove(q->request_dns_stream->queries, q); (void) set_remove(q->request_stream->queries, q);
q->request_dns_stream = dns_stream_unref(q->request_dns_stream); q->request_stream = dns_stream_unref(q->request_stream);
} }
free(q->request_address_string); free(q->request_address_string);
@ -375,17 +408,25 @@ int dns_query_new(
DnsQuery **ret, DnsQuery **ret,
DnsQuestion *question_utf8, DnsQuestion *question_utf8,
DnsQuestion *question_idna, DnsQuestion *question_idna,
DnsPacket *question_bypass,
int ifindex, int ifindex,
uint64_t flags) { uint64_t flags) {
_cleanup_(dns_query_freep) DnsQuery *q = NULL; _cleanup_(dns_query_freep) DnsQuery *q = NULL;
DnsResourceKey *key;
bool good = false;
int r;
char key_str[DNS_RESOURCE_KEY_STRING_MAX]; char key_str[DNS_RESOURCE_KEY_STRING_MAX];
DnsResourceKey *key;
int r;
assert(m); assert(m);
if (question_bypass) {
/* It's either a "bypass" query, or a regular one, but can't be both. */
if (question_utf8 || question_idna)
return -EINVAL;
} else {
bool good = false;
if (dns_question_size(question_utf8) > 0) { if (dns_question_size(question_utf8) > 0) {
r = dns_question_is_valid_for_query(question_utf8); r = dns_question_is_valid_for_query(question_utf8);
if (r < 0) if (r < 0)
@ -416,6 +457,7 @@ int dns_query_new(
if (!good) /* don't allow empty queries */ if (!good) /* don't allow empty queries */
return -EINVAL; return -EINVAL;
}
if (m->n_dns_queries >= QUERIES_MAX) if (m->n_dns_queries >= QUERIES_MAX)
return -EBUSY; return -EBUSY;
@ -427,6 +469,7 @@ int dns_query_new(
*q = (DnsQuery) { *q = (DnsQuery) {
.question_utf8 = dns_question_ref(question_utf8), .question_utf8 = dns_question_ref(question_utf8),
.question_idna = dns_question_ref(question_idna), .question_idna = dns_question_ref(question_idna),
.question_bypass = dns_packet_ref(question_bypass),
.ifindex = ifindex, .ifindex = ifindex,
.flags = flags, .flags = flags,
.answer_dnssec_result = _DNSSEC_RESULT_INVALID, .answer_dnssec_result = _DNSSEC_RESULT_INVALID,
@ -434,6 +477,11 @@ int dns_query_new(
.answer_family = AF_UNSPEC, .answer_family = AF_UNSPEC,
}; };
if (question_bypass) {
DNS_QUESTION_FOREACH(key, question_bypass->question)
log_debug("Looking up bypass packet for %s.",
dns_resource_key_to_string(key, key_str, sizeof key_str));
} else {
/* First dump UTF8 question */ /* First dump UTF8 question */
DNS_QUESTION_FOREACH(key, question_utf8) DNS_QUESTION_FOREACH(key, question_utf8)
log_debug("Looking up RR for %s.", log_debug("Looking up RR for %s.",
@ -450,6 +498,7 @@ int dns_query_new(
log_debug("Looking up IDNA RR for %s.", log_debug("Looking up IDNA RR for %s.",
dns_resource_key_to_string(key, key_str, sizeof key_str)); dns_resource_key_to_string(key, key_str, sizeof key_str));
} }
}
LIST_PREPEND(queries, m->dns_queries, q); LIST_PREPEND(queries, m->dns_queries, q);
m->n_dns_queries++; m->n_dns_queries++;
@ -457,8 +506,8 @@ int dns_query_new(
if (ret) if (ret)
*ret = q; *ret = q;
q = NULL;
TAKE_PTR(q);
return 0; return 0;
} }
@ -559,9 +608,12 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
DNS_TRANSACTION_NOT_FOUND)) DNS_TRANSACTION_NOT_FOUND))
return 0; return 0;
if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE))
return 0;
r = dns_synthesize_answer( r = dns_synthesize_answer(
q->manager, q->manager,
q->question_utf8, q->question_bypass ? q->question_bypass->question : q->question_utf8,
q->ifindex, q->ifindex,
&answer); &answer);
if (r == -ENXIO) { if (r == -ENXIO) {
@ -601,9 +653,12 @@ static int dns_query_try_etc_hosts(DnsQuery *q) {
/* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is
* done. The data from /etc/hosts hence takes precedence over the network. */ * done. The data from /etc/hosts hence takes precedence over the network. */
if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE))
return 0;
r = manager_etc_hosts_lookup( r = manager_etc_hosts_lookup(
q->manager, q->manager,
q->question_utf8, q->question_bypass ? q->question_bypass->question : q->question_utf8,
&answer); &answer);
if (r <= 0) if (r <= 0)
return r; return r;
@ -757,6 +812,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
q->answer_authenticated = false; q->answer_authenticated = false;
q->answer_errno = c->error_code; q->answer_errno = c->error_code;
q->answer_full_packet = dns_packet_unref(q->answer_full_packet);
} }
SET_FOREACH(t, c->transactions) { SET_FOREACH(t, c->transactions) {
@ -764,14 +820,24 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
switch (t->state) { switch (t->state) {
case DNS_TRANSACTION_SUCCESS: { case DNS_TRANSACTION_SUCCESS: {
/* We found a successfully reply, merge it into the answer */ /* We found a successful reply, merge it into the answer */
if (state == DNS_TRANSACTION_SUCCESS) {
r = dns_answer_extend(&q->answer, t->answer); r = dns_answer_extend(&q->answer, t->answer);
if (r < 0) if (r < 0)
goto fail; goto fail;
} else {
/* Override non-successful previous answers */
dns_answer_unref(q->answer);
q->answer = dns_answer_ref(t->answer);
}
q->answer_rcode = t->answer_rcode; q->answer_rcode = t->answer_rcode;
q->answer_errno = 0; q->answer_errno = 0;
dns_packet_unref(q->answer_full_packet);
q->answer_full_packet = dns_packet_ref(t->received);
if (t->answer_authenticated) { if (t->answer_authenticated) {
has_authenticated = true; has_authenticated = true;
dnssec_result_authenticated = t->answer_dnssec_result; dnssec_result_authenticated = t->answer_dnssec_result;
@ -800,11 +866,14 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
if (q->answer_authenticated && !t->answer_authenticated) if (q->answer_authenticated && !t->answer_authenticated)
continue; continue;
q->answer = dns_answer_unref(q->answer); dns_answer_unref(q->answer);
q->answer = dns_answer_ref(t->answer);
q->answer_rcode = t->answer_rcode; q->answer_rcode = t->answer_rcode;
q->answer_dnssec_result = t->answer_dnssec_result; q->answer_dnssec_result = t->answer_dnssec_result;
q->answer_authenticated = t->answer_authenticated; q->answer_authenticated = t->answer_authenticated;
q->answer_errno = t->answer_errno; q->answer_errno = t->answer_errno;
dns_packet_unref(q->answer_full_packet);
q->answer_full_packet = dns_packet_ref(t->received);
state = t->state; state = t->state;
break; break;
@ -998,6 +1067,9 @@ int dns_query_process_cname(DnsQuery *q) {
DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) { DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) {
assert(q); assert(q);
if (q->question_bypass)
return q->question_bypass->question;
switch (protocol) { switch (protocol) {
case DNS_PROTOCOL_DNS: case DNS_PROTOCOL_DNS:
@ -1018,6 +1090,9 @@ const char *dns_query_string(DnsQuery *q) {
/* Returns a somewhat useful human-readable lookup key string for this query */ /* Returns a somewhat useful human-readable lookup key string for this query */
if (q->question_bypass)
return dns_question_first_name(q->question_bypass->question);
if (q->request_address_string) if (q->request_address_string)
return q->request_address_string; return q->request_address_string;

View File

@ -49,12 +49,13 @@ struct DnsQuery {
DnsQuestion *question_idna; DnsQuestion *question_idna;
DnsQuestion *question_utf8; DnsQuestion *question_utf8;
/* If this is not a question by ourselves, but a "bypass" request, we propagate the original packet
* here, and use that instead. */
DnsPacket *question_bypass;
uint64_t flags; uint64_t flags;
int ifindex; int ifindex;
/* If true, the RR TTLs of the answer will be clamped by their current left validity in the cache */
bool clamp_ttl;
DnsTransactionState state; DnsTransactionState state;
unsigned n_cname_redirects; unsigned n_cname_redirects;
@ -71,6 +72,7 @@ struct DnsQuery {
DnsSearchDomain *answer_search_domain; DnsSearchDomain *answer_search_domain;
int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */
bool previous_redirect_unauthenticated; bool previous_redirect_unauthenticated;
DnsPacket *answer_full_packet;
/* Bus + Varlink client information */ /* Bus + Varlink client information */
sd_bus_message *bus_request; sd_bus_message *bus_request;
@ -82,9 +84,11 @@ struct DnsQuery {
char *request_address_string; char *request_address_string;
/* DNS stub information */ /* DNS stub information */
DnsPacket *request_dns_packet; DnsPacket *request_packet;
DnsStream *request_dns_stream; DnsStream *request_stream;
DnsPacket *reply_dns_packet; DnsAnswer *reply_answer;
DnsAnswer *reply_authoritative;
DnsAnswer *reply_additional;
DnsStubListenerExtra *stub_listener_extra; DnsStubListenerExtra *stub_listener_extra;
/* Completion callback */ /* Completion callback */
@ -109,7 +113,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQueryCandidate*, dns_query_candidate_unref);
void dns_query_candidate_notify(DnsQueryCandidate *c); void dns_query_candidate_notify(DnsQueryCandidate *c);
int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, DnsPacket *question_bypass, int family, uint64_t flags);
DnsQuery *dns_query_free(DnsQuery *q); DnsQuery *dns_query_free(DnsQuery *q);
int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for); int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for);

View File

@ -646,11 +646,10 @@ bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) {
assert(s); assert(s);
assert(key); assert(key);
/* Check if it makes sense to resolve the specified key on /* Check if it makes sense to resolve the specified key on this scope. Note that this call assumes a
* this scope. Note that this call assumes as fully qualified * fully qualified name, i.e. the search suffixes already appended. */
* name, i.e. the search suffixes already appended. */
if (key->class != DNS_CLASS_IN) if (!IN_SET(key->class, DNS_CLASS_IN, DNS_CLASS_ANY))
return false; return false;
if (s->protocol == DNS_PROTOCOL_DNS) { if (s->protocol == DNS_PROTOCOL_DNS) {
@ -672,8 +671,7 @@ bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) {
return !dns_name_is_root(name); return !dns_name_is_root(name);
} }
/* On mDNS and LLMNR, send A and AAAA queries only on the /* On mDNS and LLMNR, send A and AAAA queries only on the respective scopes */
* respective scopes */
key_family = dns_type_to_af(key->type); key_family = dns_type_to_af(key->type);
if (key_family < 0) if (key_family < 0)
@ -765,6 +763,7 @@ int dns_scope_make_reply_packet(
DnsPacket **ret) { DnsPacket **ret) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
unsigned n_answer = 0, n_soa = 0;
int r; int r;
assert(s); assert(s);
@ -796,15 +795,15 @@ int dns_scope_make_reply_packet(
return r; return r;
DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q)); DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q));
r = dns_packet_append_answer(p, answer); r = dns_packet_append_answer(p, answer, &n_answer);
if (r < 0) if (r < 0)
return r; return r;
DNS_PACKET_HEADER(p)->ancount = htobe16(dns_answer_size(answer)); DNS_PACKET_HEADER(p)->ancount = htobe16(n_answer);
r = dns_packet_append_answer(p, soa); r = dns_packet_append_answer(p, soa, &n_soa);
if (r < 0) if (r < 0)
return r; return r;
DNS_PACKET_HEADER(p)->arcount = htobe16(dns_answer_size(soa)); DNS_PACKET_HEADER(p)->arcount = htobe16(n_soa);
*ret = TAKE_PTR(p); *ret = TAKE_PTR(p);
@ -926,28 +925,52 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
} }
} }
DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { DnsTransaction *dns_scope_find_transaction(
DnsTransaction *t; DnsScope *scope,
DnsResourceKey *key,
uint64_t query_flags) {
DnsTransaction *first, *t;
assert(scope); assert(scope);
assert(key); assert(key);
/* Try to find an ongoing transaction that is a equal to the /* Iterate through the list of transactions with a matching key */
* specified question */ first = hashmap_get(scope->transactions_by_key, key);
t = hashmap_get(scope->transactions_by_key, key); LIST_FOREACH(transactions_by_key, t, first) {
if (!t)
return NULL;
/* Refuse reusing transactions that completed based on cached /* These four flags must match exactly: we cannot use a validated response for a
* data instead of a real packet, if that's requested. */ * non-validating client, and we cannot use a non-validated response for a validating
if (!cache_ok && * client. Similar, if the sources don't match things aren't usable either. */
IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) && if (((query_flags ^ t->query_flags) &
t->answer_source != DNS_TRANSACTION_NETWORK) (SD_RESOLVED_NO_VALIDATE|
return NULL; SD_RESOLVED_NO_ZONE|
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK)) != 0)
continue;
/* We can reuse a primary query if a regular one is requested, but not vice versa */
if ((query_flags & SD_RESOLVED_REQUIRE_PRIMARY) &&
!(t->query_flags & SD_RESOLVED_REQUIRE_PRIMARY))
continue;
/* Don't reuse a transaction that allowed caching when we got told not to use it */
if ((query_flags & SD_RESOLVED_NO_CACHE) &&
!(t->query_flags & SD_RESOLVED_NO_CACHE))
continue;
/* If we are are asked to clamp ttls an the existing transaction doesn't do it, we can't
* reuse */
if ((query_flags & SD_RESOLVED_CLAMP_TTL) &&
!(t->query_flags & SD_RESOLVED_CLAMP_TTL))
continue;
return t; return t;
} }
return NULL;
}
static int dns_scope_make_conflict_packet( static int dns_scope_make_conflict_packet(
DnsScope *s, DnsScope *s,
DnsResourceRecord *rr, DnsResourceRecord *rr,
@ -1289,7 +1312,7 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) {
else else
flags = goodbye ? (DNS_ANSWER_GOODBYE|DNS_ANSWER_CACHE_FLUSH) : DNS_ANSWER_CACHE_FLUSH; flags = goodbye ? (DNS_ANSWER_GOODBYE|DNS_ANSWER_CACHE_FLUSH) : DNS_ANSWER_CACHE_FLUSH;
r = dns_answer_add(answer, i->rr, 0 , flags); r = dns_answer_add(answer, i->rr, 0, flags, NULL);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to add RR to announce: %m"); return log_debug_errno(r, "Failed to add RR to announce: %m");
} }
@ -1307,7 +1330,7 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) {
if (r < 0) if (r < 0)
log_warning_errno(r, "Failed to add DNS-SD PTR record to MDNS zone: %m"); log_warning_errno(r, "Failed to add DNS-SD PTR record to MDNS zone: %m");
r = dns_answer_add(answer, rr, 0 , 0); r = dns_answer_add(answer, rr, 0, 0, NULL);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to add RR to announce: %m"); return log_debug_errno(r, "Failed to add RR to announce: %m");
} }

View File

@ -53,14 +53,12 @@ struct DnsScope {
LIST_HEAD(DnsQueryCandidate, query_candidates); LIST_HEAD(DnsQueryCandidate, query_candidates);
/* Note that we keep track of ongoing transactions in two /* Note that we keep track of ongoing transactions in two ways: once in a hashmap, indexed by the rr
* ways: once in a hashmap, indexed by the rr key, and once in * key, and once in a linked list. We use the hashmap to quickly find transactions we can reuse for a
* a linked list. We use the hashmap to quickly find * key. But note that there might be multiple transactions for the same key (because the associated
* transactions we can reuse for a key. But note that there * query flags might differ in incompatible ways: e.g. we may not reuse a non-validating transaction
* might be multiple transactions for the same key (because * as validating. Hence we maintain a per-key list of transactions, which we iterate through to find
* the zone probing can't reuse a transaction answered from * one we can reuse with matching flags. */
* the zone or the cache), and the hashmap only tracks the
* most recent entry. */
Hashmap *transactions_by_key; Hashmap *transactions_by_key;
LIST_HEAD(DnsTransaction, transactions); LIST_HEAD(DnsTransaction, transactions);
@ -90,7 +88,7 @@ int dns_scope_mdns_membership(DnsScope *s, bool b);
int dns_scope_make_reply_packet(DnsScope *s, uint16_t id, int rcode, DnsQuestion *q, DnsAnswer *answer, DnsAnswer *soa, bool tentative, DnsPacket **ret); int dns_scope_make_reply_packet(DnsScope *s, uint16_t id, int rcode, DnsQuestion *q, DnsAnswer *answer, DnsAnswer *soa, bool tentative, DnsPacket **ret);
void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p);
DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, uint64_t query_flags);
int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr);
void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p);

View File

@ -85,69 +85,259 @@ DnsStubListenerExtra *dns_stub_listener_extra_free(DnsStubListenerExtra *p) {
return mfree(p); return mfree(p);
} }
static int dns_stub_make_reply_packet( static int dns_stub_collect_answer_by_question(
DnsPacket **p, DnsAnswer **reply,
size_t max_size,
DnsQuestion *q,
DnsAnswer *answer, DnsAnswer *answer,
bool *ret_truncated) { DnsQuestion *question,
bool with_rrsig) { /* Add RRSIG RR matching each RR */
bool truncated = false; DnsAnswerItem *item;
DnsResourceRecord *rr; int r;
assert(reply);
/* Copies all RRs from 'answer' into 'reply', if they match 'question'. */
DNS_ANSWER_FOREACH_ITEM(item, answer) {
if (question) {
bool match = false;
r = dns_question_matches_rr(question, item->rr, NULL);
if (r < 0)
return r;
else if (r > 0)
match = true;
else {
r = dns_question_matches_cname_or_dname(question, item->rr, NULL);
if (r < 0)
return r;
if (r > 0)
match = true;
}
if (!match)
continue;
}
r = dns_answer_add_extend(reply, item->rr, item->ifindex, item->flags, item->rrsig);
if (r < 0)
return r;
if (with_rrsig && item->rrsig) {
r = dns_answer_add_extend(reply, item->rrsig, item->ifindex, item->flags, NULL);
if (r < 0)
return r;
}
}
return 0;
}
static int dns_stub_collect_answer_by_section(
DnsAnswer **reply,
DnsAnswer *answer,
DnsAnswerFlags section,
DnsAnswer *exclude1,
DnsAnswer *exclude2,
bool with_dnssec) { /* Include DNSSEC RRs. RRSIG, NSEC, … */
DnsAnswerItem *item;
unsigned c = 0; unsigned c = 0;
int r; int r;
assert(p); assert(reply);
/* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence /* Copies all RRs from 'answer' into 'reply', if they originate from the specified section. Also,
* roundtrips aren't expensive. */ * avoid any RRs listed in 'exclude'. */
if (!*p) { DNS_ANSWER_FOREACH_ITEM(item, answer) {
r = dns_packet_new(p, DNS_PROTOCOL_DNS, 0, max_size);
if (r < 0)
return r;
r = dns_packet_append_question(*p, q);
if (r < 0)
return r;
DNS_PACKET_HEADER(*p)->qdcount = htobe16(dns_question_size(q));
}
DNS_ANSWER_FOREACH(rr, answer) {
r = dns_question_matches_rr(q, rr, NULL);
if (r < 0)
return r;
if (r > 0)
goto add;
r = dns_question_matches_cname_or_dname(q, rr, NULL);
if (r < 0)
return r;
if (r > 0)
goto add;
if (dns_answer_contains(exclude1, item->rr) ||
dns_answer_contains(exclude2, item->rr))
continue; continue;
add:
r = dns_packet_append_rr(*p, rr, 0, NULL, NULL); if (!with_dnssec &&
if (r == -EMSGSIZE) { dns_type_is_dnssec(item->rr->key->type))
truncated = true; continue;
break;
} if (((item->flags ^ section) & (DNS_ANSWER_SECTION_ANSWER|DNS_ANSWER_SECTION_AUTHORITY|DNS_ANSWER_SECTION_ADDITIONAL)) != 0)
continue;
r = dns_answer_add_extend(reply, item->rr, item->ifindex, item->flags, item->rrsig);
if (r < 0)
return r;
c++;
if (with_dnssec && item->rrsig) {
r = dns_answer_add_extend(reply, item->rrsig, item->ifindex, item->flags, NULL);
if (r < 0) if (r < 0)
return r; return r;
c++; c++;
} }
}
return (int) c;
}
static int dns_stub_assign_sections(
DnsQuery *q,
DnsQuestion *question,
bool edns0_do) {
int r;
assert(q);
assert(question);
/* Let's assign the 'answer' and 'answer_auxiliary' RRs we collected to their respective sections in
* the reply datagram. We try to reproduce a section assignment similar to what the upstream DNS
* server responded to us. We use the DNS_ANSWER_SECTION_xyz flags to match things up, which is where
* the original upstream's packet section assignment is stored in the DnsAnswer object. Not all RRs
* in the 'answer' and 'answer_auxiliary' objects come with section information though (for example,
* because they were synthesized locally, and not from a DNS packet). To deal with that we extend the
* assignment logic a bit: anything from the 'answer' object that directly matches the original
* question is always put in the ANSWER section, regardless if it carries section info, or what that
* section info says. Then, anything from the 'answer' and 'answer_auxiliary' objects that is from
* the ANSWER or AUTHORITY sections, and wasn't already added to the ANSWER section is placed in the
* AUTHORITY section. Everything else from either object is added to the ADDITIONAL section. */
/* Include all RRs that directly answer the question in the answer section */
r = dns_stub_collect_answer_by_question(
&q->reply_answer,
q->answer,
question,
edns0_do);
if (r < 0)
return r;
/* Include all RRs that originate from the answer or authority sections, and aren't listed in the
* answer section, in the authority section */
r = dns_stub_collect_answer_by_section(
&q->reply_authoritative,
q->answer,
DNS_ANSWER_SECTION_ANSWER,
q->reply_answer, NULL,
edns0_do);
if (r < 0)
return r;
/* Include all RRs that originate from the answer or authority sections, and aren't listed in the
* answer section, in the authority section */
r = dns_stub_collect_answer_by_section(
&q->reply_authoritative,
q->answer,
DNS_ANSWER_SECTION_AUTHORITY,
q->reply_answer, NULL,
edns0_do);
if (r < 0)
return r;
/* Include all RRs that originate from the additional sections in the additional section (except if
* already listed in the other two sections). Also add all RRs with no section marking. */
r = dns_stub_collect_answer_by_section(
&q->reply_additional,
q->answer,
DNS_ANSWER_SECTION_ADDITIONAL,
q->reply_answer, q->reply_authoritative,
edns0_do);
if (r < 0)
return r;
r = dns_stub_collect_answer_by_section(
&q->reply_additional,
q->answer,
0,
q->reply_answer, q->reply_authoritative,
edns0_do);
if (r < 0)
return r;
return 0;
}
static int dns_stub_make_reply_packet(
DnsPacket **ret,
size_t max_size,
DnsQuestion *q,
bool *ret_truncated) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
bool tc = false;
int r;
assert(ret);
r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, max_size);
if (r < 0)
return r;
r = dns_packet_append_question(p, q);
if (r == -EMSGSIZE)
tc = true;
else if (r < 0)
return r;
if (ret_truncated) if (ret_truncated)
*ret_truncated = truncated; *ret_truncated = tc;
else if (truncated) else if (tc)
return -EMSGSIZE; return -EMSGSIZE;
DNS_PACKET_HEADER(*p)->ancount = htobe16(be16toh(DNS_PACKET_HEADER(*p)->ancount) + c); DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q));
*ret = TAKE_PTR(p);
return 0;
}
static int dns_stub_add_reply_packet_body(
DnsPacket *p,
DnsAnswer *answer,
DnsAnswer *authoritative,
DnsAnswer *additional,
bool edns0_do, /* Client expects DNSSEC RRs? */
bool *truncated) {
unsigned n_answer = 0, n_authoritative = 0, n_additional = 0;
bool tc = false;
int r;
assert(p);
/* Add the three sections to the packet. If the answer section doesn't fit we'll signal that as
* truncation. If the authoritative section doesn't fit and we are in DNSSEC mode, also signal
* truncation. In all other cases where things don't fit don't signal truncation, as for those cases
* the dropped RRs should not be essential. */
r = dns_packet_append_answer(p, answer, &n_answer);
if (r == -EMSGSIZE)
tc = true;
else if (r < 0)
return r;
else {
r = dns_packet_append_answer(p, authoritative, &n_authoritative);
if (r == -EMSGSIZE) {
if (edns0_do)
tc = true;
} else if (r < 0)
return r;
else {
r = dns_packet_append_answer(p, additional, &n_additional);
if (r < 0 && r != -EMSGSIZE)
return r;
}
}
if (tc) {
if (!truncated)
return -EMSGSIZE;
*truncated = true;
}
DNS_PACKET_HEADER(p)->ancount = htobe16(n_answer);
DNS_PACKET_HEADER(p)->nscount = htobe16(n_authoritative);
DNS_PACKET_HEADER(p)->arcount = htobe16(n_additional);
return 0; return 0;
} }
@ -159,6 +349,7 @@ static int dns_stub_finish_reply_packet(
bool add_opt, /* add an OPT RR to this packet? */ bool add_opt, /* add an OPT RR to this packet? */
bool edns0_do, /* set the EDNS0 DNSSEC OK bit? */ bool edns0_do, /* set the EDNS0 DNSSEC OK bit? */
bool ad, /* set the DNSSEC authenticated data bit? */ bool ad, /* set the DNSSEC authenticated data bit? */
bool cd, /* set the DNSSEC checking disabled bit? */
uint16_t max_udp_size) { /* The maximum UDP datagram size to advertise to clients */ uint16_t max_udp_size) { /* The maximum UDP datagram size to advertise to clients */
int r; int r;
@ -171,19 +362,21 @@ static int dns_stub_finish_reply_packet(
tc = true; tc = true;
else if (r < 0) else if (r < 0)
return r; return r;
} else { } else {
/* If the client can't to EDNS0, don't do DO either */ /* If the client can't to EDNS0, don't do DO either */
edns0_do = false; edns0_do = false;
/* If the client didn't do EDNS, clamp the rcode to 4 bit */ /* If we don't do EDNS, clamp the rcode to 4 bit */
if (rcode > 0xF) if (rcode > 0xF)
rcode = DNS_RCODE_SERVFAIL; rcode = DNS_RCODE_SERVFAIL;
} }
/* Don't set the AD bit unless DO is on, too */ /* Don't set the AD or CD bit unless DO is on, too */
if (!edns0_do) if (!edns0_do) {
ad = false; ad = false;
cd = false;
}
DNS_PACKET_HEADER(p)->id = id; DNS_PACKET_HEADER(p)->id = id;
@ -195,7 +388,7 @@ static int dns_stub_finish_reply_packet(
1 /* rd */, 1 /* rd */,
1 /* ra */, 1 /* ra */,
ad /* ad */, ad /* ad */,
0 /* cd */, cd /* cd */,
rcode)); rcode));
return 0; return 0;
@ -231,6 +424,66 @@ static int dns_stub_send(
return 0; return 0;
} }
static int dns_stub_send_reply(
DnsQuery *q,
int rcode) {
_cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
bool truncated, edns0_do;
int r;
assert(q);
/* Reply with DNSSEC DO set? Only if client supports it; and we did any DNSSEC verification
* ourselves, or consider the data fully authenticated because we generated it locally, or
* the client set cd */
edns0_do =
DNS_PACKET_DO(q->request_packet) &&
(q->answer_dnssec_result >= 0 || /* we did proper DNSSEC validation … */
dns_query_fully_authenticated(q) || /* … or we considered it authentic otherwise … */
DNS_PACKET_CD(q->request_packet)); /* … or client set CD */
r = dns_stub_assign_sections(
q,
q->request_packet->question,
edns0_do);
if (r < 0)
return log_debug_errno(r, "Failed to assign sections: %m");
r = dns_stub_make_reply_packet(
&reply,
DNS_PACKET_PAYLOAD_SIZE_MAX(q->request_packet),
q->request_packet->question,
&truncated);
if (r < 0)
return log_debug_errno(r, "Failed to build reply packet: %m");
r = dns_stub_add_reply_packet_body(
reply,
q->reply_answer,
q->reply_authoritative,
q->reply_additional,
edns0_do,
&truncated);
if (r < 0)
return log_debug_errno(r, "Failed to append reply packet body: %m");
r = dns_stub_finish_reply_packet(
reply,
DNS_PACKET_ID(q->request_packet),
rcode,
truncated,
!!q->request_packet->opt,
edns0_do,
dns_query_fully_authenticated(q),
DNS_PACKET_CD(q->request_packet),
q->stub_listener_extra ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX);
if (r < 0)
return log_debug_errno(r, "Failed to build failure packet: %m");
return dns_stub_send(q->manager, q->stub_listener_extra, q->request_stream, q->request_packet, reply);
}
static int dns_stub_send_failure( static int dns_stub_send_failure(
Manager *m, Manager *m,
DnsStubListenerExtra *l, DnsStubListenerExtra *l,
@ -240,12 +493,17 @@ static int dns_stub_send_failure(
bool authenticated) { bool authenticated) {
_cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
bool truncated;
int r; int r;
assert(m); assert(m);
assert(p); assert(p);
r = dns_stub_make_reply_packet(&reply, DNS_PACKET_PAYLOAD_SIZE_MAX(p), p->question, NULL, NULL); r = dns_stub_make_reply_packet(
&reply,
DNS_PACKET_PAYLOAD_SIZE_MAX(p),
p->question,
&truncated);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to make failure packet: %m"); return log_debug_errno(r, "Failed to make failure packet: %m");
@ -253,10 +511,11 @@ static int dns_stub_send_failure(
reply, reply,
DNS_PACKET_ID(p), DNS_PACKET_ID(p),
rcode, rcode,
/* truncated = */ false, truncated,
!!p->opt, !!p->opt,
DNS_PACKET_DO(p), DNS_PACKET_DO(p),
authenticated, authenticated,
DNS_PACKET_CD(p),
l ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX); l ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to build failure packet: %m"); return log_debug_errno(r, "Failed to build failure packet: %m");
@ -264,27 +523,87 @@ static int dns_stub_send_failure(
return dns_stub_send(m, l, s, p, reply); return dns_stub_send(m, l, s, p, reply);
} }
static int dns_stub_patch_bypass_reply_packet(
DnsPacket **ret, /* Where to place the patched packet */
DnsPacket *original, /* The packet to patch */
DnsPacket *request) { /* The packet the patched packet shall look like a reply to */
_cleanup_(dns_packet_unrefp) DnsPacket *c = NULL;
int r;
assert(ret);
assert(original);
assert(request);
r = dns_packet_dup(&c, original);
if (r < 0)
return r;
/* Extract the packet, so that we know where the OPT field is */
r = dns_packet_extract(c);
if (r < 0)
return r;
/* Copy over the original client request ID, so that we can make the upstream query look like our own reply. */
DNS_PACKET_HEADER(c)->id = DNS_PACKET_HEADER(request)->id;
/* Patch in our own maximum datagram size, if EDNS0 was on */
r = dns_packet_patch_max_udp_size(c, ADVERTISE_DATAGRAM_SIZE_MAX);
if (r < 0)
return r;
/* Lower all TTLs by the time passed since we received the datagram. */
if (timestamp_is_set(original->timestamp)) {
r = dns_packet_patch_ttls(c, original->timestamp);
if (r < 0)
return r;
}
/* Our upstream connection might have supported larger DNS requests than our downstream one, hence
* set the TC bit if our reply is larger than what the client supports, and truncate. */
if (c->size > DNS_PACKET_PAYLOAD_SIZE_MAX(request)) {
log_debug("Artificially truncating stub response, as advertised size of client is smaller than upstream one.");
dns_packet_truncate(c, DNS_PACKET_PAYLOAD_SIZE_MAX(request));
DNS_PACKET_HEADER(c)->flags = htobe16(be16toh(DNS_PACKET_HEADER(c)->flags) | DNS_PACKET_FLAG_TC);
}
*ret = TAKE_PTR(c);
return 0;
}
static void dns_stub_query_complete(DnsQuery *q) { static void dns_stub_query_complete(DnsQuery *q) {
int r; int r;
assert(q); assert(q);
assert(q->request_dns_packet); assert(q->request_packet);
if (q->question_bypass) {
/* This is a bypass reply. If so, let's propagate the upstream packet, if we have it and it
* is regular DNS. (We can't do this if the upstream packet is LLMNR or mDNS, since the
* packets are not 100% compatible.) */
if (q->answer_full_packet &&
q->answer_full_packet->protocol == DNS_PROTOCOL_DNS) {
_cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
r = dns_stub_patch_bypass_reply_packet(&reply, q->answer_full_packet, q->request_packet);
if (r < 0)
log_debug_errno(r, "Failed to patch bypass reply packet: %m");
else
(void) dns_stub_send(q->manager, q->stub_listener_extra, q->request_stream, q->request_packet, reply);
dns_query_free(q);
return;
}
}
switch (q->state) { switch (q->state) {
case DNS_TRANSACTION_SUCCESS: { case DNS_TRANSACTION_SUCCESS:
bool truncated; /* Follow CNAMEs, and accumulate answers. Except if DNSSEC is requested, then let the client do that. */
if (!DNS_PACKET_DO(q->request_packet)) {
r = dns_stub_make_reply_packet(&q->reply_dns_packet, DNS_PACKET_PAYLOAD_SIZE_MAX(q->request_dns_packet), q->question_idna, q->answer, &truncated);
if (r < 0) {
log_debug_errno(r, "Failed to build reply packet: %m");
break;
}
if (!truncated) {
r = dns_query_process_cname(q); r = dns_query_process_cname(q);
if (r == -ELOOP) { if (r == -ELOOP) { /* CNAME loop */
(void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); (void) dns_stub_send_reply(q, DNS_RCODE_SERVFAIL);
break; break;
} }
if (r < 0) { if (r < 0) {
@ -295,30 +614,15 @@ static void dns_stub_query_complete(DnsQuery *q) {
return; return;
} }
r = dns_stub_finish_reply_packet( (void) dns_stub_send_reply(q, q->answer_rcode);
q->reply_dns_packet,
DNS_PACKET_ID(q->request_dns_packet),
q->answer_rcode,
truncated,
!!q->request_dns_packet->opt,
DNS_PACKET_DO(q->request_dns_packet),
dns_query_fully_authenticated(q),
q->stub_listener_extra ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX);
if (r < 0) {
log_debug_errno(r, "Failed to finish reply packet: %m");
break; break;
}
(void) dns_stub_send(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, q->reply_dns_packet);
break;
}
case DNS_TRANSACTION_RCODE_FAILURE: case DNS_TRANSACTION_RCODE_FAILURE:
(void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, q->answer_rcode, dns_query_fully_authenticated(q)); (void) dns_stub_send_reply(q, q->answer_rcode);
break; break;
case DNS_TRANSACTION_NOT_FOUND: case DNS_TRANSACTION_NOT_FOUND:
(void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN, dns_query_fully_authenticated(q)); (void) dns_stub_send_reply(q, DNS_RCODE_NXDOMAIN);
break; break;
case DNS_TRANSACTION_TIMEOUT: case DNS_TRANSACTION_TIMEOUT:
@ -334,7 +638,8 @@ static void dns_stub_query_complete(DnsQuery *q) {
case DNS_TRANSACTION_NO_TRUST_ANCHOR: case DNS_TRANSACTION_NO_TRUST_ANCHOR:
case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED:
case DNS_TRANSACTION_NETWORK_DOWN: case DNS_TRANSACTION_NETWORK_DOWN:
(void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); case DNS_TRANSACTION_NO_SOURCE:
(void) dns_stub_send_reply(q, DNS_RCODE_SERVFAIL);
break; break;
case DNS_TRANSACTION_NULL: case DNS_TRANSACTION_NULL:
@ -398,13 +703,13 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea
if (dns_type_is_obsolete(p->question->keys[0]->type)) { if (dns_type_is_obsolete(p->question->keys[0]->type)) {
log_debug("Got message with obsolete key type, refusing."); log_debug("Got message with obsolete key type, refusing.");
dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); dns_stub_send_failure(m, l, s, p, DNS_RCODE_REFUSED, false);
return; return;
} }
if (dns_type_is_zone_transer(p->question->keys[0]->type)) { if (dns_type_is_zone_transer(p->question->keys[0]->type)) {
log_debug("Got request for zone transfer, refusing."); log_debug("Got request for zone transfer, refusing.");
dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); dns_stub_send_failure(m, l, s, p, DNS_RCODE_REFUSED, false);
return; return;
} }
@ -416,23 +721,29 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea
} }
if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) { if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) {
log_debug("Got request with DNSSEC CD bit set, refusing."); log_debug("Got request with DNSSEC checking disabled, enabling bypass logic.");
dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false);
return;
}
r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH); r = dns_query_new(m, &q, NULL, NULL, p, 0,
SD_RESOLVED_PROTOCOLS_ALL|
SD_RESOLVED_NO_CNAME|
SD_RESOLVED_NO_SEARCH|
SD_RESOLVED_NO_VALIDATE|
SD_RESOLVED_REQUIRE_PRIMARY|
SD_RESOLVED_CLAMP_TTL);
} else
r = dns_query_new(m, &q, p->question, p->question, NULL, 0,
SD_RESOLVED_PROTOCOLS_ALL|
SD_RESOLVED_NO_SEARCH|
(DNS_PACKET_DO(p) ? SD_RESOLVED_NO_CNAME|SD_RESOLVED_REQUIRE_PRIMARY : 0)|
SD_RESOLVED_CLAMP_TTL);
if (r < 0) { if (r < 0) {
log_error_errno(r, "Failed to generate query object: %m"); log_error_errno(r, "Failed to generate query object: %m");
dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false); dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false);
return; return;
} }
/* Request that the TTL is corrected by the cached time for this lookup, so that we return vaguely useful TTLs */ q->request_packet = dns_packet_ref(p);
q->clamp_ttl = true; q->request_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */
q->request_dns_packet = dns_packet_ref(p);
q->request_dns_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */
q->stub_listener_extra = l; q->stub_listener_extra = l;
q->complete = dns_stub_query_complete; q->complete = dns_stub_query_complete;

View File

@ -76,7 +76,7 @@ static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int if
rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK);
r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED, NULL);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -90,7 +90,7 @@ static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int if
rr->aaaa.in6_addr = in6addr_loopback; rr->aaaa.in6_addr = in6addr_loopback;
r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED, NULL);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -109,7 +109,7 @@ static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to,
if (!rr->ptr.name) if (!rr->ptr.name)
return -ENOMEM; return -ENOMEM;
return dns_answer_add(*answer, rr, ifindex, flags); return dns_answer_add(*answer, rr, ifindex, flags, NULL);
} }
static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) {
@ -155,7 +155,7 @@ static int answer_add_addresses_rr(
if (r < 0) if (r < 0)
return r; return r;
r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED, NULL);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -197,7 +197,7 @@ static int answer_add_addresses_ptr(
if (r < 0) if (r < 0)
return r; return r;
r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED, NULL);
if (r < 0) if (r < 0)
return r; return r;

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@
#include "in-addr-util.h" #include "in-addr-util.h"
typedef struct DnsTransaction DnsTransaction; typedef struct DnsTransaction DnsTransaction;
typedef struct DnsTransactionFinder DnsTransactionFinder;
typedef enum DnsTransactionState DnsTransactionState; typedef enum DnsTransactionState DnsTransactionState;
typedef enum DnsTransactionSource DnsTransactionSource; typedef enum DnsTransactionSource DnsTransactionSource;
@ -31,6 +32,7 @@ enum DnsTransactionState {
DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED,
DNS_TRANSACTION_NETWORK_DOWN, DNS_TRANSACTION_NETWORK_DOWN,
DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */ DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */
DNS_TRANSACTION_NO_SOURCE, /* All suitable DnsTransactionSource turned off */
_DNS_TRANSACTION_STATE_MAX, _DNS_TRANSACTION_STATE_MAX,
_DNS_TRANSACTION_STATE_INVALID = -EINVAL, _DNS_TRANSACTION_STATE_INVALID = -EINVAL,
}; };
@ -49,7 +51,10 @@ enum DnsTransactionSource {
struct DnsTransaction { struct DnsTransaction {
DnsScope *scope; DnsScope *scope;
DnsResourceKey *key; DnsResourceKey *key; /* For regular lookups the RR key to look for */
DnsPacket *bypass; /* For bypass lookups the full original request packet */
uint64_t query_flags;
DnsTransactionState state; DnsTransactionState state;
@ -60,8 +65,6 @@ struct DnsTransaction {
bool initial_jitter_scheduled:1; bool initial_jitter_scheduled:1;
bool initial_jitter_elapsed:1; bool initial_jitter_elapsed:1;
bool clamp_ttl:1;
bool probing:1; bool probing:1;
DnsPacket *sent, *received; DnsPacket *sent, *received;
@ -133,9 +136,10 @@ struct DnsTransaction {
LIST_FIELDS(DnsTransaction, transactions_by_scope); LIST_FIELDS(DnsTransaction, transactions_by_scope);
LIST_FIELDS(DnsTransaction, transactions_by_stream); LIST_FIELDS(DnsTransaction, transactions_by_stream);
LIST_FIELDS(DnsTransaction, transactions_by_key);
}; };
int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key, DnsPacket *bypass, uint64_t flags);
DnsTransaction* dns_transaction_free(DnsTransaction *t); DnsTransaction* dns_transaction_free(DnsTransaction *t);
bool dns_transaction_gc(DnsTransaction *t); bool dns_transaction_gc(DnsTransaction *t);
@ -150,6 +154,23 @@ void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source);
int dns_transaction_validate_dnssec(DnsTransaction *t); int dns_transaction_validate_dnssec(DnsTransaction *t);
int dns_transaction_request_dnssec_keys(DnsTransaction *t); int dns_transaction_request_dnssec_keys(DnsTransaction *t);
static inline DnsResourceKey *dns_transaction_key(DnsTransaction *t) {
assert(t);
/* Return the lookup key of this transaction. Either takes the lookup key from the bypass packet if
* we are a bypass transaction. Or take the configured key for regular transactions. */
if (t->key)
return t->key;
assert(t->bypass);
if (dns_question_isempty(t->bypass->question))
return NULL;
return t->bypass->question->keys[0];
}
const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; const char* dns_transaction_state_to_string(DnsTransactionState p) _const_;
DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_;

View File

@ -60,7 +60,7 @@ static int add_root_ksk(
if (!rr->ds.digest) if (!rr->ds.digest)
return -ENOMEM; return -ENOMEM;
r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED); r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL);
if (r < 0) if (r < 0)
return r; return r;
@ -354,7 +354,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u
old_answer = hashmap_get(d->positive_by_key, rr->key); old_answer = hashmap_get(d->positive_by_key, rr->key);
answer = dns_answer_ref(old_answer); answer = dns_answer_ref(old_answer);
r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED); r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to add trust anchor RR: %m"); return log_error_errno(r, "Failed to add trust anchor RR: %m");

View File

@ -170,7 +170,10 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) {
if (i->probe_transaction) if (i->probe_transaction)
return 0; return 0;
t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false); t = dns_scope_find_transaction(
i->scope,
&DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)),
SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
if (!t) { if (!t) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
@ -178,7 +181,7 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) {
if (!key) if (!key)
return -ENOMEM; return -ENOMEM;
r = dns_transaction_new(&t, i->scope, key); r = dns_transaction_new(&t, i->scope, key, NULL, SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -296,7 +299,7 @@ static int dns_zone_add_authenticated_answer(DnsAnswer *a, DnsZoneItem *i, int i
else else
flags = DNS_ANSWER_AUTHENTICATED; flags = DNS_ANSWER_AUTHENTICATED;
return dns_answer_add(a, i->rr, ifindex, flags); return dns_answer_add(a, i->rr, ifindex, flags, NULL);
} }
int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {

View File

@ -406,7 +406,7 @@ int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) {
if (!rr->ptr.name) if (!rr->ptr.name)
return -ENOMEM; return -ENOMEM;
r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -458,7 +458,7 @@ int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) {
if (r < 0) if (r < 0)
return r; return r;
r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL);
if (r < 0) if (r < 0)
return r; return r;
} }

View File

@ -301,25 +301,25 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us
rr->ttl = 1; rr->ttl = 1;
} }
t = dns_scope_find_transaction(scope, rr->key, false); t = dns_scope_find_transaction(scope, rr->key, SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
if (t) if (t)
dns_transaction_process_reply(t, p); dns_transaction_process_reply(t, p);
/* Also look for the various types of ANY transactions */ /* Also look for the various types of ANY transactions */
t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false); t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
if (t) if (t)
dns_transaction_process_reply(t, p); dns_transaction_process_reply(t, p);
t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), false); t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
if (t) if (t)
dns_transaction_process_reply(t, p); dns_transaction_process_reply(t, p);
t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false); t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
if (t) if (t)
dns_transaction_process_reply(t, p); dns_transaction_process_reply(t, p);
} }
dns_cache_put(&scope->cache, scope->manager->enable_cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); dns_cache_put(&scope->cache, scope->manager->enable_cache, NULL, DNS_PACKET_RCODE(p), p->answer, NULL, false, _DNSSEC_RESULT_INVALID, (uint32_t) -1, p->family, &p->sender);
} else if (dns_packet_validate_query(p) > 0) { } else if (dns_packet_validate_query(p) > 0) {
log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));

View File

@ -57,6 +57,9 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_NETWORK_DOWN: case DNS_TRANSACTION_NETWORK_DOWN:
return varlink_error(q->varlink_request, "io.systemd.Resolve.NetworkDown", NULL); return varlink_error(q->varlink_request, "io.systemd.Resolve.NetworkDown", NULL);
case DNS_TRANSACTION_NO_SOURCE:
return varlink_error(q->varlink_request, "io.systemd.Resolve.NoSource", NULL);
case DNS_TRANSACTION_NOT_FOUND: case DNS_TRANSACTION_NOT_FOUND:
/* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we
* thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */
@ -103,7 +106,7 @@ static bool validate_and_mangle_flags(
/* This checks that the specified client-provided flags parameter actually makes sense, and mangles /* This checks that the specified client-provided flags parameter actually makes sense, and mangles
* it slightly. Specifically: * it slightly. Specifically:
* *
* 1. We check that only the protocol flags and the NO_CNAME flag are on at most, plus the * 1. We check that only the protocol flags and a bunch of NO_XYZ flags are on at most, plus the
* method-specific flags specified in 'ok'. * method-specific flags specified in 'ok'.
* *
* 2. If no protocols are enabled we automatically convert that to "all protocols are enabled". * 2. If no protocols are enabled we automatically convert that to "all protocols are enabled".
@ -114,7 +117,15 @@ static bool validate_and_mangle_flags(
* "everything". * "everything".
*/ */
if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|
SD_RESOLVED_NO_CNAME|
SD_RESOLVED_NO_VALIDATE|
SD_RESOLVED_NO_SYNTHESIZE|
SD_RESOLVED_NO_CACHE|
SD_RESOLVED_NO_ZONE|
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK|
ok))
return false; return false;
if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
@ -312,7 +323,7 @@ static int vl_method_resolve_hostname(Varlink *link, JsonVariant *parameters, Va
if (r < 0 && r != -EALREADY) if (r < 0 && r != -EALREADY)
return r; return r;
r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, p.ifindex, p.flags); r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, p.ifindex, p.flags);
if (r < 0) if (r < 0)
return r; return r;
@ -481,7 +492,7 @@ static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, Var
if (r < 0) if (r < 0)
return r; return r;
r = dns_query_new(m, &q, question, question, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH); r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH);
if (r < 0) if (r < 0)
return r; return r;

View File

@ -170,7 +170,7 @@ static void test_dnssec_verify_rfc8080_ed25519_example1(void) {
answer = dns_answer_new(1); answer = dns_answer_new(1);
assert_se(answer); assert_se(answer);
assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED) >= 0); assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0);
assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey, assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey,
rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0);
@ -262,7 +262,7 @@ static void test_dnssec_verify_rfc8080_ed25519_example2(void) {
answer = dns_answer_new(1); answer = dns_answer_new(1);
assert_se(answer); assert_se(answer);
assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED) >= 0); assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0);
assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey, assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey,
rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0);
@ -344,7 +344,7 @@ static void test_dnssec_verify_rrset(void) {
answer = dns_answer_new(1); answer = dns_answer_new(1);
assert_se(answer); assert_se(answer);
assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0); assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0);
/* Validate the RR as it if was 2015-12-2 today */ /* Validate the RR as it if was 2015-12-2 today */
assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0); assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0);
@ -436,7 +436,7 @@ static void test_dnssec_verify_rrset2(void) {
answer = dns_answer_new(1); answer = dns_answer_new(1);
assert_se(answer); assert_se(answer);
assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0); assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0);
/* Validate the RR as it if was 2015-12-11 today */ /* Validate the RR as it if was 2015-12-11 today */
assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0);
@ -563,10 +563,10 @@ static void test_dnssec_verify_rrset3(void) {
answer = dns_answer_new(4); answer = dns_answer_new(4);
assert_se(answer); assert_se(answer);
assert_se(dns_answer_add(answer, mx1, 0, DNS_ANSWER_AUTHENTICATED) >= 0); assert_se(dns_answer_add(answer, mx1, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0);
assert_se(dns_answer_add(answer, mx2, 0, DNS_ANSWER_AUTHENTICATED) >= 0); assert_se(dns_answer_add(answer, mx2, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0);
assert_se(dns_answer_add(answer, mx3, 0, DNS_ANSWER_AUTHENTICATED) >= 0); assert_se(dns_answer_add(answer, mx3, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0);
assert_se(dns_answer_add(answer, mx4, 0, DNS_ANSWER_AUTHENTICATED) >= 0); assert_se(dns_answer_add(answer, mx4, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0);
/* Validate the RR as it if was 2020-02-24 today */ /* Validate the RR as it if was 2020-02-24 today */
assert_se(dnssec_verify_rrset(answer, mx1->key, rrsig, dnskey, 1582534685*USEC_PER_SEC, &result) >= 0); assert_se(dnssec_verify_rrset(answer, mx1->key, rrsig, dnskey, 1582534685*USEC_PER_SEC, &result) >= 0);

View File

@ -127,6 +127,7 @@ FallbackLeaseLifetimeSec=
UseAddress= UseAddress=
UseNTP= UseNTP=
UseDNS= UseDNS=
UseFQDN=
RapidCommit= RapidCommit=
ForceDHCPv6PDOtherInformation= ForceDHCPv6PDOtherInformation=
PrefixDelegationHint= PrefixDelegationHint=