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.
|
2021-01-30 19:24:33 -05:00
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
use std::{convert::TryFrom, ops::Sub};
|
|
|
|
use std::{
|
|
|
|
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
|
|
|
sync::{Arc, RwLock},
|
|
|
|
};
|
2021-01-05 20:33:11 -05:00
|
|
|
|
|
|
|
use crate::util;
|
|
|
|
|
2020-12-24 23:19:05 -05:00
|
|
|
use gflags;
|
2021-01-30 19:24:33 -05:00
|
|
|
use icmp_socket::{
|
|
|
|
packet::{Icmpv4Message, Icmpv6Message, WithEchoRequest},
|
|
|
|
IcmpSocket, IcmpSocket4, IcmpSocket6, Icmpv4Packet, Icmpv6Packet,
|
|
|
|
};
|
2021-02-02 20:49:29 -05:00
|
|
|
use log::{error, info};
|
2021-01-30 22:30:29 -05:00
|
|
|
use prometheus::{CounterVec, GaugeVec};
|
2021-01-30 19:24:33 -05:00
|
|
|
use socket2::{self, SockAddr};
|
2020-12-24 15:48:50 -05:00
|
|
|
|
2020-12-24 23:19:05 -05:00
|
|
|
gflags::define! {
|
2021-01-30 19:24:33 -05:00
|
|
|
/// 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! {
|
2021-01-30 19:24:33 -05:00
|
|
|
/// 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
|
|
|
}
|
|
|
|
|
2021-01-05 20:58:11 -05:00
|
|
|
fn resolve_host_address(host: &str) -> String {
|
2021-01-30 19:24:33 -05:00
|
|
|
format!(
|
|
|
|
"{}",
|
|
|
|
util::resolve_hosts(&vec![host])
|
|
|
|
.unwrap()
|
|
|
|
.first()
|
|
|
|
.unwrap()
|
|
|
|
.unwrap()
|
|
|
|
)
|
2021-01-05 20:58:11 -05:00
|
|
|
}
|
|
|
|
|
2021-01-30 19:24:33 -05:00
|
|
|
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>>,
|
2021-01-30 19:24:33 -05:00
|
|
|
) 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) -> (),
|
2021-01-30 19:24:33 -05:00
|
|
|
Sock: IcmpSocket,
|
|
|
|
Sock::AddrType: std::fmt::Display + Copy,
|
|
|
|
Sock::PacketType: WithEchoRequest<Packet = Sock::PacketType>,
|
|
|
|
{
|
2021-02-04 17:20:25 -05:00
|
|
|
if let Err(e) = socket.set_timeout(Duration::from_secs(1)) {
|
|
|
|
error!(
|
|
|
|
"ICMP: Failed to set timeout on socket. Not starting thread. {:?}",
|
|
|
|
e
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
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() {
|
2021-01-30 19:24:33 -05:00
|
|
|
info!("Stopping ping thread for {}", dest);
|
2020-12-24 15:48:50 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2021-01-30 19:24:33 -05:00
|
|
|
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-02-04 17:20:25 -05:00
|
|
|
match socket.rcv_from() {
|
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;
|
|
|
|
}
|
2021-01-30 19:24:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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,
|
2021-01-30 19:24:33 -05:00
|
|
|
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| {
|
2021-02-02 20:35:34 -05:00
|
|
|
ping_counter
|
|
|
|
.with(&prometheus::labels! {"result" => "err", "domain" => domain_name})
|
|
|
|
.inc();
|
2021-01-30 22:30:29 -05:00
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
2021-01-30 19:24:33 -05:00
|
|
|
};
|
|
|
|
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<()> {
|
2021-01-30 19:24:33 -05:00
|
|
|
// We only want to handle replies for the address we are pinging.
|
|
|
|
match p.message {
|
|
|
|
Icmpv4Message::ParameterProblem {
|
|
|
|
pointer: _,
|
|
|
|
padding: _,
|
2021-02-02 20:35:34 -05:00
|
|
|
header,
|
2021-01-30 19:24:33 -05:00
|
|
|
} => {
|
2021-02-02 20:35:34 -05:00
|
|
|
let dest_addr =
|
|
|
|
Ipv4Addr::new(header[16], header[17], header[18], header[19]);
|
|
|
|
if dest_addr == dest {
|
|
|
|
ping_counter
|
|
|
|
.with(&prometheus::labels! {"result" => "parameter_problem", "domain" => domain_name})
|
|
|
|
.inc();
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
2021-01-30 19:24:33 -05:00
|
|
|
}
|
2021-02-02 20:35:34 -05:00
|
|
|
Icmpv4Message::Unreachable { padding: _, header } => {
|
|
|
|
let dest_addr =
|
|
|
|
Ipv4Addr::new(header[16], header[17], header[18], header[19]);
|
|
|
|
if dest_addr == dest {
|
|
|
|
info!(
|
|
|
|
"ICMP: Destination: {:?} Unreachable {} response from {}",
|
|
|
|
dest_addr,
|
|
|
|
dest,
|
|
|
|
_s.as_inet().unwrap().ip()
|
|
|
|
);
|
|
|
|
ping_counter
|
|
|
|
.with(&prometheus::labels! {"result" => "unreachable", "domain" => domain_name})
|
|
|
|
.inc();
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
2021-01-30 19:24:33 -05:00
|
|
|
}
|
2021-02-02 20:35:34 -05:00
|
|
|
Icmpv4Message::TimeExceeded { padding: _, header } => {
|
|
|
|
let dest_addr =
|
|
|
|
Ipv4Addr::new(header[16], header[17], header[18], header[19]);
|
|
|
|
if dest_addr == dest {
|
|
|
|
info!("ICMP: Timeout for {}", dest);
|
|
|
|
ping_counter
|
|
|
|
.with(&prometheus::labels! {"result" => "timeout", "domain" => domain_name})
|
|
|
|
.inc();
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
2021-01-30 19:24:33 -05:00
|
|
|
}
|
|
|
|
Icmpv4Message::EchoReply {
|
2021-01-31 08:58:27 -05:00
|
|
|
identifier,
|
2021-01-30 19:24:33 -05:00
|
|
|
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 {
|
2021-02-02 20:49:29 -05:00
|
|
|
error!(
|
2021-02-02 20:35:34 -05:00
|
|
|
"ICMP: Discarding sequence {}, expected sequence {}",
|
|
|
|
sequence, seq
|
|
|
|
);
|
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;
|
2021-01-05 20:33:11 -05:00
|
|
|
info!(
|
2021-01-30 19:24:33 -05:00
|
|
|
"ICMP: Reply from {}: time={}ms, seq={}",
|
|
|
|
dest, elapsed, sequence,
|
2021-01-05 20:33:11 -05:00
|
|
|
);
|
|
|
|
ping_counter
|
2021-01-05 21:05:35 -05:00
|
|
|
.with(&prometheus::labels! {"result" => "ok", "domain" => domain_name})
|
2021-01-05 20:33:11 -05:00
|
|
|
.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-05 20:33:11 -05:00
|
|
|
}
|
2021-01-30 20:23:47 -05:00
|
|
|
p => {
|
2021-01-30 19:24:33 -05:00
|
|
|
// We ignore the rest.
|
2021-01-30 20:23:47 -05:00
|
|
|
info!("ICMP Unhandled packet {:?}", p);
|
2021-02-02 20:49:29 -05:00
|
|
|
return None;
|
2021-01-30 19:24:33 -05:00
|
|
|
}
|
|
|
|
}
|
2021-01-30 20:23:47 -05:00
|
|
|
Some(())
|
2021-01-30 19:24:33 -05:00
|
|
|
};
|
|
|
|
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<()> {
|
2021-01-30 19:24:33 -05:00
|
|
|
match p.message {
|
|
|
|
Icmpv6Message::Unreachable {
|
|
|
|
_unused,
|
2021-02-02 20:49:29 -05:00
|
|
|
invoking_packet,
|
2021-01-30 19:24:33 -05:00
|
|
|
} => {
|
2021-02-02 20:49:29 -05:00
|
|
|
match Icmpv6Packet::parse(&invoking_packet) {
|
|
|
|
Ok(Icmpv6Packet {
|
|
|
|
typ: _,
|
|
|
|
code: _,
|
|
|
|
checksum: _,
|
|
|
|
message:
|
|
|
|
Icmpv6Message::EchoRequest {
|
|
|
|
identifier,
|
|
|
|
sequence: _,
|
|
|
|
payload: _,
|
|
|
|
},
|
|
|
|
}) => {
|
|
|
|
if identifier == 42 {
|
|
|
|
ping_counter
|
|
|
|
.with(&prometheus::labels! {"result" => "unreachable", "domain" => domain_name})
|
|
|
|
.inc();
|
|
|
|
return Some(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
// We ignore these as well but log it.
|
|
|
|
error!("ICMP: Error parsing Unreachable invoking packet {:?}", e);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
// We ignore these
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return None;
|
2021-01-05 20:33:11 -05:00
|
|
|
}
|
2021-01-30 19:24:33 -05:00
|
|
|
Icmpv6Message::ParameterProblem {
|
|
|
|
pointer: _,
|
2021-02-02 20:49:29 -05:00
|
|
|
invoking_packet,
|
2021-01-30 19:24:33 -05:00
|
|
|
} => {
|
2021-02-02 20:49:29 -05:00
|
|
|
match Icmpv6Packet::parse(&invoking_packet) {
|
|
|
|
Ok(Icmpv6Packet {
|
|
|
|
typ: _,
|
|
|
|
code: _,
|
|
|
|
checksum: _,
|
|
|
|
message:
|
|
|
|
Icmpv6Message::EchoRequest {
|
|
|
|
identifier,
|
|
|
|
sequence: _,
|
|
|
|
payload: _,
|
|
|
|
},
|
|
|
|
}) => {
|
|
|
|
if identifier == 42 {
|
|
|
|
ping_counter
|
|
|
|
.with(&prometheus::labels! {"result" => "parameter_problem", "domain" => domain_name})
|
|
|
|
.inc();
|
|
|
|
return Some(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
// We ignore these as well but log it.
|
|
|
|
error!("ICMP: Error parsing Unreachable invoking packet {:?}", e);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
// We ignore these
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return None;
|
2021-01-05 20:33:11 -05:00
|
|
|
}
|
2021-01-30 19:24:33 -05:00
|
|
|
Icmpv6Message::EchoReply {
|
2021-01-31 08:58:27 -05:00
|
|
|
identifier,
|
2021-01-30 19:24:33 -05:00
|
|
|
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 {
|
2021-02-02 20:49:29 -05:00
|
|
|
error!("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,
|
|
|
|
);
|
2021-01-30 19:24:33 -05:00
|
|
|
info!(
|
|
|
|
"ICMP: Reply from {}: time={}ms, seq={}",
|
|
|
|
dest, elapsed, sequence,
|
|
|
|
);
|
2021-01-05 20:33:11 -05:00
|
|
|
ping_counter
|
2021-01-30 19:24:33 -05:00
|
|
|
.with(&prometheus::labels! {"result" => "ok", "domain" => domain_name})
|
2021-01-05 20:33:11 -05:00
|
|
|
.inc();
|
2021-01-30 22:30:29 -05:00
|
|
|
if elapsed as i32 != 0 {
|
2021-01-30 19:24:33 -05:00
|
|
|
ping_latency_guage
|
|
|
|
.with(&prometheus::labels! {"domain" => domain_name})
|
2021-01-30 22:30:29 -05:00
|
|
|
.set(elapsed);
|
2021-01-30 19:24:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
// We ignore the rest.
|
2021-02-02 20:49:29 -05:00
|
|
|
return None;
|
2021-01-05 20:33:11 -05:00
|
|
|
}
|
|
|
|
}
|
2021-01-30 20:23:47 -05:00
|
|
|
Some(())
|
2021-01-05 20:33:11 -05:00
|
|
|
};
|
2021-01-30 19:24:33 -05:00
|
|
|
loop_impl(socket, dest, packet_handler, err_handler, stop_signal);
|
|
|
|
}
|
|
|
|
};
|
2020-12-24 15:48:50 -05:00
|
|
|
}
|