1mod builtins;
71mod notification;
72mod request;
73mod response;
74mod set_handler;
75pub mod vacm;
76
77pub use vacm::{SecurityModel, VacmBuilder, VacmConfig, View, ViewCheckResult, ViewSubtree};
78
79use std::collections::{HashMap, HashSet};
80use std::net::SocketAddr;
81use std::sync::Arc;
82use std::sync::atomic::{AtomicU32, Ordering};
83use std::time::{Duration, Instant};
84
85use bytes::Bytes;
86use subtle::ConstantTimeEq;
87use tokio::net::UdpSocket;
88use tokio::sync::Semaphore;
89use tokio_util::sync::CancellationToken;
90use tracing::instrument;
91
92use std::io::IoSliceMut;
93
94use quinn_udp::{RecvMeta, Transmit, UdpSockRef, UdpSocketState};
95
96use crate::ber::Decoder;
97use crate::error::internal::DecodeErrorKind;
98use crate::error::{Error, ErrorStatus, Result};
99use crate::handler::{GetNextResult, GetResult, MibHandler, RequestContext};
100use crate::notification::UsmConfig;
101use crate::oid;
102use crate::oid::Oid;
103use crate::pdu::{Pdu, PduType};
104use crate::util::bind_udp_socket;
105use crate::v3::{SaltCounter, compute_engine_boots_time};
106use crate::value::Value;
107use crate::varbind::VarBind;
108use crate::version::Version;
109
110const DEFAULT_MAX_MESSAGE_SIZE: usize = 1472;
112
113const RESPONSE_OVERHEAD: usize = 100;
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
124pub enum BuiltinMib {
125 SnmpEngine,
130 UsmStats,
136 MpdStats,
140}
141
142pub(crate) struct RegisteredHandler {
144 pub(crate) prefix: Oid,
145 pub(crate) handler: Arc<dyn MibHandler>,
146}
147
148pub struct AgentBuilder {
193 bind_addr: String,
194 communities: Vec<Vec<u8>>,
195 usm_users: HashMap<Bytes, UsmConfig>,
196 handlers: Vec<RegisteredHandler>,
197 engine_id: Option<Vec<u8>>,
198 engine_boots: u32,
199 max_message_size: usize,
200 max_concurrent_requests: Option<usize>,
201 recv_buffer_size: Option<usize>,
202 vacm: Option<VacmConfig>,
203 cancel: Option<CancellationToken>,
204 trap_sinks: Vec<(String, crate::client::Auth)>,
205 inform_timeout: Duration,
206 inform_retry: crate::client::Retry,
207 disabled_builtins: HashSet<BuiltinMib>,
208}
209
210impl AgentBuilder {
211 pub fn new() -> Self {
221 Self {
222 bind_addr: "0.0.0.0:161".to_string(),
223 communities: Vec::new(),
224 usm_users: HashMap::new(),
225 handlers: Vec::new(),
226 engine_id: None,
227 engine_boots: 1,
228 max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
229 max_concurrent_requests: Some(1000),
230 recv_buffer_size: Some(4 * 1024 * 1024), vacm: None,
232 cancel: None,
233 trap_sinks: Vec::new(),
234 inform_timeout: Duration::from_secs(5),
235 inform_retry: crate::client::Retry::default(),
236 disabled_builtins: HashSet::new(),
237 }
238 }
239
240 pub fn bind(mut self, addr: impl Into<String>) -> Self {
278 self.bind_addr = addr.into();
279 self
280 }
281
282 pub fn community(mut self, community: &[u8]) -> Self {
303 self.communities.push(community.to_vec());
304 self
305 }
306
307 pub fn communities<I, C>(mut self, communities: I) -> Self
325 where
326 I: IntoIterator<Item = C>,
327 C: AsRef<[u8]>,
328 {
329 for c in communities {
330 self.communities.push(c.as_ref().to_vec());
331 }
332 self
333 }
334
335 pub fn usm_user<F>(mut self, username: impl Into<Bytes>, configure: F) -> Self
370 where
371 F: FnOnce(UsmConfig) -> UsmConfig,
372 {
373 let username_bytes: Bytes = username.into();
374 let config = configure(UsmConfig::new(username_bytes.clone()));
375 self.usm_users.insert(username_bytes, config);
376 self
377 }
378
379 pub fn engine_id(mut self, engine_id: impl Into<Vec<u8>>) -> Self {
400 self.engine_id = Some(engine_id.into());
401 self
402 }
403
404 pub fn engine_boots(mut self, boots: u32) -> Self {
429 self.engine_boots = boots;
430 self
431 }
432
433 pub fn max_message_size(mut self, size: usize) -> Self {
441 self.max_message_size = size;
442 self
443 }
444
445 pub fn max_concurrent_requests(mut self, limit: Option<usize>) -> Self {
453 self.max_concurrent_requests = limit;
454 self
455 }
456
457 pub fn recv_buffer_size(mut self, size: Option<usize>) -> Self {
464 self.recv_buffer_size = size;
465 self
466 }
467
468 pub fn handler(mut self, prefix: Oid, handler: Arc<dyn MibHandler>) -> Self {
509 self.handlers.push(RegisteredHandler { prefix, handler });
510 self
511 }
512
513 pub fn vacm<F>(mut self, configure: F) -> Self
552 where
553 F: FnOnce(VacmBuilder) -> VacmBuilder,
554 {
555 let builder = VacmBuilder::new();
556 self.vacm = Some(configure(builder).build());
557 self
558 }
559
560 pub fn cancel(mut self, token: CancellationToken) -> Self {
564 self.cancel = Some(token);
565 self
566 }
567
568 pub fn trap_sink(
593 mut self,
594 dest: impl Into<String>,
595 auth: impl Into<crate::client::Auth>,
596 ) -> Self {
597 self.trap_sinks.push((dest.into(), auth.into()));
598 self
599 }
600
601 pub fn inform_timeout(mut self, timeout: Duration) -> Self {
605 self.inform_timeout = timeout;
606 self
607 }
608
609 pub fn inform_retry(mut self, retry: crate::client::Retry) -> Self {
614 self.inform_retry = retry;
615 self
616 }
617
618 pub fn without_builtin_handler(mut self, mib: BuiltinMib) -> Self {
624 self.disabled_builtins.insert(mib);
625 self
626 }
627
628 pub fn without_builtin_handlers(mut self) -> Self {
634 self.disabled_builtins.insert(BuiltinMib::SnmpEngine);
635 self.disabled_builtins.insert(BuiltinMib::UsmStats);
636 self.disabled_builtins.insert(BuiltinMib::MpdStats);
637 self
638 }
639
640 pub async fn build(mut self) -> Result<Agent> {
642 let bind_addr: std::net::SocketAddr = self.bind_addr.parse().map_err(|_| {
643 Error::Config(format!("invalid bind address: {}", self.bind_addr).into())
644 })?;
645
646 let socket = bind_udp_socket(bind_addr, self.recv_buffer_size, None, false)
647 .await
648 .map_err(|e| Error::Network {
649 target: bind_addr,
650 source: e,
651 })?;
652
653 let local_addr = socket.local_addr().map_err(|e| Error::Network {
654 target: bind_addr,
655 source: e,
656 })?;
657
658 let socket_state =
659 UdpSocketState::new(UdpSockRef::from(&socket)).map_err(|e| Error::Network {
660 target: bind_addr,
661 source: e,
662 })?;
663
664 let engine_id: Bytes = self.engine_id.map(Bytes::from).unwrap_or_else(|| {
666 let mut id = vec![0x80, 0x00, 0x00, 0x00, 0x01]; let timestamp = std::time::SystemTime::now()
670 .duration_since(std::time::UNIX_EPOCH)
671 .unwrap_or_default()
672 .as_secs();
673 id.extend_from_slice(×tamp.to_be_bytes());
674 Bytes::from(id)
675 });
676
677 let cancel = self.cancel.unwrap_or_default();
678
679 let concurrency_limit = self
681 .max_concurrent_requests
682 .map(|n| Arc::new(Semaphore::new(n)));
683
684 let mut trap_sinks = Vec::with_capacity(self.trap_sinks.len());
686 for (dest_str, auth) in self.trap_sinks {
687 let dest: SocketAddr = dest_str.parse().map_err(|_| {
688 Error::Config(format!("invalid trap sink address: {}", dest_str).into())
689 })?;
690 trap_sinks.push(notification::TrapSink::new(
691 dest,
692 auth,
693 self.inform_timeout,
694 self.inform_retry.clone(),
695 ));
696 }
697
698 let state = Arc::new(AgentState {
699 engine_id,
700 engine_boots: AtomicU32::new(self.engine_boots),
701 engine_time: AtomicU32::new(0),
702 engine_start: Instant::now(),
703 engine_boots_base: self.engine_boots,
704 max_message_size: self.max_message_size,
705 snmp_invalid_msgs: AtomicU32::new(0),
706 snmp_unknown_security_models: AtomicU32::new(0),
707 snmp_silent_drops: AtomicU32::new(0),
708 usm_unknown_engine_ids: AtomicU32::new(0),
709 usm_unknown_usernames: AtomicU32::new(0),
710 usm_wrong_digests: AtomicU32::new(0),
711 usm_not_in_time_windows: AtomicU32::new(0),
712 usm_unsupported_sec_levels: AtomicU32::new(0),
713 usm_decryption_errors: AtomicU32::new(0),
714 });
715
716 if !self.disabled_builtins.contains(&BuiltinMib::SnmpEngine) {
718 self.handlers.push(RegisteredHandler {
719 prefix: oid!(1, 3, 6, 1, 6, 3, 10, 2, 1),
720 handler: Arc::new(builtins::SnmpEngineHandler {
721 state: Arc::clone(&state),
722 }),
723 });
724 }
725 if !self.disabled_builtins.contains(&BuiltinMib::UsmStats) {
726 self.handlers.push(RegisteredHandler {
727 prefix: oid!(1, 3, 6, 1, 6, 3, 15, 1, 1),
728 handler: Arc::new(builtins::UsmStatsHandler {
729 state: Arc::clone(&state),
730 }),
731 });
732 }
733 if !self.disabled_builtins.contains(&BuiltinMib::MpdStats) {
734 self.handlers.push(RegisteredHandler {
735 prefix: oid!(1, 3, 6, 1, 6, 3, 11, 2, 1),
736 handler: Arc::new(builtins::MpdStatsHandler {
737 state: Arc::clone(&state),
738 }),
739 });
740 }
741
742 self.handlers
744 .sort_by(|a, b| b.prefix.len().cmp(&a.prefix.len()));
745
746 Ok(Agent {
747 inner: Arc::new(AgentInner {
748 socket: Arc::new(socket),
749 socket_state,
750 local_addr,
751 communities: self.communities,
752 usm_users: self.usm_users,
753 handlers: self.handlers,
754 state,
755 salt_counter: SaltCounter::new(),
756 concurrency_limit,
757 vacm: self.vacm,
758 cancel,
759 trap_sinks,
760 }),
761 })
762 }
763}
764
765impl Default for AgentBuilder {
766 fn default() -> Self {
767 Self::new()
768 }
769}
770
771pub(crate) struct AgentState {
773 pub(crate) engine_id: Bytes,
774 pub(crate) engine_boots: AtomicU32,
775 pub(crate) engine_time: AtomicU32,
776 pub(crate) engine_start: Instant,
777 pub(crate) engine_boots_base: u32,
779 pub(crate) max_message_size: usize,
780 pub(crate) snmp_invalid_msgs: AtomicU32,
784 pub(crate) snmp_unknown_security_models: AtomicU32,
787 pub(crate) snmp_silent_drops: AtomicU32,
790 pub(crate) usm_unknown_engine_ids: AtomicU32,
794 pub(crate) usm_unknown_usernames: AtomicU32,
797 pub(crate) usm_wrong_digests: AtomicU32,
800 pub(crate) usm_not_in_time_windows: AtomicU32,
803 pub(crate) usm_unsupported_sec_levels: AtomicU32,
806 pub(crate) usm_decryption_errors: AtomicU32,
809}
810
811pub(crate) struct AgentInner {
813 pub(crate) socket: Arc<UdpSocket>,
814 pub(crate) socket_state: UdpSocketState,
815 pub(crate) local_addr: SocketAddr,
816 pub(crate) communities: Vec<Vec<u8>>,
817 pub(crate) usm_users: HashMap<Bytes, UsmConfig>,
818 pub(crate) handlers: Vec<RegisteredHandler>,
819 pub(crate) state: Arc<AgentState>,
820 pub(crate) salt_counter: SaltCounter,
821 pub(crate) concurrency_limit: Option<Arc<Semaphore>>,
822 pub(crate) vacm: Option<VacmConfig>,
823 pub(crate) cancel: CancellationToken,
825 pub(crate) trap_sinks: Vec<notification::TrapSink>,
827}
828
829pub struct Agent {
850 pub(crate) inner: Arc<AgentInner>,
851}
852
853impl Agent {
854 pub fn builder() -> AgentBuilder {
856 AgentBuilder::new()
857 }
858
859 pub fn local_addr(&self) -> SocketAddr {
861 self.inner.local_addr
862 }
863
864 pub fn engine_id(&self) -> &[u8] {
866 &self.inner.state.engine_id
867 }
868
869 pub fn engine_boots(&self) -> u32 {
875 self.inner.state.engine_boots.load(Ordering::Relaxed)
876 }
877
878 pub fn engine_time(&self) -> u32 {
880 self.inner.state.engine_time.load(Ordering::Relaxed)
881 }
882
883 pub fn cancel(&self) -> CancellationToken {
887 self.inner.cancel.clone()
888 }
889
890 pub fn snmp_invalid_msgs(&self) -> u32 {
897 self.inner.state.snmp_invalid_msgs.load(Ordering::Relaxed)
898 }
899
900 pub fn snmp_unknown_security_models(&self) -> u32 {
907 self.inner
908 .state
909 .snmp_unknown_security_models
910 .load(Ordering::Relaxed)
911 }
912
913 pub fn snmp_silent_drops(&self) -> u32 {
922 self.inner.state.snmp_silent_drops.load(Ordering::Relaxed)
923 }
924
925 pub fn usm_unknown_engine_ids(&self) -> u32 {
933 self.inner
934 .state
935 .usm_unknown_engine_ids
936 .load(Ordering::Relaxed)
937 }
938
939 pub fn usm_unknown_usernames(&self) -> u32 {
947 self.inner
948 .state
949 .usm_unknown_usernames
950 .load(Ordering::Relaxed)
951 }
952
953 pub fn usm_wrong_digests(&self) -> u32 {
960 self.inner.state.usm_wrong_digests.load(Ordering::Relaxed)
961 }
962
963 pub fn usm_not_in_time_windows(&self) -> u32 {
971 self.inner
972 .state
973 .usm_not_in_time_windows
974 .load(Ordering::Relaxed)
975 }
976
977 pub fn usm_unsupported_sec_levels(&self) -> u32 {
985 self.inner
986 .state
987 .usm_unsupported_sec_levels
988 .load(Ordering::Relaxed)
989 }
990
991 pub fn usm_decryption_errors(&self) -> u32 {
999 self.inner
1000 .state
1001 .usm_decryption_errors
1002 .load(Ordering::Relaxed)
1003 }
1004
1005 pub fn uptime_hundredths(&self) -> u32 {
1010 let elapsed = self.inner.state.engine_start.elapsed();
1011 let centisecs = elapsed.as_millis() / 10;
1012 centisecs.min(u32::MAX as u128) as u32
1013 }
1014
1015 #[instrument(skip(self), err, fields(snmp.local_addr = %self.local_addr()))]
1021 pub async fn run(&self) -> Result<()> {
1022 let mut buf = vec![0u8; 65535];
1023
1024 loop {
1025 let recv_meta = tokio::select! {
1026 result = self.recv_packet(&mut buf) => {
1027 result?
1028 }
1029 _ = self.inner.cancel.cancelled() => {
1030 tracing::info!(target: "async_snmp::agent", "agent shutdown requested");
1031 return Ok(());
1032 }
1033 };
1034
1035 let data = Bytes::copy_from_slice(&buf[..recv_meta.len]);
1036 let agent = self.clone();
1037
1038 let permit = if let Some(ref sem) = self.inner.concurrency_limit {
1039 Some(sem.clone().acquire_owned().await.expect("semaphore closed"))
1040 } else {
1041 None
1042 };
1043
1044 tokio::spawn(async move {
1045 agent.update_engine_time();
1046
1047 match agent.handle_request(data, recv_meta.addr).await {
1048 Ok(Some(response_bytes)) => {
1049 if response_bytes.len() > agent.inner.state.max_message_size {
1052 agent
1053 .inner
1054 .state
1055 .snmp_silent_drops
1056 .fetch_add(1, Ordering::Relaxed);
1057 tracing::debug!(target: "async_snmp::agent", { snmp.source = %recv_meta.addr, response_size = response_bytes.len(), max_size = agent.inner.state.max_message_size }, "response exceeds max message size, silently dropped");
1058 } else if let Err(e) =
1059 agent.send_response(&response_bytes, &recv_meta).await
1060 {
1061 tracing::warn!(target: "async_snmp::agent", { snmp.source = %recv_meta.addr, error = %e }, "failed to send response");
1062 }
1063 }
1064 Ok(None) => {}
1065 Err(e) => {
1066 tracing::warn!(target: "async_snmp::agent", { snmp.source = %recv_meta.addr, error = %e }, "error handling request");
1067 }
1068 }
1069
1070 drop(permit);
1071 });
1072 }
1073 }
1074
1075 async fn recv_packet(&self, buf: &mut [u8]) -> Result<RecvMeta> {
1076 let mut iov = [IoSliceMut::new(buf)];
1077 let mut meta = [RecvMeta::default()];
1078
1079 loop {
1080 self.inner
1081 .socket
1082 .readable()
1083 .await
1084 .map_err(|e| Error::Network {
1085 target: self.inner.local_addr,
1086 source: e,
1087 })?;
1088
1089 let result = self.inner.socket.try_io(tokio::io::Interest::READABLE, || {
1090 let sref = UdpSockRef::from(&*self.inner.socket);
1091 self.inner.socket_state.recv(sref, &mut iov, &mut meta)
1092 });
1093
1094 match result {
1095 Ok(n) if n > 0 => return Ok(meta[0]),
1096 Ok(_) => continue,
1097 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
1098 Err(e) => {
1099 return Err(Error::Network {
1100 target: self.inner.local_addr,
1101 source: e,
1102 }
1103 .boxed());
1104 }
1105 }
1106 }
1107 }
1108
1109 async fn send_response(&self, data: &[u8], recv_meta: &RecvMeta) -> std::io::Result<()> {
1110 let transmit = Transmit {
1111 destination: recv_meta.addr,
1112 ecn: None,
1113 contents: data,
1114 segment_size: None,
1115 src_ip: recv_meta.dst_ip,
1116 };
1117
1118 loop {
1119 self.inner.socket.writable().await?;
1120
1121 let result = self.inner.socket.try_io(tokio::io::Interest::WRITABLE, || {
1122 let sref = UdpSockRef::from(&*self.inner.socket);
1123 self.inner.socket_state.try_send(sref, &transmit)
1124 });
1125
1126 match result {
1127 Ok(()) => return Ok(()),
1128 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
1129 Err(e) => return Err(e),
1130 }
1131 }
1132 }
1133
1134 async fn handle_request(&self, data: Bytes, source: SocketAddr) -> Result<Option<Bytes>> {
1138 let mut decoder = Decoder::with_target(data.clone(), source);
1140 let mut seq = decoder.read_sequence()?;
1141 let version_num = seq.read_integer()?;
1142 let version = Version::from_i32(version_num).ok_or_else(|| {
1143 tracing::debug!(target: "async_snmp::agent", { source = %source, kind = %DecodeErrorKind::UnknownVersion(version_num) }, "unknown SNMP version");
1144 Error::MalformedResponse { target: source }.boxed()
1145 })?;
1146 drop(seq);
1147 drop(decoder);
1148
1149 match version {
1150 Version::V1 => self.handle_v1(data, source).await,
1151 Version::V2c => self.handle_v2c(data, source).await,
1152 Version::V3 => self.handle_v3(data, source).await,
1153 }
1154 }
1155
1156 fn update_engine_time(&self) {
1164 let total_secs = self.inner.state.engine_start.elapsed().as_secs();
1165 let (boots, time) =
1166 compute_engine_boots_time(self.inner.state.engine_boots_base, total_secs);
1167
1168 if boots != self.inner.state.engine_boots.load(Ordering::Relaxed)
1169 && boots > self.inner.state.engine_boots_base
1170 {
1171 tracing::warn!(
1172 target: "async_snmp::agent",
1173 engine_boots = boots,
1174 "engine time wrapped past MAX_ENGINE_TIME, incrementing engine boots"
1175 );
1176 }
1177
1178 self.inner
1179 .state
1180 .engine_boots
1181 .store(boots, Ordering::Relaxed);
1182 self.inner.state.engine_time.store(time, Ordering::Relaxed);
1183 }
1184
1185 pub(crate) fn validate_community(&self, community: &[u8]) -> bool {
1190 if self.inner.communities.is_empty() {
1191 return false;
1193 }
1194 let mut valid = false;
1198 for configured in &self.inner.communities {
1199 if configured.len() == community.len()
1201 && bool::from(configured.as_slice().ct_eq(community))
1202 {
1203 valid = true;
1204 }
1205 }
1206 valid
1207 }
1208
1209 async fn dispatch_request(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1211 match pdu.pdu_type {
1212 PduType::GetRequest => self.handle_get(ctx, pdu).await,
1213 PduType::GetNextRequest => self.handle_get_next(ctx, pdu).await,
1214 PduType::GetBulkRequest => {
1215 if ctx.version == Version::V1 {
1217 return Ok(pdu.to_error_response(ErrorStatus::GenErr, 0));
1218 }
1219 self.handle_get_bulk(ctx, pdu).await
1220 }
1221 PduType::SetRequest => self.handle_set(ctx, pdu).await,
1222 PduType::InformRequest => self.handle_inform(pdu),
1223 _ => {
1224 Ok(pdu.to_error_response(ErrorStatus::GenErr, 0))
1226 }
1227 }
1228 }
1229
1230 fn handle_inform(&self, pdu: &Pdu) -> Result<Pdu> {
1236 Ok(pdu.to_response())
1238 }
1239
1240 async fn handle_get(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1242 let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
1243
1244 for (index, vb) in pdu.varbinds.iter().enumerate() {
1245 if let Some(ref vacm) = self.inner.vacm
1247 && !vacm.check_access(ctx.read_view.as_ref(), &vb.oid)
1248 {
1249 if ctx.version == Version::V1 {
1251 return Ok(pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32));
1252 } else {
1253 response_varbinds.push(VarBind::new(vb.oid.clone(), Value::NoSuchObject));
1255 continue;
1256 }
1257 }
1258
1259 let result = if let Some(handler) = self.find_handler(&vb.oid) {
1260 handler.handler.get(ctx, &vb.oid).await
1261 } else {
1262 GetResult::NoSuchObject
1263 };
1264
1265 let response_value = match result {
1266 GetResult::Value(v) => {
1267 if ctx.version == Version::V1 && matches!(v, Value::Counter64(_)) {
1269 return Ok(
1270 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1271 );
1272 }
1273 v
1274 }
1275 GetResult::NoSuchObject => {
1276 if ctx.version == Version::V1 {
1278 return Ok(
1279 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1280 );
1281 } else {
1282 Value::NoSuchObject
1283 }
1284 }
1285 GetResult::NoSuchInstance => {
1286 if ctx.version == Version::V1 {
1288 return Ok(
1289 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1290 );
1291 } else {
1292 Value::NoSuchInstance
1293 }
1294 }
1295 };
1296
1297 response_varbinds.push(VarBind::new(vb.oid.clone(), response_value));
1298 }
1299
1300 Ok(Pdu {
1301 pdu_type: PduType::Response,
1302 request_id: pdu.request_id,
1303 error_status: 0,
1304 error_index: 0,
1305 varbinds: response_varbinds,
1306 })
1307 }
1308
1309 async fn handle_get_next(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1311 let mut response_varbinds = Vec::with_capacity(pdu.varbinds.len());
1312
1313 for (index, vb) in pdu.varbinds.iter().enumerate() {
1314 let next = self.get_next_accessible_oid(ctx, &vb.oid).await;
1318
1319 match next {
1320 Some(next_vb) => {
1321 response_varbinds.push(next_vb);
1322 }
1323 None => {
1324 if ctx.version == Version::V1 {
1326 return Ok(
1327 pdu.to_error_response(ErrorStatus::NoSuchName, (index + 1) as i32)
1328 );
1329 } else {
1330 response_varbinds.push(VarBind::new(vb.oid.clone(), Value::EndOfMibView));
1331 }
1332 }
1333 }
1334 }
1335
1336 Ok(Pdu {
1337 pdu_type: PduType::Response,
1338 request_id: pdu.request_id,
1339 error_status: 0,
1340 error_index: 0,
1341 varbinds: response_varbinds,
1342 })
1343 }
1344
1345 async fn handle_get_bulk(&self, ctx: &RequestContext, pdu: &Pdu) -> Result<Pdu> {
1350 let non_repeaters = pdu.error_status.max(0) as usize;
1352 let max_repetitions = pdu.error_index.max(0) as usize;
1353
1354 let mut response_varbinds = Vec::new();
1355 let mut current_size: usize = RESPONSE_OVERHEAD;
1356 let agent_max = self.inner.state.max_message_size;
1357 let max_size = match ctx.msg_max_size {
1358 Some(client_max) => agent_max.min(client_max as usize),
1359 None => agent_max,
1360 };
1361
1362 let can_add = |vb: &VarBind, current_size: usize| -> bool {
1364 current_size + vb.encoded_size() <= max_size
1365 };
1366
1367 for vb in pdu.varbinds.iter().take(non_repeaters) {
1369 let next = self.get_next_accessible_oid(ctx, &vb.oid).await;
1370
1371 let next_vb = match next {
1372 Some(next_vb) => next_vb,
1373 None => VarBind::new(vb.oid.clone(), Value::EndOfMibView),
1374 };
1375
1376 if !can_add(&next_vb, current_size) {
1377 if response_varbinds.is_empty() {
1379 return Ok(pdu.to_error_response(ErrorStatus::TooBig, 0));
1380 }
1381 break;
1383 }
1384
1385 current_size += next_vb.encoded_size();
1386 response_varbinds.push(next_vb);
1387 }
1388
1389 if non_repeaters < pdu.varbinds.len() {
1391 let repeaters = &pdu.varbinds[non_repeaters..];
1392 let mut current_oids: Vec<Oid> = repeaters.iter().map(|vb| vb.oid.clone()).collect();
1393 let mut all_done = vec![false; repeaters.len()];
1394
1395 'outer: for _ in 0..max_repetitions {
1396 let mut row_complete = true;
1397 for (i, oid) in current_oids.iter_mut().enumerate() {
1398 let next_vb = if all_done[i] {
1399 VarBind::new(oid.clone(), Value::EndOfMibView)
1400 } else {
1401 let next = self.get_next_accessible_oid(ctx, oid).await;
1402
1403 match next {
1404 Some(next_vb) => {
1405 *oid = next_vb.oid.clone();
1406 row_complete = false;
1407 next_vb
1408 }
1409 None => {
1410 all_done[i] = true;
1411 VarBind::new(oid.clone(), Value::EndOfMibView)
1412 }
1413 }
1414 };
1415
1416 if !can_add(&next_vb, current_size) {
1418 break 'outer;
1420 }
1421
1422 current_size += next_vb.encoded_size();
1423 response_varbinds.push(next_vb);
1424 }
1425
1426 if row_complete {
1427 break;
1428 }
1429 }
1430 }
1431
1432 Ok(Pdu {
1433 pdu_type: PduType::Response,
1434 request_id: pdu.request_id,
1435 error_status: 0,
1436 error_index: 0,
1437 varbinds: response_varbinds,
1438 })
1439 }
1440
1441 pub(crate) fn find_handler(&self, oid: &Oid) -> Option<&RegisteredHandler> {
1443 self.inner
1445 .handlers
1446 .iter()
1447 .find(|&handler| handler.handler.handles(&handler.prefix, oid))
1448 .map(|v| v as _)
1449 }
1450
1451 async fn get_next_accessible_oid(
1455 &self,
1456 ctx: &RequestContext,
1457 from_oid: &Oid,
1458 ) -> Option<VarBind> {
1459 let mut search_from = from_oid.clone();
1460 loop {
1461 let candidate = self.get_next_oid(ctx, &search_from).await;
1462 match candidate {
1463 None => return None,
1464 Some(ref next_vb) => {
1465 if next_vb.oid <= search_from {
1466 tracing::error!(
1467 target: "async_snmp::agent",
1468 from = %search_from,
1469 got = %next_vb.oid,
1470 "handler returned non-increasing OID in GETNEXT"
1471 );
1472 return None;
1473 }
1474 if ctx.version == Version::V1 && matches!(next_vb.value, Value::Counter64(_)) {
1476 search_from = next_vb.oid.clone();
1477 continue;
1478 }
1479 if let Some(ref vacm) = self.inner.vacm {
1480 if vacm.check_access(ctx.read_view.as_ref(), &next_vb.oid) {
1481 return candidate;
1482 } else {
1483 search_from = next_vb.oid.clone();
1484 }
1485 } else {
1486 return candidate;
1487 }
1488 }
1489 }
1490 }
1491 }
1492
1493 async fn get_next_oid(&self, ctx: &RequestContext, oid: &Oid) -> Option<VarBind> {
1495 let mut best_result: Option<VarBind> = None;
1504
1505 for handler in &self.inner.handlers {
1506 let prefix = &handler.prefix;
1507 if prefix <= oid && !oid.starts_with(prefix) {
1508 continue;
1509 }
1510 if let GetNextResult::Value(next) = handler.handler.get_next(ctx, oid).await {
1511 if next.oid > *oid {
1513 match &best_result {
1514 None => best_result = Some(next),
1515 Some(current) if next.oid < current.oid => best_result = Some(next),
1516 _ => {}
1517 }
1518 }
1519 }
1520 }
1521
1522 best_result
1523 }
1524}
1525
1526impl Clone for Agent {
1527 fn clone(&self) -> Self {
1528 Self {
1529 inner: Arc::clone(&self.inner),
1530 }
1531 }
1532}
1533
1534#[cfg(test)]
1535mod tests {
1536 use super::*;
1537 use crate::handler::{
1538 BoxFuture, GetNextResult, GetResult, MibHandler, RequestContext, SecurityModel, SetResult,
1539 };
1540 use crate::message::SecurityLevel;
1541 use crate::oid;
1542
1543 struct TestHandler;
1544
1545 impl MibHandler for TestHandler {
1546 fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
1547 Box::pin(async move {
1548 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
1549 return GetResult::Value(Value::Integer(42));
1550 }
1551 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
1552 return GetResult::Value(Value::OctetString(Bytes::from_static(b"test")));
1553 }
1554 GetResult::NoSuchObject
1555 })
1556 }
1557
1558 fn get_next<'a>(
1559 &'a self,
1560 _ctx: &'a RequestContext,
1561 oid: &'a Oid,
1562 ) -> BoxFuture<'a, GetNextResult> {
1563 Box::pin(async move {
1564 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
1565 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
1566
1567 if oid < &oid1 {
1568 return GetNextResult::Value(VarBind::new(oid1, Value::Integer(42)));
1569 }
1570 if oid < &oid2 {
1571 return GetNextResult::Value(VarBind::new(
1572 oid2,
1573 Value::OctetString(Bytes::from_static(b"test")),
1574 ));
1575 }
1576 GetNextResult::EndOfMibView
1577 })
1578 }
1579 }
1580
1581 fn test_ctx() -> RequestContext {
1582 RequestContext {
1583 source: "127.0.0.1:12345".parse().unwrap(),
1584 version: Version::V2c,
1585 security_model: SecurityModel::V2c,
1586 security_name: Bytes::from_static(b"public"),
1587 security_level: SecurityLevel::NoAuthNoPriv,
1588 context_name: Bytes::new(),
1589 request_id: 1,
1590 pdu_type: PduType::GetRequest,
1591 group_name: None,
1592 read_view: None,
1593 write_view: None,
1594 msg_max_size: None,
1595 }
1596 }
1597
1598 #[test]
1599 fn test_agent_builder_defaults() {
1600 let builder = AgentBuilder::new();
1601 assert_eq!(builder.bind_addr, "0.0.0.0:161");
1602 assert!(builder.communities.is_empty());
1603 assert!(builder.usm_users.is_empty());
1604 assert!(builder.handlers.is_empty());
1605 }
1606
1607 #[test]
1608 fn test_agent_builder_community() {
1609 let builder = AgentBuilder::new()
1610 .community(b"public")
1611 .community(b"private");
1612 assert_eq!(builder.communities.len(), 2);
1613 }
1614
1615 #[test]
1616 fn test_agent_builder_communities() {
1617 let builder = AgentBuilder::new().communities(["public", "private"]);
1618 assert_eq!(builder.communities.len(), 2);
1619 }
1620
1621 #[test]
1622 fn test_agent_builder_handler() {
1623 let builder =
1624 AgentBuilder::new().handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler));
1625 assert_eq!(builder.handlers.len(), 1);
1626 }
1627
1628 #[tokio::test]
1629 async fn test_mib_handler_default_set() {
1630 let handler = TestHandler;
1631 let mut ctx = test_ctx();
1632 ctx.pdu_type = PduType::SetRequest;
1633
1634 let result = handler
1635 .test_set(&ctx, &oid!(1, 3, 6, 1), &Value::Integer(1))
1636 .await;
1637 assert_eq!(result, SetResult::NotWritable);
1638 }
1639
1640 #[test]
1641 fn test_mib_handler_handles() {
1642 let handler = TestHandler;
1643 let prefix = oid!(1, 3, 6, 1, 4, 1, 99999);
1644
1645 assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0)));
1647
1648 assert!(handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99999)));
1650
1651 assert!(!handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 99998)));
1654
1655 assert!(!handler.handles(&prefix, &oid!(1, 3, 6, 1, 4, 1, 100000)));
1657 }
1658
1659 #[tokio::test]
1660 async fn test_test_handler_get() {
1661 let handler = TestHandler;
1662 let ctx = test_ctx();
1663
1664 let result = handler
1666 .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
1667 .await;
1668 assert!(matches!(result, GetResult::Value(Value::Integer(42))));
1669
1670 let result = handler
1672 .get(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 99, 0))
1673 .await;
1674 assert!(matches!(result, GetResult::NoSuchObject));
1675 }
1676
1677 #[tokio::test]
1678 async fn test_test_handler_get_next() {
1679 let handler = TestHandler;
1680 let mut ctx = test_ctx();
1681 ctx.pdu_type = PduType::GetNextRequest;
1682
1683 let next = handler.get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999)).await;
1685 assert!(next.is_value());
1686 if let GetNextResult::Value(vb) = next {
1687 assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0));
1688 }
1689
1690 let next = handler
1692 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
1693 .await;
1694 assert!(next.is_value());
1695 if let GetNextResult::Value(vb) = next {
1696 assert_eq!(vb.oid, oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0));
1697 }
1698
1699 let next = handler
1701 .get_next(&ctx, &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0))
1702 .await;
1703 assert!(next.is_end_of_mib_view());
1704 }
1705
1706 struct FiveOidHandler;
1708
1709 impl MibHandler for FiveOidHandler {
1710 fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
1711 Box::pin(async move {
1712 for i in 1u32..=5 {
1713 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, i, 0) {
1714 return GetResult::Value(Value::Integer(i as i32));
1715 }
1716 }
1717 GetResult::NoSuchObject
1718 })
1719 }
1720
1721 fn get_next<'a>(
1722 &'a self,
1723 _ctx: &'a RequestContext,
1724 oid: &'a Oid,
1725 ) -> BoxFuture<'a, GetNextResult> {
1726 Box::pin(async move {
1727 for i in 1u32..=5 {
1728 let candidate = oid!(1, 3, 6, 1, 4, 1, 99999, i, 0);
1729 if oid < &candidate {
1730 return GetNextResult::Value(VarBind::new(
1731 candidate,
1732 Value::Integer(i as i32),
1733 ));
1734 }
1735 }
1736 GetNextResult::EndOfMibView
1737 })
1738 }
1739 }
1740
1741 async fn test_agent_with_restricted_vacm() -> Agent {
1745 Agent::builder()
1746 .bind("127.0.0.1:0")
1747 .community(b"public")
1748 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
1749 .vacm(|v| {
1750 v.group("public", SecurityModel::V2c, "readers")
1751 .access("readers", |a| a.read_view("restricted"))
1752 .view("restricted", |v| {
1753 v.include(oid!(1, 3, 6, 1, 4, 1, 99999, 2))
1754 .include(oid!(1, 3, 6, 1, 4, 1, 99999, 4))
1755 })
1756 })
1757 .build()
1758 .await
1759 .unwrap()
1760 }
1761
1762 #[tokio::test]
1763 async fn test_getbulk_vacm_filters_inaccessible_oids() {
1764 let agent = test_agent_with_restricted_vacm().await;
1765
1766 let mut ctx = test_ctx();
1767 ctx.pdu_type = PduType::GetBulkRequest;
1768 ctx.read_view = Some(Bytes::from_static(b"restricted"));
1769
1770 let pdu = Pdu {
1774 pdu_type: PduType::GetBulkRequest,
1775 request_id: 1,
1776 error_status: 0, error_index: 10, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
1779 };
1780
1781 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
1782
1783 let returned_oids: Vec<&Oid> = response
1785 .varbinds
1786 .iter()
1787 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
1788 .map(|vb| &vb.oid)
1789 .collect();
1790
1791 assert!(
1793 returned_oids.contains(&&oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)),
1794 "expected .99999.2.0 in response, got: {:?}",
1795 returned_oids
1796 );
1797 assert!(
1798 returned_oids.contains(&&oid!(1, 3, 6, 1, 4, 1, 99999, 4, 0)),
1799 "expected .99999.4.0 in response (walk must continue past denied OIDs), got: {:?}",
1800 returned_oids
1801 );
1802
1803 for &oid in &[
1805 &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
1806 &oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0),
1807 &oid!(1, 3, 6, 1, 4, 1, 99999, 5, 0),
1808 ] {
1809 assert!(
1810 !returned_oids.contains(&oid),
1811 "GETBULK returned OID outside read view: {:?}",
1812 oid
1813 );
1814 }
1815 }
1816
1817 #[tokio::test]
1818 async fn test_getbulk_non_repeaters_vacm_filtered() {
1819 let agent = test_agent_with_restricted_vacm().await;
1820
1821 let mut ctx = test_ctx();
1822 ctx.pdu_type = PduType::GetBulkRequest;
1823 ctx.read_view = Some(Bytes::from_static(b"restricted"));
1824
1825 let pdu = Pdu {
1831 pdu_type: PduType::GetBulkRequest,
1832 request_id: 2,
1833 error_status: 2, error_index: 0, varbinds: vec![
1836 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null),
1837 VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999, 4, 0), Value::Null),
1838 ],
1839 };
1840
1841 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
1842
1843 assert_eq!(
1845 response.varbinds[0].oid,
1846 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0)
1847 );
1848 assert!(matches!(response.varbinds[0].value, Value::Integer(2)));
1849
1850 assert_eq!(response.varbinds[1].value, Value::EndOfMibView);
1852 }
1853
1854 struct ThreeOidHandler;
1856
1857 impl MibHandler for ThreeOidHandler {
1858 fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
1859 Box::pin(async move {
1860 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
1861 return GetResult::Value(Value::Integer(1));
1862 }
1863 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
1864 return GetResult::Value(Value::Integer(2));
1865 }
1866 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0) {
1867 return GetResult::Value(Value::Integer(3));
1868 }
1869 GetResult::NoSuchObject
1870 })
1871 }
1872
1873 fn get_next<'a>(
1874 &'a self,
1875 _ctx: &'a RequestContext,
1876 oid: &'a Oid,
1877 ) -> BoxFuture<'a, GetNextResult> {
1878 Box::pin(async move {
1879 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
1880 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
1881 let oid3 = oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0);
1882
1883 if oid < &oid1 {
1884 return GetNextResult::Value(VarBind::new(oid1, Value::Integer(1)));
1885 }
1886 if oid < &oid2 {
1887 return GetNextResult::Value(VarBind::new(oid2, Value::Integer(2)));
1888 }
1889 if oid < &oid3 {
1890 return GetNextResult::Value(VarBind::new(oid3, Value::Integer(3)));
1891 }
1892 GetNextResult::EndOfMibView
1893 })
1894 }
1895 }
1896
1897 async fn test_agent_with_gap_vacm() -> Agent {
1900 Agent::builder()
1901 .bind("127.0.0.1:0")
1902 .community(b"public")
1903 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(ThreeOidHandler))
1904 .vacm(|v| {
1905 v.group("public", SecurityModel::V2c, "readers")
1906 .access("readers", |a| a.read_view("gap"))
1907 .view("gap", |v| {
1908 v.include(oid!(1, 3, 6, 1, 4, 1, 99999, 1))
1909 .include(oid!(1, 3, 6, 1, 4, 1, 99999, 3))
1910 })
1911 })
1912 .build()
1913 .await
1914 .unwrap()
1915 }
1916
1917 #[tokio::test]
1918 async fn test_getnext_vacm_skips_inaccessible_continues_walk() {
1919 let agent = test_agent_with_gap_vacm().await;
1923
1924 let mut ctx = test_ctx();
1925 ctx.pdu_type = PduType::GetNextRequest;
1926 ctx.read_view = Some(Bytes::from_static(b"gap"));
1927
1928 let pdu = Pdu {
1929 pdu_type: PduType::GetNextRequest,
1930 request_id: 1,
1931 error_status: 0,
1932 error_index: 0,
1933 varbinds: vec![VarBind::new(
1934 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
1935 Value::Null,
1936 )],
1937 };
1938
1939 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
1940 assert_eq!(response.varbinds.len(), 1);
1941 assert_eq!(
1942 response.varbinds[0].oid,
1943 oid!(1, 3, 6, 1, 4, 1, 99999, 3, 0),
1944 "GETNEXT should skip denied .99999.2.0 and return accessible .99999.3.0"
1945 );
1946 assert!(matches!(response.varbinds[0].value, Value::Integer(3)));
1947 }
1948
1949 #[tokio::test]
1950 async fn test_getnext_vacm_all_remaining_denied_returns_end_of_mib() {
1951 let agent = test_agent_with_restricted_vacm().await;
1955
1956 let mut ctx = test_ctx();
1957 ctx.pdu_type = PduType::GetNextRequest;
1958 ctx.read_view = Some(Bytes::from_static(b"restricted"));
1959
1960 let pdu = Pdu {
1961 pdu_type: PduType::GetNextRequest,
1962 request_id: 1,
1963 error_status: 0,
1964 error_index: 0,
1965 varbinds: vec![VarBind::new(
1966 oid!(1, 3, 6, 1, 4, 1, 99999, 4, 0),
1967 Value::Null,
1968 )],
1969 };
1970
1971 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
1972 assert_eq!(response.varbinds.len(), 1);
1973 assert_eq!(
1974 response.varbinds[0].value,
1975 Value::EndOfMibView,
1976 "GETNEXT should return EndOfMibView when all remaining OIDs are denied"
1977 );
1978 }
1979
1980 #[tokio::test]
1981 async fn test_getbulk_without_vacm_returns_all_oids() {
1982 let agent = Agent::builder()
1984 .bind("127.0.0.1:0")
1985 .community(b"public")
1986 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler))
1987 .build()
1988 .await
1989 .unwrap();
1990
1991 let mut ctx = test_ctx();
1992 ctx.pdu_type = PduType::GetBulkRequest;
1993
1994 let pdu = Pdu {
1995 pdu_type: PduType::GetBulkRequest,
1996 request_id: 1,
1997 error_status: 0,
1998 error_index: 10,
1999 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2000 };
2001
2002 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2003
2004 assert!(
2006 response
2007 .varbinds
2008 .iter()
2009 .any(|vb| vb.oid == oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0))
2010 );
2011 assert!(
2012 response
2013 .varbinds
2014 .iter()
2015 .any(|vb| vb.oid == oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0))
2016 );
2017 }
2018
2019 #[tokio::test]
2020 async fn test_v1_getbulk_rejected() {
2021 let agent = Agent::builder()
2023 .bind("127.0.0.1:0")
2024 .community(b"public")
2025 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(TestHandler))
2026 .build()
2027 .await
2028 .unwrap();
2029
2030 let mut ctx = test_ctx();
2031 ctx.version = Version::V1;
2032 ctx.security_model = SecurityModel::V1;
2033 ctx.pdu_type = PduType::GetBulkRequest;
2034
2035 let pdu = Pdu {
2036 pdu_type: PduType::GetBulkRequest,
2037 request_id: 1,
2038 error_status: 0,
2039 error_index: 10,
2040 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2041 };
2042
2043 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2044 assert_eq!(
2045 ErrorStatus::from_i32(response.error_status),
2046 ErrorStatus::GenErr,
2047 "v1 GETBULK should be rejected"
2048 );
2049 }
2050
2051 struct Counter64Handler;
2053
2054 impl MibHandler for Counter64Handler {
2055 fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
2056 Box::pin(async move {
2057 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0) {
2058 return GetResult::Value(Value::Counter64(1_000_000_000_000));
2059 }
2060 if oid == &oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0) {
2061 return GetResult::Value(Value::Integer(42));
2062 }
2063 GetResult::NoSuchObject
2064 })
2065 }
2066
2067 fn get_next<'a>(
2068 &'a self,
2069 _ctx: &'a RequestContext,
2070 oid: &'a Oid,
2071 ) -> BoxFuture<'a, GetNextResult> {
2072 Box::pin(async move {
2073 let oid1 = oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0);
2074 let oid2 = oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0);
2075
2076 if oid < &oid1 {
2077 return GetNextResult::Value(VarBind::new(
2078 oid1,
2079 Value::Counter64(1_000_000_000_000),
2080 ));
2081 }
2082 if oid < &oid2 {
2083 return GetNextResult::Value(VarBind::new(oid2, Value::Integer(42)));
2084 }
2085 GetNextResult::EndOfMibView
2086 })
2087 }
2088 }
2089
2090 async fn test_agent_with_counter64() -> Agent {
2091 Agent::builder()
2092 .bind("127.0.0.1:0")
2093 .community(b"public")
2094 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(Counter64Handler))
2095 .build()
2096 .await
2097 .unwrap()
2098 }
2099
2100 #[tokio::test]
2101 async fn test_v1_get_filters_counter64() {
2102 let agent = test_agent_with_counter64().await;
2105
2106 let mut ctx = test_ctx();
2107 ctx.version = Version::V1;
2108 ctx.security_model = SecurityModel::V1;
2109 ctx.pdu_type = PduType::GetRequest;
2110
2111 let pdu = Pdu {
2112 pdu_type: PduType::GetRequest,
2113 request_id: 1,
2114 error_status: 0,
2115 error_index: 0,
2116 varbinds: vec![VarBind::new(
2117 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2118 Value::Null,
2119 )],
2120 };
2121
2122 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2123 assert_eq!(
2124 ErrorStatus::from_i32(response.error_status),
2125 ErrorStatus::NoSuchName,
2126 "v1 GET of Counter64 should return noSuchName"
2127 );
2128 }
2129
2130 #[tokio::test]
2131 async fn test_v2c_get_allows_counter64() {
2132 let agent = test_agent_with_counter64().await;
2134
2135 let ctx = test_ctx(); let pdu = Pdu {
2138 pdu_type: PduType::GetRequest,
2139 request_id: 1,
2140 error_status: 0,
2141 error_index: 0,
2142 varbinds: vec![VarBind::new(
2143 oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0),
2144 Value::Null,
2145 )],
2146 };
2147
2148 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2149 assert_eq!(response.error_status, 0);
2150 assert!(matches!(response.varbinds[0].value, Value::Counter64(_)));
2151 }
2152
2153 #[tokio::test]
2154 async fn test_getbulk_respects_v3_msg_max_size() {
2155 let agent = Agent::builder()
2160 .bind("127.0.0.1:0")
2161 .community(b"public")
2162 .max_message_size(65507) .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
2164 .build()
2165 .await
2166 .unwrap();
2167
2168 let mut ctx_unlimited = test_ctx();
2170 ctx_unlimited.pdu_type = PduType::GetBulkRequest;
2171 ctx_unlimited.msg_max_size = None;
2172
2173 let pdu = Pdu {
2174 pdu_type: PduType::GetBulkRequest,
2175 request_id: 1,
2176 error_status: 0, error_index: 10, varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2179 };
2180
2181 let full_response = agent.dispatch_request(&ctx_unlimited, &pdu).await.unwrap();
2182 let full_count = full_response
2183 .varbinds
2184 .iter()
2185 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2186 .count();
2187 assert!(
2188 full_count >= 3,
2189 "expected at least 3 data varbinds without limit, got {}",
2190 full_count
2191 );
2192
2193 let mut ctx_limited = test_ctx();
2198 ctx_limited.pdu_type = PduType::GetBulkRequest;
2199 ctx_limited.msg_max_size = Some(150); let limited_response = agent.dispatch_request(&ctx_limited, &pdu).await.unwrap();
2202 let limited_count = limited_response
2203 .varbinds
2204 .iter()
2205 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2206 .count();
2207
2208 assert!(
2209 limited_count < full_count,
2210 "V3 msg_max_size should limit response: got {} varbinds (unlimited: {})",
2211 limited_count,
2212 full_count
2213 );
2214 assert!(
2215 limited_count > 0,
2216 "should still return at least one varbind"
2217 );
2218 }
2219
2220 #[tokio::test]
2221 async fn test_getbulk_msg_max_size_none_uses_agent_max() {
2222 let agent = Agent::builder()
2225 .bind("127.0.0.1:0")
2226 .community(b"public")
2227 .max_message_size(65507)
2228 .handler(oid!(1, 3, 6, 1, 4, 1, 99999), Arc::new(FiveOidHandler))
2229 .without_builtin_handlers()
2230 .build()
2231 .await
2232 .unwrap();
2233
2234 let mut ctx = test_ctx();
2235 ctx.pdu_type = PduType::GetBulkRequest;
2236 ctx.msg_max_size = None; let pdu = Pdu {
2239 pdu_type: PduType::GetBulkRequest,
2240 request_id: 1,
2241 error_status: 0,
2242 error_index: 10,
2243 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2244 };
2245
2246 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2247 let data_count = response
2248 .varbinds
2249 .iter()
2250 .filter(|vb| !matches!(vb.value, Value::EndOfMibView))
2251 .count();
2252 assert_eq!(
2253 data_count, 5,
2254 "all 5 OIDs should be returned without msg_max_size limit"
2255 );
2256 }
2257
2258 #[tokio::test]
2259 async fn test_v1_getnext_skips_counter64() {
2260 let agent = test_agent_with_counter64().await;
2264
2265 let mut ctx = test_ctx();
2266 ctx.version = Version::V1;
2267 ctx.security_model = SecurityModel::V1;
2268 ctx.pdu_type = PduType::GetNextRequest;
2269
2270 let pdu = Pdu {
2271 pdu_type: PduType::GetNextRequest,
2272 request_id: 1,
2273 error_status: 0,
2274 error_index: 0,
2275 varbinds: vec![VarBind::new(oid!(1, 3, 6, 1, 4, 1, 99999), Value::Null)],
2276 };
2277
2278 let response = agent.dispatch_request(&ctx, &pdu).await.unwrap();
2279 assert_eq!(response.error_status, 0, "should succeed");
2280 assert_eq!(
2281 response.varbinds[0].oid,
2282 oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0),
2283 "should skip Counter64 and return next non-Counter64 OID"
2284 );
2285 assert!(matches!(response.varbinds[0].value, Value::Integer(42)));
2286 }
2287
2288 #[test]
2289 fn test_engine_time_no_overflow() {
2290 let (boots, time) = crate::v3::compute_engine_boots_time(1, 1000);
2292 assert_eq!(boots, 1);
2293 assert_eq!(time, 1000);
2294 }
2295
2296 #[test]
2297 fn test_engine_time_zero_elapsed() {
2298 let (boots, time) = crate::v3::compute_engine_boots_time(1, 0);
2299 assert_eq!(boots, 1);
2300 assert_eq!(time, 0);
2301 }
2302
2303 #[test]
2304 fn test_engine_time_just_below_max() {
2305 let max = crate::v3::MAX_ENGINE_TIME;
2306 let (boots, time) = crate::v3::compute_engine_boots_time(1, max as u64 - 1);
2307 assert_eq!(boots, 1);
2308 assert_eq!(time, max - 1);
2309 }
2310
2311 #[test]
2312 fn test_engine_time_at_max_wraps() {
2313 let max = crate::v3::MAX_ENGINE_TIME;
2315 let (boots, time) = crate::v3::compute_engine_boots_time(1, max as u64);
2316 assert_eq!(
2317 boots, 2,
2318 "boots should increment when elapsed reaches MAX_ENGINE_TIME"
2319 );
2320 assert_eq!(time, 0, "time should wrap to 0");
2321 }
2322
2323 #[test]
2324 fn test_engine_time_past_max() {
2325 let max = crate::v3::MAX_ENGINE_TIME;
2327 let (boots, time) = crate::v3::compute_engine_boots_time(1, max as u64 + 500);
2328 assert_eq!(boots, 2);
2329 assert_eq!(time, 500);
2330 }
2331
2332 #[test]
2333 fn test_engine_time_multiple_wraps() {
2334 let max = crate::v3::MAX_ENGINE_TIME;
2336 let elapsed = max as u64 * 3 + 42;
2337 let (boots, time) = crate::v3::compute_engine_boots_time(1, elapsed);
2338 assert_eq!(boots, 4, "base 1 + 3 wraps = 4");
2339 assert_eq!(time, 42);
2340 }
2341
2342 #[test]
2343 fn test_engine_time_boots_capped_at_max() {
2344 let max = crate::v3::MAX_ENGINE_TIME;
2346 let elapsed = max as u64 * (max as u64); let (boots, _time) = crate::v3::compute_engine_boots_time(1, elapsed);
2348 assert_eq!(boots, max, "boots should be capped at MAX_ENGINE_TIME");
2349 }
2350
2351 #[test]
2352 fn test_engine_time_base_boots_preserved() {
2353 let max = crate::v3::MAX_ENGINE_TIME;
2355 let (boots, time) = crate::v3::compute_engine_boots_time(5, max as u64 + 100);
2356 assert_eq!(boots, 6, "base 5 + 1 wrap = 6");
2357 assert_eq!(time, 100);
2358 }
2359
2360 #[test]
2361 fn test_engine_time_high_base_boots_capped() {
2362 let max = crate::v3::MAX_ENGINE_TIME;
2364 let (boots, _time) = crate::v3::compute_engine_boots_time(max - 1, max as u64 * 2);
2365 assert_eq!(boots, max, "should cap at MAX_ENGINE_TIME, not overflow");
2366 }
2367
2368 #[tokio::test]
2369 async fn test_engine_boots_builder() {
2370 let agent = Agent::builder()
2372 .bind("127.0.0.1:0")
2373 .community(b"public")
2374 .engine_boots(42)
2375 .build()
2376 .await
2377 .unwrap();
2378
2379 assert_eq!(agent.engine_boots(), 42);
2380 }
2381
2382 #[tokio::test]
2383 async fn test_engine_boots_default() {
2384 let agent = Agent::builder()
2386 .bind("127.0.0.1:0")
2387 .community(b"public")
2388 .build()
2389 .await
2390 .unwrap();
2391
2392 assert_eq!(agent.engine_boots(), 1);
2393 }
2394
2395 #[tokio::test]
2396 async fn test_usm_counter_accessors_default_zero() {
2397 let agent = Agent::builder()
2398 .bind("127.0.0.1:0")
2399 .community(b"public")
2400 .build()
2401 .await
2402 .unwrap();
2403
2404 assert_eq!(agent.usm_unsupported_sec_levels(), 0);
2405 assert_eq!(agent.usm_decryption_errors(), 0);
2406 }
2407
2408 #[test]
2409 fn test_builtin_mib_without_single() {
2410 let builder = AgentBuilder::new().without_builtin_handler(BuiltinMib::UsmStats);
2411 assert!(builder.disabled_builtins.contains(&BuiltinMib::UsmStats));
2412 assert!(!builder.disabled_builtins.contains(&BuiltinMib::SnmpEngine));
2413 assert!(!builder.disabled_builtins.contains(&BuiltinMib::MpdStats));
2414 }
2415
2416 #[test]
2417 fn test_builtin_mib_without_all() {
2418 let builder = AgentBuilder::new().without_builtin_handlers();
2419 assert!(builder.disabled_builtins.contains(&BuiltinMib::SnmpEngine));
2420 assert!(builder.disabled_builtins.contains(&BuiltinMib::UsmStats));
2421 assert!(builder.disabled_builtins.contains(&BuiltinMib::MpdStats));
2422 }
2423
2424 #[tokio::test]
2425 async fn test_uptime_hundredths() {
2426 let agent = Agent::builder()
2427 .bind("127.0.0.1:0")
2428 .community(b"public")
2429 .build()
2430 .await
2431 .unwrap();
2432
2433 let uptime = agent.uptime_hundredths();
2434 assert!(
2435 uptime < 100,
2436 "uptime should be less than 1 second, got {}",
2437 uptime
2438 );
2439
2440 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
2441 let uptime2 = agent.uptime_hundredths();
2442 assert!(uptime2 > uptime, "uptime should increase after delay");
2443 }
2444
2445 #[tokio::test]
2446 async fn test_builtin_handlers_registered_by_default() {
2447 let agent = Agent::builder()
2448 .bind("127.0.0.1:0")
2449 .community(b"public")
2450 .build()
2451 .await
2452 .unwrap();
2453
2454 let ctx = test_ctx();
2455
2456 let handler = agent
2458 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 4, 0))
2459 .expect("snmpEngine handler should be registered");
2460 let get_result = handler
2461 .handler
2462 .get(&ctx, &oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 4, 0))
2463 .await;
2464 assert!(matches!(get_result, GetResult::Value(Value::Integer(_))));
2465
2466 let handler = agent
2468 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0))
2469 .expect("USM stats handler should be registered");
2470 let get_result = handler
2471 .handler
2472 .get(&ctx, &oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0))
2473 .await;
2474 assert!(matches!(get_result, GetResult::Value(Value::Counter32(0))));
2475
2476 let handler = agent
2478 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
2479 .expect("MPD stats handler should be registered");
2480 let get_result = handler
2481 .handler
2482 .get(&ctx, &oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
2483 .await;
2484 assert!(matches!(get_result, GetResult::Value(Value::Counter32(0))));
2485 }
2486
2487 #[tokio::test]
2488 async fn test_builtin_handlers_disabled() {
2489 let agent = Agent::builder()
2490 .bind("127.0.0.1:0")
2491 .community(b"public")
2492 .without_builtin_handlers()
2493 .build()
2494 .await
2495 .unwrap();
2496
2497 assert!(
2498 agent
2499 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 1, 0))
2500 .is_none()
2501 );
2502 assert!(
2503 agent
2504 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0))
2505 .is_none()
2506 );
2507 assert!(
2508 agent
2509 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
2510 .is_none()
2511 );
2512 }
2513
2514 #[tokio::test]
2515 async fn test_builtin_handler_selective_disable() {
2516 let agent = Agent::builder()
2517 .bind("127.0.0.1:0")
2518 .community(b"public")
2519 .without_builtin_handler(BuiltinMib::UsmStats)
2520 .build()
2521 .await
2522 .unwrap();
2523
2524 assert!(
2525 agent
2526 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 10, 2, 1, 1, 0))
2527 .is_some()
2528 );
2529 assert!(
2530 agent
2531 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0))
2532 .is_none()
2533 );
2534 assert!(
2535 agent
2536 .find_handler(&oid!(1, 3, 6, 1, 6, 3, 11, 2, 1, 1, 0))
2537 .is_some()
2538 );
2539 }
2540}