Merge pull request #2225 from poettering/dnssec7

Seventh DNSSEC patchset
This commit is contained in:
Tom Gundersen 2015-12-27 21:19:28 +01:00
commit f791b677a8
31 changed files with 1345 additions and 470 deletions

View file

@ -140,7 +140,8 @@ bool bitmap_isset(Bitmap *b, unsigned n) {
bool bitmap_isclear(Bitmap *b) {
unsigned i;
assert(b);
if (!b)
return true;
for (i = 0; i < b->n_bitmaps; i++)
if (b->bitmaps[i] != 0)
@ -150,7 +151,9 @@ bool bitmap_isclear(Bitmap *b) {
}
void bitmap_clear(Bitmap *b) {
assert(b);
if (!b)
return;
b->bitmaps = mfree(b->bitmaps);
b->n_bitmaps = 0;
@ -197,7 +200,10 @@ bool bitmap_equal(Bitmap *a, Bitmap *b) {
Bitmap *c;
unsigned i;
if (!a ^ !b)
if (a == b)
return true;
if (!a != !b)
return false;
if (!a)

View file

@ -72,6 +72,7 @@
#define BUS_ERROR_NO_RESOURCES "org.freedesktop.resolve1.NoResources"
#define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop"
#define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted"
#define BUS_ERROR_CONNECTION_FAILURE "org.freedesktop.resolve1.ConnectionFailure"
#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError."
#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer"

View file

@ -33,6 +33,7 @@
#include "parse-util.h"
#include "resolved-def.h"
#include "resolved-dns-packet.h"
#include "terminal-util.h"
#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
@ -42,7 +43,14 @@ static uint16_t arg_type = 0;
static uint16_t arg_class = 0;
static bool arg_legend = true;
static uint64_t arg_flags = 0;
static bool arg_resolve_service = false;
static enum {
MODE_RESOLVE_HOST,
MODE_RESOLVE_RECORD,
MODE_RESOLVE_SERVICE,
MODE_STATISTICS,
MODE_RESET_STATISTICS,
} arg_mode = MODE_RESOLVE_HOST;
static void print_source(uint64_t flags, usec_t rtt) {
char rtt_str[FORMAT_TIMESTAMP_MAX];
@ -368,7 +376,7 @@ static int resolve_record(sd_bus *bus, const char *name) {
while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
_cleanup_free_ char *s = NULL;
const char *s;
uint16_t c, t;
int ifindex;
const void *d;
@ -399,15 +407,13 @@ static int resolve_record(sd_bus *bus, const char *name) {
return log_oom();
r = dns_packet_read_rr(p, &rr, NULL, NULL);
if (r < 0) {
log_error("Failed to parse RR.");
return r;
}
if (r < 0)
return log_error_errno(r, "Failed to parse RR.");
r = dns_resource_record_to_string(rr, &s);
if (r < 0) {
s = dns_resource_record_to_string(rr);
if (!s) {
log_error("Failed to format RR.");
return r;
return -ENOMEM;
}
ifname[0] = 0;
@ -639,6 +645,121 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons
return 0;
}
static int show_statistics(sd_bus *bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
uint64_t n_current_transactions, n_total_transactions,
cache_size, n_cache_hit, n_cache_miss,
n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate;
int r;
assert(bus);
r = sd_bus_get_property(bus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"TransactionStatistics",
&error,
&reply,
"(tt)");
if (r < 0)
return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r));
r = sd_bus_message_read(reply, "(tt)",
&n_current_transactions,
&n_total_transactions);
if (r < 0)
return bus_log_parse_error(r);
printf("%sTransactions%s\n"
"Current Transactions: %" PRIu64 "\n"
" Total Transactions: %" PRIu64 "\n",
ansi_highlight(),
ansi_normal(),
n_current_transactions,
n_total_transactions);
reply = sd_bus_message_unref(reply);
r = sd_bus_get_property(bus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"CacheStatistics",
&error,
&reply,
"(ttt)");
r = sd_bus_message_read(reply, "(ttt)",
&cache_size,
&n_cache_hit,
&n_cache_miss);
if (r < 0)
return bus_log_parse_error(r);
printf("\n%sCache%s\n"
" Current Cache Size: %" PRIu64 "\n"
" Cache Hits: %" PRIu64 "\n"
" Cache Misses: %" PRIu64 "\n",
ansi_highlight(),
ansi_normal(),
cache_size,
n_cache_hit,
n_cache_miss);
reply = sd_bus_message_unref(reply);
r = sd_bus_get_property(bus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"DNSSECStatistics",
&error,
&reply,
"(tttt)");
r = sd_bus_message_read(reply, "(tttt)",
&n_dnssec_secure,
&n_dnssec_insecure,
&n_dnssec_bogus,
&n_dnssec_indeterminate);
if (r < 0)
return bus_log_parse_error(r);
printf("\n%sDNSSEC%s\n"
" Secure RRsets: %" PRIu64 "\n"
" Insecure RRsets: %" PRIu64 "\n"
" Bogus RRsets: %" PRIu64 "\n"
"Indeterminate RRsets: %" PRIu64 "\n",
ansi_highlight(),
ansi_normal(),
n_dnssec_secure,
n_dnssec_insecure,
n_dnssec_bogus,
n_dnssec_indeterminate);
return 0;
}
static int reset_statistics(sd_bus *bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
r = sd_bus_call_method(bus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager",
"ResetStatistics",
&error,
NULL,
NULL);
if (r < 0)
return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r));
return 0;
}
static void help_dns_types(void) {
int i;
const char *t;
@ -683,6 +804,8 @@ static void help(void) {
" --cname=BOOL Do [not] follow CNAME redirects\n"
" --search=BOOL Do [not] use search domains\n"
" --legend=BOOL Do [not] print column headers\n"
" --statistics Show resolver statistics\n"
" --reset-statistics Reset resolver statistics\n"
, program_invocation_short_name, program_invocation_short_name);
}
@ -695,20 +818,24 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SERVICE_ADDRESS,
ARG_SERVICE_TXT,
ARG_SEARCH,
ARG_STATISTICS,
ARG_RESET_STATISTICS,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "type", required_argument, NULL, 't' },
{ "class", required_argument, NULL, 'c' },
{ "legend", required_argument, NULL, ARG_LEGEND },
{ "protocol", required_argument, NULL, 'p' },
{ "cname", required_argument, NULL, ARG_CNAME },
{ "service", no_argument, NULL, ARG_SERVICE },
{ "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS },
{ "service-txt", required_argument, NULL, ARG_SERVICE_TXT },
{ "search", required_argument, NULL, ARG_SEARCH },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "type", required_argument, NULL, 't' },
{ "class", required_argument, NULL, 'c' },
{ "legend", required_argument, NULL, ARG_LEGEND },
{ "protocol", required_argument, NULL, 'p' },
{ "cname", required_argument, NULL, ARG_CNAME },
{ "service", no_argument, NULL, ARG_SERVICE },
{ "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS },
{ "service-txt", required_argument, NULL, ARG_SERVICE_TXT },
{ "search", required_argument, NULL, ARG_SEARCH },
{ "statistics", no_argument, NULL, ARG_STATISTICS, },
{ "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS },
{}
};
@ -765,6 +892,7 @@ static int parse_argv(int argc, char *argv[]) {
arg_type = (uint16_t) r;
assert((int) arg_type == r);
arg_mode = MODE_RESOLVE_RECORD;
break;
case 'c':
@ -808,7 +936,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_SERVICE:
arg_resolve_service = true;
arg_mode = MODE_RESOLVE_SERVICE;
break;
case ARG_CNAME:
@ -851,6 +979,14 @@ static int parse_argv(int argc, char *argv[]) {
arg_flags &= ~SD_RESOLVED_NO_SEARCH;
break;
case ARG_STATISTICS:
arg_mode = MODE_STATISTICS;
break;
case ARG_RESET_STATISTICS:
arg_mode = MODE_RESET_STATISTICS;
break;
case '?':
return -EINVAL;
@ -863,7 +999,7 @@ static int parse_argv(int argc, char *argv[]) {
return -EINVAL;
}
if (arg_type != 0 && arg_resolve_service) {
if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) {
log_error("--service and --type= may not be combined.");
return -EINVAL;
}
@ -885,20 +1021,57 @@ int main(int argc, char **argv) {
if (r <= 0)
goto finish;
if (optind >= argc) {
log_error("No arguments passed");
r = -EINVAL;
goto finish;
}
r = sd_bus_open_system(&bus);
if (r < 0) {
log_error_errno(r, "sd_bus_open_system: %m");
goto finish;
}
if (arg_resolve_service) {
switch (arg_mode) {
case MODE_RESOLVE_HOST:
if (optind >= argc) {
log_error("No arguments passed");
r = -EINVAL;
goto finish;
}
while (argv[optind]) {
int family, ifindex, k;
union in_addr_union a;
k = parse_address(argv[optind], &family, &a, &ifindex);
if (k >= 0)
k = resolve_address(bus, family, &a, ifindex);
else
k = resolve_host(bus, argv[optind]);
if (r == 0)
r = k;
optind++;
}
break;
case MODE_RESOLVE_RECORD:
if (optind >= argc) {
log_error("No arguments passed");
r = -EINVAL;
goto finish;
}
while (argv[optind]) {
int k;
k = resolve_record(bus, argv[optind]);
if (r == 0)
r = k;
optind++;
}
break;
case MODE_RESOLVE_SERVICE:
if (argc < optind + 1) {
log_error("Domain specification required.");
r = -EINVAL;
@ -916,27 +1089,27 @@ int main(int argc, char **argv) {
goto finish;
}
goto finish;
}
break;
while (argv[optind]) {
int family, ifindex, k;
union in_addr_union a;
if (arg_type != 0)
k = resolve_record(bus, argv[optind]);
else {
k = parse_address(argv[optind], &family, &a, &ifindex);
if (k >= 0)
k = resolve_address(bus, family, &a, ifindex);
else
k = resolve_host(bus, argv[optind]);
case MODE_STATISTICS:
if (argc > optind) {
log_error("Too many arguments.");
r = -EINVAL;
goto finish;
}
if (r == 0)
r = k;
r = show_statistics(bus);
break;
optind++;
case MODE_RESET_STATISTICS:
if (argc > optind) {
log_error("Too many arguments.");
r = -EINVAL;
goto finish;
}
r = reset_statistics(bus);
break;
}
finish:

View file

@ -95,6 +95,25 @@ bool dns_class_is_valid_rr(uint16_t class) {
return class != DNS_CLASS_ANY;
}
bool dns_type_may_redirect(uint16_t type) {
/* The following record types should never be redirected using
* CNAME/DNAME RRs. See
* <https://tools.ietf.org/html/rfc4035#section-2.5>. */
if (dns_type_is_pseudo(type))
return false;
return !IN_SET(type,
DNS_TYPE_CNAME,
DNS_TYPE_DNAME,
DNS_TYPE_NSEC3,
DNS_TYPE_NSEC,
DNS_TYPE_RRSIG,
DNS_TYPE_NXT,
DNS_TYPE_SIG,
DNS_TYPE_KEY);
}
const char *dns_class_to_string(uint16_t class) {
switch (class) {

View file

@ -128,6 +128,7 @@ enum {
bool dns_type_is_pseudo(uint16_t type);
bool dns_type_is_valid_query(uint16_t type);
bool dns_type_is_valid_rr(uint16_t type);
bool dns_type_may_redirect(uint16_t type);
bool dns_class_is_pseudo(uint16_t class);
bool dns_class_is_valid_rr(uint16_t class);

View file

@ -57,6 +57,9 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_RESOURCES:
return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_RESOURCES, "Not enough resources");
case DNS_TRANSACTION_CONNECTION_FAILURE:
return sd_bus_reply_method_errorf(q->request, BUS_ERROR_CONNECTION_FAILURE, "DNS server connection failure");
case DNS_TRANSACTION_ABORTED:
return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted");
@ -1214,16 +1217,101 @@ static int bus_property_get_search_domains(
return sd_bus_message_close_container(reply);
}
static int bus_property_get_transaction_statistics(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
Manager *m = userdata;
assert(reply);
assert(m);
return sd_bus_message_append(reply, "(tt)",
(uint64_t) hashmap_size(m->dns_transactions),
(uint64_t) m->n_transactions_total);
}
static int bus_property_get_cache_statistics(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
uint64_t size = 0, hit = 0, miss = 0;
Manager *m = userdata;
DnsScope *s;
assert(reply);
assert(m);
LIST_FOREACH(scopes, s, m->dns_scopes) {
size += dns_cache_size(&s->cache);
hit += s->cache.n_hit;
miss += s->cache.n_miss;
}
return sd_bus_message_append(reply, "(ttt)", size, hit, miss);
}
static int bus_property_get_dnssec_statistics(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
Manager *m = userdata;
assert(reply);
assert(m);
return sd_bus_message_append(reply, "(tttt)",
(uint64_t) m->n_dnssec_secure,
(uint64_t) m->n_dnssec_insecure,
(uint64_t) m->n_dnssec_bogus,
(uint64_t) m->n_dnssec_indeterminate);
}
static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
DnsScope *s;
assert(message);
assert(m);
LIST_FOREACH(scopes, s, m->dns_scopes)
s->cache.n_hit = s->cache.n_miss = 0;
m->n_transactions_total = 0;
m->n_dnssec_secure = m->n_dnssec_insecure = m->n_dnssec_bogus = m->n_dnssec_indeterminate = 0;
return sd_bus_reply_method_return(message, NULL);
}
static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0),
SD_BUS_PROPERTY("DNSServers", "a(iiay)", bus_property_get_dns_servers, 0, 0),
SD_BUS_PROPERTY("SearchDomains", "a(is)", bus_property_get_search_domains, 0, 0),
SD_BUS_PROPERTY("TransactionStatistics", "(tt)", bus_property_get_transaction_statistics, 0, 0),
SD_BUS_PROPERTY("CacheStatistics", "(ttt)", bus_property_get_cache_statistics, 0, 0),
SD_BUS_PROPERTY("DNSSECStatistics", "(tttt)", bus_property_get_dnssec_statistics, 0, 0),
SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0),
SD_BUS_VTABLE_END,
};

View file

@ -303,8 +303,8 @@ int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) {
}
int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) {
DnsResourceRecord *rr;
DnsAnswerFlags rr_flags;
DnsResourceRecord *rr, *soa = NULL;
DnsAnswerFlags rr_flags, soa_flags = 0;
int r;
assert(key);
@ -318,15 +318,29 @@ int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceReco
if (r < 0)
return r;
if (r > 0) {
if (ret)
*ret = rr;
if (flags)
*flags = rr_flags;
return 1;
if (soa) {
r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(soa->key));
if (r < 0)
return r;
if (r > 0)
continue;
}
soa = rr;
soa_flags = rr_flags;
}
}
return 0;
if (!soa)
return 0;
if (ret)
*ret = soa;
if (flags)
*flags = soa_flags;
return 1;
}
int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) {
@ -337,7 +351,7 @@ int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsR
assert(key);
/* For a {C,D}NAME record we can never find a matching {C,D}NAME record */
if (key->type == DNS_TYPE_CNAME || key->type == DNS_TYPE_DNAME)
if (!dns_type_may_redirect(key->type))
return 0;
DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) {
@ -643,18 +657,18 @@ int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) {
void dns_answer_dump(DnsAnswer *answer, FILE *f) {
DnsResourceRecord *rr;
DnsAnswerFlags flags;
int ifindex, r;
int ifindex;
if (!f)
f = stdout;
DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) {
_cleanup_free_ char *t = NULL;
const char *t;
fputc('\t', f);
r = dns_resource_record_to_string(rr, &t);
if (r < 0) {
t = dns_resource_record_to_string(rr);
if (!t) {
log_oom();
continue;
}

View file

@ -470,6 +470,14 @@ static int dns_cache_put_negative(
i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key));
if (!i->key)
return -ENOMEM;
/* Make sure to remove any previous entry for this
* specific ANY key. (For non-ANY keys the cache data
* is already cleared by the caller.) Note that we
* don't bother removing positive or NODATA cache
* items in this case, because it would either be slow
* or require explicit indexing by name */
dns_cache_remove_by_key(c, key);
} else
i->key = dns_resource_key_ref(key);
@ -607,7 +615,6 @@ int dns_cache_put(
/* See https://tools.ietf.org/html/rfc2308, which say that a
* matching SOA record in the packet is used to to enable
* negative caching. */
r = dns_answer_find_soa(answer, key, &soa, &flags);
if (r < 0)
goto fail;
@ -672,11 +679,7 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
if (i && i->type == DNS_CACHE_NXDOMAIN)
return i;
/* The following record types should never be redirected. See
* <https://tools.ietf.org/html/rfc4035#section-2.5>. */
if (!IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME,
DNS_TYPE_NSEC3, DNS_TYPE_NSEC, DNS_TYPE_RRSIG,
DNS_TYPE_NXT, DNS_TYPE_SIG, DNS_TYPE_KEY)) {
if (dns_type_may_redirect(k->type)) {
/* Check if we have a CNAME record instead */
i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n));
if (i)
@ -737,6 +740,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
log_debug("Ignoring cache for ANY lookup: %s", key_str);
}
c->n_miss++;
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
return 0;
@ -754,6 +759,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
log_debug("Cache miss for %s", key_str);
}
c->n_miss++;
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
return 0;
@ -791,9 +798,15 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
*rcode = DNS_RCODE_SUCCESS;
*authenticated = nsec->authenticated;
return !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_DNAME);
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_DNAME)) {
c->n_hit++;
return 1;
}
c->n_miss++;
return 0;
}
if (log_get_max_level() >= LOG_DEBUG) {
@ -808,6 +821,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
}
if (n <= 0) {
c->n_hit++;
*ret = NULL;
*rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
*authenticated = have_authenticated && !have_non_authenticated;
@ -827,6 +842,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
return r;
}
c->n_hit++;
*ret = answer;
*rcode = DNS_RCODE_SUCCESS;
*authenticated = have_authenticated && !have_non_authenticated;
@ -935,13 +952,13 @@ void dns_cache_dump(DnsCache *cache, FILE *f) {
DnsCacheItem *j;
LIST_FOREACH(by_key, j, i) {
_cleanup_free_ char *t = NULL;
fputc('\t', f);
if (j->rr) {
r = dns_resource_record_to_string(j->rr, &t);
if (r < 0) {
const char *t;
t = dns_resource_record_to_string(j->rr);
if (!t) {
log_oom();
continue;
}
@ -949,13 +966,14 @@ void dns_cache_dump(DnsCache *cache, FILE *f) {
fputs(t, f);
fputc('\n', f);
} else {
r = dns_resource_key_to_string(j->key, &t);
_cleanup_free_ char *z = NULL;
r = dns_resource_key_to_string(j->key, &z);
if (r < 0) {
log_oom();
continue;
}
fputs(t, f);
fputs(z, f);
fputs(" -- ", f);
fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f);
fputc('\n', f);
@ -970,3 +988,10 @@ bool dns_cache_is_empty(DnsCache *cache) {
return hashmap_isempty(cache->by_key);
}
unsigned dns_cache_size(DnsCache *cache) {
if (!cache)
return 0;
return hashmap_size(cache->by_key);
}

View file

@ -29,6 +29,8 @@
typedef struct DnsCache {
Hashmap *by_key;
Prioq *by_expiry;
unsigned n_hit;
unsigned n_miss;
} DnsCache;
#include "resolved-dns-answer.h"
@ -47,4 +49,6 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_
void dns_cache_dump(DnsCache *cache, FILE *f);
bool dns_cache_is_empty(DnsCache *cache);
unsigned dns_cache_size(DnsCache *cache);
int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p);

View file

@ -36,7 +36,7 @@
* TODO:
*
* - Make trust anchor store read additional DS+DNSKEY data from disk
* - wildcard zones compatibility
* - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
* - multi-label zone compatibility
* - cname/dname compatibility
* - per-interface DNSSEC setting
@ -384,10 +384,17 @@ int dnssec_verify_rrset(
gcry_md_write(md, wire_format_name, r);
for (k = 0; k < n; k++) {
const char *suffix;
size_t l;
rr = list[k];
r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true);
r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
if (r < 0)
goto finish;
if (r > 0) /* This is a wildcard! */
gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
gcry_md_write(md, wire_format_name, r);
@ -497,6 +504,8 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske
}
int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
int r;
assert(key);
assert(rrsig);
@ -509,6 +518,18 @@ int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig)
if (rrsig->rrsig.type_covered != key->type)
return 0;
/* Make sure signer is a parent of the RRset */
r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
if (r <= 0)
return r;
/* Make sure the owner name has at least as many labels as the "label" fields indicates. */
r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
if (r < 0)
return r;
if (r < rrsig->rrsig.labels)
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
}
@ -810,6 +831,15 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_
if (ds->key->type != DNS_TYPE_DS)
continue;
if (ds->key->class != dnskey->key->class)
continue;
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key));
if (r < 0)
return r;
if (r == 0)
continue;
r = dnssec_verify_dnskey(dnskey, ds);
if (r < 0)
return r;
@ -888,62 +918,138 @@ finish:
return r;
}
static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) {
const char *a, *b;
int r;
assert(rr);
if (rr->key->type != DNS_TYPE_NSEC3)
return 0;
/* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
if (!IN_SET(rr->nsec3.flags, 0, 1))
return 0;
if (!nsec3)
return 1;
/* If a second NSEC3 RR is specified, also check if they are from the same zone. */
if (nsec3 == rr) /* Shortcut */
return 1;
if (rr->key->class != nsec3->key->class)
return 0;
if (rr->nsec3.algorithm != nsec3->nsec3.algorithm)
return 0;
if (rr->nsec3.iterations != nsec3->nsec3.iterations)
return 0;
if (rr->nsec3.salt_size != nsec3->nsec3.salt_size)
return 0;
if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0)
return 0;
a = DNS_RESOURCE_KEY_NAME(rr->key);
r = dns_name_parent(&a); /* strip off hash */
if (r < 0)
return r;
if (r == 0)
return 0;
b = DNS_RESOURCE_KEY_NAME(nsec3->key);
r = dns_name_parent(&b); /* strip off hash */
if (r < 0)
return r;
if (r == 0)
return 0;
return dns_name_equal(a, b);
}
static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
_cleanup_free_ char *next_closer_domain = NULL, *l = NULL;
uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
const char *p, *pp = NULL;
DnsResourceRecord *rr;
const char *suffix, *p, *pp = NULL;
DnsResourceRecord *rr, *suffix_rr;
DnsAnswerFlags flags;
int hashed_size, r;
bool a;
assert(key);
assert(result);
assert(authenticated);
/* First step, look for the closest encloser NSEC3 RR in 'answer' that matches 'key' */
p = DNS_RESOURCE_KEY_NAME(key);
/* First step, look for the longest common suffix we find with any NSEC3 RR in the response. */
suffix = DNS_RESOURCE_KEY_NAME(key);
for (;;) {
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) {
_cleanup_free_ char *hashed_domain = NULL, *label = NULL;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
if (rr->key->type != DNS_TYPE_NSEC3)
continue;
/* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
if (!IN_SET(rr->nsec3.flags, 0, 1))
continue;
r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), p);
r = nsec3_is_good(suffix_rr, flags, NULL);
if (r < 0)
return r;
if (r == 0)
continue;
r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, suffix);
if (r < 0)
return r;
if (r > 0)
goto found_suffix;
}
/* Strip one label from the front */
r = dns_name_parent(&suffix);
if (r < 0)
return r;
if (r == 0)
break;
}
*result = DNSSEC_NSEC_NO_RR;
return 0;
found_suffix:
/* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
p = DNS_RESOURCE_KEY_NAME(key);
for (;;) {
_cleanup_free_ char *hashed_domain = NULL, *label = NULL;
hashed_size = dnssec_nsec3_hash(suffix_rr, p, hashed);
if (hashed_size == -EOPNOTSUPP) {
*result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
return 0;
}
if (hashed_size < 0)
return hashed_size;
label = base32hexmem(hashed, hashed_size, false);
if (!label)
return -ENOMEM;
hashed_domain = strjoin(label, ".", suffix, NULL);
if (!hashed_domain)
return -ENOMEM;
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
r = nsec3_is_good(rr, flags, suffix_rr);
if (r < 0)
return r;
if (r == 0)
continue;
hashed_size = dnssec_nsec3_hash(rr, p, hashed);
if (hashed_size == -EOPNOTSUPP) {
*result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
return 0;
}
if (hashed_size < 0)
return hashed_size;
if (rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
return -EBADMSG;
label = base32hexmem(hashed, hashed_size, false);
if (!label)
return -ENOMEM;
hashed_domain = strjoin(label, ".", p, NULL);
if (!hashed_domain)
return -ENOMEM;
continue;
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain);
if (r < 0)
return r;
if (r > 0)
goto found;
if (r > 0) {
a = flags & DNS_ANSWER_AUTHENTICATED;
goto found_closest_encloser;
}
}
/* We didn't find the closest encloser with this name,
@ -963,7 +1069,7 @@ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecR
*result = DNSSEC_NSEC_NO_RR;
return 0;
found:
found_closest_encloser:
/* We found a closest encloser in 'p'; next closer is 'pp' */
/* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
@ -981,6 +1087,7 @@ found:
if (!pp) {
/* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */
*result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
*authenticated = a;
return 0;
}
@ -1000,26 +1107,8 @@ found:
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
_cleanup_free_ char *label = NULL, *next_hashed_domain = NULL;
const char *nsec3_parent;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
if (rr->key->type != DNS_TYPE_NSEC3)
continue;
/* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
if (!IN_SET(rr->nsec3.flags, 0, 1))
continue;
nsec3_parent = DNS_RESOURCE_KEY_NAME(rr->key);
r = dns_name_parent(&nsec3_parent);
if (r < 0)
return r;
if (r == 0)
continue;
r = dns_name_equal(p, nsec3_parent);
r = nsec3_is_good(rr, flags, suffix_rr);
if (r < 0)
return r;
if (r == 0)
@ -1042,6 +1131,7 @@ found:
else
*result = DNSSEC_NSEC_NXDOMAIN;
*authenticated = a && (flags & DNS_ANSWER_AUTHENTICATED);
return 1;
}
}
@ -1050,7 +1140,7 @@ found:
return 0;
}
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
DnsResourceRecord *rr;
bool have_nsec3 = false;
DnsAnswerFlags flags;
@ -1058,6 +1148,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
assert(key);
assert(result);
assert(authenticated);
/* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
@ -1066,9 +1157,6 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
if (rr->key->class != key->class)
continue;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
switch (rr->key->type) {
case DNS_TYPE_NSEC:
@ -1078,6 +1166,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
return r;
if (r > 0) {
*result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
return 0;
}
@ -1086,6 +1175,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
return r;
if (r > 0) {
*result = DNSSEC_NSEC_NXDOMAIN;
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
return 0;
}
break;
@ -1098,7 +1188,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
/* OK, this was not sufficient. Let's see if NSEC3 can help. */
if (have_nsec3)
return dnssec_test_nsec3(answer, key, result);
return dnssec_test_nsec3(answer, key, result, authenticated);
/* No approproate NSEC RR found, report this. */
*result = DNSSEC_NSEC_NO_RR;
@ -1107,6 +1197,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
[DNSSEC_DOWNGRADE_OK] = "downgrade-ok",
[DNSSEC_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
@ -1121,5 +1212,6 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_UNSIGNED] = "unsigned",
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
[DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);

