494737e2dfafa2ee372bfff18774921dea1ad256
[stakingWatchdogWatchdog.git] / src / main.rs
1 use std::{
2 fmt,
3 net::UdpSocket,
4 thread,
5 time::{self, Duration},
6 };
7
8 use anyhow::Result;
9 use lettre::{
10 message::header::ContentType, transport::smtp::authentication::Credentials, Message,
11 SmtpTransport, Transport,
12 };
13 use rand::{rngs::ThreadRng, Rng};
14
15 use crate::config::Config;
16
17 mod config;
18
19 const FILE_CONF: &str = "config.ron";
20 const PING_PERIOD: Duration = Duration::from_secs(5); // 5 s.
21 const EMAIL_RESEND_PERIOD: Duration = Duration::from_secs(2 * 60 * 60); // 2 h.
22 const STATE_PRINT_PERIOD: Duration = Duration::from_secs(15 * 60); // 15 min.
23 const SOCKET_TIMEOUT: Duration = Duration::from_secs(7);
24
25 fn main() -> Result<()> {
26 println!("Staking Watchdog Watchdog");
27
28 let config = Config::read(FILE_CONF)?;
29
30 println!(
31 "Configuration: {:?}",
32 Config {
33 smtp_password: "*****".to_string(),
34 ..config.clone()
35 }
36 );
37
38 let mut time_last_email_send = time::Instant::now() - EMAIL_RESEND_PERIOD;
39 let mut time_last_state_printed = time::Instant::now() - STATE_PRINT_PERIOD;
40 let mut error_state = false;
41
42 let mut rng = rand::thread_rng();
43
44 let mut number_of_pings = 0;
45 let mut total_ping_duration = Duration::default();
46
47 let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
48 socket.connect("192.168.2.102:8739").unwrap();
49 socket.set_nonblocking(false).unwrap();
50 socket.set_read_timeout(Some(SOCKET_TIMEOUT)).unwrap();
51 socket.set_write_timeout(Some(SOCKET_TIMEOUT)).unwrap();
52
53 loop {
54 let time_beginning_loop = time::Instant::now();
55
56 match ping(&socket, &mut rng) {
57 Ok(t) => {
58 total_ping_duration += t;
59 number_of_pings += 1;
60
61 if error_state {
62 error_state = false;
63 println!("End of erroneous state");
64 }
65
66 if time::Instant::now() - time_last_state_printed >= STATE_PRINT_PERIOD {
67 println!(
68 "No error detected. Mean of ping time: {} μs",
69 total_ping_duration.as_micros() / number_of_pings
70 );
71 total_ping_duration = Duration::default();
72 number_of_pings = 0;
73 time_last_state_printed = time::Instant::now();
74 }
75 }
76 Err(error) => {
77 error_state = true;
78 println!("Error: {:?}", error);
79 if time::Instant::now() - time_last_email_send >= EMAIL_RESEND_PERIOD {
80 // Send e-mail.
81 println!("Sending email...");
82 match send_email(
83 "Watchdog Watchdog: Check alive error",
84 &format!("Error: {}", error),
85 &config.smtp_login,
86 &config.smtp_password,
87 ) {
88 Err(email_error) => println!("Error sending email: {:?}", email_error),
89 _ => {
90 println!("Email successfully sent");
91 time_last_email_send = time::Instant::now();
92 }
93 }
94 }
95 }
96 }
97
98 let elapsed = time::Instant::now() - time_beginning_loop;
99
100 if elapsed < PING_PERIOD {
101 let to_wait = PING_PERIOD - elapsed;
102 thread::sleep(to_wait);
103 }
104 }
105 }
106
107 #[derive(Debug)]
108 enum PingError {
109 SocketReceiveError(std::io::Error),
110 SocketSendError(std::io::Error),
111 WrongMessageReceived(String),
112 }
113
114 impl fmt::Display for PingError {
115 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116 match self {
117 PingError::SocketReceiveError(error) => {
118 write!(f, "Didn't receive any response from watchdog: {}", error)
119 }
120 PingError::SocketSendError(error) => {
121 write!(f, "Unable to send the message: {}", error)
122 }
123 PingError::WrongMessageReceived(message) => {
124 write!(f, "Watchdog replay with a wrong message: {}", message)
125 }
126 }
127 }
128 }
129
130 fn ping(socket: &UdpSocket, rng: &mut ThreadRng) -> std::result::Result<Duration, PingError> {
131 loop {
132 let number: u64 = rng.gen();
133 let mut buffer = number.to_le_bytes();
134 let now = time::Instant::now();
135 match socket.send(&buffer) {
136 Ok(_size_sent) => {
137 buffer.fill(0);
138 match socket.recv(&mut buffer) {
139 Ok(size_received) => {
140 if size_received == 8 {
141 let number_received = u64::from_le_bytes(buffer);
142 if number_received == number {
143 return Ok(time::Instant::now() - now);
144 } else {
145 return Err(PingError::WrongMessageReceived(format!(
146 "Message number receceived ({}) is not equal to the one sent ({})",
147 number_received, number
148 )));
149 }
150 } else {
151 return Err(PingError::WrongMessageReceived(format!(
152 "Size of packet must be 8, received size: {}",
153 size_received
154 )));
155 }
156 }
157 // FIXME.
158 // Test the kind because sometime 'recv' returns
159 // '(Os { code: 11, kind: WouldBlock, message: "Resource temporarily unavailable" }'.
160 // Try again in this case.
161 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
162 println!("WouldBlock error: {}", error)
163 }
164 Err(error) => return Err(PingError::SocketReceiveError(error)),
165 }
166 }
167 Err(error) => return Err(PingError::SocketSendError(error)),
168 }
169 }
170 }
171
172 fn send_email(title: &str, body: &str, login: &str, pass: &str) -> Result<()> {
173 let email = Message::builder()
174 .message_id(None)
175 .from("Staking Watchdog Watchdog <redmine@d-lan.net>".parse()?)
176 .to("Greg Burri <greg.burri@gmail.com>".parse()?)
177 .subject(title)
178 .header(ContentType::TEXT_PLAIN)
179 .body(body.to_string())?;
180
181 let creds = Credentials::new(login.to_string(), pass.to_string());
182
183 // Open a remote connection to gmail
184 let mailer = SmtpTransport::relay("mail.infomaniak.com")?
185 .credentials(creds)
186 .build();
187
188 // Send the email
189 let response = mailer.send(&email)?;
190
191 println!("{:?}", response);
192
193 Ok(())
194 }