From 368ae10e0f7b10b0a539c395f924d6fe03fe9b27 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 24 Dec 2020 23:19:05 -0500 Subject: [PATCH] Export ping statistics to prometheus --- Cargo.lock | 124 +++++++++++++++++++++++++++------------------- Cargo.toml | 3 +- src/icmp.rs | 139 +++++++++++++++++++--------------------------------- src/main.rs | 58 +++++++++++++--------- 4 files changed, 161 insertions(+), 163 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e70e733..58d0e36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,12 +35,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - [[package]] name = "byteorder" version = "1.3.4" @@ -91,22 +85,44 @@ name = "durnitisp" version = "0.1.0" dependencies = [ "anyhow", + "ekko", "gflags", "log", "nursery", - "packet", "prometheus", - "socket2", "stderrlog", "tiny_http", ] +[[package]] +name = "ekko" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f867af1a5d502e71ac40aa6204f057bda420d36eb3a8d6f1eb3a9e16f17b3184" +dependencies = [ + "byteorder", + "rand", + "socket2", + "thiserror", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi", +] + [[package]] name = "gflags" version = "0.3.5" @@ -141,15 +157,6 @@ dependencies = [ "syn", ] -[[package]] -name = "hwaddr" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e414433a9e4338f4e87fa29d0670c883a5e73e7955c45f4a49130c0aa992c85b" -dependencies = [ - "phf", -] - [[package]] name = "idna" version = "0.2.0" @@ -247,18 +254,6 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4bd2d4e0cd7c6bb256afbc59a5921c3ead56f05d7696c92e05b6978858b6fa5" -[[package]] -name = "packet" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c136c7ad0619ed4f88894aecf66ad86c80683e7b5d707996e6a3a7e0e3916944" -dependencies = [ - "bitflags", - "byteorder", - "hwaddr", - "thiserror", -] - [[package]] name = "percent-encoding" version = "2.1.0" @@ -266,22 +261,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] -name = "phf" -version = "0.8.0" +name = "ppv-lite86" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher", -] +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro2" @@ -321,6 +304,47 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -356,12 +380,6 @@ dependencies = [ "syn", ] -[[package]] -name = "siphasher" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" - [[package]] name = "socket2" version = "0.3.19" @@ -533,6 +551,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 4b80073..04e1196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,5 +14,4 @@ nursery = "^0.0.1" prometheus = "^0.9.0" stderrlog = "0.4" tiny_http = "^0.7.0" -packet = "^0.1.4" -socket2 = "^0.3.19" \ No newline at end of file +ekko = "0.2.0" \ No newline at end of file diff --git a/src/icmp.rs b/src/icmp.rs index cfc1832..be58f03 100644 --- a/src/icmp.rs +++ b/src/icmp.rs @@ -11,72 +11,41 @@ // 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::convert::Into; -use std::net::{IpAddr, SocketAddr}; -use std::sync::{Arc, RwLock}; -use std::time::{Duration, Instant}; - +use ekko::{Ekko, EkkoResponse}; use gflags; -use log::{debug, error, info}; -use packet::icmp::echo::{Builder, Packet}; -use packet::Builder as PBuilder; -use socket2::Domain; -use socket2::Protocol; -use socket2::SockAddr; -use socket2::Socket; +use log::{error, info}; +use prometheus::{CounterVec, IntGaugeVec}; +use std::sync::{Arc, RwLock}; +use std::time::Duration; gflags::define! { // The size in bytes of the ping requests. - --pingPayload = "durnitisp ping test" + --pingPayload = "durnitisp" } -fn make_echo_packet(ident: u16) -> Packet> { - let buffer = Builder::default() - .request() - .unwrap() - .identifier(ident) - .unwrap() - .sequence(0) - .unwrap() - .payload(PINGPAYLOAD.flag.as_bytes()) - .unwrap() - .build() - .unwrap(); - Packet::unchecked(buffer) +gflags::define! { + // The size in bytes of the ping requests. + --pingTTL: u32 = 113 +} + +gflags::define! { + // The size in bytes of the ping requests. + --pingTimeout: u64 = 2048 +} + +gflags::define! { + // The size in bytes of the ping requests. + --maxHops: u8 = 50 } pub fn start_echo_loop( domain_name: &str, stop_signal: Arc>, - addr: IpAddr, - ident: u16, + ping_latency_guage: IntGaugeVec, + ping_counter: CounterVec, ) { - info!("Starting ping of {}", domain_name); - // First we construct our icmp transport - // TODO(jwall): Timeouts. - // TODO(jwall): Handle out of order packets. - let (domain, protocol) = match addr { - IpAddr::V4(_) => (Domain::ipv4(), Protocol::icmpv4()), - IpAddr::V6(_) => (Domain::ipv6(), Protocol::icmpv6()), - }; - // Construct a socket to send the ICMP request on. - // socket type: Ip, Datagram, ICMP - let addr: SocketAddr = (addr, 0).into(); - let addr: SockAddr = addr.into(); - let socket = match Socket::new(domain, socket2::Type::raw(), Some(protocol)) { - Ok(s) => s, - Err(e) => { - error!("Unable to create socket for icmp request:\n {}", e); - return; - } - }; - - socket - .set_read_timeout(Some(Duration::from_millis(2048))) - .unwrap(); - // then we start our loop - let mut n = 0; - let mut pkt = make_echo_packet(ident); + info!("Pinging {}", domain_name); + let mut sender = Ekko::with_target(domain_name).unwrap(); loop { { // Limit the scope of this lock @@ -85,41 +54,35 @@ pub fn start_echo_loop( return; } } - pkt.set_sequence(n).unwrap(); - // TODO(jwall): Count the errors? - // construct echo packet - let time_of_send = Instant::now(); - // send echo packet - let pkt_buf: &[u8] = pkt.as_ref(); - debug!("Sending echo request for {}", domain_name); - let sent = socket.send_to(pkt_buf, &addr).unwrap(); - if pkt_buf.len() != sent { - error!("Failed to send a complete icmp packet!"); - continue; - } - // // Wait for echo response - debug!("Waiting for echo reply from {}", domain_name); - let mut buf = vec![0; sent]; - let _rcv_size = match socket.recv(&mut buf) { - Ok(sz) => sz, - Err(e) => { - if let std::io::ErrorKind::TimedOut = e.kind() { - error!("icmp echo request timed out to {}", domain_name); - continue; - } - error!("Error recieving on icmp socket! {:?}", e); - return; + let response = sender + .send_with_timeout(MAXHOPS.flag, Some(Duration::from_millis(PINGTIMEOUT.flag))) + .unwrap(); + match response { + EkkoResponse::DestinationResponse(r) => { + info!( + "ICMP: Reply from {}: time={}ms", + r.address.unwrap(), + r.elapsed.as_millis(), + ); + ping_counter + .with(&prometheus::labels! {"result" => "ok", "domain" => domain_name}) + .inc(); + ping_latency_guage + .with(&prometheus::labels! {"domain" => domain_name}) + .set(r.elapsed.as_millis() as i64); + } + EkkoResponse::ExceededResponse(r) => { + ping_counter + .with(&prometheus::labels! {"result" => "timedout", "domain" => domain_name}) + .inc(); + } + _ => { + ping_counter + .with(&prometheus::labels! {"result" => "err", "domain" => domain_name}) + .inc(); + error!("{:?}", response); } - }; - let echo = Packet::new(&buf).unwrap(); - if echo.sequence() == n { - let round_trip_time = Instant::now().checked_duration_since(time_of_send).unwrap(); - // record this time - info!("Sequence # {} {}ms", n, round_trip_time.as_millis()); - } else { - error!("Got the wrong sequence number {}", echo.sequence()); } - // Increment our sequence number - n += 1; + std::thread::sleep(Duration::from_secs(3)); } } diff --git a/src/main.rs b/src/main.rs index 371cf86..f595ecc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,35 +88,50 @@ fn main() -> anyhow::Result<()> { } // FIXME(jwall): allow them to override ping hosts let ping_hosts = default_ping_hosts; - let counter_opts = Opts::new( - "stun_attempt_counter", - "Counter for the good, bad, and total attempts to connect to stun server.", - ); - let gauge_opts = Opts::new( - "stun_attempt_latency_ms", - "Latency guage in millis per stun domain.", - ); - let stop_signal = Arc::new(RwLock::new(false)); // Create a Registry and register metrics. let r = Registry::new(); - let stun_counter_vec = CounterVec::new(counter_opts, &["result", "domain"]).unwrap(); + let stun_counter_vec = CounterVec::new( + Opts::new( + "stun_attempt_counter", + "Counter for the good, bad, and total attempts to connect to stun server.", + ), + &["result", "domain"], + ) + .unwrap(); let stun_success_vec = IntGaugeVec::new( Opts::new("stun_success", "Stun probe successes"), &["domain"], ) .unwrap(); + let stun_latency_vec = IntGaugeVec::new( + Opts::new( + "stun_attempt_latency_ms", + "Latency guage in millis per stun domain.", + ), + &["domain"], + ) + .unwrap(); + let ping_latency_vec = + IntGaugeVec::new(Opts::new("ping_latency", "ICMP Ping latency"), &["domain"]).unwrap(); + let ping_counter_vec = CounterVec::new( + Opts::new("ping_counter", "Ping Request Counter"), + &["result", "domain"], + ) + .unwrap(); r.register(Box::new(stun_counter_vec.clone())) .expect("Failed to register stun connection counter"); - let stun_latency_vec = IntGaugeVec::new(gauge_opts, &["domain"]).unwrap(); r.register(Box::new(stun_latency_vec.clone())) .expect("Failed to register stun latency guage"); r.register(Box::new(stun_success_vec.clone())) .expect("Failed to register stun success gauge"); + r.register(Box::new(ping_latency_vec.clone())) + .expect("Failed to register ping latency guage"); + r.register(Box::new(ping_counter_vec.clone())) + .expect("Failed to register ping counter"); let stun_socket_addrs = util::resolve_addrs(&stun_servers).unwrap(); let stun_servers = Arc::new(stun_servers); - let ping_addrs = util::resolve_ip_addrs(&ping_hosts).unwrap(); let ping_hosts = Arc::new(ping_hosts); let mut parent = Nursery::new(); @@ -161,18 +176,15 @@ fn main() -> anyhow::Result<()> { }); parent.adopt(Box::new(render_thread)); } - for (i, addr) in ping_addrs.iter().cloned().enumerate() { + for (i, domain_name) in ping_hosts.iter().cloned().enumerate() { // TODO(Prometheus stats) - let ping_hosts_copy = ping_hosts.clone(); - if let Some(addr) = dbg!(addr) { - let domain_name = *ping_hosts_copy.get(i).unwrap(); - debug!("Pinging {}", domain_name); - let stop_signal = stop_signal.clone(); - let ping_thread = thread::Pending::new(move || { - icmp::start_echo_loop(domain_name, stop_signal, addr, i as u16); - }); - parent.schedule(Box::new(ping_thread)); - } + let stop_signal = stop_signal.clone(); + let ping_latency_vec = ping_latency_vec.clone(); + let ping_counter_vec = ping_counter_vec.clone(); + let ping_thread = thread::Pending::new(move || { + icmp::start_echo_loop(domain_name, stop_signal, ping_latency_vec, ping_counter_vec); + }); + parent.schedule(Box::new(ping_thread)); } // Then we attempt to start connections to each stun server. for (i, s) in stun_socket_addrs.iter().enumerate() {