1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use rasn::types::{Integer, ObjectIdentifier, OctetString};
use rasn_smi::v2::{ApplicationSyntax, SimpleSyntax, TimeTicks};
use rasn_snmp::v2::{ObjectSyntax, Pdu, Trap, VarBind, VarBindList, VarBindValue};
//use rasn_snmp::v3::{
// HeaderData, Message, Pdus, ScopedPdu, ScopedPduData, Trap, USMSecurityParameters, VarBind,
//};
use rasn_snmp::v2c::Message;
use std::net::UdpSocket;
//use std::str::FromStr;
use log::{info, warn};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread::{self};
use std::time::Instant;
const ARC_TRAP_OID: [u32; 11] = [1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0];
const ARC_SYS_UP_TIME: [u32; 9] = [1, 3, 6, 1, 2, 1, 1, 3, 0];
#[derive(Debug)]
/// Data structure to be passed down the channel to trigger a notification.
///
pub struct Notification {
/// OID that identifies the notification type
pub name: ObjectIdentifier,
/// Possibly empty list of Varbinds that conveys details associated with the notification.
pub vb: VarBindList,
}
/// Private data structure for notification thread.
pub struct Notifier {
socket: UdpSocket,
community: Vec<u8>,
_engine_id: OctetString,
start_time: Instant,
request_id: i32,
_message_id: i32,
target_addr: String,
receiver: Receiver<Notification>,
}
impl Notifier {
/// Constructor for notification struct. Arguments set some private fields.
fn new(
target: &str,
engine_id: OctetString,
start_time: Instant,
community: Vec<u8>,
rx: Receiver<Notification>,
) -> Self {
let socket: UdpSocket = UdpSocket::bind("0.0.0.0:0").expect("couldn't bind to address");
Notifier {
socket,
_engine_id: engine_id,
start_time,
community,
request_id: rand::random::<i32>(),
_message_id: rand::random::<i32>(),
target_addr: target.to_string(),
receiver: rx,
}
}
/// Start the notification thread.
///
/// target is the address and port of the notification sink. Convenetionally, this will be on port 162.
/// community is the community string of the trap receiver; "public" is a common value.
/// engine_id is not used in v2, but would be in v3.
/// start_time is when the agent started, used for generating the timestamps on notifications.
pub fn start(
target: &str,
community: &str,
engine_id: OctetString,
start_time: Instant,
) -> Sender<Notification> {
let (tx, rx): (Sender<Notification>, Receiver<Notification>) = channel();
let zero_dot_zero = ObjectIdentifier::new(&[0, 0]).unwrap(); //Checked, valid arc
let mut notif = Notifier::new(
target,
engine_id,
start_time,
community.as_bytes().to_vec(),
rx,
);
let _child = thread::spawn(move || {
// The thread takes ownership over `rx`
// Each thread queues a message in the channel
let socket = notif.socket.try_clone().unwrap(); // Checked, startup failure only
let target_addr = notif.target_addr.clone();
loop {
let val = notif.receiver.recv();
if let Ok(num) = val {
info!("Notifier got {num:?}");
if num.name == zero_dot_zero {
break;
}
// Ignore send or encode errors
if let Ok(msg) = rasn::ber::encode(¬if.msg_v2_trap(num)) {
let _ = socket.send_to(&msg, &target_addr);
} else {
warn!(
"Encode error in notification sending, discarding message without send"
);
}
} // Just ignore receive errors
}
// Sending is a non-blocking operation, the thread will continue
// immediately after sending its message
warn!("thread finished");
});
tx
}
/// Send an SNMP V2 Trap.
///
/// This is fire and forget - there is no check that the packet has arrived at the far end.
///
pub fn msg_v2_trap(&mut self, num: Notification) -> Message<Trap> {
let up = self.start_time.elapsed().as_millis() / 10;
let run_time = up.try_into().unwrap_or(u32::MAX); // Checked, alternative value
self.request_id += 1;
let mut vb: VarBindList = vec![
VarBind {
name: ObjectIdentifier::new(&ARC_SYS_UP_TIME).unwrap(), // Checked, valid arc
value: VarBindValue::Value(ObjectSyntax::ApplicationWide(
ApplicationSyntax::Ticks(TimeTicks { 0: run_time }),
)),
},
VarBind {
name: ObjectIdentifier::new(&ARC_TRAP_OID).unwrap(), // Checked, valid arc
value: VarBindValue::Value(ObjectSyntax::Simple(SimpleSyntax::ObjectId(num.name))),
},
];
if !num.vb.is_empty() {
vb.extend(num.vb);
}
let pdu = Pdu {
request_id: self.request_id,
error_index: 0,
error_status: 0,
variable_bindings: vb,
};
let trap: Trap = Trap(pdu);
Message::<Trap> {
version: Integer::from(1),
community: OctetString::from_slice(&self.community),
data: trap,
}
}
}