From 8d87f1133ab2a3f9e599594cdf339194ae7f1e6b Mon Sep 17 00:00:00 2001 From: Victor Roemer Date: Tue, 6 Aug 2024 07:36:02 -0500 Subject: [PATCH 1/9] packet/dns: Initial study of DNS packets This commit introduces an initial study for the DNS protocol. These changes are still a work in progress. The DNS protocol is very information dense, and all the information I'm interested in is easily abusable. include/packet/dns.h, src/dns.cc: * Add data types to facilitate DNS parsing * Added decode_dns() function Currently only capable of parsing queries and answers. src/udp.c: * Initial definition of a bind_udp() to passthrough to decode_dns(). NOTE: This is disabled for now. --- CMakeLists.txt | 4 +- include/packet/dns.h | 51 ++++++++++++++ src/CMakeLists.txt | 2 + src/dns.cc | 161 +++++++++++++++++++++++++++++++++++++++++++ src/udp.c | 11 +++ 5 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 include/packet/dns.h create mode 100644 src/dns.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index b154d04..1f41559 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 3.0) -project(libpacket C) +cmake_minimum_required(VERSION 3.20) +project(libpacket C CXX) add_subdirectory(src) # Install library headers diff --git a/include/packet/dns.h b/include/packet/dns.h new file mode 100644 index 0000000..12cd901 --- /dev/null +++ b/include/packet/dns.h @@ -0,0 +1,51 @@ +#ifndef LIBPACKET_DECODE_DNS_H +#define LIBPACKET_DECODE_DNS_H + +#include +struct dns_stats { + uint64_t dns_tooshort; + uint64_t dns_too_many_queries; // > 256 DNS Questions + uint64_t dns_too_many_answers; // > 256 DNS Answers +}; + +extern struct dns_stats s_dns_stats; + +// DNS Header +struct dns_header { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} __attribute__((packed)); + +struct dns_label { + uint8_t *p; + uint8_t len; +}; + +struct dns_query { + //struct dns_label labels[32]; + // Store QTYPE and QCLASS (assuming a structure in Packet to store this information) + uint16_t dns_qtype; + uint16_t dns_qclass; +}; + +struct dns_answer { + uint16_t dns_atype; + uint16_t dns_aclass; + uint16_t dns_ttl; + uint16_t dns_rdlength; + uint16_t dns_rdata; +}; + +struct dns { + struct dns_header h; + struct dns_query questions[256]; + struct dns_answer answers[256]; +}; + +int decode_dns(uint8_t const *pkt, uint32_t const len, dns* dns); + +#endif /* LIBPACKET_DECODE_DNS_H */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1aba50b..5233065 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,5 @@ set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) add_library( packet @@ -51,6 +52,7 @@ target_sources( icmp6.h vlan.c vlan.h + dns.cc ) target_include_directories( diff --git a/src/dns.cc b/src/dns.cc new file mode 100644 index 0000000..eb15d82 --- /dev/null +++ b/src/dns.cc @@ -0,0 +1,161 @@ +// +// libpacket/src/dns.c: DNS protocol decoder +// Victor Roemer (wtfbbqhax), . +// +#include +#include +#include +#include +#include // dns_print_data + +#include + +#include "packet_private.h" +#include "packet/dns.h" + +struct dns_stats s_dns_stats; + +uint32_t constexpr MINIMUM_DNS_HEADER_SIZE = (sizeof(dns_header)); + +// Function to decode the DNS protocol +int +decode_dns(uint8_t const * pkt, uint32_t const len, dns* dns) +{ + if (len < MINIMUM_DNS_HEADER_SIZE) { + s_dns_stats.dns_tooshort++; + return -1; + } + + struct dns_header const* raw = + (struct dns_header const*)pkt; + + dns->h.id = ntohs(raw->id); + dns->h.flags = ntohs(raw->flags); + dns->h.qdcount = ntohs(raw->qdcount); + dns->h.ancount = ntohs(raw->ancount); + dns->h.nscount = ntohs(raw->nscount); + dns->h.arcount = ntohs(raw->arcount); + + uint8_t const * ptr = pkt + MINIMUM_DNS_HEADER_SIZE; + uint32_t remaining_len = len - MINIMUM_DNS_HEADER_SIZE; + + if (dns->h.qdcount >= 256) + { + s_dns_stats.dns_too_many_queries++; + return 0; + } + + // Parsing Question Section + for (int i = 0; i < dns->h.qdcount; i++) + { + struct dns_query *q = &dns->questions[i]; + + // Parse QNAME + while (remaining_len > 0 && *ptr != 0) + { + uint8_t label_len = *ptr; + ptr++; + remaining_len--; + + if (remaining_len < label_len) + { + s_dns_stats.dns_tooshort++; + return -1; + } + + ptr += label_len; + remaining_len -= label_len; + } + + // Null byte at the end of QNAME + if (remaining_len == 0) + { + s_dns_stats.dns_tooshort++; + return -1; + } + ptr++; + remaining_len--; + + // Parse QTYPE and QCLASS + if (remaining_len < 4) { + s_dns_stats.dns_tooshort++; + return -1; + } + uint16_t qtype = ntohs(*(uint16_t *)ptr); + ptr += 2; + remaining_len -= 2; + uint16_t qclass = ntohs(*(uint16_t *)ptr); + ptr += 2; + remaining_len -= 2; + + // Store QTYPE and QCLASS (assuming a structure in Packet to store this information) + q->dns_qtype = qtype; + q->dns_qclass = qclass; + } + + if (dns->h.ancount >= 256) { + s_dns_stats.dns_too_many_answers++; + return 0; + } + + for (int i = 0; i < dns->h.ancount; i++) + { + struct dns_answer *a = &dns->answers[i]; + + // Parse NAME (pointer or label) + if (remaining_len < 2) { + s_dns_stats.dns_tooshort++; + return -1; + } + // FIXME: Store this value? + uint16_t name = ntohs(*(uint16_t *)ptr); + ptr += 2; + remaining_len -= 2; + + // Parse TYPE, CLASS, TTL, RDLENGTH + if (remaining_len < 10) { + s_dns_stats.dns_tooshort++; + return -1; + } + + uint16_t atype = ntohs(*(uint16_t *)ptr); + assert(sizeof atype == 2); + ptr += sizeof atype; + remaining_len -= sizeof atype; + + uint16_t aclass = ntohs(*(uint16_t *)ptr); + assert(sizeof aclass == 2); + ptr += sizeof aclass; + remaining_len -= sizeof aclass; + + uint32_t ttl = ntohl(*(uint32_t *)ptr); + assert(sizeof ttl == 4); + ptr += sizeof ttl; + remaining_len -= sizeof ttl; + + uint16_t rdlength = ntohs(*(uint16_t *)ptr); + assert(sizeof atype == 2); + ptr += sizeof rdlength; + remaining_len -= sizeof rdlength; + + // Parse RDATA + if (remaining_len < rdlength) { + s_dns_stats.dns_tooshort++; + return -1; + } + + const uint8_t *rdata = ptr; + ptr += rdlength; + remaining_len -= rdlength; + + // Store answer information (assuming a structure in Packet to store this information) + a->dns_atype = atype; + a->dns_aclass = aclass; + a->dns_ttl = ttl; + a->dns_rdlength = rdlength; + a->dns_rdata = *((uint16_t*)rdata); + } + + return 0; +} + diff --git a/src/udp.c b/src/udp.c index 1c6cc60..227a667 100644 --- a/src/udp.c +++ b/src/udp.c @@ -43,6 +43,17 @@ extern struct packet_stats s_stats; +/* +static inline int +bind_udp(const uint8_t * pkt, const uint32_t len, Packet *p) +{ + int ret = -1; + if ((p->srcport == 53) || (p->dstport == 53)) + return decode_dns(pkt, len, p); + return 0; +} +*/ + int decode_udp(const uint8_t *pkt, const uint32_t len, Packet *p) { From 0c6b0b249ae8a2af85d2fdbc49c087a95620be5c Mon Sep 17 00:00:00 2001 From: Victor Roemer Date: Tue, 6 Aug 2024 07:36:12 -0500 Subject: [PATCH 2/9] Containerfile: Add --- Containerfile | 23 +++++++++++++++++++++++ Makefile | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 Containerfile diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..46cf0c9 --- /dev/null +++ b/Containerfile @@ -0,0 +1,23 @@ +# Using www.github.com/wtfbbqhax/krakatoa +FROM arm64v8/krakatoa AS libpacket_dev_env + +USER root +RUN apk update + +VOLUME /volume/libpacket +WORKDIR /volume/libpacket + +RUN apk add \ + libdaq-dev@local \ + libdaq-pcap-module@local \ + libdaq-dump-module@local + +RUN apk add \ + build-base \ + cmake \ + ninja \ + gtest-dev + +RUN echo alias vi=nvim > /root/.profile + +RUN apk add neovim tmux ctags diff --git a/Makefile b/Makefile index b34cdf1..50ec0c9 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,51 @@ -.PHONY: build clean install test uninstall +.DEFAULT_GOAL := build +# Makefile rules to build the libpacket source code +.PHONY: build build: cmake -B build -G Ninja . \ -D CMAKE_BUILD_TYPE:STRING=Debug \ -D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE cmake --build build -clean: - rm -rf build/ - +.PHONY: install install: build cmake --install build +.PHONY: clean +clean: + rm -rf build/ + +.PHONY: test test: install make -C tests/ +.PHONY: uninstall uninstall: rm -f /usr/local/include/packet.h rm -rf /usr/local/include/packet/ rm -f /usr/local/lib/libpacket.so.* rm -f /usr/local/lib/libpacket.so + +# Makefile rules to build the libpacket development environment. +# +# If you have a working Docker environment, you can use and contribute to this +# code base. +IMAGE_NAME=wtfbbqhax/libpacket + +.PHONY: container +container: + docker build . -f Containerfile -t $(IMAGE_NAME) + +.PHONY: start +start: + docker run --rm -td -v "$(PWD)":/volume/libpacket "$(IMAGE_NAME)" + +.PHONY: kill +kill: + docker kill $(shell docker ps --filter "ancestor=$(IMAGE_NAME)" --format "{{.ID}}") + +.PHONY: attach +attach: + docker exec -ti $(shell docker ps --filter "ancestor=$(IMAGE_NAME)" --format "{{.ID}}" | head -n1) sh + From dfe5a07ddee0bde182a2066b4493f6251d96e8b3 Mon Sep 17 00:00:00 2001 From: Victor Roemer Date: Thu, 8 Aug 2024 19:15:51 -0500 Subject: [PATCH 3/9] dns: update --- include/packet/dns.h | 34 ++++++++++++++++++++++-------- src/dns.cc | 49 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/include/packet/dns.h b/include/packet/dns.h index 12cd901..83efc12 100644 --- a/include/packet/dns.h +++ b/include/packet/dns.h @@ -1,7 +1,12 @@ #ifndef LIBPACKET_DECODE_DNS_H #define LIBPACKET_DECODE_DNS_H +// See RFC 1035, +// https://datatracker.ietf.org/doc/html/rfc1035 + +#include #include + struct dns_stats { uint64_t dns_tooshort; uint64_t dns_too_many_queries; // > 256 DNS Questions @@ -20,13 +25,8 @@ struct dns_header { uint16_t arcount; } __attribute__((packed)); -struct dns_label { - uint8_t *p; - uint8_t len; -}; - struct dns_query { - //struct dns_label labels[32]; + std::string label; // Store QTYPE and QCLASS (assuming a structure in Packet to store this information) uint16_t dns_qtype; uint16_t dns_qclass; @@ -36,8 +36,7 @@ struct dns_answer { uint16_t dns_atype; uint16_t dns_aclass; uint16_t dns_ttl; - uint16_t dns_rdlength; - uint16_t dns_rdata; + std::string data; }; struct dns { @@ -46,6 +45,25 @@ struct dns { struct dns_answer answers[256]; }; +enum dns_types { + TYPE_A, // 1 + TYPE_NS, // 2 + TYPE_MD, // 3 + TYPE_MF, // 4 + TYPE_CNAME, // 5 + TYPE_SOA, // 6 + TYPE_MB, // 7 + TYPE_MG, // 8 + TYPE_MR, // 9 + TYPE_NULL, // 10 + TYPE_WKS, // 11 + TYPE_PTR, // 12 + TYPE_HINFO, // 13 + TYPE_MINFO, // 14 + TYPE_MX, // 15 + TYPE_TXT // 16 +}; + int decode_dns(uint8_t const *pkt, uint32_t const len, dns* dns); #endif /* LIBPACKET_DECODE_DNS_H */ diff --git a/src/dns.cc b/src/dns.cc index eb15d82..82d538d 100644 --- a/src/dns.cc +++ b/src/dns.cc @@ -1,5 +1,5 @@ -// -// libpacket/src/dns.c: DNS protocol decoder +// +// libpacket/src/dns.c: DNS protocol decoder // Victor Roemer (wtfbbqhax), . // #include @@ -10,6 +10,9 @@ #include +#include +#include + #include "packet_private.h" #include "packet/dns.h" @@ -27,7 +30,7 @@ decode_dns(uint8_t const * pkt, uint32_t const len, dns* dns) } struct dns_header const* raw = - (struct dns_header const*)pkt; + (struct dns_header const*)pkt; dns->h.id = ntohs(raw->id); dns->h.flags = ntohs(raw->flags); @@ -48,8 +51,8 @@ decode_dns(uint8_t const * pkt, uint32_t const len, dns* dns) // Parsing Question Section for (int i = 0; i < dns->h.qdcount; i++) { - struct dns_query *q = &dns->questions[i]; - + struct dns_query* q = &dns->questions[i]; + // Parse QNAME while (remaining_len > 0 && *ptr != 0) { @@ -63,10 +66,13 @@ decode_dns(uint8_t const * pkt, uint32_t const len, dns* dns) return -1; } + q->label.append(reinterpret_cast(ptr), label_len); + q->label.append("."); + ptr += label_len; remaining_len -= label_len; } - + // Null byte at the end of QNAME if (remaining_len == 0) { @@ -152,8 +158,35 @@ decode_dns(uint8_t const * pkt, uint32_t const len, dns* dns) a->dns_atype = atype; a->dns_aclass = aclass; a->dns_ttl = ttl; - a->dns_rdlength = rdlength; - a->dns_rdata = *((uint16_t*)rdata); + + // Parse rdata + while (rdata < ptr) + { + uint16_t us = *reinterpret_cast(rdata); + if (us == name) + { + rdata += 2; + rdlength -= 2; + continue; + } + + uint8_t len = 0; + if (rdlength >= 1) + { + len = *rdata; + rdata += 1; + } + // abort if len == 0 && rdatalength > 0 // ANOMALY? + + len = rdlength > len ? len : rdlength; + + if (len) + { + a->data.append(reinterpret_cast(rdata), len); + a->data.append("."); + } + rdata += len; + } } return 0; From 54cb7d2b69eac6014a3cb38bf82273d90adfd4cd Mon Sep 17 00:00:00 2001 From: Victor Roemer Date: Thu, 8 Aug 2024 19:17:12 -0500 Subject: [PATCH 4/9] piglet wrapper --- piglet-bpf-filter/.gitignore | 1 + piglet-bpf-filter/Makefile | 7 + piglet-bpf-filter/daq_print.cc | 228 +++++++++++ piglet-bpf-filter/daq_print.h | 24 ++ piglet-bpf-filter/pcaps/dns.pcap | Bin 0 -> 4338 bytes piglet-bpf-filter/piglet-bpf-filter.cc | 532 +++++++++++++++++++++++++ 6 files changed, 792 insertions(+) create mode 100644 piglet-bpf-filter/.gitignore create mode 100644 piglet-bpf-filter/Makefile create mode 100644 piglet-bpf-filter/daq_print.cc create mode 100644 piglet-bpf-filter/daq_print.h create mode 100644 piglet-bpf-filter/pcaps/dns.pcap create mode 100644 piglet-bpf-filter/piglet-bpf-filter.cc diff --git a/piglet-bpf-filter/.gitignore b/piglet-bpf-filter/.gitignore new file mode 100644 index 0000000..2e95f57 --- /dev/null +++ b/piglet-bpf-filter/.gitignore @@ -0,0 +1 @@ +piglet-bpf-filter diff --git a/piglet-bpf-filter/Makefile b/piglet-bpf-filter/Makefile new file mode 100644 index 0000000..0b8225d --- /dev/null +++ b/piglet-bpf-filter/Makefile @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +piglet-bpf-filter: piglet-bpf-filter.cc daq_print.cc daq_print.h + c++ -ggdb -std=c++14 -lstdc++ $^ -lpcap -ldaq -lpacket -o $@ + +clean: + rm piglet-bpf-filter diff --git a/piglet-bpf-filter/daq_print.cc b/piglet-bpf-filter/daq_print.cc new file mode 100644 index 0000000..2b64df3 --- /dev/null +++ b/piglet-bpf-filter/daq_print.cc @@ -0,0 +1,228 @@ +// print_packet, coppied from many of my tools using libpacket +// Victor Roemer, wtfbbqhax + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "daq_print.h" + +#define IS_SET(flags, bit) ((flags & bit) == bit) + +// From https://github.com/the-tcpdump-group/tcpdump/blob/master/nameser.h#L312 + +/* + * Macros for subfields of flag fields. + */ +#define DNS_QR(flags) ((flags) & 0x8000) /* response flag */ +#define DNS_OPCODE(flags) (((flags) >> 11) & 0xF) /* purpose of message */ +#define DNS_AA(flags) (flags & 0x0400) /* authoritative answer */ +#define DNS_TC(flags) (flags & 0x0200) /* truncated message */ +#define DNS_RD(flags) (flags & 0x0100) /* recursion desired */ +#define DNS_RA(flags) (flags & 0x0080) /* recursion available */ +#define DNS_AD(flags) (flags & 0x0020) /* authentic data from named */ +#define DNS_CD(flags) (flags & 0x0010) /* checking disabled by resolver */ +#define DNS_RCODE(flags) (flags & 0x000F) /* response code */ + +// Function to decode the DNS protocol +int print_dns(dns const& dns) +{ + bool is_response = DNS_QR(dns.h.flags); + + if (is_response) + { + printf("[dns response] [rcode:%d, id:%d, qdcount: %d, ancount: %d, nscount: %d, arcount:%d]\n", + DNS_RCODE(dns.h.flags), + dns.h.id, + dns.h.qdcount, + dns.h.ancount, + dns.h.nscount, + dns.h.arcount); + } + else + { + printf("[dns query] [id:%d, qdcount: %d]\n", + dns.h.id, + dns.h.qdcount); + } + + // Parsing Question Section + for (int i = 0; i < dns.h.qdcount; i++) + { + struct dns_query const& q = dns.questions[i]; + // FIXME: Formatting/printing the QNAME + //struct dns_query *q = &dns.questions[i]; + // Display QNAME + // Display QTYPE and QCLASS (assuming a structure in Packet to store this information) + printf("[query] [label: %s, type: %d, class: %d]\n", + q.label.c_str(), + q.dns_qtype, + q.dns_qclass); + } + + // Parsing Answers Section + for (int i = 0; i < dns.h.ancount; i++) + { + char addr[INET6_ADDRSTRLEN]; + std::string human; + + struct dns_answer const &a = dns.answers[i]; + if (a.dns_atype == 1) + { + inet_ntop(AF_INET, a.data.data(), addr, sizeof(addr)); + human.append(addr, strnlen(addr, INET6_ADDRSTRLEN)); + + } + else + { + human = a.data; + } + printf("[answer] [data: %s, type: %d, class %d, ttl: %d]\n", + human.c_str(), + a.dns_atype, + a.dns_aclass, + a.dns_ttl); + // // Display QNAME + // // Display QTYPE and QCLASS (assuming a structure in Packet to store this information) + // (void)dns[0].questions[i].dns_qtype; + // (void)dns[0].questions[i].dns_qclass; + } + + return 0; +} + +// Taken from Pcapstats BSD License +// +void print_data(uint8_t const * data, int64_t length) +{ + int i, x, j, c; + int w = 0; + + for( i=0; length>0; length -= 16 ) + { + c = length >= 16 ? 16 : length; + printf("%06X ", w); + w+=16; + + for( j=0; j 128 ? 128 : max; + print_data(payload, max); + + //print_data(data, len); + +#ifdef PRINT_PACKET_STATS + // Packet stats are useful for determining decoding errors + struct packet_stats const * stats; + packet_stats(&stats); + + printf("ip4 headers: %u\n" + "ip4 badsum: %u\n" + "ip4 tooshort: %u\n" + "ip4 toosmall: %u\n" + "ip4 badhlen: %u\n" + "ip4 badlen: %u\n", + stats->ips_packets, + stats->ips_badsum, + stats->ips_tooshort, + stats->ips_toosmall, + stats->ips_badhlen, + stats->ips_badlen); + printf("tcp headers: %u\n" + "tcp badsum: %u\n" + "tcp badoff: %u\n" + "tcp tooshort %u\n", + stats->tcps_packets, + stats->tcps_badsum, + stats->tcps_badoff, + stats->tcps_tooshort); +#endif +} + + + + diff --git a/piglet-bpf-filter/daq_print.h b/piglet-bpf-filter/daq_print.h new file mode 100644 index 0000000..e1db1e2 --- /dev/null +++ b/piglet-bpf-filter/daq_print.h @@ -0,0 +1,24 @@ +#ifndef MINISNORT_PRINT_PACKET_H +#define MINISNORT_PRINT_PACKET_H + +#include +#include + +/* + * PRINT_PACKET_LAYERS + * @desc Print the protocol layer composition. + */ +#undef PRINT_PACKET_LAYERS + +/* + * PRINT_PACKET_STATS + * @desc Print the packet decoding stats. + * Useful for debugging decoding errors. + */ +#undef PRINT_PACKET_STATS + +typedef struct _daq_pkt_hdr DAQ_PktHdr_t; +void print_packet(int const instance_id, DAQ_PktHdr_t const * hdr, uint8_t const * data, size_t const len); + +#endif // MINISNORT_PRINT_PACKET_H + diff --git a/piglet-bpf-filter/pcaps/dns.pcap b/piglet-bpf-filter/pcaps/dns.pcap new file mode 100644 index 0000000000000000000000000000000000000000..911d77b58cf242b48590842e5ab292129ea88a91 GIT binary patch literal 4338 zcmb`KdrVVz6vxl)6KFwLbc&*4CI*Q$v`}nSv>=a&xJ^;xG#Osn0>wTUw}{3~QD#sl zZgX?8naqG2&J7>axh>Ppy8Yo6A>$9GY%z<@J+fpAIOou8ft_<(x%c+giiu4Q>3#Hj zzMpf>?{_b~U!7`GfC}z?hKB(dJUCTVl7IYFEfnE*3J4rBRJTikY^eoO|2R1f{C z5mfm(70s2UtDE}Kx=Gv*n0{$saU0(cnnmffqUuFdb^W4(m3WOpwg!qrYrsO2O3M5i z$Qu9U!Lx!_r7<&`u_}#LlOiWa>+%{{hoer#*445A`d||KZz|Y^`V`@#1YGwdev}3z z*2}k_Ie1oB5)Vj%GlZ#tS(XN=`kWjCnlV@nXiQn<^v3!d{7$zM*QnC#^g}{*N&eP` zI4D6c6f)}hA?igE>hu$AeLzIr(9obkxeCsq^wm|-j=+%e-W(mQK~V}BYU#Ik33b0! zf@(QDzCIvAji8Ktv^Oxeeo%F_exaX=nO=z1Mq%)k((ZNpA1On&X`nX>LkcjYeEnz~ zRHA(f8N=!xL~BfXy-UJSP(Ep(HwptPBLn`$9yLN@DxFoHeA4!qfW{@w{?H%G2}LR_iEv3gkQv%(`!`E=qqQ^R&2HPKWV` zpDAiO&!3`%C3pvg%y(|ccfQ2;%$@AHsZ!rs=SJ33d+!mCwn_R{r$&$tx1D?W z5ZT__F0nmRd;Q#0sco5bJ4z=u=>Z3l51H${kZXE-5cXq3LHZ0&WY=1+&tK*8Rs$`) z!zka=7?h?IveC)AGMjfkM&jIX{+$_~9ZfVE^r3>^b1sp;fbz4(#Q{Dv_>j5BZrbRL zsz~BKE#EtIL+W1X;_Sgqbr=|u{W?#d+4k!y); zXg30U4+!o?IQVoUe&R#BEnUOi_S4m5;iH8*SdJGdWMl;)vUww9Dfq6KsW7i86UZ1N zlgVT%jix#Bha_cyd>yz@oI(bso0>yVT*OcuTd2fuC0h#C-0ttzri*@_# zth?IB`GFD_Ez!Xu%t;|bGl$ShM$jfB+O*csQ+Eq!LS+@fhW)TpCp_>UI|`;JU|me% z&nx+NV6OQ`VyW4Wl9un|RTDHHpk%pqz~mJq)#YvnTdOMf+iS-cI4jwDx8G3Ys^k2I zGMC?Jc#`!vHRT+B&XK-%xtGspo;Jc{o3g@quUWpC;hJQ(w z-R`bp@pkG=?qyLK`2qr8%wQR*Gjo&fa8yDnC77|0oed$wn-}s4_NA(Ze2` z5{h8CMYFr5`6kcUFv55Y8EYC^n<6t#&oozQ>my z?_V{YBXv8Dbu&(E=~*Y5jpF5tmoAO8it)9P*2B1Y2||i+{K61^n+X3n7~hEPStlBX zXeqwI6hColAw~Fv7dHRPcfVtV;sjCncWtnbl7dn!{7jT$G!!SmIH2BOA0@^ANZ?xt literal 0 HcmV?d00001 diff --git a/piglet-bpf-filter/piglet-bpf-filter.cc b/piglet-bpf-filter/piglet-bpf-filter.cc new file mode 100644 index 0000000..d70c3b8 --- /dev/null +++ b/piglet-bpf-filter/piglet-bpf-filter.cc @@ -0,0 +1,532 @@ +// DAQ Global - vjr +#include +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +//#include +//#include + + +#include "daq_print.h" + +/* DLT_RAW + * + * Mandatory, as the VPP daq only supports L3 delivery. + * + * This is used to set the "base protocol" used in libpcap and libpacket + * features. + */ +//#define DLT_RAW 12 + +/* DAQ_BATCH_SIZE + * + * The maximum number of packets (DAQ_Msg_h) that we will batch read/process at + * once. + */ +#define DAQ_BATCH_SIZE 16 + +/* SNAPLEN + * + * The SNAPLEN is the absolulte maximum size packet we support processing. + * NOTICE: This value should be defined by VPP as the vlib buffer size. + */ +#define SNAPLEN 2048 + +/* TIMEOUT + * + * The TIMEOUT amount of usec waiting for daq_receive to return. + * NOTICE: TIMEOUT support in vpp DAQ only works in interrupt mode. + */ +#define TIMEOUT 100 + +/* STATIC_MODULES + * + * Enables the support of builtin libdaq_static_vpp.la and libdaq_static_geneve.la + * + * If you're in a pinch and need to build an all-in-one static binary, you can + * do it, but not using the Makefile. */ +#undef STATIC_MODULES + +#define UNUSED(name) name ## _unused __attribute__ ((unused)) +#define IS_SET(test, bits) (((test) & (bits)) == (bits)) + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un*)NULL)->sun_path)) +#endif + +using socketpath_t = char[UNIX_PATH_MAX]; + +#define ERRBUF_SIZE 256 +using Errbuf = std::array; + +// DaqVariable +using DaqVariable = std::pair; +using DaqVars = std::vector; + +char const* str_from_verdict(DAQ_Verdict const& verdict); + +namespace DAQ +{ +#ifdef STATIC_MODULES + // from libdaq_static_vpp.a + extern "C" const DAQ_ModuleAPI_t vpp_daq_module_data; + extern "C" const DAQ_ModuleAPI_t geneve_daq_module_data; + + static DAQ_Module_h static_modules[] = + { + &vpp_daq_module_data, + &geneve_daq_module_data, + nullptr + }; +#endif + + static char const *module_paths[] = + { + // Location of all the open source Snort daqs (bpf) + "/usr/local/lib/daq", + nullptr + }; + + static bool s_modules_loaded = false; + void load_modules() + { + if (s_modules_loaded == false) + { + s_modules_loaded = true; +#ifdef STATIC_MODULES + daq_load_static_modules(static_modules); +#endif + daq_load_dynamic_modules(module_paths); + } + } + + void unload_modules() + { + if (s_modules_loaded == true) + { + daq_unload_modules(); + s_modules_loaded = false; + } + } +} + +// DaqConfig +// +// DaqConfig is a "copy safe" representation of a DAQ_Config_h opaque pointer. +// This would be trivial to implement if the DAQ_Config_t type was avaiable +// from the DAQ API. +// +// The DaqConfig attempts to make a mutable representation of a DAQ_Config_h. +// To accomplish this task, it implements a factory pattern, constructring C++ +// type DAQ_Config_p that provides a constructor/desctructor for DAQ_Config_h. +// +// NOTICE +// +// * Implementing this would be easier if the DAQ_Config_t was not hidden from +// the DAQ API. +// +class DaqConfig +{ +public: + DaqConfig(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : module(module), + input(input), + mode(mode), + vars(vars) + { } + + struct DAQ_Config_p + { + DAQ_Config_p(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : config(nullptr) + { + daq_config_new(&config); + daq_config_set_input(config, input.c_str()); + daq_config_set_snaplen(config, SNAPLEN); + daq_config_set_timeout(config, TIMEOUT); + + DAQ_Module_h mod = daq_find_module(module.c_str()); + if (mod == nullptr) { + daq_config_destroy(config); + config = nullptr; + } + + DAQ_ModuleConfig_h modconf = nullptr; + int result = daq_module_config_new(&modconf, mod); + if (result != DAQ_SUCCESS) { + daq_config_destroy(config); + config = nullptr; + } + + daq_module_config_set_mode(modconf, mode); + for (auto const & var : vars) { + daq_module_config_set_variable(modconf, var.first.c_str(), var.second.c_str()); + } + + result = daq_config_push_module_config(config, modconf); + if (result != DAQ_SUCCESS) { + // NOTICE This is the only time we are allowed to call this ourselves. + daq_module_config_destroy(modconf); + modconf = nullptr; + + daq_config_destroy(config); + config = nullptr; + } + } + + ~DAQ_Config_p() + { + // Calling daq_config_destroy will call + // `daq_module_config_destroy(modcfg)` on each module. + daq_config_destroy(config); + config = nullptr; + } + + DAQ_Config_h config; + }; + + DAQ_Config_p get_config() const + { + return DAQ_Config_p(module, input, mode, vars); + } + +private: + std::string module; + std::string input; + DAQ_Mode mode; + DaqVars vars; +}; + +#define FRAME_SIZE 256 + +struct DaqMsgFrame +{ + std::array msgs; + size_t recv_count; +}; + +struct DaqVerdictFrame +{ + std::array verdicts; +}; + +struct RecvResult +{ + DAQ_RecvStatus status; + DaqMsgFrame const& frame; +}; + +class DaqInstance +{ +public: + DaqInstance(DaqConfig const & config) + : instance(NULL), config(config) + {} + + ~DaqInstance() + { + if (instance) { + daq_instance_destroy(instance); + instance = nullptr; + } + + msgs.recv_count = 0; + } + + DaqInstance(DaqInstance const & other) + : config(other.config) + { } + + int instantiate() + { + return daq_instance_instantiate( + config.get_config().config, + &instance, + errbuf.data(), + errbuf.size()); + } + + int start() + { + return daq_instance_start(instance); + } + + int stop() + { + return daq_instance_stop(instance); + } + + RecvResult receive_msgs() + { + DAQ_RecvStatus rstat; + msgs.recv_count = daq_instance_msg_receive( + instance, + msgs.msgs.max_size(), + msgs.msgs.data(), + &rstat); + + return { rstat, msgs }; + } + + //void finalize_msgs(DAQ_Verdict const & verdict) + void finalize_msgs(DaqVerdictFrame const & verdicts) + { + for (unsigned i = 0; i < msgs.recv_count; i++) + { + DAQ_Msg_h msg = msgs.msgs[i]; + DAQ_Verdict verdict = verdicts.verdicts[i]; + daq_instance_msg_finalize(instance, msg, verdict); + } + + msgs.recv_count = 0; + } + + DAQ_Stats_t get_stats() const + { + DAQ_Stats_t stats; + daq_instance_get_stats(instance, &stats); + return stats; + } + + void reset_stats() const + { + daq_instance_reset_stats(instance); + } + +private: + DAQ_Instance_h instance; + DaqConfig config; + DaqMsgFrame msgs; + Errbuf errbuf; +}; + +class DataPlaneWorker +{ + using threadname_t = char[256]; + +public: + DataPlaneWorker(DaqConfig config, unsigned id, std::string filter, DAQ_Verdict verdict, DAQ_Verdict default_verdict) + : config(config), + id(id), + match_verdict(verdict), + default_verdict(default_verdict) + { + if (pcap_compile_nopcap(SNAPLEN, DLT_EN10MB, &fcode, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1) + { + fprintf(stderr, "%s: BPF state machine compilation failed!", __func__); + abort(); + } + + int result = bpf_validate(fcode.bf_insns, fcode.bf_len); + if (result != 1) + { + fprintf(stderr, "%s: BPF is not valid!", __func__); + abort(); + } + + thread = std::thread(&DataPlaneWorker::eval, this); + pthread_t native = thread.native_handle(); + + snprintf(name, sizeof(name), "pkt_wk_%u", id); + pthread_setname_np(native, name); + + // Float these workers on cpus 3 & 4 + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(3, &cpuset); + CPU_SET(4, &cpuset); + pthread_setaffinity_np(native, sizeof(cpuset), &cpuset); + } + + ~DataPlaneWorker() + { + pcap_freecode(&fcode); + } + + void join() + { + thread.join(); + } + + void eval() + { + DaqInstance in(config); + + in.instantiate(); + in.start(); + + state = START; + + do { + auto recv = in.receive_msgs(); + + if (recv.frame.recv_count > 0) { + print_packets(recv.frame); + } + + if (recv.status == DAQ_RSTAT_ERROR || + recv.status == DAQ_RSTAT_INVALID) { + state = STOP; + } + + in.finalize_msgs(verdicts); + + } while(state != STOP); + + in.stop(); + } + + void stop() + { + state = STOP; + } + +private: + bool filter_packet(UNUSED(DAQ_PktHdr_t const* hdr), + uint8_t const* data, + uint32_t const size, + bpf_program const& fcode) + { + return bpf_filter(fcode.bf_insns, data, size, size) == SNAPLEN; + } + + char const* str_from_verdict(DAQ_Verdict const& verdict) + { + if (verdict == DAQ_VERDICT_PASS) + return "\e[34m" "pass" "\e[0m"; + if (verdict == DAQ_VERDICT_BLOCK) + return "\e[31m" "block" "\e[0m"; + if (verdict == DAQ_VERDICT_WHITELIST) + return "\e[32m" "allowlist" "\e[0m"; + if (verdict == DAQ_VERDICT_BLACKLIST) + return "\e[35m" "blocklist" "\e[0m"; + if (verdict == DAQ_VERDICT_IGNORE) + return "ignore"; + return ""; + } + + void print_packets(DaqMsgFrame const& frame) + { + for (unsigned i = 0; i < frame.recv_count; i++) + { + auto const & msg = frame.msgs[i]; + if (msg->type == DAQ_MSG_TYPE_PACKET) { + DAQ_PktHdr_t const * hdr = daq_msg_get_pkthdr(msg); + uint8_t const * data = daq_msg_get_data(msg); + uint32_t const size = daq_msg_get_data_len(msg); + + DAQ_Verdict verdict = default_verdict; + bool matched = false; + if (filter_packet(hdr, data, size, fcode)) { + verdict = match_verdict; + matched = true; + } + + verdicts.verdicts[i] = verdict; + printf(matched ? "[match|%s] " : "[%s]", str_from_verdict(verdict)); + print_packet(id, hdr, data, hdr->pktlen); + } + } + } + + DaqConfig config; + unsigned id; + + enum { INVAL, STOP, START } state; + std::thread thread; + threadname_t name; + bpf_program fcode; + DaqVerdictFrame verdicts; + + DAQ_Verdict match_verdict; + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; +}; + +// this is similar to how tcpdump +static inline std::string +concat_args(int argc, char const* argv[]) +{ + std::string result; + for (int i = 0; i < argc; ++i) + { + if (i > 0) + { + result += " "; + } + result += argv[i]; + } + return result; +} + +static inline DAQ_Verdict +verdict_from_str(std::string const& arg) +{ + if (arg == "pass") + return DAQ_VERDICT_PASS; + + if (arg == "block") + return DAQ_VERDICT_BLOCK; + + if (arg == "allowlist" || arg == "whitelist") + return DAQ_VERDICT_WHITELIST; + + if (arg == "blocklist" || arg == "blacklist") + return DAQ_VERDICT_BLACKLIST; + + abort(); + return DAQ_VERDICT_IGNORE; +} + +int main(int argc, char const* argv[]) +{ + DAQ::load_modules(); + packet_set_datalink(DLT_EN10MB); + + //socketpath_t socket_path {}; + //snprintf(socket_path, sizeof(socket_path), "/tmp/snort.sock"); + + DaqVars vars { + //{ "socket_path", socket_path }, + //{ "debug", "true" }, + }; + + if (argc < 3) + { + fprintf(stderr, "Usage: piglet-bpf-filter [pass|block|allowlist|blocklist] \n"); + exit(1); + } + + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + DAQ_Verdict match_verdict = verdict_from_str(argv[1]); + std::string filter = concat_args(argc-2, argv+2); + + DaqConfig pcap_config("pcap", "pcaps/dns.pcap", DAQ_MODE_READ_FILE, vars); + DataPlaneWorker wk0(pcap_config, 0, filter, match_verdict, default_verdict); + sleep(2); + wk0.stop(); + wk0.join(); + + //DataPlaneWorker wk1(vpp_inline_config, 1, filter, match_verdict, default_verdict); + //wk1.stop(); wk1.join(); + + DAQ::unload_modules(); + return 0; +} From beb053524fe8d28a2662d7fce1fa5c1a074e6dd8 Mon Sep 17 00:00:00 2001 From: Victor Roemer Date: Tue, 3 Sep 2024 23:45:19 +0000 Subject: [PATCH 5/9] DNS Initial Support --- Containerfile | 2 +- {piglet-bpf-filter => bin}/.gitignore | 0 {piglet-bpf-filter => bin}/Makefile | 2 +- {piglet-bpf-filter => bin}/daq_print.cc | 35 +++---- {piglet-bpf-filter => bin}/daq_print.h | 0 .../piglet-bpf-filter.cc => bin/dns_hog.cc | 16 ++- bin/pcaps/dns-label-loop.pcap | Bin 0 -> 216 bytes {piglet-bpf-filter => bin}/pcaps/dns.pcap | Bin include/packet/dns.h | 2 +- src/dns.cc | 96 +++++++++++------- 10 files changed, 90 insertions(+), 63 deletions(-) rename {piglet-bpf-filter => bin}/.gitignore (100%) rename {piglet-bpf-filter => bin}/Makefile (63%) rename {piglet-bpf-filter => bin}/daq_print.cc (91%) rename {piglet-bpf-filter => bin}/daq_print.h (100%) rename piglet-bpf-filter/piglet-bpf-filter.cc => bin/dns_hog.cc (97%) create mode 100644 bin/pcaps/dns-label-loop.pcap rename {piglet-bpf-filter => bin}/pcaps/dns.pcap (100%) diff --git a/Containerfile b/Containerfile index 46cf0c9..9096adf 100644 --- a/Containerfile +++ b/Containerfile @@ -1,5 +1,5 @@ # Using www.github.com/wtfbbqhax/krakatoa -FROM arm64v8/krakatoa AS libpacket_dev_env +FROM amd64/krakatoa AS libpacket_dev_env USER root RUN apk update diff --git a/piglet-bpf-filter/.gitignore b/bin/.gitignore similarity index 100% rename from piglet-bpf-filter/.gitignore rename to bin/.gitignore diff --git a/piglet-bpf-filter/Makefile b/bin/Makefile similarity index 63% rename from piglet-bpf-filter/Makefile rename to bin/Makefile index 0b8225d..39482c2 100644 --- a/piglet-bpf-filter/Makefile +++ b/bin/Makefile @@ -1,6 +1,6 @@ #!/usr/bin/env bash -piglet-bpf-filter: piglet-bpf-filter.cc daq_print.cc daq_print.h +dns-hog: dns_hog.cc daq_print.cc daq_print.h c++ -ggdb -std=c++14 -lstdc++ $^ -lpcap -ldaq -lpacket -o $@ clean: diff --git a/piglet-bpf-filter/daq_print.cc b/bin/daq_print.cc similarity index 91% rename from piglet-bpf-filter/daq_print.cc rename to bin/daq_print.cc index 2b64df3..73fc4eb 100644 --- a/piglet-bpf-filter/daq_print.cc +++ b/bin/daq_print.cc @@ -36,9 +36,9 @@ int print_dns(dns const& dns) { bool is_response = DNS_QR(dns.h.flags); - if (is_response) + //if (is_response) { - printf("[dns response] [rcode:%d, id:%d, qdcount: %d, ancount: %d, nscount: %d, arcount:%d]\n", + printf("[dns] [rcode:%d, id:%d, qdcount: %d, ancount: %d, nscount: %d, arcount:%d]\n", DNS_RCODE(dns.h.flags), dns.h.id, dns.h.qdcount, @@ -46,12 +46,12 @@ int print_dns(dns const& dns) dns.h.nscount, dns.h.arcount); } - else - { - printf("[dns query] [id:%d, qdcount: %d]\n", - dns.h.id, - dns.h.qdcount); - } + //else + //{ + // printf("[dns query] [id:%d, qdcount: %d]\n", + // dns.h.id, + // dns.h.qdcount); + //} // Parsing Question Section for (int i = 0; i < dns.h.qdcount; i++) @@ -78,7 +78,11 @@ int print_dns(dns const& dns) { inet_ntop(AF_INET, a.data.data(), addr, sizeof(addr)); human.append(addr, strnlen(addr, INET6_ADDRSTRLEN)); - + } + else if (a.dns_atype == 28) + { + inet_ntop(AF_INET6, a.data.data(), addr, sizeof(addr)); + human.append(addr, strnlen(addr, INET6_ADDRSTRLEN)); } else { @@ -95,6 +99,7 @@ int print_dns(dns const& dns) // (void)dns[0].questions[i].dns_qclass; } + printf("\n"); return 0; } @@ -179,21 +184,17 @@ print_packet(int const instance_id, DAQ_PktHdr_t const* hdr, uint8_t const * dat packet_frag_mf(&packet) ? "mf" : ""); } + uint32_t max = packet_paysize(&packet); + const uint8_t *payload = packet_payload(&packet); if (sport == 53 || dport == 53) { dns _dns; - decode_dns(packet_payload(&packet), - packet_paysize(&packet), - &_dns); + decode_dns(payload, max, &_dns); print_dns(_dns); } - uint32_t max = packet_paysize(&packet); - const uint8_t *payload = packet_payload(&packet); max = max > 128 ? 128 : max; - print_data(payload, max); - - //print_data(data, len); + //print_data(payload, max); #ifdef PRINT_PACKET_STATS // Packet stats are useful for determining decoding errors diff --git a/piglet-bpf-filter/daq_print.h b/bin/daq_print.h similarity index 100% rename from piglet-bpf-filter/daq_print.h rename to bin/daq_print.h diff --git a/piglet-bpf-filter/piglet-bpf-filter.cc b/bin/dns_hog.cc similarity index 97% rename from piglet-bpf-filter/piglet-bpf-filter.cc rename to bin/dns_hog.cc index d70c3b8..ad60f2e 100644 --- a/piglet-bpf-filter/piglet-bpf-filter.cc +++ b/bin/dns_hog.cc @@ -21,9 +21,7 @@ #include #include -//#include -//#include - +#define TXT_FG_PURPLE(str) "\e[35m" str "\e[0m" #include "daq_print.h" @@ -441,7 +439,8 @@ class DataPlaneWorker } verdicts.verdicts[i] = verdict; - printf(matched ? "[match|%s] " : "[%s]", str_from_verdict(verdict)); + printf(matched ? "[" TXT_FG_PURPLE("match") "] " : ""); + printf("[%s] ", str_from_verdict(verdict)); print_packet(id, hdr, data, hdr->pktlen); } } @@ -508,9 +507,9 @@ int main(int argc, char const* argv[]) //{ "debug", "true" }, }; - if (argc < 3) + if (argc < 2) { - fprintf(stderr, "Usage: piglet-bpf-filter [pass|block|allowlist|blocklist] \n"); + fprintf(stderr, "Usage: piglet-bpf-filter \n"); exit(1); } @@ -520,13 +519,12 @@ int main(int argc, char const* argv[]) DaqConfig pcap_config("pcap", "pcaps/dns.pcap", DAQ_MODE_READ_FILE, vars); DataPlaneWorker wk0(pcap_config, 0, filter, match_verdict, default_verdict); + sleep(2); + wk0.stop(); wk0.join(); - //DataPlaneWorker wk1(vpp_inline_config, 1, filter, match_verdict, default_verdict); - //wk1.stop(); wk1.join(); - DAQ::unload_modules(); return 0; } diff --git a/bin/pcaps/dns-label-loop.pcap b/bin/pcaps/dns-label-loop.pcap new file mode 100644 index 0000000000000000000000000000000000000000..0470c58833f24feabaf8df6824faf6083ae52ff5 GIT binary patch literal 216 zcmca|c+)~A1{MYw`2U}Qff2|t@7kiyB}3}k~a6DJ2d8!Hnd6Eh1d2ZJjEgDFUv zgJ8@OE}$3}P(ptr), label_len); + _label.append("."); + ptr += label_len; + + if (ptr[0] != 0) { + goto scan_again; + } +} + // Function to decode the DNS protocol -int +extern "C" int decode_dns(uint8_t const * pkt, uint32_t const len, dns* dns) { if (len < MINIMUM_DNS_HEADER_SIZE) { @@ -113,8 +144,23 @@ decode_dns(uint8_t const * pkt, uint32_t const len, dns* dns) s_dns_stats.dns_tooshort++; return -1; } - // FIXME: Store this value? - uint16_t name = ntohs(*(uint16_t *)ptr); + + // The "name" appears to be a partial label, but I can't find that + // documented in rfc1035, more testing is needed. + std::string name; + if (IS_SET(ptr[0], 0xC0)) { + uint8_t off = ptr[1]; + uint8_t const* label = &pkt[off]; + uint8_t label_len = *label; + while(label[0] != 0) + { + label++; + name.append(reinterpret_cast(label), label_len); + name.append("."); + label += label_len; + } + } + ptr += 2; remaining_len -= 2; @@ -150,43 +196,25 @@ decode_dns(uint8_t const * pkt, uint32_t const len, dns* dns) return -1; } - const uint8_t *rdata = ptr; - ptr += rdlength; - remaining_len -= rdlength; - - // Store answer information (assuming a structure in Packet to store this information) + // Store answer information (assuming a structure in Packet to store + // this information) a->dns_atype = atype; a->dns_aclass = aclass; a->dns_ttl = ttl; - // Parse rdata - while (rdata < ptr) + if (atype == 1 || atype == 28) { - uint16_t us = *reinterpret_cast(rdata); - if (us == name) - { - rdata += 2; - rdlength -= 2; - continue; - } - - uint8_t len = 0; - if (rdlength >= 1) - { - len = *rdata; - rdata += 1; - } - // abort if len == 0 && rdatalength > 0 // ANOMALY? - - len = rdlength > len ? len : rdlength; - - if (len) - { - a->data.append(reinterpret_cast(rdata), len); - a->data.append("."); - } - rdata += len; + a->data.append(reinterpret_cast(ptr), rdlength); + } + else + { + // Parse rdata + decode_label(pkt, ptr, a->data); } + + //const uint8_t *rdata = ptr; + ptr += rdlength; + remaining_len -= rdlength; } return 0; From ce5993ab455b6da902a42b583a54ac9676ab9df5 Mon Sep 17 00:00:00 2001 From: Victor Roemer Date: Wed, 4 Sep 2024 00:04:36 +0000 Subject: [PATCH 6/9] dnshog: Take path to pcap as only argument ./dnshog --- Makefile | 6 +++--- bin/Makefile | 2 +- bin/dns_hog.cc | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 50ec0c9..26bc0dd 100644 --- a/Makefile +++ b/Makefile @@ -39,13 +39,13 @@ container: .PHONY: start start: - docker run --rm -td -v "$(PWD)":/volume/libpacket "$(IMAGE_NAME)" + docker run --name libpacket --rm -td -v "$(PWD)":/volume/libpacket "$(IMAGE_NAME)" .PHONY: kill kill: - docker kill $(shell docker ps --filter "ancestor=$(IMAGE_NAME)" --format "{{.ID}}") + docker kill libpacket .PHONY: attach attach: - docker exec -ti $(shell docker ps --filter "ancestor=$(IMAGE_NAME)" --format "{{.ID}}" | head -n1) sh + docker exec -ti libpacket sh diff --git a/bin/Makefile b/bin/Makefile index 39482c2..2822c85 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -1,6 +1,6 @@ #!/usr/bin/env bash -dns-hog: dns_hog.cc daq_print.cc daq_print.h +dnshog: dns_hog.cc daq_print.cc daq_print.h c++ -ggdb -std=c++14 -lstdc++ $^ -lpcap -ldaq -lpacket -o $@ clean: diff --git a/bin/dns_hog.cc b/bin/dns_hog.cc index ad60f2e..bb224ed 100644 --- a/bin/dns_hog.cc +++ b/bin/dns_hog.cc @@ -509,15 +509,16 @@ int main(int argc, char const* argv[]) if (argc < 2) { - fprintf(stderr, "Usage: piglet-bpf-filter \n"); + fprintf(stderr, "Usage: dnshog \n"); exit(1); } DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; - DAQ_Verdict match_verdict = verdict_from_str(argv[1]); + //DAQ_Verdict match_verdict = verdict_from_str(argv[1]); + DAQ_Verdict match_verdict = verdict_from_str("pass"); std::string filter = concat_args(argc-2, argv+2); - DaqConfig pcap_config("pcap", "pcaps/dns.pcap", DAQ_MODE_READ_FILE, vars); + DaqConfig pcap_config("pcap", argv[1], DAQ_MODE_READ_FILE, vars); DataPlaneWorker wk0(pcap_config, 0, filter, match_verdict, default_verdict); sleep(2); From 2e28eff130175250ba81e3f867091da75e99ecbd Mon Sep 17 00:00:00 2001 From: Victor Roemer Date: Wed, 4 Sep 2024 00:05:26 +0000 Subject: [PATCH 7/9] tests/abc: Abc and pcap generations for testing --- tests/abc/dns-1.abc | 91 +++++++++++++++++++ tests/abc/dns-canonical.pcap | Bin 0 -> 289 bytes tests/abc/dns-custom.pcap | Bin 0 -> 289 bytes tests/abc/dns-label-loop.abc | 39 ++++++++ tests/abc/dns-label-loop.pcap | Bin 0 -> 216 bytes tests/abc/dns.abc | 56 ++++++++++++ tests/abc/variants/dns-canonical.pcap | Bin 0 -> 289 bytes tests/abc/variants/dns-label-ptr-to-ptr.pcap | Bin 0 -> 289 bytes tests/abc/variants/dns.abc | 56 ++++++++++++ 9 files changed, 242 insertions(+) create mode 100644 tests/abc/dns-1.abc create mode 100644 tests/abc/dns-canonical.pcap create mode 100644 tests/abc/dns-custom.pcap create mode 100644 tests/abc/dns-label-loop.abc create mode 100644 tests/abc/dns-label-loop.pcap create mode 100644 tests/abc/dns.abc create mode 100644 tests/abc/variants/dns-canonical.pcap create mode 100644 tests/abc/variants/dns-label-ptr-to-ptr.pcap create mode 100644 tests/abc/variants/dns.abc diff --git a/tests/abc/dns-1.abc b/tests/abc/dns-1.abc new file mode 100644 index 0000000..f598e74 --- /dev/null +++ b/tests/abc/dns-1.abc @@ -0,0 +1,91 @@ +#!abcip + +d(stack="eth:ip4:udp") +c(udp:b=53) + +a( pay= + # Client DNS Query + "|20 8a|" # id + "|01 00|" # flags + + "|00 01|" # qcount + "|00 00|" # ancount + "|00 00|" # aucount + "|00 00|" # adcount + + "|03|isc|03|org|00|" # label + "|00 02|" # # qtype + "|00 01|" # # qclass +) + +#b( pay= +# "|20 8a|" # id +# "|81 80|" # flags +# +# "|00 01|" # question count +# "|00 04|" # answer count +# "|00 00|" # authority count +# "|00 00|" # additional count +# +# # Begin DNS Questions +# "|03|vic|03|org|00|" # labels +# "|00 02|" # qtype [NS] +# "|00 01|" # qclass [IN] +# +# # Begin Answers 1 +# "|c0 0c|" # name +# "|00 02|" # type [NS] +# "|00 01|" # qclass [IN] +# "|00 00 0e 10|" # ttl +# "|00 0e|" # rdata length (14) +# "|06|ns-ext|04|nrt1|c0 0c|" # rdata +# +# # Begin Answers 2 +# "|c0 0c|" +# "|00 02|" +# "|00 01|" +# "|00 00 0e 10|" +# "|00 0e|" +# "|06|ns-ext|04|sth1|c0 0c|" +# +# # Begin Answers 3 +# "|c0 0c|" +# "|00 02|" +# "|00 01|" +# "|00 00 0e 10|" +# "|00 09|" +# "|06|ns-ext|c0 0c|" +# +# # Begin Answers 4 +# "|c0 0c|" +# "|00 02|" +# "|00 01|" +# "|00 00 0e 10|" +# "|00 0e|" +# "|06|ns-ext|04|lga1|c0 0c|" +#) + + +b( pay= + "|00 01|" # id + "|81 80|" # flags + + "|00 01|" # question count + "|00 01|" # answer count + "|00 00|" # authority count + "|00 00|" # additional count + + # Begin DNS Questions + "|03|vic|03|org|00|" # labels + "|00 02|" # qtype [NS] + "|00 01|" # qclass [IN] + + # Begin Answers 1 + "|c0 25|" # name + "|00 02|" # type [NS] + "|00 01|" # qclass [IN] + "|00 00 0e 10|" # ttl + "|00 06|" # rdata length (14) + "|04|home|00|" +) + diff --git a/tests/abc/dns-canonical.pcap b/tests/abc/dns-canonical.pcap new file mode 100644 index 0000000000000000000000000000000000000000..5bbbd8dbd73171cde94cb682b0034a7246f289b7 GIT binary patch literal 289 zcmca|c+)~A1{MYw`2U}Qff2|_>DZQbIEIVC8OR1&nTtG1{pzPi^KruxjPlYa^1jq;=V9qQ~X3j542kK{F1Q}T$%f+w^C;`F{!ypEO zjhq2B(h+1F$XEtbprI{alNGue8-Q|PSE3ksfCpqS0|TD`10P#nv2JQb2}@p4iQxer WY%;|q8K^RxP(9d{=cFeBl>-2J;4}&V literal 0 HcmV?d00001 diff --git a/tests/abc/dns-custom.pcap b/tests/abc/dns-custom.pcap new file mode 100644 index 0000000000000000000000000000000000000000..0235a55903d8840eb59ed11d62f05c7814ab4e89 GIT binary patch literal 289 zcmca|c+)~A1{MYw`2U}Qff2~j@7R`BIh&2a8OR1&nTtG1{pzPi^KruxjPlYa^1jq;=V9qQ~X3j542kK{F1Q}^HhmBzwPy&P@hCvJl z8#x1Nq$9{Ukg*J=Kto$zBr9|^HUQ20pgDV%^k=5|+H862k+M b*kp=JG7Jy!Ajxn-^`HpC4a!MR1S$sr1q3sP literal 0 HcmV?d00001 diff --git a/tests/abc/dns-label-loop.abc b/tests/abc/dns-label-loop.abc new file mode 100644 index 0000000..90fb5e8 --- /dev/null +++ b/tests/abc/dns-label-loop.abc @@ -0,0 +1,39 @@ +#!abcip + +d(stack="eth:ip4:udp") +c(udp:b=53) + +a( pay="|20 8a 01 00 00 01 00 00 00 00 00 00 03|isc" # ............isc + "|03|org|00 00||02 00 01|") # .org..... + +b( pay= + # Plain DNS header + "|20 8a|" # id + "|81 80|" # flags + + "|00 01|" # question count + "|00 01|" # answer count + "|00 00|" # authority count + "|00 00|" # additional count + + # Begin DNS Questions + "|03|isc|03|org|00|" # labels + "|00 02|" # qtype [NS] + "|00 01|" # qclass [IN] + + # Begin Answers 1 + "|c0 10|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 0E|" + + # 0x19 points to Answer 1 + # x.y.z -> (unnecessary jump) .123 -> (skip 'isc') .org + "|01|x" + "|01|y" + "|01|z" + "|c0 2d|" + "|03|123|c0 10|" +) + diff --git a/tests/abc/dns-label-loop.pcap b/tests/abc/dns-label-loop.pcap new file mode 100644 index 0000000000000000000000000000000000000000..0470c58833f24feabaf8df6824faf6083ae52ff5 GIT binary patch literal 216 zcmca|c+)~A1{MYw`2U}Qff2|t@7kiyB}3}k~a6DJ2d8!Hnd6Eh1d2ZJjEgDFUv zgJ8@OE}$3}PDZQbIEIVC8OR1&nTtG1{pzPi^KruxjPlYa^1jq;=V9qQ~X3j542kK{F1Q}T$%f+w^C;`F{!ypEO zjhq2B(h+1F$XEtbprI{alNGue8-Q|PSE3ksfCpqS0|TD`10P#nv2JQb2}@p4iQxer WY%;|q8K^RxP(9d{=cFeBl>-2J;4}&V literal 0 HcmV?d00001 diff --git a/tests/abc/variants/dns-label-ptr-to-ptr.pcap b/tests/abc/variants/dns-label-ptr-to-ptr.pcap new file mode 100644 index 0000000000000000000000000000000000000000..0235a55903d8840eb59ed11d62f05c7814ab4e89 GIT binary patch literal 289 zcmca|c+)~A1{MYw`2U}Qff2~j@7R`BIh&2a8OR1&nTtG1{pzPi^KruxjPlYa^1jq;=V9qQ~X3j542kK{F1Q}^HhmBzwPy&P@hCvJl z8#x1Nq$9{Ukg*J=Kto$zBr9|^HUQ20pgDV%^k=5|+H862k+M b*kp=JG7Jy!Ajxn-^`HpC4a!MR1S$sr1q3sP literal 0 HcmV?d00001 diff --git a/tests/abc/variants/dns.abc b/tests/abc/variants/dns.abc new file mode 100644 index 0000000..cc99b39 --- /dev/null +++ b/tests/abc/variants/dns.abc @@ -0,0 +1,56 @@ +#!abcip + +d(stack="eth:ip4:udp") +c(udp:b=53) + +a( pay="|20 8a 01 00 00 01 00 00 00 00 00 00 03|isc" # ............isc + "|03|org|00 00||02 00 01|") # .org..... + +b( pay= + # Plain DNS header + "|20 8a|" # id + "|81 80|" # flags + + "|00 01|" # question count + "|00 04|" # answer count + "|00 00|" # authority count + "|00 00|" # additional count + + # Begin DNS Questions + "|03|isc|03|org|00|" # labels + "|00 02|" # qtype [NS] + "|00 01|" # qclass [IN] + + # Begin Answers 1 + "|c0 0c|" # name + "|00 02|" # type [NS] + "|00 01|" # qclass [IN] + "|00 00 0e 10|" # ttl + "|00 0e|" # rdata length (14) + "|06|ns-ext|04|nrt1|c0 19|" # rdata + + # Begin Answers 2 + "|c0 0c|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 0e|" + "|06|ns-ext|04|sth1|c0 0c|" + + # Begin Answers 3 + "|c0 0c|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 09|" + "|06|ns-ext|c0 0c|" + + # Begin Answers 4 + "|c0 0c|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 0e|" + "|06|ns-ext|04|lga1|c0 0c|" +) + From 0074c28296d02ff911bd7647e07baf79411c5957 Mon Sep 17 00:00:00 2001 From: Victor Roemer Date: Wed, 4 Sep 2024 00:22:35 +0000 Subject: [PATCH 8/9] bin/CMakeLists.txt: Build executables with CMake as well --- bin/.gitignore | 2 +- bin/CMakeLists.txt | 25 +++++++++++++++++++++++++ bin/Makefile | 24 ++++++++++++++++++++---- 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 bin/CMakeLists.txt diff --git a/bin/.gitignore b/bin/.gitignore index 2e95f57..567609b 100644 --- a/bin/.gitignore +++ b/bin/.gitignore @@ -1 +1 @@ -piglet-bpf-filter +build/ diff --git a/bin/CMakeLists.txt b/bin/CMakeLists.txt new file mode 100644 index 0000000..cf259ca --- /dev/null +++ b/bin/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.20) +project(pig C CXX) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +add_executable( + dnshog +) + +target_sources( + dnshog + PRIVATE + dns_hog.cc + daq_print.cc + daq_print.h +) + +target_link_libraries( + dnshog + PRIVATE + daq + pcap + packet +) diff --git a/bin/Makefile b/bin/Makefile index 2822c85..11597d2 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -1,7 +1,23 @@ -#!/usr/bin/env bash +.DEFAULT_GOAL := build -dnshog: dns_hog.cc daq_print.cc daq_print.h - c++ -ggdb -std=c++14 -lstdc++ $^ -lpcap -ldaq -lpacket -o $@ +# Makefile rules to build the libpacket source code +.PHONY: build +build: + cmake -B build -G Ninja . \ + -D CMAKE_BUILD_TYPE:STRING=Debug \ + -D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE + cmake --build build +.PHONY: install +install: build + cmake --install build + +.PHONY: clean clean: - rm piglet-bpf-filter + rm -rf build/ + +.PHONY: test +test: install + make -C tests/ + + From 5803ca8ec089f6447e3b256f658de31499d67df1 Mon Sep 17 00:00:00 2001 From: Victor Roemer Date: Wed, 4 Sep 2024 00:38:01 +0000 Subject: [PATCH 9/9] dnshog: fixup formatting output --- bin/daq_print.cc | 16 ++++++++-------- bin/dns_hog.cc | 15 ++++++++++----- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/bin/daq_print.cc b/bin/daq_print.cc index 73fc4eb..7e79f4d 100644 --- a/bin/daq_print.cc +++ b/bin/daq_print.cc @@ -36,9 +36,9 @@ int print_dns(dns const& dns) { bool is_response = DNS_QR(dns.h.flags); - //if (is_response) + if (is_response) { - printf("[dns] [rcode:%d, id:%d, qdcount: %d, ancount: %d, nscount: %d, arcount:%d]\n", + printf("[dns response] [rcode:%d, id:%d, qdcount: %d, ancount: %d, nscount: %d, arcount:%d]\n", DNS_RCODE(dns.h.flags), dns.h.id, dns.h.qdcount, @@ -46,12 +46,12 @@ int print_dns(dns const& dns) dns.h.nscount, dns.h.arcount); } - //else - //{ - // printf("[dns query] [id:%d, qdcount: %d]\n", - // dns.h.id, - // dns.h.qdcount); - //} + else + { + printf("[dns query] [id:%d, qdcount: %d]\n", + dns.h.id, + dns.h.qdcount); + } // Parsing Question Section for (int i = 0; i < dns.h.qdcount; i++) diff --git a/bin/dns_hog.cc b/bin/dns_hog.cc index bb224ed..06b8df7 100644 --- a/bin/dns_hog.cc +++ b/bin/dns_hog.cc @@ -21,6 +21,11 @@ #include #include + +#define TXT_FG_RED(str) "\e[31m" str "\e[0m" +#define TXT_FG_GREEN(str) "\e[32m" str "\e[0m" +#define TXT_FG_ORANGE(str) "\e[33m" str "\e[0m" +#define TXT_FG_TEAL(str) "\e[34m" str "\e[0m" #define TXT_FG_PURPLE(str) "\e[35m" str "\e[0m" #include "daq_print.h" @@ -409,13 +414,13 @@ class DataPlaneWorker char const* str_from_verdict(DAQ_Verdict const& verdict) { if (verdict == DAQ_VERDICT_PASS) - return "\e[34m" "pass" "\e[0m"; + return TXT_FG_TEAL("pass"); if (verdict == DAQ_VERDICT_BLOCK) - return "\e[31m" "block" "\e[0m"; + return TXT_FG_RED("block"); if (verdict == DAQ_VERDICT_WHITELIST) - return "\e[32m" "allowlist" "\e[0m"; + return TXT_FG_GREEN("allowlist"); if (verdict == DAQ_VERDICT_BLACKLIST) - return "\e[35m" "blocklist" "\e[0m"; + return TXT_FG_PURPLE("blocklist"); if (verdict == DAQ_VERDICT_IGNORE) return "ignore"; return ""; @@ -439,7 +444,7 @@ class DataPlaneWorker } verdicts.verdicts[i] = verdict; - printf(matched ? "[" TXT_FG_PURPLE("match") "] " : ""); + //printf(matched ? "[" TXT_FG_PURPLE("match") "] " : ""); printf("[%s] ", str_from_verdict(verdict)); print_packet(id, hdr, data, hdr->pktlen); }