wm: dnsparser

ref: f3b27e844fd44767de671fe634690b67e104667b
dir: /dnsparser.c/

View raw version
/*
 * 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;
}