enh(lib/generic): order in leafs has its importance and snmpbulkwalk is almost implemented

This commit is contained in:
David Boucher 2025-02-16 16:23:12 +01:00
parent 66abd1c93c
commit 5318cb5c38
3 changed files with 122 additions and 30 deletions

View File

@ -1,7 +1,7 @@
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
use lib::{r_snmp_walk, SnmpResult}; use lib::{r_snmp_bulk_walk, SnmpResult};
use serde::Deserialize; use serde::Deserialize;
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
@ -54,16 +54,16 @@ pub struct CmdResult {
impl Command { impl Command {
pub fn execute(&self, target: &str) -> CmdResult { pub fn execute(&self, target: &str) -> CmdResult {
let mut agregation = ("", Operation::None); let mut agregation = ("", 0, Operation::None);
let mut res: Option<(&str, SnmpResult)> = None; let mut res: Option<(&str, SnmpResult)> = None;
for entry in &self.leaf.entries { for (idx, entry) in self.leaf.entries.iter().enumerate() {
match entry { match entry {
Entry::Agregation(op) => { Entry::Agregation(op) => {
agregation = (&op.name, op.op); agregation = (&op.name, idx, op.op);
} }
Entry::Query(query) => match query.query { Entry::Query(query) => match query.query {
QueryType::Walk => { QueryType::Walk => {
res = Some((&query.name, r_snmp_walk(target, &query.oid))); res = Some((&query.name, r_snmp_bulk_walk(target, &query.oid)));
} }
}, },
} }
@ -80,16 +80,16 @@ impl Command {
idx += 1; idx += 1;
}); });
let count = values.len(); let count = values.len();
let ag = match agregation.1 { let ag = match agregation.2 {
Operation::Average => { Operation::Average => {
let sum: f32 = values.iter().sum(); let sum: f32 = values.iter().sum();
Some((agregation.0, sum / values.len() as f32)) Some((agregation.0, agregation.1, sum / values.len() as f32))
} }
_ => None, _ => None,
}; };
let metrics = self.build_metrics(&labels, &values, &ag); let metrics = self.build_metrics(&labels, &values, &ag);
let status = self.build_status(); let status = self.build_status();
let output = self.build_output(&ag, count, status, &metrics); let output = self.build_output(count, status, &metrics);
return CmdResult { status, output }; return CmdResult { status, output };
} }
None => { None => {
@ -107,10 +107,9 @@ impl Command {
fn build_output( fn build_output(
&self, &self,
ag: &Option<(&str, f32)>,
count: usize, count: usize,
status: i32, status: i32,
metrics: &BTreeMap<String, f32>, metrics: &Vec<(String, f32)>,
) -> String { ) -> String {
let mut retval = self let mut retval = self
.leaf .leaf
@ -125,12 +124,6 @@ impl Command {
_ => "UNKNOWN", _ => "UNKNOWN",
}, },
); );
match ag {
Some(a) => {
retval = retval.replace(format!("{{{}}}", a.0).as_str(), a.1.to_string().as_str());
}
None => {}
};
retval += " |"; retval += " |";
metrics.iter().for_each(|(k, v)| { metrics.iter().for_each(|(k, v)| {
retval += format!(" {}={}", k, v).as_str(); retval += format!(" {}={}", k, v).as_str();
@ -142,15 +135,25 @@ impl Command {
&self, &self,
labels: &Vec<String>, labels: &Vec<String>,
values: &Vec<f32>, values: &Vec<f32>,
ag: &Option<(&str, f32)>, ag: &Option<(&str, usize, f32)>,
) -> BTreeMap<String, f32> { ) -> Vec<(String, f32)> {
let mut metrics: BTreeMap<String, f32> = BTreeMap::new(); let mut metrics: Vec<(String, f32)> = Vec::new();
match ag {
Some(a) => {
if a.1 == 0 {
metrics.push((a.0.to_string(), a.2));
}
}
None => (),
}
values.iter().enumerate().for_each(|(i, v)| { values.iter().enumerate().for_each(|(i, v)| {
metrics.insert(labels[i].clone(), *v); metrics.push((labels[i].clone(), *v));
}); });
match ag { match ag {
Some(a) => { Some(a) => {
metrics.insert(a.0.to_string(), a.1); if a.1 > 0 {
metrics.push((a.0.to_string(), a.2));
}
} }
None => (), None => (),
} }

View File

@ -6,13 +6,13 @@ extern crate regex;
use log::{info, trace, warn}; use log::{info, trace, warn};
use rasn::types::ObjectIdentifier; use rasn::types::ObjectIdentifier;
use rasn_snmp::v2::GetNextRequest;
use rasn_snmp::v2::GetRequest;
use rasn_snmp::v2::Pdu;
use rasn_snmp::v2::Pdus; use rasn_snmp::v2::Pdus;
use rasn_snmp::v2::VarBind; use rasn_snmp::v2::VarBind;
use rasn_snmp::v2::VarBindValue; use rasn_snmp::v2::VarBindValue;
use rasn_snmp::v2::{BulkPdu, Pdu};
use rasn_snmp::v2::{GetBulkRequest, GetNextRequest, GetRequest};
use rasn_snmp::v2c::Message; use rasn_snmp::v2c::Message;
use regex::Regex;
use std::ffi::CStr; use std::ffi::CStr;
use std::ffi::CString; use std::ffi::CString;
use std::net::UdpSocket; use std::net::UdpSocket;
@ -174,7 +174,7 @@ pub fn r_snmp_get(target: &str, oid: &str, community: &str) -> SnmpResult {
trace!("Received {} bytes", resp.0); trace!("Received {} bytes", resp.0);
assert!(resp.0 > 0); assert!(resp.0 > 0);
let decoded: Message<Pdus> = rasn::ber::decode(&buf[0..resp.0]).unwrap(); let decoded: Message<Pdus> = rasn::ber::decode(&buf[0..resp.0]).unwrap();
build_response(decoded) build_response(decoded, false)
} }
#[no_mangle] #[no_mangle]
@ -254,19 +254,95 @@ pub fn r_snmp_walk(target: &str, oid: &str) -> SnmpResult {
} }
message = create_next_request(request_id, &resp_oid); message = create_next_request(request_id, &resp_oid);
} }
retval.concat(build_response(decoded)); retval.concat(build_response(decoded, true));
request_id += 1; request_id += 1;
} }
retval retval
} }
fn build_response(decoded: Message<Pdus>) -> SnmpResult { ///
/// Bulk walk
/// This function is similar to the walk function but it uses the GetBulkRequest PDU
/// to retrieve multiple values at once.
///
/// # Arguments
/// * `target` - The target IP address and port
/// * `oid` - The OID to walk
/// # Returns
/// An SnmpResult structure containing the variables
///
/// # Example
/// ```
/// use snmp_rust::r_snmp_bulk_walk;
/// let result = r_snmp_bulk_walk("127.0.0.1:161", "1.3.6.1.2.1.25.3.3.1.2");
/// ```
pub fn r_snmp_bulk_walk(target: &str, oid: &str) -> SnmpResult {
let community = "public";
let oid_tab = oid
.split('.')
.map(|x| x.parse::<u32>().unwrap())
.collect::<Vec<u32>>();
let mut retval = SnmpResult::new();
let mut request_id: i32 = 1;
let variable_bindings = vec![VarBind {
name: ObjectIdentifier::new_unchecked(oid_tab.to_vec().into()),
value: VarBindValue::Unspecified,
}];
let pdu = BulkPdu {
request_id,
non_repeaters: 0,
max_repetitions: 10,
variable_bindings,
};
let get_request: GetBulkRequest = GetBulkRequest(pdu);
let message: Message<GetBulkRequest> = Message {
version: 1.into(),
community: community.into(),
data: get_request.into(),
};
// Send the message through an UDP socket
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
socket.connect(target).expect("connect function failed");
let encoded: Vec<u8> = rasn::der::encode(&message).unwrap();
let res: usize = socket.send(&encoded).unwrap();
assert!(res == encoded.len());
let mut buf: [u8; 1024] = [0; 1024];
let resp: (usize, std::net::SocketAddr) = socket.recv_from(buf.as_mut_slice()).unwrap();
trace!("Received {} bytes", resp.0);
assert!(resp.0 > 0);
let decoded: Message<Pdus> = rasn::ber::decode(&buf[0..resp.0]).unwrap();
if let Pdus::Response(resp) = &decoded.data {
let resp_oid = &resp.0.variable_bindings[0].name;
let n = resp_oid.len() - 1;
}
retval.concat(build_response(decoded, true));
retval
}
fn build_response(decoded: Message<Pdus>, walk: bool) -> SnmpResult {
let mut retval = SnmpResult::new(); let mut retval = SnmpResult::new();
if let Pdus::Response(resp) = &decoded.data { if let Pdus::Response(resp) = &decoded.data {
let vars = &resp.0.variable_bindings; let vars = &resp.0.variable_bindings;
let mut header = "".to_string();
for var in vars { for var in vars {
let name = var.name.to_string(); let name = var.name.to_string();
if walk {
if header == "" {
// We remove from name the last number after the last "." to get the header.
let n = name.rfind('.').unwrap();
header = name[0..n].to_string();
} else if !name.starts_with(&header) {
break;
}
}
match &var.value { match &var.value {
VarBindValue::Unspecified => { VarBindValue::Unspecified => {
warn!("Unspecified"); warn!("Unspecified");
@ -321,7 +397,7 @@ mod tests {
let expected = SnmpResult { let expected = SnmpResult {
variables: vec![SnmpVariable::new( variables: vec![SnmpVariable::new(
"1.3.6.1.2.1.1.1.0".to_string(), "1.3.6.1.2.1.1.1.0".to_string(),
"Linux CNTR-PORT-A104 6.1.0-28-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.119-1 (2024-11-22) x86_64".to_string())], "Linux CNTR-PORT-A104 6.1.0-31-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.128-1 (2025-02-07) x86_64".to_string())],
}; };
assert_eq!(result, expected); assert_eq!(result, expected);
} }
@ -338,4 +414,17 @@ mod tests {
assert!(re.is_match(&v.value)); assert!(re.is_match(&v.value));
} }
} }
#[test]
fn test_snmp_bulk_walk() {
let result = r_snmp_bulk_walk("127.0.0.1:161", "1.3.6.1.2.1.25.3.3.1.2");
let re = Regex::new(r"[0-9]+").unwrap();
assert!(result.variables.len() > 0);
for v in result.variables.iter() {
println!("{:?}", v);
let name = &v.name;
assert!(name.starts_with("1.3.6.1.2.1.25.3.3.1.2"));
assert!(re.is_match(&v.value));
}
}
} }

View File

@ -3,8 +3,8 @@
"name": "cpu", "name": "cpu",
"output": "{status}: {count} CPU(s) average usage is {total_cpu_avg} %", "output": "{status}: {count} CPU(s) average usage is {total_cpu_avg} %",
"entries": [ "entries": [
{ "Query": { "name": "cpu_{idx}", "oid": "1.3.6.1.2.1.25.3.3.1.2", "query": "Walk" }}, { "Agregation": { "name": "total_cpu_avg", "op": "Average"}},
{ "Agregation": { "name": "total_cpu_avg", "op": "Average"}} { "Query": { "name": "cpu_{idx}", "oid": "1.3.6.1.2.1.25.3.3.1.2", "query": "Walk" }}
] ]
} }
} }