ref: bd7e601f244a8da12def26966afec3d5c0c338af
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; }