srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-05-28 01:55:00 +0200
committersrdusr <[email protected]>2024-05-28 01:55:00 +0200
commitf8fc8806401596b08779cf8c88da31408c9b0547 (patch)
tree36d873799695bb23ad6bb0989e1b0f05b734041d /include
parentb565d7d9c47ca1ec5af0effd828431ee96027d60 (diff)
downloadpacketeer-f8fc8806401596b08779cf8c88da31408c9b0547.tar.gz
packeteer-f8fc8806401596b08779cf8c88da31408c9b0547.zip
Add ARP decoding and bring the TUI up to -c/-a parity
ARP had zero treatment until now - its ethertype just fell through summarize_packet's "not IPv4/IPv6" branch, on traffic that appears on essentially every real LAN capture. Scoped to Ethernet/IPv4 addressing (the case that covers virtually all real ARP traffic), with tcpdump's own "who-has X tell Y" / "X is-at Y" phrasing rather than inventing new wording. Live-verified by flushing this machine's real gateway ARP entry and capturing the resulting request/reply on wlp1s0. TUI -c/-a were being parsed into RenderOptions but silently did nothing: run_tui()'s consumer thread had its own loop that never read them, unlike plain-text mode's render_packet(). Fixed to match, and caught a real bug while doing it - pushing up to two rows per packet (summary + reassembly line) against a single pop_front() would let the row deque grow past its cap under sustained -a activity; needed a while loop instead. Verified under tmux against the same split-segment HTTP scenario used to verify -a on the CLI and GUI.
Diffstat (limited to 'include')
-rw-r--r--include/packeteer/net/arp.hpp73
-rw-r--r--include/packeteer/summarize.hpp29
2 files changed, 102 insertions, 0 deletions
diff --git a/include/packeteer/net/arp.hpp b/include/packeteer/net/arp.hpp
new file mode 100644
index 0000000..470dc9d
--- /dev/null
+++ b/include/packeteer/net/arp.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include <algorithm>
+#include <cstdint>
+#include <optional>
+#include <span>
+
+#include "packeteer/byteio.hpp"
+#include "packeteer/net/ethernet.hpp"
+#include "packeteer/net/ipv4.hpp"
+
+// RFC 826 ARP, scoped to the case that accounts for essentially all
+// real traffic on a modern LAN: Ethernet hardware addresses (hlen=6)
+// and IPv4 protocol addresses (plen=4). Other hardware/protocol
+// combinations still decode the fixed header (hwtype/protype/opcode),
+// just without sender/target addresses - there's no safe way to guess
+// an address width other than what hardware_len/protocol_len actually
+// say.
+namespace packeteer::net {
+
+inline constexpr std::uint16_t kArpOpRequest = 1;
+inline constexpr std::uint16_t kArpOpReply = 2;
+
+struct ArpHeader {
+ std::uint16_t hardware_type;
+ std::uint16_t protocol_type;
+ std::uint8_t hardware_len;
+ std::uint8_t protocol_len;
+ std::uint16_t opcode;
+};
+
+struct ArpPacket {
+ ArpHeader header;
+ // All four populated together only when hardware_len == 6 and
+ // protocol_len == 4; left as nullopt otherwise.
+ std::optional<MacAddress> sender_mac;
+ std::optional<Ipv4Address> sender_ip;
+ std::optional<MacAddress> target_mac;
+ std::optional<Ipv4Address> target_ip;
+};
+
+inline std::optional<ArpPacket> parse_arp(std::span<const unsigned char> bytes) {
+ if (bytes.size() < 8) return std::nullopt;
+
+ ArpHeader header{};
+ header.hardware_type = read_be16(bytes, 0);
+ header.protocol_type = read_be16(bytes, 2);
+ header.hardware_len = bytes[4];
+ header.protocol_len = bytes[5];
+ header.opcode = read_be16(bytes, 6);
+
+ ArpPacket packet{header, std::nullopt, std::nullopt, std::nullopt, std::nullopt};
+
+ if (header.hardware_len == 6 && header.protocol_len == 4 && bytes.size() >= 28) {
+ MacAddress sender_mac{};
+ std::copy_n(bytes.begin() + 8, 6, sender_mac.bytes.begin());
+ Ipv4Address sender_ip{};
+ std::copy_n(bytes.begin() + 14, 4, sender_ip.bytes.begin());
+ MacAddress target_mac{};
+ std::copy_n(bytes.begin() + 18, 6, target_mac.bytes.begin());
+ Ipv4Address target_ip{};
+ std::copy_n(bytes.begin() + 24, 4, target_ip.bytes.begin());
+
+ packet.sender_mac = sender_mac;
+ packet.sender_ip = sender_ip;
+ packet.target_mac = target_mac;
+ packet.target_ip = target_ip;
+ }
+
+ return packet;
+}
+
+} // namespace packeteer::net
diff --git a/include/packeteer/summarize.hpp b/include/packeteer/summarize.hpp
index 77d9ec3..7ff06a4 100644
--- a/include/packeteer/summarize.hpp
+++ b/include/packeteer/summarize.hpp
@@ -14,6 +14,7 @@
#include "packeteer/l7/mdns.hpp"
#include "packeteer/l7/ssh.hpp"
#include "packeteer/l7/tls.hpp"
+#include "packeteer/net/arp.hpp"
#include "packeteer/net/ethernet.hpp"
#include "packeteer/net/icmp.hpp"
#include "packeteer/net/ipv4.hpp"
@@ -40,6 +41,29 @@ inline std::string ipv4_to_string(const net::Ipv4Address& ip) {
return buf;
}
+// tcpdump's own "who-has X tell Y" / "X is-at Y" phrasing - a
+// deliberate match, not a coincidence, since anyone who's ever read
+// ARP traffic in tcpdump/Wireshark will recognize it instantly. Target
+// hardware/protocol addresses aren't shown even when present: a
+// request's target MAC is unknown by definition (that's what's being
+// asked), and a reply's target is just "whoever asked", which tcpdump
+// itself omits from this line too.
+inline std::string arp_summary(const net::ArpPacket& arp) {
+ if (!arp.sender_ip || !arp.sender_mac || !arp.target_ip) {
+ return "ARP opcode=" + std::to_string(arp.header.opcode) +
+ " (non-Ethernet/IPv4 addressing)";
+ }
+ if (arp.header.opcode == net::kArpOpRequest) {
+ return "ARP who-has " + ipv4_to_string(*arp.target_ip) + " tell " +
+ ipv4_to_string(*arp.sender_ip) + " (" + mac_to_string(*arp.sender_mac) + ")";
+ }
+ if (arp.header.opcode == net::kArpOpReply) {
+ return "ARP " + ipv4_to_string(*arp.sender_ip) + " is-at " + mac_to_string(*arp.sender_mac);
+ }
+ return "ARP opcode=" + std::to_string(arp.header.opcode) + " " +
+ ipv4_to_string(*arp.sender_ip) + " (" + mac_to_string(*arp.sender_mac) + ")";
+}
+
inline std::string tcp_flags_to_string(std::uint8_t flags) {
using namespace net;
std::string out;
@@ -187,6 +211,11 @@ inline std::string summarize_packet(std::span<const unsigned char> bytes, int da
std::snprintf(eth_buf, sizeof(eth_buf), " ethertype=0x%04x", eth->header.ethertype);
out += eth_buf;
+ if (eth->header.ethertype == net::kEthertypeArp) {
+ if (auto arp = net::parse_arp(eth->payload)) out += " | " + arp_summary(*arp);
+ return out;
+ }
+
if (eth->header.ethertype != net::kEthertypeIPv4 &&
eth->header.ethertype != net::kEthertypeIPv6) {
return out;