AgentBuilder

Struct AgentBuilder 

Source
pub struct AgentBuilder { /* private fields */ }
Expand description

Builder for Agent.

Use this builder to configure and construct an SNMP agent. The builder pattern allows you to chain configuration methods before calling build() to create the agent.

§Access Control

By default, the agent operates in permissive mode: any authenticated request (valid community string for v1/v2c, valid USM credentials for v3) has full read and write access to all registered handlers.

For production deployments, use the vacm() method to configure View-based Access Control (RFC 3415), which allows fine-grained control over which security names can access which OID subtrees.

§Minimal Example

use async_snmp::agent::Agent;
use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
use async_snmp::{Oid, Value, VarBind, oid};
use std::sync::Arc;

struct MyHandler;
impl MibHandler for MyHandler {
    fn get<'a>(&'a self, _: &'a RequestContext, _: &'a Oid) -> BoxFuture<'a, GetResult> {
        Box::pin(async { GetResult::NoSuchObject })
    }
    fn get_next<'a>(&'a self, _: &'a RequestContext, _: &'a Oid) -> BoxFuture<'a, GetNextResult> {
        Box::pin(async { GetNextResult::EndOfMibView })
    }
}

let agent = Agent::builder()
    .bind("0.0.0.0:1161")  // Use non-privileged port
    .community(b"public")
    .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(MyHandler))
    .build()
    .await?;

Implementations§

Source§

impl AgentBuilder

Source

pub fn new() -> Self

Create a new builder with default settings.

Defaults:

  • Bind address: 0.0.0.0:161 (UDP)
  • Max message size: 1472 bytes (Ethernet MTU - IP/UDP headers)
  • Max concurrent requests: 1000
  • Receive buffer size: 4MB (requested from kernel)
  • No communities or USM users (all requests rejected)
  • No handlers registered
Source

pub fn bind(self, addr: impl Into<String>) -> Self

Set the UDP bind address.

Default is 0.0.0.0:161 (standard SNMP agent port). Note that binding to UDP port 161 typically requires root/administrator privileges.

§IPv4 Examples
use async_snmp::agent::Agent;

// Bind to all IPv4 interfaces on standard port (requires privileges)
let agent = Agent::builder().bind("0.0.0.0:161").community(b"public").build().await?;

// Bind to localhost only on non-privileged port
let agent = Agent::builder().bind("127.0.0.1:1161").community(b"public").build().await?;

// Bind to specific interface
let agent = Agent::builder().bind("192.168.1.100:161").community(b"public").build().await?;
§IPv6 / Dual-Stack Examples
use async_snmp::agent::Agent;

// Bind to all interfaces via dual-stack (handles both IPv4 and IPv6)
let agent = Agent::builder().bind("[::]:161").community(b"public").build().await?;

// Bind to IPv6 localhost only
let agent = Agent::builder().bind("[::1]:1161").community(b"public").build().await?;
Source

pub fn community(self, community: &[u8]) -> Self

Add an accepted community string for v1/v2c requests.

Multiple communities can be added. If none are added, all v1/v2c requests are rejected.

§Example
use async_snmp::agent::Agent;

let agent = Agent::builder()
    .bind("0.0.0.0:1161")
    .community(b"public")   // Read-only access
    .community(b"private")  // Read-write access (with VACM)
    .build()
    .await?;
Source

pub fn communities<I, C>(self, communities: I) -> Self
where I: IntoIterator<Item = C>, C: AsRef<[u8]>,

Add multiple community strings.

§Example
use async_snmp::agent::Agent;

let communities = ["public", "private", "monitor"];
let agent = Agent::builder()
    .bind("0.0.0.0:1161")
    .communities(communities)
    .build()
    .await?;
Source

pub fn usm_user<F>(self, username: impl Into<Bytes>, configure: F) -> Self

Add a USM user for SNMPv3 authentication.

Configure authentication and privacy settings using the closure. Multiple users can be added with different security levels.

§Security Levels
  • noAuthNoPriv: No authentication or encryption
  • authNoPriv: Authentication only (HMAC verification)
  • authPriv: Authentication and encryption
§Example
use async_snmp::agent::Agent;
use async_snmp::{AuthProtocol, PrivProtocol};

let agent = Agent::builder()
    .bind("0.0.0.0:1161")
    // Read-only user with authentication only
    .usm_user("monitor", |u| {
        u.auth(AuthProtocol::Sha256, b"monitorpass123")
    })
    // Admin user with full encryption
    .usm_user("admin", |u| {
        u.auth(AuthProtocol::Sha256, b"adminauth123")
         .privacy(PrivProtocol::Aes128, b"adminpriv123")
    })
    .build()
    .await?;
Source

pub fn engine_id(self, engine_id: impl Into<Vec<u8>>) -> Self

Set the engine ID for SNMPv3.

If not set, a default engine ID will be generated based on the RFC 3411 format using enterprise number and timestamp.

§Example
use async_snmp::agent::Agent;

let agent = Agent::builder()
    .bind("0.0.0.0:1161")
    .engine_id(b"\x80\x00\x00\x00\x01MyEngine".to_vec())
    .community(b"public")
    .build()
    .await?;
Source

pub fn max_message_size(self, size: usize) -> Self

Set the maximum message size for responses.

Default is 1472 octets (fits Ethernet MTU minus IP/UDP headers). GETBULK responses will be truncated to fit within this limit.

For SNMPv3 requests, the agent uses the minimum of this value and the msgMaxSize from the request.

Source

pub fn max_concurrent_requests(self, limit: Option<usize>) -> Self

Set the maximum number of concurrent requests the agent will process.

Default is 1000. Requests beyond this limit will queue until a slot becomes available. Set to None for unbounded concurrency.

This controls memory usage under high load while still allowing parallel request processing.

Source

pub fn recv_buffer_size(self, size: Option<usize>) -> Self

Set the UDP socket receive buffer size.

Default is 4MB. The kernel may cap this at net.core.rmem_max. A larger buffer prevents packet loss during request bursts.

Set to None to use the kernel default.

Source

pub fn handler(self, prefix: Oid, handler: Arc<dyn MibHandler>) -> Self

Register a MIB handler for an OID subtree.

Handlers are matched by longest prefix. When a request comes in, the handler with the longest matching prefix is used.

§Example
use async_snmp::agent::Agent;
use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, BoxFuture};
use async_snmp::{Oid, Value, VarBind, oid};
use std::sync::Arc;

struct SystemHandler;
impl MibHandler for SystemHandler {
    fn get<'a>(&'a self, _: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
        Box::pin(async move {
            if oid == &oid!(1, 3, 6, 1, 2, 1, 1, 1, 0) {
                GetResult::Value(Value::OctetString("My Agent".into()))
            } else {
                GetResult::NoSuchObject
            }
        })
    }
    fn get_next<'a>(&'a self, _: &'a RequestContext, _: &'a Oid) -> BoxFuture<'a, GetNextResult> {
        Box::pin(async { GetNextResult::EndOfMibView })
    }
}

let agent = Agent::builder()
    .bind("0.0.0.0:1161")
    .community(b"public")
    // Register handler for system MIB subtree
    .handler(oid!(1, 3, 6, 1, 2, 1, 1), Arc::new(SystemHandler))
    .build()
    .await?;
Source

pub fn vacm<F>(self, configure: F) -> Self

Configure VACM (View-based Access Control Model) using a builder function.

When VACM is configured, all requests are checked against the configured access control rules. Requests that don’t have proper access are rejected with noAccess error (v2c/v3) or noSuchName (v1).

Without VACM configuration, the agent operates in permissive mode: any authenticated request has full read/write access to all handlers.

§Example
use async_snmp::agent::{Agent, SecurityModel, VacmBuilder};
use async_snmp::message::SecurityLevel;
use async_snmp::oid;

let agent = Agent::builder()
    .bind("0.0.0.0:161")
    .community(b"public")
    .community(b"private")
    .vacm(|v| v
        .group("public", SecurityModel::V2c, "readonly_group")
        .group("private", SecurityModel::V2c, "readwrite_group")
        .access("readonly_group", |a| a
            .read_view("full_view"))
        .access("readwrite_group", |a| a
            .read_view("full_view")
            .write_view("write_view"))
        .view("full_view", |v| v
            .include(oid!(1, 3, 6, 1)))
        .view("write_view", |v| v
            .include(oid!(1, 3, 6, 1, 2, 1, 1))))
    .build()
    .await?;
Source

pub fn cancel(self, token: CancellationToken) -> Self

Set a cancellation token for graceful shutdown.

If not set, the agent creates its own token accessible via Agent::cancel().

Source

pub async fn build(self) -> Result<Agent>

Build the agent.

Trait Implementations§

Source§

impl Default for AgentBuilder

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more