View file

@ -32,7 +32,14 @@ enum DnssecMode {
/* No DNSSEC validation is done */
DNSSEC_NO,
/* Validate locally, if the server knows DO, but if not, don't. Don't trust the AD bit */
/* Validate locally, if the server knows DO, but if not,
* don't. Don't trust the AD bit. If the server doesn't do
* DNSSEC properly, downgrade to non-DNSSEC operation. Of
* course, we then are vulnerable to a downgrade attack, but
* that's life and what is configured. */
DNSSEC_DOWNGRADE_OK,
/* Insist on DNSSEC server support, and rather fail than downgrading. */
DNSSEC_YES,
_DNSSEC_MODE_MAX,
@ -54,6 +61,8 @@ enum DnssecResult {
DNSSEC_UNSIGNED,
DNSSEC_FAILED_AUXILIARY,
DNSSEC_NSEC_MISMATCH,
DNSSEC_INCOMPATIBLE_SERVER,
_DNSSEC_RESULT_MAX,
_DNSSEC_RESULT_INVALID = -1
};
@ -89,7 +98,7 @@ typedef enum DnssecNsecResult {
DNSSEC_NSEC_OPTOUT,
} DnssecNsecResult;
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result);
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated);
const char* dnssec_mode_to_string(DnssecMode m) _const_;
DnssecMode dnssec_mode_from_string(const char *s) _pure_;

