Skip to content

Commit

Permalink
Make disconnect error show up as warning instead of failure, since co…
Browse files Browse the repository at this point in the history
…nnect works fine. (#39)
  • Loading branch information
r12f authored Jul 23, 2021
1 parent 3530414 commit 72dc4e2
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 90 deletions.
34 changes: 22 additions & 12 deletions src/ping_clients/ping_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,49 @@ use std::time::Duration;
use std::net::SocketAddr;
use async_trait::async_trait;

#[derive(thiserror::Error, Debug)]
pub enum PingClientWarning {
#[error("{0}")]
DisconnectFailed(Box<dyn std::error::Error + Send>),

#[error("{0}")]
AppHandshakeFailed(Box<dyn std::error::Error + Send>),
}

#[derive(thiserror::Error, Debug)]
pub enum PingClientError {
#[error("{0}")]
PreparationFailed(Box<dyn std::error::Error + Send>),

#[error("{0}")]
PingFailed(Box<dyn std::error::Error + Send>),
}


#[derive(Debug)]
pub struct PingClientPingResultDetails {
pub actual_local_addr: Option<SocketAddr>,
pub round_trip_time: Duration,
pub is_timeout: bool,
pub handshake_error: Option<Box<dyn std::error::Error + Send>>,
pub warning: Option<PingClientWarning>,
}

impl PingClientPingResultDetails {
pub fn new(
actual_local_addr: Option<SocketAddr>,
round_trip_time: Duration,
is_timeout: bool,
handshake_error: Option<Box<dyn std::error::Error + Send>>,
warning: Option<PingClientWarning>,
) -> PingClientPingResultDetails {
PingClientPingResultDetails {
actual_local_addr,
round_trip_time,
is_timeout,
handshake_error,
warning,
}
}
}

#[derive(thiserror::Error, Debug)]
pub enum PingClientError {
#[error("{0}")]
PreparationFailed(Box<dyn std::error::Error + Send>),

#[error("{0}")]
PingFailed(Box<dyn std::error::Error + Send>),
}

pub type PingClientResult<T, E = PingClientError> = std::result::Result<T, E>;

#[async_trait]
Expand Down
12 changes: 6 additions & 6 deletions src/ping_clients/ping_client_quic.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::ping_clients::ping_client::{
PingClient, PingClientError, PingClientPingResultDetails, PingClientResult,
};
use crate::ping_clients::ping_client::{PingClient, PingClientError, PingClientPingResultDetails, PingClientResult, PingClientWarning};
use crate::PingClientConfig;
use async_trait::async_trait;
use quinn::{ClientConfigBuilder, Endpoint, EndpointError, ConnectionError};
Expand Down Expand Up @@ -94,9 +92,11 @@ impl PingClientQuic {
Err(e) => match e {
ConnectionError::TimedOut => Err(PingClientError::PingFailed(Box::new(e))),
ConnectionError::LocallyClosed => Err(PingClientError::PingFailed(Box::new(e))),
_ => {
return Ok(PingClientPingResultDetails::new(None, rtt, false, Some(Box::new(e))))
},
_ => return Ok(PingClientPingResultDetails::new(
None,
rtt,
false,
Some(PingClientWarning::AppHandshakeFailed(Box::new(e))))),
}
}?;

Expand Down
14 changes: 7 additions & 7 deletions src/ping_clients/ping_client_tcp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::ping_clients::ping_client::{PingClient, PingClientResult, PingClientPingResultDetails, PingClientError};
use crate::ping_clients::ping_client::{PingClient, PingClientResult, PingClientPingResultDetails, PingClientError, PingClientWarning};
use crate::PingClientConfig;
use socket2::{Domain, SockAddr, Socket, Type};
use std::io;
Expand Down Expand Up @@ -36,19 +36,19 @@ impl PingClientTcp {
let local_addr = socket.local_addr();

// Check closing connection as well as opening connection
let mut warning: Option<PingClientWarning> = None;
if self.config.check_disconnect {
let shutdown_result = self.shutdown_connection(socket);
match shutdown_result {
Err(e) => return Err(PingClientError::PingFailed(Box::new(e))),
Ok(_) => (),
warning = match self.shutdown_connection(socket) {
Err(e) => Some(PingClientWarning::DisconnectFailed(Box::new(e))),
Ok(_) => None,
}
}

// If getting local address failed, we ignore it.
// The worse case we can get is to output a 0.0.0.0 as source IP, which is not critical to what we are trying to do.
return match local_addr {
Ok(addr) => Ok(PingClientPingResultDetails::new(Some(addr.as_socket().unwrap()), rtt, false, None)),
Err(_) => Ok(PingClientPingResultDetails::new(None, rtt, false, None)),
Ok(addr) => Ok(PingClientPingResultDetails::new(Some(addr.as_socket().unwrap()), rtt, false, warning)),
Err(_) => Ok(PingClientPingResultDetails::new(None, rtt, false, warning)),
};
}

Expand Down
114 changes: 72 additions & 42 deletions src/ping_result.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::ping_clients::ping_client::PingClientError::{self, PingFailed, PreparationFailed};
use crate::ping_clients::ping_client::PingClientError;
use chrono::{offset::Utc, DateTime};
use std::{net::SocketAddr, time::Duration};
use contracts::requires;
use crate::ping_clients::ping_client::PingClientWarning;

#[derive(Debug)]
pub struct PingResult {
Expand All @@ -14,14 +15,14 @@ pub struct PingResult {
is_succeeded: bool,
round_trip_time: Duration,
is_timed_out: bool,
warning: Option<PingClientWarning>,
error: Option<PingClientError>,
handshake_error: Option<Box<dyn std::error::Error + Send>>,
}

impl PingResult {
#[requires(is_succeeded -> !is_timed_out && error.is_none())]
#[requires(handshake_error.is_some() -> is_succeeded)]
#[requires(!is_succeeded -> (is_timed_out || error.is_some()) && handshake_error.is_none())]
#[requires(warning.is_some() -> is_succeeded)]
#[requires(!is_succeeded -> (is_timed_out || error.is_some()) && warning.is_none())]
pub fn new(
time: &DateTime<Utc>,
worker_id: u32,
Expand All @@ -32,8 +33,8 @@ impl PingResult {
is_succeeded: bool,
round_trip_time: Duration,
is_timed_out: bool,
warning: Option<PingClientWarning>,
error: Option<PingClientError>,
handshake_error: Option<Box<dyn std::error::Error + Send>>,
) -> PingResult {
PingResult {
ping_time: time.clone(),
Expand All @@ -45,8 +46,8 @@ impl PingResult {
is_succeeded,
round_trip_time,
is_timed_out,
warning,
error,
handshake_error,
}
}

Expand Down Expand Up @@ -77,14 +78,14 @@ impl PingResult {
pub fn is_timed_out(&self) -> bool {
self.is_timed_out
}
pub fn handshake_error(&self) -> &Option<Box<dyn std::error::Error + Send>> {
&self.handshake_error
pub fn warning(&self) -> &Option<PingClientWarning> {
&self.warning
}
pub fn error(&self) -> &Option<PingClientError> {
&self.error
}
pub fn is_preparation_error(&self) -> bool {
if let Some(PreparationFailed(_)) = self.error() {
if let Some(PingClientError::PreparationFailed(_)) = self.error() {
true
} else {
false
Expand All @@ -107,7 +108,7 @@ impl PingResult {

if let Some(error) = self.error() {
return match error {
PreparationFailed(e) => {
PingClientError::PreparationFailed(e) => {
format!(
"Unable to perform ping to {} {} from {}{}, because failed preparing to ping: Error = {}",
self.protocol(),
Expand All @@ -117,7 +118,7 @@ impl PingResult {
e)
}

PingFailed(e) => {
PingClientError::PingFailed(e) => {
format!(
"Reaching {} {} from {}{} failed: {}",
self.protocol(),
Expand All @@ -130,16 +131,32 @@ impl PingResult {
};
}

if let Some(handshake_error) = self.handshake_error() {
return format!(
"Reaching {} {} from {}{} succeeded, but handshake failed: RTT={:.2}ms, Error = {}",
self.protocol(),
self.target(),
self.source(),
warmup_sign,
self.round_trip_time().as_micros() as f64 / 1000.0,
handshake_error,
);
if let Some(warning) = self.warning() {
match warning {
PingClientWarning::AppHandshakeFailed(e) => {
return format!(
"Reaching {} {} from {}{} succeeded, but handshake failed: RTT={:.2}ms, Error = {}",
self.protocol(),
self.target(),
self.source(),
warmup_sign,
self.round_trip_time().as_micros() as f64 / 1000.0,
e,
);
}

PingClientWarning::DisconnectFailed(e) => {
return format!(
"Reaching {} {} from {}{} succeeded, but disconnect failed: RTT={:.2}ms, Error = {}",
self.protocol(),
self.target(),
self.source(),
warmup_sign,
self.round_trip_time().as_micros() as f64 / 1000.0,
e,
);
}
}
}

return format!(
Expand All @@ -154,19 +171,23 @@ impl PingResult {

pub fn format_as_json_string(&self) -> String {
let preparation_error = self.error().as_ref().map_or(String::from(""), |e| {
if let PreparationFailed(pe) = e { pe.to_string() } else { String::from("") }
if let PingClientError::PreparationFailed(pe) = e { pe.to_string() } else { String::from("") }
});

let ping_error = self.error().as_ref().map_or(String::from(""), |e| {
if let PingFailed(pe) = e { pe.to_string() } else { String::from("") }
if let PingClientError::PingFailed(pe) = e { pe.to_string() } else { String::from("") }
});

let handshake_error = self.warning().as_ref().map_or(String::from(""), |w| {
if let PingClientWarning::AppHandshakeFailed(hw) = w { hw.to_string() } else { String::from("") }
});

let handshake_error = self.handshake_error().as_ref().map_or(String::from(""), |e| {
e.to_string()
let disconnect_error = self.warning().as_ref().map_or(String::from(""), |w| {
if let PingClientWarning::DisconnectFailed(dw) = w { dw.to_string() } else { String::from("") }
});

let json = format!(
"{{\"utcTime\":\"{:?}\",\"protocol\":\"{}\",\"workerId\":{},\"targetIp\":\"{}\",\"targetPort\":{},\"sourceIp\":\"{}\",\"sourcePort\":{},\"isWarmup\":{},\"isSucceeded\":{},\"rttInMs\":{:.2},\"isTimedOut\":{},\"preparationError\":\"{}\",\"pingError\":\"{}\",\"handshakeError\":\"{}\"}}",
"{{\"utcTime\":\"{:?}\",\"protocol\":\"{}\",\"workerId\":{},\"targetIp\":\"{}\",\"targetPort\":{},\"sourceIp\":\"{}\",\"sourcePort\":{},\"isWarmup\":{},\"isSucceeded\":{},\"rttInMs\":{:.2},\"isTimedOut\":{},\"preparationError\":\"{}\",\"pingError\":\"{}\",\"handshakeError\":\"{}\",\"disconnectError\":\"{}\"}}",
self.ping_time(),
self.protocol(),
self.worker_id(),
Expand All @@ -181,26 +202,31 @@ impl PingResult {
preparation_error,
ping_error,
handshake_error,
disconnect_error,
);

return json;
}

pub fn format_as_csv_string(&self) -> String {
let preparation_error = self.error().as_ref().map_or(String::from(""), |e| {
if let PreparationFailed(pe) = e { pe.to_string() } else { String::from("") }
if let PingClientError::PreparationFailed(pe) = e { pe.to_string() } else { String::from("") }
});

let ping_error = self.error().as_ref().map_or(String::from(""), |e| {
if let PingFailed(pe) = e { pe.to_string() } else { String::from("") }
if let PingClientError::PingFailed(pe) = e { pe.to_string() } else { String::from("") }
});

let handshake_error = self.warning().as_ref().map_or(String::from(""), |w| {
if let PingClientWarning::AppHandshakeFailed(hw) = w { hw.to_string() } else { String::from("") }
});

let handshake_error = self.handshake_error().as_ref().map_or(String::from(""), |e| {
e.to_string()
let disconnect_error = self.warning().as_ref().map_or(String::from(""), |w| {
if let PingClientWarning::DisconnectFailed(dw) = w { dw.to_string() } else { String::from("") }
});

let csv = format!(
"{:?},{},{},{},{},{},{},{},{},{:.2},{},\"{}\",\"{}\",\"{}\"",
"{:?},{},{},{},{},{},{},{},{},{:.2},{},\"{}\",\"{}\",\"{}\",\"{}\"",
self.ping_time(),
self.worker_id(),
self.protocol(),
Expand All @@ -215,6 +241,7 @@ impl PingResult {
preparation_error,
ping_error,
handshake_error,
disconnect_error,
);

return csv;
Expand Down Expand Up @@ -254,7 +281,7 @@ mod tests {
assert!(r.is_succeeded());
assert_eq!(Duration::from_millis(10), r.round_trip_time());
assert!(r.error().is_none());
assert!(r.handshake_error().is_none());
assert!(r.warning().is_none());
}

#[test]
Expand All @@ -265,6 +292,7 @@ mod tests {
"Reaching TCP 1.2.3.4:443 from 5.6.7.8:8080 (warmup) succeeded: RTT=10.00ms",
"Reaching TCP 1.2.3.4:443 from 5.6.7.8:8080 failed: Timed out, RTT = 1000.00ms",
"Reaching TCP 1.2.3.4:443 from 5.6.7.8:8080 succeeded, but handshake failed: RTT=20.00ms, Error = connect aborted",
"Reaching TCP 1.2.3.4:443 from 5.6.7.8:8080 succeeded, but disconnect failed: RTT=20.00ms, Error = disconnect timeout",
"Reaching TCP 1.2.3.4:443 from 5.6.7.8:8080 failed: connect failed",
"Unable to perform ping to TCP 1.2.3.4:443 from 5.6.7.8:8080, because failed preparing to ping: Error = address in use",
],
Expand All @@ -280,11 +308,12 @@ mod tests {
let results = rnp_test_common::generate_ping_result_test_samples();
assert_eq!(
vec![
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":true,\"isSucceeded\":true,\"rttInMs\":10.00,\"isTimedOut\":false,\"preparationError\":\"\",\"pingError\":\"\",\"handshakeError\":\"\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":false,\"isSucceeded\":false,\"rttInMs\":1000.00,\"isTimedOut\":true,\"preparationError\":\"\",\"pingError\":\"\",\"handshakeError\":\"\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":false,\"isSucceeded\":true,\"rttInMs\":20.00,\"isTimedOut\":false,\"preparationError\":\"\",\"pingError\":\"\",\"handshakeError\":\"connect aborted\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":false,\"isSucceeded\":false,\"rttInMs\":0.00,\"isTimedOut\":false,\"preparationError\":\"\",\"pingError\":\"connect failed\",\"handshakeError\":\"\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":false,\"isSucceeded\":false,\"rttInMs\":0.00,\"isTimedOut\":false,\"preparationError\":\"address in use\",\"pingError\":\"\",\"handshakeError\":\"\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":true,\"isSucceeded\":true,\"rttInMs\":10.00,\"isTimedOut\":false,\"preparationError\":\"\",\"pingError\":\"\",\"handshakeError\":\"\",\"disconnectError\":\"\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":false,\"isSucceeded\":false,\"rttInMs\":1000.00,\"isTimedOut\":true,\"preparationError\":\"\",\"pingError\":\"\",\"handshakeError\":\"\",\"disconnectError\":\"\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":false,\"isSucceeded\":true,\"rttInMs\":20.00,\"isTimedOut\":false,\"preparationError\":\"\",\"pingError\":\"\",\"handshakeError\":\"connect aborted\",\"disconnectError\":\"\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":false,\"isSucceeded\":true,\"rttInMs\":20.00,\"isTimedOut\":false,\"preparationError\":\"\",\"pingError\":\"\",\"handshakeError\":\"\",\"disconnectError\":\"disconnect timeout\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":false,\"isSucceeded\":false,\"rttInMs\":0.00,\"isTimedOut\":false,\"preparationError\":\"\",\"pingError\":\"connect failed\",\"handshakeError\":\"\",\"disconnectError\":\"\"}",
"{\"utcTime\":\"2021-07-06T09:10:11.012Z\",\"protocol\":\"TCP\",\"workerId\":1,\"targetIp\":\"1.2.3.4\",\"targetPort\":443,\"sourceIp\":\"5.6.7.8\",\"sourcePort\":8080,\"isWarmup\":false,\"isSucceeded\":false,\"rttInMs\":0.00,\"isTimedOut\":false,\"preparationError\":\"address in use\",\"pingError\":\"\",\"handshakeError\":\"\",\"disconnectError\":\"\"}",
],
results.into_iter().map(|x| x.format_as_json_string()).collect::<Vec<String>>()
);
Expand All @@ -295,11 +324,12 @@ mod tests {
let results = rnp_test_common::generate_ping_result_test_samples();
assert_eq!(
vec![
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,true,true,10.00,false,\"\",\"\",\"\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,false,false,1000.00,true,\"\",\"\",\"\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,false,true,20.00,false,\"\",\"\",\"connect aborted\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,false,false,0.00,false,\"\",\"connect failed\",\"\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,false,false,0.00,false,\"address in use\",\"\",\"\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,true,true,10.00,false,\"\",\"\",\"\",\"\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,false,false,1000.00,true,\"\",\"\",\"\",\"\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,false,true,20.00,false,\"\",\"\",\"connect aborted\",\"\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,false,true,20.00,false,\"\",\"\",\"\",\"disconnect timeout\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,false,false,0.00,false,\"\",\"connect failed\",\"\",\"\"",
"2021-07-06T09:10:11.012Z,1,TCP,1.2.3.4,443,5.6.7.8,8080,false,false,0.00,false,\"address in use\",\"\",\"\",\"\"",
],
results
.into_iter()
Expand Down
Loading

0 comments on commit 72dc4e2

Please sign in to comment.