├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── benches └── bench.rs └── src ├── lib.rs └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | src/private 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snmp" 3 | version = "0.2.2" 4 | authors = ["Hroi Sigurdsson "] 5 | description = "SNMP client library" 6 | license = "MIT/Apache-2.0" 7 | repository = "https://github.com/hroi/rust-snmp" 8 | documentation = "https://docs.rs/crate/snmp" 9 | readme = "README.md" 10 | keywords = ["networking", "snmp", "monitoring"] 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RUST-SNMP 2 | Dependency-free basic SNMPv2 client in Rust. 3 | 4 | [![Documentation](https://docs.rs/snmp/badge.svg)](https://docs.rs/snmp/) 5 | [![Build Status](https://travis-ci.org/hroi/rust-snmp.svg?branch=master)](https://travis-ci.org/hroi/rust-snmp) 6 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate snmp; 4 | extern crate test; 5 | 6 | const BULK_NAMES: &'static [&'static [u32]] = 7 | &[IF_NAME, IF_HCINUCASTPKTS, IF_HCINOCTETS, IF_HCOUTUCASTPKTS, IF_HCOUTOCTETS]; 8 | 9 | const IF_INDEX: u32 = 0; 10 | 11 | const IF_NAME: &'static [u32] = &[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 1, IF_INDEX]; 12 | const IF_HCINOCTETS: &'static [u32] = &[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, IF_INDEX]; 13 | const IF_HCINUCASTPKTS: &'static [u32] = &[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 7, IF_INDEX]; 14 | const IF_HCOUTOCTETS: &'static [u32] = &[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 10, IF_INDEX]; 15 | const IF_HCOUTUCASTPKTS: &'static [u32] = &[1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 11, IF_INDEX]; 16 | 17 | #[bench] 18 | fn pdu_getnext(b: &mut test::Bencher) { 19 | let mut buf = snmp::pdu::Buf::default(); 20 | b.iter(|| snmp::pdu::build_getnext(b"tyS0n43d", 0, &[1, 3, 6, 1, 2, 1, 1, 1, 0], &mut buf)); 21 | } 22 | 23 | #[bench] 24 | fn pdu_getbulk(b: &mut test::Bencher) { 25 | let mut buf = snmp::pdu::Buf::default(); 26 | b.iter(|| snmp::pdu::build_getbulk(b"tyS0n43d", 0, BULK_NAMES, 3, 10, &mut buf)); 27 | } 28 | 29 | #[bench] 30 | fn asn1_parse_getnext_pdu(b: &mut test::Bencher) { 31 | let pdu = &[0x30, 0x2b, 0x02, 0x01, 0x01, 0x04, 0x08, 0x74, 0x79, 0x53, 0x30, 0x6e, 32 | 0x34, 0x33, 0x64, 0xa1, 0x1c, 0x02, 0x04, 0x4a, 0x9b, 0x6b, 0xa2, 0x02, 33 | 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x08, 0x2b, 34 | 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, 0x05, 0x00]; 35 | b.iter(|| { 36 | let mut reader = snmp::AsnReader::from_bytes(&pdu[..]); 37 | reader.read_asn_sequence(|rdr| { 38 | let version = try!(rdr.read_asn_integer()); 39 | assert_eq!(version, snmp::snmp::VERSION_2 as i64); 40 | let community = try!(rdr.read_asn_octetstring()); 41 | assert_eq!(community, b"tyS0n43d"); 42 | let msg_ident = try!(rdr.peek_byte()); 43 | assert_eq!(msg_ident, snmp::snmp::MSG_GET_NEXT); 44 | rdr.read_constructed(msg_ident, |rdr| { 45 | let req_id = try!(rdr.read_asn_integer()); 46 | let error_status = try!(rdr.read_asn_integer()); 47 | let error_index = try!(rdr.read_asn_integer()); 48 | assert_eq!(req_id, 1251699618); 49 | assert_eq!(error_status, 0); 50 | assert_eq!(error_index, 0); 51 | rdr.read_asn_sequence(|rdr| { 52 | rdr.read_asn_sequence(|rdr| { 53 | let name = try!(rdr.read_asn_objectidentifier()); 54 | let expected = [1, 3, 6, 1, 2, 1, 1, 1, 0]; 55 | assert_eq!(name, &expected[..]); 56 | rdr.read_asn_null() 57 | }) 58 | }) 59 | }) 60 | }).unwrap(); 61 | }); 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Hroi Sigurdsson 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! # RUST-SNMP 10 | //! Dependency-free basic SNMPv2 client in Rust. 11 | //! 12 | //! Suppports: 13 | //! 14 | //! - GET 15 | //! - GETNEXT 16 | //! - GETBULK 17 | //! - SET 18 | //! - Basic SNMPv2 types 19 | //! - Synchronous requests 20 | //! - UDP transport 21 | //! 22 | //! Currently does not support: 23 | //! 24 | //! - SNMPv1 25 | //! - SNMPv3 26 | //! - MIBs 27 | //! - Async requests 28 | //! - Transports other than UDP 29 | //! 30 | //! ## TODO 31 | //! - Async requests 32 | //! - Walking function 33 | //! - Additional `ObjectIdentifier` utility methods 34 | //! - Decouple PDU building/parsing from socket handling 35 | //! - SNMPv3 (would require an external dependency) 36 | //! 37 | 38 | //! # Examples 39 | //! 40 | //! ## GET NEXT 41 | //! ```no_run 42 | //! use std::time::Duration; 43 | //! use snmp::{SyncSession, Value}; 44 | //! 45 | //! let sys_descr_oid = &[1,3,6,1,2,1,1,1,]; 46 | //! let agent_addr = "198.51.100.123:161"; 47 | //! let community = b"f00b4r"; 48 | //! let timeout = Duration::from_secs(2); 49 | //! 50 | //! let mut sess = SyncSession::new(agent_addr, community, Some(timeout), 0).unwrap(); 51 | //! let mut response = sess.getnext(sys_descr_oid).unwrap(); 52 | //! if let Some((_oid, Value::OctetString(sys_descr))) = response.varbinds.next() { 53 | //! println!("myrouter sysDescr: {}", String::from_utf8_lossy(sys_descr)); 54 | //! } 55 | //! ``` 56 | //! ## GET BULK 57 | //! ```no_run 58 | //! use std::time::Duration; 59 | //! use snmp::SyncSession; 60 | //! 61 | //! let system_oid = &[1,3,6,1,2,1,1,]; 62 | //! let agent_addr = "[2001:db8:f00:b413::abc]:161"; 63 | //! let community = b"f00b4r"; 64 | //! let timeout = Duration::from_secs(2); 65 | //! let non_repeaters = 0; 66 | //! let max_repetitions = 7; // number of items in "system" OID 67 | //! 68 | //! let mut sess = SyncSession::new(agent_addr, community, Some(timeout), 0).unwrap(); 69 | //! let response = sess.getbulk(&[system_oid], non_repeaters, max_repetitions).unwrap(); 70 | //! 71 | //! for (name, val) in response.varbinds { 72 | //! println!("{} => {:?}", name, val); 73 | //! } 74 | //! ``` 75 | //! ## SET 76 | //! ```no_run 77 | //! use std::time::Duration; 78 | //! use snmp::{SyncSession, Value}; 79 | //! 80 | //! let syscontact_oid = &[1,3,6,1,2,1,1,4,0]; 81 | //! let contact = Value::OctetString(b"Thomas A. Anderson"); 82 | //! let agent_addr = "[2001:db8:f00:b413::abc]:161"; 83 | //! let community = b"f00b4r"; 84 | //! let timeout = Duration::from_secs(2); 85 | //! 86 | //! let mut sess = SyncSession::new(agent_addr, community, Some(timeout), 0).unwrap(); 87 | //! let response = sess.set(&[(syscontact_oid, contact)]).unwrap(); 88 | //! 89 | //! assert_eq!(response.error_status, snmp::snmp::ERRSTATUS_NOERROR); 90 | //! for (name, val) in response.varbinds { 91 | //! println!("{} => {:?}", name, val); 92 | //! } 93 | //! ``` 94 | 95 | #![cfg_attr(feature = "private-tests", feature(test))] 96 | #![allow(unknown_lints, doc_markdown)] 97 | 98 | use std::fmt; 99 | use std::io; 100 | use std::mem; 101 | use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs, UdpSocket}; 102 | use std::num::Wrapping; 103 | use std::ptr; 104 | use std::time::Duration; 105 | 106 | #[cfg(target_pointer_width="32")] 107 | const USIZE_LEN: usize = 4; 108 | #[cfg(target_pointer_width="64")] 109 | const USIZE_LEN: usize = 8; 110 | 111 | #[cfg(test)] 112 | mod tests; 113 | 114 | #[derive(Debug, PartialEq)] 115 | pub enum SnmpError { 116 | AsnParseError, 117 | AsnInvalidLen, 118 | AsnWrongType, 119 | AsnUnsupportedType, 120 | AsnEof, 121 | AsnIntOverflow, 122 | 123 | UnsupportedVersion, 124 | RequestIdMismatch, 125 | CommunityMismatch, 126 | ValueOutOfRange, 127 | 128 | SendError, 129 | ReceiveError, 130 | } 131 | 132 | type SnmpResult = Result; 133 | 134 | const BUFFER_SIZE: usize = 4096; 135 | 136 | pub mod asn1 { 137 | #![allow(dead_code, identity_op, eq_op)] 138 | 139 | pub const PRIMITIVE: u8 = 0b00000000; 140 | pub const CONSTRUCTED: u8 = 0b00100000; 141 | 142 | pub const CLASS_UNIVERSAL: u8 = 0b00000000; 143 | pub const CLASS_APPLICATION: u8 = 0b01000000; 144 | pub const CLASS_CONTEXTSPECIFIC: u8 = 0b10000000; 145 | pub const CLASS_PRIVATE: u8 = 0b11000000; 146 | 147 | pub const TYPE_BOOLEAN: u8 = CLASS_UNIVERSAL | PRIMITIVE | 1; 148 | pub const TYPE_INTEGER: u8 = CLASS_UNIVERSAL | PRIMITIVE | 2; 149 | pub const TYPE_OCTETSTRING: u8 = CLASS_UNIVERSAL | PRIMITIVE | 4; 150 | pub const TYPE_NULL: u8 = CLASS_UNIVERSAL | PRIMITIVE | 5; 151 | pub const TYPE_OBJECTIDENTIFIER: u8 = CLASS_UNIVERSAL | PRIMITIVE | 6; 152 | pub const TYPE_SEQUENCE: u8 = CLASS_UNIVERSAL | CONSTRUCTED | 16; 153 | pub const TYPE_SET: u8 = CLASS_UNIVERSAL | CONSTRUCTED | 17; 154 | } 155 | 156 | pub mod snmp { 157 | #![allow(dead_code, identity_op, eq_op)] 158 | 159 | use super::asn1; 160 | 161 | pub const VERSION_2: i64 = 1; 162 | 163 | pub const MSG_GET: u8 = asn1::CLASS_CONTEXTSPECIFIC | asn1::CONSTRUCTED | 0; 164 | pub const MSG_GET_NEXT: u8 = asn1::CLASS_CONTEXTSPECIFIC | asn1::CONSTRUCTED | 1; 165 | pub const MSG_RESPONSE: u8 = asn1::CLASS_CONTEXTSPECIFIC | asn1::CONSTRUCTED | 2; 166 | pub const MSG_SET: u8 = asn1::CLASS_CONTEXTSPECIFIC | asn1::CONSTRUCTED | 3; 167 | pub const MSG_GET_BULK: u8 = asn1::CLASS_CONTEXTSPECIFIC | asn1::CONSTRUCTED | 5; 168 | pub const MSG_INFORM: u8 = asn1::CLASS_CONTEXTSPECIFIC | asn1::CONSTRUCTED | 6; 169 | pub const MSG_TRAP: u8 = asn1::CLASS_CONTEXTSPECIFIC | asn1::CONSTRUCTED | 7; 170 | pub const MSG_REPORT: u8 = asn1::CLASS_CONTEXTSPECIFIC | asn1::CONSTRUCTED | 8; 171 | 172 | pub const TYPE_IPADDRESS: u8 = asn1::CLASS_APPLICATION | 0; 173 | pub const TYPE_COUNTER32: u8 = asn1::CLASS_APPLICATION | 1; 174 | pub const TYPE_UNSIGNED32: u8 = asn1::CLASS_APPLICATION | 2; 175 | pub const TYPE_GAUGE32: u8 = TYPE_UNSIGNED32; 176 | pub const TYPE_TIMETICKS: u8 = asn1::CLASS_APPLICATION | 3; 177 | pub const TYPE_OPAQUE: u8 = asn1::CLASS_APPLICATION | 4; 178 | pub const TYPE_COUNTER64: u8 = asn1::CLASS_APPLICATION | 6; 179 | 180 | pub const SNMP_NOSUCHOBJECT: u8 = (asn1::CLASS_CONTEXTSPECIFIC | asn1::PRIMITIVE | 0x0); /* 80=128 */ 181 | pub const SNMP_NOSUCHINSTANCE: u8 = (asn1::CLASS_CONTEXTSPECIFIC | asn1::PRIMITIVE | 0x1); /* 81=129 */ 182 | pub const SNMP_ENDOFMIBVIEW: u8 = (asn1::CLASS_CONTEXTSPECIFIC | asn1::PRIMITIVE | 0x2); /* 82=130 */ 183 | 184 | pub const ERRSTATUS_NOERROR: u32 = 0; 185 | pub const ERRSTATUS_TOOBIG: u32 = 1; 186 | pub const ERRSTATUS_NOSUCHNAME: u32 = 2; 187 | pub const ERRSTATUS_BADVALUE: u32 = 3; 188 | pub const ERRSTATUS_READONLY: u32 = 4; 189 | pub const ERRSTATUS_GENERR: u32 = 5; 190 | pub const ERRSTATUS_NOACCESS: u32 = 6; 191 | pub const ERRSTATUS_WRONGTYPE: u32 = 7; 192 | pub const ERRSTATUS_WRONGLENGTH: u32 = 8; 193 | pub const ERRSTATUS_WRONGENCODING: u32 = 9; 194 | pub const ERRSTATUS_WRONGVALUE: u32 = 10; 195 | pub const ERRSTATUS_NOCREATION: u32 = 11; 196 | pub const ERRSTATUS_INCONSISTENTVALUE: u32 = 12; 197 | pub const ERRSTATUS_RESOURCEUNAVAILABLE: u32 = 13; 198 | pub const ERRSTATUS_COMMITFAILED: u32 = 14; 199 | pub const ERRSTATUS_UNDOFAILED: u32 = 15; 200 | pub const ERRSTATUS_AUTHORIZATIONERROR: u32 = 16; 201 | pub const ERRSTATUS_NOTWRITABLE: u32 = 17; 202 | pub const ERRSTATUS_INCONSISTENTNAME: u32 = 18; 203 | } 204 | 205 | pub mod pdu { 206 | use super::{BUFFER_SIZE, USIZE_LEN, asn1, snmp, Value}; 207 | use std::{fmt, mem, ops, ptr}; 208 | 209 | pub struct Buf { 210 | len: usize, 211 | buf: [u8; BUFFER_SIZE], 212 | } 213 | 214 | impl fmt::Debug for Buf { 215 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 216 | fmt.debug_list() 217 | .entries(&self[..]) 218 | .finish() 219 | } 220 | } 221 | 222 | impl Default for Buf { 223 | fn default() -> Buf { 224 | Buf { 225 | len: 0, 226 | buf: unsafe { mem::uninitialized() }, 227 | } 228 | } 229 | } 230 | 231 | impl ops::Deref for Buf { 232 | type Target = [u8]; 233 | fn deref(&self) -> &[u8] { 234 | &self.buf[BUFFER_SIZE - self.len..] 235 | } 236 | } 237 | 238 | impl Buf { 239 | 240 | fn available(&mut self) -> &mut [u8] { 241 | &mut self.buf[..(BUFFER_SIZE - self.len)] 242 | } 243 | 244 | fn push_chunk(&mut self, chunk: &[u8]) { 245 | let offset = BUFFER_SIZE - self.len; 246 | self.buf[(offset - chunk.len())..offset].copy_from_slice(chunk); 247 | self.len += chunk.len(); 248 | } 249 | 250 | fn push_byte(&mut self, byte: u8) { 251 | self.buf[BUFFER_SIZE - self.len - 1] = byte; 252 | self.len += 1; 253 | } 254 | 255 | fn reset(&mut self) { 256 | self.len = 0; 257 | } 258 | 259 | fn scribble_bytes(&mut self, mut f: F) 260 | where F: FnMut(&mut [u8]) -> usize 261 | { 262 | let scribbled = f(self.available()); 263 | self.len += scribbled; 264 | } 265 | 266 | fn push_constructed(&mut self, ident: u8, mut f: F) 267 | where F: FnMut(&mut Self) 268 | { 269 | let before_len = self.len; 270 | f(self); 271 | let written = self.len - before_len; 272 | self.push_length(written); 273 | self.push_byte(ident); 274 | } 275 | 276 | fn push_sequence(&mut self, f: F) 277 | where F: FnMut(&mut Self) 278 | { 279 | self.push_constructed(asn1::TYPE_SEQUENCE, f) 280 | } 281 | 282 | // fn push_set(&mut self, f: F) 283 | // where F: FnMut(&mut Self) 284 | // { 285 | // self.push_constructed(asn1::TYPE_SET, f) 286 | // } 287 | 288 | fn push_length(&mut self, len: usize) { 289 | if len < 128 { 290 | // short form 291 | self.push_byte(len as u8); 292 | } else { 293 | // long form 294 | let num_leading_nulls = (len.leading_zeros() / 8) as usize; 295 | let length_len = mem::size_of::() - num_leading_nulls; 296 | let leading_byte = length_len as u8 | 0b1000_0000; 297 | self.scribble_bytes(|o| { 298 | assert!(o.len() >= length_len + 1); 299 | let bytes = unsafe { mem::transmute::(len.to_be()) }; 300 | let write_offset = o.len() - length_len - 1; 301 | o[write_offset] = leading_byte; 302 | o[write_offset + 1..].copy_from_slice(&bytes[num_leading_nulls..]); 303 | length_len + 1 304 | }); 305 | } 306 | } 307 | 308 | fn push_integer(&mut self, n: i64) { 309 | let len = self.push_i64(n); 310 | self.push_length(len); 311 | self.push_byte(asn1::TYPE_INTEGER); 312 | } 313 | 314 | fn push_endofmibview(&mut self) { 315 | self.push_chunk(&[snmp::SNMP_ENDOFMIBVIEW, 0]); 316 | } 317 | 318 | fn push_nosuchobject(&mut self) { 319 | self.push_chunk(&[snmp::SNMP_NOSUCHOBJECT, 0]); 320 | } 321 | 322 | fn push_nosuchinstance(&mut self) { 323 | self.push_chunk(&[snmp::SNMP_NOSUCHINSTANCE, 0]); 324 | } 325 | 326 | fn push_counter32(&mut self, n: u32) { 327 | let len = self.push_i64(n as i64); 328 | self.push_length(len); 329 | self.push_byte(snmp::TYPE_COUNTER32); 330 | } 331 | 332 | fn push_unsigned32(&mut self, n: u32) { 333 | let len = self.push_i64(n as i64); 334 | self.push_length(len); 335 | self.push_byte(snmp::TYPE_UNSIGNED32); 336 | } 337 | 338 | fn push_timeticks(&mut self, n: u32) { 339 | let len = self.push_i64(n as i64); 340 | self.push_length(len); 341 | self.push_byte(snmp::TYPE_TIMETICKS); 342 | } 343 | 344 | fn push_opaque(&mut self, bytes: &[u8]) { 345 | self.push_chunk(bytes); 346 | self.push_length(bytes.len()); 347 | self.push_byte(snmp::TYPE_OPAQUE); 348 | } 349 | 350 | fn push_counter64(&mut self, n: u64) { 351 | let len = self.push_i64(n as i64); 352 | self.push_length(len); 353 | self.push_byte(snmp::TYPE_COUNTER64); 354 | } 355 | 356 | fn push_i64(&mut self, mut n: i64) -> usize { 357 | let (null, num_null_bytes) = if !n.is_negative() { 358 | (0x00u8, (n.leading_zeros() / 8) as usize) 359 | } else { 360 | (0xffu8, ((!n).leading_zeros() / 8) as usize) 361 | }; 362 | n = n.to_be(); 363 | let count = unsafe { 364 | let mut wbuf = self.available(); 365 | let mut src_ptr = &n as *const i64 as *const u8; 366 | let mut dst_ptr = wbuf.as_mut_ptr() 367 | .offset((wbuf.len() - mem::size_of::()) as isize); 368 | let mut count = mem::size_of::() - num_null_bytes; 369 | if count == 0 { 370 | count = 1; 371 | } 372 | // preserve sign 373 | if (*(src_ptr.offset((mem::size_of::() - count) as isize)) ^ null) > 127u8 { 374 | count += 1; 375 | } 376 | assert!(wbuf.len() >= count); 377 | let offset = (mem::size_of::() - count) as isize; 378 | src_ptr = src_ptr.offset(offset); 379 | dst_ptr = dst_ptr.offset(offset); 380 | ptr::copy_nonoverlapping(src_ptr, dst_ptr, count); 381 | count 382 | }; 383 | self.len += count; 384 | count 385 | } 386 | 387 | fn push_boolean(&mut self, boolean: bool) { 388 | if boolean == true { 389 | self.push_byte(0x1); 390 | } else { 391 | self.push_byte(0x0); 392 | } 393 | self.push_length(1); 394 | self.push_byte(asn1::TYPE_BOOLEAN); 395 | } 396 | 397 | fn push_ipaddress(&mut self, ip: &[u8; 4]) { 398 | self.push_chunk(ip); 399 | self.push_length(ip.len()); 400 | self.push_byte(snmp::TYPE_IPADDRESS); 401 | } 402 | 403 | fn push_null(&mut self) { 404 | self.push_chunk(&[asn1::TYPE_NULL, 0]); 405 | } 406 | 407 | fn push_object_identifier_raw(&mut self, input: &[u8]) { 408 | self.push_chunk(input); 409 | self.push_length(input.len()); 410 | self.push_byte(asn1::TYPE_OBJECTIDENTIFIER); 411 | } 412 | 413 | fn push_object_identifier(&mut self, input: &[u32]) { 414 | assert!(input.len() >= 2); 415 | let length_before = self.len; 416 | 417 | self.scribble_bytes(|output| { 418 | let mut pos = output.len() - 1; 419 | let (head, tail) = input.split_at(2); 420 | assert!(head[0] < 3 && head[1] < 40); 421 | 422 | // encode the subids in reverse order 423 | for subid in tail.iter().rev() { 424 | let mut subid = *subid; 425 | let mut last_byte = true; 426 | loop { 427 | assert!(pos != 0); 428 | if last_byte { 429 | // continue bit is cleared 430 | output[pos] = (subid & 0b01111111) as u8; 431 | last_byte = false; 432 | } else { 433 | // continue bit is set 434 | output[pos] = (subid | 0b10000000) as u8; 435 | } 436 | pos -= 1; 437 | subid >>= 7; 438 | 439 | if subid == 0 { 440 | break; 441 | } 442 | } 443 | } 444 | 445 | // encode the head last 446 | output[pos] = (head[0] * 40 + head[1]) as u8; 447 | output.len() - pos 448 | }); 449 | let length_after = self.len; 450 | self.push_length(length_after - length_before); 451 | self.push_byte(asn1::TYPE_OBJECTIDENTIFIER); 452 | } 453 | 454 | fn push_octet_string(&mut self, bytes: &[u8]) { 455 | self.push_chunk(bytes); 456 | self.push_length(bytes.len()); 457 | self.push_byte(asn1::TYPE_OCTETSTRING); 458 | } 459 | } 460 | 461 | pub fn build_get(community: &[u8], req_id: i32, name: &[u32], buf: &mut Buf) { 462 | buf.reset(); 463 | buf.push_sequence(|buf| { 464 | buf.push_constructed(snmp::MSG_GET, |buf| { 465 | buf.push_sequence(|buf| { 466 | buf.push_sequence(|buf| { 467 | buf.push_null(); // value 468 | buf.push_object_identifier(name); // name 469 | }) 470 | }); 471 | buf.push_integer(0); // error index 472 | buf.push_integer(0); // error status 473 | buf.push_integer(req_id as i64); 474 | }); 475 | buf.push_octet_string(community); 476 | buf.push_integer(snmp::VERSION_2 as i64); 477 | }); 478 | } 479 | 480 | pub fn build_getnext(community: &[u8], req_id: i32, name: &[u32], buf: &mut Buf) { 481 | buf.reset(); 482 | buf.push_sequence(|buf| { 483 | buf.push_constructed(snmp::MSG_GET_NEXT, |buf| { 484 | buf.push_sequence(|buf| { 485 | buf.push_sequence(|buf| { 486 | buf.push_null(); // value 487 | buf.push_object_identifier(name); // name 488 | }) 489 | }); 490 | buf.push_integer(0); // error index 491 | buf.push_integer(0); // error status 492 | buf.push_integer(req_id as i64); 493 | }); 494 | buf.push_octet_string(community); 495 | buf.push_integer(snmp::VERSION_2 as i64); 496 | }); 497 | } 498 | 499 | pub fn build_getbulk(community: &[u8], req_id: i32, names: &[&[u32]], 500 | non_repeaters: u32, max_repetitions: u32, buf: &mut Buf) { 501 | buf.reset(); 502 | buf.push_sequence(|buf| { 503 | buf.push_constructed(snmp::MSG_GET_BULK, |buf| { 504 | buf.push_sequence(|buf| { 505 | for name in names.iter().rev() { 506 | buf.push_sequence(|buf| { 507 | buf.push_null(); // value 508 | buf.push_object_identifier(name); // name 509 | }); 510 | } 511 | }); 512 | buf.push_integer(max_repetitions as i64); 513 | buf.push_integer(non_repeaters as i64); 514 | buf.push_integer(req_id as i64); 515 | }); 516 | buf.push_octet_string(community); 517 | buf.push_integer(snmp::VERSION_2 as i64); 518 | }); 519 | } 520 | 521 | pub fn build_set(community: &[u8], req_id: i32, values: &[(&[u32], Value)], buf: &mut Buf) { 522 | buf.reset(); 523 | buf.push_sequence(|buf| { 524 | buf.push_constructed(snmp::MSG_SET, |buf| { 525 | buf.push_sequence(|buf| { 526 | for &(ref name, ref val) in values.iter().rev() { 527 | buf.push_sequence(|buf| { 528 | use Value::*; 529 | match *val { 530 | Boolean(b) => buf.push_boolean(b), 531 | Null => buf.push_null(), 532 | Integer(i) => buf.push_integer(i), 533 | OctetString(ostr) => buf.push_octet_string(ostr), 534 | ObjectIdentifier(ref objid) => buf.push_object_identifier_raw(objid.raw()), 535 | IpAddress(ref ip) => buf.push_ipaddress(ip), 536 | Counter32(i) => buf.push_counter32(i), 537 | Unsigned32(i) => buf.push_unsigned32(i), 538 | Timeticks(tt) => buf.push_timeticks(tt), 539 | Opaque(bytes) => buf.push_opaque(bytes), 540 | Counter64(i) => buf.push_counter64(i), 541 | _ => unimplemented!(), 542 | } 543 | buf.push_object_identifier(name); // name 544 | }); 545 | } 546 | }); 547 | buf.push_integer(0); 548 | buf.push_integer(0); 549 | buf.push_integer(req_id as i64); 550 | }); 551 | buf.push_octet_string(community); 552 | buf.push_integer(snmp::VERSION_2 as i64); 553 | }); 554 | } 555 | 556 | pub fn build_response(community: &[u8], req_id: i32, values: &[(&[u32], Value)], buf: &mut Buf) { 557 | buf.reset(); 558 | buf.push_sequence(|buf| { 559 | buf.push_constructed(snmp::MSG_RESPONSE, |buf| { 560 | buf.push_sequence(|buf| { 561 | for &(ref name, ref val) in values.iter().rev() { 562 | buf.push_sequence(|buf| { 563 | use Value::*; 564 | match *val { 565 | Boolean(b) => buf.push_boolean(b), 566 | Null => buf.push_null(), 567 | Integer(i) => buf.push_integer(i), 568 | OctetString(ostr) => buf.push_octet_string(ostr), 569 | ObjectIdentifier(ref objid) => buf.push_object_identifier_raw(objid.raw()), 570 | IpAddress(ref ip) => buf.push_ipaddress(ip), 571 | Counter32(i) => buf.push_counter32(i), 572 | Unsigned32(i) => buf.push_unsigned32(i), 573 | Timeticks(tt) => buf.push_timeticks(tt), 574 | Opaque(bytes) => buf.push_opaque(bytes), 575 | Counter64(i) => buf.push_counter64(i), 576 | EndOfMibView => buf.push_endofmibview(), 577 | NoSuchObject => buf.push_nosuchobject(), 578 | NoSuchInstance => buf.push_nosuchinstance(), 579 | _ => unimplemented!(), 580 | } 581 | buf.push_object_identifier(name); // name 582 | }); 583 | } 584 | }); 585 | buf.push_integer(0); 586 | buf.push_integer(0); 587 | buf.push_integer(req_id as i64); 588 | }); 589 | buf.push_octet_string(community); 590 | buf.push_integer(snmp::VERSION_2 as i64); 591 | }); 592 | } 593 | } 594 | 595 | fn decode_i64(i: &[u8]) -> SnmpResult { 596 | if i.len() > mem::size_of::() { 597 | return Err(SnmpError::AsnIntOverflow); 598 | } 599 | let mut bytes = [0u8; 8]; 600 | bytes[(mem::size_of::() - i.len())..].copy_from_slice(i); 601 | 602 | let mut ret = unsafe { mem::transmute::<[u8; 8], i64>(bytes).to_be()}; 603 | { 604 | //sign extend 605 | let shift_amount = (mem::size_of::() - i.len()) * 8; 606 | ret = (ret << shift_amount) >> shift_amount; 607 | } 608 | Ok(ret) 609 | } 610 | 611 | /// Wrapper around raw bytes representing an ASN.1 OBJECT IDENTIFIER. 612 | #[derive(PartialEq)] 613 | pub struct ObjectIdentifier<'a> { 614 | inner: &'a [u8], 615 | } 616 | 617 | impl<'a> fmt::Debug for ObjectIdentifier<'a> { 618 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 619 | f.debug_list().entries(self.inner).finish() 620 | } 621 | } 622 | 623 | pub type ObjIdBuf = [u32; 128]; 624 | 625 | impl<'a> fmt::Display for ObjectIdentifier<'a> { 626 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 627 | let mut buf: ObjIdBuf = unsafe { mem::uninitialized() }; 628 | let mut first = true; 629 | match self.read_name(&mut buf) { 630 | Ok(name) => { 631 | for subid in name { 632 | if first { 633 | first = false; 634 | f.write_fmt(format_args!("{}", subid))?; 635 | } else { 636 | f.write_fmt(format_args!(".{}", subid))?; 637 | } 638 | } 639 | Ok(()) 640 | } 641 | Err(err) => f.write_fmt(format_args!("Invalid OID: {:?}", err)) 642 | } 643 | } 644 | } 645 | 646 | impl<'a> PartialEq<[u32]> for ObjectIdentifier<'a> { 647 | fn eq(&self, other: &[u32]) -> bool { 648 | let mut buf: ObjIdBuf = unsafe { mem::uninitialized() }; 649 | if let Ok(name) = self.read_name(&mut buf) { 650 | name == other 651 | } else { 652 | false 653 | } 654 | } 655 | } 656 | 657 | impl<'a, 'b> PartialEq<&'b [u32]> for ObjectIdentifier<'a> { 658 | fn eq(&self, other: &&[u32]) -> bool { 659 | self == *other 660 | } 661 | } 662 | 663 | impl<'a> ObjectIdentifier<'a> { 664 | fn from_bytes(bytes: &[u8]) -> ObjectIdentifier { 665 | ObjectIdentifier { 666 | inner: bytes, 667 | } 668 | } 669 | 670 | /// Reads out the OBJECT IDENTIFIER sub-IDs as a slice of u32s. 671 | /// Caller must provide storage for 128 sub-IDs. 672 | pub fn read_name<'b>(&self, out: &'b mut ObjIdBuf) -> SnmpResult<&'b [u32]> { 673 | let input = self.inner; 674 | let output = &mut out[..]; 675 | if input.len() < 2 { 676 | return Err(SnmpError::AsnInvalidLen); 677 | } 678 | let subid1 = (input[0] / 40) as u32; 679 | let subid2 = (input[0] % 40) as u32; 680 | output[0] = subid1; 681 | output[1] = subid2; 682 | let mut pos = 2; 683 | let mut cur_oid: u32 = 0; 684 | let mut is_done = false; 685 | for b in &input[1..] { 686 | if pos == output.len() { 687 | return Err(SnmpError::AsnEof); 688 | } 689 | is_done = b & 0b10000000 == 0; 690 | let val = b & 0b01111111; 691 | cur_oid = cur_oid.checked_shl(7).ok_or(SnmpError::AsnIntOverflow)?; 692 | cur_oid |= val as u32; 693 | if is_done { 694 | output[pos] = cur_oid; 695 | pos += 1; 696 | cur_oid = 0; 697 | } 698 | } 699 | if !is_done { 700 | Err(SnmpError::AsnParseError) 701 | } else { 702 | Ok(&output[..pos]) 703 | } 704 | } 705 | 706 | pub fn raw(&self) -> &'a [u8] { 707 | self.inner 708 | } 709 | } 710 | 711 | /// ASN.1/DER decoder iterator. 712 | /// 713 | /// Supports: 714 | /// 715 | /// - types required by SNMP. 716 | /// 717 | /// Does not support: 718 | /// 719 | /// - extended tag IDs. 720 | /// - indefinite lengths (disallowed by DER). 721 | /// - INTEGER values not representable by i64. 722 | pub struct AsnReader<'a> { 723 | inner: &'a [u8], 724 | } 725 | 726 | impl<'a> Clone for AsnReader<'a> { 727 | fn clone(&self) -> AsnReader<'a> { 728 | AsnReader { 729 | inner: self.inner, 730 | } 731 | } 732 | } 733 | 734 | impl<'a> fmt::Debug for AsnReader<'a> { 735 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 736 | f.debug_list().entries(self.clone()).finish() 737 | } 738 | } 739 | 740 | impl<'a> AsnReader<'a> { 741 | 742 | pub fn from_bytes(bytes: &[u8]) -> AsnReader { 743 | AsnReader {inner: bytes} 744 | } 745 | 746 | pub fn peek_byte(&mut self) -> SnmpResult { 747 | if self.inner.is_empty() { 748 | Err(SnmpError::AsnEof) 749 | } else { 750 | Ok(self.inner[0]) 751 | } 752 | } 753 | 754 | pub fn read_byte(&mut self) -> SnmpResult { 755 | match self.inner.split_first() { 756 | Some((head, tail)) => { 757 | self.inner = tail; 758 | Ok(*head) 759 | } 760 | _ => Err(SnmpError::AsnEof) 761 | } 762 | } 763 | 764 | pub fn read_length(&mut self) -> SnmpResult { 765 | if let Some((head, tail)) = self.inner.split_first() { 766 | let o: usize; 767 | if head < &128 { 768 | // short form 769 | o = *head as usize; 770 | self.inner = tail; 771 | Ok(o) 772 | } else if head == &0xff { 773 | Err(SnmpError::AsnInvalidLen) // reserved for future use 774 | } else { 775 | // long form 776 | let length_len = (*head & 0b01111111) as usize; 777 | if length_len == 0 { 778 | // Indefinite length. Not allowed in DER. 779 | return Err(SnmpError::AsnInvalidLen); 780 | } 781 | 782 | let mut bytes = [0u8; USIZE_LEN]; 783 | bytes[(USIZE_LEN - length_len)..] 784 | .copy_from_slice(&tail[..length_len]); 785 | 786 | o = unsafe { mem::transmute::<[u8; USIZE_LEN], usize>(bytes).to_be()}; 787 | self.inner = &tail[length_len as usize..]; 788 | Ok(o) 789 | } 790 | } else { 791 | Err(SnmpError::AsnEof) 792 | } 793 | } 794 | 795 | pub fn read_i64_type(&mut self, expected_ident: u8) -> SnmpResult { 796 | let ident = self.read_byte()?; 797 | if ident != expected_ident { 798 | return Err(SnmpError::AsnWrongType); 799 | } 800 | let val_len = self.read_length()?; 801 | if val_len > self.inner.len() { 802 | return Err(SnmpError::AsnInvalidLen); 803 | } 804 | let (val, remaining) = self.inner.split_at(val_len); 805 | self.inner = remaining; 806 | decode_i64(val) 807 | } 808 | 809 | pub fn read_raw(&mut self, expected_ident: u8) -> SnmpResult<&'a [u8]> { 810 | let ident = self.read_byte()?; 811 | if ident != expected_ident { 812 | return Err(SnmpError::AsnWrongType); 813 | } 814 | let val_len = self.read_length()?; 815 | if val_len > self.inner.len() { 816 | return Err(SnmpError::AsnInvalidLen); 817 | } 818 | let (val, remaining) = self.inner.split_at(val_len); 819 | self.inner = remaining; 820 | Ok(val) 821 | } 822 | 823 | pub fn read_constructed(&mut self, expected_ident: u8, f: F) -> SnmpResult<()> 824 | where F: Fn(&mut AsnReader) -> SnmpResult<()> 825 | { 826 | let ident = self.read_byte()?; 827 | if ident != expected_ident { 828 | return Err(SnmpError::AsnWrongType); 829 | } 830 | let seq_len = self.read_length()?; 831 | if seq_len > self.inner.len() { 832 | return Err(SnmpError::AsnInvalidLen); 833 | } 834 | let (seq_bytes, remaining) = self.inner.split_at(seq_len); 835 | let mut reader = AsnReader::from_bytes(seq_bytes); 836 | self.inner = remaining; 837 | f(&mut reader) 838 | } 839 | 840 | // 841 | // ASN 842 | // 843 | 844 | pub fn read_asn_boolean(&mut self) -> SnmpResult { 845 | let ident = self.read_byte()?; 846 | if ident != asn1::TYPE_NULL { 847 | return Err(SnmpError::AsnWrongType); 848 | } 849 | let val_len = self.read_length()?; 850 | if val_len != 1 { 851 | return Err(SnmpError::AsnInvalidLen); 852 | } 853 | match self.read_byte()? { 854 | 0 => Ok(false), 855 | 1 => Ok(true), 856 | _ => Err(SnmpError::AsnParseError), // DER mandates 1/0 for booleans 857 | } 858 | } 859 | 860 | pub fn read_asn_integer(&mut self) -> SnmpResult { 861 | self.read_i64_type(asn1::TYPE_INTEGER) 862 | } 863 | 864 | pub fn read_asn_octetstring(&mut self) -> SnmpResult<&'a [u8]> { 865 | self.read_raw(asn1::TYPE_OCTETSTRING) 866 | } 867 | 868 | pub fn read_asn_null(&mut self) -> SnmpResult<()> { 869 | let ident = self.read_byte()?; 870 | if ident != asn1::TYPE_NULL { 871 | return Err(SnmpError::AsnWrongType); 872 | } 873 | let null_len = self.read_length()?; 874 | if null_len != 0 { 875 | Err(SnmpError::AsnInvalidLen) 876 | } else { 877 | Ok(()) 878 | } 879 | } 880 | 881 | pub fn read_asn_objectidentifier(&mut self) -> SnmpResult> { 882 | let ident = self.read_byte()?; 883 | if ident != asn1::TYPE_OBJECTIDENTIFIER { 884 | return Err(SnmpError::AsnWrongType); 885 | } 886 | let val_len = self.read_length()?; 887 | if val_len > self.inner.len() { 888 | return Err(SnmpError::AsnInvalidLen); 889 | } 890 | let (input, remaining) = self.inner.split_at(val_len); 891 | self.inner = remaining; 892 | 893 | Ok(ObjectIdentifier::from_bytes(input)) 894 | } 895 | 896 | pub fn read_asn_sequence(&mut self, f: F) -> SnmpResult<()> 897 | where F: Fn(&mut AsnReader) -> SnmpResult<()> 898 | { 899 | self.read_constructed(asn1::TYPE_SEQUENCE, f) 900 | } 901 | 902 | // fn read_asn_set(&mut self, f: F) -> SnmpResult<()> 903 | // where F: Fn(&mut AsnReader) -> SnmpResult<()> 904 | // { 905 | // self.read_constructed(asn1::TYPE_SET, f) 906 | // } 907 | 908 | // 909 | // SNMP 910 | // 911 | 912 | pub fn read_snmp_counter32(&mut self) -> SnmpResult { 913 | self.read_i64_type(snmp::TYPE_COUNTER32).map(|v| v as u32) 914 | } 915 | 916 | pub fn read_snmp_unsigned32(&mut self) -> SnmpResult { 917 | self.read_i64_type(snmp::TYPE_UNSIGNED32).map(|v| v as u32) 918 | } 919 | 920 | pub fn read_snmp_timeticks(&mut self) -> SnmpResult { 921 | self.read_i64_type(snmp::TYPE_TIMETICKS).map(|v| v as u32) 922 | } 923 | 924 | pub fn read_snmp_counter64(&mut self) -> SnmpResult { 925 | self.read_i64_type(snmp::TYPE_COUNTER64).map(|v| v as u64) 926 | } 927 | 928 | pub fn read_snmp_opaque(&mut self) -> SnmpResult<&'a [u8]> { 929 | self.read_raw(snmp::TYPE_OPAQUE) 930 | } 931 | 932 | pub fn read_snmp_ipaddress(&mut self) -> SnmpResult<[u8; 4]> { 933 | //let mut ip = [0u8; 4]; 934 | let val = self.read_raw(snmp::TYPE_IPADDRESS)?; 935 | if val.len() != 4 { 936 | return Err(SnmpError::AsnInvalidLen); 937 | } 938 | //&mut ip[..].copy_from_slice(val); 939 | //Ok(ip) 940 | unsafe { Ok(ptr::read(val.as_ptr() as *const _)) } 941 | } 942 | 943 | // fn read_snmp_get(&mut self, f: F) -> SnmpResult<()> 944 | // where F: Fn(&mut AsnReader) -> SnmpResult<()> 945 | // { 946 | // self.read_constructed(snmp::MSG_GET, f) 947 | // } 948 | 949 | // fn read_snmp_getnext(&mut self, f: F) -> SnmpResult<()> 950 | // where F: Fn(&mut AsnReader) -> SnmpResult<()> 951 | // { 952 | // self.read_constructed(snmp::MSG_GET_NEXT, f) 953 | // } 954 | 955 | // fn read_snmp_getbulk(&mut self, f: F) -> SnmpResult<()> 956 | // where F: Fn(&mut AsnReader) -> SnmpResult<()> 957 | // { 958 | // self.read_constructed(snmp::MSG_GET_BULK, f) 959 | // } 960 | 961 | // fn read_snmp_response(&mut self, f: F) -> SnmpResult<()> 962 | // where F: Fn(&mut AsnReader) -> SnmpResult<()> 963 | // { 964 | // self.read_constructed(snmp::MSG_RESPONSE, f) 965 | // } 966 | 967 | // fn read_snmp_inform(&mut self, f: F) -> SnmpResult<()> 968 | // where F: Fn(&mut AsnReader) -> SnmpResult<()> 969 | // { 970 | // self.read_constructed(snmp::MSG_INFORM, f) 971 | // } 972 | 973 | // fn read_snmp_report(&mut self, f: F) -> SnmpResult<()> 974 | // where F: Fn(&mut AsnReader) -> SnmpResult<()> 975 | // { 976 | // self.read_constructed(snmp::MSG_REPORT, f) 977 | // } 978 | 979 | // fn read_snmp_set(&mut self, f: F) -> SnmpResult<()> 980 | // where F: Fn(&mut AsnReader) -> SnmpResult<()> 981 | // { 982 | // self.read_constructed(snmp::MSG_SET, f) 983 | // } 984 | 985 | // fn read_snmp_trap(&mut self, f: F) -> SnmpResult<()> 986 | // where F: Fn(&mut AsnReader) -> SnmpResult<()> 987 | // { 988 | // self.read_constructed(snmp::MSG_TRAP, f) 989 | // } 990 | 991 | } 992 | 993 | 994 | pub enum Value<'a> { 995 | Boolean(bool), 996 | Null, 997 | Integer(i64), 998 | OctetString(&'a [u8]), 999 | ObjectIdentifier(ObjectIdentifier<'a>), 1000 | Sequence(AsnReader<'a>), 1001 | Set(AsnReader<'a>), 1002 | Constructed(u8, AsnReader<'a>), 1003 | 1004 | IpAddress([u8;4]), 1005 | Counter32(u32), 1006 | Unsigned32(u32), 1007 | Timeticks(u32), 1008 | Opaque(&'a [u8]), 1009 | Counter64(u64), 1010 | 1011 | EndOfMibView, 1012 | NoSuchObject, 1013 | NoSuchInstance, 1014 | 1015 | SnmpGetRequest(AsnReader<'a>), 1016 | SnmpGetNextRequest(AsnReader<'a>), 1017 | SnmpGetBulkRequest(AsnReader<'a>), 1018 | SnmpResponse(AsnReader<'a>), 1019 | SnmpSetRequest(AsnReader<'a>), 1020 | SnmpInformRequest(AsnReader<'a>), 1021 | SnmpTrap(AsnReader<'a>), 1022 | SnmpReport(AsnReader<'a>), 1023 | } 1024 | 1025 | impl<'a> fmt::Debug for Value<'a> { 1026 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1027 | use Value::*; 1028 | match *self { 1029 | Boolean(v) => write!(f, "BOOLEAN: {}", v), 1030 | Integer(n) => write!(f, "INTEGER: {}", n), 1031 | OctetString(slice) => write!(f, "OCTET STRING: {}", String::from_utf8_lossy(slice)), 1032 | ObjectIdentifier(ref obj_id) => write!(f, "OBJECT IDENTIFIER: {}", obj_id), 1033 | Null => write!(f, "NULL"), 1034 | Sequence(ref val) => write!(f, "SEQUENCE: {:#?}", val), 1035 | Set(ref val) => write!(f, "SET: {:?}", val), 1036 | Constructed(ident, ref val) => write!(f, "CONSTRUCTED-{}: {:#?}", ident, val), 1037 | 1038 | IpAddress(val) => write!(f, "IP ADDRESS: {}.{}.{}.{}", val[0], val[1], val[2], val[3]), 1039 | Counter32(val) => write!(f, "COUNTER32: {}", val), 1040 | Unsigned32(val) => write!(f, "UNSIGNED32: {}", val), 1041 | Timeticks(val) => write!(f, "TIMETICKS: {}", val), 1042 | Opaque(val) => write!(f, "OPAQUE: {:?}", val), 1043 | Counter64(val) => write!(f, "COUNTER64: {}", val), 1044 | 1045 | EndOfMibView => write!(f, "END OF MIB VIEW"), 1046 | NoSuchObject => write!(f, "NO SUCH OBJECT"), 1047 | NoSuchInstance => write!(f, "NO SUCH INSTANCE"), 1048 | 1049 | SnmpGetRequest(ref val) => write!(f, "SNMP GET REQUEST: {:#?}", val), 1050 | SnmpGetNextRequest(ref val) => write!(f, "SNMP GET NEXT REQUEST: {:#?}", val), 1051 | SnmpGetBulkRequest(ref val) => write!(f, "SNMP GET BULK REQUEST: {:#?}", val), 1052 | SnmpResponse(ref val) => write!(f, "SNMP RESPONSE: {:#?}", val), 1053 | SnmpSetRequest(ref val) => write!(f, "SNMP SET REQUEST: {:#?}", val), 1054 | SnmpInformRequest(ref val) => write!(f, "SNMP INFORM REQUEST: {:#?}", val), 1055 | SnmpTrap(ref val) => write!(f, "SNMP TRAP: {:#?}", val), 1056 | SnmpReport(ref val) => write!(f, "SNMP REPORT: {:#?}", val), 1057 | } 1058 | } 1059 | } 1060 | 1061 | impl<'a> Iterator for AsnReader<'a> { 1062 | type Item = Value<'a>; 1063 | 1064 | fn next(&mut self) -> Option> { 1065 | use Value::*; 1066 | if let Ok(ident) = self.peek_byte() { 1067 | let ret: SnmpResult = match ident { 1068 | asn1::TYPE_BOOLEAN => self.read_asn_boolean().map(Boolean), 1069 | asn1::TYPE_NULL => self.read_asn_null().map(|_| Null), 1070 | asn1::TYPE_INTEGER => self.read_asn_integer().map(Integer), 1071 | asn1::TYPE_OCTETSTRING => self.read_asn_octetstring().map(OctetString), 1072 | asn1::TYPE_OBJECTIDENTIFIER => self.read_asn_objectidentifier().map(ObjectIdentifier), 1073 | asn1::TYPE_SEQUENCE => self.read_raw(ident).map(|v| Sequence(AsnReader::from_bytes(v))), 1074 | asn1::TYPE_SET => self.read_raw(ident).map(|v| Set(AsnReader::from_bytes(v))), 1075 | snmp::TYPE_IPADDRESS => self.read_snmp_ipaddress().map(IpAddress), 1076 | snmp::TYPE_COUNTER32 => self.read_snmp_counter32().map(Counter32), 1077 | snmp::TYPE_UNSIGNED32 => self.read_snmp_unsigned32().map(Unsigned32), 1078 | snmp::TYPE_TIMETICKS => self.read_snmp_timeticks().map(Timeticks), 1079 | snmp::TYPE_OPAQUE => self.read_snmp_opaque().map(Opaque), 1080 | snmp::TYPE_COUNTER64 => self.read_snmp_counter64().map(Counter64), 1081 | snmp::MSG_GET => self.read_raw(ident).map(|v| SnmpGetRequest(AsnReader::from_bytes(v))), 1082 | snmp::MSG_GET_NEXT => self.read_raw(ident).map(|v| SnmpGetNextRequest(AsnReader::from_bytes(v))), 1083 | snmp::MSG_GET_BULK => self.read_raw(ident).map(|v| SnmpGetBulkRequest(AsnReader::from_bytes(v))), 1084 | snmp::MSG_RESPONSE => self.read_raw(ident).map(|v| SnmpResponse(AsnReader::from_bytes(v))), 1085 | snmp::MSG_SET => self.read_raw(ident).map(|v| SnmpSetRequest(AsnReader::from_bytes(v))), 1086 | snmp::MSG_INFORM => self.read_raw(ident).map(|v| SnmpInformRequest(AsnReader::from_bytes(v))), 1087 | snmp::MSG_TRAP => self.read_raw(ident).map(|v| SnmpTrap(AsnReader::from_bytes(v))), 1088 | snmp::MSG_REPORT => self.read_raw(ident).map(|v| SnmpReport(AsnReader::from_bytes(v))), 1089 | ident if ident & asn1::CONSTRUCTED == asn1::CONSTRUCTED => 1090 | self.read_raw(ident).map(|v| Constructed(ident, AsnReader::from_bytes(v))), 1091 | _ => Err(SnmpError::AsnUnsupportedType), 1092 | }; 1093 | ret.ok() 1094 | } else { 1095 | None 1096 | } 1097 | } 1098 | } 1099 | 1100 | /// Synchronous SNMPv2 client. 1101 | pub struct SyncSession { 1102 | socket: UdpSocket, 1103 | community: Vec, 1104 | req_id: Wrapping, 1105 | send_pdu: pdu::Buf, 1106 | recv_buf: [u8; BUFFER_SIZE], 1107 | } 1108 | 1109 | impl SyncSession { 1110 | pub fn new(destination: SA, community: &[u8], timeout: Option, starting_req_id: i32) -> io::Result 1111 | where SA: ToSocketAddrs 1112 | { 1113 | let socket = match destination.to_socket_addrs()?.next() { 1114 | Some(SocketAddr::V4(_)) => UdpSocket::bind((Ipv4Addr::new(0,0,0,0), 0))?, 1115 | Some(SocketAddr::V6(_)) => UdpSocket::bind((Ipv6Addr::new(0,0,0,0,0,0,0,0), 0))?, 1116 | None => panic!("empty list of socket addrs"), 1117 | }; 1118 | socket.set_read_timeout(timeout)?; 1119 | socket.connect(destination)?; 1120 | Ok(SyncSession { 1121 | socket: socket, 1122 | community: community.to_vec(), 1123 | req_id: Wrapping(starting_req_id), 1124 | send_pdu: pdu::Buf::default(), 1125 | recv_buf: [0; 4096], 1126 | }) 1127 | } 1128 | 1129 | fn send_and_recv(socket: &UdpSocket, pdu: &pdu::Buf, out: &mut [u8]) -> SnmpResult { 1130 | if let Ok(_pdu_len) = socket.send(&pdu[..]) { 1131 | match socket.recv(out) { 1132 | Ok(len) => Ok(len), 1133 | Err(_) => Err(SnmpError::ReceiveError) 1134 | } 1135 | } else { 1136 | Err(SnmpError::SendError) 1137 | } 1138 | } 1139 | 1140 | pub fn get(&mut self, name: &[u32]) -> SnmpResult { 1141 | let req_id = self.req_id.0; 1142 | pdu::build_get(self.community.as_slice(), req_id, name, &mut self.send_pdu); 1143 | let recv_len = Self::send_and_recv(&self.socket, &self.send_pdu, &mut self.recv_buf[..])?; 1144 | self.req_id += Wrapping(1); 1145 | let pdu_bytes = &self.recv_buf[..recv_len]; 1146 | let resp = SnmpPdu::from_bytes(pdu_bytes)?; 1147 | if resp.message_type != SnmpMessageType::Response { 1148 | return Err(SnmpError::AsnWrongType); 1149 | } 1150 | if resp.req_id != req_id { 1151 | return Err(SnmpError::RequestIdMismatch); 1152 | } 1153 | if resp.community != &self.community[..] { 1154 | return Err(SnmpError::CommunityMismatch); 1155 | } 1156 | Ok(resp) 1157 | } 1158 | 1159 | pub fn getnext(&mut self, name: &[u32]) -> SnmpResult { 1160 | let req_id = self.req_id.0; 1161 | pdu::build_getnext(self.community.as_slice(), req_id, name, &mut self.send_pdu); 1162 | let recv_len = Self::send_and_recv(&self.socket, &self.send_pdu, &mut self.recv_buf[..])?; 1163 | self.req_id += Wrapping(1); 1164 | let pdu_bytes = &self.recv_buf[..recv_len]; 1165 | let resp = SnmpPdu::from_bytes(pdu_bytes)?; 1166 | if resp.message_type != SnmpMessageType::Response { 1167 | return Err(SnmpError::AsnWrongType); 1168 | } 1169 | if resp.req_id != req_id { 1170 | return Err(SnmpError::RequestIdMismatch); 1171 | } 1172 | if resp.community != &self.community[..] { 1173 | return Err(SnmpError::CommunityMismatch); 1174 | } 1175 | Ok(resp) 1176 | } 1177 | 1178 | pub fn getbulk(&mut self, names: &[&[u32]], non_repeaters: u32, max_repetitions: u32) -> SnmpResult { 1179 | let req_id = self.req_id.0; 1180 | pdu::build_getbulk(self.community.as_slice(), req_id, names, non_repeaters, max_repetitions, &mut self.send_pdu); 1181 | let recv_len = Self::send_and_recv(&self.socket, &self.send_pdu, &mut self.recv_buf[..])?; 1182 | self.req_id += Wrapping(1); 1183 | let pdu_bytes = &self.recv_buf[..recv_len]; 1184 | let resp = SnmpPdu::from_bytes(pdu_bytes)?; 1185 | if resp.message_type != SnmpMessageType::Response { 1186 | return Err(SnmpError::AsnWrongType); 1187 | } 1188 | if resp.req_id != req_id { 1189 | return Err(SnmpError::RequestIdMismatch); 1190 | } 1191 | if resp.community != &self.community[..] { 1192 | return Err(SnmpError::CommunityMismatch); 1193 | } 1194 | Ok(resp) 1195 | } 1196 | 1197 | /// # Panics if any of the values are not one of these supported types: 1198 | /// - `Boolean` 1199 | /// - `Null` 1200 | /// - `Integer` 1201 | /// - `OctetString` 1202 | /// - `ObjectIdentifier` 1203 | /// - `IpAddress` 1204 | /// - `Counter32` 1205 | /// - `Unsigned32` 1206 | /// - `Timeticks` 1207 | /// - `Opaque` 1208 | /// - `Counter64` 1209 | pub fn set(&mut self, values: &[(&[u32], Value)]) -> SnmpResult { 1210 | let req_id = self.req_id.0; 1211 | pdu::build_set(self.community.as_slice(), req_id, values, &mut self.send_pdu); 1212 | let recv_len = Self::send_and_recv(&self.socket, &self.send_pdu, &mut self.recv_buf[..])?; 1213 | self.req_id += Wrapping(1); 1214 | let pdu_bytes = &self.recv_buf[..recv_len]; 1215 | let resp = SnmpPdu::from_bytes(pdu_bytes)?; 1216 | if resp.message_type != SnmpMessageType::Response { 1217 | return Err(SnmpError::AsnWrongType); 1218 | } 1219 | if resp.req_id != req_id { 1220 | return Err(SnmpError::RequestIdMismatch); 1221 | } 1222 | if resp.community != &self.community[..] { 1223 | return Err(SnmpError::CommunityMismatch); 1224 | } 1225 | Ok(resp) 1226 | } 1227 | } 1228 | 1229 | #[derive(Debug)] 1230 | pub struct SnmpPdu<'a> { 1231 | version: i64, 1232 | community: &'a [u8], 1233 | pub message_type: SnmpMessageType, 1234 | pub req_id: i32, 1235 | pub error_status: u32, 1236 | pub error_index: u32, 1237 | pub varbinds: Varbinds<'a>, 1238 | } 1239 | 1240 | impl<'a> SnmpPdu<'a> { 1241 | pub fn from_bytes(bytes: &'a [u8]) -> SnmpResult> { 1242 | let seq = AsnReader::from_bytes(bytes).read_raw(asn1::TYPE_SEQUENCE)?; 1243 | let mut rdr = AsnReader::from_bytes(seq); 1244 | let version = rdr.read_asn_integer()?; 1245 | if version != snmp::VERSION_2 { 1246 | return Err(SnmpError::UnsupportedVersion); 1247 | } 1248 | let community = rdr.read_asn_octetstring()?; 1249 | let ident = rdr.peek_byte()?; 1250 | let message_type = SnmpMessageType::from_ident(ident)?; 1251 | 1252 | let mut response_pdu = AsnReader::from_bytes(rdr.read_raw(ident)?); 1253 | 1254 | let req_id = response_pdu.read_asn_integer()?; 1255 | if req_id < i32::min_value() as i64 || req_id > i32::max_value() as i64 { 1256 | return Err(SnmpError::ValueOutOfRange); 1257 | } 1258 | 1259 | let error_status = response_pdu.read_asn_integer()?; 1260 | if error_status < 0 || error_status > i32::max_value() as i64 { 1261 | return Err(SnmpError::ValueOutOfRange); 1262 | } 1263 | 1264 | let error_index = response_pdu.read_asn_integer()?; 1265 | if error_index < 0 || error_index > i32::max_value() as i64 { 1266 | return Err(SnmpError::ValueOutOfRange); 1267 | } 1268 | 1269 | let varbind_bytes = response_pdu.read_raw(asn1::TYPE_SEQUENCE)?; 1270 | let varbinds = Varbinds::from_bytes(varbind_bytes); 1271 | 1272 | Ok( 1273 | SnmpPdu { 1274 | version: version, 1275 | community: community, 1276 | message_type: message_type, 1277 | req_id: req_id as i32, 1278 | error_status: error_status as u32, 1279 | error_index: error_index as u32, 1280 | varbinds: varbinds, 1281 | } 1282 | ) 1283 | } 1284 | } 1285 | 1286 | #[derive(Debug, PartialEq)] 1287 | pub enum SnmpMessageType { 1288 | GetRequest, 1289 | GetNextRequest, 1290 | GetBulkRequest, 1291 | Response, 1292 | SetRequest, 1293 | InformRequest, 1294 | Trap, 1295 | Report, 1296 | } 1297 | 1298 | impl SnmpMessageType { 1299 | pub fn from_ident(ident: u8) -> SnmpResult { 1300 | use SnmpMessageType::*; 1301 | Ok( 1302 | match ident { 1303 | snmp::MSG_GET => GetRequest, 1304 | snmp::MSG_GET_NEXT => GetNextRequest, 1305 | snmp::MSG_GET_BULK => GetBulkRequest, 1306 | snmp::MSG_RESPONSE => Response, 1307 | snmp::MSG_SET => SetRequest, 1308 | snmp::MSG_INFORM => InformRequest, 1309 | snmp::MSG_TRAP => Trap, 1310 | snmp::MSG_REPORT => Report, 1311 | _ => return Err(SnmpError::AsnWrongType), 1312 | } 1313 | ) 1314 | } 1315 | } 1316 | 1317 | #[derive(Clone)] 1318 | pub struct Varbinds<'a> { 1319 | inner: AsnReader<'a>, 1320 | } 1321 | 1322 | impl<'a> fmt::Debug for Varbinds<'a> { 1323 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1324 | // f.debug_list().entries(self.clone()).finish() 1325 | let mut ds = f.debug_struct("Varbinds"); 1326 | for (name, val) in self.clone() { 1327 | ds.field(&format!("{}", name), &format!("{:?}", val)); 1328 | } 1329 | ds.finish() 1330 | } 1331 | } 1332 | 1333 | impl<'a> Varbinds<'a> { 1334 | fn from_bytes(bytes: &'a [u8]) -> Varbinds<'a> { 1335 | Varbinds { 1336 | inner: AsnReader::from_bytes(bytes) 1337 | } 1338 | } 1339 | } 1340 | 1341 | impl<'a> Iterator for Varbinds<'a> { 1342 | type Item = (ObjectIdentifier<'a>, Value<'a>); 1343 | fn next(&mut self) -> Option { 1344 | if let Ok(seq) = self.inner.read_raw(asn1::TYPE_SEQUENCE) { 1345 | let mut pair = AsnReader::from_bytes(seq); 1346 | if let (Ok(name), Some(value)) = (pair.read_asn_objectidentifier(), pair.next()) { 1347 | return Some((name, value)); 1348 | } 1349 | } 1350 | None 1351 | } 1352 | } 1353 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use super::{AsnReader, SnmpError}; 2 | use super::{pdu, snmp}; 3 | 4 | #[test] 5 | fn build_getnext_pdu() { 6 | let mut pdu = pdu::Buf::default(); 7 | pdu::build_getnext(b"tyS0n43d", 8 | 1251699618, 9 | &[1, 3, 6, 1, 2, 1, 1, 1, 0], 10 | &mut pdu); 11 | 12 | let expected = &[0x30, 0x2b, 0x02, 0x01, 0x01, 0x04, 0x08, 0x74, 0x79, 0x53, 0x30, 0x6e, 13 | 0x34, 0x33, 0x64, 0xa1, 0x1c, 0x02, 0x04, 0x4a, 0x9b, 0x6b, 0xa2, 0x02, 14 | 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x08, 0x2b, 15 | 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, 0x05, 0x00]; 16 | 17 | println!("{:?}", pdu); 18 | println!("{:?}", &expected[..]); 19 | 20 | assert_eq!(&pdu[..], &expected[..]); 21 | } 22 | 23 | #[test] 24 | fn asn_read_byte() { 25 | let bytes = [1,2,3,4]; 26 | let mut reader = AsnReader::from_bytes(&bytes[..]); 27 | let a = reader.read_byte().unwrap(); 28 | let b = reader.read_byte().unwrap(); 29 | let c = reader.read_byte().unwrap(); 30 | let d = reader.read_byte().unwrap(); 31 | assert_eq!(&[a,b,c,d], &bytes[..]); 32 | assert_eq!(reader.read_byte(), Err(SnmpError::AsnEof)); 33 | } 34 | 35 | 36 | #[test] 37 | fn asn_parse_getnext_pdu() { 38 | let pdu = &[0x30, 0x2b, 0x02, 0x01, 0x01, 0x04, 0x08, 0x74, 0x79, 0x53, 0x30, 0x6e, 39 | 0x34, 0x33, 0x64, 0xa1, 0x1c, 0x02, 0x04, 0x4a, 0x9b, 0x6b, 0xa2, 0x02, 40 | 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x08, 0x2b, 41 | 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, 0x05, 0x00]; 42 | let mut reader = AsnReader::from_bytes(&pdu[..]); 43 | reader.read_asn_sequence(|rdr| { 44 | let version = rdr.read_asn_integer()?; 45 | assert_eq!(version, snmp::VERSION_2 as i64); 46 | let community = rdr.read_asn_octetstring()?; 47 | assert_eq!(community, b"tyS0n43d"); 48 | println!("version: {}", version); 49 | let msg_ident = rdr.peek_byte()?; 50 | println!("msg_ident: {}", msg_ident); 51 | assert_eq!(msg_ident, snmp::MSG_GET_NEXT); 52 | rdr.read_constructed(msg_ident, |rdr| { 53 | let req_id = rdr.read_asn_integer()?; 54 | let error_status = rdr.read_asn_integer()?; 55 | let error_index = rdr.read_asn_integer()?; 56 | println!("req_id: {}, error_status: {}, error_index: {}", 57 | req_id, error_status, error_index); 58 | assert_eq!(req_id, 1251699618); 59 | assert_eq!(error_status, 0); 60 | assert_eq!(error_index, 0); 61 | rdr.read_asn_sequence(|rdr| { 62 | rdr.read_asn_sequence(|rdr| { 63 | let name = rdr.read_asn_objectidentifier()?; 64 | let expected = [1, 3, 6, 1, 2, 1, 1, 1, 0]; 65 | println!("name: {}", name); 66 | assert_eq!(name, &expected[..]); 67 | rdr.read_asn_null() 68 | }) 69 | }) 70 | }) 71 | }).unwrap(); 72 | } 73 | --------------------------------------------------------------------------------