View file

@ -58,6 +58,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
p->size = p->rindex = DNS_PACKET_HEADER_SIZE;
p->allocated = a;
p->protocol = protocol;
p->opt_start = p->opt_size = (size_t) -1;
p->n_ref = 1;
*ret = p;
@ -499,7 +500,7 @@ int dns_packet_append_name(
saved_size = p->size;
while (*name) {
_cleanup_free_ char *s = NULL;
const char *z = name;
char label[DNS_LABEL_MAX];
size_t n = 0;
int k;
@ -518,12 +519,6 @@ int dns_packet_append_name(
}
}
s = strdup(name);
if (!s) {
r = -ENOMEM;
goto fail;
}
r = dns_label_unescape(&name, label, sizeof(label));
if (r < 0)
goto fail;
@ -544,6 +539,14 @@ int dns_packet_append_name(
goto fail;
if (allow_compression) {
_cleanup_free_ char *s = NULL;
s = strdup(z);
if (!s) {
r = -ENOMEM;
goto fail;
}
r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops);
if (r < 0)
goto fail;
@ -643,7 +646,6 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) {
int r;
assert(p);
assert(types);
saved_size = p->size;
@ -680,7 +682,7 @@ fail:
}
/* Append the OPT pseudo-RR described in RFC6891 */
int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) {
int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) {
size_t saved_size;
int r;
@ -688,6 +690,11 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do,
/* we must never advertise supported packet size smaller than the legacy max */
assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX);
if (p->opt_start != (size_t) -1)
return -EBUSY;
assert(p->opt_size == (size_t) -1);
saved_size = p->size;
/* empty name */
@ -720,6 +727,11 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do,
if (r < 0)
goto fail;
DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1);
p->opt_start = saved_size;
p->opt_size = p->size - saved_size;
if (start)
*start = saved_size;
@ -730,6 +742,27 @@ fail:
return r;
}
int dns_packet_truncate_opt(DnsPacket *p) {
assert(p);
if (p->opt_start == (size_t) -1) {
assert(p->opt_size == (size_t) -1);
return 0;
}
assert(p->opt_size != (size_t) -1);
assert(DNS_PACKET_ARCOUNT(p) > 0);
if (p->opt_start + p->opt_size != p->size)
return -EBUSY;
dns_packet_truncate(p, p->opt_start);
DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1);
p->opt_start = p->opt_size = (size_t) -1;
return 1;
}
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) {
size_t saved_size, rdlength_offset, end, rdlength, rds;
int r;
@ -1045,7 +1078,6 @@ fail:
return r;
}
int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) {
assert(p);

View file

@ -76,6 +76,7 @@ struct DnsPacket {
size_t size, allocated, rindex;
void *_data; /* don't access directly, use DNS_PACKET_DATA()! */
Hashmap *names; /* For name compression */
size_t opt_start, opt_size;
/* Parsed data */
DnsQuestion *question;
@ -173,9 +174,10 @@ int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonica
int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start);
int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start);
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start);
int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start);
int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start);
void dns_packet_truncate(DnsPacket *p, size_t sz);
int dns_packet_truncate_opt(DnsPacket *p);
int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start);
int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start);

View file

@ -1039,8 +1039,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
if (state == DNS_TRANSACTION_SUCCESS)
continue;
dns_answer_unref(q->answer);
q->answer = dns_answer_ref(t->answer);
q->answer = dns_answer_unref(q->answer);
q->answer_rcode = t->answer_rcode;
q->answer_dnssec_result = t->answer_dnssec_result;

View file

