towerops-agent/src/snmp/neighbor.rs

280 lines
8.5 KiB
Rust

use super::client::SnmpClient;
use super::types::{SnmpResult, SnmpValue};
use crate::metrics::{NeighborDiscovery, Timestamp};
use std::collections::HashMap;
/// LLDP-MIB remote table base OID
const LLDP_REM_TABLE_OID: &str = "1.0.8802.1.1.2.1.4.1.1";
/// CISCO-CDP-MIB neighbor table base OID
const CDP_CACHE_TABLE_OID: &str = "1.3.6.1.4.1.9.9.23.1.2.1.1";
/// Discover neighbors using LLDP and CDP
pub async fn discover_neighbors(
client: &SnmpClient,
ip_address: &str,
community: &str,
version: &str,
port: u16,
interfaces: &[(String, u32)], // (interface_id, if_index) pairs
) -> Vec<NeighborDiscovery> {
let mut neighbors = Vec::new();
// Discover LLDP neighbors
if let Ok(lldp_neighbors) =
discover_lldp_neighbors(client, ip_address, community, version, port, interfaces).await
{
neighbors.extend(lldp_neighbors);
}
// Discover CDP neighbors
if let Ok(cdp_neighbors) =
discover_cdp_neighbors(client, ip_address, community, version, port, interfaces).await
{
neighbors.extend(cdp_neighbors);
}
neighbors
}
/// Discover LLDP neighbors
async fn discover_lldp_neighbors(
client: &SnmpClient,
ip_address: &str,
community: &str,
version: &str,
port: u16,
interfaces: &[(String, u32)],
) -> SnmpResult<Vec<NeighborDiscovery>> {
let entries = client
.walk(ip_address, community, version, port, LLDP_REM_TABLE_OID)
.await?;
if entries.is_empty() {
return Ok(Vec::new());
}
// Group entries by neighbor key (timemark.local_port_num.index)
let mut neighbors_map: HashMap<String, HashMap<String, String>> = HashMap::new();
for (oid, value) in entries {
if let Some(neighbor_key) = extract_lldp_neighbor_key(&oid) {
let field_name = extract_lldp_field_name(&oid);
let value_str = snmp_value_to_string(value);
neighbors_map
.entry(neighbor_key)
.or_default()
.insert(field_name, value_str);
}
}
// Convert to NeighborDiscovery structs
let mut neighbors = Vec::new();
for (_key, fields) in neighbors_map {
if let Some(neighbor) = build_lldp_neighbor(fields, interfaces) {
neighbors.push(neighbor);
}
}
Ok(neighbors)
}
/// Discover CDP neighbors
async fn discover_cdp_neighbors(
client: &SnmpClient,
ip_address: &str,
community: &str,
version: &str,
port: u16,
interfaces: &[(String, u32)],
) -> SnmpResult<Vec<NeighborDiscovery>> {
let entries = client
.walk(ip_address, community, version, port, CDP_CACHE_TABLE_OID)
.await?;
if entries.is_empty() {
return Ok(Vec::new());
}
// Group entries by neighbor key (if_index.device_index)
let mut neighbors_map: HashMap<String, HashMap<String, String>> = HashMap::new();
for (oid, value) in entries {
if let Some(neighbor_key) = extract_cdp_neighbor_key(&oid) {
let field_name = extract_cdp_field_name(&oid);
let value_str = snmp_value_to_string(value);
neighbors_map
.entry(neighbor_key)
.or_default()
.insert(field_name, value_str);
}
}
// Convert to NeighborDiscovery structs
let mut neighbors = Vec::new();
for (_key, fields) in neighbors_map {
if let Some(neighbor) = build_cdp_neighbor(fields, interfaces) {
neighbors.push(neighbor);
}
}
Ok(neighbors)
}
/// Extract neighbor key from LLDP OID (timemark.local_port_num.index)
fn extract_lldp_neighbor_key(oid: &str) -> Option<String> {
// OID format: 1.0.8802.1.1.2.1.4.1.1.X.timemark.local_port_num.index
let parts: Vec<&str> = oid.split('.').collect();
if parts.len() >= 14 {
// Extract last 3 parts as neighbor key
Some(parts[parts.len() - 3..].join("."))
} else {
None
}
}
/// Extract field name from LLDP OID
fn extract_lldp_field_name(oid: &str) -> String {
// OID format: 1.0.8802.1.1.2.1.4.1.1.X.timemark.local_port_num.index
// X is the field identifier
let parts: Vec<&str> = oid.split('.').collect();
if parts.len() >= 11 {
parts[10].to_string()
} else {
"unknown".to_string()
}
}
/// Extract neighbor key from CDP OID (if_index.device_index)
fn extract_cdp_neighbor_key(oid: &str) -> Option<String> {
// OID format: 1.3.6.1.4.1.9.9.23.1.2.1.1.X.if_index.device_index
let parts: Vec<&str> = oid.split('.').collect();
if parts.len() >= 13 {
// Extract last 2 parts as neighbor key
Some(parts[parts.len() - 2..].join("."))
} else {
None
}
}
/// Extract field name from CDP OID
fn extract_cdp_field_name(oid: &str) -> String {
// OID format: 1.3.6.1.4.1.9.9.23.1.2.1.1.X.if_index.device_index
// X is the field identifier
let parts: Vec<&str> = oid.split('.').collect();
if parts.len() >= 11 {
parts[10].to_string()
} else {
"unknown".to_string()
}
}
/// Convert SnmpValue to string
fn snmp_value_to_string(value: SnmpValue) -> String {
match value {
SnmpValue::String(s) => s,
SnmpValue::Integer(i) => i.to_string(),
SnmpValue::Counter32(c) => c.to_string(),
SnmpValue::Counter64(c) => c.to_string(),
SnmpValue::Gauge32(g) => g.to_string(),
SnmpValue::TimeTicks(t) => t.to_string(),
SnmpValue::IpAddress(ip) => ip,
}
}
/// Build LLDP neighbor from fields
fn build_lldp_neighbor(
fields: HashMap<String, String>,
interfaces: &[(String, u32)],
) -> Option<NeighborDiscovery> {
// Field IDs from LLDP-MIB
// 4 = lldpRemChassisIdSubtype
// 5 = lldpRemChassisId
// 6 = lldpRemPortIdSubtype
// 7 = lldpRemPortId
// 8 = lldpRemPortDesc
// 9 = lldpRemSysName
// 10 = lldpRemSysDesc
// 12 = lldpRemManAddr
let local_port_num = fields.get("local_port_num")?;
let if_index: u32 = local_port_num.parse().ok()?;
let interface_id = interfaces
.iter()
.find(|(_, idx)| *idx == if_index)
.map(|(id, _)| id.clone())?;
let remote_chassis_id = fields.get("5").cloned().unwrap_or_default();
let remote_system_name = fields.get("9").cloned().unwrap_or_default();
let remote_system_description = fields.get("10").cloned().unwrap_or_default();
let remote_port_id = fields.get("7").cloned().unwrap_or_default();
let remote_port_description = fields.get("8").cloned().unwrap_or_default();
let remote_address = fields.get("12").cloned().unwrap_or_default();
// Parse capabilities (if available)
let remote_capabilities = Vec::new(); // TODO: Parse from lldpRemSysCapEnabled
Some(NeighborDiscovery {
interface_id,
protocol: "lldp".to_string(),
remote_chassis_id,
remote_system_name,
remote_system_description,
remote_platform: String::new(),
remote_port_id,
remote_port_description,
remote_address,
remote_capabilities,
timestamp: Timestamp::now(),
})
}
/// Build CDP neighbor from fields
fn build_cdp_neighbor(
fields: HashMap<String, String>,
interfaces: &[(String, u32)],
) -> Option<NeighborDiscovery> {
// Field IDs from CISCO-CDP-MIB
// 4 = cdpCacheAddressType
// 5 = cdpCacheAddress
// 6 = cdpCacheVersion
// 7 = cdpCacheDeviceId
// 8 = cdpCacheDevicePort
// 9 = cdpCachePlatform
// 10 = cdpCacheCapabilities
let if_index_str = fields.get("if_index")?;
let if_index: u32 = if_index_str.parse().ok()?;
let interface_id = interfaces
.iter()
.find(|(_, idx)| *idx == if_index)
.map(|(id, _)| id.clone())?;
let remote_chassis_id = fields.get("7").cloned().unwrap_or_default();
let remote_system_name = remote_chassis_id.clone();
let remote_system_description = fields.get("6").cloned().unwrap_or_default();
let remote_platform = fields.get("9").cloned().unwrap_or_default();
let remote_port_id = fields.get("8").cloned().unwrap_or_default();
let remote_address = fields.get("5").cloned().unwrap_or_default();
// Parse capabilities (if available)
let remote_capabilities = Vec::new(); // TODO: Parse from cdpCacheCapabilities
Some(NeighborDiscovery {
interface_id,
protocol: "cdp".to_string(),
remote_chassis_id,
remote_system_name,
remote_system_description,
remote_platform,
remote_port_id,
remote_port_description: String::new(),
remote_address,
remote_capabilities,
timestamp: Timestamp::now(),
})
}