support snmp v1

This commit is contained in:
Graham McIntire 2026-01-29 11:28:45 -06:00
parent 0548ca313d
commit dbf66af5c0
No known key found for this signature in database
2 changed files with 76 additions and 20 deletions

View file

@ -24,29 +24,23 @@ impl SnmpClient {
port: u16,
oid: &str,
) -> SnmpResult<SnmpValue> {
// 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<u32>
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<Vec<(String, SnmpValue)>> {
// 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<u32>
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<i32> {
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<u32>
fn parse_oid(oid: &str) -> SnmpResult<Vec<u32>> {
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");

View file

@ -357,6 +357,23 @@ impl AgentClient {
/// Execute an SNMP job and collect results.
async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender<SnmpResult>) -> 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<String, String> = HashMap::new();
let snmp_client = SnmpClient::new();
@ -382,9 +399,12 @@ async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender<SnmpResult>
}
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<SnmpResult>
}
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
);