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;
+}