srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_net.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_net.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_net.cpp')
-rw-r--r--tests/test_net.cpp124
1 files changed, 124 insertions, 0 deletions
diff --git a/tests/test_net.cpp b/tests/test_net.cpp
new file mode 100644
index 0000000..09de9b0
--- /dev/null
+++ b/tests/test_net.cpp
@@ -0,0 +1,124 @@
+#include <doctest/doctest.h>
+
+#include <vector>
+
+#include "wireframe/net/ethernet.hpp"
+#include "wireframe/net/ipv4.hpp"
+#include "wireframe/net/tcp.hpp"
+#include "wireframe/net/udp.hpp"
+
+using namespace wireframe::net;
+
+TEST_CASE("parse_ethernet decodes header fields and leaves the right payload") {
+ std::vector<unsigned char> bytes = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // dst mac
+ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // src mac
+ 0x08, 0x00, // ethertype: IPv4
+ 0xDE, 0xAD, 0xBE, 0xEF, // payload
+ };
+ auto frame = parse_ethernet(bytes);
+ REQUIRE(frame.has_value());
+ CHECK(frame->header.dst.bytes == std::array<unsigned char, 6>{0x11, 0x22, 0x33, 0x44, 0x55, 0x66});
+ CHECK(frame->header.src.bytes == std::array<unsigned char, 6>{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF});
+ CHECK(frame->header.ethertype == kEthertypeIPv4);
+ REQUIRE(frame->payload.size() == 4);
+ CHECK(frame->payload[0] == 0xDE);
+}
+
+TEST_CASE("parse_ethernet rejects a frame shorter than the header") {
+ std::vector<unsigned char> bytes(10, 0); // header is 14 bytes
+ CHECK_FALSE(parse_ethernet(bytes).has_value());
+}
+
+TEST_CASE("parse_ipv4 decodes header fields and leaves the right payload") {
+ std::vector<unsigned char> bytes(20, 0);
+ bytes[0] = 0x45; // version 4, IHL 5 (20-byte header, no options)
+ bytes[2] = 0x00;
+ bytes[3] = 0x28; // total_length = 40
+ bytes[8] = 64; // ttl
+ bytes[9] = kProtoTcp;
+ bytes[12] = 10; bytes[13] = 0; bytes[14] = 0; bytes[15] = 1; // src 10.0.0.1
+ bytes[16] = 10; bytes[17] = 0; bytes[18] = 0; bytes[19] = 2; // dst 10.0.0.2
+ bytes.push_back(0x01);
+ bytes.push_back(0x02);
+
+ auto ip = parse_ipv4(bytes);
+ REQUIRE(ip.has_value());
+ CHECK(ip->header.version == 4);
+ CHECK(ip->header.ihl == 5);
+ CHECK(ip->header.total_length == 40);
+ CHECK(ip->header.ttl == 64);
+ CHECK(ip->header.protocol == kProtoTcp);
+ CHECK(ip->header.src.bytes == std::array<unsigned char, 4>{10, 0, 0, 1});
+ CHECK(ip->header.dst.bytes == std::array<unsigned char, 4>{10, 0, 0, 2});
+ REQUIRE(ip->payload.size() == 2);
+ CHECK(ip->payload[0] == 0x01);
+}
+
+TEST_CASE("parse_ipv4 rejects a non-IPv4 version") {
+ std::vector<unsigned char> bytes(20, 0);
+ bytes[0] = 0x65; // version 6
+ CHECK_FALSE(parse_ipv4(bytes).has_value());
+}
+
+TEST_CASE("parse_ipv4 rejects a buffer shorter than the header") {
+ std::vector<unsigned char> bytes(10, 0);
+ CHECK_FALSE(parse_ipv4(bytes).has_value());
+}
+
+TEST_CASE("parse_ipv4 honors IHL > 5 (options present)") {
+ std::vector<unsigned char> bytes(24, 0); // IHL=6 -> 24-byte header
+ bytes[0] = 0x46;
+ bytes[9] = kProtoUdp;
+
+ auto ip = parse_ipv4(bytes);
+ REQUIRE(ip.has_value());
+ CHECK(ip->header.ihl == 6);
+ CHECK(ip->payload.empty());
+}
+
+TEST_CASE("parse_tcp decodes header fields and flags") {
+ std::vector<unsigned char> bytes(20, 0);
+ bytes[0] = 0x00; bytes[1] = 0x50; // src port 80
+ bytes[2] = 0x1F; bytes[3] = 0x90; // dst port 8080
+ bytes[4] = 0; bytes[5] = 0; bytes[6] = 0; bytes[7] = 1; // seq = 1
+ bytes[8] = 0; bytes[9] = 0; bytes[10] = 0; bytes[11] = 2; // ack = 2
+ bytes[12] = 5 << 4; // data_offset = 5 (20-byte header, no options)
+ bytes[13] = 0x12; // SYN | ACK
+ bytes[14] = 0xFF; bytes[15] = 0xFF; // window 65535
+
+ auto tcp = parse_tcp(bytes);
+ REQUIRE(tcp.has_value());
+ CHECK(tcp->header.src_port == 80);
+ CHECK(tcp->header.dst_port == 8080);
+ CHECK(tcp->header.seq == 1);
+ CHECK(tcp->header.ack == 2);
+ CHECK(tcp->header.data_offset == 5);
+ CHECK((tcp->header.flags & kTcpSyn) != 0);
+ CHECK((tcp->header.flags & kTcpAck) != 0);
+ CHECK((tcp->header.flags & kTcpFin) == 0);
+ CHECK(tcp->header.window == 65535);
+ CHECK(tcp->payload.empty());
+}
+
+TEST_CASE("parse_tcp rejects a buffer shorter than the header") {
+ std::vector<unsigned char> bytes(10, 0);
+ CHECK_FALSE(parse_tcp(bytes).has_value());
+}
+
+TEST_CASE("parse_udp decodes header fields and leaves the right payload") {
+ std::vector<unsigned char> bytes = {0x00, 0x35, 0x1F, 0x90, 0x00, 0x0A,
+ 0x00, 0x00, 'h', 'i'};
+ auto udp = parse_udp(bytes);
+ REQUIRE(udp.has_value());
+ CHECK(udp->header.src_port == 53);
+ CHECK(udp->header.dst_port == 8080);
+ CHECK(udp->header.length == 10);
+ REQUIRE(udp->payload.size() == 2);
+ CHECK(udp->payload[0] == 'h');
+}
+
+TEST_CASE("parse_udp rejects a buffer shorter than the header") {
+ std::vector<unsigned char> bytes(4, 0);
+ CHECK_FALSE(parse_udp(bytes).has_value());
+}