async-snmp

Crates.ioasync-snmp
lib.rsasync-snmp
version0.5.0
created_at2026-01-01 00:26:38.471931+00
updated_at2026-01-18 11:03:05.051341+00
descriptionModern async-first SNMP client library for Rust
homepage
repositoryhttps://github.com/lukeod/async-snmp
max_upload_size
id2015540
size1,341,085
lukeod (lukeod)

documentation

https://docs.rs/async-snmp

README

async-snmp

CI Crates.io Documentation MSRV License

Modern, async-first SNMP client library for Rust.

Note

This library is not currently stable. While pre v1.0, breaking changes are likely to occur frequently, no attempt will be made to maintain backward compatibility pre-1.0.

Features

  • Full protocol support: SNMPv1, v2c, and v3 (USM)
  • Async-first: Built on Tokio for high-performance async I/O
  • All operations: GET, GETNEXT, GETBULK, SET, WALK, BULKWALK
  • SNMPv3 security: MD5/SHA-1/SHA-2 authentication, DES/3DES/AES-128/192/256 privacy
  • Multiple transports: UDP, TCP, and shared UDP for scalable polling
  • Zero-copy decoding: Minimal allocations using bytes crate
  • Type-safe: Compile-time OID validation with oid! macro

Protocol Support Matrix

Feature v1 v2c v3
GET / GETNEXT Y Y Y
GETBULK - Y Y
SET Y Y Y
WALK / BULKWALK Y Y Y
Receive Traps Y Y Y
Receive Informs - Y Y

SNMPv3 Security

Authentication: MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512

Privacy: DES, 3DES, AES-128, AES-192, AES-256

Installation

cargo add async-snmp

Or add to your Cargo.toml:

[dependencies]
async-snmp = "0.5"

Quick Start

SNMPv2c

use async_snmp::{Auth, Client, oid};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), async_snmp::Error> {
    let client = Client::builder("192.168.1.1:161", Auth::v2c("public"))
        .timeout(Duration::from_secs(5))
        .connect()
        .await?;

    let result = client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await?;
    println!("sysDescr: {:?}", result.value);

    Ok(())
}

SNMPv3 with Authentication and Privacy

use async_snmp::{Auth, Client, oid, v3::{AuthProtocol, PrivProtocol}};

#[tokio::main]
async fn main() -> Result<(), async_snmp::Error> {
    let client = Client::builder("192.168.1.1:161",
        Auth::usm("admin")
            .auth(AuthProtocol::Sha256, "authpass123")
            .privacy(PrivProtocol::Aes128, "privpass123"))
        .connect()
        .await?;

    let result = client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await?;
    println!("sysDescr: {:?}", result.value);

    Ok(())
}

Walking a Subtree

use async_snmp::{Auth, Client, oid};
use futures::StreamExt;

#[tokio::main]
async fn main() -> Result<(), async_snmp::Error> {
    let client = Client::builder("192.168.1.1:161", Auth::v2c("public"))
        .connect()
        .await?;

    // Walk the system subtree
    let mut walk = client.walk(oid!(1, 3, 6, 1, 2, 1, 1))?;
    while let Some(result) = walk.next().await {
        let vb = result?;
        println!("{}: {:?}", vb.oid, vb.value);
    }

    Ok(())
}

Scalable Polling (Shared Transport)

For monitoring systems polling thousands of targets, share a single UDP socket across all clients. This provides significant resource efficiency without sacrificing throughput:

use async_snmp::{Auth, Client, UdpTransport, oid};

#[tokio::main]
async fn main() -> Result<(), async_snmp::Error> {
    // Single dual-stack socket shared across all clients
    let shared = UdpTransport::bind("[::]:0").await?;

    let targets = vec!["192.168.1.1:161", "192.168.1.2:161", "192.168.1.3:161"];

    let clients: Vec<_> = targets.iter()
        .map(|t| {
            Client::builder(*t, Auth::v2c("public"))
                .build_with(&shared)
        })
        .collect::<Result<_, _>>()?;

    // Poll all targets concurrently - sharing one UDP socket
    let results = futures::future::join_all(
        clients.iter().map(|c| c.get(&oid!(1, 3, 6, 1, 2, 1, 1, 3, 0)))
    ).await;

    for (client, result) in clients.iter().zip(results) {
        match result {
            Ok(vb) => println!("{}: {:?}", client.peer_addr(), vb.value),
            Err(e) => eprintln!("{}: {}", client.peer_addr(), e),
        }
    }

    Ok(())
}

Benefits of shared transport:

  • 1 file descriptor for all targets (vs 1 per target with separate sockets)
  • Firewall session reuse between polls to the same target
  • Lower memory from shared socket buffers
  • No per-poll socket creation overhead

Scaling guidance:

Approach When to use
Single shared socket Recommended for most use cases
Multiple shared sockets Extreme scale (~100,000s+ targets), shard by target
Per-client socket (.connect()) When scrape isolation is required (has FD and syscall overhead)

Tracing

The library uses the tracing crate for structured logging. Filter by target:

# All library logs at debug level
RUST_LOG=async_snmp=debug cargo run

# Trace client operations only
RUST_LOG=async_snmp::client=trace cargo run

# Debug transport layer
RUST_LOG=async_snmp::transport=debug cargo run

Available targets:

  • Core: async_snmp::client, async_snmp::agent, async_snmp::notification
  • Protocol: async_snmp::ber, async_snmp::pdu, async_snmp::oid, async_snmp::value
  • SNMPv3: async_snmp::v3, async_snmp::usm, async_snmp::crypto, async_snmp::engine
  • Transport: async_snmp::transport, async_snmp::transport::tcp, async_snmp::transport::udp
  • Operations: async_snmp::walk, async_snmp::error

Documentation

Full API documentation is available on docs.rs.

Feature Flags

Feature Description
cli CLI utilities (asnmp-get, asnmp-walk, asnmp-set)

Minimum Supported Rust Version

This crate requires Rust 1.88 or later. The MSRV may be increased in minor version releases.

License

Licensed under either of:

at your option.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Commit count: 113

cargo fmt