diff options
Diffstat (limited to 'include')
| -rw-r--r-- | include/packeteer/net/icmp.hpp | 75 | ||||
| -rw-r--r-- | include/packeteer/summarize.hpp | 6 |
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 |