srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/include/wireframe/net
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-05-27 22:00:00 +0200
committersrdusr <[email protected]>2024-05-27 22:00:00 +0200
commitb565d7d9c47ca1ec5af0effd828431ee96027d60 (patch)
treefbe0c9c897e78f507443507354d5b1d8fb851099 /include/wireframe/net
parentfbedc55d5aa861c381701c9f913b34ee7ab57ec4 (diff)
downloadpacketeer-b565d7d9c47ca1ec5af0effd828431ee96027d60.tar.gz
packeteer-b565d7d9c47ca1ec5af0effd828431ee96027d60.zip
Rename project from wireframe to packeteer
Decided on the name after weighing alternatives in NAMES.md: packeteer (packet + -eer, "one who wields packets") fit the project's actual scope better than the wire/frame pun once it had grown into full L2-L7 dissection, reassembly, checksums, privilege dropping, and dual TUI/GUI frontends. No existing packet-capture project uses the name; the one real-world collision (Packeteer, Inc., a networking company acquired and folded into Blue Coat/Symantec by 2008) is long defunct. Mechanical rename throughout: CMake project/target names, the wireframe:: namespace and include/wireframe/ directory (git mv, history preserved), every #include path, CLI/GUI help text, and the project's own working directory. NAMES.md rewritten to record the decision instead of leaving stale self-referential etymology behind from the blind rename pass. Verified after every step: full rebuild (all four targets, no warnings) and the full test suite (128/128 cases, 366/366 assertions) both from a fresh reconfigure and again after the directory move.
Diffstat (limited to 'include/wireframe/net')
-rw-r--r--include/wireframe/net/checksum.hpp91
-rw-r--r--include/wireframe/net/ethernet.hpp44
-rw-r--r--include/wireframe/net/icmp.hpp84
-rw-r--r--include/wireframe/net/ipv4.hpp56
-rw-r--r--include/wireframe/net/ipv6.hpp173
-rw-r--r--include/wireframe/net/tcp.hpp54
-rw-r--r--include/wireframe/net/tcp_reassembly.hpp125
-rw-r--r--include/wireframe/net/udp.hpp35
8 files changed, 0 insertions, 662 deletions
diff --git a/include/wireframe/net/checksum.hpp b/include/wireframe/net/checksum.hpp
deleted file mode 100644
index 97e5254..0000000
--- a/include/wireframe/net/checksum.hpp
+++ /dev/null
@@ -1,91 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <span>
-#include <vector>
-
-#include "wireframe/net/ipv4.hpp"
-
-// RFC 1071 Internet checksum, and the IPv4/TCP/UDP verification built
-// on it. Not wired into summarize_packet(): on loopback, and for many
-// packets captured right as they leave the local machine, the
-// transmitted checksum is legitimately 0x0000 or garbage - modern
-// NICs compute it in hardware ("checksum offload") only once the frame
-// actually reaches them, which is *after* most capture points see it.
-// Flagging that as "BAD" by default would be noise, not signal, on
-// exactly the interfaces this project has been tested against all
-// session (lo, tailscale0). Wireshark makes this opt-in for the same
-// reason; so does this (CLI's -c flag calls these directly).
-namespace wireframe::net {
-
-// One's-complement sum of 16-bit big-endian words, folded back into 16
-// bits, then complemented. Used identically by IPv4's header checksum
-// and, over a pseudo-header + segment instead of a plain header, by
-// TCP/UDP.
-inline std::uint16_t internet_checksum(std::span<const unsigned char> data) {
- std::uint32_t sum = 0;
- std::size_t i = 0;
- for (; i + 1 < data.size(); i += 2) {
- sum += (static_cast<std::uint32_t>(data[i]) << 8) | data[i + 1];
- }
- if (i < data.size()) {
- sum += static_cast<std::uint32_t>(data[i]) << 8; // odd trailing byte: high half only
- }
- while (sum >> 16) {
- sum = (sum & 0xFFFFu) + (sum >> 16);
- }
- return static_cast<std::uint16_t>(~sum & 0xFFFFu);
-}
-
-// `header_bytes` must be exactly the IPv4 header as it appeared on the
-// wire (IHL*4 bytes, options included, checksum field included as its
-// real transmitted value - not zeroed). Summing a header that already
-// contains its own valid checksum comes out to exactly 0; that's the
-// verification, no need for a mutable copy with the field zeroed out.
-inline bool verify_ipv4_checksum(std::span<const unsigned char> header_bytes) {
- return internet_checksum(header_bytes) == 0;
-}
-
-enum class ChecksumResult { kValid, kInvalid, kNotPresent };
-
-namespace detail {
-
-inline std::vector<unsigned char> build_ipv4_pseudo_header(const Ipv4Address& src,
- const Ipv4Address& dst,
- std::uint8_t protocol,
- std::span<const unsigned char> segment) {
- std::vector<unsigned char> buf;
- buf.reserve(12 + segment.size());
- buf.insert(buf.end(), src.bytes.begin(), src.bytes.end());
- buf.insert(buf.end(), dst.bytes.begin(), dst.bytes.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 detail
-
-// TCP's checksum is mandatory - always kValid or kInvalid.
-inline ChecksumResult verify_tcp_checksum_ipv4(const Ipv4Address& src, const Ipv4Address& dst,
- std::span<const unsigned char> tcp_segment) {
- auto buf = detail::build_ipv4_pseudo_header(src, dst, kProtoTcp, tcp_segment);
- return internet_checksum(buf) == 0 ? ChecksumResult::kValid : ChecksumResult::kInvalid;
-}
-
-// UDP's checksum is optional over IPv4 (RFC 768): a transmitted value
-// of exactly 0x0000 means "no checksum was computed", not "checksum is
-// zero" - that's kNotPresent, not a failure.
-inline ChecksumResult verify_udp_checksum_ipv4(const Ipv4Address& src, const Ipv4Address& dst,
- std::span<const unsigned char> udp_datagram) {
- if (udp_datagram.size() >= 8 && udp_datagram[6] == 0 && udp_datagram[7] == 0) {
- return ChecksumResult::kNotPresent;
- }
- auto buf = detail::build_ipv4_pseudo_header(src, dst, kProtoUdp, udp_datagram);
- return internet_checksum(buf) == 0 ? ChecksumResult::kValid : ChecksumResult::kInvalid;
-}
-
-} // namespace wireframe::net
diff --git a/include/wireframe/net/ethernet.hpp b/include/wireframe/net/ethernet.hpp
deleted file mode 100644
index 2da4cc8..0000000
--- a/include/wireframe/net/ethernet.hpp
+++ /dev/null
@@ -1,44 +0,0 @@
-#pragma once
-
-#include <algorithm>
-#include <array>
-#include <cstdint>
-#include <optional>
-#include <span>
-
-#include "wireframe/byteio.hpp"
-
-namespace wireframe::net {
-
-inline constexpr std::size_t kEthernetHeaderLen = 14;
-inline constexpr std::uint16_t kEthertypeIPv4 = 0x0800;
-inline constexpr std::uint16_t kEthertypeIPv6 = 0x86DD;
-inline constexpr std::uint16_t kEthertypeArp = 0x0806;
-
-struct MacAddress {
- std::array<unsigned char, 6> bytes;
-};
-
-struct EthernetHeader {
- MacAddress dst;
- MacAddress src;
- std::uint16_t ethertype;
-};
-
-struct EthernetFrame {
- EthernetHeader header;
- std::span<const unsigned char> payload;
-};
-
-inline std::optional<EthernetFrame> parse_ethernet(std::span<const unsigned char> bytes) {
- if (bytes.size() < kEthernetHeaderLen) return std::nullopt;
-
- EthernetHeader header{};
- std::copy_n(bytes.begin(), 6, header.dst.bytes.begin());
- std::copy_n(bytes.begin() + 6, 6, header.src.bytes.begin());
- header.ethertype = read_be16(bytes, 12);
-
- return EthernetFrame{header, bytes.subspan(kEthernetHeaderLen)};
-}
-
-} // namespace wireframe::net
diff --git a/include/wireframe/net/icmp.hpp b/include/wireframe/net/icmp.hpp
deleted file mode 100644
index af83916..0000000
--- a/include/wireframe/net/icmp.hpp
+++ /dev/null
@@ -1,84 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <optional>
-#include <span>
-#include <string>
-
-#include "wireframe/byteio.hpp"
-
-// ICMPv4 (RFC 792) and ICMPv6 (RFC 4443) share the same first-4-byte
-// shape (Type, Code, Checksum) but a completely different type
-// namespace - the same numeric type means something different in each
-// - so they get separate parse functions and separate type-name
-// tables, sharing only the header struct shape. Neither protocol has
-// ports, so this doesn't fit L7Registry's port-keyed dispatch at all;
-// it's handled directly by protocol number in summarize.hpp instead.
-namespace wireframe::net {
-
-struct IcmpHeader {
- std::uint8_t type;
- std::uint8_t code;
- std::optional<std::uint16_t> identifier; // echo request/reply only
- std::optional<std::uint16_t> sequence; // echo request/reply only
-};
-
-inline std::optional<IcmpHeader> parse_icmpv4(std::span<const unsigned char> bytes) {
- if (bytes.size() < 4) return std::nullopt;
-
- IcmpHeader header{};
- header.type = bytes[0];
- header.code = bytes[1];
- if ((header.type == 8 || header.type == 0) && bytes.size() >= 8) { // echo request/reply
- header.identifier = read_be16(bytes, 4);
- header.sequence = read_be16(bytes, 6);
- }
- return header;
-}
-
-inline std::string icmpv4_type_name(std::uint8_t type) {
- switch (type) {
- case 0: return "Echo Reply";
- case 3: return "Destination Unreachable";
- case 4: return "Source Quench";
- case 5: return "Redirect";
- case 8: return "Echo Request";
- case 11: return "Time Exceeded";
- case 12: return "Parameter Problem";
- case 13: return "Timestamp Request";
- case 14: return "Timestamp Reply";
- default: return "type=" + std::to_string(type);
- }
-}
-
-inline std::optional<IcmpHeader> parse_icmpv6(std::span<const unsigned char> bytes) {
- if (bytes.size() < 4) return std::nullopt;
-
- IcmpHeader header{};
- header.type = bytes[0];
- header.code = bytes[1];
- if ((header.type == 128 || header.type == 129) && bytes.size() >= 8) { // echo request/reply
- header.identifier = read_be16(bytes, 4);
- header.sequence = read_be16(bytes, 6);
- }
- return header;
-}
-
-inline std::string icmpv6_type_name(std::uint8_t type) {
- switch (type) {
- case 1: return "Destination Unreachable";
- case 2: return "Packet Too Big";
- case 3: return "Time Exceeded";
- case 4: return "Parameter Problem";
- case 128: return "Echo Request";
- case 129: return "Echo Reply";
- case 133: return "Router Solicitation";
- case 134: return "Router Advertisement";
- case 135: return "Neighbor Solicitation";
- case 136: return "Neighbor Advertisement";
- case 137: return "Redirect";
- default: return "type=" + std::to_string(type);
- }
-}
-
-} // namespace wireframe::net
diff --git a/include/wireframe/net/ipv4.hpp b/include/wireframe/net/ipv4.hpp
deleted file mode 100644
index f53b4f2..0000000
--- a/include/wireframe/net/ipv4.hpp
+++ /dev/null
@@ -1,56 +0,0 @@
-#pragma once
-
-#include <algorithm>
-#include <array>
-#include <cstdint>
-#include <optional>
-#include <span>
-
-#include "wireframe/byteio.hpp"
-
-namespace wireframe::net {
-
-inline constexpr std::uint8_t kProtoIcmp = 1;
-inline constexpr std::uint8_t kProtoTcp = 6;
-inline constexpr std::uint8_t kProtoUdp = 17;
-
-struct Ipv4Address {
- std::array<unsigned char, 4> bytes;
-};
-
-struct Ipv4Header {
- std::uint8_t version;
- std::uint8_t ihl; // header length in 32-bit words
- std::uint16_t total_length;
- std::uint8_t ttl;
- std::uint8_t protocol;
- Ipv4Address src;
- Ipv4Address dst;
-};
-
-struct Ipv4Packet {
- Ipv4Header header;
- std::span<const unsigned char> payload;
-};
-
-inline std::optional<Ipv4Packet> parse_ipv4(std::span<const unsigned char> bytes) {
- if (bytes.size() < 20) return std::nullopt;
-
- std::uint8_t version = static_cast<std::uint8_t>(bytes[0] >> 4);
- std::uint8_t ihl = bytes[0] & 0x0F;
- std::size_t header_len = static_cast<std::size_t>(ihl) * 4;
- if (version != 4 || header_len < 20 || bytes.size() < header_len) return std::nullopt;
-
- Ipv4Header header{};
- header.version = version;
- header.ihl = ihl;
- header.total_length = read_be16(bytes, 2);
- header.ttl = bytes[8];
- header.protocol = bytes[9];
- std::copy_n(bytes.begin() + 12, 4, header.src.bytes.begin());
- std::copy_n(bytes.begin() + 16, 4, header.dst.bytes.begin());
-
- return Ipv4Packet{header, bytes.subspan(header_len)};
-}
-
-} // namespace wireframe::net
diff --git a/include/wireframe/net/ipv6.hpp b/include/wireframe/net/ipv6.hpp
deleted file mode 100644
index 4b6b28a..0000000
--- a/include/wireframe/net/ipv6.hpp
+++ /dev/null
@@ -1,173 +0,0 @@
-#pragma once
-
-#include <algorithm>
-#include <array>
-#include <cstdint>
-#include <cstdio>
-#include <optional>
-#include <span>
-#include <string>
-
-#include "wireframe/byteio.hpp"
-
-namespace wireframe::net {
-
-inline constexpr std::size_t kIpv6HeaderLen = 40;
-inline constexpr std::uint8_t kNextHeaderHopByHop = 0;
-inline constexpr std::uint8_t kNextHeaderRouting = 43;
-inline constexpr std::uint8_t kNextHeaderFragment = 44;
-inline constexpr std::uint8_t kNextHeaderEsp = 50;
-inline constexpr std::uint8_t kNextHeaderAh = 51;
-inline constexpr std::uint8_t kNextHeaderIcmpv6 = 58;
-inline constexpr std::uint8_t kNextHeaderDestOptions = 60;
-
-struct Ipv6Address {
- std::array<unsigned char, 16> bytes;
-};
-
-struct Ipv6Header {
- std::uint8_t version;
- std::uint8_t traffic_class;
- std::uint32_t flow_label;
- std::uint16_t payload_length;
- std::uint8_t next_header; // transport protocol, or an extension header type
- std::uint8_t hop_limit;
- Ipv6Address src;
- Ipv6Address dst;
-};
-
-struct Ipv6Packet {
- Ipv6Header header;
- std::span<const unsigned char> payload;
-};
-
-// Only the fixed 40-byte header is decoded here - header.next_header
-// may name an extension header rather than a transport protocol.
-// walk_ipv6_extension_headers() (below) resolves that; parse_ipv6()
-// itself stays a direct, unconditional decode of exactly the fixed
-// header, nothing more.
-inline std::optional<Ipv6Packet> parse_ipv6(std::span<const unsigned char> bytes) {
- if (bytes.size() < kIpv6HeaderLen) return std::nullopt;
-
- std::uint8_t version = static_cast<std::uint8_t>(bytes[0] >> 4);
- if (version != 6) return std::nullopt;
-
- Ipv6Header header{};
- header.version = version;
- std::uint32_t first_word = read_be32(bytes, 0);
- header.traffic_class = static_cast<std::uint8_t>((first_word >> 20) & 0xFF);
- header.flow_label = first_word & 0x000FFFFF;
- header.payload_length = read_be16(bytes, 4);
- header.next_header = bytes[6];
- header.hop_limit = bytes[7];
- std::copy_n(bytes.begin() + 8, 16, header.src.bytes.begin());
- std::copy_n(bytes.begin() + 24, 16, header.dst.bytes.begin());
-
- return Ipv6Packet{header, bytes.subspan(kIpv6HeaderLen)};
-}
-
-struct Ipv6ExtensionWalkResult {
- std::uint8_t final_next_header; // a transport protocol, or an extension type we stopped at
- std::span<const unsigned char> payload; // bytes after every extension header walked
- bool stopped_at_esp; // true if ESP was hit - see walk_ipv6_extension_headers()
-};
-
-// Walks Hop-by-Hop, Routing, Destination Options, Fragment, and AH
-// extension headers to find the real transport protocol underneath
-// them, so e.g. TCP/UDP wrapped in a Hop-by-Hop options header is still
-// decoded instead of silently stopping at "next_header=0". Each header
-// carries its own length, so this never needs to understand a header
-// type's *meaning* to skip over it correctly - only Hop-by-Hop/
-// Routing/Dest-Options (length in 8-byte units from a trailing byte),
-// Fragment (fixed 8 bytes), and AH (length in 4-byte units, RFC 4302)
-// have different encodings, all handled explicitly below.
-//
-// ESP is a hard stop, not a bug: its own next-header field lives in a
-// trailer *after* the encrypted payload, at an offset this code has no
-// way to know without decrypting first. Reported as stopped_at_esp
-// rather than guessed at.
-//
-// Bounded to a handful of iterations as defense in depth against a
-// hostile/corrupt chain - not strictly needed for termination (every
-// header is at least 8 bytes, so payload.size() strictly decreases
-// each iteration and the loop can't actually run forever), but a
-// pathological chain of many tiny headers would otherwise still cost
-// real work for no legitimate reason.
-inline Ipv6ExtensionWalkResult walk_ipv6_extension_headers(std::uint8_t next_header,
- std::span<const unsigned char> payload) {
- constexpr int kMaxExtensionHeaders = 8;
-
- for (int i = 0; i < kMaxExtensionHeaders; ++i) {
- if (next_header == kNextHeaderEsp) {
- return {next_header, payload, /*stopped_at_esp=*/true};
- }
-
- std::size_t ext_len;
- if (next_header == kNextHeaderFragment) {
- if (payload.size() < 8) return {next_header, payload, false};
- ext_len = 8;
- } else if (next_header == kNextHeaderAh) {
- if (payload.size() < 2) return {next_header, payload, false};
- ext_len = (static_cast<std::size_t>(payload[1]) + 2) * 4;
- } else if (next_header == kNextHeaderHopByHop || next_header == kNextHeaderRouting ||
- next_header == kNextHeaderDestOptions) {
- if (payload.size() < 2) return {next_header, payload, false};
- ext_len = (static_cast<std::size_t>(payload[1]) + 1) * 8;
- } else {
- break; // TCP/UDP/ICMPv6/anything else we don't chain through: stop here
- }
-
- if (payload.size() < ext_len) return {next_header, payload, false}; // truncated: stop
-
- std::uint8_t this_next_header = payload[0];
- payload = payload.subspan(ext_len);
- next_header = this_next_header;
- }
-
- return {next_header, payload, false};
-}
-
-// RFC 5952 canonical text form: lowercase hex, and the longest run of
-// two-or-more consecutive zero groups (leftmost wins a tie) collapsed to
-// "::". A lone zero group is left as "0", not compressed, per 5952 4.2.2.
-inline std::string ipv6_to_string(const Ipv6Address& addr) {
- std::array<std::uint16_t, 8> groups{};
- for (std::size_t i = 0; i < 8; ++i) {
- groups[i] = static_cast<std::uint16_t>((addr.bytes[i * 2] << 8) | addr.bytes[i * 2 + 1]);
- }
-
- int best_start = -1;
- int best_len = 0;
- int cur_start = -1;
- int cur_len = 0;
- for (int i = 0; i < 8; ++i) {
- if (groups[i] == 0) {
- if (cur_start < 0) cur_start = i;
- ++cur_len;
- if (cur_len > best_len) {
- best_start = cur_start;
- best_len = cur_len;
- }
- } else {
- cur_start = -1;
- cur_len = 0;
- }
- }
- if (best_len < 2) best_start = -1; // don't compress a lone zero group
-
- std::string out;
- char buf[6];
- for (int i = 0; i < 8; ++i) {
- if (i == best_start) {
- out += "::";
- i += best_len - 1; // the for-loop's ++i advances past the run
- continue;
- }
- if (!out.empty() && out.back() != ':') out += ':';
- std::snprintf(buf, sizeof(buf), "%x", groups[i]);
- out += buf;
- }
- return out;
-}
-
-} // namespace wireframe::net
diff --git a/include/wireframe/net/tcp.hpp b/include/wireframe/net/tcp.hpp
deleted file mode 100644
index f691a7f..0000000
--- a/include/wireframe/net/tcp.hpp
+++ /dev/null
@@ -1,54 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <optional>
-#include <span>
-
-#include "wireframe/byteio.hpp"
-
-namespace wireframe::net {
-
-// Lower 6 bits of the flags byte: URG ACK PSH RST SYN FIN. CWR/ECE (the
-// top 2 bits) are masked off - not needed for now.
-inline constexpr std::uint8_t kTcpFin = 0x01;
-inline constexpr std::uint8_t kTcpSyn = 0x02;
-inline constexpr std::uint8_t kTcpRst = 0x04;
-inline constexpr std::uint8_t kTcpPsh = 0x08;
-inline constexpr std::uint8_t kTcpAck = 0x10;
-inline constexpr std::uint8_t kTcpUrg = 0x20;
-
-struct TcpHeader {
- std::uint16_t src_port;
- std::uint16_t dst_port;
- std::uint32_t seq;
- std::uint32_t ack;
- std::uint8_t data_offset; // header length in 32-bit words
- std::uint8_t flags;
- std::uint16_t window;
-};
-
-struct TcpSegment {
- TcpHeader header;
- std::span<const unsigned char> payload;
-};
-
-inline std::optional<TcpSegment> parse_tcp(std::span<const unsigned char> bytes) {
- if (bytes.size() < 20) return std::nullopt;
-
- std::uint8_t data_offset = static_cast<std::uint8_t>(bytes[12] >> 4);
- std::size_t header_len = static_cast<std::size_t>(data_offset) * 4;
- if (header_len < 20 || bytes.size() < header_len) return std::nullopt;
-
- TcpHeader header{};
- header.src_port = read_be16(bytes, 0);
- header.dst_port = read_be16(bytes, 2);
- header.seq = read_be32(bytes, 4);
- header.ack = read_be32(bytes, 8);
- header.data_offset = data_offset;
- header.flags = bytes[13] & 0x3F;
- header.window = read_be16(bytes, 14);
-
- return TcpSegment{header, bytes.subspan(header_len)};
-}
-
-} // namespace wireframe::net
diff --git a/include/wireframe/net/tcp_reassembly.hpp b/include/wireframe/net/tcp_reassembly.hpp
deleted file mode 100644
index 90824a4..0000000
--- a/include/wireframe/net/tcp_reassembly.hpp
+++ /dev/null
@@ -1,125 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <map>
-#include <optional>
-#include <span>
-#include <tuple>
-#include <vector>
-
-#include "wireframe/net/ipv4.hpp"
-
-// Minimal, in-order-only TCP stream reassembly: tracks each flow's two
-// directions separately, accumulating payload bytes as segments arrive
-// exactly in sequence order. Out-of-order segments and retransmissions
-// are dropped rather than buffered for later reordering - a real
-// limitation, but a reasonable one for a learning-focused reassembler
-// capturing directly on an endpoint (this project's demonstrated use
-// all session: lo, wlp1s0, tailscale0), where segments mostly do
-// arrive in order. A capture point far from either endpoint (e.g. a
-// middlebox) would need real out-of-order buffering this doesn't do.
-//
-// The point: HTTP's dissector (wireframe/l7/http.hpp) only ever sees
-// one segment at a time, so a request/response split across TCP
-// segments - a Host: header landing in the second packet of a
-// request, say - is invisible to it. Feeding the *reassembled* stream
-// back through the same parse_http() lets it see what single-segment
-// dissection structurally can't.
-namespace wireframe::net {
-
-struct FlowKey {
- Ipv4Address ip_a;
- std::uint16_t port_a;
- Ipv4Address ip_b;
- std::uint16_t port_b;
-
- bool operator<(const FlowKey& other) const {
- return std::tie(ip_a.bytes, port_a, ip_b.bytes, port_b) <
- std::tie(other.ip_a.bytes, other.port_a, other.ip_b.bytes, other.port_b);
- }
-};
-
-// Canonicalizes a (src, dst) pair into a direction-independent
-// FlowKey - both directions of the same connection map to the same
-// key - plus whether this segment's source was the "a" side.
-inline std::pair<FlowKey, bool> canonicalize_flow(const Ipv4Address& src_ip,
- std::uint16_t src_port,
- const Ipv4Address& dst_ip,
- std::uint16_t dst_port) {
- bool src_is_a = std::tie(src_ip.bytes, src_port) < std::tie(dst_ip.bytes, dst_port);
- FlowKey key = src_is_a ? FlowKey{src_ip, src_port, dst_ip, dst_port}
- : FlowKey{dst_ip, dst_port, src_ip, src_port};
- return {key, src_is_a};
-}
-
-struct DirectionState {
- bool syn_seen = false;
- std::uint32_t next_seq = 0;
- std::vector<unsigned char> buffer;
-};
-
-struct FlowState {
- DirectionState a_to_b;
- DirectionState b_to_a;
-};
-
-class TcpReassembler {
-public:
- explicit TcpReassembler(std::size_t max_buffer_per_direction = 65536,
- std::size_t max_flows = 4096)
- : max_buffer_(max_buffer_per_direction), max_flows_(max_flows) {}
-
- // Feeds one TCP segment in. Returns a snapshot of the *sender's*
- // accumulated stream so far if this segment extended it
- // contiguously in order; nullopt if the segment was out of order,
- // a retransmission, a control segment with no payload, or the flow
- // table was full and this would be a brand new flow. Returned by
- // value rather than by reference: the buffer this points at can
- // grow/move on the next call, and bounded copies (max 64 KiB by
- // default) are cheap enough that this isn't worth the lifetime risk.
- std::optional<std::vector<unsigned char>> process_segment(
- const Ipv4Address& src_ip, std::uint16_t src_port, const Ipv4Address& dst_ip,
- std::uint16_t dst_port, std::uint32_t seq, std::uint8_t flags,
- std::span<const unsigned char> payload) {
- auto [key, src_is_a] = canonicalize_flow(src_ip, src_port, dst_ip, dst_port);
-
- auto it = flows_.find(key);
- if (it == flows_.end()) {
- if (flows_.size() >= max_flows_) return std::nullopt; // table full: drop new flows
- it = flows_.emplace(key, FlowState{}).first;
- }
- DirectionState& dir = src_is_a ? it->second.a_to_b : it->second.b_to_a;
-
- constexpr std::uint8_t kSyn = 0x02;
- if (flags & kSyn) {
- dir.syn_seen = true;
- dir.next_seq = seq + 1; // the SYN itself consumes one sequence number
- return std::nullopt;
- }
-
- // seq != dir.next_seq covers both out-of-order segments and
- // retransmissions (a retransmit repeats a seq already below
- // next_seq) - unsigned wraparound makes plain equality correct
- // even across a sequence-number wrap, no need for RFC 1982
- // serial-number comparison for an exact-match check like this.
- if (!dir.syn_seen || payload.empty() || seq != dir.next_seq) {
- return std::nullopt;
- }
-
- if (dir.buffer.size() + payload.size() <= max_buffer_) {
- dir.buffer.insert(dir.buffer.end(), payload.begin(), payload.end());
- }
- dir.next_seq = seq + static_cast<std::uint32_t>(payload.size());
-
- return dir.buffer;
- }
-
- std::size_t flow_count() const { return flows_.size(); }
-
-private:
- std::map<FlowKey, FlowState> flows_;
- std::size_t max_buffer_;
- std::size_t max_flows_;
-};
-
-} // namespace wireframe::net
diff --git a/include/wireframe/net/udp.hpp b/include/wireframe/net/udp.hpp
deleted file mode 100644
index 07664c2..0000000
--- a/include/wireframe/net/udp.hpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <optional>
-#include <span>
-
-#include "wireframe/byteio.hpp"
-
-namespace wireframe::net {
-
-inline constexpr std::size_t kUdpHeaderLen = 8;
-
-struct UdpHeader {
- std::uint16_t src_port;
- std::uint16_t dst_port;
- std::uint16_t length;
-};
-
-struct UdpDatagram {
- UdpHeader header;
- std::span<const unsigned char> payload;
-};
-
-inline std::optional<UdpDatagram> parse_udp(std::span<const unsigned char> bytes) {
- if (bytes.size() < kUdpHeaderLen) return std::nullopt;
-
- UdpHeader header{};
- header.src_port = read_be16(bytes, 0);
- header.dst_port = read_be16(bytes, 2);
- header.length = read_be16(bytes, 4);
-
- return UdpDatagram{header, bytes.subspan(kUdpHeaderLen)};
-}
-
-} // namespace wireframe::net