diff options
| author | srdusr <[email protected]> | 2024-05-14 01:42:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-05-14 01:42:00 +0200 |
| commit | 08332a4195956611db80a2cfe3710d760cbd6acf (patch) | |
| tree | 0cb5cdf9fdcfdd8dc8c129a33575ad9b182d5c01 /tests/test_dns.cpp | |
| download | packeteer-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.cpp | 82 |
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()); +} |