#snmp #monitoring #networking

snmp2

SNMP v1/v2/v3 sync/async client library with traps and MIB support

12 releases

new 0.4.2 Jan 5, 2025
0.4.1 Jan 1, 2025
0.3.8 Dec 29, 2024

#87 in Authentication

Download history 472/week @ 2024-12-25 431/week @ 2025-01-01

903 downloads per month

MIT/Apache

100KB
2.5K SLoC

RUST-SNMP crates.io page docs.rs page GitHub Actions CI

Dependency-free basic SNMP v1/v2/v3 client in Rust.

This is a fork of the original snmp crate which has been abandoned long time ago.

SNMP2 is a part of RoboPLC project.

New features added to the fork:

  • SNMP v1 support (including v1 traps)
  • SNMP v3 authentication (MD5, SHA1, SHA224, SHA256, SHA384, SHA512)
  • SNMP v3 privacy (DES, AES128, AES192, AES256)
  • MIBs support (requires mibs feature and libnetsnmp library installed)
  • Async session (requires tokio feature)
  • Crate code has been refactored and cleaned up
  • OIDs have been migrated to asn1
  • Improved PDU API, added trap handling examples

Supports:

  • GET
  • GETNEXT
  • GETBULK
  • SET
  • Basic SNMP v1/v2 types
  • Synchronous/Asynchronous requests
  • UDP transport
  • MIBs (with mibs feature, requires libnetsnmp)
  • SNMP v3 (requires v3 feature)

Examples

GET NEXT

use std::time::Duration;
use snmp2::{SyncSession, Value, Oid};

let sys_descr_oid = Oid::from(&[1,3,6,1,2,1,1,1,]).unwrap();
let agent_addr    = "198.51.100.123:161";
let community     = b"f00b4r";
let timeout       = Duration::from_secs(2);

let mut sess = SyncSession::new_v2c(agent_addr, community, Some(timeout), 0).unwrap();
let mut response = sess.getnext(&sys_descr_oid).unwrap();
if let Some((_oid, Value::OctetString(sys_descr))) = response.varbinds.next() {
    println!("myrouter sysDescr: {}", String::from_utf8_lossy(sys_descr));
}

GET BULK

use std::time::Duration;
use snmp2::{SyncSession, Oid};

let system_oid      = Oid::from(&[1,3,6,1,2,1,1,]).unwrap();
let agent_addr      = "[2001:db8:f00:b413::abc]:161";
let community       = b"f00b4r";
let timeout         = Duration::from_secs(2);
let non_repeaters   = 0;
let max_repetitions = 7; // number of items in "system" OID

let mut sess = SyncSession::new_v2c(agent_addr, community, Some(timeout), 0).unwrap();
let response = sess.getbulk(&[&system_oid], non_repeaters, max_repetitions).unwrap();

for (name, val) in response.varbinds {
    println!("{} => {:?}", name, val);
}

SET

use std::time::Duration;
use snmp2::{SyncSession, Value, Oid};

let syscontact_oid  = Oid::from(&[1,3,6,1,2,1,1,4,0]).unwrap();
let contact         = Value::OctetString(b"Thomas A. Anderson");
let agent_addr      = "[2001:db8:f00:b413::abc]:161";
let community       = b"f00b4r";
let timeout         = Duration::from_secs(2);

let mut sess = SyncSession::new_v2c(agent_addr, community, Some(timeout), 0).unwrap();
let response = sess.set(&[(&syscontact_oid, contact)]).unwrap();

assert_eq!(response.error_status, snmp2::snmp::ERRSTATUS_NOERROR);
for (name, val) in response.varbinds {
    println!("{} => {:?}", name, val);
}

TRAPS

use std::net::UdpSocket;
use snmp2::Pdu;

let socket = UdpSocket::bind("0.0.0.0:1162").expect("Could not bind socket");
loop {
    let mut buf = [0; 1500];
    let size = socket.recv(&mut buf).expect("Could not receive data");
    let data = &buf[..size];
    let pdu = Pdu::from_bytes(data).expect("Could not parse PDU");
    println!("Version: {}", pdu.version().unwrap());
    println!("Community: {}", std::str::from_utf8(pdu.community).unwrap());
    for (name, value) in pdu.varbinds {
        println!("{}={:?}", name, value);
    }
}