@ -261,16 +261,13 @@ int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *
/* Checks whether 'soa' is a SOA record for the specified key. */
if (soa->class != DNS_CLASS_IN)
if (soa->class != key->class)
return 0;
if (soa->type != DNS_TYPE_SOA)
return 0;
if (!dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa)))
return 0;
return 1;
return dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa));
}
static void dns_resource_key_hash_func(const void *i, struct siphash *state) {
@ -451,6 +448,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) {
dns_resource_key_unref(rr->key);
}
free(rr->to_string);
free(rr);
return NULL;
@ -766,16 +764,19 @@ static char *format_txt(DnsTxtItem *first) {
return s;
}
int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
_cleanup_free_ char *k = NULL, *t = NULL;
char *s;
int r;
assert(rr);
if (rr->to_string)
return rr->to_string;
r = dns_resource_key_to_string(rr->key, &k);
if (r < 0)
return r;
return NULL;
switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) {
@ -787,7 +788,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->srv.port,
strna(rr->srv.name));
if (r < 0)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_PTR:
@ -796,25 +797,25 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
case DNS_TYPE_DNAME:
s = strjoin(k, " ", rr->ptr.name, NULL);
if (!s)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_HINFO:
s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL);
if (!s)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT:
t = format_txt(rr->txt.items);
if (!t)
return -ENOMEM;
return NULL;
s = strjoin(k, " ", t, NULL);
if (!s)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_A: {
@ -822,22 +823,22 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x);
if (r < 0)
return r;
return NULL;
s = strjoin(k, " ", x, NULL);
if (!s)
return -ENOMEM;
return NULL;
break;
}
case DNS_TYPE_AAAA:
r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t);
if (r < 0)
return r;
return NULL;
s = strjoin(k, " ", t, NULL);
if (!s)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_SOA:
@ -851,7 +852,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->soa.expire,
rr->soa.minimum);
if (r < 0)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_MX:
@ -860,7 +861,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->mx.priority,
rr->mx.exchange);
if (r < 0)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_LOC:
@ -873,17 +874,17 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->loc.horiz_pre,
rr->loc.vert_pre);
if (!t)
return -ENOMEM;
return NULL;
s = strjoin(k, " ", t, NULL);
if (!s)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_DS:
t = hexmem(rr->ds.digest, rr->ds.digest_size);
if (!t)
return -ENOMEM;
return NULL;
r = asprintf(&s, "%s %u %u %u %s",
k,
@ -892,13 +893,13 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->ds.digest_type,
t);
if (r < 0)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_SSHFP:
t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size);
if (!t)
return -ENOMEM;
return NULL;
r = asprintf(&s, "%s %u %u %s",
k,
@ -906,7 +907,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->sshfp.fptype,
t);
if (r < 0)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_DNSKEY: {
@ -916,7 +917,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
t = base64mem(rr->dnskey.key, rr->dnskey.key_size);
if (!t)
return -ENOMEM;
return NULL;
r = asprintf(&s, "%s %u %u %.*s%.*u %s",
k,
@ -926,7 +927,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
alg ? 0 : 1, alg ? 0u : (unsigned) rr->dnskey.algorithm,
t);
if (r < 0)
return -ENOMEM;
return NULL;
break;
}
@ -939,15 +940,15 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
t = base64mem(rr->rrsig.signature, rr->rrsig.signature_size);
if (!t)
return -ENOMEM;
return NULL;
r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration);
if (r < 0)
return r;
return NULL;
r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception);
if (r < 0)
return r;
return NULL;
/* TYPE?? follows
* http://tools.ietf.org/html/rfc3597#section-5 */
@ -966,21 +967,21 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->rrsig.signer,
t);
if (r < 0)
return -ENOMEM;
return NULL;
break;
}
case DNS_TYPE_NSEC:
t = format_types(rr->nsec.types);
if (!t)
return -ENOMEM;
return NULL;
r = asprintf(&s, "%s %s %s",
k,
rr->nsec.next_domain_name,
t);
if (r < 0)
return -ENOMEM;
return NULL;
break;
case DNS_TYPE_NSEC3: {
@ -989,16 +990,16 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
if (rr->nsec3.salt_size > 0) {
salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size);
if (!salt)
return -ENOMEM;
return NULL;
}
hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false);
if (!hash)
return -ENOMEM;
return NULL;
t = format_types(rr->nsec3.types);
if (!t)
return -ENOMEM;
return NULL;
r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s",
k,
@ -1009,7 +1010,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
hash,
t);
if (r < 0)
return -ENOMEM;
return NULL;
break;
}
@ -1017,16 +1018,16 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
default:
t = hexmem(rr->generic.data, rr->generic.size);
if (!t)
return -ENOMEM;
return NULL;
r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.size, t);
if (r < 0)
return -ENOMEM;
return NULL;
break;
}
*ret = s;
return 0;
rr->to_string = s;
return s;
}
int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) {

View file

@ -95,6 +95,7 @@ struct DnsTxtItem {
struct DnsResourceRecord {
unsigned n_ref;
DnsResourceKey *key;
char *to_string;
uint32_t ttl;
bool unparseable:1;
bool wire_format_canonical:1;
@ -253,7 +254,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr);
int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name);
int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name);
int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b);
int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret);
const char* dns_resource_record_to_string(DnsResourceRecord *rr);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref);
int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical);

View file

@ -162,17 +162,15 @@ void dns_scope_packet_lost(DnsScope *s, usec_t usec) {
s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC);
}
static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
union in_addr_union addr;
int ifindex = 0, r;
int family;
uint32_t mtu;
size_t saved_size = 0;
assert(s);
assert(p);
assert(p->protocol == s->protocol);
assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0));
if (s->link) {
mtu = s->link->mtu;
@ -181,30 +179,13 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket
mtu = manager_find_mtu(s->manager);
switch (s->protocol) {
case DNS_PROTOCOL_DNS:
assert(server);
assert(fd >= 0);
if (DNS_PACKET_QDCOUNT(p) > 1)
return -EOPNOTSUPP;
if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_EDNS0) {
bool edns_do;
size_t packet_size;
edns_do = server->possible_features >= DNS_SERVER_FEATURE_LEVEL_DO;
if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_LARGE)
packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
else
packet_size = server->received_udp_packet_max;
r = dns_packet_append_opt_rr(p, packet_size, edns_do, &saved_size);
if (r < 0)
return r;
DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) + 1);
}
if (p->size > DNS_PACKET_UNICAST_SIZE_MAX)
return -EMSGSIZE;
@ -215,15 +196,11 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket
if (r < 0)
return r;
if (saved_size > 0) {
dns_packet_truncate(p, saved_size);
DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) - 1);
}
break;
case DNS_PROTOCOL_LLMNR:
assert(fd < 0);
if (DNS_PACKET_QDCOUNT(p) > 1)
return -EOPNOTSUPP;
@ -250,6 +227,8 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket
break;
case DNS_PROTOCOL_MDNS:
assert(fd < 0);
if (!ratelimit_test(&s->ratelimit))
return -EBUSY;
@ -279,13 +258,13 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket
return 1;
}
int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) {
int r;
assert(s);
assert(p);
assert(p->protocol == s->protocol);
assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0));
assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0));
do {
/* If there are multiple linked packets, set the TC bit in all but the last of them */
@ -294,18 +273,24 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
dns_packet_set_flags(p, true, true);
}
r = dns_scope_emit_one(s, fd, server, p);
r = dns_scope_emit_one(s, fd, p);
if (r < 0)
return r;
p = p->more;
} while(p);
} while (p);
return 0;
}
static int dns_scope_socket(DnsScope *s, int type, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) {
DnsServer *srv = NULL;
static int dns_scope_socket(
DnsScope *s,
int type,
int family,
const union in_addr_union *address,
DnsServer *server,
uint16_t port) {
_cleanup_close_ int fd = -1;
union sockaddr_union sa = {};
socklen_t salen;
@ -313,31 +298,27 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad
int ret, r;
assert(s);
assert((family == AF_UNSPEC) == !address);
if (family == AF_UNSPEC) {
srv = dns_scope_get_dns_server(s);
if (!srv)
return -ESRCH;
if (server) {
assert(family == AF_UNSPEC);
assert(!address);
srv->possible_features = dns_server_possible_features(srv);
if (type == SOCK_DGRAM && srv->possible_features < DNS_SERVER_FEATURE_LEVEL_UDP)
return -EAGAIN;
sa.sa.sa_family = srv->family;
if (srv->family == AF_INET) {
sa.sa.sa_family = server->family;
if (server->family == AF_INET) {
sa.in.sin_port = htobe16(port);
sa.in.sin_addr = srv->address.in;
sa.in.sin_addr = server->address.in;
salen = sizeof(sa.in);
} else if (srv->family == AF_INET6) {
} else if (server->family == AF_INET6) {
sa.in6.sin6_port = htobe16(port);
sa.in6.sin6_addr = srv->address.in6;
sa.in6.sin6_addr = server->address.in6;
sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
salen = sizeof(sa.in6);
} else
return -EAFNOSUPPORT;
} else {
assert(family != AF_UNSPEC);
assert(address);
sa.sa.sa_family = family;
if (family == AF_INET) {
@ -395,21 +376,18 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad
if (r < 0 && errno != EINPROGRESS)
return -errno;
if (server)
*server = srv;
ret = fd;
fd = -1;
return ret;
}
int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server) {
return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, 53, server);
int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) {
return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port);
}
int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) {
return dns_scope_socket(s, SOCK_STREAM, family, address, port, server);
int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) {
return dns_scope_socket(s, SOCK_STREAM, family, address, server, port);
}
DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) {
@ -868,7 +846,7 @@ static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata
return 0;
}
r = dns_scope_emit(scope, -1, NULL, p);
r = dns_scope_emit_udp(scope, -1, p);
if (r < 0)
log_debug_errno(r, "Failed to send conflict packet: %m");
}

View file

@ -82,9 +82,9 @@ DnsScope* dns_scope_free(DnsScope *s);
void dns_scope_packet_received(DnsScope *s, usec_t rtt);
void dns_scope_packet_lost(DnsScope *s, usec_t usec);
int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p);
int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server);
int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server);
int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p);
int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port);
int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port);
DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain);
int dns_scope_good_key(DnsScope *s, DnsResourceKey *key);

View file

