resolved: gather statistics about resolved names

This collects statistical data about transactions, dnssec verifications
and the cache, and exposes it over the bus. The systemd-resolve-host
tool learns new options to query these statistics and reset them.
This commit is contained in:
Lennart Poettering 2015-12-23 19:06:36 +01:00
parent ed29bfdce6
commit a150ff5e4e
6 changed files with 346 additions and 41 deletions

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];
@ -637,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;
@ -681,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);
}
@ -693,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 },
{}
};
@ -763,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':
@ -806,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:
@ -849,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;
@ -861,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;
}
@ -883,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;
@ -914,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

@ -1214,16 +1214,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

@ -740,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;
@ -757,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;
@ -794,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) {
@ -811,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;
@ -830,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;
@ -974,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

@ -153,6 +153,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;
@ -1996,6 +1998,8 @@ 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;
@ -2018,6 +2022,8 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (r < 0)
return r;
t->scope->manager->n_dnssec_insecure++;
changed = true;
break;
}
@ -2039,11 +2045,22 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
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;

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 */