From dbf66af5c0657bf34891014ca3c464748e6a2888 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 29 Jan 2026 11:28:45 -0600 Subject: [PATCH] support snmp v1 --- src/snmp/client.rs | 69 ++++++++++++++++++++++++++++++----------- src/websocket_client.rs | 27 ++++++++++++++-- 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/snmp/client.rs b/src/snmp/client.rs index 99403f8..dee70e1 100644 --- a/src/snmp/client.rs +++ b/src/snmp/client.rs @@ -24,29 +24,23 @@ impl SnmpClient { port: u16, oid: &str, ) -> SnmpResult { - // Only SNMPv2c is supported by the snmp crate - if version != "2c" { - return Err(SnmpError::RequestFailed(format!( - "Unsupported SNMP version: {}. Only 2c is supported.", - version - ))); - } - // Parse OID string to Vec let oid_parts = parse_oid(oid)?; // Clone data for the blocking task let addr = format!("{}:{}", ip_address, port); let community = community.as_bytes().to_vec(); + let version_num = parse_snmp_version(version)?; // Run SNMP operation in blocking thread pool let result = tokio::task::spawn_blocking(move || { // Create session with configurable timeout + // The snmp crate supports both v1 (0) and v2c (1) let mut session = SyncSession::new( addr.as_str(), &community, Some(Duration::from_secs(SNMP_TIMEOUT_SECS)), - 0, + version_num, ) .map_err(|_| SnmpError::NetworkUnreachable)?; @@ -84,29 +78,23 @@ impl SnmpClient { port: u16, base_oid: &str, ) -> SnmpResult> { - // Only SNMPv2c is supported by the snmp crate - if version != "2c" { - return Err(SnmpError::RequestFailed(format!( - "Unsupported SNMP version: {}. Only 2c is supported.", - version - ))); - } - // Parse OID string to Vec let base_oid_parts = parse_oid(base_oid)?; // Clone data for the blocking task let addr = format!("{}:{}", ip_address, port); let community = community.as_bytes().to_vec(); + let version_num = parse_snmp_version(version)?; // Run SNMP walk in blocking thread pool let results = tokio::task::spawn_blocking(move || { // Create session with configurable timeout + // The snmp crate supports both v1 (0) and v2c (1) let mut session = SyncSession::new( addr.as_str(), &community, Some(Duration::from_secs(SNMP_TIMEOUT_SECS)), - 0, + version_num, ) .map_err(|_| SnmpError::NetworkUnreachable)?; @@ -167,6 +155,21 @@ impl Default for SnmpClient { } } +/// Parse SNMP version string to integer for snmp crate +/// Returns: 0 for SNMPv1, 1 for SNMPv2c +fn parse_snmp_version(version: &str) -> SnmpResult { + let normalized = version.trim().to_lowercase(); + + match normalized.as_str() { + "1" | "v1" | "snmpv1" => Ok(0), + "2c" | "v2c" | "snmpv2c" | "2" | "v2" => Ok(1), + _ => Err(SnmpError::RequestFailed(format!( + "Unsupported SNMP version: '{}'. Supported versions: 1, v1, 2c, v2c", + version + ))), + } +} + /// Parse OID string like "1.3.6.1.2.1.1.1.0" to Vec fn parse_oid(oid: &str) -> SnmpResult> { oid.split('.') @@ -246,6 +249,36 @@ mod tests { assert!(format!("{:?}", client).contains("SnmpClient")); } + #[test] + fn test_parse_snmp_version_v1() { + assert_eq!(parse_snmp_version("1").unwrap(), 0); + assert_eq!(parse_snmp_version("v1").unwrap(), 0); + assert_eq!(parse_snmp_version("V1").unwrap(), 0); + assert_eq!(parse_snmp_version("snmpv1").unwrap(), 0); + } + + #[test] + fn test_parse_snmp_version_v2c() { + assert_eq!(parse_snmp_version("2c").unwrap(), 1); + assert_eq!(parse_snmp_version("2C").unwrap(), 1); + assert_eq!(parse_snmp_version("v2c").unwrap(), 1); + assert_eq!(parse_snmp_version("V2C").unwrap(), 1); + assert_eq!(parse_snmp_version("2").unwrap(), 1); + assert_eq!(parse_snmp_version("v2").unwrap(), 1); + } + + #[test] + fn test_parse_snmp_version_invalid() { + let result = parse_snmp_version("3"); + assert!(result.is_err()); + match result { + Err(SnmpError::RequestFailed(msg)) => { + assert!(msg.contains("Unsupported SNMP version")); + } + _ => panic!("Expected RequestFailed error"), + } + } + #[test] fn test_parse_oid_valid() { let result = parse_oid("1.3.6.1.2.1.1.1.0"); diff --git a/src/websocket_client.rs b/src/websocket_client.rs index 55e46ae..510ce2c 100644 --- a/src/websocket_client.rs +++ b/src/websocket_client.rs @@ -357,6 +357,23 @@ impl AgentClient { /// Execute an SNMP job and collect results. async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender) -> Result<()> { let snmp_device = job.snmp_device.ok_or("Job missing SNMP device info")?; + + // Log SNMP connection parameters for debugging (mask community for security) + let community_masked = if snmp_device.community.len() > 4 { + format!("{}***", &snmp_device.community[..2]) + } else { + "***".to_string() + }; + + crate::log_info!( + "Executing SNMP job for device {} at {}:{} (community: {}, version: {})", + job.device_id, + snmp_device.ip, + snmp_device.port, + community_masked, + snmp_device.version + ); + let mut oid_values: HashMap = HashMap::new(); let snmp_client = SnmpClient::new(); @@ -382,9 +399,12 @@ async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender } Err(e) => { crate::log_warn!( - "SNMP GET failed for device {} ({}), OID {}: {}", + "SNMP GET failed for device {} at {}:{} (version: {}, community: {}), OID {}: {}", job.device_id, snmp_device.ip, + snmp_device.port, + snmp_device.version, + community_masked, oid, e ); @@ -412,9 +432,12 @@ async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender } Err(e) => { crate::log_warn!( - "SNMP WALK failed for device {} ({}), OID {}: {}", + "SNMP WALK failed for device {} at {}:{} (version: {}, community: {}), OID {}: {}", job.device_id, snmp_device.ip, + snmp_device.port, + snmp_device.version, + community_masked, base_oid, e );