@ -68,8 +68,8 @@ int dns_server_new(
s->n_ref = 1;
s->manager = m;
s->verified_features = _DNS_SERVER_FEATURE_LEVEL_INVALID;
s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST;
s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC;
s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX;
s->type = type;
@ -224,23 +224,25 @@ void dns_server_move_back_and_unmark(DnsServer *s) {
}
}
void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size) {
void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel level, usec_t rtt, size_t size) {
assert(s);
if (features == DNS_SERVER_FEATURE_LEVEL_LARGE) {
/* even if we successfully receive a reply to a request announcing
support for large packets, that does not mean we can necessarily
receive large packets. */
if (s->verified_features < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) {
s->verified_features = DNS_SERVER_FEATURE_LEVEL_LARGE - 1;
if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) {
/* Even if we successfully receive a reply to a
request announcing support for large packets, that
does not mean we can necessarily receive large
packets. */
if (s->verified_feature_level < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) {
s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1;
assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
}
} else if (s->verified_features < features) {
s->verified_features = features;
} else if (s->verified_feature_level < level) {
s->verified_feature_level = level;
assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
}
if (s->possible_features == features)
if (s->possible_feature_level == level)
s->n_failed_attempts = 0;
/* Remember the size of the largest UDP packet we received from a server,
@ -255,11 +257,11 @@ void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, us
}
}
void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec) {
void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel level, usec_t usec) {
assert(s);
assert(s->manager);
if (s->possible_features == features)
if (s->possible_feature_level == level)
s->n_failed_attempts ++;
if (s->resend_timeout > usec)
@ -268,16 +270,27 @@ void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t
s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC);
}
void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features) {
void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) {
assert(s);
assert(s->manager);
if (s->possible_features != features)
if (s->possible_feature_level != level)
return;
s->n_failed_attempts = (unsigned) -1;
}
void dns_server_packet_rrsig_missing(DnsServer *s) {
_cleanup_free_ char *ip = NULL;
assert(s);
assert(s->manager);
in_addr_to_string(s->family, &s->address, &ip);
log_warning("DNS server %s does not augment replies with RRSIG records, DNSSEC not available.", strna(ip));
s->rrsig_missing = true;
}
static bool dns_server_grace_period_expired(DnsServer *s) {
usec_t ts;
@ -297,35 +310,64 @@ static bool dns_server_grace_period_expired(DnsServer *s) {
return true;
}
DnsServerFeatureLevel dns_server_possible_features(DnsServer *s) {
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
assert(s);
if (s->possible_features != DNS_SERVER_FEATURE_LEVEL_BEST &&
if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST &&
dns_server_grace_period_expired(s)) {
_cleanup_free_ char *ip = NULL;
s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST;
s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
s->n_failed_attempts = 0;
s->verified_usec = 0;
s->rrsig_missing = false;
in_addr_to_string(s->family, &s->address, &ip);
log_info("Grace period over, resuming full feature set for DNS server %s", strna(ip));
} else if (s->possible_features <= s->verified_features)
s->possible_features = s->verified_features;
} else if (s->possible_feature_level <= s->verified_feature_level)
s->possible_feature_level = s->verified_feature_level;
else if (s->n_failed_attempts >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
s->possible_features > DNS_SERVER_FEATURE_LEVEL_WORST) {
s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_WORST) {
_cleanup_free_ char *ip = NULL;
s->possible_features --;
s->possible_feature_level --;
s->n_failed_attempts = 0;
s->verified_usec = 0;
in_addr_to_string(s->family, &s->address, &ip);
log_warning("Using degraded feature set (%s) for DNS server %s",
dns_server_feature_level_to_string(s->possible_features), strna(ip));
dns_server_feature_level_to_string(s->possible_feature_level), strna(ip));
}
return s->possible_features;
return s->possible_feature_level;
}
int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) {
size_t packet_size;
bool edns_do;
int r;
assert(server);
assert(packet);
assert(packet->protocol == DNS_PROTOCOL_DNS);
/* Fix the OPT field in the packet to match our current feature level. */
r = dns_packet_truncate_opt(packet);
if (r < 0)
return r;
if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0)
return 0;
edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO;
if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE)
packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
else
packet_size = server->received_udp_packet_max;
return dns_packet_append_opt(packet, packet_size, edns_do, NULL);
}
static void dns_server_hash_func(const void *p, struct siphash *state) {

View file

@ -61,18 +61,25 @@ struct DnsServer {
int family;
union in_addr_union address;
bool marked:1;
usec_t resend_timeout;
usec_t max_rtt;
DnsServerFeatureLevel verified_features;
DnsServerFeatureLevel possible_features;
DnsServerFeatureLevel verified_feature_level;
DnsServerFeatureLevel possible_feature_level;
size_t received_udp_packet_max;
unsigned n_failed_attempts;
usec_t verified_usec;
usec_t features_grace_period_usec;
/* Indicates whether responses are augmented with RRSIG by
* server or not. Note that this is orthogonal to the feature
* level stuff, as it's only information describing responses,
* and has no effect on how the questions are asked. */
bool rrsig_missing:1;
/* Used when GC'ing old DNS servers when configuration changes. */
bool marked:1;
/* If linked is set, then this server appears in the servers linked list */
bool linked:1;
LIST_FIELDS(DnsServer, servers);
@ -92,9 +99,14 @@ DnsServer* dns_server_unref(DnsServer *s);
void dns_server_unlink(DnsServer *s);
void dns_server_move_back_and_unmark(DnsServer *s);
void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size);
void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec);
void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features);
void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel level, usec_t rtt, size_t size);
void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel level, usec_t usec);
void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_rrsig_missing(DnsServer *s);
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s);
int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level);
DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr);
@ -110,6 +122,4 @@ void manager_next_dns_server(Manager *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref);
DnsServerFeatureLevel dns_server_possible_features(DnsServer *s);
extern const struct hash_ops dns_server_hash_ops;

View file

@ -347,7 +347,6 @@ DnsStream *dns_stream_free(DnsStream *s) {
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free);
int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
static const int one = 1;
_cleanup_(dns_stream_freep) DnsStream *s = NULL;
int r;
@ -364,10 +363,6 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
s->fd = -1;
s->protocol = protocol;
r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
if (r < 0)
return -errno;
r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s);
if (r < 0)
return r;

View file

