ref: be9d2d0b1ffac9b27c7ab326c5912aa8e25d927e
dir: /dnsparser.c/
/*
* 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;
}