diff options
Diffstat (limited to 'include')
| -rw-r--r-- | include/packeteer/net/lldp.hpp | 92 | ||||
| -rw-r--r-- | include/packeteer/summarize.hpp | 6 |
2 files changed, 98 insertions, 0 deletions
diff --git a/include/packeteer/net/lldp.hpp b/include/packeteer/net/lldp.hpp new file mode 100644 index 0000000..ccf9f13 --- /dev/null +++ b/include/packeteer/net/lldp.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include <cstdint> +#include <cstdio> +#include <optional> +#include <span> +#include <string> + +#include "packeteer/byteio.hpp" + +// IEEE 802.1AB LLDP. Sent directly on Ethernet (ethertype 0x88CC), no +// IP layer at all - the same reasoning ARP is dispatched by ethertype +// in summarize.hpp rather than through anything IP-based. TLV-encoded: +// each TLV is a 2-byte header (7-bit type, 9-bit length) followed by +// that many bytes of value, terminated by an End of LLDPDU TLV (type +// 0). Only the three mandatory TLVs (Chassis ID, Port ID, TTL) plus +// System Name - usually the single most useful, human-readable field +// in the whole frame - are decoded; the rest (Port/System +// Description, Capabilities, Management Address, and any +// organizationally-specific TLVs) are walked over correctly (so +// nothing after them is missed) but not rendered. +namespace packeteer::net { + +inline constexpr std::uint16_t kEthertypeLldp = 0x88CC; + +struct LldpInfo { + std::optional<std::string> chassis_id; + std::optional<std::string> port_id; + std::optional<std::uint16_t> ttl; + std::optional<std::string> system_name; +}; + +// Chassis ID and Port ID share the same subtype+value shape. Subtype 4 +// (MAC address) is rendered as hex-colon; everything else (interface +// name/alias, component, locally-assigned string, etc.) is treated as +// ASCII text - true for every subtype except "network address" +// (subtype 5 for Chassis ID), which has its own AFI-prefixed encoding +// this doesn't attempt to decode specially and would render as +// mangled text instead. Uncommon enough in practice not to be worth a +// separate code path for a one-line summary. +inline std::string format_lldp_id(std::uint8_t subtype, std::span<const unsigned char> value) { + if (subtype == 4 && value.size() == 6) { + char buf[18]; + std::snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", value[0], value[1], + value[2], value[3], value[4], value[5]); + return buf; + } + return std::string(reinterpret_cast<const char*>(value.data()), value.size()); +} + +inline std::optional<LldpInfo> parse_lldp(std::span<const unsigned char> bytes) { + LldpInfo info{}; + std::size_t pos = 0; + constexpr int kMaxTlvs = 32; // real LLDPDUs rarely carry more than a handful + + for (int i = 0; i < kMaxTlvs; ++i) { + if (pos + 2 > bytes.size()) break; + std::uint16_t tlv_header = read_be16(bytes, pos); + std::uint8_t type = static_cast<std::uint8_t>(tlv_header >> 9); + std::uint16_t length = tlv_header & 0x01FF; + pos += 2; + if (pos + length > bytes.size()) break; // truncated: stop, keep what was decoded + std::span<const unsigned char> value = bytes.subspan(pos, length); + + if (type == 0) break; // End of LLDPDU + if (type == 1 && length >= 1) { + info.chassis_id = format_lldp_id(value[0], value.subspan(1)); + } else if (type == 2 && length >= 1) { + info.port_id = format_lldp_id(value[0], value.subspan(1)); + } else if (type == 3 && length == 2) { + info.ttl = read_be16(value, 0); + } else if (type == 5 && length >= 1) { + info.system_name = std::string(reinterpret_cast<const char*>(value.data()), value.size()); + } + + pos += length; + } + + // The three mandatory TLVs (802.1AB 9.2) - anything missing one + // of these isn't really a well-formed LLDPDU. + if (!info.chassis_id || !info.port_id || !info.ttl) return std::nullopt; + return info; +} + +inline std::string lldp_summary(const LldpInfo& info) { + std::string out = "LLDP chassis=" + *info.chassis_id + " port=" + *info.port_id + + " ttl=" + std::to_string(*info.ttl); + if (info.system_name) out += " name=" + *info.system_name; + return out; +} + +} // namespace packeteer::net diff --git a/include/packeteer/summarize.hpp b/include/packeteer/summarize.hpp index 4bb2d24..c261083 100644 --- a/include/packeteer/summarize.hpp +++ b/include/packeteer/summarize.hpp @@ -28,6 +28,7 @@ #include "packeteer/net/igmp.hpp" #include "packeteer/net/ipv4.hpp" #include "packeteer/net/ipv6.hpp" +#include "packeteer/net/lldp.hpp" #include "packeteer/net/rtcp.hpp" #include "packeteer/net/rtp.hpp" #include "packeteer/net/tcp.hpp" @@ -301,6 +302,11 @@ inline std::string summarize_packet(std::span<const unsigned char> bytes, int da return out; } + if (vlan.ethertype == net::kEthertypeLldp) { + if (auto lldp = net::parse_lldp(vlan.payload)) out += " | " + net::lldp_summary(*lldp); + return out; + } + if (vlan.ethertype != net::kEthertypeIPv4 && vlan.ethertype != net::kEthertypeIPv6) { return out; } |