@ -29,6 +29,35 @@
#include "resolved-llmnr.h"
#include "string-table.h"
static void dns_transaction_reset_answer(DnsTransaction *t) {
assert(t);
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
t->answer_rcode = 0;
t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
t->answer_authenticated = false;
}
static void dns_transaction_close_connection(DnsTransaction *t) {
assert(t);
t->stream = dns_stream_free(t->stream);
t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source);
t->dns_udp_fd = safe_close(t->dns_udp_fd);
}
static void dns_transaction_stop(DnsTransaction *t) {
assert(t);
t->timeout_event_source = sd_event_source_unref(t->timeout_event_source);
t->stream = dns_stream_free(t->stream);
/* Note that we do not drop the UDP socket here, as we want to
* reuse it to repeat the interaction. */
}
DnsTransaction* dns_transaction_free(DnsTransaction *t) {
DnsQueryCandidate *c;
DnsZoneItem *i;
@ -37,18 +66,13 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
if (!t)
return NULL;
sd_event_source_unref(t->timeout_event_source);
dns_transaction_close_connection(t);
dns_transaction_stop(t);
dns_packet_unref(t->sent);
dns_packet_unref(t->received);
dns_answer_unref(t->answer);
sd_event_source_unref(t->dns_udp_event_source);
safe_close(t->dns_udp_fd);
dns_transaction_reset_answer(t);
dns_server_unref(t->server);
dns_stream_free(t->stream);
if (t->scope) {
hashmap_remove_value(t->scope->transactions_by_key, t->key, t);
@ -58,8 +82,6 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id));
}
dns_resource_key_unref(t->key);
while ((c = set_steal_first(t->notify_query_candidates)))
set_remove(c->transactions, t);
set_free(t->notify_query_candidates);
@ -79,8 +101,9 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
set_free(t->dnssec_transactions);
dns_answer_unref(t->validated_keys);
dns_resource_key_unref(t->key);
free(t->key_string);
free(t);
return NULL;
}
@ -153,6 +176,8 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
LIST_PREPEND(transactions_by_scope, s->transactions, t);
t->scope = s;
s->manager->n_transactions_total ++;
if (ret)
*ret = t;
@ -161,16 +186,6 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
return 0;
}
static void dns_transaction_stop(DnsTransaction *t) {
assert(t);
t->timeout_event_source = sd_event_source_unref(t->timeout_event_source);
t->stream = dns_stream_free(t->stream);
/* Note that we do not drop the UDP socket here, as we want to
* reuse it to repeat the interaction. */
}
static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
_cleanup_free_ char *pretty = NULL;
DnsZoneItem *z;
@ -224,6 +239,14 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
assert(t);
assert(!DNS_TRANSACTION_IS_LIVE(state));
if (state == DNS_TRANSACTION_DNSSEC_FAILED)
log_struct(LOG_NOTICE,
LOG_MESSAGE("DNSSEC validation failed for question %s: %s", dns_transaction_key_string(t), dnssec_result_to_string(t->answer_dnssec_result)),
"DNS_TRANSACTION=%" PRIu16, t->id,
"DNS_QUESTION=%s", dns_transaction_key_string(t),
"DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result),
NULL);
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function. */
@ -240,6 +263,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
t->state = state;
dns_transaction_close_connection(t);
dns_transaction_stop(t);
/* Notify all queries that are interested, but make sure the
@ -284,6 +308,27 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
dns_transaction_gc(t);
}
static int dns_transaction_pick_server(DnsTransaction *t) {
DnsServer *server;
assert(t);
assert(t->scope->protocol == DNS_PROTOCOL_DNS);
server = dns_scope_get_dns_server(t->scope);
if (!server)
return -ESRCH;
t->current_features = dns_server_possible_feature_level(server);
if (server == t->server)
return 0;
dns_server_unref(t->server);
t->server = dns_server_ref(server);
return 1;
}
static int on_stream_complete(DnsStream *s, int error) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
DnsTransaction *t;
@ -298,6 +343,11 @@ static int on_stream_complete(DnsStream *s, int error) {
t->stream = dns_stream_free(t->stream);
if (IN_SET(error, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE)) {
dns_transaction_complete(t, DNS_TRANSACTION_CONNECTION_FAILURE);
return 0;
}
if (error != 0) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
return 0;
@ -315,32 +365,43 @@ static int on_stream_complete(DnsStream *s, int error) {
dns_transaction_process_reply(t, p);
t->block_gc--;
/* If the response wasn't useful, then complete the transition now */
/* If the response wasn't useful, then complete the transition
* now. After all, we are the worst feature set now with TCP
* sockets, and there's really no point in retrying. */
if (t->state == DNS_TRANSACTION_PENDING)
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
else
dns_transaction_gc(t);
return 0;
}
static int dns_transaction_open_tcp(DnsTransaction *t) {
DnsServer *server = NULL;
_cleanup_close_ int fd = -1;
int r;
assert(t);
if (t->stream)
return 0;
dns_transaction_close_connection(t);
switch (t->scope->protocol) {
case DNS_PROTOCOL_DNS:
fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53, &server);
r = dns_transaction_pick_server(t);
if (r < 0)
return r;
r = dns_server_adjust_opt(t->server, t->sent, t->current_features);
if (r < 0)
return r;
fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53);
break;
case DNS_PROTOCOL_LLMNR:
/* When we already received a reply to this (but it was truncated), send to its sender address */
if (t->received)
fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL);
fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port);
else {
union in_addr_union address;
int family = AF_UNSPEC;
@ -357,7 +418,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
if (family != t->scope->family)
return -ESRCH;
fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT, NULL);
fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT);
}
break;
@ -372,7 +433,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd);
if (r < 0)
return r;
fd = -1;
r = dns_stream_write_packet(t->stream, t->sent);
@ -381,11 +441,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
return r;
}
dns_server_unref(t->server);
t->server = dns_server_ref(server);
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
t->answer_rcode = 0;
t->stream->complete = on_stream_complete;
t->stream->transaction = t;
@ -395,19 +450,13 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
if (t->scope->link)
t->stream->ifindex = t->scope->link->ifindex;
dns_transaction_reset_answer(t);
t->tried_stream = true;
return 0;
}
static void dns_transaction_next_dns_server(DnsTransaction *t) {
assert(t);
t->server = dns_server_unref(t->server);
t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source);
t->dns_udp_fd = safe_close(t->dns_udp_fd);
dns_scope_next_dns_server(t->scope);
}
static void dns_transaction_cache_answer(DnsTransaction *t) {
assert(t);
@ -463,10 +512,19 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) {
return;
}
if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER &&
t->scope->dnssec_mode == DNSSEC_YES) {
/* We are not in automatic downgrade mode, and the
* server is bad, refuse operation. */
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return;
}
if (!IN_SET(t->answer_dnssec_result,
_DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */
DNSSEC_VALIDATED, /* Answer is signed and validated successfully */
DNSSEC_UNSIGNED)) { /* Answer is right-fully unsigned */
_DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */
DNSSEC_VALIDATED, /* Answer is signed and validated successfully */
DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */
DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return;
}
@ -485,10 +543,12 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
assert(t);
assert(p);
assert(t->state == DNS_TRANSACTION_PENDING);
assert(t->scope);
assert(t->scope->manager);
if (t->state != DNS_TRANSACTION_PENDING)
return;
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function. */
@ -618,16 +678,16 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
}
/* On DNS, couldn't send? Try immediately again, with a new server */
dns_transaction_next_dns_server(t);
dns_scope_next_dns_server(t->scope);
r = dns_transaction_go(t);
if (r < 0) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
return;
}
return;
}
return;
}
/* Parse message, if it isn't parsed yet. */
@ -654,6 +714,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
dns_answer_unref(t->answer);
t->answer = dns_answer_ref(p->answer);
t->answer_rcode = DNS_PACKET_RCODE(p);
t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
t->answer_authenticated = false;
r = dns_transaction_request_dnssec_keys(t);
@ -688,39 +749,54 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use
DNS_PACKET_ID(p) == t->id)
dns_transaction_process_reply(t, p);
else
log_debug("Invalid DNS packet, ignoring.");
log_debug("Invalid DNS UDP packet, ignoring.");
return 0;
}
static int dns_transaction_emit(DnsTransaction *t) {
static int dns_transaction_emit_udp(DnsTransaction *t) {
int r;
assert(t);
if (t->scope->protocol == DNS_PROTOCOL_DNS && !t->server) {
DnsServer *server = NULL;
_cleanup_close_ int fd = -1;
if (t->scope->protocol == DNS_PROTOCOL_DNS) {
fd = dns_scope_udp_dns_socket(t->scope, &server);
if (fd < 0)
return fd;
r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t);
r = dns_transaction_pick_server(t);
if (r < 0)
return r;
t->dns_udp_fd = fd;
fd = -1;
t->server = dns_server_ref(server);
}
if (t->current_features < DNS_SERVER_FEATURE_LEVEL_UDP)
return -EAGAIN;
r = dns_scope_emit(t->scope, t->dns_udp_fd, t->server, t->sent);
if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */
int fd;
dns_transaction_close_connection(t);
fd = dns_scope_socket_udp(t->scope, t->server, 53);
if (fd < 0)
return fd;
r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t);
if (r < 0) {
safe_close(fd);
return r;
}
t->dns_udp_fd = fd;
}
r = dns_server_adjust_opt(t->server, t->sent, t->current_features);
if (r < 0)
return r;
} else
dns_transaction_close_connection(t);
r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent);
if (r < 0)
return r;
if (t->server)
t->current_features = t->server->possible_features;
dns_transaction_reset_answer(t);
return 0;
}
@ -735,17 +811,17 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) {
/* Timeout reached? Increase the timeout for the server used */
switch (t->scope->protocol) {
case DNS_PROTOCOL_DNS:
assert(t->server);
dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec);
break;
case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_MDNS:
dns_scope_packet_lost(t->scope, usec - t->start_usec);
break;
default:
assert_not_reached("Invalid DNS protocol.");
}
@ -757,7 +833,7 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
log_debug("Timeout reached on transaction %" PRIu16 ".", t->id);
/* ...and try again with a new server */
dns_transaction_next_dns_server(t);
dns_scope_next_dns_server(t->scope);
r = dns_transaction_go(t);
if (r < 0)
@ -771,28 +847,28 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) {
assert(t->scope);
switch (t->scope->protocol) {
case DNS_PROTOCOL_DNS:
assert(t->server);
return t->server->resend_timeout;
case DNS_PROTOCOL_MDNS:
assert(t->n_attempts > 0);
return (1 << (t->n_attempts - 1)) * USEC_PER_SEC;
case DNS_PROTOCOL_LLMNR:
return t->scope->resend_timeout;
default:
assert_not_reached("Invalid DNS protocol.");
}
}
static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
bool had_stream;
int r;
assert(t);
had_stream = !!t->stream;
dns_transaction_stop(t);
if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) {
@ -800,7 +876,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
return 0;
}
if (t->scope->protocol == DNS_PROTOCOL_LLMNR && had_stream) {
if (t->scope->protocol == DNS_PROTOCOL_LLMNR && t->tried_stream) {
/* If we already tried via a stream, then we don't
* retry on LLMNR. See RFC 4795, Section 2.7. */
dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
@ -809,10 +885,8 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
t->n_attempts++;
t->start_usec = ts;
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
t->answer_rcode = 0;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
dns_transaction_reset_answer(t);
/* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */
if (t->scope->protocol == DNS_PROTOCOL_DNS) {
@ -986,7 +1060,7 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
if (t->sent)
return 0;
r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO);
if (r < 0)
return r;
@ -1041,10 +1115,12 @@ int dns_transaction_go(DnsTransaction *t) {
random_bytes(&jitter, sizeof(jitter));
switch (t->scope->protocol) {
case DNS_PROTOCOL_LLMNR:
jitter %= LLMNR_JITTER_INTERVAL_USEC;
accuracy = LLMNR_JITTER_INTERVAL_USEC;
break;
case DNS_PROTOCOL_MDNS:
jitter %= MDNS_JITTER_RANGE_USEC;
jitter += MDNS_JITTER_MIN_USEC;
@ -1093,7 +1169,7 @@ int dns_transaction_go(DnsTransaction *t) {
} else {
/* Try via UDP, and if that fails due to large size or lack of
* support try via TCP */
r = dns_transaction_emit(t);
r = dns_transaction_emit_udp(t);
if (r == -EMSGSIZE || r == -EAGAIN)
r = dns_transaction_open_tcp(t);
}
@ -1109,7 +1185,7 @@ int dns_transaction_go(DnsTransaction *t) {
}
/* Couldn't send? Try immediately again, with a new server */
dns_transaction_next_dns_server(t);
dns_scope_next_dns_server(t->scope);
return dns_transaction_go(t);
}
@ -1315,15 +1391,20 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
* - For RRSIG we get the matching DNSKEY
* - For DNSKEY we get the matching DS
* - For unsigned SOA/NS we get the matching DS
* - For unsigned CNAME/DNAME we get the parent SOA RR
* - For unsigned CNAME/DNAME/DS we get the parent SOA RR
* - For other unsigned RRs we get the matching SOA RR
* - For SOA/NS/DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR
* - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR
*/
if (t->scope->dnssec_mode != DNSSEC_YES)
if (t->scope->dnssec_mode == DNSSEC_NO)
return 0;
if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO)
return 0; /* Server doesn't do DNSSEC, there's no point in requesting any RRs then. */
if (t->server && t->server->rrsig_missing)
return 0; /* Server handles DNSSEC requests, but isn't augmenting responses with RRSIGs. No point in trying DNSSEC then. */
DNS_ANSWER_FOREACH(rr, t->answer) {
if (dns_type_is_pseudo(rr->key->type))
@ -1404,15 +1485,6 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
break;
}
case DNS_TYPE_DS:
case DNS_TYPE_NSEC:
case DNS_TYPE_NSEC3:
/* Don't acquire anything for
* DS/NSEC/NSEC3. We require they come with an
* RRSIG without us asking for anything, and
* that's sufficient. */
break;
case DNS_TYPE_SOA:
case DNS_TYPE_NS: {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL;
@ -1448,6 +1520,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
break;
}
case DNS_TYPE_DS:
case DNS_TYPE_CNAME:
case DNS_TYPE_DNAME: {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL;
@ -1458,7 +1531,10 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
* unsigned CNAME/DNAME RRs, maybe that's the
* apex. But do all that only if this is
* actually a response to our original
* question. */
* question.
*
* Similar for DS RRs, which are signed when
* the parent SOA is signed. */
r = dns_transaction_is_primary_response(t, rr);
if (r < 0)
@ -1483,7 +1559,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
if (!soa)
return -ENOMEM;
log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key));
log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key));
r = dns_transaction_request_dnssec_rr(t, soa);
if (r < 0)
return r;
@ -1494,10 +1570,11 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
default: {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL;
/* For other unsigned RRsets, look for proof
* the zone is unsigned, by requesting the SOA
* RR of the zone. However, do so only if they
* are directly relevant to our original
/* For other unsigned RRsets (including
* NSEC/NSEC3!), look for proof the zone is
* unsigned, by requesting the SOA RR of the
* zone. However, do so only if they are
* directly relevant to our original
* question. */
r = dns_transaction_is_primary_response(t, rr);
@ -1516,7 +1593,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
if (!soa)
return -ENOMEM;
log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key));
log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dns_resource_record_to_string(rr));
r = dns_transaction_request_dnssec_rr(t, soa);
if (r < 0)
return r;
@ -1671,7 +1748,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
/* Checks if the RR we are looking for must be signed with an
* RRSIG. This is used for positive responses. */
if (t->scope->dnssec_mode != DNSSEC_YES)
if (t->scope->dnssec_mode == DNSSEC_NO)
return false;
if (dns_type_is_pseudo(rr->key->type))
@ -1679,13 +1756,6 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
switch (rr->key->type) {
case DNS_TYPE_DNSKEY:
case DNS_TYPE_DS:
case DNS_TYPE_NSEC:
case DNS_TYPE_NSEC3:
/* We never consider DNSKEY, DS, NSEC, NSEC3 RRs if they aren't signed. */
return true;
case DNS_TYPE_RRSIG:
/* RRSIGs are the signatures themselves, they need no signing. */
return false;
@ -1695,7 +1765,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
DnsTransaction *dt;
Iterator i;
/* For SOA or NS RRs we look for a matching DS transaction, or a SOA transaction of the parent */
/* For SOA or NS RRs we look for a matching DS transaction */
SET_FOREACH(dt, t->dnssec_transactions, i) {
@ -1726,13 +1796,18 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
return true;
}
case DNS_TYPE_DS:
case DNS_TYPE_CNAME:
case DNS_TYPE_DNAME: {
const char *parent = NULL;
DnsTransaction *dt;
Iterator i;
/* CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. */
/*
* CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA.
*
* DS RRs are signed if the parent is signed, hence also look at the parent SOA
*/
SET_FOREACH(dt, t->dnssec_transactions, i) {
@ -1747,6 +1822,9 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
if (r < 0)
return r;
if (r == 0) {
if (rr->key->type == DNS_TYPE_DS)
return true;
/* A CNAME/DNAME without a parent? That's sooo weird. */
log_debug("Transaction %" PRIu16 " claims CNAME/DNAME at root. Refusing.", t->id);
return -EBADMSG;
@ -1769,7 +1847,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
DnsTransaction *dt;
Iterator i;
/* Any other kind of RR. Let's see if our SOA lookup was authenticated */
/* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */
SET_FOREACH(dt, t->dnssec_transactions, i) {
@ -1807,7 +1885,7 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) {
/* Checks if we need to insist on NSEC/NSEC3 RRs for proving
* this negative reply */
if (t->scope->dnssec_mode != DNSSEC_YES)
if (t->scope->dnssec_mode == DNSSEC_NO)
return false;
if (dns_type_is_pseudo(t->key->type))
@ -1855,6 +1933,82 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) {
return true;
}
static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRecord *rr) {
DnsResourceRecord *rrsig;
bool found = false;
int r;
/* Checks whether any of the DNSKEYs used for the RRSIGs for
* the specified RRset is authenticated (i.e. has a matching
* DS RR). */
DNS_ANSWER_FOREACH(rrsig, t->answer) {
DnsTransaction *dt;
Iterator i;
r = dnssec_key_match_rrsig(rr->key, rrsig);
if (r < 0)
return r;
if (r == 0)
continue;
SET_FOREACH(dt, t->dnssec_transactions, i) {
if (dt->key->class != rr->key->class)
continue;
if (dt->key->type == DNS_TYPE_DNSKEY) {
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer);
if (r < 0)
return r;
if (r == 0)
continue;
/* OK, we found an auxiliary DNSKEY
* lookup. If that lookup is
* authenticated, report this. */
if (dt->answer_authenticated)
return true;
found = true;
} else if (dt->key->type == DNS_TYPE_DS) {
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer);
if (r < 0)
return r;
if (r == 0)
continue;
/* OK, we found an auxiliary DS
* lookup. If that lookup is
* authenticated and non-zero, we
* won! */
if (!dt->answer_authenticated)
return false;
return dns_answer_match_key(dt->answer, dt->key, NULL);
}
}
}
return found ? false : -ENXIO;
}
static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) {
assert(t);
assert(rr);
/* We know that the root domain is signed, hence if it appears
* not to be signed, there's a problem with the DNS server */
return rr->key->class == DNS_CLASS_IN &&
dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key));
}
int dns_transaction_validate_dnssec(DnsTransaction *t) {
_cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
bool dnskeys_finalized = false;
@ -1868,7 +2022,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
* t->validated_keys, let's see which RRs we can now
* authenticate with that. */
if (t->scope->dnssec_mode != DNSSEC_YES)
if (t->scope->dnssec_mode == DNSSEC_NO)
return 0;
/* Already validated */
@ -1886,6 +2040,13 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (t->answer_source != DNS_TRANSACTION_NETWORK)
return 0;
if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO ||
(t->server && t->server->rrsig_missing)) {
/* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */
t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
return 0;
}
log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t));
/* First see if there are DNSKEYs we already known a validated DS for. */
@ -1906,12 +2067,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (r < 0)
return r;
if (log_get_max_level() >= LOG_DEBUG) {
_cleanup_free_ char *rrs = NULL;
(void) dns_resource_record_to_string(rr, &rrs);
log_debug("Looking at %s: %s", rrs ? strstrip(rrs) : "???", dnssec_result_to_string(result));
}
log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result));
if (result == DNSSEC_VALIDATED) {
@ -1936,11 +2092,14 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (r < 0)
return r;
t->scope->manager->n_dnssec_secure++;
/* Exit the loop, we dropped something from the answer, start from the beginning */
changed = true;
break;
} else if (dnskeys_finalized) {
/* If we haven't read all DNSKEYs yet
* a negative result of the validation
* is irrelevant, as there might be
@ -1957,11 +2116,72 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (r < 0)
return r;
t->scope->manager->n_dnssec_insecure++;
changed = true;
break;
}
r = dns_transaction_known_signed(t, rr);
if (r < 0)
return r;
if (r > 0) {
/* This is an RR we know has to be signed. If it isn't this means
* the server is not attaching RRSIGs, hence complain. */
dns_server_packet_rrsig_missing(t->server);
if (t->scope->dnssec_mode == DNSSEC_DOWNGRADE_OK) {
/* Downgrading is OK? If so, just consider the information unsigned */
r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
if (r < 0)
return r;
t->scope->manager->n_dnssec_insecure++;
changed = true;
break;
}
/* Otherwise, fail */
t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
return 0;
}
}
if (IN_SET(result,
DNSSEC_MISSING_KEY,
DNSSEC_SIGNATURE_EXPIRED,
DNSSEC_UNSUPPORTED_ALGORITHM)) {
r = dns_transaction_dnskey_authenticated(t, rr);
if (r < 0 && r != -ENXIO)
return r;
if (r == 0) {
/* The DNSKEY transaction was not authenticated, this means there's
* no DS for this, which means it's OK if no keys are found for this signature. */
r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
if (r < 0)
return r;
t->scope->manager->n_dnssec_insecure++;
changed = true;
break;
}
}
if (IN_SET(result,
DNSSEC_INVALID,
DNSSEC_SIGNATURE_EXPIRED,
DNSSEC_NO_SIGNATURE,
DNSSEC_UNSUPPORTED_ALGORITHM))
t->scope->manager->n_dnssec_bogus++;
else
t->scope->manager->n_dnssec_indeterminate++;
r = dns_transaction_is_primary_response(t, rr);
if (r < 0)
return r;
@ -2030,9 +2250,10 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
} else if (r == 0) {
DnssecNsecResult nr;
bool authenticated = false;
/* Bummer! Let's check NSEC/NSEC3 */
r = dnssec_test_nsec(t->answer, t->key, &nr);
r = dnssec_test_nsec(t->answer, t->key, &nr, &authenticated);
if (r < 0)
return r;
@ -2043,7 +2264,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t));
t->answer_dnssec_result = DNSSEC_VALIDATED;
t->answer_rcode = DNS_RCODE_NXDOMAIN;
t->answer_authenticated = true;
t->answer_authenticated = authenticated;
break;
case DNSSEC_NSEC_NODATA:
@ -2051,7 +2272,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t));
t->answer_dnssec_result = DNSSEC_VALIDATED;
t->answer_rcode = DNS_RCODE_SUCCESS;
t->answer_authenticated = true;
t->answer_authenticated = authenticated;
break;
case DNSSEC_NSEC_OPTOUT:
@ -2116,6 +2337,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX]
[DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached",
[DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply",
[DNS_TRANSACTION_RESOURCES] = "resources",
[DNS_TRANSACTION_CONNECTION_FAILURE] = "connection-failure",
[DNS_TRANSACTION_ABORTED] = "aborted",
[DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed",
};

View file

@ -36,6 +36,7 @@ enum DnsTransactionState {
DNS_TRANSACTION_ATTEMPTS_MAX_REACHED,
DNS_TRANSACTION_INVALID_REPLY,
DNS_TRANSACTION_RESOURCES,
DNS_TRANSACTION_CONNECTION_FAILURE,
DNS_TRANSACTION_ABORTED,
DNS_TRANSACTION_DNSSEC_FAILED,
_DNS_TRANSACTION_STATE_MAX,
@ -68,6 +69,8 @@ struct DnsTransaction {
uint16_t id;
bool tried_stream:1;
bool initial_jitter_scheduled:1;
bool initial_jitter_elapsed:1;
@ -97,18 +100,19 @@ struct DnsTransaction {
sd_event_source *timeout_event_source;
unsigned n_attempts;
/* UDP connection logic, if we need it */
int dns_udp_fd;
sd_event_source *dns_udp_event_source;
/* TCP connection logic, if we need it */
DnsStream *stream;
/* The active server */
DnsServer *server;
/* The features of the DNS server at time of transaction start */
DnsServerFeatureLevel current_features;
/* TCP connection logic, if we need it */
DnsStream *stream;
/* Query candidates this transaction is referenced by and that
* shall be notified about this specific transaction
* completing. */

View file

@ -471,15 +471,12 @@ return_empty:
}
void dns_zone_item_conflict(DnsZoneItem *i) {
_cleanup_free_ char *pretty = NULL;
assert(i);
if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
return;
dns_resource_record_to_string(i->rr, &pretty);
log_info("Detected conflict on %s", strna(pretty));
log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
dns_zone_item_probe_stop(i);
@ -492,8 +489,6 @@ void dns_zone_item_conflict(DnsZoneItem *i) {
}
void dns_zone_item_notify(DnsZoneItem *i) {
_cleanup_free_ char *pretty = NULL;
assert(i);
assert(i->probe_transaction);
@ -530,15 +525,13 @@ void dns_zone_item_notify(DnsZoneItem *i) {
log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
}
dns_resource_record_to_string(i->rr, &pretty);
log_debug("Record %s successfully probed.", strna(pretty));
log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
dns_zone_item_probe_stop(i);
i->state = DNS_ZONE_ITEM_ESTABLISHED;
}
static int dns_zone_item_verify(DnsZoneItem *i) {
_cleanup_free_ char *pretty = NULL;
int r;
assert(i);
@ -546,8 +539,7 @@ static int dns_zone_item_verify(DnsZoneItem *i) {
if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
return 0;
dns_resource_record_to_string(i->rr, &pretty);
log_debug("Verifying RR %s", strna(pretty));
log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
i->state = DNS_ZONE_ITEM_VERIFYING;
r = dns_zone_item_probe_start(i);
@ -632,7 +624,6 @@ void dns_zone_verify_all(DnsZone *zone) {
void dns_zone_dump(DnsZone *zone, FILE *f) {
Iterator iterator;
DnsZoneItem *i;
int r;
if (!zone)
return;
@ -644,10 +635,10 @@ void dns_zone_dump(DnsZone *zone, FILE *f) {
DnsZoneItem *j;
LIST_FOREACH(by_key, j, i) {
_cleanup_free_ char *t = NULL;
const char *t;
r = dns_resource_record_to_string(j->rr, &t);
if (r < 0) {
t = dns_resource_record_to_string(j->rr);
if (!t) {
log_oom();
continue;
}

View file

@ -117,7 +117,7 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u
dns_scope_process_query(scope, NULL, p);
} else
log_debug("Invalid LLMNR UDP packet.");
log_debug("Invalid LLMNR UDP packet, ignoring.");
return 0;
}

View file

@ -128,6 +128,9 @@ struct Manager {
sd_bus_slot *prepare_for_sleep_slot;
sd_event_source *sigusr1_event_source;
unsigned n_transactions_total;
unsigned n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate;
};
/* Manager */

View file

@ -56,7 +56,6 @@ static void test_dnssec_verify_rrset2(void) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
_cleanup_free_ char *x = NULL, *y = NULL, *z = NULL;
DnssecResult result;
nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov");
@ -77,8 +76,7 @@ static void test_dnssec_verify_rrset2(void) {
assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0);
assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0);
assert_se(dns_resource_record_to_string(nsec, &x) >= 0);
log_info("NSEC: %s", x);
log_info("NSEC: %s", strna(dns_resource_record_to_string(nsec)));
rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV.");
assert_se(rrsig);
@ -96,8 +94,7 @@ static void test_dnssec_verify_rrset2(void) {
rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size);
assert_se(rrsig->rrsig.signature);
assert_se(dns_resource_record_to_string(rrsig, &y) >= 0);
log_info("RRSIG: %s", y);
log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig)));
dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV");
assert_se(dnskey);
@ -109,8 +106,7 @@ static void test_dnssec_verify_rrset2(void) {
dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
assert_se(dnskey->dnskey.key);
assert_se(dns_resource_record_to_string(dnskey, &z) >= 0);
log_info("DNSKEY: %s", z);
log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0);
@ -152,7 +148,6 @@ static void test_dnssec_verify_rrset(void) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
_cleanup_free_ char *x = NULL, *y = NULL, *z = NULL;
DnssecResult result;
a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov");
@ -160,8 +155,7 @@ static void test_dnssec_verify_rrset(void) {
a->a.in_addr.s_addr = inet_addr("52.0.14.116");
assert_se(dns_resource_record_to_string(a, &x) >= 0);
log_info("A: %s", x);
log_info("A: %s", strna(dns_resource_record_to_string(a)));
rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV.");
assert_se(rrsig);
@ -179,8 +173,7 @@ static void test_dnssec_verify_rrset(void) {
rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size);
assert_se(rrsig->rrsig.signature);
assert_se(dns_resource_record_to_string(rrsig, &y) >= 0);
log_info("RRSIG: %s", y);
log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig)));
dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV");
assert_se(dnskey);
@ -192,8 +185,7 @@ static void test_dnssec_verify_rrset(void) {
dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
assert_se(dnskey->dnskey.key);
assert_se(dns_resource_record_to_string(dnskey, &z) >= 0);
log_info("DNSKEY: %s", z);
log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0);
@ -239,7 +231,6 @@ static void test_dnssec_verify_dns_key(void) {
};
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL;
_cleanup_free_ char *a = NULL, *b = NULL, *c = NULL;
/* The two DS RRs in effect for nasa.gov on 2015-12-01. */
ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov");
@ -252,8 +243,7 @@ static void test_dnssec_verify_dns_key(void) {
ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size);
assert_se(ds1->ds.digest);
assert_se(dns_resource_record_to_string(ds1, &a) >= 0);
log_info("DS1: %s", a);
log_info("DS1: %s", strna(dns_resource_record_to_string(ds1)));
ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV");
assert_se(ds2);
@ -265,8 +255,7 @@ static void test_dnssec_verify_dns_key(void) {
ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size);
assert_se(ds2->ds.digest);
assert_se(dns_resource_record_to_string(ds2, &b) >= 0);
log_info("DS2: %s", b);
log_info("DS2: %s", strna(dns_resource_record_to_string(ds2)));
dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV");
assert_se(dnskey);
@ -278,8 +267,7 @@ static void test_dnssec_verify_dns_key(void) {
dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
assert_se(dnskey->dnskey.key);
assert_se(dns_resource_record_to_string(dnskey, &c) >= 0);
log_info("DNSKEY: %s", c);
log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
assert_se(dnssec_verify_dnskey(dnskey, ds1) > 0);
@ -310,8 +298,8 @@ static void test_dnssec_nsec3_hash(void) {
static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE };
static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 };
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_free_ char *a = NULL, *b = NULL;
uint8_t h[DNSSEC_HASH_SIZE_MAX];
_cleanup_free_ char *b = NULL;
int k;
/* The NSEC3 RR for eurid.eu on 2015-12-14. */
@ -328,8 +316,7 @@ static void test_dnssec_nsec3_hash(void) {
assert_se(rr->nsec3.next_hashed_name);
rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name);
assert_se(dns_resource_record_to_string(rr, &a) >= 0);
log_info("NSEC3: %s", a);
log_info("NSEC3: %s", strna(dns_resource_record_to_string(rr)));
k = dnssec_nsec3_hash(rr, "eurid.eu", &h);
assert_se(k >= 0);

View file

@ -154,20 +154,24 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
return 0;
}
assert(**label_terminal == '.' || **label_terminal == 0);
terminal = *label_terminal;
assert(*terminal == '.' || *terminal == 0);
/* skip current terminal character */
terminal = *label_terminal - 1;
/* Skip current terminal character (and accept domain names ending it ".") */
if (*terminal == 0)
terminal--;
if (terminal >= name && *terminal == '.')
terminal--;
/* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
/* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
for (;;) {
if (terminal < name) {
/* reached the first label, so indicate that there are no more */
/* Reached the first label, so indicate that there are no more */
terminal = NULL;
break;
}
/* find the start of the last label */
/* Find the start of the last label */
if (*terminal == '.') {
const char *y;
unsigned slashes = 0;
@ -176,7 +180,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
slashes ++;
if (slashes % 2 == 0) {
/* the '.' was not escaped */
/* The '.' was not escaped */
name = terminal + 1;
break;
} else {
@ -533,7 +537,7 @@ int dns_name_compare_func(const void *a, const void *b) {
if (k > 0)
r = k;
if (w > 0)
r = w;
q = w;
la[r] = lb[q] = 0;
r = strcasecmp(la, lb);
@ -1159,3 +1163,77 @@ finish:
return 0;
}
int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
const char* labels[DNS_N_LABELS_MAX+1];
unsigned n = 0;
const char *p;
int r;
assert(name);
assert(ret);
p = name;
for (;;) {
if (n > DNS_N_LABELS_MAX)
return -EINVAL;
labels[n] = p;
r = dns_name_parent(&p);
if (r < 0)
return r;
if (r == 0)
break;
n++;
}
if (n < n_labels)
return -EINVAL;
*ret = labels[n - n_labels];
return (int) (n - n_labels);
}
int dns_name_count_labels(const char *name) {
unsigned n = 0;
const char *p;
int r;
assert(name);
p = name;
for (;;) {
r = dns_name_parent(&p);
if (r < 0)
return r;
if (r == 0)
break;
if (n >= DNS_N_LABELS_MAX)
return -EINVAL;
n++;
}
return (int) n;
}
int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
int r;
assert(a);
assert(b);
while (n_labels > 0) {
r = dns_name_parent(&a);
if (r <= 0)
return r;
n_labels --;
}
return dns_name_equal(a, b);
}

View file

@ -42,6 +42,9 @@
/* Maximum length of a full hostname, on the wire, including the final NUL byte */
#define DNS_WIRE_FOMAT_HOSTNAME_MAX 255
/* Maximum number of labels per valid hostname */
#define DNS_N_LABELS_MAX 127
int dns_label_unescape(const char **name, char *dest, size_t sz);
int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz);
int dns_label_escape(const char *p, size_t l, char *dest, size_t sz);
@ -96,3 +99,8 @@ bool dns_service_name_is_valid(const char *name);
int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
int dns_service_split(const char *joined, char **name, char **type, char **domain);
int dns_name_suffix(const char *name, unsigned n_labels, const char **ret);
int dns_name_count_labels(const char *name);
int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);

