srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_dns.cpp
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-05-14 01:42:00 +0200
committersrdusr <[email protected]>2024-05-14 01:42:00 +0200
commit08332a4195956611db80a2cfe3710d760cbd6acf (patch)
tree0cb5cdf9fdcfdd8dc8c129a33575ad9b182d5c01 /tests/test_dns.cpp
downloadpacketeer-08332a4195956611db80a2cfe3710d760cbd6acf.tar.gz
packeteer-08332a4195956611db80a2cfe3710d760cbd6acf.zip
Initial commit: wireframe packet capture/analysis tool
Terminal packet capture and analysis tool built to learn the C++ memory model (byte layout, alignment, endianness, std::span over unowned buffers) via a real capture pipeline. - Hand-rolled L2-L4 decoders (Ethernet, IPv4, IPv6 with extension header walking, TCP, UDP) over std::span, no struct-casting - L7 dissector interface with DNS, HTTP, and TLS SNI implementations - pcapng read/write for Wireshark-compatible capture files - Bounded capture queue: drop-on-backpressure for live capture, blocking push for faithful file replay - Kernel-level BPF filtering (-f) and a separate display-only search (-g / interactive) that doesn't touch what's captured - Replay mode (-r) reads a saved pcapng file back through the same pipeline as live capture, no root or live device needed - pcap_stats() surfaces kernel/interface drops invisible to the capture queue's own counter - Three frontends sharing one CaptureSession setup path: CLI, TUI (FTXUI, primary), GUI (Dear ImGui + SDL3, secondary) - 89 unit tests (doctest) plus 9 libFuzzer harnesses covering every hand-rolled parser; fuzzing found and fixed a real OOM in the pcapng reader (unbounded allocation from an untrusted length field)
Diffstat (limited to 'tests/test_dns.cpp')
-rw-r--r--tests/test_dns.cpp82
1 files changed, 82 insertions, 0 deletions
diff --git a/tests/test_dns.cpp b/tests/test_dns.cpp
new file mode 100644
index 0000000..9a389bb
--- /dev/null
+++ b/tests/test_dns.cpp
@@ -0,0 +1,82 @@
+#include <doctest/doctest.h>
+
+#include <vector>
+
+#include "wireframe/l7/dns.hpp"
+
+using namespace wireframe::net;
+
+namespace {
+
+// "example.com" A query, id=0x129d - the same shape as the real query
+// captured live over tailscale0 while testing the DNS dissector against
+// tshark (id 0x129d / 4765 matched tshark's independent decode exactly).
+std::vector<unsigned char> example_com_query() {
+ return {
+ 0x12, 0x9d, // id = 4765
+ 0x01, 0x00, // flags: RD=1
+ 0x00, 0x01, // qdcount = 1
+ 0x00, 0x00, // ancount = 0
+ 0x00, 0x00, // nscount = 0
+ 0x00, 0x00, // arcount = 0
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0,
+ 0x00, 0x01, // qtype = A
+ 0x00, 0x01, // qclass = IN
+ };
+}
+
+} // namespace
+
+TEST_CASE("parse_dns decodes a query") {
+ auto msg = parse_dns(example_com_query());
+ REQUIRE(msg.has_value());
+ CHECK(msg->header.id == 4765);
+ CHECK_FALSE(msg->header.is_response);
+ CHECK(msg->header.qdcount == 1);
+ REQUIRE(msg->question.has_value());
+ CHECK(msg->question->name == "example.com");
+ CHECK(msg->question->qtype == 1);
+}
+
+TEST_CASE("parse_dns decodes a response") {
+ std::vector<unsigned char> bytes = {
+ 0x12, 0x9d,
+ 0x81, 0x80, // flags: QR=1 (response), RD=1, RA=1
+ 0x00, 0x01, // qdcount = 1
+ 0x00, 0x02, // ancount = 2
+ 0x00, 0x00,
+ 0x00, 0x00,
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0,
+ 0x00, 0x01, 0x00, 0x01,
+ };
+ auto msg = parse_dns(bytes);
+ REQUIRE(msg.has_value());
+ CHECK(msg->header.is_response);
+ CHECK(msg->header.ancount == 2);
+}
+
+TEST_CASE("parse_dns rejects a truncated header") {
+ std::vector<unsigned char> bytes(5, 0);
+ CHECK_FALSE(parse_dns(bytes).has_value());
+}
+
+TEST_CASE("read_dns_name rejects a compression pointer") {
+ std::vector<unsigned char> bytes = {0xC0, 0x0C}; // pointer: unsupported by design
+ CHECK_FALSE(read_dns_name(bytes, 0).has_value());
+}
+
+TEST_CASE("DnsDissector claims port 53 and its summary matches parse_dns") {
+ DnsDissector dissector;
+ CHECK(dissector.port() == kDnsPort);
+
+ auto summary = dissector.summarize(example_com_query());
+ REQUIRE(summary.has_value());
+ CHECK(summary->substr(0, 9) == "DNS query");
+ CHECK(summary->find("example.com") != std::string::npos);
+}
+
+TEST_CASE("DnsDissector::summarize returns nullopt for a truncated payload") {
+ DnsDissector dissector;
+ std::vector<unsigned char> bytes(5, 0);
+ CHECK_FALSE(dissector.summarize(bytes).has_value());
+}