srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-05-21 22:24:00 +0200
committersrdusr <[email protected]>2024-05-21 22:24:00 +0200
commitfbedc55d5aa861c381701c9f913b34ee7ab57ec4 (patch)
treed34c3648b61d417f2a5d8690e0eb4a76bd64c943 /src
parente0f4c701028aa81026a17cf9ebfb36112184f4bc (diff)
downloadpacketeer-fbedc55d5aa861c381701c9f913b34ee7ab57ec4.tar.gz
packeteer-fbedc55d5aa861c381701c9f913b34ee7ab57ec4.zip
Add GUI parity for -c/-a, mDNS/SSH dissectors, and two new fuzz harnesses
GUI parity: checksum_status()/reassembled_http_status() moved out of main.cpp into a shared wireframe/packet_diagnostics.hpp so the GUI can show the same -c/-a diagnostics for the selected packet without duplicating the Ethernet/IPv4/TCP walk. Visually verified under Xvfb with the same split-segment scenario used to verify -a on the CLI. Two new L7 dissectors: mDNS (reuses parse_dns outright - RFC 6762 keeps DNS's wire format, just a different port) and SSH's cleartext identification banner. Live-verified against this machine's real sshd and a real DNS-wire-format packet sent to port 5353. Two new fuzz harnesses (fuzz_checksum, fuzz_tcp_reassembly) covering code added here that the original nine harnesses never touched. All 12 run clean across ~90M executions with no crashes. NAMES.md and PLAN.md updated with this round's decisions and naming candidates.
Diffstat (limited to 'src')
-rw-r--r--src/gui_main.cpp45
-rw-r--r--src/main.cpp105
2 files changed, 49 insertions, 101 deletions
diff --git a/src/gui_main.cpp b/src/gui_main.cpp
index 16ee769..8a7771a 100644
--- a/src/gui_main.cpp
+++ b/src/gui_main.cpp
@@ -23,6 +23,8 @@
#include <thread>
#include "wireframe/capture_session.hpp"
+#include "wireframe/net/tcp_reassembly.hpp"
+#include "wireframe/packet_diagnostics.hpp"
#include "wireframe/search.hpp"
#include "wireframe/summarize.hpp"
@@ -31,6 +33,11 @@ namespace {
struct PacketRow {
std::string summary;
std::vector<unsigned char> data;
+ // -a only: computed once at consume time (reassembly needs
+ // in-order state across packets, unlike checksum status below,
+ // which is stateless and cheap enough to compute lazily when a row
+ // is selected instead of storing it on every row).
+ std::optional<std::string> reassembled_http;
};
constexpr std::size_t kMaxRows = 5000; // cap memory; oldest rows scroll off
@@ -52,7 +59,7 @@ struct SharedState {
};
void consumer_loop(wireframe::CaptureSession& session, wireframe::CaptureQueue& queue,
- SharedState& state) {
+ SharedState& state, wireframe::net::TcpReassembler* reassembler) {
while (auto packet = queue.pop()) {
std::span<const unsigned char> bytes{packet->data};
std::string summary = wireframe::summarize_packet(bytes, session.datalink());
@@ -62,8 +69,15 @@ void consumer_loop(wireframe::CaptureSession& session, wireframe::CaptureQueue&
packet->original_len);
}
+ std::optional<std::string> reassembled_http;
+ if (reassembler) {
+ reassembled_http = wireframe::reassembled_http_status(bytes, session.datalink(),
+ *reassembler);
+ }
+
std::lock_guard<std::mutex> lock(state.mutex);
- state.rows.push_back({std::move(summary), std::move(packet->data)});
+ state.rows.push_back({std::move(summary), std::move(packet->data),
+ std::move(reassembled_http)});
if (state.rows.size() > kMaxRows) state.rows.pop_front();
++state.packet_count;
}
@@ -89,6 +103,11 @@ void print_usage(const char* argv0) {
" -r <file> Replay a saved pcapng file instead of a live device\n"
" -f <expr> Kernel-level capture filter (tcpdump/BPF syntax); also\n"
" applies to what -w writes. Can't be combined with -r.\n"
+ " -c Show IPv4/TCP/UDP checksum validity for the selected packet.\n"
+ " Off by default - see the CLI's -h for why (checksum offload).\n"
+ " -a Reassemble TCP streams and show HTTP requests/responses\n"
+ " joined across segments for the selected packet, when its\n"
+ " segment contributed to one. In-order segments only.\n"
" -h, --help Show this help and exit\n"
"\n"
"Examples:\n"
@@ -109,6 +128,8 @@ int main(int argc, char** argv) {
}
wireframe::CaptureSessionOptions options;
+ bool enable_checksums = false;
+ bool enable_reassembly = false;
for (int i = 1; i < argc; ++i) {
if (std::strcmp(argv[i], "-w") == 0 && i + 1 < argc) {
options.pcapng_output_path = argv[++i];
@@ -116,6 +137,10 @@ int main(int argc, char** argv) {
options.filter_expr = argv[++i];
} else if (std::strcmp(argv[i], "-r") == 0 && i + 1 < argc) {
options.replay_input_path = argv[++i];
+ } else if (std::strcmp(argv[i], "-c") == 0) {
+ enable_checksums = true;
+ } else if (std::strcmp(argv[i], "-a") == 0) {
+ enable_reassembly = true;
} else if (options.device.empty()) {
options.device = argv[i];
}
@@ -158,9 +183,10 @@ int main(int argc, char** argv) {
wireframe::CaptureQueue queue(4096);
SharedState state;
+ wireframe::net::TcpReassembler reassembler;
std::thread capture_thread = session.start_capture_thread(queue);
std::thread consumer_thread(consumer_loop, std::ref(session), std::ref(queue),
- std::ref(state));
+ std::ref(state), enable_reassembly ? &reassembler : nullptr);
int selected_row = -1;
bool quit = false;
@@ -248,7 +274,18 @@ int main(int argc, char** argv) {
{
std::lock_guard<std::mutex> lock(state.mutex);
if (selected_row >= 0 && selected_row < static_cast<int>(state.rows.size())) {
- for (const auto& line : wireframe::hex_dump_lines(state.rows[selected_row].data)) {
+ const auto& row = state.rows[selected_row];
+ if (enable_checksums) {
+ std::string status = wireframe::checksum_status(row.data, session.datalink());
+ if (!status.empty()) {
+ ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "%s", status.c_str());
+ }
+ }
+ if (row.reassembled_http) {
+ ImGui::TextColored(ImVec4(0.6f, 1.0f, 0.6f, 1.0f), "%s",
+ row.reassembled_http->c_str());
+ }
+ for (const auto& line : wireframe::hex_dump_lines(row.data)) {
ImGui::TextUnformatted(line.c_str());
}
} else {
diff --git a/src/main.cpp b/src/main.cpp
index 31fca73..72dd267 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -48,12 +48,8 @@
#include <ftxui/dom/elements.hpp>
#include "wireframe/capture_session.hpp"
-#include "wireframe/l7/http.hpp"
-#include "wireframe/net/checksum.hpp"
-#include "wireframe/net/ethernet.hpp"
-#include "wireframe/net/ipv4.hpp"
-#include "wireframe/net/tcp.hpp"
#include "wireframe/net/tcp_reassembly.hpp"
+#include "wireframe/packet_diagnostics.hpp"
#include "wireframe/search.hpp"
#include "wireframe/summarize.hpp"
@@ -72,95 +68,6 @@ void hex_dump(std::span<const unsigned char> bytes) {
std::printf("\n");
}
-// -c only: checksum validation isn't part of summarize_packet()'s
-// shared output (see wireframe/net/checksum.hpp for why - checksum
-// offload makes it noise, not signal, on most of the interfaces this
-// project has actually been tested against). IPv4 only for now; this
-// does its own minimal walk down to the IP/TCP/UDP byte spans the
-// checksum functions need, reusing the existing decoders rather than
-// duplicating their parsing logic.
-std::string checksum_status(std::span<const unsigned char> bytes, int datalink) {
- std::span<const unsigned char> ip_bytes;
- if (datalink == DLT_RAW) {
- ip_bytes = bytes;
- } else {
- auto eth = wireframe::net::parse_ethernet(bytes);
- if (!eth || eth->header.ethertype != wireframe::net::kEthertypeIPv4) return "";
- ip_bytes = eth->payload;
- }
- if (ip_bytes.empty() || (ip_bytes[0] >> 4) != 4) return ""; // only IPv4 checksums, for now
-
- auto ip = wireframe::net::parse_ipv4(ip_bytes);
- if (!ip) return "";
-
- std::size_t header_len = static_cast<std::size_t>(ip->header.ihl) * 4;
- std::string out = " checksums: IP=";
- out += wireframe::net::verify_ipv4_checksum(ip_bytes.first(header_len)) ? "ok" : "BAD";
-
- using wireframe::net::ChecksumResult;
- if (ip->header.protocol == wireframe::net::kProtoTcp) {
- auto result =
- wireframe::net::verify_tcp_checksum_ipv4(ip->header.src, ip->header.dst, ip->payload);
- out += result == ChecksumResult::kValid ? " TCP=ok" : " TCP=BAD";
- } else if (ip->header.protocol == wireframe::net::kProtoUdp) {
- auto result =
- wireframe::net::verify_udp_checksum_ipv4(ip->header.src, ip->header.dst, ip->payload);
- out += result == ChecksumResult::kValid ? " UDP=ok"
- : result == ChecksumResult::kNotPresent ? " UDP=none"
- : " UDP=BAD";
- }
- return out;
-}
-
-// -a only: TCP stream reassembly (wireframe/net/tcp_reassembly.hpp),
-// re-run through the same HTTP dissector summarize_packet() already
-// uses for a single segment - reassembly only helps when a message is
-// actually split across packets, and HTTP is the L7 dissector in this
-// project that's structured around lines/headers rather than one fixed
-// datagram (DNS/TLS ClientHello are each their own single UDP datagram
-// or first TCP segment already). Printed as its own line rather than
-// folded into the per-packet summary: it reflects accumulated flow
-// state, not just this one packet. In-order-only reassembly (see the
-// header's own comment) means this can legitimately fire again on a
-// later packet of the same request with an unchanged result once the
-// headers are already complete - an honest simplification, not
-// deduplicated further.
-std::optional<std::string> reassembled_http_status(std::span<const unsigned char> bytes,
- int datalink,
- wireframe::net::TcpReassembler& reassembler) {
- std::span<const unsigned char> ip_bytes;
- if (datalink == DLT_RAW) {
- ip_bytes = bytes;
- } else {
- auto eth = wireframe::net::parse_ethernet(bytes);
- if (!eth || eth->header.ethertype != wireframe::net::kEthertypeIPv4) return std::nullopt;
- ip_bytes = eth->payload;
- }
- if (ip_bytes.empty() || (ip_bytes[0] >> 4) != 4) return std::nullopt; // IPv4 only, for now
-
- auto ip = wireframe::net::parse_ipv4(ip_bytes);
- if (!ip || ip->header.protocol != wireframe::net::kProtoTcp) return std::nullopt;
-
- auto tcp = wireframe::net::parse_tcp(ip->payload);
- if (!tcp) return std::nullopt;
-
- auto reassembled = reassembler.process_segment(ip->header.src, tcp->header.src_port,
- ip->header.dst, tcp->header.dst_port,
- tcp->header.seq, tcp->header.flags,
- tcp->payload);
- if (!reassembled) return std::nullopt;
-
- auto http = wireframe::net::parse_http(*reassembled);
- if (!http) return std::nullopt;
-
- std::string out = " [reassembled ";
- out += http->is_request ? "request] " : "response] ";
- out += http->method_or_version + " " + http->target_or_status;
- if (http->host) out += " Host: " + *http->host;
- out += " (" + std::to_string(reassembled->size()) + " bytes so far)";
- return out;
-}
-
struct RenderOptions {
bool verbose_hex;
bool verbose_checksums;
@@ -185,11 +92,15 @@ void render_packet(const wireframe::CapturedPacket& packet, const RenderOptions&
if (!wireframe::matches_search(line, opts.search_term)) return;
- if (opts.verbose_checksums) line += checksum_status(bytes, opts.datalink);
+ if (opts.verbose_checksums) {
+ std::string status = wireframe::checksum_status(bytes, opts.datalink);
+ if (!status.empty()) line += " " + status;
+ }
std::printf("%s\n", line.c_str());
if (opts.reassembler) {
- if (auto status = reassembled_http_status(bytes, opts.datalink, *opts.reassembler)) {
- std::printf("%s\n", status->c_str());
+ if (auto status = wireframe::reassembled_http_status(bytes, opts.datalink,
+ *opts.reassembler)) {
+ std::printf(" [%s]\n", status->c_str());
}
}
if (opts.verbose_hex) hex_dump(bytes);