View file

@ -86,6 +86,8 @@ _SD_BEGIN_DECLARATIONS;
#define SD_MESSAGE_BOOTCHART SD_ID128_MAKE(9f,26,aa,56,2c,f4,40,c2,b1,6c,77,3d,04,79,b5,18)
#define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d)
_SD_END_DECLARATIONS;
#endif

View file

@ -140,9 +140,9 @@ static void test_dns_label_unescape_suffix(void) {
test_dns_label_unescape_suffix_one("hallo\\", "hallo", "hallo", 20, -EINVAL, -EINVAL);
test_dns_label_unescape_suffix_one("hallo\\032 ", "hallo ", "", 20, 7, 0);
test_dns_label_unescape_suffix_one(".", "", "", 20, 0, 0);
test_dns_label_unescape_suffix_one("..", "", "", 20, 0, 0);
test_dns_label_unescape_suffix_one("..", "", "", 20, 0, -EINVAL);
test_dns_label_unescape_suffix_one(".foobar", "foobar", "", 20, 6, -EINVAL);
test_dns_label_unescape_suffix_one("foobar.", "", "foobar", 20, 0, 6);
test_dns_label_unescape_suffix_one("foobar.", "foobar", "", 20, 6, 0);
test_dns_label_unescape_suffix_one("foo\\\\bar", "foo\\bar", "", 20, 7, 0);
test_dns_label_unescape_suffix_one("foo.bar", "bar", "foo", 20, 3, 3);
test_dns_label_unescape_suffix_one("foo..bar", "bar", "", 20, 3, -EINVAL);
@ -475,6 +475,90 @@ static void test_dns_name_change_suffix(void) {
test_dns_name_change_suffix_one("a", "b", "c", 0, NULL);
}
static void test_dns_name_suffix_one(const char *name, unsigned n_labels, const char *result, int ret) {
const char *p = NULL;
assert_se(ret == dns_name_suffix(name, n_labels, &p));
assert_se(streq_ptr(p, result));
}
static void test_dns_name_suffix(void) {
test_dns_name_suffix_one("foo.bar", 2, "foo.bar", 0);
test_dns_name_suffix_one("foo.bar", 1, "bar", 1);
test_dns_name_suffix_one("foo.bar", 0, "", 2);
test_dns_name_suffix_one("foo.bar", 3, NULL, -EINVAL);
test_dns_name_suffix_one("foo.bar", 4, NULL, -EINVAL);
test_dns_name_suffix_one("bar", 1, "bar", 0);
test_dns_name_suffix_one("bar", 0, "", 1);
test_dns_name_suffix_one("bar", 2, NULL, -EINVAL);
test_dns_name_suffix_one("bar", 3, NULL, -EINVAL);
test_dns_name_suffix_one("", 0, "", 0);
test_dns_name_suffix_one("", 1, NULL, -EINVAL);
test_dns_name_suffix_one("", 2, NULL, -EINVAL);
}
static void test_dns_name_count_labels_one(const char *name, int n) {
assert_se(dns_name_count_labels(name) == n);
}
static void test_dns_name_count_labels(void) {
test_dns_name_count_labels_one("foo.bar.quux.", 3);
test_dns_name_count_labels_one("foo.bar.quux", 3);
test_dns_name_count_labels_one("foo.bar.", 2);
test_dns_name_count_labels_one("foo.bar", 2);
test_dns_name_count_labels_one("foo.", 1);
test_dns_name_count_labels_one("foo", 1);
test_dns_name_count_labels_one("", 0);
test_dns_name_count_labels_one(".", 0);
test_dns_name_count_labels_one("..", -EINVAL);
}
static void test_dns_name_equal_skip_one(const char *a, unsigned n_labels, const char *b, int ret) {
assert_se(dns_name_equal_skip(a, n_labels, b) == ret);
}
static void test_dns_name_equal_skip(void) {
test_dns_name_equal_skip_one("foo", 0, "bar", 0);
test_dns_name_equal_skip_one("foo", 0, "foo", 1);
test_dns_name_equal_skip_one("foo", 1, "foo", 0);
test_dns_name_equal_skip_one("foo", 2, "foo", 0);
test_dns_name_equal_skip_one("foo.bar", 0, "foo.bar", 1);
test_dns_name_equal_skip_one("foo.bar", 1, "foo.bar", 0);
test_dns_name_equal_skip_one("foo.bar", 2, "foo.bar", 0);
test_dns_name_equal_skip_one("foo.bar", 3, "foo.bar", 0);
test_dns_name_equal_skip_one("foo.bar", 0, "bar", 0);
test_dns_name_equal_skip_one("foo.bar", 1, "bar", 1);
test_dns_name_equal_skip_one("foo.bar", 2, "bar", 0);
test_dns_name_equal_skip_one("foo.bar", 3, "bar", 0);
test_dns_name_equal_skip_one("foo.bar", 0, "", 0);
test_dns_name_equal_skip_one("foo.bar", 1, "", 0);
test_dns_name_equal_skip_one("foo.bar", 2, "", 1);
test_dns_name_equal_skip_one("foo.bar", 3, "", 0);
test_dns_name_equal_skip_one("", 0, "", 1);
test_dns_name_equal_skip_one("", 1, "", 0);
test_dns_name_equal_skip_one("", 1, "foo", 0);
test_dns_name_equal_skip_one("", 2, "foo", 0);
}
static void test_dns_name_compare_func(void) {
assert_se(dns_name_compare_func("", "") == 0);
assert_se(dns_name_compare_func("", ".") == 0);
assert_se(dns_name_compare_func(".", "") == 0);
assert_se(dns_name_compare_func("foo", "foo.") == 0);
assert_se(dns_name_compare_func("foo.", "foo") == 0);
assert_se(dns_name_compare_func("foo", "foo") == 0);
assert_se(dns_name_compare_func("foo.", "foo.") == 0);
assert_se(dns_name_compare_func("heise.de", "HEISE.DE.") == 0);
assert_se(dns_name_compare_func("de.", "heise.de") != 0);
}
int main(int argc, char *argv[]) {
test_dns_label_unescape();
@ -495,6 +579,10 @@ int main(int argc, char *argv[]) {
test_dns_service_join();
test_dns_service_split();
test_dns_name_change_suffix();
test_dns_name_suffix();
test_dns_name_count_labels();
test_dns_name_equal_skip();
test_dns_name_compare_func();
return 0;
}