durnitisp/src/icmp.rs

287 lines
10 KiB
Rust
Raw Normal View History

2020-12-24 15:48:50 -05:00
// Copyright 2020 Jeremy Wall
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::time::{Duration, Instant};
use std::{convert::TryFrom, ops::Sub};
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
sync::{Arc, RwLock},
};
use crate::util;
2020-12-24 23:19:05 -05:00
use gflags;
use icmp_socket::{
packet::{Icmpv4Message, Icmpv6Message, WithEchoRequest},
IcmpSocket, IcmpSocket4, IcmpSocket6, Icmpv4Packet, Icmpv6Packet,
};
2020-12-24 23:19:05 -05:00
use log::{error, info};
2021-01-30 22:30:29 -05:00
use prometheus::{CounterVec, GaugeVec};
use socket2::{self, SockAddr};
2020-12-24 15:48:50 -05:00
2020-12-24 23:19:05 -05:00
gflags::define! {
/// The payload to use for the ping requests.
2020-12-24 23:19:05 -05:00
--pingPayload = "durnitisp"
}
2020-12-24 15:48:50 -05:00
gflags::define! {
/// The timeout for ping requests.
2020-12-24 23:19:05 -05:00
--pingTimeout: u64 = 2048
}
gflags::define! {
2020-12-24 23:28:30 -05:00
/// The size in bytes of the ping requests.
2020-12-24 23:19:05 -05:00
--maxHops: u8 = 50
2020-12-24 15:48:50 -05:00
}
fn resolve_host_address(host: &str) -> String {
format!(
"{}",
util::resolve_hosts(&vec![host])
.unwrap()
.first()
.unwrap()
.unwrap()
)
}
fn loop_impl<Sock, PH, EH>(
mut socket: Sock,
dest: Sock::AddrType,
packet_handler: PH,
err_handler: EH,
2020-12-24 15:48:50 -05:00
stop_signal: Arc<RwLock<bool>>,
) where
2021-01-30 20:23:47 -05:00
PH: Fn(Sock::PacketType, socket2::SockAddr, Instant, u16) -> Option<()>,
2021-01-30 22:30:29 -05:00
EH: Fn(std::io::Error, bool) -> (),
Sock: IcmpSocket,
Sock::AddrType: std::fmt::Display + Copy,
Sock::PacketType: WithEchoRequest<Packet = Sock::PacketType>,
{
2021-01-30 20:23:47 -05:00
let mut sequence: u16 = 0;
2020-12-24 15:48:50 -05:00
loop {
{
// Limit the scope of this lock
if *stop_signal.read().unwrap() {
info!("Stopping ping thread for {}", dest);
2020-12-24 15:48:50 -05:00
return;
}
}
let packet = Sock::PacketType::with_echo_request(
42,
sequence,
PINGPAYLOAD.flag.as_bytes().to_owned(),
)
.unwrap();
let send_time = Instant::now();
if let Err(e) = socket.send_to(dest, packet) {
2021-01-30 22:30:29 -05:00
err_handler(e, true);
2021-01-30 20:23:47 -05:00
} else {
loop {
// Keep going until we get the packet we are looking for.
2021-01-30 22:30:29 -05:00
match socket.rcv_with_timeout(Duration::from_secs(1)) {
2021-01-30 20:23:47 -05:00
Err(e) => {
2021-01-30 22:30:29 -05:00
err_handler(e, false);
2021-01-30 20:23:47 -05:00
}
Ok((resp, sock_addr)) => {
if packet_handler(resp, sock_addr, send_time, sequence).is_some() {
2021-01-30 22:30:29 -05:00
sequence = sequence.wrapping_add(1);
2021-01-30 20:23:47 -05:00
break;
}
}
}
2021-01-30 22:30:29 -05:00
// Give up after 3 seconds and send another packet.
if Instant::now() - send_time > Duration::from_secs(3) {
break;
}
}
}
std::thread::sleep(Duration::from_secs(3));
}
}
pub fn start_echo_loop(
domain_name: &str,
stop_signal: Arc<RwLock<bool>>,
2021-01-30 22:30:29 -05:00
ping_latency_guage: GaugeVec,
ping_counter: CounterVec,
) {
let resolved = resolve_host_address(domain_name);
info!(
"Attempting to ping domain {} at address: {}",
domain_name, resolved
);
let dest = resolved
.parse::<IpAddr>()
.expect(&format!("Invalid IP Address {}", resolved));
2021-01-30 22:30:29 -05:00
let err_handler = |e: std::io::Error, send: bool| {
if send {
error!(
"ICMP: error sending to domain: {} and address: {} failed: {:?}, Trying again later",
domain_name, &dest, e
);
} else {
error!(
"ICMP: error receiving for domain: {} and address: {} failed: {:?}, Trying again later",
domain_name, &dest, e
);
}
};
match dest {
IpAddr::V4(dest) => {
let mut socket = IcmpSocket4::try_from(Ipv4Addr::new(0, 0, 0, 0)).unwrap();
socket.set_max_hops(MAXHOPS.flag as u32);
2021-01-30 20:23:47 -05:00
let packet_handler = |p: Icmpv4Packet,
2021-01-31 08:58:27 -05:00
_s: SockAddr,
2021-01-30 20:23:47 -05:00
send_time: Instant,
seq: u16|
-> Option<()> {
// We only want to handle replies for the address we are pinging.
match p.message {
Icmpv4Message::ParameterProblem {
pointer: _,
padding: _,
header: _,
} => {
ping_counter
2021-01-30 20:23:47 -05:00
.with(&prometheus::labels! {"result" => "parameter_problem", "domain" => domain_name})
.inc();
}
Icmpv4Message::Unreachable {
padding: _,
header: _,
} => {
2021-01-31 08:58:27 -05:00
info!(
"ICMP: Destination Unreachable {} from {}",
dest,
_s.as_inet().unwrap().ip()
);
ping_counter
2021-01-30 20:23:47 -05:00
.with(&prometheus::labels! {"result" => "unreachable", "domain" => domain_name})
.inc();
}
Icmpv4Message::TimeExceeded {
padding: _,
header: _,
} => {
2021-01-30 20:23:47 -05:00
info!("ICMP: Timeout for {}", dest);
ping_counter
2021-01-30 20:23:47 -05:00
.with(&prometheus::labels! {"result" => "timeout", "domain" => domain_name})
.inc();
}
Icmpv4Message::EchoReply {
2021-01-31 08:58:27 -05:00
identifier,
sequence,
payload: _,
} => {
2021-01-31 08:58:27 -05:00
if identifier != 42 {
info!("ICMP: Discarding wrong identifier {}", identifier);
return None;
}
2021-01-30 20:23:47 -05:00
if sequence != seq {
info!("ICMP: Discarding sequence {}", sequence);
2021-01-31 08:58:27 -05:00
return None;
2021-01-30 20:23:47 -05:00
}
2021-01-30 22:30:29 -05:00
let elapsed =
Instant::now().sub(send_time.clone()).as_micros() as f64 / 1000.00;
info!(
"ICMP: Reply from {}: time={}ms, seq={}",
dest, elapsed, sequence,
);
ping_counter
.with(&prometheus::labels! {"result" => "ok", "domain" => domain_name})
.inc();
2021-01-30 22:30:29 -05:00
if elapsed as i32 != 0 {
2021-01-05 21:56:51 -05:00
ping_latency_guage
.with(&prometheus::labels! {"domain" => domain_name})
2021-01-30 22:30:29 -05:00
.set(elapsed);
2021-01-05 21:56:51 -05:00
}
}
2021-01-30 20:23:47 -05:00
p => {
// We ignore the rest.
2021-01-30 20:23:47 -05:00
info!("ICMP Unhandled packet {:?}", p);
}
}
2021-01-30 20:23:47 -05:00
Some(())
};
loop_impl(socket, dest, packet_handler, err_handler, stop_signal);
}
IpAddr::V6(dest) => {
let mut socket = IcmpSocket6::try_from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)).unwrap();
socket.set_max_hops(MAXHOPS.flag as u32);
2021-01-30 20:23:47 -05:00
let packet_handler = |p: Icmpv6Packet,
2021-01-31 08:58:27 -05:00
_s: SockAddr,
2021-01-30 20:23:47 -05:00
send_time: Instant,
seq: u16|
-> Option<()> {
match p.message {
Icmpv6Message::Unreachable {
_unused,
invoking_packet: _,
} => {
ping_counter
.with(&prometheus::labels! {"result" => "unreachable", "domain" => domain_name})
.inc();
}
Icmpv6Message::ParameterProblem {
pointer: _,
invoking_packet: _,
} => {
ping_counter
.with(&prometheus::labels! {"result" => "parameter_problem", "domain" => domain_name})
.inc();
}
Icmpv6Message::EchoReply {
2021-01-31 08:58:27 -05:00
identifier,
sequence,
payload: _,
} => {
2021-01-31 08:58:27 -05:00
if identifier != 42 {
info!("ICMP: Discarding wrong identifier {}", identifier);
return None;
}
2021-01-30 22:30:29 -05:00
if sequence != seq {
info!("ICMP: Discarding sequence {}", sequence);
2021-01-31 08:58:27 -05:00
return None;
2021-01-30 22:30:29 -05:00
}
let elapsed =
Instant::now().sub(send_time.clone()).as_micros() as f64 / 1000.00;
info!(
"ICMP: Reply from {}: time={}ms, seq={}",
dest, elapsed, sequence,
);
info!(
"ICMP: Reply from {}: time={}ms, seq={}",
dest, elapsed, sequence,
);
ping_counter
.with(&prometheus::labels! {"result" => "ok", "domain" => domain_name})
.inc();
2021-01-30 22:30:29 -05:00
if elapsed as i32 != 0 {
ping_latency_guage
.with(&prometheus::labels! {"domain" => domain_name})
2021-01-30 22:30:29 -05:00
.set(elapsed);
}
}
_ => {
// We ignore the rest.
}
}
2021-01-30 20:23:47 -05:00
Some(())
};
loop_impl(socket, dest, packet_handler, err_handler, stop_signal);
}
};
2020-12-24 15:48:50 -05:00
}