wm: dnsparser

Download patch

ref: f3b27e844fd44767de671fe634690b67e104667b
author: mkf <mkf@cloud9p.org>
date: Wed Jul 31 03:10:46 EDT 2024

dnsparser: import

--- /dev/null
+++ b/dnsparser.c
@@ -1,0 +1,658 @@
+/*
+ * program to parse dns packets,
+ * it assumes packets are not sent over qinq or vxlan or jumbo frames
+ * or avian carriers
+ */
+
+#include <stdio.h>
+#include <pcap.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <string.h>
+
+#define debug 0
+
+/* /etc/protocols */
+enum
+{
+	/* sizes */
+	ETHER_LEN = 14,
+	ETHER_ADDR_LEN = 6,
+	
+	UDP_SIZE = 8,
+
+	/* relative position of each field in packet */
+	ETHER_DST = 0,
+	ETHER_SRC = 6,
+	ETHER_TYPE = 12,
+
+	PKT_VHL = 0,
+	PKT_LEN = 2,
+	PKT_ID = 4,
+	PKT_TTL = 8,
+	PKT_PROTO = 9,
+	PKT_SUM = 10,
+	PKT_SRC = 12,
+	PKT_DST = 14,
+
+	/* udp stuff */
+	UDP_SRCPORT = 0,
+	UDP_DSTPORT = 2,
+	UDP_LEN = 4,
+	UDP_SUM = 6,
+	
+	/* tcp stuff */
+	TCP_SRCPORT = 0,
+	TCP_DSTPORT = 2,
+	TCP_SEQNUM = 4,
+	TCP_ACKNUM = 8,
+	TCP_OFFSET = 12, /* half a byte is used */
+	TCP_FLAGS = 13,
+	TCP_WINSIZE = 14,
+	TCP_SUM = 16,
+	TCP_URGPTR = 18,
+	TCP_OPTS = 20,
+
+	/* tcp flags @ 13 */
+	TCP_FLAG_ACK = 0x010,
+	TCP_FLAG_PSH = 0x008,
+	TCP_FLAG_SYN = 0x002,
+	TCP_FLAG_FIN = 0x001,
+	
+	/* dns fields */
+	DNS_ID = 0,
+	DNS_FLAGS = 2,
+	DNS_COUNT_QUERIES = 4,
+	DNS_COUNT_ANSWERS = 6,
+	DNS_COUNT_AUTH_RR = 8,
+	DNS_COUNT_ADD_RR = 10,
+	DNS_QUERY = 12,
+
+	/* dns flags bits @ 2 */
+	DNS_ISRESP = 0x8000,
+	DNS_OPCODE = 0x7800,
+	
+	DNS_FLAGS_OPCODE_QUERY = 0x4000,
+	DNS_FLAGS_OPCODE_IQUERY = 0x2000,
+	DNS_FLAGS_OPCODE_STATUS = 0x1000,	/* 2 bits */
+	DNS_FLAGS_OPCODE_NOTIFY = 0x0800,
+	
+	DNS_AUTH = 0x0400,
+	DNS_TRUNCATED = 0x0200,
+	DNS_RD = 0x0100,
+	DNS_RA = 0x0080,
+	DNS_Z = 0x0040,
+	DNS_AD = 0x0020,
+	DNS_CD = 0x0010,
+	DNS_RCODE = 0x000F,
+	
+	/* ether types */
+	IP4 = 0x800,
+	IP6 = 0x86dd,
+
+	/* packet types */
+	TCP = 6,
+	UDP = 17,
+
+	/* dns types */
+	A = 1,
+	CNAME = 5,
+	MX = 15,
+	TXT = 16,
+	AAAA = 28,
+	
+	/* dns classes */
+	IN = 1,
+};
+
+typedef struct
+{
+	uint8_t dst[ETHER_ADDR_LEN];
+	uint8_t src[ETHER_ADDR_LEN];
+	int type;
+}Ether;
+
+typedef struct
+{
+	uint8_t version;
+
+	uint8_t headerlen;
+	uint8_t len;
+
+	uint8_t ttl;
+	uint8_t proto; /* UDP, TCP */
+	uint16_t sum; /* checksum of packet */
+
+	uint32_t srcip;
+	uint32_t dstip;
+}Packet;
+
+#define IP4_HL(octet)	(octet & 0x0f)
+#define IP4_V(octet)	(octet >> 4) 
+
+typedef struct
+{
+	uint16_t srcport;
+	uint16_t dstport;
+
+	uint16_t len;
+	uint16_t sum;
+}UdpPkt;
+
+typedef struct
+{
+	uint16_t srcport;
+	uint16_t dstport;
+
+	uint32_t seqnum;
+	uint32_t acknum;
+	
+	uint16_t offset;
+	uint16_t flags;
+	
+	uint16_t winsize;
+	uint8_t sum;
+	
+	uint16_t urgentptr;
+	/* we don't care about options */
+}TcpPkt;
+
+typedef struct
+{
+	/* is it response or request */
+	uint16_t id;
+	uint16_t flags;
+	
+	/* count(s) */
+	uint16_t nQueries;
+	uint16_t nAnswers;
+	uint16_t nAuthRR;
+	uint16_t nAddRR;
+	
+	char domain[255];
+	
+	uint8_t type;
+	uint8_t class;
+	uint32_t ttl;
+	uint32_t len;
+	union
+	{
+		/* A */
+		uint8_t ip[4];
+
+		/* AAAA */
+		uint16_t ip6[8];
+
+		/* CNAME */
+		u_char cname[255];
+	};
+}DnsQuery;
+
+pcap_t *handle;
+struct bpf_program dnsfilter;
+
+char errbuf[PCAP_ERRBUF_SIZE];	/* Error string */
+
+/* aux */
+void
+err(int fatal, char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+	
+	fprintf(stderr, fatal ? "FATAL" : "WARN" ": ");
+	vfprintf(stderr, fmt, args);
+	fprintf(stderr, "\n");
+	
+	va_end(args);
+	fflush(stdout);
+	fclose(stdout);
+	
+	if(fatal)
+		abort();
+}
+
+static uint16_t
+get2(const u_char *octet)
+{
+	return octet[0] << 8 | octet[1];
+}
+
+static uint32_t
+get4(const u_char *octet)
+{
+	return get2(octet) << 8 * 2 | get2(octet + 2);
+}
+
+char*
+etherTypeToStr(uint16_t frame)
+{
+	switch(frame)
+	{
+		case IP4:
+			return "IPv4";
+		case IP6:
+			return "IPv6";
+		default:
+			err(1, "etherTypeToStr(%d): unkown type", frame);
+			return "unkown";
+	}
+}
+
+char*
+pktTypeToStr(const u_char pkt)
+{
+	switch(pkt)
+	{
+		case IPPROTO_UDP:
+			return "UDP";
+		case IPPROTO_TCP:
+			return "TCP";
+		default:
+			err(1, "pktTypeToStr(%d): unkown type", pkt);
+			return NULL;
+	}
+}
+
+char*
+dnsTypeToStr(uint16_t type)
+{
+	switch(type)
+	{
+		case A:
+			return "A";
+		case CNAME:
+			return "CNAME";
+		case TXT:
+			return "TXT";
+		case AAAA:
+			return "AAAA";
+		case MX:
+			return "MX";
+		default:
+			err(1, "dnsTypeToStr(%d): unkown type", type);
+			return NULL;
+	}
+}
+
+char*
+dnsClassToStr(uint16_t class)
+{
+	switch(class)
+	{
+		case IN:
+			return "IN";
+		default:
+			err(1, "dnsClassToStr(%d): unkown class", class);
+			return NULL;
+	}
+}
+
+void
+parseEther(const u_char *pkt, Ether *e)
+{
+	for(int i = 0 ; i < ETHER_ADDR_LEN ; i++)
+		e->dst[i] = pkt[i];
+
+	for(int i = 0 ; i < ETHER_ADDR_LEN ; i++)
+		e->src[i] = pkt[i+ETHER_ADDR_LEN];
+
+	e->type = get2(pkt + ETHER_TYPE);
+}
+
+void
+parsePacket(const u_char *pkt, Packet *p)
+{
+	p->version = IP4_V(pkt[0]);
+	p->headerlen = IP4_HL(pkt[0]) * 4;
+
+	p->len = get2(pkt + PKT_LEN);
+	p->ttl = pkt[PKT_TTL];
+	p->proto = pkt[PKT_PROTO];
+	p->sum = get2(pkt + PKT_SUM);
+
+	p->srcip = get4(pkt + PKT_SRC);
+	p->dstip = get4(pkt + PKT_DST);
+}
+
+void
+parseUdp(const u_char *pkt, UdpPkt *udp)
+{
+	udp->srcport = get2(pkt + UDP_SRCPORT);
+	udp->dstport = get2(pkt + UDP_DSTPORT);
+
+	udp->len = get2(pkt + UDP_LEN);
+	udp->sum = get2(pkt + UDP_SUM);
+}
+
+void
+parseTcp(const u_char *pkt, TcpPkt *tcp)
+{
+	tcp->srcport = get2(pkt + TCP_SRCPORT);
+	tcp->dstport = get2(pkt + TCP_DSTPORT);
+
+	tcp->seqnum = get4(pkt + TCP_SEQNUM);
+	tcp->acknum = get4(pkt + TCP_ACKNUM);
+	
+	/* offsets are multiplies of 4 (32-bit) values */
+	tcp->offset = (pkt[TCP_OFFSET] >> 4) * 4;
+	tcp->flags = pkt[TCP_FLAGS];
+	
+	tcp->winsize = get2(pkt + TCP_WINSIZE);
+	tcp->sum = pkt[TCP_SUM];
+	
+	tcp->urgentptr = get2(pkt + TCP_URGPTR);
+}
+
+/* 
+ * pos must be after data length, before size of first string
+ * len must be dq.len
+ */
+int
+parseDnsCname(const u_char *pkt, u_char cname[255], uint pos, uint len)
+{
+	int section_header, cname_head = 0;
+	const u_char *pkt_head;
+	
+	pkt_head = pkt + DNS_QUERY + pos;
+	for(int i = 0 ; i < len ; i++)
+	{
+		section_header = pkt_head[i];
+		
+		/* compressed, we'r too lazy to handle it */
+		if(section_header == 0xc0 || section_header == 0x0)
+			break;
+		
+		/* not compressed, regular size */
+		strncpy(cname + cname_head, pkt_head + i + 1, section_header);
+		
+		i += section_header;
+		cname_head += section_header;
+		cname[cname_head++] = '.';
+	}
+	return 1;
+}
+
+/*
+ * dns have some variable length fields,
+ * we need to keep our positon somewhere
+ */
+int
+parseDnsQuery(const u_char *pkt, DnsQuery *dq)
+{
+	int section_header;
+	int head = 0;
+	int name_head = 0;
+	
+	dq->id = get2(pkt + DNS_ID);
+	dq->flags = get2(pkt + DNS_FLAGS);
+	dq->nQueries = get2(pkt + DNS_COUNT_QUERIES);
+	dq->nAnswers = get2(pkt + DNS_COUNT_ANSWERS);
+	dq->nAuthRR = get2(pkt + DNS_COUNT_AUTH_RR);
+	dq->nAddRR = get2(pkt + DNS_COUNT_ADD_RR);
+	
+	head += DNS_QUERY;
+	memset(dq->domain, '\0', sizeof(dq->domain));
+	
+	for(int i = 0 ; i < sizeof(dq->domain) ; i++)
+	{
+		section_header = pkt[head];
+		head++;
+		
+		if(section_header == 0x0)
+			break;
+		
+		strncpy(dq->domain + name_head, pkt + head, section_header);
+		name_head += section_header;
+		dq->domain[name_head++] = '.';
+		head += section_header;
+	}
+
+	dq->type = get2(pkt + head);
+	head += 2;
+	dq->class = get2(pkt + head);
+	head += 2;
+		
+	if(dq->flags & DNS_ISRESP)
+	{
+		/* a resposnse, should have answers */
+		head += 2; /* skip domain name */
+		head += 2; /* skip type */
+		head += 2; /* skip class */
+		
+		/* we are in next packet */
+		dq->ttl = get4(pkt + head);
+		head += 4;
+		
+		dq->len = get2(pkt + head);
+		head += 2;
+	}
+	else
+		return 1;
+
+	switch(dq->type)
+	{
+		case A:
+			dq->ip[0] = pkt[head++];
+			dq->ip[1] = pkt[head++];
+			dq->ip[2] = pkt[head++];
+			dq->ip[3] = pkt[head++];
+			break;
+		case AAAA:
+			dq->ip6[0] = get2(pkt + head);
+			head += 2;		
+			dq->ip6[1] = get2(pkt + head);
+			head += 2;
+			dq->ip6[2] = get2(pkt + head);
+			head += 2;
+			dq->ip6[3] = get2(pkt + head);
+			head += 2;
+			dq->ip6[4] = get2(pkt + head);
+			head += 2;		
+			dq->ip6[5] = get2(pkt + head);
+			head += 2;
+			dq->ip6[6] = get2(pkt + head);
+			head += 2;
+			dq->ip6[7] = get2(pkt + head);
+			head += 2;			
+			break;
+		case CNAME:
+			memset(dq->cname, '\0', sizeof(dq->domain));
+			parseDnsCname(pkt, dq->cname, head, dq->len);
+			break;
+		case MX:
+			break;
+		default:
+			err(1, "parseDnsQuery(%d) unknown type", dq->type);
+	}
+	return 1;
+}
+
+void
+printEther(Ether e)
+{
+	printf("ether info:\n\tdst:");
+	for(int i = 0 ; i < ETHER_ADDR_LEN ; i++)
+		printf("%x:", e.dst[i]);
+
+	printf("\tsrc: ");
+	for(int i = 0 ; i < ETHER_ADDR_LEN ; i++)
+		printf("%x:", e.src[i]);
+
+	printf("\ttype: 0x%x (%s)\n", e.type, etherTypeToStr(e.type));
+}
+
+void
+printPacket(Packet p)
+{
+	printf("ip packet info:\n"
+	"\tversion: 0x%x\theaderlen: 0x%x\tlen: 0x%x\n",
+	p.version, p.headerlen, p.len);
+
+	printf("\tttl: 0%x (%d)\tproto 0%x (%s)\tsum: 0x%x\n",
+	p.ttl, p.ttl, p.proto, pktTypeToStr(p.proto), p.sum);
+
+	printf("\tsrcip: 0x%x\tdstip: 0x%x\n", p.srcip, p.dstip);
+}
+
+void
+printUdpPkt(UdpPkt udp)
+{
+		printf("udp pkt:\n"
+		"\tsrcport: %d\tdstport: %d\n"
+		"\tlen: %d\tsum 0x%x\n",
+		udp.srcport, udp.dstport, udp.len, udp.sum);
+}
+
+void
+printTcpPkt(TcpPkt tcp)
+{
+		printf("tcp pkt:\n"
+		"\tsrcport: %d\tdstport: %d\n"
+		"\tseqnum: %d\tacknum: %d\n"
+		"\toffset: %d\tflags: %b (%x)\n"
+		"\twinsize: %d (%x)\tsum: %d\n",
+		
+		tcp.srcport, tcp.dstport,
+		tcp.seqnum, tcp.acknum,
+		tcp.offset, tcp.flags, tcp.flags,
+		tcp.winsize, tcp.winsize, tcp.sum);
+}
+
+void
+printDnsQuery(DnsQuery dq)
+{
+	printf("dns query:\n"
+	"\tid: %d\tflags: %b (%x)\n"
+	"\tnQueries: %d\tnAnswers: %d\tnAuthRR: %d\tnAddRR: %d\n"
+	"\tdomain: %s (%p)\n"
+	"\ttype: %d (%s)\tclass: %s\n"
+	"\tttl: %x (%d)\tlen: %x (%d)\n",
+	
+	dq.id, dq.flags, dq.flags,
+	dq.nQueries, dq.nAnswers, dq.nAuthRR, dq.nAddRR,
+	dq.domain, dq.domain,
+	dq.type, dnsTypeToStr(dq.type), dnsClassToStr(dq.class),
+	dq.ttl, dq.ttl, dq.len, dq.len);
+	
+	if(dq.flags & DNS_ISRESP)
+	{
+		switch(dq.type)
+		{
+		case A:
+			printf("\tip: %d.%d.%d.%d\n", dq.ip[0], dq.ip[1], dq.ip[2], dq.ip[3]);
+			break;
+		case AAAA:
+			printf("\tip6: %x:%x:%x:%x:%x:%x:%x:%x\n", dq.ip6[0], dq.ip6[1], dq.ip6[2], dq.ip6[3], dq.ip6[4], dq.ip6[5], dq.ip6[6], dq.ip6[7]);
+			break;
+		case CNAME:
+			printf("\tcname: %s\n", dq.cname);
+			break;
+		case MX:
+			break;
+		default:
+			err(1, "parseDnsQuery: unknown type: %d");
+		}
+	}
+}
+
+/* magic happens here! */
+void
+dns_reader(u_char *args, const struct pcap_pkthdr *header, const u_char *pkt)
+{
+	static int packets = 1;
+	int head = 0;
+	Ether e;
+	Packet p;
+	UdpPkt udp;
+	TcpPkt tcp;
+	DnsQuery dq;
+
+	printf("\n==================%3d  ==================\n", packets++);
+	
+	
+	parseEther(pkt + head, &e);
+	head += ETHER_LEN;
+	
+	parsePacket(pkt + head, &p);
+	head += p.headerlen;
+
+	if(p.proto == IPPROTO_UDP)
+	{
+		parseUdp(pkt + head, &udp);
+		head += UDP_SIZE;
+	}
+	else if(p.proto == IPPROTO_TCP)
+	{
+		parseTcp(pkt + head, &tcp);
+
+		/* reduce the noise */
+		if(tcp.flags & (TCP_FLAG_FIN|TCP_FLAG_SYN|TCP_FLAG_ACK) &&
+		!(tcp.flags & TCP_FLAG_PSH))
+		{
+			printf("skipped noise\n");
+			return;
+		}
+
+		head += tcp.offset;
+		head += 2; /* there is a dns-over-tcp len field we ignore */
+	}
+	else
+		err(1, "unknown proto: %d", p.proto);
+	
+	if(debug)
+	{
+		fprintf(stderr, "DEBUG: ");
+		for(int i = 0 ; i < header->caplen ; i++)
+			fprintf(stderr, "%d: 0x%x\t", i, pkt[i]);
+		
+		fprintf(stderr, "\n");
+	}
+
+	parseDnsQuery(pkt + head, &dq);
+
+	printf("frame info:\n"
+	"\tlen: %d\tcaplen: %d\n"
+	"\ttimeval.tv_sec: %ld\ttimeval.tv_usec: %ld\n",
+	header->len, header->caplen,
+	header->ts.tv_sec, header->ts.tv_usec);
+	
+	printEther(e);
+	printPacket(p);
+	
+	if(p.proto == IPPROTO_UDP)
+		printUdpPkt(udp);
+	else if(p.proto == IPPROTO_TCP)
+		printTcpPkt(tcp);
+	
+	printDnsQuery(dq);
+}
+
+int
+main(int argc, char **argv)
+{
+	if(argc != 2)
+		err(1, "not enough arguments");
+
+	pcap_init(PCAP_CHAR_ENC_LOCAL, errbuf);
+	handle = pcap_open_offline(argv[1], errbuf);
+
+	if(handle == NULL)
+		err(1, "%s: unable to open: %s", argv[0], argv[1]);
+
+	if (pcap_compile(handle, &dnsfilter, "src port 53", 0, PCAP_NETMASK_UNKNOWN) == -1)
+		err(1, "%s: failed to compile the expr: %s\n", argv[0], pcap_geterr(handle));
+
+	if (pcap_setfilter(handle, &dnsfilter) == -1)
+		err(1, "%s: failed to install filter: %s\n", argv[0], pcap_geterr(handle));
+
+
+	pcap_loop(handle, -1, dns_reader, NULL);
+
+	pcap_freecode(&dnsfilter);
+	pcap_close(handle);
+
+	return 0;
+}