srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/packeteer/net/ethernet.hpp37
-rw-r--r--include/packeteer/summarize.hpp25
2 files changed, 57 insertions, 5 deletions
diff --git a/include/packeteer/net/ethernet.hpp b/include/packeteer/net/ethernet.hpp
index 5b851bc..77d2927 100644
--- a/include/packeteer/net/ethernet.hpp
+++ b/include/packeteer/net/ethernet.hpp
@@ -5,6 +5,7 @@
#include <cstdint>
#include <optional>
#include <span>
+#include <vector>
#include "packeteer/byteio.hpp"
@@ -14,6 +15,8 @@ inline constexpr std::size_t kEthernetHeaderLen = 14;
inline constexpr std::uint16_t kEthertypeIPv4 = 0x0800;
inline constexpr std::uint16_t kEthertypeIPv6 = 0x86DD;
inline constexpr std::uint16_t kEthertypeArp = 0x0806;
+inline constexpr std::uint16_t kEthertypeVlan = 0x8100; // IEEE 802.1Q
+inline constexpr std::uint16_t kEthertypeVlanQinQ = 0x88A8; // IEEE 802.1ad, stacked tags
struct MacAddress {
std::array<unsigned char, 6> bytes;
@@ -41,4 +44,38 @@ inline std::optional<EthernetFrame> parse_ethernet(std::span<const unsigned char
return EthernetFrame{header, bytes.subspan(kEthernetHeaderLen)};
}
+struct VlanWalkResult {
+ std::vector<std::uint16_t> vlan_ids; // outer to inner; usually 0 or 1, 2 for QinQ
+ std::uint16_t ethertype; // the real ethertype once every tag is stripped
+ std::span<const unsigned char> payload;
+};
+
+// A VLAN-tagged frame inserts a 4-byte tag (2-byte TPID + 2-byte TCI,
+// the low 12 bits of which are the VLAN ID) right where the ethertype
+// field would otherwise be, pushing the real ethertype 4 bytes deeper
+// - parse_ethernet() alone has no way to know this, since it always
+// reads a fixed 14-byte header and hands back whatever follows as
+// "payload" regardless of whether that's really IP/ARP or another
+// tag. Composable and separate from parse_ethernet() the same way
+// walk_ipv6_extension_headers() is separate from parse_ipv6(): the
+// base parse stays an unconditional fixed-header decode, and this
+// walk is what a caller uses when it actually needs the protocol
+// underneath. Bounded at 4 tags so a corrupt/hostile frame claiming
+// tag-of-a-tag-of-a-tag indefinitely can't spin - real QinQ stacks
+// are 2 deep at most.
+inline VlanWalkResult walk_vlan_tags(std::uint16_t ethertype,
+ std::span<const unsigned char> payload) {
+ VlanWalkResult result{{}, ethertype, payload};
+ constexpr int kMaxTags = 4;
+ for (int i = 0; i < kMaxTags; ++i) {
+ if (result.ethertype != kEthertypeVlan && result.ethertype != kEthertypeVlanQinQ) break;
+ if (result.payload.size() < 4) break; // truncated tag
+ std::uint16_t tci = read_be16(result.payload, 0);
+ result.vlan_ids.push_back(tci & 0x0FFF);
+ result.ethertype = read_be16(result.payload, 2);
+ result.payload = result.payload.subspan(4);
+ }
+ return result;
+}
+
} // namespace packeteer::net
diff --git a/include/packeteer/summarize.hpp b/include/packeteer/summarize.hpp
index 4143e44..302b380 100644
--- a/include/packeteer/summarize.hpp
+++ b/include/packeteer/summarize.hpp
@@ -220,16 +220,31 @@ 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);
+ // Unwraps any 802.1Q/802.1ad VLAN tags between the Ethernet
+ // header and the real protocol - without this, every tagged
+ // frame's actual ethertype reads as 0x8100 and everything
+ // below (ARP/IPv4/IPv6 and everything built on them) would be
+ // invisible on any VLAN-tagged network. The line still shows
+ // the literal on-the-wire outer ethertype above; dispatch from
+ // here on uses the walked (real) one.
+ auto vlan = net::walk_vlan_tags(eth->header.ethertype, eth->payload);
+ if (!vlan.vlan_ids.empty()) {
+ out += " vlan=";
+ for (std::size_t i = 0; i < vlan.vlan_ids.size(); ++i) {
+ if (i > 0) out += ",";
+ out += std::to_string(vlan.vlan_ids[i]);
+ }
+ }
+
+ if (vlan.ethertype == net::kEthertypeArp) {
+ if (auto arp = net::parse_arp(vlan.payload)) out += " | " + arp_summary(*arp);
return out;
}
- if (eth->header.ethertype != net::kEthertypeIPv4 &&
- eth->header.ethertype != net::kEthertypeIPv6) {
+ if (vlan.ethertype != net::kEthertypeIPv4 && vlan.ethertype != net::kEthertypeIPv6) {
return out;
}
- ip_bytes = eth->payload;
+ ip_bytes = vlan.payload;
}
if (ip_bytes.empty()) {