#![allow(dead_code)]
use std::net::{Ipv4Addr, Ipv6Addr};
use super::proto::DiscoveryProtocol;
use super::row::LldpRow;
use super::capture::{
parse_lldp_basic,
fmt_identifier,
lldp_payload,
cdp_payload,
};
pub fn parse_any_row_from_frame(iface: &str, frame: &[u8], timestamp_ms: u64) -> Option<LldpRow> {
if let Some(row) = parse_lldp_row_from_frame(iface, frame, timestamp_ms) {
return Some(row);
}
if let Some(row) = parse_cdp_row_from_frame(iface, frame, timestamp_ms) {
return Some(row);
}
None
}
pub fn parse_lldp_row_from_frame(iface: &str, frame: &[u8], timestamp_ms: u64) -> Option<LldpRow> {
let payload = lldp_payload(frame)?;
let basic = parse_lldp_basic(payload)?;
let chassis_s = basic
.chassis_id
.as_ref()
.map(|b| fmt_identifier(basic.chassis_id_subtype, b))
.unwrap_or_else(|| "<unknown>".to_string());
let port_s = basic
.port_id
.as_ref()
.map(|b| fmt_identifier(basic.port_id_subtype, b))
.unwrap_or_else(|| "<unknown>".to_string());
let (caps, mgmt_ip, vlan) = parse_lldp_extras(payload);
Some(LldpRow {
interface: iface.to_string(),
protocol: DiscoveryProtocol::LLDP,
chassis_id: chassis_s,
port_id: port_s,
system_name: basic.system_name.clone(),
system_desc: basic.system_desc.clone(),
port_desc: basic.port_desc.clone(),
vlan,
management_ip: mgmt_ip,
capabilities: caps,
ttl: basic.ttl,
timestamp_ms,
})
}
pub fn parse_cdp_row_from_frame(iface: &str, frame: &[u8], timestamp_ms: u64) -> Option<LldpRow> {
let payload = cdp_payload(frame)?;
if payload.len() < 4 {
return None;
}
let version = payload[0];
let ttl_u8 = payload[1];
let _checksum = u16::from_be_bytes([payload[2], payload[3]]);
if frame.len() < 12 {
return None;
}
let src_mac = &frame[6..12];
let chassis_s = format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5]
);
let mut system_name: Option<String> = None; let mut port_id: Option<String> = None; let mut system_desc: Option<String> = None; let mut vlan: Option<String> = None; let mut mgmt_ip: Option<String> = None; let mut capabilities: Vec<String> = Vec::new();
let mut i = 4usize;
while i + 4 <= payload.len() {
let tlv_type = u16::from_be_bytes([payload[i], payload[i + 1]]);
let tlv_len = u16::from_be_bytes([payload[i + 2], payload[i + 3]]) as usize;
if tlv_len < 4 || i + tlv_len > payload.len() {
break;
}
let value = &payload[i + 4 .. i + tlv_len];
match tlv_type {
0x0001 => { if let Ok(s) = String::from_utf8(value.to_vec()) {
if !s.is_empty() { system_name = Some(s); }
}
}
0x0003 => { if let Ok(s) = String::from_utf8(value.to_vec()) {
if !s.is_empty() { port_id = Some(s); }
}
}
0x0004 => { if value.len() >= 4 {
let caps = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
capabilities = decode_cdp_capabilities(caps);
}
}
0x000a => { if value.len() >= 2 {
let vid = u16::from_be_bytes([value[0], value[1]]);
vlan = Some(vid.to_string());
}
}
0x0002 => { if let Some(addr_s) = parse_cdp_addresses(value) {
mgmt_ip = Some(addr_s);
}
}
0x0005 => { if let Ok(s) = String::from_utf8(value.to_vec()) {
system_desc = Some(match system_desc.take() {
Some(prev) => format!("{} | {}", prev, s),
None => s,
});
}
}
0x0006 => { if let Ok(s) = String::from_utf8(value.to_vec()) {
system_desc = Some(match system_desc.take() {
Some(prev) => format!("{} | {}", prev, s),
None => s,
});
}
}
_ => { }
}
i += tlv_len;
}
let port_id_f = port_id.unwrap_or_else(|| "<unknown>".into());
Some(LldpRow {
interface: iface.to_string(),
protocol: DiscoveryProtocol::CDP,
chassis_id: chassis_s,
port_id: port_id_f,
system_name,
system_desc,
port_desc: None,
vlan,
management_ip: mgmt_ip,
capabilities,
ttl: Some(ttl_u8 as u16),
timestamp_ms,
}).filter(|_| version != 0) }
fn parse_lldp_extras(payload: &[u8]) -> (Vec<String>, Option<String>, Option<String>) {
let mut caps_out: Vec<String> = Vec::new();
let mut mgmt_ip: Option<String> = None;
let mut vlan: Option<String> = None;
let mut i = 0usize;
while i < payload.len() {
let (typ, len) = match tlv_header(payload, &mut i) {
Some(x) => x,
None => break,
};
if len == 0 && typ == 0 { break; }
if i + len > payload.len() { break; }
match typ {
7 => {
if len >= 4 {
let enabled = u16::from_be_bytes([payload[i + 2], payload[i + 3]]);
caps_out = decode_lldp_capabilities(enabled);
}
}
8 => {
if mgmt_ip.is_none() {
if let Some(addr) = parse_lldp_mgmt_addr(&payload[i .. i + len]) {
mgmt_ip = Some(addr);
}
}
}
127 => {
if len >= 4 && payload[i..i + 3] == [0x00, 0x80, 0xC2] {
if payload[i + 3] == 0x01 && len >= 6 {
let pvid = u16::from_be_bytes([payload[i + 4], payload[i + 5]]);
vlan = Some(pvid.to_string());
}
}
}
_ => {}
}
i += len;
}
(caps_out, mgmt_ip, vlan)
}
fn decode_lldp_capabilities(bits: u16) -> Vec<String> {
let mut out = Vec::new();
if bits & 0x0002 != 0 { out.push("repeater".into()); }
if bits & 0x0004 != 0 { out.push("bridge".into()); }
if bits & 0x0008 != 0 { out.push("wlan-ap".into()); }
if bits & 0x0010 != 0 { out.push("router".into()); }
if bits & 0x0020 != 0 { out.push("telephone".into()); }
if bits & 0x0040 != 0 { out.push("docsis".into()); }
if bits & 0x0080 != 0 { out.push("station".into()); }
if bits & 0x0100 != 0 { out.push("cvlan".into()); }
if bits & 0x0200 != 0 { out.push("svlan".into()); }
if bits & 0x0800 != 0 { out.push("tpmr".into()); }
out
}
fn parse_lldp_mgmt_addr(tlv_value: &[u8]) -> Option<String> {
if tlv_value.len() < 1 { return None; }
let l = tlv_value[0] as usize;
if tlv_value.len() < 1 + l + 1 + 4 + 1 {
return None;
}
let subtype = tlv_value[1];
let addr_bytes = &tlv_value[2 .. 1 + l]; match subtype {
1 => { if addr_bytes.len() == 4 {
let ip = Ipv4Addr::new(addr_bytes[0], addr_bytes[1], addr_bytes[2], addr_bytes[3]);
return Some(ip.to_string());
}
}
2 => { if addr_bytes.len() == 16 {
let mut segs = [0u16; 8];
for i in 0..8 {
segs[i] = u16::from_be_bytes([addr_bytes[i*2], addr_bytes[i*2+1]]);
}
let ip = Ipv6Addr::new(
segs[0], segs[1], segs[2], segs[3],
segs[4], segs[5], segs[6], segs[7]
);
return Some(ip.to_string());
}
}
_ => {}
}
None
}
#[inline]
fn tlv_header(buf: &[u8], idx: &mut usize) -> Option<(u8, usize)> {
if buf.len() < *idx + 2 { return None; }
let b1 = buf[*idx];
let b2 = buf[*idx + 1];
*idx += 2;
let typ = (b1 >> 1) & 0x7F;
let len = (((b1 as u16 & 0x01) << 8) | b2 as u16) as usize;
Some((typ, len))
}
fn decode_cdp_capabilities(bits: u32) -> Vec<String> {
let mut out = Vec::new();
if bits & 0x00000001 != 0 { out.push("router".into()); }
if bits & 0x00000002 != 0 { out.push("bridge".into()); } if bits & 0x00000004 != 0 { out.push("srb".into()); } if bits & 0x00000008 != 0 { out.push("switch".into()); } if bits & 0x00000010 != 0 { out.push("l3-switch".into()); } if bits & 0x00000020 != 0 { out.push("host".into()); }
if bits & 0x00000080 != 0 { out.push("repeater".into()); }
out
}
fn parse_cdp_addresses(mut v: &[u8]) -> Option<String> {
if v.len() < 4 { return None; }
let num = u32::from_be_bytes([v[0], v[1], v[2], v[3]]) as usize;
v = &v[4..];
let mut first_ip: Option<String> = None;
for _ in 0..num {
if v.len() < 2 { break; }
let proto_type = v[0]; let proto_len = v[1] as usize;
v = &v[2..];
if v.len() < proto_len + 2 { break; }
let proto = &v[..proto_len];
v = &v[proto_len..];
let addr_len = u16::from_be_bytes([v[0], v[1]]) as usize;
v = &v[2..];
if v.len() < addr_len { break; }
let addr = &v[..addr_len];
v = &v[addr_len..];
if proto_type == 1 {
if proto_len == 1 && proto[0] == 0xCC && addr_len == 4 {
let ip = Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]).to_string();
return Some(ip);
}
}
if addr_len == 4 {
let ip = Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]).to_string();
if first_ip.is_none() { first_ip = Some(ip); }
} else if addr_len == 16 {
let mut segs = [0u16; 8];
for i in 0..8 {
segs[i] = u16::from_be_bytes([addr[i*2], addr[i*2+1]]);
}
let ip6 = Ipv6Addr::new(
segs[0], segs[1], segs[2], segs[3],
segs[4], segs[5], segs[6], segs[7]
).to_string();
if first_ip.is_none() { first_ip = Some(ip6); }
}
}
first_ip
}