Skip to content

Instantly share code, notes, and snippets.

@philpennock
Last active October 1, 2022 06:01
Show Gist options
  • Select an option

  • Save philpennock/ed8f49e9edf2e8ea5dfb042e302dd502 to your computer and use it in GitHub Desktop.

Select an option

Save philpennock/ed8f49e9edf2e8ea5dfb042e302dd502 to your computer and use it in GitHub Desktop.
// Copyright © 2017 Pennock Tech, LLC.
// All rights reserved, except as granted under license.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#ifdef __gnu_linux__
# define _BSD_SOURCE
#endif
#include <ctype.h>
#ifdef __gnu_linux__
# include <getopt.h>
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#ifdef __APPLE__
# define BIND_8_COMPAT /* to get <arpa/nameser_compat.h> with T_FOO macros */
#endif
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <resolv.h>
// Untested by me, but _should_ work
#ifdef __use_openbsd__
# define NS_HFIXEDSZ HFIXEDSZ
#endif
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
#ifndef T_A6
# define T_A6 38
// missing on macOS
#endif
#ifndef T_OPT
# define T_OPT 41
// missing on Linux
#endif
#ifndef T_RRSIG
# define T_RRSIG 46
#endif
#ifndef T_SPF
# define T_SPF 99
#endif
#ifndef T_TSIG
# define T_TSIG 250
// missing on macOS
#endif
#ifndef T_URI
# define T_URI 256
#endif
#ifndef T_CAA
# define T_CAA 257
#endif
static struct qtype_num_s {
char *name;
int qtype;
} qtypes[] = {
{ "a", T_A },
{ "a6", T_A6 },
{ "aaaa", T_AAAA },
{ "afsdb", T_AFSDB },
{ "any", T_ANY },
{ "atma", T_ATMA },
{ "axfr", T_AXFR },
{ "caa", T_CAA },
{ "cname", T_CNAME },
{ "eid", T_EID },
{ "gpos", T_GPOS },
{ "hinfo", T_HINFO },
{ "isdn", T_ISDN },
{ "ixfr", T_IXFR },
{ "key", T_KEY },
{ "loc", T_LOC },
{ "maila", T_MAILA },
{ "mailb", T_MAILB },
{ "mb", T_MB },
{ "md", T_MD },
{ "mf", T_MF },
{ "mg", T_MG },
{ "minfo", T_MINFO },
{ "mr", T_MR },
{ "mx", T_MX },
{ "naptr", T_NAPTR },
{ "nimloc", T_NIMLOC },
{ "ns", T_NS },
{ "nsap", T_NSAP },
{ "nsap_ptr", T_NSAP_PTR },
{ "null", T_NULL },
{ "nxt", T_NXT },
{ "opt", T_OPT },
{ "ptr", T_PTR },
{ "px", T_PX },
{ "rp", T_RP },
{ "rt", T_RT },
{ "sig", T_SIG },
{ "soa", T_SOA },
{ "spf", T_SPF },
{ "srv", T_SRV },
{ "tsig", T_TSIG },
{ "txt", T_TXT },
{ "uri", T_URI },
{ "wks", T_WKS },
{ "x25", T_X25 },
};
static int qtypes_size = sizeof(qtypes) / sizeof(qtypes[0]);
static int
dns_type_from_string(const char * const name)
{
char *lname = strdup(name); //
size_t len = strlen(lname);
for (size_t i = 0; lname[i] && i < len; i++) {
lname[i] = tolower(lname[i]);
}
// Expect usually A/AAAA; it's a small list, sufficiently efficient to just walk it each time.
for (size_t i = 0; i < qtypes_size; i++) {
if (strcmp(lname, qtypes[i].name) == 0) {
free(lname);
return qtypes[i].qtype;
}
}
free(lname);
return -1;
}
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
const char *
rcode_to_string(uint16_t rcode)
{
switch (rcode) {
case 0: return "NoError: No Error";
case 1: return "FormErr: Format Error";
case 2: return "ServFail: Server Failure";
case 3: return "NXDomain: Non-Existent Domain";
case 4: return "NotImp: Not Implemented";
case 5: return "Refused: Query Refused";
case 6: return "YXDomain: Name Exists when it should not";
case 7: return "YXRRSet: RR Set Exists when it should not";
case 8: return "NXRRSet: RR Set that should exist does not";
case 9: return "NotAuth: Not Authorized / Server Not Authoritative for zone";
case 10: return "NotZone: Name not contained in zone";
case 16: return "BADSIG: TSIG Signature Failure (or BADVERS: Bad OPT Version)";
case 17: return "BADKEY: Key not recognized";
case 18: return "BADTIME: Signature out of time window";
case 19: return "BADMODE: Bad TKEY Mode";
case 20: return "BADNAME: Duplicate key name";
case 21: return "BADALG: Algorithm not supported";
case 22: return "BADTRUNC: Bad Truncation";
case 23: return "BADCOOKIE: Bad/missing Server Cookie";
default: return "<unknown>";
}
}
const char *
edns0_optcode_to_string(uint16_t optcode)
{
switch (optcode) {
case 1: return "LLQ";
case 2: return "UL";
case 3: return "NSID";
case 4: return "Reserved";
case 5: return "DAU";
case 6: return "DHU";
case 7: return "N3U";
case 8: return "edns-client-subnet";
case 9: return "EDNS EXPIRE";
case 10: return "COOKIE";
case 11: return "edns-tcp-keepalive";
case 12: return "Padding";
case 13: return "CHAIN";
case 26946: return "DeviceID";
default: return "<unknown>";
}
}
struct opts {
char *progname;
int argv_nonoption;
int qtype;
bool init;
bool aaonly;
bool debug;
bool defnames;
bool dnsrch;
bool dnssec;
bool edns0;
bool inet6;
bool noaliases;
bool recurse;
bool use_vc;
};
static void
usage(struct opts *o, int exitval)
{
FILE *out = exitval ? stderr : stdout;
fprintf(out, "Usage: %s [-<features>] [-t QTYPE] <domain> [<domain> ...]\n", o->progname);
fprintf(out, " Options lower-case to turn ON, upper-case to turn OFF.\n");
fprintf(out, " a: aaonly d: debug i: init l: noaliases n: defnames\n");
fprintf(out, " r: recurse s: dnsrc v: use-vc k: dnssec\n");
fprintf(out, " 6: INET6 on, ^: off; 0: EDNS0 on, ): off\n");
exit(exitval);
}
static void
parse_options(struct opts *o, int argc, char *argv[])
{
o->argv_nonoption = 0;
o->qtype = T_A;
o->debug = o->aaonly = o->use_vc = o->recurse = o->defnames = o->dnsrch = o-> noaliases = o->inet6 = o->edns0 = false;
o->init = true;
o->progname = strrchr(argv[0], '/');
o->progname = o->progname ? o->progname+1 : argv[0];
int ch;
while ((ch = getopt(argc, argv, ":aAdDhiIkKlLnNrRsSt:vV6^0)")) != -1) {
switch (tolower(ch)) {
case 'h': usage(o, 0); break;
case '?': fprintf(stderr, "%s: unknown option -%c; try -h\n", o->progname, ch); exit(EX_USAGE); break;
case ':': fprintf(stderr, "%s: missing value for option -%c\n", o->progname, ch); exit(EX_USAGE); break;
#define Handle(Letter, Name) case Letter: o->Name = islower(ch) ? true : false; break
Handle('a', aaonly);
Handle('d', debug);
Handle('i', init);
Handle('k', dnssec);
Handle('l', noaliases);
Handle('n', defnames);
Handle('r', recurse);
Handle('s', dnsrch);
Handle('v', use_vc);
#undef Handle
case 't':
o->qtype = dns_type_from_string(optarg); break;
if (o->qtype < 0) {
fprintf(stderr, "bad DNS query type '%s'\n", optarg);
exit(1);
}
case '6':
case '^':
o->inet6 = ch == '6' ? true : false; break;
case '0':
case ')':
o->edns0 = ch == '0' ? true : false; break;
}
}
o->argv_nonoption = optind;
}
static void
initialize_dns(struct opts *o)
{
if (o->init) res_init();
#define SetOnOff(Name, Macro) do { if (o->Name) { _res.options |= (Macro); } else { _res.options &= ~(Macro); } } while (0)
SetOnOff(aaonly, RES_AAONLY);
SetOnOff(debug, RES_DEBUG);
SetOnOff(defnames, RES_DEFNAMES);
SetOnOff(dnsrch, RES_DNSRCH);
SetOnOff(dnssec, RES_USE_DNSSEC);
SetOnOff(edns0, RES_USE_EDNS0);
SetOnOff(inet6, RES_USE_INET6);
SetOnOff(noaliases, RES_NOALIASES);
SetOnOff(recurse, RES_RECURSE);
SetOnOff(use_vc, RES_USEVC);
#undef SetOnOff
}
struct accumulated_flags {
uint16_t id; // host order
uint8_t opcode_4 :4;
uint8_t rcode_4 :4;
bool aa, tc, rd, ra, ad, cd;
bool do_;
uint16_t rcode;
uint8_t edns0_version;
uint16_t edns0_flags;
};
static void
render_accumulated_flags(struct accumulated_flags *f)
{
printf("\n; id=%hu edns_version=%hhu opcode=%hhu\n", f->id, f->edns0_version, f->opcode_4);
printf("; RCODE: %hu\t%s\n", f->rcode, rcode_to_string(f->rcode));
#define B(Flag) (f->Flag?'Y':'n')
printf("; aa=%c tc=%c rd=%c ra=%c ad=%c cd=%c do=%c\n", B(aa), B(tc), B(rd), B(ra), B(ad), B(cd), B(do_));
#undef B
}
// The caller should have ensured that there is a full avlen of data available at avdata onwards.
static bool
emit_edns0(struct accumulated_flags *aflags,
uint16_t udp_payload_sz, uint32_t ext_rcode_flags,
uint16_t avlen, const u_char *avdata)
{
printf("\n; EDNS0 UDP payload size = %hu extended rcode/flags = %u\n", udp_payload_sz, ext_rcode_flags);
aflags->rcode |= (ext_rcode_flags & 0xFF000000) >> 20; // upper 8 bits of 12-bit value, from top 8 of 32
aflags->edns0_version = (ext_rcode_flags & 0x00FF0000) >> 16;
aflags->edns0_flags = ext_rcode_flags & 0xFFFF;
aflags->do_ = ext_rcode_flags & 0x8000 ? true : false;
printf("; EDNS0 attribute/value octets: %hu\n", avlen);
while (avlen > 0) {
if (avlen < 4) {
fprintf(stderr, "; ERROR: not enough space for A/V header %hu octets left\n", avlen);
return false;
}
uint16_t opt_code = avdata[0] * 256 + avdata[1];
uint16_t opt_len = avdata[2] * 256 + avdata[3];
avlen -= 4;
if (opt_len > avlen) {
fprintf(stderr, "; ERROR; EDNS A/V option %hu claims %hu octets payload, only %hu left\n",
opt_code, opt_len, avlen);
return false;
}
printf("; A/V Option %hu (%s) Payload length %hu\n", opt_code, edns0_optcode_to_string(opt_code), opt_len);
avlen -= opt_len;
avdata += 4 + opt_len;
}
return true;
}
static bool
maybe_emit_rrdata(struct accumulated_flags *aflags,
uint16_t qtype,
uint16_t rdlen,
const u_char *rddata,
const u_char *packet_start,
const u_char *packet_past_end
)
{
char expanded_name[256];
int consumed;
uint16_t u16_1;
#define ExpandCompressed(Ptr) do { \
consumed = dn_expand(packet_start, packet_past_end, (Ptr), expanded_name, sizeof(expanded_name)); if (consumed < 0) { \
fprintf(stderr, "; failed to expand name\n"); return false; } } while (0)
#define FailLength(Msg, Got) do { fprintf(stderr, "; %s, saw len %hu\n", (Msg), (Got)); return false; } while (0)
#define ExtractU16(Var, Ptr) do { Var = Ptr[0] * 256 + Ptr[1]; } while (0)
switch (qtype) {
case T_A:
if (rdlen != 4) FailLength("\"A\" record must have length 4", rdlen);
printf("\t\tA\t%hhu.%hhu.%hhu.%hhu\n", rddata[0], rddata[1], rddata[2], rddata[3]);
break;
case T_AAAA:
break;
case T_NS:
case T_CNAME:
ExpandCompressed(rddata);
printf("\t\t%s\t%s\n", (qtype == T_NS ? "NS" : "CNAME"), expanded_name);
break;
case T_TXT:
case T_SPF:
break;
case T_MX:
ExtractU16(u16_1, rddata);
ExpandCompressed(rddata+2);
printf("\t\t%hu\t%s\n", u16_1, expanded_name);
break;
case T_PTR:
break;
case T_CAA:
break;
default:
// nothing, we are happy to just skip the full dump
break;
}
return true;
#undef ExpandCompressed
#undef FailLength
#undef ExtractU16
}
static bool
perform_resolution(struct opts *o, const char *name)
{
int res_len;
u_char buf[65536];
char expanded_name[256];
int buflen_i = (int) sizeof(buf);
res_len = res_search(name, C_IN, o->qtype, buf, buflen_i);
if (res_len < 0) {
fprintf(stderr, "%s: resolving for '%s' failed: [%d] %s\n", o->progname, name, h_errno, hstrerror(h_errno));
return false;
}
if (res_len < NS_HFIXEDSZ) {
fprintf(stderr, "%s: resolving for '%s' response len (%d) too short\n", o->progname, name, res_len);
return false;
}
HEADER* hdr = (HEADER*) buf;
printf(";;; id=%hu %s\taa=%d tc=%d rd=%d ra=%d ad=%d cd=%d\n",
ntohs(hdr->id), hdr->qr? "response" : "query", hdr->aa, hdr->tc, hdr->rd, hdr->ra, hdr->ad, hdr->cd);
struct accumulated_flags accum = {
.id = ntohs(hdr->id),
.aa = hdr->aa,
.tc = hdr->tc,
.rd = hdr->rd,
.ra = hdr->ra,
.ad = hdr->ad,
.cd = hdr->cd,
.opcode_4 = hdr->opcode,
.rcode_4 = hdr->rcode,
.rcode = hdr->rcode,
};
uint16_t count_questions = ntohs(hdr->qdcount);
uint16_t count_answers = ntohs(hdr->ancount);
uint16_t count_authority = ntohs(hdr->nscount);
uint16_t count_additional = ntohs(hdr->arcount);
struct {
const int next;
const uint16_t *count;
const bool full_rr;
const char *label;
} sections[5] = {
{ 1, &count_questions, false, "QUESTION" },
{ 2, &count_answers, true, "ANSWERS" },
{ 3, &count_authority, true, "AUTHORITY" },
{ -1, &count_additional, true, "ADDITIONAL" },
};
bool okay = true;
u_char *extract_from = buf + NS_HFIXEDSZ;
for (int section = 0; section >= 0; section = sections[section].next) {
int rrexpect = *sections[section].count;
printf("\n;;; %s : %d entries\n", sections[section].label, rrexpect);
while (rrexpect-- > 0) {
int consumed =
dn_expand(buf, buf + res_len, extract_from, expanded_name, sizeof(expanded_name));
#define parse_error(Label) do { okay = false; \
fprintf(stderr, ";;; %s item %d\n", (Label), *sections[section].count - rrexpect); \
goto PARSE_ABORT; } while(0)
if (consumed < 0) parse_error("error expanding");
extract_from += consumed;
if ((extract_from + 4) > buf + res_len) parse_error("no room for type/class in");
uint16_t qt = extract_from[0] * 256 + extract_from[1];
uint16_t qc = extract_from[2] * 256 + extract_from[3];
extract_from += 4;
if (sections[section].full_rr) {
if (extract_from + 6 > buf + res_len) parse_error("no room for rest of RR");
uint32_t attl = extract_from[0] * 256 * 256 * 256 +
extract_from[1] * 256 * 256 +
extract_from[2] * 256 +
extract_from[3];
uint16_t rdlen = extract_from[4] * 256 + extract_from[5];
extract_from += 6 + rdlen;
if (extract_from > buf + res_len) parse_error("no room for rrdata");
// EDNS0 is Option 41 and not in headers as a const (non-portably? as enum)
if (qt == 41) {
// expanded_name should be empty, type is 41, the rest is passed along
if (!emit_edns0(&accum, qc, attl, rdlen, extract_from-rdlen)) {
okay = false;
goto PARSE_ABORT;
}
} else { // regular
printf("%s\ttype=%hu class=%hu ttl=%u rrlen=%hu%s\n",
expanded_name, qt, qc, attl, rdlen,
// Only stuff we flag without decoding here; decoding into maybe_emit_rrdata
(qt == T_RRSIG ? " [RRSIG]" : "")
);
if (!maybe_emit_rrdata(&accum, qt, rdlen, extract_from-rdlen, buf, buf+res_len)) {
okay = false;
goto PARSE_ABORT;
}
}
} else {
printf("%s\ttype=%hu class=%hu\n", expanded_name, qt, qc);
}
if (extract_from > buf + res_len) parse_error("gone past end of packet in");
}
}
render_accumulated_flags(&accum);
#undef parse_error
PARSE_ABORT:
return okay;
}
int
main(int argc, char *argv[])
{
struct opts p_o;
parse_options(&p_o, argc, argv);
if (p_o.argv_nonoption < 1 || p_o.argv_nonoption == argc) {
usage(&p_o, EX_USAGE);
}
initialize_dns(&p_o);
int exval = 0;
for (int i = p_o.argv_nonoption; i < argc; i++) {
if (!perform_resolution(&p_o, argv[i])) {
exval = 1;
}
}
return exval;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment