async_snmp/agent/mod.rs
1//! SNMP Agent (RFC 3413).
2//!
3//! This module provides SNMP agent functionality for responding to
4//! GET, GETNEXT, GETBULK, and SET requests.
5//!
6//! # Features
7//!
8//! - **Async handlers**: All handler methods are async for database queries, network calls, etc.
9//! - **Atomic SET**: Two-phase commit protocol (test/commit/undo) per RFC 3416
10//! - **VACM support**: Optional View-based Access Control Model (RFC 3415)
11//!
12//! # Example
13//!
14//! ```rust,no_run
15//! use async_snmp::agent::Agent;
16//! use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
17//! use async_snmp::{Oid, Value, VarBind, oid};
18//! use std::sync::Arc;
19//!
20//! // Define a simple handler for the system MIB subtree
21//! struct SystemMibHandler;
22//!
23//! impl MibHandler for SystemMibHandler {
24//! fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
25//! Box::pin(async move {
26//! // sysDescr.0
27//! if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 1, 0) {
28//! return GetResult::Value(Value::OctetString("My SNMP Agent".into()));
29//! }
30//! // sysObjectID.0
31//! if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 2, 0) {
32//! return GetResult::Value(Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 99999)));
33//! }
34//! GetResult::NoSuchObject
35//! })
36//! }
37//!
38//! fn get_next<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetNextResult> {
39//! Box::pin(async move {
40//! // Return the lexicographically next OID after the given one
41//! let sys_descr = oid!(1, 3, 6, 1, 2, 1, 1, 1, 0);
42//! let sys_object_id = oid!(1, 3, 6, 1, 2, 1, 1, 2, 0);
43//!
44//! if oid < &sys_descr {
45//! return GetNextResult::Value(VarBind::new(sys_descr, Value::OctetString("My SNMP Agent".into())));
46//! }
47//! if oid < &sys_object_id {
48//! return GetNextResult::Value(VarBind::new(sys_object_id, Value::ObjectIdentifier(oid!(1, 3, 6, 1, 4, 1, 99999))));
49//! }
50//! GetNextResult::EndOfMibView
51//! })
52//! }
53//! }
54//!
55//! #[tokio::main]
56//! async fn main() -> Result<(), async_snmp::Error> {
57//! let agent = Agent::builder()
58//! .bind("0.0.0.0:161")
59//! .community(b"public")
60//! .handler(oid!(1, 3, 6, 1, 2, 1, 1), Arc::new(SystemMibHandler))
61//! .build()
62//! .await?;
63//!
64//! agent.run().await
65//! }
66//! ```
67
68mod request;
69mod response;
70mod set_handler;
71pub mod vacm;
72
73pub use vacm::{SecurityModel, VacmBuilder, VacmConfig, View, ViewSubtree};
74
75use std::collections::HashMap;
76use std::net::SocketAddr;
77use std::sync::Arc;
78use std::sync::atomic::{AtomicU32, Ordering};
79use std::time::Instant;
80
81use bytes::Bytes;
82use subtle::ConstantTimeEq;
83use tokio::net::UdpSocket;
84use tracing::instrument;
85
86use crate::ber::Decoder;
87use crate::error::{DecodeErrorKind, Error, ErrorStatus, Result};
88use crate::handler::{GetNextResult, GetResult, MibHandler, RequestContext};
89use crate::notification::UsmUserConfig;
90use crate::oid::Oid;
91use crate::pdu::{Pdu, PduType};
92use crate::util::bind_udp_socket;
93use crate::v3::SaltCounter;
94use crate::value::Value;
95use crate::varbind::VarBind;
96use crate::version::Version;
97
98/// Default maximum message size for UDP (RFC 3417 recommendation).
99const DEFAULT_MAX_MESSAGE_SIZE: usize = 1472;
100
101/// Overhead for SNMP message encoding (approximate conservative estimate).
102/// This accounts for version, community/USM, PDU headers, etc.
103const RESPONSE_OVERHEAD: usize = 100;
104
105/// Registered handler with its OID prefix.
106pub(crate) struct RegisteredHandler {
107 pub(crate) prefix: Oid,
108 pub(crate) handler: Arc<dyn MibHandler>,
109}
110
111/// Builder for [`Agent`].
112///
113/// Use this builder to configure and construct an SNMP agent. The builder
114/// pattern allows you to chain configuration methods before calling
115/// [`build()`](AgentBuilder::build) to create the agent.
116///
117/// # Minimal Example
118///
119/// ```rust,no_run
120/// use async_snmp::agent::Agent;
121/// use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
122/// use async_snmp::{Oid, Value, VarBind, oid};
123/// use std::sync::Arc;
124///
125/// struct MyHandler;
126/// impl MibHandler for MyHandler {
127/// fn get<'a>(&'a self, _: &'a RequestContext, _: &'a Oid) -> BoxFuture<'a, GetResult> {
128/// Box::pin(async { GetResult::NoSuchObject })
129/// }
130/// fn get_next<'a>(&'a self, _: &'a RequestContext, _: &'a Oid) -> BoxFuture<'a, GetNextResult> {
131/// Box::pin(async { GetNextResult::EndOfMibView })
132/// }
133/// }
134///
135/// # async fn example() -> Result<(), async_snmp::Error> {
136/// let agent = Agent::builder()
137/// .bind("0.0.0.0:1161") // Use non-privileged port
138/// .community(b"public")
139/// .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(MyHandler))
140/// .build()
141/// .await?;
142/// # Ok(())
143/// # }
144/// ```
145pub struct AgentBuilder {
146 bind_addr: String,
147 communities: Vec<Vec<u8>>,
148 usm_users: HashMap<Bytes, UsmUserConfig>,
149 handlers: Vec<RegisteredHandler>,
150 engine_id: Option<Vec<u8>>,
151 max_message_size: usize,
152 vacm: Option<VacmConfig>,
153}
154
155impl AgentBuilder {
156 /// Create a new builder with default settings.
157 ///
158 /// Defaults:
159 /// - Bind address: `0.0.0.0:161`
160 /// - Max message size: 1472 bytes (Ethernet MTU - IP/UDP headers)
161 /// - No communities or USM users (all requests rejected)
162 /// - No handlers registered
163 pub fn new() -> Self {
164 Self {
165 bind_addr: "0.0.0.0:161".to_string(),
166 communities: Vec::new(),
167 usm_users: HashMap::new(),
168 handlers: Vec::new(),
169 engine_id: None,
170 max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
171 vacm: None,
172 }
173 }
174
175 /// Set the bind address.
176 ///
177 /// Default is `0.0.0.0:161` (standard SNMP port). Note that binding to
178 /// port 161 typically requires root/administrator privileges.
179 ///
180 /// # IPv4 Examples
181 ///
182 /// ```rust,no_run
183 /// use async_snmp::agent::Agent;
184 ///
185 /// # async fn example() -> Result<(), async_snmp::Error> {
186 /// // Bind to all IPv4 interfaces on standard port (requires privileges)
187 /// let agent = Agent::builder().bind("0.0.0.0:161").community(b"public").build().await?;
188 ///
189 /// // Bind to localhost only on non-privileged port
190 /// let agent = Agent::builder().bind("127.0.0.1:1161").community(b"public").build().await?;
191 ///
192 /// // Bind to specific interface
193 /// let agent = Agent::builder().bind("192.168.1.100:161").community(b"public").build().await?;
194 /// # Ok(())
195 /// # }
196 /// ```
197 ///
198 /// # IPv6 Examples
199 ///
200 /// ```rust,no_run
201 /// use async_snmp::agent::Agent;
202 ///
203 /// # async fn example() -> Result<(), async_snmp::Error> {
204 /// // Bind to all IPv6 interfaces (IPv6_V6ONLY is set, no IPv4 traffic)
205 /// let agent = Agent::builder().bind("[::]:161").community(b"public").build().await?;
206 ///
207 /// // Bind to IPv6 localhost
208 /// let agent = Agent::builder().bind("[::1]:1161").community(b"public").build().await?;
209 /// # Ok(())
210 /// # }
211 /// ```
212 pub fn bind(mut self, addr: impl Into<String>) -> Self {
213 self.bind_addr = addr.into();
214 self
215 }
216
217 /// Add an accepted community string for v1/v2c requests.
218 ///
219 /// Multiple communities can be added. If none are added,
220 /// all v1/v2c requests are rejected.
221 ///
222 /// # Example
223 ///
224 /// ```rust,no_run
225 /// use async_snmp::agent::Agent;
226 ///
227 /// # async fn example() -> Result<(), async_snmp::Error> {
228 /// let agent = Agent::builder()
229 /// .bind("0.0.0.0:1161")
230 /// .community(b"public") // Read-only access
231 /// .community(b"private") // Read-write access (with VACM)
232 /// .build()
233 /// .await?;
234 /// # Ok(())
235 /// # }
236 /// ```
237 pub fn community(mut self, community: &[u8]) -> Self {
238 self.communities.push(community.to_vec());
239 self
240 }
241
242 /// Add multiple community strings.
243 ///
244 /// # Example
245 ///
246 /// ```rust,no_run
247 /// use async_snmp::agent::Agent;
248 ///
249 /// # async fn example() -> Result<(), async_snmp::Error> {
250 /// let communities = ["public", "private", "monitor"];
251 /// let agent = Agent::builder()
252 /// .bind("0.0.0.0:1161")
253 /// .communities(communities)
254 /// .build()
255 /// .await?;
256 /// # Ok(())
257 /// # }
258 /// ```
259 pub fn communities<I, C>(mut self, communities: I) -> Self
260 where
261 I: IntoIterator<Item = C>,
262 C: AsRef<[u8]>,
263 {
264 for c in communities {
265 self.communities.push(c.as_ref().to_vec());
266 }
267 self
268 }
269
270 /// Add a USM user for SNMPv3 authentication.
271 ///
272 /// Configure authentication and privacy settings using the closure.
273 /// Multiple users can be added with different security levels.
274 ///
275 /// # Security Levels
276 ///
277 /// - **noAuthNoPriv**: No authentication or encryption (not recommended)
278 /// - **authNoPriv**: Authentication only (HMAC verification)
279 /// - **authPriv**: Authentication and encryption (most secure)
280 ///
281 /// # Example
282 ///
283 /// ```rust,no_run
284 /// use async_snmp::agent::Agent;
285 /// use async_snmp::{AuthProtocol, PrivProtocol};
286 ///
287 /// # async fn example() -> Result<(), async_snmp::Error> {
288 /// let agent = Agent::builder()
289 /// .bind("0.0.0.0:1161")
290 /// // Read-only user with authentication only
291 /// .usm_user("monitor", |u| {
292 /// u.auth(AuthProtocol::Sha256, b"monitorpass123")
293 /// })
294 /// // Admin user with full encryption
295 /// .usm_user("admin", |u| {
296 /// u.auth(AuthProtocol::Sha256, b"adminauth123")
297 /// .privacy(PrivProtocol::Aes128, b"adminpriv123")
298 /// })
299 /// .build()
300 /// .await?;
301 /// # Ok(())
302 /// # }
303 /// ```
304 pub fn usm_user<F>(mut self, username: impl Into<Bytes>, configure: F) -> Self
305 where
306 F: FnOnce(UsmUserConfig) -> UsmUserConfig,
307 {
308 let username_bytes: Bytes = username.into();
309 let config = configure(UsmUserConfig::new(username_bytes.clone()));
310 self.usm_users.insert(username_bytes, config);
311 self
312 }
313
314 /// Set the engine ID for SNMPv3.
315 ///
316 /// If not set, a default engine ID will be generated based on the
317 /// RFC 3411 format using enterprise number and timestamp.
318 ///
319 /// # Example
320 ///
321 /// ```rust,no_run
322 /// use async_snmp::agent::Agent;
323 ///
324 /// # async fn example() -> Result<(), async_snmp::Error> {
325 /// let agent = Agent::builder()
326 /// .bind("0.0.0.0:1161")
327 /// .engine_id(b"\x80\x00\x00\x00\x01MyEngine".to_vec())
328 /// .community(b"public")
329 /// .build()
330 /// .await?;
331 /// # Ok(())
332 /// # }
333 /// ```
334 pub fn engine_id(mut self, engine_id: impl Into<Vec<u8>>) -> Self {
335 self.engine_id = Some(engine_id.into());
336 self
337 }
338
339 /// Set the maximum message size for responses.
340 ///
341 /// Default is 1472 octets (fits Ethernet MTU minus IP/UDP headers).
342 /// GETBULK responses will be truncated to fit within this limit.
343 ///
344 /// For SNMPv3 requests, the agent uses the minimum of this value
345 /// and the msgMaxSize from the request.
346 pub fn max_message_size(mut self, size: usize) -> Self {
347 self.max_message_size = size;
348 self
349 }
350
351 /// Register a MIB handler for an OID subtree.
352 ///
353 /// Handlers are matched by longest prefix. When a request comes in,
354 /// the handler with the longest matching prefix is used.
355 ///
356 /// # Example
357 ///
358 /// ```rust,no_run
359 /// use async_snmp::agent::Agent;
360 /// use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
361 /// use async_snmp::{Oid, Value, VarBind, oid};
362 /// use std::sync::Arc;
363 ///
364 /// struct SystemHandler;
365 /// impl MibHandler for SystemHandler {
366 /// fn get<'a>(&'a self, _: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
367 /// Box::pin(async move {
368 /// if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 1, 0) {
369 /// GetResult::Value(Value::OctetString("My Agent".into()))
370 /// } else {
371 /// GetResult::NoSuchObject
372 /// }
373 /// })
374 /// }
375 /// fn get_next<'a>(&'a self, _: &'a RequestContext, _: &'a Oid) -> BoxFuture<'a, GetNextResult> {
376 /// Box::pin(async { GetNextResult::EndOfMibView })
377 /// }
378 /// }
379 ///
380 /// # async fn example() -> Result<(), async_snmp::Error> {
381 /// let agent = Agent::builder()
382 /// .bind("0.0.0.0:1161")
383 /// .community(b"public")
384 /// // Register handler for system MIB subtree
385 /// .handler(oid!(1, 3, 6, 1, 2, 1, 1), Arc::new(SystemHandler))
386 /// .build()
387 /// .await?;
388 /// # Ok(())
389 /// # }
390 /// ```
391 pub fn handler(mut self, prefix: Oid, handler: Arc<dyn MibHandler>) -> Self {
392 self.handlers.push(RegisteredHandler { prefix, handler });
393 self
394 }
395
396 /// Configure VACM (View-based Access Control Model) using a builder function.
397 ///
398 /// When VACM is enabled, all requests are checked against the configured
399 /// access control rules. Requests that don't have proper access are rejected
400 /// with `noAccess` error (v2c/v3) or `noSuchName` (v1).
401 ///
402 /// # Example
403 ///
404 /// ```rust,no_run
405 /// use async_snmp::agent::{Agent, SecurityModel, VacmBuilder};
406 /// use async_snmp::message::SecurityLevel;
407 /// use async_snmp::oid;
408 ///
409 /// # async fn example() -> Result<(), async_snmp::Error> {
410 /// let agent = Agent::builder()
411 /// .bind("0.0.0.0:161")
412 /// .community(b"public")
413 /// .community(b"private")
414 /// .vacm(|v| v
415 /// .group("public", SecurityModel::V2c, "readonly_group")
416 /// .group("private", SecurityModel::V2c, "readwrite_group")
417 /// .access("readonly_group", |a| a
418 /// .read_view("full_view"))
419 /// .access("readwrite_group", |a| a
420 /// .read_view("full_view")
421 /// .write_view("write_view"))
422 /// .view("full_view", |v| v
423 /// .include(oid!(1, 3, 6, 1)))
424 /// .view("write_view", |v| v
425 /// .include(oid!(1, 3, 6, 1, 2, 1, 1))))
426 /// .build()
427 /// .await?;
428 /// # Ok(())
429 /// # }
430 /// ```
431 pub fn vacm<F>(mut self, configure: F) -> Self
432 where
433 F: FnOnce(VacmBuilder) -> VacmBuilder,
434 {
435 let builder = VacmBuilder::new();
436 self.vacm = Some(configure(builder).build());
437 self
438 }
439
440 /// Build the agent.
441 ///
442 /// For IPv6 bind addresses, the socket has `IPV6_V6ONLY` set to true.
443 pub async fn build(mut self) -> Result<Agent> {
444 let bind_addr: std::net::SocketAddr = self.bind_addr.parse().map_err(|_| Error::Io {
445 target: None,
446 source: std::io::Error::new(
447 std::io::ErrorKind::InvalidInput,
448 format!("invalid bind address: {}", self.bind_addr),
449 ),
450 })?;
451
452 let socket = bind_udp_socket(bind_addr).await.map_err(|e| Error::Io {
453 target: Some(bind_addr),
454 source: e,
455 })?;
456
457 let local_addr = socket.local_addr().map_err(|e| Error::Io {
458 target: Some(bind_addr),
459 source: e,
460 })?;
461
462 // Generate default engine ID if not provided
463 let engine_id = self.engine_id.unwrap_or_else(|| {
464 // RFC 3411 format: enterprise number + format + local identifier
465 // Use a simple format: 0x80 (local) + timestamp + random
466 let mut id = vec![0x80, 0x00, 0x00, 0x00, 0x01]; // Enterprise format indicator
467 let timestamp = std::time::SystemTime::now()
468 .duration_since(std::time::UNIX_EPOCH)
469 .unwrap_or_default()
470 .as_secs();
471 id.extend_from_slice(×tamp.to_be_bytes());
472 id
473 });
474
475 // Sort handlers by prefix length (longest first) for matching
476 self.handlers
477 .sort_by(|a, b| b.prefix.len().cmp(&a.prefix.len()));
478
479 Ok(Agent {
480 inner: Arc::new(AgentInner {
481 socket,
482 local_addr,
483 communities: self.communities,
484 usm_users: self.usm_users,
485 handlers: self.handlers,
486 engine_id,
487 engine_boots: AtomicU32::new(1),
488 engine_time: AtomicU32::new(0),
489 engine_start: Instant::now(),
490 salt_counter: SaltCounter::new(),
491 max_message_size: self.max_message_size,
492 vacm: self.vacm,
493 snmp_invalid_msgs: AtomicU32::new(0),
494 snmp_unknown_security_models: AtomicU32::new(0),
495 snmp_silent_drops: AtomicU32::new(0),
496 }),
497 })
498 }
499}
500
501impl Default for AgentBuilder {
502 fn default() -> Self {
503 Self::new()
504 }
505}
506
507/// Inner state shared across agent clones.
508pub(crate) struct AgentInner {
509 pub(crate) socket: UdpSocket,
510 pub(crate) local_addr: SocketAddr,
511 pub(crate) communities: Vec<Vec<u8>>,
512 pub(crate) usm_users: HashMap<Bytes, UsmUserConfig>,
513 pub(crate) handlers: Vec<RegisteredHandler>,
514 pub(crate) engine_id: Vec<u8>,
515 pub(crate) engine_boots: AtomicU32,
516 pub(crate) engine_time: AtomicU32,
517 pub(crate) engine_start: Instant,
518 pub(crate) salt_counter: SaltCounter,
519 pub(crate) max_message_size: usize,
520 pub(crate) vacm: Option<VacmConfig>,
521 // RFC 3412 statistics counters
522 /// snmpInvalidMsgs (1.3.6.1.6.3.11.2.1.2) - messages with invalid msgFlags
523 /// (e.g., privacy without authentication)
524 pub(crate) snmp_invalid_msgs: AtomicU32,
525 /// snmpUnknownSecurityModels (1.3.6.1.6.3.11.2.1.1) - messages with
526 /// unrecognized security model
527 pub(crate) snmp_unknown_security_models: AtomicU32,
528 /// snmpSilentDrops (1.3.6.1.6.3.11.2.1.3) - confirmed-class PDUs silently
529 /// dropped because even an empty response would exceed max message size
530 pub(crate) snmp_silent_drops: AtomicU32,
531}
532
533/// SNMP Agent.
534///
535/// Listens for and responds to SNMP requests (GET, GETNEXT, GETBULK, SET).
536///
537/// # Example
538///
539/// ```rust,no_run
540/// use async_snmp::agent::Agent;
541/// use async_snmp::oid;
542///
543/// # async fn example() -> Result<(), async_snmp::Error> {
544/// let agent = Agent::builder()
545/// .bind("0.0.0.0:161")
546/// .community(b"public")
547/// .build()
548/// .await?;
549///
550/// agent.run().await
551/// # }
552/// ```
553pub struct Agent {
554 pub(crate) inner: Arc<AgentInner>,
555}
556
557impl Agent {
558 /// Create a builder for configuring the agent.
559 pub fn builder() -> AgentBuilder {
560 AgentBuilder::new()
561 }
562
563 /// Get the local address the agent is bound to.
564 pub fn local_addr(&self) -> SocketAddr {
565 self.inner.local_addr
566 }
567
568 /// Get the engine ID.
569 pub fn engine_id(&self) -> &[u8] {
570 &self.inner.engine_id
571 }
572
573 /// Get the snmpInvalidMsgs counter value.
574 ///
575 /// This counter tracks messages with invalid msgFlags, such as
576 /// privacy-without-authentication (RFC 3412 Section 7.2 Step 5d).
577 ///
578 /// OID: 1.3.6.1.6.3.11.2.1.2
579 pub fn snmp_invalid_msgs(&self) -> u32 {
580 self.inner.snmp_invalid_msgs.load(Ordering::Relaxed)
581 }
582
583 /// Get the snmpUnknownSecurityModels counter value.
584 ///
585 /// This counter tracks messages with unrecognized security models
586 /// (RFC 3412 Section 7.2 Step 2).
587 ///
588 /// OID: 1.3.6.1.6.3.11.2.1.1
589 pub fn snmp_unknown_security_models(&self) -> u32 {
590 self.inner
591 .snmp_unknown_security_models
592 .load(Ordering::Relaxed)
593 }
594
595 /// Get the snmpSilentDrops counter value.
596 ///
597 /// This counter tracks confirmed-class PDUs (GetRequest, GetNextRequest,
598 /// GetBulkRequest, SetRequest, InformRequest) that were silently dropped
599 /// because even an empty Response-PDU would exceed the maximum message
600 /// size constraint (RFC 3412 Section 7.1).
601 ///
602 /// OID: 1.3.6.1.6.3.11.2.1.3
603 pub fn snmp_silent_drops(&self) -> u32 {
604 self.inner.snmp_silent_drops.load(Ordering::Relaxed)
605 }
606
607 /// Run the agent, processing requests indefinitely.
608 ///
609 /// This method runs until an error occurs or the task is cancelled.
610 #[instrument(skip(self), err, fields(snmp.local_addr = %self.local_addr()))]
611 pub async fn run(&self) -> Result<()> {
612 let mut buf = vec![0u8; 65535];
613
614 loop {
615 let (len, source) =
616 self.inner
617 .socket
618 .recv_from(&mut buf)
619 .await
620 .map_err(|e| Error::Io {
621 target: Some(self.inner.local_addr),
622 source: e,
623 })?;
624
625 let data = Bytes::copy_from_slice(&buf[..len]);
626
627 // Update engine time before processing
628 self.update_engine_time();
629
630 match self.handle_request(data, source).await {
631 Ok(Some(response_bytes)) => {
632 if let Err(e) = self.inner.socket.send_to(&response_bytes, source).await {
633 tracing::warn!(snmp.source = %source, error = %e, "failed to send response");
634 }
635 }
636 Ok(None) => {
637 // No response needed (e.g., invalid message)
638 }
639 Err(e) => {
640 tracing::warn!(snmp.source = %source, error = %e, "error handling request");
641 }
642 }
643 }
644 }
645
646 /// Process a single request and return the response bytes.
647 ///
648 /// Returns `None` if no response should be sent.
649 async fn handle_request(&self, data: Bytes, source: SocketAddr) -> Result<Option<Bytes>> {
650 // Peek at version
651 let mut decoder = Decoder::new(data.clone());
652 let mut seq = decoder.read_sequence()?;
653 let version_num = seq.read_integer()?;
654 let version = Version::from_i32(version_num).ok_or_else(|| {
655 Error::decode(seq.offset(), DecodeErrorKind::UnknownVersion(version_num))
656 })?;
657 drop(seq);
658 drop(decoder);
659
660 match version {
661 Version::V1 => self.handle_v1(data, source).await,
662 Version::V2c => self.handle_v2c(data, source).await,
663 Version::V3 => self.handle_v3(data, source).await,
664 }
665 }
666
667 /// Update engine time based on elapsed time since start.
668 fn update_engine_time(&self) {
669 let elapsed = self.inner.engine_start.elapsed().as_secs() as u32;
670 self.inner.engine_time.store(elapsed, Ordering::Relaxed);
671 }
672
673 /// Validate community string using constant-time comparison.
674 ///
675 /// Uses constant-time comparison to prevent timing attacks that could
676 /// be used to guess valid community strings character by character.
677 pub(crate) fn validate_community(&self, community: &[u8]) -> bool {
678 if self.inner.communities.is_empty() {
679 // No communities configured = reject all
680 return false;
681 }
682 // Use constant-time comparison for each community string.
683 // We compare against all configured communities regardless of
684 // early matches to maintain constant-time behavior.
685 let mut valid = false;
686 for configured in &self.inner.communities {
687 // ct_eq returns a Choice, which we convert to bool after comparison
688 if configured.len() == community.len()
689 && bool::from(configured.as_slice().ct_eq(community))
690 {
691 valid = true;
692 }
693 }
694 valid
695 }
696
697 /// Dispatch a request to the appropriate handler.
698 async fn dispatch_request(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
699 match pdu.pdu_type {
700 PduType::GetRequest => self.handle_get(ctx, pdu).await,
701 PduType::GetNextRequest => self.handle_get_next(ctx, pdu).await,
702 PduType::GetBulkRequest => self.handle_get_bulk(ctx, pdu).await,
703 PduType::SetRequest => self.handle_set(ctx, pdu).await,
704 PduType::InformRequest => self.handle_inform(pdu),
705 _ => {
706 // Should not happen - filtered earlier
707 Ok(pdu.to_error_response(ErrorStatus::GenErr, 0))
708 }
709 }
710 }
711
712 /// Handle InformRequest PDU.
713 ///
714 /// Per RFC 3416 Section 4.2.7, an InformRequest is a confirmed-class PDU
715 /// that the receiver acknowledges by returning a Response with the same
716 /// request-id and varbind list.
717 fn handle_inform(&self, pdu: &Pdu) -> Result<Pdu> {
718 // Simply acknowledge by returning the same varbinds in a Response
719 Ok(Pdu {
720 pdu_type: PduType::Response,
721 request_id: pdu.request_id,
722 error_status: 0,
723 error_index: 0,
724 varbinds: pdu.varbinds.clone(),
725 })
726 }
727
728 /// Handle GET request.
729 async fn handle_get(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
730 let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
731
732 for (index, vb) in pdu.varbinds.iter().enumerate() {
733 // VACM read access check
734 if let Some(ref vacm) = self.inner.vacm
735 && !vacm.check_access(ctx.read_view.as_ref(), &vb.oid)
736 {
737 // v1: noSuchName, v2c/v3: noAccess or NoSuchObject
738 if ctx.version == Version::V1 {
739 return Ok(Pdu {
740 pdu_type: PduType::Response,
741 request_id: pdu.request_id,
742 error_status: ErrorStatus::NoSuchName.as_i32(),
743 error_index: (index + 1) as i32,
744 varbinds: pdu.varbinds.clone(),
745 });
746 } else {
747 // For GET, return NoSuchObject for inaccessible OIDs per RFC 3415
748 response_varbinds.push(VarBind::new(vb.oid.clone(), Value::NoSuchObject));
749 continue;
750 }
751 }
752
753 let result = if let Some(handler) = self.find_handler(&vb.oid) {
754 handler.handler.get(ctx, &vb.oid).await
755 } else {
756 GetResult::NoSuchObject
757 };
758
759 let response_value = match result {
760 GetResult::Value(v) => v,
761 GetResult::NoSuchObject => {
762 // v1 returns noSuchName error, v2c/v3 returns NoSuchObject exception
763 if ctx.version == Version::V1 {
764 return Ok(Pdu {
765 pdu_type: PduType::Response,
766 request_id: pdu.request_id,
767 error_status: ErrorStatus::NoSuchName.as_i32(),
768 error_index: (index + 1) as i32,
769 varbinds: pdu.varbinds.clone(),
770 });
771 } else {
772 Value::NoSuchObject
773 }
774 }
775 GetResult::NoSuchInstance => {
776 // v1 returns noSuchName error, v2c/v3 returns NoSuchInstance exception
777 if ctx.version == Version::V1 {
778 return Ok(Pdu {
779 pdu_type: PduType::Response,
780 request_id: pdu.request_id,
781 error_status: ErrorStatus::NoSuchName.as_i32(),
782 error_index: (index + 1) as i32,
783 varbinds: pdu.varbinds.clone(),
784 });
785 } else {
786 Value::NoSuchInstance
787 }
788 }
789 };
790
791 response_varbinds.push(VarBind::new(vb.oid.clone(), response_value));
792 }
793
794 Ok(Pdu {
795 pdu_type: PduType::Response,
796 request_id: pdu.request_id,
797 error_status: 0,
798 error_index: 0,
799 varbinds: response_varbinds,
800 })
801 }
802
803 /// Handle GETNEXT request.
804 async fn handle_get_next(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
805 let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
806
807 for (index, vb) in pdu.varbinds.iter().enumerate() {
808 // Try to find the next OID from any handler
809 let next = self.get_next_oid(ctx, &vb.oid).await;
810
811 // Check VACM access for the returned OID (if VACM enabled)
812 let next = if let Some(ref next_vb) = next {
813 if let Some(ref vacm) = self.inner.vacm {
814 if vacm.check_access(ctx.read_view.as_ref(), &next_vb.oid) {
815 next
816 } else {
817 // OID not accessible, continue searching
818 // For simplicity, we just return EndOfMibView here
819 // A more complete implementation would continue the search
820 None
821 }
822 } else {
823 next
824 }
825 } else {
826 next
827 };
828
829 match next {
830 Some(next_vb) => {
831 response_varbinds.push(next_vb);
832 }
833 None => {
834 // v1 returns noSuchName, v2c/v3 returns endOfMibView
835 if ctx.version == Version::V1 {
836 return Ok(Pdu {
837 pdu_type: PduType::Response,
838 request_id: pdu.request_id,
839 error_status: ErrorStatus::NoSuchName.as_i32(),
840 error_index: (index + 1) as i32,
841 varbinds: pdu.varbinds.clone(),
842 });
843 } else {
844 response_varbinds.push(VarBind::new(vb.oid.clone(), Value::EndOfMibView));
845 }
846 }
847 }
848 }
849
850 Ok(Pdu {
851 pdu_type: PduType::Response,
852 request_id: pdu.request_id,
853 error_status: 0,
854 error_index: 0,
855 varbinds: response_varbinds,
856 })
857 }
858
859 /// Handle GETBULK request.
860 ///
861 /// Per RFC 3416 Section 4.2.3, if the response would exceed the message
862 /// size limit, we return fewer variable bindings rather than all of them.
863 async fn handle_get_bulk(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
864 // For GETBULK, error_status is non_repeaters and error_index is max_repetitions
865 let non_repeaters = pdu.error_status.max(0) as usize;
866 let max_repetitions = pdu.error_index.max(0) as usize;
867
868 let mut response_varbinds = Vec::new();
869 let mut current_size: usize = RESPONSE_OVERHEAD;
870 let max_size = self.inner.max_message_size;
871
872 // Helper to check if we can add a varbind
873 let can_add = |vb: &VarBind, current_size: usize| -> bool {
874 current_size + vb.encoded_size() <= max_size
875 };
876
877 // Handle non-repeaters (first N varbinds get one GETNEXT each)
878 for vb in pdu.varbinds.iter().take(non_repeaters) {
879 let next_vb = match self.get_next_oid(ctx, &vb.oid).await {
880 Some(next_vb) => next_vb,
881 None => VarBind::new(vb.oid.clone(), Value::EndOfMibView),
882 };
883
884 if !can_add(&next_vb, current_size) {
885 // Can't fit even non-repeaters, return tooBig if we have nothing
886 if response_varbinds.is_empty() {
887 return Ok(Pdu {
888 pdu_type: PduType::Response,
889 request_id: pdu.request_id,
890 error_status: ErrorStatus::TooBig.as_i32(),
891 error_index: 0,
892 varbinds: pdu.varbinds.clone(),
893 });
894 }
895 // Otherwise return what we have
896 break;
897 }
898
899 current_size += next_vb.encoded_size();
900 response_varbinds.push(next_vb);
901 }
902
903 // Handle repeaters
904 if non_repeaters < pdu.varbinds.len() {
905 let repeaters = &pdu.varbinds[non_repeaters..];
906 let mut current_oids: Vec<Oid> = repeaters.iter().map(|vb| vb.oid.clone()).collect();
907 let mut all_done = vec![false; repeaters.len()];
908
909 'outer: for _ in 0..max_repetitions {
910 let mut row_complete = true;
911 for (i, oid) in current_oids.iter_mut().enumerate() {
912 let next_vb = if all_done[i] {
913 VarBind::new(oid.clone(), Value::EndOfMibView)
914 } else {
915 match self.get_next_oid(ctx, oid).await {
916 Some(next_vb) => {
917 *oid = next_vb.oid.clone();
918 row_complete = false;
919 next_vb
920 }
921 None => {
922 all_done[i] = true;
923 VarBind::new(oid.clone(), Value::EndOfMibView)
924 }
925 }
926 };
927
928 // Check size before adding
929 if !can_add(&next_vb, current_size) {
930 // Can't fit more, return what we have
931 break 'outer;
932 }
933
934 current_size += next_vb.encoded_size();
935 response_varbinds.push(next_vb);
936 }
937
938 if row_complete {
939 break;
940 }
941 }
942 }
943
944 Ok(Pdu {
945 pdu_type: PduType::Response,
946 request_id: pdu.request_id,
947 error_status: 0,
948 error_index: 0,
949 varbinds: response_varbinds,
950 })
951 }
952
953 /// Find the handler for a given OID.
954 pub(crate) fn find_handler(&self, oid: &Oid) -> Option<&RegisteredHandler> {
955 // Handlers are sorted by prefix length (longest first)
956 self.inner
957 .handlers
958 .iter()
959 .find(|&handler| handler.handler.handles(&handler.prefix, oid))
960 .map(|v| v as _)
961 }
962
963 /// Get the next OID from any handler.
964 async fn get_next_oid(&self, ctx: &RequestContext, oid: &Oid) -> Option<VarBind> {
965 // Find the first handler that can provide a next OID
966 let mut best_result: Option<VarBind> = None;
967
968 for handler in &self.inner.handlers {
969 if let GetNextResult::Value(next) = handler.handler.get_next(ctx, oid).await {
970 // Must be lexicographically greater than the request OID
971 if next.oid > *oid {
972 match &best_result {
973 None => best_result = Some(next),
974 Some(current) if next.oid < current.oid => best_result = Some(next),
975 _ => {}
976 }
977 }
978 }
979 }
980
981 best_result
982 }
983}
984
985impl Clone for Agent {
986 fn clone(&self) -> Self {
987 Self {
988 inner: Arc::clone(&self.inner),
989 }
990 }
991}
992
993#[cfg(test)]
994mod tests {
995 use super::*;
996 use crate::handler::{
997 BoxFuture, GetNextResult, GetResult, MibHandler, RequestContext, SecurityModel, SetResult,
998 };
999 use crate::message::SecurityLevel;
1000 use crate::oid;
1001
1002 struct TestHandler;
1003
1004 impl MibHandler for TestHandler {
1005 fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
1006 Box::pin(async move {
1007 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
1008 return GetResult::Value(Value::Integer(42));
1009 }
1010 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
1011 return GetResult::Value(Value::OctetString(Bytes::from_static(b"test")));
1012 }
1013 GetResult::NoSuchObject
1014 })
1015 }
1016
1017 fn get_next<'a>(
1018 &'a self,
1019 _ctx: &'a RequestContext,
1020 oid: &'a Oid,
1021 ) -> BoxFuture<'a, GetNextResult> {
1022 Box::pin(async move {
1023 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
1024 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
1025
1026 if oid < &oid1 {
1027 return GetNextResult::Value(VarBind::new(oid1, Value::Integer(42)));
1028 }
1029 if oid < &oid2 {
1030 return GetNextResult::Value(VarBind::new(
1031 oid2,
1032 Value::OctetString(Bytes::from_static(b"test")),
1033 ));
1034 }
1035 GetNextResult::EndOfMibView
1036 })
1037 }
1038 }
1039
1040 fn test_ctx() -> RequestContext {
1041 RequestContext {
1042 source: "127.0.0.1:12345".parse().unwrap(),
1043 version: Version::V2c,
1044 security_model: SecurityModel::V2c,
1045 security_name: Bytes::from_static(b"public"),
1046 security_level: SecurityLevel::NoAuthNoPriv,
1047 context_name: Bytes::new(),
1048 request_id: 1,
1049 pdu_type: PduType::GetRequest,
1050 group_name: None,
1051 read_view: None,
1052 write_view: None,
1053 }
1054 }
1055
1056 #[test]
1057 fn test_agent_builder_defaults() {
1058 let builder = AgentBuilder::new();
1059 assert_eq!(builder.bind_addr, "0.0.0.0:161");
1060 assert!(builder.communities.is_empty());
1061 assert!(builder.usm_users.is_empty());
1062 assert!(builder.handlers.is_empty());
1063 }
1064
1065 #[test]
1066 fn test_agent_builder_community() {
1067 let builder = AgentBuilder::new()
1068 .community(b"public")
1069 .community(b"private");
1070 assert_eq!(builder.communities.len(), 2);
1071 }
1072
1073 #[test]
1074 fn test_agent_builder_communities() {
1075 let builder = AgentBuilder::new().communities(["public", "private"]);
1076 assert_eq!(builder.communities.len(), 2);
1077 }
1078
1079 #[test]
1080 fn test_agent_builder_handler() {
1081 let builder =
1082 AgentBuilder::new().handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler));
1083 assert_eq!(builder.handlers.len(), 1);
1084 }
1085
1086 #[tokio::test]
1087 async fn test_mib_handler_default_set() {
1088 let handler = TestHandler;
1089 let mut ctx = test_ctx();
1090 ctx.pdu_type = PduType::SetRequest;
1091
1092 let result = handler
1093 .test_set(&ctx, &oid!(1, 3, 6, 1), &Value::Integer(1))
1094 .await;
1095 assert_eq!(result, SetResult::NotWritable);
1096 }
1097
1098 #[test]
1099 fn test_mib_handler_handles() {
1100 let handler = TestHandler;
1101 let prefix = oid!(1, 3, 6, 1, 4, 1, 99999);
1102
1103 // OID within prefix
1104 assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0)));
1105
1106 // OID before prefix (GETNEXT should still try)
1107 assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99998)));
1108
1109 // OID after prefix (not handled)
1110 assert!(!handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 100000)));
1111 }
1112
1113 #[tokio::test]
1114 async fn test_test_handler_get() {
1115 let handler = TestHandler;
1116 let ctx = test_ctx();
1117
1118 // Existing OID
1119 let result = handler
1120 .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
1121 .await;
1122 assert!(matches!(result, GetResult::Value(Value::Integer(42))));
1123
1124 // Non-existing OID
1125 let result = handler
1126 .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 99, 0))
1127 .await;
1128 assert!(matches!(result, GetResult::NoSuchObject));
1129 }
1130
1131 #[tokio::test]
1132 async fn test_test_handler_get_next() {
1133 let handler = TestHandler;
1134 let mut ctx = test_ctx();
1135 ctx.pdu_type = PduType::GetNextRequest;
1136
1137 // Before first OID
1138 let next = handler.get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999)).await;
1139 assert!(next.is_value());
1140 if let GetNextResult::Value(vb) = next {
1141 assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0));
1142 }
1143
1144 // Between OIDs
1145 let next = handler
1146 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
1147 .await;
1148 assert!(next.is_value());
1149 if let GetNextResult::Value(vb) = next {
1150 assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0));
1151 }
1152
1153 // After last OID
1154 let next = handler
1155 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0))
1156 .await;
1157 assert!(next.is_end_of_mib_view());
1158 }
1159}