async_snmp/lib.rs
1// Allow large error types - the Error enum includes OIDs inline for debugging convenience.
2// Boxing them would add complexity and allocations for a marginal size reduction.
3#![allow(clippy::result_large_err)]
4
5//! # async-snmp
6//!
7//! Modern, async-first SNMP client library for Rust.
8//!
9//! ## Features
10//!
11//! - Full SNMPv1, v2c, and v3 support
12//! - Async-first API built on Tokio
13//! - Zero-copy BER encoding/decoding
14//! - Type-safe OID and value handling
15//! - Config-driven client construction
16//!
17//! ## Quick Start
18//!
19//! ```rust,no_run
20//! use async_snmp::{Auth, Client, oid};
21//! use std::time::Duration;
22//!
23//! #[tokio::main]
24//! async fn main() -> Result<(), async_snmp::Error> {
25//! // SNMPv2c client
26//! let client = Client::builder("192.168.1.1:161", Auth::v2c("public"))
27//! .timeout(Duration::from_secs(5))
28//! .connect()
29//! .await?;
30//!
31//! let result = client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await?;
32//! println!("sysDescr: {:?}", result.value);
33//!
34//! Ok(())
35//! }
36//! ```
37//!
38//! ## SNMPv3 Example
39//!
40//! ```rust,no_run
41//! use async_snmp::{Auth, Client, oid, v3::{AuthProtocol, PrivProtocol}};
42//!
43//! #[tokio::main]
44//! async fn main() -> Result<(), async_snmp::Error> {
45//! let client = Client::builder("192.168.1.1:161",
46//! Auth::usm("admin")
47//! .auth(AuthProtocol::Sha256, "authpass123")
48//! .privacy(PrivProtocol::Aes128, "privpass123"))
49//! .connect()
50//! .await?;
51//!
52//! let result = client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await?;
53//! println!("sysDescr: {:?}", result.value);
54//!
55//! Ok(())
56//! }
57//! ```
58//!
59//! # Advanced Topics
60//!
61//! ## Error Handling Patterns
62//!
63//! The library provides detailed error information for debugging and recovery.
64//! See the [`error`] module for complete documentation.
65//!
66//! ```rust,no_run
67//! use async_snmp::{Auth, Client, Error, ErrorStatus, oid};
68//! use std::time::Duration;
69//!
70//! async fn poll_device(addr: &str) -> Result<String, String> {
71//! let client = Client::builder(addr, Auth::v2c("public"))
72//! .timeout(Duration::from_secs(5))
73//! .retries(2)
74//! .connect()
75//! .await
76//! .map_err(|e| format!("Failed to connect: {}", e))?;
77//!
78//! match client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await {
79//! Ok(vb) => Ok(vb.value.as_str().unwrap_or("(non-string)").to_string()),
80//! Err(Error::Timeout { retries, .. }) => {
81//! Err(format!("Device unreachable after {} retries", retries))
82//! }
83//! Err(Error::Snmp { status: ErrorStatus::NoSuchName, .. }) => {
84//! Err("OID not supported by device".to_string())
85//! }
86//! Err(e) => Err(format!("SNMP error: {}", e)),
87//! }
88//! }
89//! ```
90//!
91//! ## Concurrent Operations
92//!
93//! Use standard async patterns to poll multiple devices concurrently.
94//! The [`SharedUdpTransport`] is recommended for polling many targets.
95//!
96//! ```rust,no_run
97//! use async_snmp::{Auth, Client, SharedUdpTransport, oid};
98//! use futures::future::join_all;
99//!
100//! async fn poll_many_devices(targets: Vec<String>) -> Vec<(String, Result<String, String>)> {
101//! // Create a shared transport for efficient socket usage
102//! let transport = SharedUdpTransport::bind("0.0.0.0:0")
103//! .await
104//! .expect("failed to bind");
105//!
106//! let sys_descr = oid!(1, 3, 6, 1, 2, 1, 1, 1, 0);
107//!
108//! // Spawn concurrent requests
109//! let futures: Vec<_> = targets.iter().map(|target| {
110//! let handle = transport.handle(target.parse().expect("invalid addr"));
111//! let oid = sys_descr.clone();
112//! let target = target.clone();
113//! async move {
114//! let client = match Client::builder(target.clone(), Auth::v2c("public"))
115//! .build(handle) {
116//! Ok(c) => c,
117//! Err(e) => return (target, Err(e.to_string())),
118//! };
119//! let result: Result<String, String> = match client.get(&oid).await {
120//! Ok(vb) => Ok(vb.value.to_string()),
121//! Err(e) => Err(e.to_string()),
122//! };
123//! (target, result)
124//! }
125//! }).collect();
126//!
127//! join_all(futures).await
128//! }
129//! ```
130//!
131//! ## High-Throughput SNMPv3 Polling
132//!
133//! SNMPv3 has two expensive per-connection operations:
134//! - **Password derivation**: ~850μs to derive keys from passwords (SHA-256)
135//! - **Engine discovery**: Round-trip to learn the agent's engine ID and time
136//!
137//! For polling many targets with shared credentials, cache both:
138//!
139//! ```rust,no_run
140//! use async_snmp::{Auth, AuthProtocol, Client, EngineCache, MasterKeys, PrivProtocol};
141//! use async_snmp::{SharedUdpTransport, oid};
142//! use std::sync::Arc;
143//!
144//! # async fn example() -> async_snmp::Result<()> {
145//! // 1. Derive master keys once (expensive: ~850μs)
146//! let master_keys = MasterKeys::new(AuthProtocol::Sha256, b"authpassword")
147//! .with_privacy(PrivProtocol::Aes128, b"privpassword");
148//!
149//! // 2. Share engine discovery results across clients
150//! let engine_cache = Arc::new(EngineCache::new());
151//!
152//! // 3. Use shared transport for socket efficiency
153//! let transport = SharedUdpTransport::bind("0.0.0.0:0").await?;
154//!
155//! // Poll multiple targets - only ~1μs key localization per engine
156//! for target in ["192.0.2.1:161", "192.0.2.2:161"] {
157//! let handle = transport.handle(target.parse().unwrap());
158//! let auth = Auth::usm("snmpuser").with_master_keys(master_keys.clone());
159//!
160//! let client = Client::builder(target, auth)
161//! .engine_cache(engine_cache.clone())
162//! .build(handle)?;
163//!
164//! let result = client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await?;
165//! println!("{}: {:?}", target, result.value);
166//! }
167//! # Ok(())
168//! # }
169//! ```
170//!
171//! | Optimization | Without | With | Savings |
172//! |--------------|---------|------|---------|
173//! | `MasterKeys` | 850μs/engine | 1μs/engine | ~99.9% |
174//! | `EngineCache` | 1 RTT/engine | 0 RTT (cached) | 1 RTT |
175//! | `SharedUdpTransport` | 1 socket/target | 1 socket total | FD limits |
176//!
177//! ## Graceful Shutdown
178//!
179//! Use `tokio::select!` or cancellation tokens for clean shutdown.
180//!
181//! ```rust,no_run
182//! use async_snmp::{Auth, Client, oid};
183//! use std::time::Duration;
184//! use tokio::time::interval;
185//!
186//! async fn poll_with_shutdown(
187//! addr: &str,
188//! mut shutdown: tokio::sync::oneshot::Receiver<()>,
189//! ) {
190//! let client = Client::builder(addr, Auth::v2c("public"))
191//! .connect()
192//! .await
193//! .expect("failed to connect");
194//!
195//! let sys_uptime = oid!(1, 3, 6, 1, 2, 1, 1, 3, 0);
196//! let mut poll_interval = interval(Duration::from_secs(30));
197//!
198//! loop {
199//! tokio::select! {
200//! _ = &mut shutdown => {
201//! println!("Shutdown signal received");
202//! break;
203//! }
204//! _ = poll_interval.tick() => {
205//! match client.get(&sys_uptime).await {
206//! Ok(vb) => println!("Uptime: {:?}", vb.value),
207//! Err(e) => eprintln!("Poll failed: {}", e),
208//! }
209//! }
210//! }
211//! }
212//! }
213//! ```
214//!
215//! ## Tracing Integration
216//!
217//! The library uses the `tracing` crate for structured logging. All SNMP
218//! operations emit spans and events with relevant context.
219//!
220//! ### Basic Setup
221//!
222//! ```rust,no_run
223//! use async_snmp::{Auth, Client, oid};
224//! use tracing_subscriber::EnvFilter;
225//!
226//! #[tokio::main]
227//! async fn main() {
228//! tracing_subscriber::fmt()
229//! .with_env_filter(
230//! EnvFilter::from_default_env()
231//! .add_directive("async_snmp=debug".parse().unwrap())
232//! )
233//! .init();
234//!
235//! let client = Client::builder("192.168.1.1:161", Auth::v2c("public"))
236//! .connect()
237//! .await
238//! .expect("failed to connect");
239//!
240//! // Logs: DEBUG async_snmp::client snmp.target=192.168.1.1:161 snmp.request_id=12345
241//! let _ = client.get(&oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)).await;
242//! }
243//! ```
244//!
245//! ### Log Levels
246//!
247//! | Level | What's Logged |
248//! |-------|---------------|
249//! | ERROR | Socket errors, fatal transport failures |
250//! | WARN | Auth failures, parse errors, source address mismatches |
251//! | INFO | Connect/disconnect, walk completion |
252//! | DEBUG | Request/response flow, engine discovery, retries |
253//! | TRACE | Auth verification, raw packet data |
254//!
255//! ### Structured Fields
256//!
257//! All fields use the `snmp.` prefix for easy filtering:
258//!
259//! | Field | Description |
260//! |-------|-------------|
261//! | `snmp.target` | Target address for outgoing requests |
262//! | `snmp.source` | Source address of incoming messages |
263//! | `snmp.request_id` | SNMP request identifier |
264//! | `snmp.retries` | Current retry attempt number |
265//! | `snmp.elapsed_ms` | Request duration in milliseconds |
266//! | `snmp.pdu_type` | PDU type (Get, GetNext, etc.) |
267//! | `snmp.varbind_count` | Number of varbinds in request/response |
268//! | `snmp.error_status` | SNMP error status from response |
269//! | `snmp.error_index` | Index of problematic varbind |
270//! | `snmp.non_repeaters` | GETBULK non-repeaters parameter |
271//! | `snmp.max_repetitions` | GETBULK max-repetitions parameter |
272//! | `snmp.username` | SNMPv3 USM username |
273//! | `snmp.security_level` | SNMPv3 security level |
274//! | `snmp.engine_id` | SNMPv3 engine identifier (hex) |
275//! | `snmp.local_addr` | Local bind address |
276//!
277//! ### Filtering Examples
278//!
279//! ```bash
280//! # See all async-snmp logs at debug level
281//! RUST_LOG=async_snmp=debug cargo run
282//!
283//! # Only see retries and errors
284//! RUST_LOG=async_snmp=warn cargo run
285//!
286//! # Trace a specific module
287//! RUST_LOG=async_snmp::client=trace cargo run
288//! ```
289//!
290//! ## Agent Compatibility
291//!
292//! Real-world SNMP agents often have quirks. This library provides several
293//! options to handle non-conformant implementations.
294//!
295//! ### Walk Issues
296//!
297//! | Problem | Solution |
298//! |---------|----------|
299//! | GETBULK returns errors or garbage | Use [`WalkMode::GetNext`] |
300//! | OIDs returned out of order | Use [`OidOrdering::AllowNonIncreasing`] |
301//! | Walk never terminates | Set [`ClientBuilder::max_walk_results`] |
302//! | Slow responses cause timeouts | Reduce [`ClientBuilder::max_repetitions`] |
303//!
304//! **Warning**: [`OidOrdering::AllowNonIncreasing`] uses O(n) memory to track
305//! seen OIDs for cycle detection. Always pair it with [`ClientBuilder::max_walk_results`]
306//! to bound memory usage. The cycle detection catches duplicate OIDs, but a
307//! pathological agent could still return an infinite sequence of unique OIDs.
308//!
309//! ```rust,no_run
310//! use async_snmp::{Auth, Client, WalkMode, OidOrdering};
311//!
312//! # async fn example() -> async_snmp::Result<()> {
313//! // Configure for a problematic agent
314//! let client = Client::builder("192.168.1.1:161", Auth::v2c("public"))
315//! .walk_mode(WalkMode::GetNext) // Avoid buggy GETBULK
316//! .oid_ordering(OidOrdering::AllowNonIncreasing) // Handle out-of-order OIDs
317//! .max_walk_results(10_000) // IMPORTANT: bound memory usage
318//! .max_repetitions(10) // Smaller responses
319//! .connect()
320//! .await?;
321//! # Ok(())
322//! # }
323//! ```
324//!
325//! ### Permissive Parsing
326//!
327//! The BER decoder accepts non-conformant encodings that some agents produce:
328//! - Non-minimal integer encodings (extra leading bytes)
329//! - Non-minimal OID subidentifier encodings
330//! - Truncated values (logged as warnings)
331//!
332//! This matches net-snmp's permissive behavior.
333//!
334//! ### Unknown Value Types
335//!
336//! Unrecognized BER tags are preserved as [`Value::Unknown`] rather than
337//! causing decode errors. This provides forward compatibility with new
338//! SNMP types or vendor extensions.
339
340pub mod agent;
341pub mod ber;
342pub mod client;
343pub mod error;
344pub mod format;
345pub mod handler;
346pub mod message;
347pub mod notification;
348pub mod oid;
349pub mod pdu;
350pub mod prelude;
351pub mod transport;
352pub mod v3;
353pub mod value;
354pub mod varbind;
355pub mod version;
356
357pub(crate) mod util;
358
359#[cfg(feature = "cli")]
360pub mod cli;
361
362// Re-exports for convenience
363pub use agent::{Agent, AgentBuilder, VacmBuilder, VacmConfig, View};
364pub use client::{
365 Auth, BulkWalk, Client, ClientBuilder, ClientConfig, CommunityVersion, OidOrdering, UsmAuth,
366 UsmBuilder, V3SecurityConfig, Walk, WalkMode, WalkStream,
367};
368pub use error::{
369 AuthErrorKind, CryptoErrorKind, DecodeErrorKind, EncodeErrorKind, Error, ErrorStatus,
370 OidErrorKind, Result,
371};
372pub use handler::{
373 BoxFuture, GetNextResult, GetResult, MibHandler, OidTable, RequestContext, Response,
374 SecurityModel, SetResult,
375};
376pub use message::SecurityLevel;
377pub use notification::{
378 Notification, NotificationReceiver, NotificationReceiverBuilder, UsmUserConfig,
379 validate_notification_varbinds,
380};
381pub use oid::Oid;
382pub use pdu::{GenericTrap, Pdu, PduType, TrapV1Pdu};
383pub use transport::{SharedUdpHandle, SharedUdpTransport, TcpTransport, Transport, UdpTransport};
384pub use v3::{
385 AuthProtocol, EngineCache, LocalizedKey, MasterKey, MasterKeys, ParseProtocolError,
386 PrivProtocol,
387};
388pub use value::Value;
389pub use varbind::VarBind;
390pub use version::Version;
391
392/// Type alias for a client using the shared UDP transport.
393///
394/// This is useful for high-throughput scenarios where many clients share
395/// a single UDP socket via [`SharedUdpTransport`].
396pub type SharedClient = Client<SharedUdpHandle>;
397
398/// Type alias for a client using a dedicated UDP socket.
399///
400/// This is the default transport type, suitable for most use cases
401/// with up to ~100 concurrent targets.
402pub type UdpClient = Client<UdpTransport>;
403
404/// Type alias for a client using a TCP connection.
405pub type TcpClient = Client<TcpTransport>;
406
407/// Testing utilities exposed via the `testing` feature.
408#[cfg(feature = "testing")]
409pub mod testing {
410 pub use crate::format::hex::{Bytes as HexBytes, DecodeError, decode, encode};
411}