diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/gui_main.cpp | 45 | ||||
| -rw-r--r-- | src/main.cpp | 105 |
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); |