diff options
| author | srdusr <[email protected]> | 2024-05-17 19:54:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-05-17 19:54:00 +0200 |
| commit | e0f4c701028aa81026a17cf9ebfb36112184f4bc (patch) | |
| tree | 31c05e4ccbba0dd2ab4c0567630275ebfc6cd264 /tests/test_checksum.cpp | |
| parent | 08332a4195956611db80a2cfe3710d760cbd6acf (diff) | |
| download | packeteer-e0f4c701028aa81026a17cf9ebfb36112184f4bc.tar.gz packeteer-e0f4c701028aa81026a17cf9ebfb36112184f4bc.zip | |
Add privilege dropping, AF_PACKET demo, ICMP, checksum validation, --help, and TCP reassembly
Rounds out the build order in PLAN.md with six incremental additions:
drop root privileges immediately after opening the capture handle;
a standalone AF_PACKET/mmap ring-buffer demo (kept separate from
CaptureSession, see its header comment for why); ICMPv4/ICMPv6 type
and code decoding; opt-in IPv4/TCP/UDP checksum validation (-c);
CLI --help; and opt-in, in-order-only TCP stream reassembly (-a) so
HTTP requests/responses split across segments can be seen whole.
Each addition is unit-tested and, where it touches live traffic
behavior, verified against real captured packets - see PLAN.md's
Decisions section for the verification notes on each.
Diffstat (limited to 'tests/test_checksum.cpp')
| -rw-r--r-- | tests/test_checksum.cpp | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/tests/test_checksum.cpp b/tests/test_checksum.cpp new file mode 100644 index 0000000..1295499 --- /dev/null +++ b/tests/test_checksum.cpp @@ -0,0 +1,136 @@ +#include <doctest/doctest.h> + +#include <vector> + +#include "wireframe/net/checksum.hpp" + +using namespace wireframe::net; + +namespace { + +// Mirrors checksum.hpp's own detail::build_ipv4_pseudo_header, kept +// separate here deliberately: constructing expected test vectors using +// the exact same private helper the code under test uses would make +// these tests circular. A few duplicated lines of test-only setup is +// the honest cost of testing independently. +std::vector<unsigned char> pseudo_header(const std::array<unsigned char, 4>& src, + const std::array<unsigned char, 4>& dst, + unsigned char protocol, + std::span<const unsigned char> segment) { + std::vector<unsigned char> buf; + buf.insert(buf.end(), src.begin(), src.end()); + buf.insert(buf.end(), dst.begin(), dst.end()); + buf.push_back(0); + buf.push_back(protocol); + std::uint16_t len = static_cast<std::uint16_t>(segment.size()); + buf.push_back(static_cast<unsigned char>(len >> 8)); + buf.push_back(static_cast<unsigned char>(len & 0xFF)); + buf.insert(buf.end(), segment.begin(), segment.end()); + return buf; +} + +} // namespace + +TEST_CASE("internet_checksum matches RFC 1071's own worked example") { + // The RFC's example data (0001 f203 f4f5 f6f7) computes to checksum + // 220d - an external reference, not derived from this code. + std::vector<unsigned char> data = {0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7}; + CHECK(internet_checksum(data) == 0x220d); +} + +TEST_CASE("internet_checksum of data with its own valid checksum appended is zero") { + // Direct consequence of the RFC 1071 example: appending that + // checksum as one more word should sum to all-ones, complementing + // to exactly zero - this is the actual verification technique + // verify_ipv4_checksum() etc. rely on. + std::vector<unsigned char> data = {0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7, 0x22, 0x0d}; + CHECK(internet_checksum(data) == 0); +} + +TEST_CASE("internet_checksum handles an odd-length buffer (trailing byte padded high)") { + std::vector<unsigned char> data = {0x00, 0x01, 0xf2}; // 3 bytes: one word + one odd byte + // 0x0001 + 0xf200 (odd byte in the high half) = 0xf201; ~0xf201 = 0x0dfe + CHECK(internet_checksum(data) == 0x0dfe); +} + +TEST_CASE("verify_ipv4_checksum accepts a header with a correctly computed checksum") { + std::vector<unsigned char> header(20, 0); + header[0] = 0x45; + header[8] = 64; // ttl + header[9] = kProtoTcp; + header[12] = 10; header[13] = 0; header[14] = 0; header[15] = 1; + header[16] = 10; header[17] = 0; header[18] = 0; header[19] = 2; + // checksum field (bytes 10-11) computed with itself still zeroed + std::uint16_t csum = internet_checksum(header); + header[10] = static_cast<unsigned char>(csum >> 8); + header[11] = static_cast<unsigned char>(csum & 0xFF); + + CHECK(verify_ipv4_checksum(header)); +} + +TEST_CASE("verify_ipv4_checksum rejects a header corrupted after the checksum was computed") { + std::vector<unsigned char> header(20, 0); + header[0] = 0x45; + header[9] = kProtoTcp; + std::uint16_t csum = internet_checksum(header); + header[10] = static_cast<unsigned char>(csum >> 8); + header[11] = static_cast<unsigned char>(csum & 0xFF); + + header[15] ^= 0xFF; // flip a source-address byte after the fact + CHECK_FALSE(verify_ipv4_checksum(header)); +} + +TEST_CASE("verify_tcp_checksum_ipv4 accepts a segment with a correctly computed checksum") { + std::array<unsigned char, 4> src = {10, 0, 0, 1}; + std::array<unsigned char, 4> dst = {10, 0, 0, 2}; + + std::vector<unsigned char> tcp(20, 0); + tcp[0] = 0; tcp[1] = 80; // src port + tcp[2] = 0x01; tcp[3] = 0xbb; // dst port 443 + tcp[12] = 5 << 4; // data_offset = 5 + + auto buf = pseudo_header(src, dst, kProtoTcp, tcp); + std::uint16_t csum = internet_checksum(buf); + tcp[16] = static_cast<unsigned char>(csum >> 8); + tcp[17] = static_cast<unsigned char>(csum & 0xFF); + + CHECK(verify_tcp_checksum_ipv4({src}, {dst}, tcp) == ChecksumResult::kValid); +} + +TEST_CASE("verify_tcp_checksum_ipv4 rejects a segment corrupted after the checksum was computed") { + std::array<unsigned char, 4> src = {10, 0, 0, 1}; + std::array<unsigned char, 4> dst = {10, 0, 0, 2}; + + std::vector<unsigned char> tcp(20, 0); + tcp[12] = 5 << 4; + auto buf = pseudo_header(src, dst, kProtoTcp, tcp); + std::uint16_t csum = internet_checksum(buf); + tcp[16] = static_cast<unsigned char>(csum >> 8); + tcp[17] = static_cast<unsigned char>(csum & 0xFF); + + tcp[0] ^= 0xFF; // corrupt the source port after the fact + CHECK(verify_tcp_checksum_ipv4({src}, {dst}, tcp) == ChecksumResult::kInvalid); +} + +TEST_CASE("verify_udp_checksum_ipv4 treats a transmitted checksum of 0x0000 as not present") { + std::array<unsigned char, 4> src = {10, 0, 0, 1}; + std::array<unsigned char, 4> dst = {10, 0, 0, 2}; + std::vector<unsigned char> udp = {0x00, 0x35, 0x00, 0x35, 0x00, 0x08, 0x00, 0x00}; // csum=0 + CHECK(verify_udp_checksum_ipv4({src}, {dst}, udp) == ChecksumResult::kNotPresent); +} + +TEST_CASE("verify_udp_checksum_ipv4 accepts a datagram with a correctly computed checksum") { + std::array<unsigned char, 4> src = {10, 0, 0, 1}; + std::array<unsigned char, 4> dst = {10, 0, 0, 2}; + + std::vector<unsigned char> udp = {0x00, 0x35, 0x00, 0x35, 0x00, 0x08, 0x00, 0x00}; + auto buf = pseudo_header(src, dst, kProtoUdp, udp); + std::uint16_t csum = internet_checksum(buf); + // A computed checksum of exactly 0 is itself sent as 0xFFFF per + // RFC 768, to keep it distinguishable from "no checksum" - not + // exercised by this test's specific values, but worth the note. + udp[6] = static_cast<unsigned char>(csum >> 8); + udp[7] = static_cast<unsigned char>(csum & 0xFF); + + CHECK(verify_udp_checksum_ipv4({src}, {dst}, udp) == ChecksumResult::kValid); +} |