1137 lines
37 KiB
C
1137 lines
37 KiB
C
/*
|
|
* ZMap Copyright 2015 Regents of the University of Michigan
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
* use this file except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*/
|
|
|
|
// Module for scanning for open UDP DNS resolvers.
|
|
//
|
|
// This module optionally takes in an argument of the form "TYPE,QUESTION"
|
|
// (e.g. "A,google.com").
|
|
//
|
|
// Given no arguments it will default to asking for an A record for
|
|
// www.google.com.
|
|
//
|
|
// This module does minimal answer verification. It only verifies that the
|
|
// response roughly looks like a DNS response. It will not, for example,
|
|
// require the QR bit be set to 1. All such analysis should happen offline.
|
|
// Specifically, to be included in the output it requires:
|
|
// - That the response packet is >= the query packet.
|
|
// - That the ports match and the packet is complete.
|
|
// To be marked as success it also requires:
|
|
// - That the response bytes that should be the ID field matches the send bytes.
|
|
// - That the response bytes that should be question match send bytes.
|
|
// To be marked as app_success it also requires:
|
|
// - That the QR bit be 1 and rcode == 0.
|
|
//
|
|
// Usage:
|
|
// zmap -p 53 --probe-module=dns --probe-args="ANY,www.example.com"
|
|
// -O json --output-fields=* 8.8.8.8
|
|
//
|
|
// We also support multiple questions, of the form:
|
|
// "A,example.com;AAAA,www.example.com" This requires --probes=X, where X
|
|
// is a multiple of the number of questions in --probe-args, and either
|
|
// --output-filter="" or --output-module=csv to remove the implicit
|
|
// "filter_duplicates" configuration flag.
|
|
//
|
|
|
|
#include "module_dns.h"
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include "../../lib/includes.h"
|
|
#include "../../lib/random.h"
|
|
#include "../../lib/xalloc.h"
|
|
#include "probe_modules.h"
|
|
#include "packet.h"
|
|
#include "logger.h"
|
|
#include "module_udp.h"
|
|
#include "../fieldset.h"
|
|
|
|
#define DNS_PAYLOAD_LEN_LIMIT 512 // This is arbitrary
|
|
#define PCAP_SNAPLEN 1500 // This is even more arbitrary
|
|
#define MAX_QTYPE 255
|
|
#define ICMP_UNREACH_HEADER_SIZE 8
|
|
#define BAD_QTYPE_STR "BAD QTYPE"
|
|
#define BAD_QTYPE_VAL -1
|
|
#define MAX_LABEL_RECURSION 10
|
|
#define DNS_QR_ANSWER 1
|
|
|
|
// Note: each label has a max length of 63 bytes. So someone has to be doing
|
|
// something really annoying. Will raise a warning.
|
|
// THIS INCLUDES THE NULL BYTE
|
|
#define MAX_NAME_LENGTH 512
|
|
|
|
#if defined(__NetBSD__) && !defined(__cplusplus) && defined(bool)
|
|
#undef bool
|
|
#endif
|
|
|
|
typedef uint8_t bool;
|
|
|
|
// zmap boilerplate
|
|
probe_module_t module_dns;
|
|
static int num_ports;
|
|
|
|
const char default_domain[] = "www.google.com";
|
|
const uint16_t default_qtype = DNS_QTYPE_A;
|
|
const uint8_t default_rdbit = 0xFF;
|
|
|
|
static char **dns_packets;
|
|
static uint16_t *dns_packet_lens; // Not including udp header
|
|
static uint16_t *qname_lens;
|
|
static char **qnames;
|
|
static uint16_t *qtypes;
|
|
static int num_questions = 0; // How many DNS questions to query. Note: There's a requirement that probes is a multiple of DNS questions
|
|
// necessary to null-terminate these since strtrk_r can take multiple delimitors as a char*, and since these are contiguous in memory,
|
|
// they were being used jointly when the intention is to use only one at a time.
|
|
static const char* probe_arg_delimitor = ";\0";
|
|
static const char* domain_qtype_delimitor = ",\0";
|
|
static const char* rn_delimitor = ":\0";
|
|
|
|
static uint8_t *rdbits;
|
|
const char *qopts_rn = "nr"; // used in query to disable recursion bit in DNS header
|
|
|
|
/* Array of qtypes we support. Jumping through some hoops (1 level of
|
|
* indirection) so the per-packet processing time is fast. Keep this in sync
|
|
* with: dns_qtype (.h) qtype_strid_to_qtype (below) qtype_qtype_to_strid
|
|
* (below, and setup_qtype_str_map())
|
|
*/
|
|
const char *qtype_strs[] = {"A", "NS", "CNAME", "SOA", "PTR",
|
|
"MX", "TXT", "AAAA", "RRSIG", "ALL"};
|
|
const int qtype_strs_len = 10;
|
|
|
|
const dns_qtype qtype_strid_to_qtype[] = {
|
|
DNS_QTYPE_A, DNS_QTYPE_NS, DNS_QTYPE_CNAME, DNS_QTYPE_SOA,
|
|
DNS_QTYPE_PTR, DNS_QTYPE_MX, DNS_QTYPE_TXT, DNS_QTYPE_AAAA,
|
|
DNS_QTYPE_RRSIG, DNS_QTYPE_ALL};
|
|
|
|
int8_t qtype_qtype_to_strid[256] = {BAD_QTYPE_VAL};
|
|
|
|
void setup_qtype_str_map(void)
|
|
{
|
|
qtype_qtype_to_strid[DNS_QTYPE_A] = 0;
|
|
qtype_qtype_to_strid[DNS_QTYPE_NS] = 1;
|
|
qtype_qtype_to_strid[DNS_QTYPE_CNAME] = 2;
|
|
qtype_qtype_to_strid[DNS_QTYPE_SOA] = 3;
|
|
qtype_qtype_to_strid[DNS_QTYPE_PTR] = 4;
|
|
qtype_qtype_to_strid[DNS_QTYPE_MX] = 5;
|
|
qtype_qtype_to_strid[DNS_QTYPE_TXT] = 6;
|
|
qtype_qtype_to_strid[DNS_QTYPE_AAAA] = 7;
|
|
qtype_qtype_to_strid[DNS_QTYPE_RRSIG] = 8;
|
|
qtype_qtype_to_strid[DNS_QTYPE_ALL] = 9;
|
|
}
|
|
|
|
static uint16_t qtype_str_to_code(const char *str)
|
|
{
|
|
for (int i = 0; i < qtype_strs_len; i++) {
|
|
if (strcmp(qtype_strs[i], str) == 0)
|
|
return qtype_strid_to_qtype[i];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint16_t domain_to_qname(char **qname_handle, const char *domain)
|
|
{
|
|
// String + 1byte header + null byte
|
|
uint16_t len = strlen(domain) + 1 + 1;
|
|
char *qname = xmalloc(len);
|
|
// Add a . before the domain. This will make the following simpler.
|
|
qname[0] = '.';
|
|
// Move the domain into the qname buffer.
|
|
strcpy(qname + 1, domain);
|
|
for (int i = 0; i < len; i++) {
|
|
if (qname[i] == '.') {
|
|
int j;
|
|
for (j = i + 1; j < (len - 1); j++) {
|
|
if (qname[j] == '.') {
|
|
break;
|
|
}
|
|
}
|
|
qname[i] = j - i - 1;
|
|
}
|
|
}
|
|
*qname_handle = qname;
|
|
assert((*qname_handle)[len - 1] == '\0');
|
|
return len;
|
|
}
|
|
|
|
static int build_global_dns_packets(char *domains[], int num_domains, size_t *max_len)
|
|
{
|
|
size_t _max_len = 0;
|
|
for (int i = 0; i < num_domains; i++) {
|
|
|
|
qname_lens[i] = domain_to_qname(&qnames[i], domains[i]);
|
|
if (domains[i] != (char *)default_domain) {
|
|
free(domains[i]);
|
|
}
|
|
uint16_t len = sizeof(dns_header) + qname_lens[i] +
|
|
sizeof(dns_question_tail);
|
|
dns_packet_lens[i] = len;
|
|
if (len > _max_len) {
|
|
_max_len = len;
|
|
}
|
|
|
|
if (dns_packet_lens[i] > DNS_PAYLOAD_LEN_LIMIT) {
|
|
log_fatal("dns",
|
|
"DNS packet bigger (%d) than our limit (%d)",
|
|
dns_packet_lens[i], DNS_PAYLOAD_LEN_LIMIT);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
dns_packets[i] = xmalloc(dns_packet_lens[i]);
|
|
|
|
dns_header *dns_header_p = (dns_header *)dns_packets[i];
|
|
char *qname_p = dns_packets[i] + sizeof(dns_header);
|
|
dns_question_tail *tail_p =
|
|
(dns_question_tail *)(dns_packets[i] + sizeof(dns_header) +
|
|
qname_lens[i]);
|
|
|
|
// All other header fields should be 0. Except id, which we set
|
|
// per thread. Please recurse as needed.
|
|
dns_header_p->rd = rdbits[i];
|
|
|
|
// We have 1 question
|
|
dns_header_p->qdcount = htons(1);
|
|
memcpy(qname_p, qnames[i], qname_lens[i]);
|
|
// Set the qtype to what we passed from args
|
|
tail_p->qtype = htons(qtypes[i]);
|
|
// Set the qclass to The Internet
|
|
tail_p->qclass = htons(0x01);
|
|
}
|
|
*max_len = _max_len;
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
static uint16_t get_name_helper(const char *data, uint16_t data_len,
|
|
const char *payload, uint16_t payload_len,
|
|
char *name, uint16_t name_len,
|
|
uint16_t recursion_level)
|
|
{
|
|
log_trace("dns",
|
|
"_get_name_helper IN, datalen: %d namelen: %d recursion: %d",
|
|
data_len, name_len, recursion_level);
|
|
if (data_len == 0 || name_len == 0 || payload_len == 0) {
|
|
log_trace(
|
|
"dns",
|
|
"_get_name_helper OUT, err. 0 length field. datalen %d namelen %d payloadlen %d",
|
|
data_len, name_len, payload_len);
|
|
return 0;
|
|
}
|
|
if (recursion_level > MAX_LABEL_RECURSION) {
|
|
log_trace("dns", "_get_name_helper OUT. ERR, MAX RECURSION");
|
|
return 0;
|
|
}
|
|
uint16_t bytes_consumed = 0;
|
|
// The start of data is either a sequence of labels or a ptr.
|
|
while (data_len > 0) {
|
|
uint8_t byte = data[0];
|
|
// Is this a pointer?
|
|
if (byte >= 0xc0) {
|
|
log_trace("dns", "_get_name_helper, ptr encountered");
|
|
// Do we have enough bytes to check ahead?
|
|
if (data_len < 2) {
|
|
log_trace(
|
|
"dns",
|
|
"_get_name_helper OUT. ptr byte encountered. No offset. ERR.");
|
|
return 0;
|
|
}
|
|
// No. ntohs isn't needed here. It's because of
|
|
// the upper 2 bits indicating a pointer.
|
|
uint16_t offset =
|
|
((byte & 0x03) << 8) | (uint8_t)data[1];
|
|
log_trace("dns", "_get_name_helper. ptr offset 0x%x",
|
|
offset);
|
|
if (offset >= payload_len) {
|
|
log_trace(
|
|
"dns",
|
|
"_get_name_helper OUT. offset exceeded payload len %d ERR",
|
|
payload_len);
|
|
return 0;
|
|
}
|
|
|
|
// We need to add a dot if we are:
|
|
// -- Not first level recursion.
|
|
// -- have consumed bytes
|
|
if (recursion_level > 0 || bytes_consumed > 0) {
|
|
|
|
if (name_len < 1) {
|
|
log_warn(
|
|
"dns",
|
|
"Exceeded static name field allocation.");
|
|
return 0;
|
|
}
|
|
|
|
name[0] = '.';
|
|
name++;
|
|
name_len--;
|
|
}
|
|
uint16_t rec_bytes_consumed = get_name_helper(
|
|
payload + offset, payload_len - offset, payload,
|
|
payload_len, name, name_len, recursion_level + 1);
|
|
// We are done so don't bother to increment the
|
|
// pointers.
|
|
if (rec_bytes_consumed == 0) {
|
|
log_trace(
|
|
"dns",
|
|
"_get_name_helper OUT. rec level %d failed",
|
|
recursion_level);
|
|
return 0;
|
|
} else {
|
|
bytes_consumed += 2;
|
|
log_trace(
|
|
"dns",
|
|
"_get_name_helper OUT. rec level %d success. "
|
|
"%d rec bytes consumed. %d bytes consumed.",
|
|
recursion_level, rec_bytes_consumed,
|
|
bytes_consumed);
|
|
return bytes_consumed;
|
|
}
|
|
} else if (byte == '\0') {
|
|
// don't bother with pointer incrementation. We're done.
|
|
bytes_consumed += 1;
|
|
log_trace(
|
|
"dns",
|
|
"_get_name_helper OUT. rec level %d success. %d bytes consumed.",
|
|
recursion_level, bytes_consumed);
|
|
return bytes_consumed;
|
|
} else {
|
|
log_trace("dns",
|
|
"_get_name_helper, segment 0x%hx encountered",
|
|
byte);
|
|
// We've now consumed a byte.
|
|
++data;
|
|
--data_len;
|
|
// Mark byte consumed after we check for first
|
|
// iteration. Do we have enough data left (must have
|
|
// null byte too)?
|
|
if ((byte + 1) > data_len) {
|
|
log_trace(
|
|
"dns",
|
|
"_get_name_helper OUT. ERR. Not enough data for segment %hd");
|
|
return 0;
|
|
}
|
|
// If we've consumed any bytes and are in a label, we're
|
|
// in a label chain. We need to add a dot.
|
|
if (bytes_consumed > 0) {
|
|
|
|
if (name_len < 1) {
|
|
log_warn(
|
|
"dns",
|
|
"Exceeded static name field allocation.");
|
|
return 0;
|
|
}
|
|
|
|
name[0] = '.';
|
|
name++;
|
|
name_len--;
|
|
}
|
|
// Now we've consumed a byte.
|
|
++bytes_consumed;
|
|
// Did we run out of our arbitrary buffer?
|
|
if (byte > name_len) {
|
|
log_warn(
|
|
"dns",
|
|
"Exceeded static name field allocation.");
|
|
return 0;
|
|
}
|
|
|
|
assert(data_len > 0);
|
|
memcpy(name, data, byte);
|
|
name += byte;
|
|
name_len -= byte;
|
|
data_len -= byte;
|
|
data += byte;
|
|
bytes_consumed += byte;
|
|
// Handled in the byte+1 check above.
|
|
assert(data_len > 0);
|
|
}
|
|
}
|
|
// We should never get here.
|
|
// For each byte we either have:
|
|
// -- a ptr, which terminates
|
|
// -- a null byte, which terminates
|
|
// -- a segment length which either terminates or ensures we keep
|
|
// looping
|
|
assert(0);
|
|
return 0;
|
|
}
|
|
|
|
// data: Where we are in the dns payload
|
|
// payload: the entire udp payload
|
|
static char *get_name(const char *data, uint16_t data_len, const char *payload,
|
|
uint16_t payload_len, uint16_t *bytes_consumed)
|
|
{
|
|
log_trace("dns", "call to get_name, data_len: %d", data_len);
|
|
char *name = xmalloc(MAX_NAME_LENGTH);
|
|
*bytes_consumed = get_name_helper(data, data_len, payload, payload_len,
|
|
name, MAX_NAME_LENGTH - 1, 0);
|
|
if (*bytes_consumed == 0) {
|
|
free(name);
|
|
return NULL;
|
|
}
|
|
// Our memset ensured null byte.
|
|
assert(name[MAX_NAME_LENGTH - 1] == '\0');
|
|
log_trace(
|
|
"dns",
|
|
"return success from get_name, bytes_consumed: %d, string: %s",
|
|
*bytes_consumed, name);
|
|
return name;
|
|
}
|
|
|
|
static bool process_response_question(char **data, uint16_t *data_len,
|
|
const char *payload, uint16_t payload_len,
|
|
fieldset_t *list)
|
|
{
|
|
// Payload is the start of the DNS packet, including header
|
|
// data is handle to the start of this RR
|
|
// data_len is a pointer to the how much total data we have to work
|
|
// with. This is awful. I'm bad and should feel bad.
|
|
uint16_t bytes_consumed = 0;
|
|
char *question_name =
|
|
get_name(*data, *data_len, payload, payload_len, &bytes_consumed);
|
|
// Error.
|
|
if (question_name == NULL) {
|
|
return 1;
|
|
}
|
|
assert(bytes_consumed > 0);
|
|
if ((bytes_consumed + sizeof(dns_question_tail)) > *data_len) {
|
|
free(question_name);
|
|
return 1;
|
|
}
|
|
dns_question_tail *tail = (dns_question_tail *)(*data + bytes_consumed);
|
|
uint16_t qtype = ntohs(tail->qtype);
|
|
uint16_t qclass = ntohs(tail->qclass);
|
|
// Build our new question fieldset
|
|
fieldset_t *qfs = fs_new_fieldset(NULL);
|
|
fs_add_unsafe_string(qfs, "name", question_name, 1);
|
|
fs_add_uint64(qfs, "qtype", qtype);
|
|
if (qtype > MAX_QTYPE || qtype_qtype_to_strid[qtype] == BAD_QTYPE_VAL) {
|
|
fs_add_string(qfs, "qtype_str", (char *)BAD_QTYPE_STR, 0);
|
|
} else {
|
|
// I've written worse things than this 3rd arg. But I want to be
|
|
// fast.
|
|
fs_add_string(qfs, "qtype_str",
|
|
(char *)qtype_strs[qtype_qtype_to_strid[qtype]],
|
|
0);
|
|
}
|
|
fs_add_uint64(qfs, "qclass", qclass);
|
|
// Now we're adding the new fs to the list.
|
|
fs_add_fieldset(list, NULL, qfs);
|
|
// Now update the pointers.
|
|
*data = *data + bytes_consumed + sizeof(dns_question_tail);
|
|
*data_len = *data_len - bytes_consumed - sizeof(dns_question_tail);
|
|
return 0;
|
|
}
|
|
|
|
static bool process_response_answer(char **data, uint16_t *data_len,
|
|
const char *payload, uint16_t payload_len,
|
|
fieldset_t *list)
|
|
{
|
|
log_trace("dns", "call to process_response_answer, data_len: %d",
|
|
*data_len);
|
|
// Payload is the start of the DNS packet, including header
|
|
// data is handle to the start of this RR
|
|
// data_len is a pointer to the how much total data we have to work
|
|
// with. This is awful. I'm bad and should feel bad.
|
|
uint16_t bytes_consumed = 0;
|
|
char *answer_name =
|
|
get_name(*data, *data_len, payload, payload_len, &bytes_consumed);
|
|
// Error.
|
|
if (answer_name == NULL) {
|
|
return 1;
|
|
}
|
|
assert(bytes_consumed > 0);
|
|
if ((bytes_consumed + sizeof(dns_answer_tail)) > *data_len) {
|
|
free(answer_name);
|
|
return 1;
|
|
}
|
|
dns_answer_tail *tail = (dns_answer_tail *)(*data + bytes_consumed);
|
|
uint16_t type = ntohs(tail->type);
|
|
uint16_t class = ntohs(tail->class);
|
|
uint32_t ttl = ntohl(tail->ttl);
|
|
uint16_t rdlength = ntohs(tail->rdlength);
|
|
char *rdata = tail->rdata;
|
|
|
|
if ((rdlength + bytes_consumed + sizeof(dns_answer_tail)) > *data_len) {
|
|
free(answer_name);
|
|
return 1;
|
|
}
|
|
// Build our new question fieldset
|
|
fieldset_t *afs = fs_new_fieldset(NULL);
|
|
fs_add_unsafe_string(afs, "name", answer_name, 1);
|
|
fs_add_uint64(afs, "type", type);
|
|
if (type > MAX_QTYPE || qtype_qtype_to_strid[type] == BAD_QTYPE_VAL) {
|
|
fs_add_string(afs, "type_str", (char *)BAD_QTYPE_STR, 0);
|
|
} else {
|
|
// I've written worse things than this 3rd arg. But I want to be
|
|
// fast.
|
|
fs_add_string(afs, "type_str",
|
|
(char *)qtype_strs[qtype_qtype_to_strid[type]],
|
|
0);
|
|
}
|
|
fs_add_uint64(afs, "class", class);
|
|
fs_add_uint64(afs, "ttl", ttl);
|
|
fs_add_uint64(afs, "rdlength", rdlength);
|
|
|
|
// XXX Fill this out for the other types we care about.
|
|
if (type == DNS_QTYPE_NS || type == DNS_QTYPE_CNAME) {
|
|
uint16_t rdata_bytes_consumed = 0;
|
|
char *rdata_name = get_name(rdata, rdlength, payload,
|
|
payload_len, &rdata_bytes_consumed);
|
|
if (rdata_name == NULL) {
|
|
fs_add_uint64(afs, "rdata_is_parsed", 0);
|
|
fs_add_binary(afs, "rdata", rdlength, rdata, 0);
|
|
} else {
|
|
fs_add_uint64(afs, "rdata_is_parsed", 1);
|
|
fs_add_unsafe_string(afs, "rdata", rdata_name, 1);
|
|
}
|
|
} else if (type == DNS_QTYPE_MX) {
|
|
uint16_t rdata_bytes_consumed = 0;
|
|
if (rdlength <= 4) {
|
|
fs_add_uint64(afs, "rdata_is_parsed", 0);
|
|
fs_add_binary(afs, "rdata", rdlength, rdata, 0);
|
|
} else {
|
|
char *rdata_name =
|
|
get_name(rdata + 2, rdlength - 2, payload,
|
|
payload_len, &rdata_bytes_consumed);
|
|
if (rdata_name == NULL) {
|
|
fs_add_uint64(afs, "rdata_is_parsed", 0);
|
|
fs_add_binary(afs, "rdata", rdlength, rdata, 0);
|
|
} else {
|
|
// (largest value 16bit) + " " + answer + null
|
|
char *rdata_with_pref =
|
|
xmalloc(5 + 1 + strlen(rdata_name) + 1);
|
|
|
|
uint8_t num_printed =
|
|
snprintf(rdata_with_pref, 6, "%hu ",
|
|
ntohs(*(uint16_t *)rdata));
|
|
memcpy(rdata_with_pref + num_printed,
|
|
rdata_name, strlen(rdata_name));
|
|
fs_add_uint64(afs, "rdata_is_parsed", 1);
|
|
fs_add_unsafe_string(afs, "rdata",
|
|
rdata_with_pref, 1);
|
|
}
|
|
}
|
|
} else if (type == DNS_QTYPE_TXT) {
|
|
if (rdlength >= 1 && (rdlength - 1) != *(uint8_t *)rdata) {
|
|
log_warn(
|
|
"dns",
|
|
"TXT record with wrong TXT len. Not processing.");
|
|
fs_add_uint64(afs, "rdata_is_parsed", 0);
|
|
fs_add_binary(afs, "rdata", rdlength, rdata, 0);
|
|
} else {
|
|
fs_add_uint64(afs, "rdata_is_parsed", 1);
|
|
char *txt = xmalloc(rdlength);
|
|
memcpy(txt, rdata + 1, rdlength - 1);
|
|
fs_add_unsafe_string(afs, "rdata", txt, 1);
|
|
}
|
|
} else if (type == DNS_QTYPE_A) {
|
|
if (rdlength != 4) {
|
|
log_warn(
|
|
"dns",
|
|
"A record with IP of length %d. Not processing.",
|
|
rdlength);
|
|
fs_add_uint64(afs, "rdata_is_parsed", 0);
|
|
fs_add_binary(afs, "rdata", rdlength, rdata, 0);
|
|
} else {
|
|
fs_add_uint64(afs, "rdata_is_parsed", 1);
|
|
char *addr =
|
|
strdup(inet_ntoa(*(struct in_addr *)rdata));
|
|
fs_add_unsafe_string(afs, "rdata", addr, 1);
|
|
}
|
|
} else if (type == DNS_QTYPE_AAAA) {
|
|
if (rdlength != 16) {
|
|
log_warn(
|
|
"dns",
|
|
"AAAA record with IP of length %d. Not processing.",
|
|
rdlength);
|
|
fs_add_uint64(afs, "rdata_is_parsed", 0);
|
|
fs_add_binary(afs, "rdata", rdlength, rdata, 0);
|
|
} else {
|
|
fs_add_uint64(afs, "rdata_is_parsed", 1);
|
|
char *ipv6_str = xmalloc(INET6_ADDRSTRLEN);
|
|
|
|
inet_ntop(AF_INET6, (struct sockaddr_in6 *)rdata,
|
|
ipv6_str, INET6_ADDRSTRLEN);
|
|
|
|
fs_add_unsafe_string(afs, "rdata", ipv6_str, 1);
|
|
}
|
|
} else {
|
|
fs_add_uint64(afs, "rdata_is_parsed", 0);
|
|
fs_add_binary(afs, "rdata", rdlength, rdata, 0);
|
|
}
|
|
// Now we're adding the new fs to the list.
|
|
fs_add_fieldset(list, NULL, afs);
|
|
// Now update the pointers.
|
|
*data = *data + bytes_consumed + sizeof(dns_answer_tail) + rdlength;
|
|
*data_len =
|
|
*data_len - bytes_consumed - sizeof(dns_answer_tail) - rdlength;
|
|
log_trace("dns",
|
|
"return success from process_response_answer, data_len: %d",
|
|
*data_len);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Start of required zmap exports.
|
|
*/
|
|
|
|
|
|
|
|
static int dns_global_initialize(struct state_conf *conf)
|
|
{
|
|
setup_qtype_str_map();
|
|
// strip off any leading or trailing semicolons
|
|
if (*conf->probe_args == probe_arg_delimitor[0]) {
|
|
log_debug("dns", "Probe args (%s) contains leading semicolon. Stripping.", conf->probe_args);
|
|
conf->probe_args++;
|
|
}
|
|
if (conf->probe_args[strlen(conf->probe_args) - 1] == probe_arg_delimitor[0]) {
|
|
log_debug("dns", "Probe args (%s) contains trailing semicolon. Stripping.", conf->probe_args);
|
|
conf->probe_args[strlen(conf->probe_args) - 1] = '\0';
|
|
}
|
|
|
|
char **domains = NULL;
|
|
num_questions = 0;
|
|
|
|
if (conf->probe_args) {
|
|
char* questions_ctx;
|
|
char* domain_ctx;
|
|
char* domain_and_qtype = strtok_r(conf->probe_args, probe_arg_delimitor, &questions_ctx);
|
|
|
|
// Process each pair
|
|
while (domain_and_qtype != NULL) {
|
|
// resize the array to accommodate the new pair
|
|
domains = xrealloc(domains, (num_questions + 1) * sizeof(char *));
|
|
qtypes = xrealloc(qtypes, (num_questions + 1) * sizeof(uint16_t));
|
|
rdbits = xrealloc(rdbits, (num_questions + 1) * sizeof(uint8_t));
|
|
rdbits[num_questions] = default_rdbit;
|
|
|
|
// Tokenize pair based on comma
|
|
char *qtype_token = strtok_r(domain_and_qtype, domain_qtype_delimitor, &domain_ctx);
|
|
char *domain_token = strtok_r(NULL, domain_qtype_delimitor, &domain_ctx);
|
|
if (strchr(qtype_token, rn_delimitor[0]) != NULL) {
|
|
// need to check if user supplied the no-recursion bit
|
|
char* rbit_ctx;
|
|
char *recurse_token = strtok_r(qtype_token, rn_delimitor, &rbit_ctx);
|
|
recurse_token = strtok_r(NULL, rn_delimitor, &rbit_ctx);
|
|
// check if the no-recursion field matches the expected value ("nr")
|
|
if (strcmp(recurse_token, qopts_rn) == 0) {
|
|
rdbits[num_questions] = 0;
|
|
} else {
|
|
log_warn("dns", "invalid text after DNS query type (%s). no recursion set with \"nr\"", recurse_token);
|
|
}
|
|
}
|
|
if (domain_token == NULL || qtype_token == NULL) {
|
|
log_fatal( "dns", "Invalid probe args (%s). Format: \"A,google.com\" " "or \"A,google.com;A,example.com\"", conf->probe_args);
|
|
}
|
|
if (strlen(domain_token) == 0) {
|
|
log_fatal( "dns", "Invalid domain, domain cannot be empty.");
|
|
}
|
|
uint domain_len = strlen(domain_token);
|
|
// add space for the null terminator
|
|
char* domain_ptr = xmalloc(domain_len + 1);
|
|
strncpy(domain_ptr, domain_token, domain_len);
|
|
// add null terminator
|
|
domain_ptr[domain_len] = '\0';
|
|
|
|
|
|
// print debug info
|
|
if (rdbits[num_questions] == 0) {
|
|
// recursion disabled
|
|
log_debug("dns", "parsed user input to scan domain (%s), for qtype (%s) w/o recursion", domain_ptr, qtype_token);
|
|
} else {
|
|
log_debug("dns", "parsed user input to scan domain (%s), for qtype (%s) with recursion", domain_ptr, qtype_token);
|
|
}
|
|
// add the new pair to the array
|
|
domains[num_questions] = domain_ptr;
|
|
qtypes[num_questions] = qtype_str_to_code(qtype_token);
|
|
|
|
if (!qtypes[num_questions]) {
|
|
log_fatal("dns", "Incorrect qtype supplied. %s", qtype_token);
|
|
}
|
|
|
|
// move to the next pair of domain/qtype
|
|
domain_and_qtype = strtok_r(NULL, probe_arg_delimitor, &questions_ctx);
|
|
num_questions++;
|
|
}
|
|
}
|
|
|
|
if (num_questions == 0) {
|
|
// user didn't provide any questions, setting up a default
|
|
log_warn("dns", "no dns questions provided, using default domain (%s) and qtype (%s)", default_domain, qtype_strs[qtype_qtype_to_strid[default_qtype]]);
|
|
// Resize the array to accommodate the new pair
|
|
domains = xrealloc(domains, (num_questions + 1) * sizeof(char *));
|
|
qtypes = xrealloc(qtypes, (num_questions + 1) * sizeof(uint16_t));
|
|
|
|
// Add the new pair to the array
|
|
domains[num_questions] = strdup(default_domain);
|
|
qtypes[num_questions] = default_qtype;
|
|
|
|
num_questions = 1;
|
|
} else {
|
|
log_debug("dns", "number of dns questions: %d", num_questions);
|
|
}
|
|
|
|
if (conf->packet_streams % num_questions != 0) {
|
|
// probe count must be a multiple of the number of DNS questions
|
|
log_fatal("dns", "number of probes (%d) must be a multiple of the number of DNS questions (%d)."
|
|
"Example: '-P 4 --probe-args \"A,google.com;AAAA,cloudflare.com\"'", conf->packet_streams, num_questions);
|
|
}
|
|
// Setup the global structures
|
|
dns_packets = xmalloc(sizeof(char *) * num_questions);
|
|
dns_packet_lens = xmalloc(sizeof(uint16_t) * num_questions);
|
|
qname_lens = xmalloc(sizeof(uint16_t) * num_questions);
|
|
qnames = xmalloc(sizeof(char *) * num_questions);
|
|
num_ports = conf->source_port_last - conf->source_port_first + 1;
|
|
|
|
size_t max_payload_len;
|
|
int ret = build_global_dns_packets(domains, num_questions, &max_payload_len);
|
|
module_dns.max_packet_length = max_payload_len + sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr);
|
|
return ret;
|
|
}
|
|
|
|
static int dns_global_cleanup(UNUSED struct state_conf *zconf,
|
|
UNUSED struct state_send *zsend,
|
|
UNUSED struct state_recv *zrecv)
|
|
{
|
|
if (dns_packets) {
|
|
for (int i = 0; i < num_questions; i++) {
|
|
if (dns_packets[i]) {
|
|
free(dns_packets[i]);
|
|
}
|
|
}
|
|
free(dns_packets);
|
|
}
|
|
dns_packets = NULL;
|
|
|
|
if (qnames) {
|
|
for (int i = 0; i < num_questions; i++) {
|
|
if (qnames[i]) {
|
|
free(qnames[i]);
|
|
}
|
|
}
|
|
free(qnames);
|
|
}
|
|
qnames = NULL;
|
|
|
|
if (dns_packet_lens) {
|
|
free(dns_packet_lens);
|
|
}
|
|
|
|
if (qname_lens) {
|
|
free(qname_lens);
|
|
}
|
|
|
|
if (qtypes) {
|
|
free(qtypes);
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int dns_init_perthread(void *buf, macaddr_t *src, macaddr_t *gw,
|
|
UNUSED void **arg_ptr)
|
|
{
|
|
memset(buf, 0, MAX_PACKET_SIZE);
|
|
|
|
// Setup assuming num_questions == 0
|
|
struct ether_header *eth_header = (struct ether_header *)buf;
|
|
make_eth_header(eth_header, src, gw);
|
|
|
|
struct ip *ip_header = (struct ip *)(ð_header[1]);
|
|
uint16_t len = htons(sizeof(struct ip) + sizeof(struct udphdr) +
|
|
dns_packet_lens[0]);
|
|
make_ip_header(ip_header, IPPROTO_UDP, len);
|
|
|
|
struct udphdr *udp_header = (struct udphdr *)(&ip_header[1]);
|
|
len = sizeof(struct udphdr) + dns_packet_lens[0];
|
|
make_udp_header(udp_header, len);
|
|
|
|
char *payload = (char *)(&udp_header[1]);
|
|
|
|
memcpy(payload, dns_packets[0], dns_packet_lens[0]);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
// get_dns_question_index_by_probe_num - Find the dns question associated with this probe number
|
|
// We allow users to enter a probe count that is a multiple of the number of DNS questions.
|
|
// send.c will iterate with this probe count, sending a packet for each probe number
|
|
// Ex. -P 4 --probe-args="A,google.com;AAAA,cloudflare.com" - send 2 probes for each question
|
|
// Probe_num | num_questions = dns_index
|
|
// 0 | 2 = 0
|
|
// 1 | 2 = 1
|
|
// 2 | 2 = 0
|
|
// 3 | 2 = 1
|
|
int get_dns_question_index_by_probe_num(int probe_num) {
|
|
assert(probe_num >= 0);
|
|
return probe_num % num_questions;
|
|
}
|
|
|
|
int dns_make_packet(void *buf, size_t *buf_len, ipaddr_n_t src_ip,
|
|
ipaddr_n_t dst_ip, port_n_t dport, uint8_t ttl,
|
|
uint32_t *validation, int probe_num, UNUSED void *arg)
|
|
{
|
|
struct ether_header *eth_header = (struct ether_header *)buf;
|
|
struct ip *ip_header = (struct ip *)(ð_header[1]);
|
|
struct udphdr *udp_header = (struct udphdr *)&ip_header[1];
|
|
|
|
// For num_questions == 0, we handle this in per-thread init. Do less
|
|
// work
|
|
if (num_questions > 0) {
|
|
int dns_index = get_dns_question_index_by_probe_num(probe_num);
|
|
uint16_t encoded_len =
|
|
htons(sizeof(struct ip) + sizeof(struct udphdr) +
|
|
dns_packet_lens[dns_index]);
|
|
make_ip_header(ip_header, IPPROTO_UDP, encoded_len);
|
|
|
|
encoded_len =
|
|
sizeof(struct udphdr) + dns_packet_lens[dns_index];
|
|
make_udp_header(udp_header, encoded_len);
|
|
|
|
char *payload = (char *)(&udp_header[1]);
|
|
*buf_len = sizeof(struct ether_header) + sizeof(struct ip) +
|
|
sizeof(struct udphdr) + dns_packet_lens[dns_index];
|
|
|
|
assert(*buf_len <= MAX_PACKET_SIZE);
|
|
|
|
memcpy(payload, dns_packets[dns_index],
|
|
dns_packet_lens[dns_index]);
|
|
}
|
|
|
|
ip_header->ip_src.s_addr = src_ip;
|
|
ip_header->ip_dst.s_addr = dst_ip;
|
|
ip_header->ip_ttl = ttl;
|
|
// Above we wanted to look up the dns question index (so we could send 2 probes for the same DNS query)
|
|
// Here we want the port to be unique regardless of if this is the 2nd probe to the same DNS query so using
|
|
// probe_num itself to set the unique UDP source port.
|
|
udp_header->uh_sport =
|
|
htons(get_src_port(num_ports, probe_num, validation));
|
|
udp_header->uh_dport = dport;
|
|
|
|
dns_header *dns_header_p = (dns_header *)&udp_header[1];
|
|
|
|
dns_header_p->id = validation[2] & 0xFFFF;
|
|
|
|
ip_header->ip_sum = 0;
|
|
ip_header->ip_sum = zmap_ip_checksum((unsigned short *)ip_header);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
void dns_print_packet(FILE *fp, void *packet)
|
|
{
|
|
struct ether_header *ethh = (struct ether_header *)packet;
|
|
struct ip *iph = (struct ip *)ðh[1];
|
|
struct udphdr *udph = (struct udphdr *)(&iph[1]);
|
|
fprintf(fp, PRINT_PACKET_SEP);
|
|
fprintf(fp, "dns { source: %u | dest: %u | checksum: %#04X }\n",
|
|
ntohs(udph->uh_sport), ntohs(udph->uh_dport),
|
|
ntohs(udph->uh_sum));
|
|
fprintf_ip_header(fp, iph);
|
|
fprintf_eth_header(fp, ethh);
|
|
fprintf(fp, PRINT_PACKET_SEP);
|
|
}
|
|
|
|
int dns_validate_packet(const struct ip *ip_hdr, uint32_t len, uint32_t *src_ip,
|
|
uint32_t *validation, const struct port_conf *ports)
|
|
{
|
|
// this does the heavy lifting including ICMP validation
|
|
if (udp_do_validate_packet(ip_hdr, len, src_ip, validation, num_ports,
|
|
SRC_PORT_VALIDATION,
|
|
ports) == PACKET_INVALID) {
|
|
return PACKET_INVALID;
|
|
}
|
|
if (ip_hdr->ip_p == IPPROTO_UDP) {
|
|
struct udphdr *udp = get_udp_header(ip_hdr, len);
|
|
if (!udp) {
|
|
return PACKET_INVALID;
|
|
}
|
|
// verify our packet length
|
|
uint16_t udp_len = ntohs(udp->uh_ulen);
|
|
int match = 0;
|
|
for (int i = 0; i < num_questions; i++) {
|
|
if (udp_len >= dns_packet_lens[i]) {
|
|
match += 1;
|
|
}
|
|
}
|
|
if (match == 0) {
|
|
return PACKET_INVALID;
|
|
}
|
|
if (len < udp_len) {
|
|
return PACKET_INVALID;
|
|
}
|
|
}
|
|
return PACKET_VALID;
|
|
}
|
|
|
|
void dns_add_null_fs(fieldset_t *fs)
|
|
{
|
|
fs_add_null(fs, "dns_id");
|
|
fs_add_null(fs, "dns_rd");
|
|
fs_add_null(fs, "dns_tc");
|
|
fs_add_null(fs, "dns_aa");
|
|
fs_add_null(fs, "dns_opcode");
|
|
fs_add_null(fs, "dns_qr");
|
|
fs_add_null(fs, "dns_rcode");
|
|
fs_add_null(fs, "dns_cd");
|
|
fs_add_null(fs, "dns_ad");
|
|
fs_add_null(fs, "dns_z");
|
|
fs_add_null(fs, "dns_ra");
|
|
fs_add_null(fs, "dns_qdcount");
|
|
fs_add_null(fs, "dns_ancount");
|
|
fs_add_null(fs, "dns_nscount");
|
|
fs_add_null(fs, "dns_arcount");
|
|
|
|
fs_add_repeated(fs, "dns_questions", fs_new_repeated_fieldset());
|
|
fs_add_repeated(fs, "dns_answers", fs_new_repeated_fieldset());
|
|
fs_add_repeated(fs, "dns_authorities", fs_new_repeated_fieldset());
|
|
fs_add_repeated(fs, "dns_additionals", fs_new_repeated_fieldset());
|
|
|
|
fs_add_uint64(fs, "dns_parse_err", 1);
|
|
fs_add_uint64(fs, "dns_unconsumed_bytes", 0);
|
|
}
|
|
|
|
void dns_process_packet(const u_char *packet, uint32_t len, fieldset_t *fs,
|
|
uint32_t *validation,
|
|
__attribute__((unused)) struct timespec ts)
|
|
{
|
|
struct ip *ip_hdr = (struct ip *)&packet[sizeof(struct ether_header)];
|
|
if (ip_hdr->ip_p == IPPROTO_UDP) {
|
|
struct udphdr *udp_hdr = get_udp_header(ip_hdr, len);
|
|
assert(udp_hdr);
|
|
uint16_t udp_len = ntohs(udp_hdr->uh_ulen);
|
|
|
|
int match = 0;
|
|
bool is_valid = 0;
|
|
for (int i = 0; i < num_questions; i++) {
|
|
if (udp_len < dns_packet_lens[i]) {
|
|
continue;
|
|
}
|
|
match += 1;
|
|
|
|
char *qname_p = NULL;
|
|
dns_question_tail *tail_p = NULL;
|
|
dns_header *dns_header_p = (dns_header *)&udp_hdr[1];
|
|
// verify our dns transaction id
|
|
if (dns_header_p->id == (validation[2] & 0xFFFF)) {
|
|
// Verify our question
|
|
qname_p =
|
|
(char *)dns_header_p + sizeof(dns_header);
|
|
tail_p =
|
|
(dns_question_tail *)(dns_packets[i] +
|
|
sizeof(dns_header) +
|
|
qname_lens[i]);
|
|
// Verify our qname
|
|
if (strcmp(qnames[i], qname_p) == 0) {
|
|
// Verify the qtype and qclass.
|
|
if (tail_p->qtype == htons(qtypes[i]) &&
|
|
tail_p->qclass == htons(0x01)) {
|
|
is_valid = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert(match > 0);
|
|
|
|
dns_header *dns_hdr = (dns_header *)&udp_hdr[1];
|
|
uint16_t qr = dns_hdr->qr;
|
|
uint16_t rcode = dns_hdr->rcode;
|
|
// Success: Has the right validation bits and the right Q
|
|
// App success: has qr and rcode bits right
|
|
// Any app level parsing issues: dns_parse_err
|
|
//
|
|
fs_add_uint64(fs, "sport", ntohs(udp_hdr->uh_sport));
|
|
fs_add_uint64(fs, "dport", ntohs(udp_hdr->uh_dport));
|
|
|
|
// High level info
|
|
fs_add_string(fs, "classification", (char *)"dns", 0);
|
|
fs_add_bool(fs, "success", is_valid);
|
|
// additional UDP information
|
|
fs_add_bool(fs, "app_success",
|
|
is_valid && (qr == DNS_QR_ANSWER) &&
|
|
(rcode == DNS_RCODE_NOERR));
|
|
// ICMP info
|
|
fs_add_null_icmp(fs);
|
|
fs_add_uint64(fs, "udp_len", udp_len);
|
|
// DNS data
|
|
if (!is_valid) {
|
|
dns_add_null_fs(fs);
|
|
} else {
|
|
// DNS header
|
|
fs_add_uint64(fs, "dns_id", ntohs(dns_hdr->id));
|
|
fs_add_uint64(fs, "dns_rd", dns_hdr->rd);
|
|
fs_add_uint64(fs, "dns_tc", dns_hdr->tc);
|
|
fs_add_uint64(fs, "dns_aa", dns_hdr->aa);
|
|
fs_add_uint64(fs, "dns_opcode", dns_hdr->opcode);
|
|
fs_add_uint64(fs, "dns_qr", qr);
|
|
fs_add_uint64(fs, "dns_rcode", rcode);
|
|
fs_add_uint64(fs, "dns_cd", dns_hdr->cd);
|
|
fs_add_uint64(fs, "dns_ad", dns_hdr->ad);
|
|
fs_add_uint64(fs, "dns_z", dns_hdr->z);
|
|
fs_add_uint64(fs, "dns_ra", dns_hdr->ra);
|
|
fs_add_uint64(fs, "dns_qdcount",
|
|
ntohs(dns_hdr->qdcount));
|
|
fs_add_uint64(fs, "dns_ancount",
|
|
ntohs(dns_hdr->ancount));
|
|
fs_add_uint64(fs, "dns_nscount",
|
|
ntohs(dns_hdr->nscount));
|
|
fs_add_uint64(fs, "dns_arcount",
|
|
ntohs(dns_hdr->arcount));
|
|
// And now for the complicated part. Hierarchical data.
|
|
char *data = ((char *)dns_hdr) + sizeof(dns_header);
|
|
uint16_t data_len =
|
|
udp_len - sizeof(udp_hdr) - sizeof(dns_header);
|
|
bool err = 0;
|
|
// Questions
|
|
fieldset_t *list = fs_new_repeated_fieldset();
|
|
for (int i = 0; i < ntohs(dns_hdr->qdcount) && !err;
|
|
i++) {
|
|
err = process_response_question(
|
|
&data, &data_len, (char *)dns_hdr, udp_len,
|
|
list);
|
|
}
|
|
fs_add_repeated(fs, "dns_questions", list);
|
|
// Answers
|
|
list = fs_new_repeated_fieldset();
|
|
for (int i = 0; i < ntohs(dns_hdr->ancount) && !err;
|
|
i++) {
|
|
err = process_response_answer(&data, &data_len,
|
|
(char *)dns_hdr,
|
|
udp_len, list);
|
|
}
|
|
fs_add_repeated(fs, "dns_answers", list);
|
|
// Authorities
|
|
list = fs_new_repeated_fieldset();
|
|
for (int i = 0; i < ntohs(dns_hdr->nscount) && !err;
|
|
i++) {
|
|
err = process_response_answer(&data, &data_len,
|
|
(char *)dns_hdr,
|
|
udp_len, list);
|
|
}
|
|
fs_add_repeated(fs, "dns_authorities", list);
|
|
// Additionals
|
|
list = fs_new_repeated_fieldset();
|
|
for (int i = 0; i < ntohs(dns_hdr->arcount) && !err;
|
|
i++) {
|
|
err = process_response_answer(&data, &data_len,
|
|
(char *)dns_hdr,
|
|
udp_len, list);
|
|
}
|
|
fs_add_repeated(fs, "dns_additionals", list);
|
|
// Do we have unconsumed data?
|
|
if (data_len != 0) {
|
|
err = 1;
|
|
}
|
|
// Did we parse OK?
|
|
fs_add_uint64(fs, "dns_parse_err", err);
|
|
fs_add_uint64(fs, "dns_unconsumed_bytes", data_len);
|
|
}
|
|
// Now the raw stuff.
|
|
fs_add_binary(fs, "raw_data", (udp_len - sizeof(struct udphdr)),
|
|
(void *)&udp_hdr[1], 0);
|
|
} else if (ip_hdr->ip_p == IPPROTO_ICMP) {
|
|
fs_add_null(fs, "sport");
|
|
fs_add_null(fs, "dport");
|
|
fs_add_constchar(fs, "classification", "icmp");
|
|
fs_add_bool(fs, "success", 0);
|
|
fs_add_bool(fs, "app_success", 0);
|
|
// Populate all ICMP Fields
|
|
fs_populate_icmp_from_iphdr(ip_hdr, len, fs);
|
|
fs_add_null(fs, "udp_len");
|
|
dns_add_null_fs(fs);
|
|
fs_add_binary(fs, "raw_data", len, (char *)packet, 0);
|
|
} else {
|
|
// This should not happen. Both the pcap filter and validate
|
|
// packet prevent this.
|
|
log_fatal("dns", "Die. This can only happen if you "
|
|
"change the pcap filter and don't update the "
|
|
"process function.");
|
|
}
|
|
}
|
|
|
|
static fielddef_t fields[] = {
|
|
{.name = "sport", .type = "int", .desc = "UDP source port"},
|
|
{.name = "dport", .type = "int", .desc = "UDP destination port"},
|
|
CLASSIFICATION_SUCCESS_FIELDSET_FIELDS,
|
|
{.name = "app_success",
|
|
.type = "bool",
|
|
.desc = "Is the RA bit set with no error code?"},
|
|
ICMP_FIELDSET_FIELDS,
|
|
{.name = "udp_len", .type = "int", .desc = "UDP packet length"},
|
|
{.name = "dns_id", .type = "int", .desc = "DNS transaction ID"},
|
|
{.name = "dns_rd", .type = "int", .desc = "DNS recursion desired"},
|
|
{.name = "dns_tc", .type = "int", .desc = "DNS packet truncated"},
|
|
{.name = "dns_aa", .type = "int", .desc = "DNS authoritative answer"},
|
|
{.name = "dns_opcode", .type = "int", .desc = "DNS opcode (query type)"},
|
|
{.name = "dns_qr", .type = "int", .desc = "DNS query(0) or response (1)"},
|
|
{.name = "dns_rcode", .type = "int", .desc = "DNS response code"},
|
|
{.name = "dns_cd", .type = "int", .desc = "DNS checking disabled"},
|
|
{.name = "dns_ad", .type = "int", .desc = "DNS authenticated data"},
|
|
{.name = "dns_z", .type = "int", .desc = "DNS reserved"},
|
|
{.name = "dns_ra", .type = "int", .desc = "DNS recursion available"},
|
|
{.name = "dns_qdcount", .type = "int", .desc = "DNS number questions"},
|
|
{.name = "dns_ancount", .type = "int", .desc = "DNS number answer RR's"},
|
|
{.name = "dns_nscount",
|
|
.type = "int",
|
|
.desc = "DNS number NS RR's in authority section"},
|
|
{.name = "dns_arcount",
|
|
.type = "int",
|
|
.desc = "DNS number additional RR's"},
|
|
{.name = "dns_questions", .type = "repeated", .desc = "DNS question list"},
|
|
{.name = "dns_answers", .type = "repeated", .desc = "DNS answer list"},
|
|
{.name = "dns_authorities",
|
|
.type = "repeated",
|
|
.desc = "DNS authority list"},
|
|
{.name = "dns_additionals",
|
|
.type = "repeated",
|
|
.desc = "DNS additional list"},
|
|
{.name = "dns_parse_err",
|
|
.type = "int",
|
|
.desc = "Problem parsing the DNS response"},
|
|
{.name = "dns_unconsumed_bytes",
|
|
.type = "int",
|
|
.desc = "Bytes left over when parsing"
|
|
" the DNS response"},
|
|
{.name = "raw_data", .type = "binary", .desc = "UDP payload"},
|
|
};
|
|
|
|
probe_module_t module_dns = {
|
|
.name = "dns",
|
|
.max_packet_length = 0, // set in init
|
|
.pcap_filter = "udp || icmp",
|
|
.pcap_snaplen = PCAP_SNAPLEN,
|
|
.port_args = 1,
|
|
.thread_initialize = &dns_init_perthread,
|
|
.global_initialize = &dns_global_initialize,
|
|
.make_packet = &dns_make_packet,
|
|
.print_packet = &dns_print_packet,
|
|
.validate_packet = &dns_validate_packet,
|
|
.process_packet = &dns_process_packet,
|
|
.close = &dns_global_cleanup,
|
|
.output_type = OUTPUT_TYPE_DYNAMIC,
|
|
.fields = fields,
|
|
.numfields = sizeof(fields) / sizeof(fields[0]),
|
|
.helptext =
|
|
"This module sends out DNS queries and parses basic responses. "
|
|
"By default, the module will perform an A record lookup for "
|
|
"google.com. You can specify other queries using the --probe-args "
|
|
"argument in the form: 'type,query', e.g. 'A,google.com'. The --probes/-P "
|
|
"flag must be set to a multiple of the number of DNS questions. The module "
|
|
"supports sending the the following types of queries: A, NS, CNAME, SOA, "
|
|
"PTR, MX, TXT, AAAA, RRSIG, and ALL. In order to send queries with the "
|
|
"'recursion desired' bit set to 0, append the suffix ':nr' to the query "
|
|
"type, e.g. 'A:nr,google.com'. The module will accept and attempt "
|
|
"to parse all DNS responses. There is currently support for parsing out "
|
|
"full data from A, NS, CNAME, MX, TXT, and AAAA. Any other types will be "
|
|
"output in raw form."
|
|
|
|
};
|