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_net.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_net.cpp')
| -rw-r--r-- | tests/test_net.cpp | 124 |
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()); +} |