srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/packeteer/net/icmp.hpp75
-rw-r--r--include/packeteer/summarize.hpp6
2 files changed, 81 insertions, 0 deletions
diff --git a/include/packeteer/net/icmp.hpp b/include/packeteer/net/icmp.hpp
index d2613a2..60fd1d7 100644
--- a/include/packeteer/net/icmp.hpp
+++ b/include/packeteer/net/icmp.hpp
@@ -1,11 +1,14 @@
#pragma once
#include <cstdint>
+#include <cstdio>
#include <optional>
#include <span>
#include <string>
#include "packeteer/byteio.hpp"
+#include "packeteer/net/ipv4.hpp"
+#include "packeteer/net/ipv6.hpp"
// ICMPv4 (RFC 792) and ICMPv6 (RFC 4443) share the same first-4-byte
// shape (Type, Code, Checksum) but a completely different type
@@ -36,6 +39,22 @@ inline std::optional<IcmpHeader> parse_icmpv4(std::span<const unsigned char> byt
return header;
}
+// Destination Unreachable, Source Quench, Redirect, Time Exceeded, and
+// Parameter Problem are the ICMPv4 types that carry an embedded
+// original packet (RFC 792) - everything else (echo, timestamp)
+// doesn't, so icmpv4_embedded_flow() is only worth trying for these.
+inline bool icmpv4_is_error_type(std::uint8_t type) {
+ return type == 3 || type == 4 || type == 5 || type == 11 || type == 12;
+}
+
+// Destination Unreachable, Packet Too Big, Time Exceeded, and
+// Parameter Problem for ICMPv6 (RFC 4443) - echo and the various
+// Neighbor Discovery types (Router/Neighbor Solicitation/
+// Advertisement, Redirect) don't carry an embedded packet at all.
+inline bool icmpv6_is_error_type(std::uint8_t type) {
+ return type == 1 || type == 2 || type == 3 || type == 4;
+}
+
inline std::string icmpv4_type_name(std::uint8_t type) {
switch (type) {
case 0: return "Echo Reply";
@@ -81,4 +100,60 @@ inline std::string icmpv6_type_name(std::uint8_t type) {
}
}
+namespace detail {
+inline std::string icmp_ipv4_to_string(const Ipv4Address& addr) {
+ char buf[16];
+ std::snprintf(buf, sizeof(buf), "%u.%u.%u.%u", addr.bytes[0], addr.bytes[1], addr.bytes[2],
+ addr.bytes[3]);
+ return buf;
+}
+} // namespace detail
+
+// Destination Unreachable, Time Exceeded, Redirect, and Parameter
+// Problem all carry, after their own fixed 8-byte header, as much of
+// the packet that triggered the error as the network could fit --
+// always at least its IP header plus the first 8 bytes of payload
+// (RFC 792/4443), exactly enough to recover TCP/UDP port numbers. This
+// is what actually answers "which flow got this error", the reason an
+// ICMP error shows up in a capture in the first place. Reuses
+// parse_ipv4() directly on those bytes rather than a separate parser:
+// it's a genuine (if truncated) IPv4 packet, not a different format.
+inline std::optional<std::string> icmpv4_embedded_flow(std::span<const unsigned char> icmp_bytes) {
+ if (icmp_bytes.size() < 8) return std::nullopt;
+ auto ip = parse_ipv4(icmp_bytes.subspan(8));
+ if (!ip) return std::nullopt;
+
+ std::string out = detail::icmp_ipv4_to_string(ip->header.src) + " -> " +
+ detail::icmp_ipv4_to_string(ip->header.dst) +
+ " proto=" + std::to_string(ip->header.protocol);
+ if ((ip->header.protocol == kProtoTcp || ip->header.protocol == kProtoUdp) &&
+ ip->payload.size() >= 4) {
+ out += " (" + std::to_string(read_be16(ip->payload, 0)) + " -> " +
+ std::to_string(read_be16(ip->payload, 2)) + ")";
+ }
+ return out;
+}
+
+// Same idea for ICMPv6, over the embedded IPv6 packet's fixed 40-byte
+// header. Extension headers on the embedded packet aren't walked (see
+// walk_ipv6_extension_headers() in ipv6.hpp for the full version this
+// deliberately doesn't reuse here) - an embedded packet's next_header
+// naming an extension header rather than TCP/UDP directly is rare
+// enough in practice not to be worth the extra complexity for a
+// nested, best-effort field.
+inline std::optional<std::string> icmpv6_embedded_flow(std::span<const unsigned char> icmp_bytes) {
+ if (icmp_bytes.size() < 8) return std::nullopt;
+ auto ip6 = parse_ipv6(icmp_bytes.subspan(8));
+ if (!ip6) return std::nullopt;
+
+ std::string out = ipv6_to_string(ip6->header.src) + " -> " + ipv6_to_string(ip6->header.dst) +
+ " next=" + std::to_string(ip6->header.next_header);
+ if ((ip6->header.next_header == kProtoTcp || ip6->header.next_header == kProtoUdp) &&
+ ip6->payload.size() >= 4) {
+ out += " (" + std::to_string(read_be16(ip6->payload, 0)) + " -> " +
+ std::to_string(read_be16(ip6->payload, 2)) + ")";
+ }
+ return out;
+}
+
} // namespace packeteer::net
diff --git a/include/packeteer/summarize.hpp b/include/packeteer/summarize.hpp
index c538cec..4126eb5 100644
--- a/include/packeteer/summarize.hpp
+++ b/include/packeteer/summarize.hpp
@@ -218,6 +218,10 @@ inline std::string summarize_transport_and_above(const IpInfo& info) {
if (icmp->identifier) {
out += " id=" + std::to_string(*icmp->identifier) +
" seq=" + std::to_string(*icmp->sequence);
+ } else if (net::icmpv4_is_error_type(icmp->type)) {
+ // Answers the actual reason this error shows up in a
+ // capture: which flow triggered it.
+ if (auto flow = net::icmpv4_embedded_flow(info.payload)) out += " [" + *flow + "]";
}
}
} else if (info.proto == net::kProtoIgmp) {
@@ -234,6 +238,8 @@ inline std::string summarize_transport_and_above(const IpInfo& info) {
if (icmp->identifier) {
out += " id=" + std::to_string(*icmp->identifier) +
" seq=" + std::to_string(*icmp->sequence);
+ } else if (net::icmpv6_is_error_type(icmp->type)) {
+ if (auto flow = net::icmpv6_embedded_flow(info.payload)) out += " [" + *flow + "]";
}
} else {
out += " | ICMPv6"; // truncated: at least say what it is