srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/packeteer/net/ipv4.hpp11
-rw-r--r--include/packeteer/net/ipv6.hpp53
-rw-r--r--include/packeteer/summarize.hpp28
3 files changed, 86 insertions, 6 deletions
diff --git a/include/packeteer/net/ipv4.hpp b/include/packeteer/net/ipv4.hpp
index c6cb656..bcc8fa6 100644
--- a/include/packeteer/net/ipv4.hpp
+++ b/include/packeteer/net/ipv4.hpp
@@ -27,6 +27,13 @@ struct Ipv4Header {
std::uint8_t protocol;
Ipv4Address src;
Ipv4Address dst;
+ std::uint16_t identification; // ties fragments of the same original datagram together
+ bool more_fragments;
+ // In 8-byte units (RFC 791) - 0 means this is either an unfragmented
+ // packet or the *first* fragment, either way the one that actually
+ // carries the transport header. Nonzero means a later fragment:
+ // pure payload continuation, no TCP/UDP/ICMP header present at all.
+ std::uint16_t fragment_offset;
};
struct Ipv4Packet {
@@ -46,6 +53,10 @@ inline std::optional<Ipv4Packet> parse_ipv4(std::span<const unsigned char> bytes
header.version = version;
header.ihl = ihl;
header.total_length = read_be16(bytes, 2);
+ header.identification = read_be16(bytes, 4);
+ std::uint16_t flags_and_offset = read_be16(bytes, 6);
+ header.more_fragments = (flags_and_offset & 0x2000) != 0;
+ header.fragment_offset = flags_and_offset & 0x1FFF;
header.ttl = bytes[8];
header.protocol = bytes[9];
std::copy_n(bytes.begin() + 12, 4, header.src.bytes.begin());
diff --git a/include/packeteer/net/ipv6.hpp b/include/packeteer/net/ipv6.hpp
index 8c048e8..d7ea0d5 100644
--- a/include/packeteer/net/ipv6.hpp
+++ b/include/packeteer/net/ipv6.hpp
@@ -70,6 +70,18 @@ struct Ipv6ExtensionWalkResult {
std::uint8_t final_next_header; // a transport protocol, or an extension type we stopped at
std::span<const unsigned char> payload; // bytes after every extension header walked
bool stopped_at_esp; // true if ESP was hit - see walk_ipv6_extension_headers()
+ // True when a Fragment header (RFC 8200 4.5) was walked with a
+ // nonzero fragment offset - a later fragment, not the first.
+ // final_next_header still correctly names the eventual transport
+ // protocol, but `payload` here is pure continuation data with no
+ // TCP/UDP/ICMPv6 header in it at all; callers must not hand it to
+ // a transport parser. fragment_id is that header's 4-byte
+ // Identification field, set whenever a Fragment header was seen
+ // at all (first fragment or not) - useful for correlating
+ // fragments of the same original datagram even when this flag is
+ // false for the first one.
+ bool is_non_first_fragment;
+ std::optional<std::uint32_t> fragment_id;
};
// Walks Hop-by-Hop, Routing, Destination Options, Fragment, and AH
@@ -96,35 +108,64 @@ struct Ipv6ExtensionWalkResult {
inline Ipv6ExtensionWalkResult walk_ipv6_extension_headers(std::uint8_t next_header,
std::span<const unsigned char> payload) {
constexpr int kMaxExtensionHeaders = 8;
+ // Persists across loop iterations, unlike a Fragment header's other
+ // fields: once a Fragment header is seen (even the first one, whose
+ // walk continues normally afterward), every return from here on
+ // should still report its Identification field, not lose it the
+ // moment the loop moves past that one header.
+ std::optional<std::uint32_t> seen_fragment_id;
for (int i = 0; i < kMaxExtensionHeaders; ++i) {
if (next_header == kNextHeaderEsp) {
- return {next_header, payload, /*stopped_at_esp=*/true};
+ return {next_header, payload, /*stopped_at_esp=*/true, false, seen_fragment_id};
}
std::size_t ext_len;
if (next_header == kNextHeaderFragment) {
- if (payload.size() < 8) return {next_header, payload, false};
+ if (payload.size() < 8) return {next_header, payload, false, false, seen_fragment_id};
ext_len = 8;
} else if (next_header == kNextHeaderAh) {
- if (payload.size() < 2) return {next_header, payload, false};
+ if (payload.size() < 2) return {next_header, payload, false, false, seen_fragment_id};
ext_len = (static_cast<std::size_t>(payload[1]) + 2) * 4;
} else if (next_header == kNextHeaderHopByHop || next_header == kNextHeaderRouting ||
next_header == kNextHeaderDestOptions) {
- if (payload.size() < 2) return {next_header, payload, false};
+ if (payload.size() < 2) return {next_header, payload, false, false, seen_fragment_id};
ext_len = (static_cast<std::size_t>(payload[1]) + 1) * 8;
} else {
break; // TCP/UDP/ICMPv6/anything else we don't chain through: stop here
}
- if (payload.size() < ext_len) return {next_header, payload, false}; // truncated: stop
+ if (payload.size() < ext_len) {
+ return {next_header, payload, false, false, seen_fragment_id}; // truncated: stop
+ }
+
+ if (next_header == kNextHeaderFragment) {
+ // Fragment Offset (13 bits) + Res (2 bits) + M flag (1 bit)
+ // at bytes[2:4]; Identification (4 bytes) at bytes[4:8].
+ std::uint16_t offset_res_m = read_be16(payload, 2);
+ std::uint16_t fragment_offset = (offset_res_m >> 3) & 0x1FFF;
+ seen_fragment_id = read_be32(payload, 4);
+ std::uint8_t this_next_header = payload[0];
+ std::span<const unsigned char> remaining = payload.subspan(ext_len);
+ if (fragment_offset != 0) {
+ // A later fragment: `remaining` is pure continuation
+ // data, not a transport header, regardless of what
+ // this_next_header names. Stop here rather than
+ // walking (and definitely rather than letting a
+ // caller hand this to a transport parser).
+ return {this_next_header, remaining, false, true, seen_fragment_id};
+ }
+ payload = remaining;
+ next_header = this_next_header;
+ continue;
+ }
std::uint8_t this_next_header = payload[0];
payload = payload.subspan(ext_len);
next_header = this_next_header;
}
- return {next_header, payload, false};
+ return {next_header, payload, false, false, seen_fragment_id};
}
// RFC 5952 canonical text form: lowercase hex, and the longest run of
diff --git a/include/packeteer/summarize.hpp b/include/packeteer/summarize.hpp
index 4126eb5..4bb2d24 100644
--- a/include/packeteer/summarize.hpp
+++ b/include/packeteer/summarize.hpp
@@ -319,9 +319,27 @@ inline std::string summarize_packet(std::span<const unsigned char> bytes, int da
out += " | IPv4 (truncated)";
return out;
}
+ bool is_fragment = ip->header.more_fragments || ip->header.fragment_offset != 0;
+ if (ip->header.fragment_offset != 0) {
+ // A later fragment: no TCP/UDP/ICMP header is present in
+ // this packet at all, only continuation payload - handing
+ // it to a transport parser would misread arbitrary payload
+ // bytes as port numbers/sequence numbers/etc. Report the
+ // fragment itself and stop, rather than decode garbage.
+ char buf[96];
+ std::snprintf(buf, sizeof(buf), " | IPv4 %s -> %s ttl=%u proto=%u fragment id=%u offset=%u%s",
+ ipv4_to_string(ip->header.src).c_str(), ipv4_to_string(ip->header.dst).c_str(),
+ ip->header.ttl, ip->header.protocol, ip->header.identification,
+ ip->header.fragment_offset * 8, ip->header.more_fragments ? " (more)" : "");
+ out += buf;
+ return out;
+ }
out += summarize_transport_and_above({"IPv4", ipv4_to_string(ip->header.src),
ipv4_to_string(ip->header.dst), ip->header.ttl,
ip->header.protocol, ip->payload});
+ if (is_fragment) {
+ out += " (fragmented, id=" + std::to_string(ip->header.identification) + ", more follow)";
+ }
} else if (version == 6) {
auto ip6 = net::parse_ipv6(ip_bytes);
if (!ip6) {
@@ -341,9 +359,19 @@ inline std::string summarize_packet(std::span<const unsigned char> bytes, int da
src_str.c_str(), dst_str.c_str(), ip6->header.hop_limit,
net::kNextHeaderEsp);
out += buf;
+ } else if (walked.is_non_first_fragment) {
+ char buf[128];
+ std::snprintf(buf, sizeof(buf),
+ " | IPv6 %s -> %s ttl=%u proto=%u fragment id=%u (not first)",
+ src_str.c_str(), dst_str.c_str(), ip6->header.hop_limit,
+ walked.final_next_header, *walked.fragment_id);
+ out += buf;
} else {
out += summarize_transport_and_above({"IPv6", src_str, dst_str, ip6->header.hop_limit,
walked.final_next_header, walked.payload});
+ if (walked.fragment_id) {
+ out += " (fragmented, id=" + std::to_string(*walked.fragment_id) + ")";
+ }
}
} else {
out += " | IP version " + std::to_string(version) + " (unsupported)";