srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/include/wireframe/summarize.hpp
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/summarize.hpp
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/summarize.hpp')
-rw-r--r--include/wireframe/summarize.hpp275
1 files changed, 0 insertions, 275 deletions
diff --git a/include/wireframe/summarize.hpp b/include/wireframe/summarize.hpp
deleted file mode 100644
index 840ddf9..0000000
--- a/include/wireframe/summarize.hpp
+++ /dev/null
@@ -1,275 +0,0 @@
-#pragma once
-
-#include <cstdio>
-#include <optional>
-#include <span>
-#include <string>
-#include <vector>
-
-#include <pcap.h>
-
-#include "wireframe/l7/dissector.hpp"
-#include "wireframe/l7/dns.hpp"
-#include "wireframe/l7/http.hpp"
-#include "wireframe/l7/mdns.hpp"
-#include "wireframe/l7/ssh.hpp"
-#include "wireframe/l7/tls.hpp"
-#include "wireframe/net/ethernet.hpp"
-#include "wireframe/net/icmp.hpp"
-#include "wireframe/net/ipv4.hpp"
-#include "wireframe/net/ipv6.hpp"
-#include "wireframe/net/tcp.hpp"
-#include "wireframe/net/udp.hpp"
-
-// Packet -> human-readable summary. Shared by every frontend (plain
-// CLI, TUI, GUI) so they can't drift apart on what a given packet
-// decodes to - one source of truth, not three copies to keep in sync.
-namespace wireframe {
-
-inline std::string mac_to_string(const net::MacAddress& mac) {
- char buf[18];
- std::snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", mac.bytes[0], mac.bytes[1],
- mac.bytes[2], mac.bytes[3], mac.bytes[4], mac.bytes[5]);
- return buf;
-}
-
-inline std::string ipv4_to_string(const net::Ipv4Address& ip) {
- char buf[16];
- std::snprintf(buf, sizeof(buf), "%u.%u.%u.%u", ip.bytes[0], ip.bytes[1], ip.bytes[2],
- ip.bytes[3]);
- return buf;
-}
-
-inline std::string tcp_flags_to_string(std::uint8_t flags) {
- using namespace net;
- std::string out;
- if (flags & kTcpSyn) out += 'S';
- if (flags & kTcpAck) out += 'A';
- if (flags & kTcpFin) out += 'F';
- if (flags & kTcpRst) out += 'R';
- if (flags & kTcpPsh) out += 'P';
- if (flags & kTcpUrg) out += 'U';
- return out.empty() ? "-" : out;
-}
-
-// Registered once. DNS (UDP) was the first L7 dissector, proving the
-// interface (wireframe/l7/dissector.hpp) is enough to add a protocol
-// without touching the L2-L4 decode path; HTTP (TCP) is the second,
-// and the first to actually exercise L7Registry's TCP-payload path --
-// DNS alone never did, since it only ever runs over UDP port 53. TLS
-// (also TCP, port 443) covers what HTTP increasingly can't: most web
-// traffic today is encrypted, and SNI is the one piece of a TLS
-// handshake still readable without decrypting anything. mDNS reuses
-// DNS's own parser (same wire format, different port/label) at
-// essentially no extra cost. SSH is the first dissector whose *entire*
-// protocol is one cleartext line before everything else encrypts --
-// unlike TLS's SNI, there's nothing further to ever add here.
-inline const net::L7Registry& l7_registry() {
- static const net::DnsDissector dns_dissector;
- static const net::HttpDissector http_dissector;
- static const net::TlsSniDissector tls_dissector;
- static const net::MdnsDissector mdns_dissector;
- static const net::SshDissector ssh_dissector;
- static const net::L7Registry registry = [] {
- net::L7Registry r;
- r.add(&dns_dissector);
- r.add(&http_dissector);
- r.add(&tls_dissector);
- r.add(&mdns_dissector);
- r.add(&ssh_dissector);
- return r;
- }();
- return registry;
-}
-
-// Tries the destination port first (the common case: a client talking
-// to a well-known server port), then the source port (a server's
-// reply, coming from that same well-known port).
-inline std::optional<std::string> l7_summarize(std::span<const unsigned char> payload,
- std::uint16_t src_port, std::uint16_t dst_port) {
- if (auto summary = l7_registry().dissect(dst_port, payload)) return summary;
- return l7_registry().dissect(src_port, payload);
-}
-
-// IPv4 and IPv6 headers carry different fields (ttl vs. hop_limit,
-// 4-byte vs. 16-byte addresses), but everything above the IP layer --
-// TCP/UDP decode plus the L7 lookup - is identical once normalized to
-// this. Keeping that dispatch in one place means TCP/UDP/L7 formatting
-// can't drift between the two IP versions.
-struct IpInfo {
- const char* label; // "IPv4" or "IPv6"
- std::string src_str;
- std::string dst_str;
- std::uint8_t ttl_or_hop_limit;
- std::uint8_t proto;
- std::span<const unsigned char> payload;
-};
-
-inline std::string summarize_transport_and_above(const IpInfo& info) {
- char ip_buf[160];
- std::snprintf(ip_buf, sizeof(ip_buf), " | %s %s -> %s ttl=%u proto=%u", info.label,
- info.src_str.c_str(), info.dst_str.c_str(), info.ttl_or_hop_limit, info.proto);
- std::string out = ip_buf;
-
- if (info.proto == net::kProtoTcp) {
- if (auto tcp = net::parse_tcp(info.payload)) {
- char tcp_buf[128];
- std::snprintf(tcp_buf, sizeof(tcp_buf), " | TCP %u -> %u [%s] seq=%u ack=%u win=%u",
- tcp->header.src_port, tcp->header.dst_port,
- tcp_flags_to_string(tcp->header.flags).c_str(), tcp->header.seq,
- tcp->header.ack, tcp->header.window);
- out += tcp_buf;
- if (auto l7 = l7_summarize(tcp->payload, tcp->header.src_port, tcp->header.dst_port)) {
- out += " | " + *l7;
- }
- }
- } else if (info.proto == net::kProtoUdp) {
- if (auto udp = net::parse_udp(info.payload)) {
- char udp_buf[64];
- std::snprintf(udp_buf, sizeof(udp_buf), " | UDP %u -> %u len=%u",
- udp->header.src_port, udp->header.dst_port, udp->header.length);
- out += udp_buf;
- if (auto l7 = l7_summarize(udp->payload, udp->header.src_port, udp->header.dst_port)) {
- out += " | " + *l7;
- }
- }
- } else if (info.proto == net::kProtoIcmp) {
- if (auto icmp = net::parse_icmpv4(info.payload)) {
- out += " | ICMP " + net::icmpv4_type_name(icmp->type);
- if (icmp->identifier) {
- out += " id=" + std::to_string(*icmp->identifier) +
- " seq=" + std::to_string(*icmp->sequence);
- }
- }
- } else if (info.proto == net::kNextHeaderIcmpv6) {
- if (auto icmp = net::parse_icmpv6(info.payload)) {
- out += " | ICMPv6 " + net::icmpv6_type_name(icmp->type);
- if (icmp->identifier) {
- out += " id=" + std::to_string(*icmp->identifier) +
- " seq=" + std::to_string(*icmp->sequence);
- }
- } else {
- out += " | ICMPv6"; // truncated: at least say what it is
- }
- }
- return out;
-}
-
-// `datalink` is the interface's actual pcap_datalink() type, not an
-// assumption: tunnel/VPN interfaces (tailscale0, wireguard, plain
-// tun/tap) hand libpcap raw IP with no link-layer header at all
-// (DLT_RAW), unlike a real NIC or even `lo` (both DLT_EN10MB on
-// Linux). Treating raw IP bytes as an Ethernet frame silently produces
-// garbage MACs and ethertypes - verified by actually capturing on
-// tailscale0 before this branch existed.
-//
-// IP version is read from the packet itself (the first nibble), not
-// inferred from ethertype/datalink: DLT_RAW has no ethertype to key
-// off at all, and even on Ethernet this keeps IPv4/IPv6 dispatch in
-// one place.
-inline std::string summarize_packet(std::span<const unsigned char> bytes, int datalink) {
- std::span<const unsigned char> ip_bytes;
- std::string out;
-
- if (datalink == DLT_RAW) {
- out = "RAW";
- ip_bytes = bytes;
- } else {
- auto eth = net::parse_ethernet(bytes);
- if (!eth) {
- char buf[64];
- std::snprintf(buf, sizeof(buf), "[%zu bytes] truncated ethernet frame", bytes.size());
- return buf;
- }
-
- out = "ETH " + mac_to_string(eth->header.src) + " -> " + mac_to_string(eth->header.dst);
- char eth_buf[32];
- std::snprintf(eth_buf, sizeof(eth_buf), " ethertype=0x%04x", eth->header.ethertype);
- out += eth_buf;
-
- if (eth->header.ethertype != net::kEthertypeIPv4 &&
- eth->header.ethertype != net::kEthertypeIPv6) {
- return out;
- }
- ip_bytes = eth->payload;
- }
-
- if (ip_bytes.empty()) {
- out += " | IP (empty payload)";
- return out;
- }
- std::uint8_t version = static_cast<std::uint8_t>(ip_bytes[0] >> 4);
-
- if (version == 4) {
- auto ip = net::parse_ipv4(ip_bytes);
- if (!ip) {
- out += " | IPv4 (truncated)";
- return out;
- }
- out += summarize_transport_and_above({"IPv4", ipv4_to_string(ip->header.src),
- ipv4_to_string(ip->header.dst), ip->header.ttl,
- ip->header.protocol, ip->payload});
- } else if (version == 6) {
- auto ip6 = net::parse_ipv6(ip_bytes);
- if (!ip6) {
- out += " | IPv6 (truncated)";
- return out;
- }
- std::string src_str = net::ipv6_to_string(ip6->header.src);
- std::string dst_str = net::ipv6_to_string(ip6->header.dst);
-
- // next_header may name an extension header (Hop-by-Hop,
- // Routing, Dest Options, Fragment, AH) rather than the actual
- // transport protocol; walk through those to find it.
- auto walked = net::walk_ipv6_extension_headers(ip6->header.next_header, ip6->payload);
- if (walked.stopped_at_esp) {
- char buf[160];
- std::snprintf(buf, sizeof(buf), " | IPv6 %s -> %s ttl=%u proto=%u | ESP (encrypted)",
- src_str.c_str(), dst_str.c_str(), ip6->header.hop_limit,
- net::kNextHeaderEsp);
- out += buf;
- } else {
- out += summarize_transport_and_above({"IPv6", src_str, dst_str, ip6->header.hop_limit,
- walked.final_next_header, walked.payload});
- }
- } else {
- out += " | IP version " + std::to_string(version) + " (unsupported)";
- }
- return out;
-}
-
-// One formatted line per 16 bytes: offset, hex, ASCII gutter. Returned
-// as lines rather than printed so both the CLI's -x output and a GUI
-// details pane can use the same formatting.
-inline std::vector<std::string> hex_dump_lines(std::span<const unsigned char> bytes) {
- std::vector<std::string> lines;
- for (std::size_t offset = 0; offset < bytes.size(); offset += 16) {
- char offset_buf[32];
- std::snprintf(offset_buf, sizeof(offset_buf), "%06zx ", offset);
- std::string line = offset_buf;
-
- std::size_t line_len = std::min<std::size_t>(16, bytes.size() - offset);
- for (std::size_t i = 0; i < 16; ++i) {
- if (i < line_len) {
- char byte_buf[4];
- std::snprintf(byte_buf, sizeof(byte_buf), "%02x ", bytes[offset + i]);
- line += byte_buf;
- } else {
- line += " ";
- }
- if (i == 7) line += ' ';
- }
-
- line += " |";
- for (std::size_t i = 0; i < line_len; ++i) {
- unsigned char c = bytes[offset + i];
- line += (c >= 0x20 && c < 0x7f) ? static_cast<char>(c) : '.';
- }
- line += '|';
-
- lines.push_back(std::move(line));
- }
- return lines;
-}
-
-} // namespace wireframe