Async session

use std::time::Duration;
use snmp2::{AsyncSession, Value, Oid};

async fn get_next() {
    // timeouts should be handled by the caller with `tokio::time::timeout`
    let sys_descr_oid = Oid::from(&[1,3,6,1,2,1,1,1,]).unwrap();
    let agent_addr    = "198.51.100.123:161";
    let community     = b"f00b4r";
    let mut sess = AsyncSession::new_v2c(agent_addr, community, 0).await.unwrap();
    let mut response = sess.getnext(&sys_descr_oid).await.unwrap();
    if let Some((_oid, Value::OctetString(sys_descr))) = response.varbinds.next() {
        println!("myrouter sysDescr: {}", String::from_utf8_lossy(sys_descr));
    }
}

Working with MIBs

Prepare the system

apt-get install libsnmp-dev snmp-mibs-downloader
use snmp2::{mibs::{self, MibConversion as _}, Oid};

mibs::init(&mibs::Config::new().mibs(&["./ibmConvergedPowerSystems.mib"]))
    .unwrap();
let snmp_oid = Oid::from(&[1, 3, 6, 1, 4, 1, 2, 6, 201, 3]).unwrap();
let name = snmp_oid.mib_name().unwrap();
assert_eq!(name, "IBM-CPS-MIB::cpsSystemSendTrap");
let snmp_oid2 = Oid::from_mib_name(&name).unwrap();
assert_eq!(snmp_oid, snmp_oid2);

SNMPv3

  • Requires v3 crate feature.

  • All cryptographic algorithms are provided by openssl.

  • For authentication, supports: MD5 (RFC3414), SHA1 (RFC3414) and non-standard SHA224, SHA256, SHA384, SHA512.

  • For privacy, supports: DES (RFC3414), AES128-CFB (RFC3826) and non-standard AES192-CFB, AES256-CFB. Additional/different AES modes are not supported and may require patching the crate.

Note: DES legacy encryption may be disabled in openssl by default or even not supported at all. Refer to the library documentation how to enable it.

Example

Authentication: SHA1, encryption: AES128-CFB

use snmp2::{SyncSession, v3, Oid};
use std::time::Duration;

// the security parameters also keep authoritative engine ID and boot/time
// counters. these can be either set or resolved/updated automatically.
let security = v3::Security::new(b"public", b"secure")
    .with_auth_protocol(v3::AuthProtocol::Sha1)
    .with_auth(v3::Auth::AuthPriv {
        cipher: v3::Cipher::Aes128,
        privacy_password: b"secure-encrypt".to_vec(),
    });
let mut sess =
    SyncSession::new_v3("192.168.1.1:161", Some(Duration::from_secs(2)), 0, security).unwrap();
// In case if engine_id is not provided in security parameters, it is necessary
// to call init() method to send a blank unauthenticated request to the target
// to get the engine_id.
sess.init().unwrap();
loop {
    let res = match sess.get(&Oid::from(&[1, 3, 6, 1, 2, 1, 1, 3, 0]).unwrap()) {
        Ok(r) => r,
        // In case if the engine boot / time counters are not set in the security parameters or
        // they have been changed on the target, e.g. after a reboot, the session returns
        // an error with the AuthUpdated code. In this case, security parameters are automatically
        // updated and the request should be repeated.
        Err(snmp2::Error::AuthUpdated) => continue,
        Err(e) => panic!("{}", e),
    };
    println!("{} {:?}", res.version().unwrap(), res.varbinds);
    std::thread::sleep(Duration::from_secs(1));
}

Building

In case of problems (e.g. with cross-rs), add openssl with vendored feature:

cargo add openssl --features vendored

FIPS-140 support

The crate uses openssl cryptography only and becomes FIPS-140 compliant as soon as FIPS mode is activated in openssl. Refer to the openssl crate crate and openssl library documentation for more details.

MSRV

1.68.0

Copyright 2016-2018 Hroi Sigurdsson

Copyright 2024 Serhij Symonenko, Bohemia Automation Limited

Licensed under the Apache License, Version 2.0 or the MIT license, at your option. This file may not be copied, modified, or distributed except according to those terms.

Dependencies

~2–11MB
~112K SLoC