#pragma once #include #include #include #include #include #include // Minimal pcapng reader, paired with writer.hpp: reads Enhanced Packet // Blocks sequentially, skipping the Section Header Block, Interface // Description Block, and any other block type transparently. // // Assumes little-endian block encoding (checked against the Section // Header Block's byte-order magic, not just assumed) since that's what // writer.hpp emits and what pcapng writers on this class of hardware // (tcpdump, dumpcap) produce. A big-endian file is out of scope - this // pairs with our own writer, not general pcapng interop. namespace wireframe::pcapng { struct PacketRecord { std::uint32_t interface_id; std::uint64_t timestamp_us; std::uint32_t original_len; std::vector data; }; class Reader { public: explicit Reader(std::FILE* file) : file_(file) {} // Returns the next packet, or nullopt once the file is exhausted or // a malformed/unsupported block is hit - treated as end of stream // rather than a hard error, to keep this reader small. std::optional next_packet() { for (;;) { std::array field{}; if (std::fread(field.data(), 1, 4, file_) != 4) return std::nullopt; std::uint32_t type = get_u32(field); if (std::fread(field.data(), 1, 4, file_) != 4) return std::nullopt; std::uint32_t total_len = get_u32(field); if (total_len < 12) return std::nullopt; std::size_t body_len = total_len - 12; // total_len is an untrusted 32-bit value straight from the // file; without a cap, a corrupted/hostile file can claim // a multi-gigabyte block and OOM the process on the // allocation below before a single byte is even read to // check whether the file actually contains that much data // (found by fuzzing fuzz_pcapng_reader.cpp - real crash, // not theoretical). Bounded well above any block our own // writer produces (packets capped at a 65535 snaplen; this // reader is explicitly scoped to pair with that writer, // not arbitrary pcapng interop). if (body_len > kMaxBlockBodyLen) return std::nullopt; std::vector body(body_len); if (body_len > 0 && std::fread(body.data(), 1, body_len, file_) != body_len) { return std::nullopt; } if (std::fread(field.data(), 1, 4, file_) != 4) return std::nullopt; if (get_u32(field) != total_len) return std::nullopt; // corrupt trailer if (type == kBlockTypeShb) { if (body_len < 4 || get_u32({body.data(), 4}) != kByteOrderMagic) { return std::nullopt; // not little-endian, or malformed } continue; } if (type == kBlockTypeIdb) { // LinkType is the first 2 bytes of the IDB body (see // writer.hpp's write_interface_description). Only the // first IDB is captured - correct for a file our own // writer produced, which only ever writes one // interface, matching this reader's documented scope. if (!link_type_ && body_len >= 2) { link_type_ = static_cast(body[0] | (body[1] << 8)); } continue; } if (type != kBlockTypeEpb) continue; // anything else: skip if (body_len < 20) return std::nullopt; PacketRecord record; record.interface_id = get_u32({body.data() + 0, 4}); std::uint32_t ts_high = get_u32({body.data() + 4, 4}); std::uint32_t ts_low = get_u32({body.data() + 8, 4}); record.timestamp_us = (static_cast(ts_high) << 32) | ts_low; std::uint32_t caplen = get_u32({body.data() + 12, 4}); record.original_len = get_u32({body.data() + 16, 4}); if (body_len < 20 + caplen) return std::nullopt; record.data.assign(body.begin() + 20, body.begin() + 20 + caplen); return record; } } // The interface's link type, learned from the Interface // Description Block once next_packet() has read past it (which // happens before it ever returns the first EPB, so this is // populated by the time the first successful next_packet() call // returns). nullopt if no IDB has been seen yet. std::optional link_type() const { return link_type_; } private: static std::uint32_t get_u32(std::span b) { return static_cast(b[0]) | (static_cast(b[1]) << 8) | (static_cast(b[2]) << 16) | (static_cast(b[3]) << 24); } static constexpr std::uint32_t kBlockTypeShb = 0x0A0D0D0A; static constexpr std::uint32_t kBlockTypeIdb = 0x00000001; static constexpr std::uint32_t kBlockTypeEpb = 0x00000006; static constexpr std::uint32_t kByteOrderMagic = 0x1A2B3C4D; static constexpr std::size_t kMaxBlockBodyLen = 1 << 20; // 1 MiB std::FILE* file_; std::optional link_type_; }; } // namespace wireframe::pcapng