471 lines
14 KiB
C
471 lines
14 KiB
C
|
/*
|
||
|
* Author Duane Wessels
|
||
|
*/
|
||
|
|
||
|
#define _GNU_SOURCE
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
#include <memory.h>
|
||
|
#include <time.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <errno.h>
|
||
|
#include <assert.h>
|
||
|
#include <sys/wait.h>
|
||
|
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <arpa/inet.h>
|
||
|
|
||
|
#include <arpa/nameser.h>
|
||
|
|
||
|
#include <netinet/in_systm.h>
|
||
|
#include <netinet/ip.h>
|
||
|
#include <netinet/ip6.h>
|
||
|
#include <netinet/ip_icmp.h>
|
||
|
|
||
|
#include <ldns/ldns.h>
|
||
|
|
||
|
#include "dnscap_common.h"
|
||
|
|
||
|
static logerr_t* logerr = 0;
|
||
|
static my_bpftimeval open_ts = { 0, 0 };
|
||
|
static my_bpftimeval clos_ts = { 0, 0 };
|
||
|
static char* report_zone = 0;
|
||
|
static char* report_server = 0;
|
||
|
static char* report_node = 0;
|
||
|
static char* keytag_zone = 0;
|
||
|
static unsigned short resolver_port = 0;
|
||
|
static unsigned int resolver_use_tcp = 0;
|
||
|
static ldns_resolver* res;
|
||
|
|
||
|
static int dry_run = 0;
|
||
|
|
||
|
output_t rzkeychange_output;
|
||
|
is_responder_t rzkeychange_is_responder = 0;
|
||
|
ia_str_t rzkeychange_ia_str = 0;
|
||
|
|
||
|
#define MAX_KEY_TAG_SIGNALS 500
|
||
|
static unsigned int num_key_tag_signals;
|
||
|
struct {
|
||
|
iaddr addr;
|
||
|
uint8_t flags;
|
||
|
const char* signal;
|
||
|
} key_tag_signals[MAX_KEY_TAG_SIGNALS];
|
||
|
|
||
|
#define KEYTAG_FLAG_DO 1
|
||
|
#define KEYTAG_FLAG_CD 2
|
||
|
#define KEYTAG_FLAG_RD 4
|
||
|
|
||
|
struct {
|
||
|
uint64_t dnskey;
|
||
|
uint64_t tc_bit;
|
||
|
uint64_t tcp;
|
||
|
uint64_t icmp_unreach_frag;
|
||
|
uint64_t icmp_timxceed_reass;
|
||
|
uint64_t icmp_timxceed_intrans;
|
||
|
uint64_t total;
|
||
|
} counts;
|
||
|
|
||
|
#define MAX_NAMESERVERS 10
|
||
|
static unsigned int num_ns_addrs = 0;
|
||
|
static char* ns_addrs[MAX_NAMESERVERS];
|
||
|
|
||
|
void rzkeychange_usage()
|
||
|
{
|
||
|
fprintf(stderr,
|
||
|
"\nrzkeychange.so options:\n"
|
||
|
"\t-? print these instructions and exit\n"
|
||
|
"\t-D dry run, just print queries\n"
|
||
|
"\t-z <zone> Report counters to DNS zone <zone> (required)\n"
|
||
|
"\t-s <server> Data is from server <server> (required)\n"
|
||
|
"\t-n <node> Data is from site/node <node> (required)\n"
|
||
|
"\t-k <zone> Report RFC 8145 key tag signals to <zone>\n"
|
||
|
"\t-a <addr> Send DNS queries to this addr\n"
|
||
|
"\t-p <port> Send DNS queries to this port\n"
|
||
|
"\t-t Use TCP for DNS queries\n");
|
||
|
}
|
||
|
|
||
|
void rzkeychange_extension(int ext, void* arg)
|
||
|
{
|
||
|
switch (ext) {
|
||
|
case DNSCAP_EXT_IS_RESPONDER:
|
||
|
rzkeychange_is_responder = (is_responder_t)arg;
|
||
|
break;
|
||
|
case DNSCAP_EXT_IA_STR:
|
||
|
rzkeychange_ia_str = (ia_str_t)arg;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rzkeychange_getopt(int* argc, char** argv[])
|
||
|
{
|
||
|
int c;
|
||
|
while ((c = getopt(*argc, *argv, "?a:k:n:p:s:tz:D")) != EOF) {
|
||
|
switch (c) {
|
||
|
case 'n':
|
||
|
if (report_node)
|
||
|
free(report_node);
|
||
|
report_node = strdup(optarg);
|
||
|
if (!report_node) {
|
||
|
fprintf(stderr, "strdup() out of memory\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
break;
|
||
|
case 's':
|
||
|
if (report_server)
|
||
|
free(report_server);
|
||
|
report_server = strdup(optarg);
|
||
|
if (!report_server) {
|
||
|
fprintf(stderr, "strdup() out of memory\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
break;
|
||
|
case 'z':
|
||
|
if (report_zone)
|
||
|
free(report_zone);
|
||
|
report_zone = strdup(optarg);
|
||
|
if (!report_zone) {
|
||
|
fprintf(stderr, "strdup() out of memory\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
break;
|
||
|
case 'k':
|
||
|
if (keytag_zone)
|
||
|
free(keytag_zone);
|
||
|
keytag_zone = strdup(optarg);
|
||
|
if (!keytag_zone) {
|
||
|
fprintf(stderr, "strdup() out of memory\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
break;
|
||
|
case 'a':
|
||
|
if (num_ns_addrs < MAX_NAMESERVERS) {
|
||
|
ns_addrs[num_ns_addrs] = strdup(optarg);
|
||
|
if (!ns_addrs[num_ns_addrs]) {
|
||
|
fprintf(stderr, "strdup() out of memory\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
num_ns_addrs++;
|
||
|
} else {
|
||
|
fprintf(stderr, "too many nameservers\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
break;
|
||
|
case 'p':
|
||
|
resolver_port = strtoul(optarg, 0, 10);
|
||
|
break;
|
||
|
case 't':
|
||
|
resolver_use_tcp = 1;
|
||
|
break;
|
||
|
case 'D':
|
||
|
dry_run = 1;
|
||
|
break;
|
||
|
case '?':
|
||
|
rzkeychange_usage();
|
||
|
if (!optopt || optopt == '?') {
|
||
|
exit(0);
|
||
|
}
|
||
|
// fallthrough
|
||
|
default:
|
||
|
exit(1);
|
||
|
}
|
||
|
}
|
||
|
if (!report_zone || !report_server || !report_node) {
|
||
|
rzkeychange_usage();
|
||
|
exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ldns_pkt*
|
||
|
dns_query(const char* name, ldns_rr_type type)
|
||
|
{
|
||
|
fprintf(stderr, "%s\n", name);
|
||
|
if (dry_run) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ldns_rdf* domain = ldns_dname_new_frm_str(name);
|
||
|
if (0 == domain) {
|
||
|
fprintf(stderr, "bad query name: '%s'\n", name);
|
||
|
exit(1);
|
||
|
}
|
||
|
ldns_pkt* pkt = ldns_resolver_query(res,
|
||
|
domain,
|
||
|
type,
|
||
|
LDNS_RR_CLASS_IN,
|
||
|
LDNS_RD);
|
||
|
ldns_rdf_deep_free(domain);
|
||
|
return pkt;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
add_resolver_nameserver(const char* s)
|
||
|
{
|
||
|
ldns_rdf* nsaddr;
|
||
|
fprintf(stderr, "adding nameserver '%s' to resolver config\n", s);
|
||
|
if (strchr(s, ':'))
|
||
|
nsaddr = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_AAAA, s);
|
||
|
else
|
||
|
nsaddr = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_A, s);
|
||
|
if (!nsaddr) {
|
||
|
logerr("rzkeychange.so: invalid IP address '%s'", s);
|
||
|
exit(1);
|
||
|
}
|
||
|
assert(LDNS_STATUS_OK == ldns_resolver_push_nameserver(res, nsaddr));
|
||
|
}
|
||
|
|
||
|
int rzkeychange_start(logerr_t* a_logerr)
|
||
|
{
|
||
|
ldns_pkt* pkt;
|
||
|
struct timeval to;
|
||
|
char qname[256];
|
||
|
logerr = a_logerr;
|
||
|
if (LDNS_STATUS_OK != ldns_resolver_new_frm_file(&res, NULL)) {
|
||
|
fprintf(stderr, "Failed to initialize ldns resolver\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
if (num_ns_addrs) {
|
||
|
unsigned int i;
|
||
|
ldns_resolver_set_nameserver_count(res, 0);
|
||
|
for (i = 0; i < num_ns_addrs; i++)
|
||
|
add_resolver_nameserver(ns_addrs[i]);
|
||
|
}
|
||
|
if (0 == ldns_resolver_nameserver_count(res))
|
||
|
add_resolver_nameserver("127.0.0.1");
|
||
|
if (resolver_port)
|
||
|
ldns_resolver_set_port(res, resolver_port);
|
||
|
if (resolver_use_tcp)
|
||
|
ldns_resolver_set_usevc(res, 1);
|
||
|
|
||
|
if (dry_run) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
fprintf(stderr, "Testing reachability of zone '%s'\n", report_zone);
|
||
|
pkt = dns_query(report_zone, LDNS_RR_TYPE_TXT);
|
||
|
if (!pkt) {
|
||
|
fprintf(stderr, "Test of zone '%s' failed\n", report_zone);
|
||
|
exit(1);
|
||
|
}
|
||
|
if (0 != ldns_pkt_get_rcode(pkt)) {
|
||
|
fprintf(stderr, "Query to zone '%s' returned rcode %d\n", report_zone, ldns_pkt_get_rcode(pkt));
|
||
|
exit(1);
|
||
|
}
|
||
|
fprintf(stderr, "Success.\n");
|
||
|
if (pkt)
|
||
|
ldns_pkt_free(pkt);
|
||
|
/*
|
||
|
* For all subsequent queries we don't actually care about the response
|
||
|
* and don't wait to wait very long for it so the timeout is set really low.
|
||
|
*/
|
||
|
to.tv_sec = 0;
|
||
|
to.tv_usec = 500000;
|
||
|
ldns_resolver_set_timeout(res, to);
|
||
|
snprintf(qname, sizeof(qname), "ts-elapsed-tot-dnskey-tcp-tc-unreachfrag-texcfrag-texcttl.%s.%s.%s", report_node, report_server, report_zone);
|
||
|
pkt = dns_query(qname, LDNS_RR_TYPE_TXT);
|
||
|
if (pkt)
|
||
|
ldns_pkt_free(pkt);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void rzkeychange_stop()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
int rzkeychange_open(my_bpftimeval ts)
|
||
|
{
|
||
|
open_ts = clos_ts.tv_sec ? clos_ts : ts;
|
||
|
memset(&counts, 0, sizeof(counts));
|
||
|
memset(&key_tag_signals, 0, sizeof(key_tag_signals));
|
||
|
num_key_tag_signals = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void rzkeychange_submit_counts(void)
|
||
|
{
|
||
|
char qname[256];
|
||
|
ldns_pkt* pkt;
|
||
|
double elapsed = (double)clos_ts.tv_sec - (double)open_ts.tv_sec + 0.000001 * clos_ts.tv_usec - 0.000001 * open_ts.tv_usec; //NOSONAR
|
||
|
int k;
|
||
|
|
||
|
k = snprintf(qname, sizeof(qname), "%lu-%u-%" PRIu64 "-%" PRIu64 "-%" PRIu64 "-%" PRIu64 "-%" PRIu64 "-%" PRIu64 "-%" PRIu64 ".%s.%s.%s",
|
||
|
(u_long)open_ts.tv_sec,
|
||
|
(unsigned int)(elapsed + 0.5),
|
||
|
counts.total,
|
||
|
counts.dnskey,
|
||
|
counts.tcp,
|
||
|
counts.tc_bit,
|
||
|
counts.icmp_unreach_frag,
|
||
|
counts.icmp_timxceed_reass,
|
||
|
counts.icmp_timxceed_intrans,
|
||
|
report_node,
|
||
|
report_server,
|
||
|
report_zone);
|
||
|
|
||
|
if (k < sizeof(qname)) {
|
||
|
pkt = dns_query(qname, LDNS_RR_TYPE_TXT);
|
||
|
if (pkt)
|
||
|
ldns_pkt_free(pkt);
|
||
|
}
|
||
|
|
||
|
if (keytag_zone != 0) {
|
||
|
unsigned int i;
|
||
|
|
||
|
for (i = 0; i < num_key_tag_signals; i++) {
|
||
|
char* s = strdup(rzkeychange_ia_str(key_tag_signals[i].addr));
|
||
|
char* t;
|
||
|
|
||
|
if (0 == s) {
|
||
|
/*
|
||
|
* Apparently out of memory. This function is called in
|
||
|
* a child process which will exit right after this we
|
||
|
* break from the loop and return from this function.
|
||
|
*/
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
for (t = s; *t; t++)
|
||
|
if (*t == '.' || *t == ':')
|
||
|
*t = '-';
|
||
|
|
||
|
k = snprintf(qname, sizeof(qname), "%lu.%s.%hhx.%s.%s.%s.%s",
|
||
|
(u_long)open_ts.tv_sec,
|
||
|
s,
|
||
|
key_tag_signals[i].flags,
|
||
|
key_tag_signals[i].signal,
|
||
|
report_node,
|
||
|
report_server,
|
||
|
keytag_zone);
|
||
|
free(s);
|
||
|
|
||
|
if (k >= sizeof(qname))
|
||
|
continue; // qname was truncated in snprintf()
|
||
|
|
||
|
pkt = dns_query(qname, LDNS_RR_TYPE_TXT);
|
||
|
if (pkt)
|
||
|
ldns_pkt_free(pkt);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Fork a separate process so that we don't block the main dnscap. Use
|
||
|
* double-fork to avoid zombies for the main dnscap process.
|
||
|
*/
|
||
|
int rzkeychange_close(my_bpftimeval ts)
|
||
|
{
|
||
|
pid_t pid;
|
||
|
pid = fork();
|
||
|
if (pid < 0) {
|
||
|
logerr("rzkeychange.so: fork: %s", strerror(errno));
|
||
|
return 1;
|
||
|
} else if (pid) {
|
||
|
/* parent */
|
||
|
waitpid(pid, NULL, 0);
|
||
|
return 0;
|
||
|
}
|
||
|
/* 1st gen child continues */
|
||
|
pid = fork();
|
||
|
if (pid < 0) {
|
||
|
logerr("rzkeychange.so: fork: %s", strerror(errno));
|
||
|
return 1;
|
||
|
} else if (pid) {
|
||
|
/* 1st gen child exits */
|
||
|
exit(0);
|
||
|
}
|
||
|
/* grandchild (2nd gen) continues */
|
||
|
clos_ts = ts;
|
||
|
rzkeychange_submit_counts();
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
void rzkeychange_keytagsignal(const ldns_pkt* pkt, const ldns_rr* question_rr, iaddr addr)
|
||
|
{
|
||
|
ldns_rdf* qn;
|
||
|
char* qn_str = 0;
|
||
|
if (LDNS_RR_TYPE_NULL != ldns_rr_get_type(question_rr))
|
||
|
return;
|
||
|
if (num_key_tag_signals == MAX_KEY_TAG_SIGNALS)
|
||
|
return;
|
||
|
qn = ldns_rr_owner(question_rr);
|
||
|
if (qn == 0)
|
||
|
return;
|
||
|
qn_str = ldns_rdf2str(qn);
|
||
|
if (qn_str == 0)
|
||
|
return;
|
||
|
if (0 != strncasecmp(qn_str, "_ta-", 4))
|
||
|
goto keytagsignal_done;
|
||
|
qn_str[strlen(qn_str) - 1] = 0; // ldns always adds terminating dot
|
||
|
if (strchr(qn_str, '.')) // dont want non-root keytag signals
|
||
|
goto keytagsignal_done;
|
||
|
key_tag_signals[num_key_tag_signals].addr = addr;
|
||
|
key_tag_signals[num_key_tag_signals].signal = strdup(qn_str);
|
||
|
assert(key_tag_signals[num_key_tag_signals].signal);
|
||
|
if (ldns_pkt_rd(pkt))
|
||
|
key_tag_signals[num_key_tag_signals].flags |= KEYTAG_FLAG_RD;
|
||
|
if (ldns_pkt_cd(pkt))
|
||
|
key_tag_signals[num_key_tag_signals].flags |= KEYTAG_FLAG_CD;
|
||
|
if (ldns_pkt_edns_do(pkt))
|
||
|
key_tag_signals[num_key_tag_signals].flags |= KEYTAG_FLAG_DO;
|
||
|
num_key_tag_signals++;
|
||
|
keytagsignal_done:
|
||
|
if (qn_str)
|
||
|
free(qn_str);
|
||
|
}
|
||
|
|
||
|
void rzkeychange_output(const char* descr, iaddr from, iaddr to, uint8_t proto, unsigned flags,
|
||
|
unsigned sport, unsigned dport, my_bpftimeval ts,
|
||
|
const u_char* pkt_copy, const unsigned olen,
|
||
|
const u_char* payload, const unsigned payloadlen)
|
||
|
{
|
||
|
ldns_pkt* pkt = 0;
|
||
|
ldns_rr_list* question_rr_list = 0;
|
||
|
ldns_rr* question_rr = 0;
|
||
|
if (!(flags & DNSCAP_OUTPUT_ISDNS)) {
|
||
|
if (IPPROTO_ICMP == proto && payloadlen >= 4) {
|
||
|
struct icmp* icmp;
|
||
|
if (rzkeychange_is_responder && !rzkeychange_is_responder(to))
|
||
|
goto done;
|
||
|
icmp = (void*)payload;
|
||
|
if (ICMP_UNREACH == icmp->icmp_type) {
|
||
|
if (ICMP_UNREACH_NEEDFRAG == icmp->icmp_code)
|
||
|
counts.icmp_unreach_frag++;
|
||
|
} else if (ICMP_TIMXCEED == icmp->icmp_type) {
|
||
|
if (ICMP_TIMXCEED_INTRANS == icmp->icmp_code)
|
||
|
counts.icmp_timxceed_intrans++;
|
||
|
else if (ICMP_TIMXCEED_REASS == icmp->icmp_code)
|
||
|
counts.icmp_timxceed_reass++;
|
||
|
}
|
||
|
}
|
||
|
goto done;
|
||
|
}
|
||
|
if (LDNS_STATUS_OK != ldns_wire2pkt(&pkt, payload, payloadlen))
|
||
|
return;
|
||
|
if (0 == ldns_pkt_qr(pkt))
|
||
|
goto done;
|
||
|
counts.total++;
|
||
|
if (IPPROTO_UDP == proto) {
|
||
|
if (0 != ldns_pkt_tc(pkt))
|
||
|
counts.tc_bit++;
|
||
|
} else if (IPPROTO_TCP == proto) {
|
||
|
counts.tcp++;
|
||
|
}
|
||
|
if (LDNS_PACKET_QUERY != ldns_pkt_get_opcode(pkt))
|
||
|
goto done;
|
||
|
question_rr_list = ldns_pkt_question(pkt);
|
||
|
if (0 == question_rr_list)
|
||
|
goto done;
|
||
|
question_rr = ldns_rr_list_rr(question_rr_list, 0);
|
||
|
if (0 == question_rr)
|
||
|
goto done;
|
||
|
if (LDNS_RR_CLASS_IN == ldns_rr_get_class(question_rr))
|
||
|
if (LDNS_RR_TYPE_DNSKEY == ldns_rr_get_type(question_rr))
|
||
|
counts.dnskey++;
|
||
|
if (keytag_zone != 0)
|
||
|
rzkeychange_keytagsignal(pkt, question_rr, to); // 'to' here because plugin should be processing responses
|
||
|
done:
|
||
|
ldns_pkt_free(pkt);
|
||
|
}
|