mirror of
https://github.com/telemt/telemt.git
synced 2026-04-22 13:04:10 +03:00
Human-readable Peer Close Classification
This commit is contained in:
@@ -231,7 +231,10 @@ fn print_help() {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{is_expected_handshake_eof, resolve_runtime_config_path};
|
use super::{
|
||||||
|
expected_handshake_close_description, is_expected_handshake_eof, peer_close_description,
|
||||||
|
resolve_runtime_config_path,
|
||||||
|
};
|
||||||
use crate::error::{ProxyError, StreamError};
|
use crate::error::{ProxyError, StreamError};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -314,6 +317,67 @@ mod tests {
|
|||||||
)));
|
)));
|
||||||
assert!(is_expected_handshake_eof(&err));
|
assert!(is_expected_handshake_eof(&err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn peer_close_description_is_human_readable_for_all_peer_close_kinds() {
|
||||||
|
let cases = [
|
||||||
|
(
|
||||||
|
std::io::ErrorKind::ConnectionReset,
|
||||||
|
"Peer reset TCP connection (RST)",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
std::io::ErrorKind::ConnectionAborted,
|
||||||
|
"Peer aborted TCP connection during transport",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
std::io::ErrorKind::BrokenPipe,
|
||||||
|
"Peer closed write side (broken pipe)",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
std::io::ErrorKind::NotConnected,
|
||||||
|
"Socket was already closed by peer",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (kind, expected) in cases {
|
||||||
|
let err = ProxyError::Io(std::io::Error::from(kind));
|
||||||
|
assert_eq!(peer_close_description(&err), Some(expected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn handshake_close_description_is_human_readable_for_all_expected_kinds() {
|
||||||
|
let cases = [
|
||||||
|
(
|
||||||
|
ProxyError::Io(std::io::Error::from(std::io::ErrorKind::UnexpectedEof)),
|
||||||
|
"Peer closed before sending full 64-byte MTProto handshake",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProxyError::Io(std::io::Error::from(std::io::ErrorKind::ConnectionReset)),
|
||||||
|
"Peer reset TCP connection during initial MTProto handshake",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProxyError::Io(std::io::Error::from(std::io::ErrorKind::ConnectionAborted)),
|
||||||
|
"Peer aborted TCP connection during initial MTProto handshake",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProxyError::Io(std::io::Error::from(std::io::ErrorKind::BrokenPipe)),
|
||||||
|
"Peer closed write side before MTProto handshake completed",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProxyError::Io(std::io::Error::from(std::io::ErrorKind::NotConnected)),
|
||||||
|
"Handshake socket was already closed by peer",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProxyError::Stream(StreamError::UnexpectedEof),
|
||||||
|
"Peer closed before sending full 64-byte MTProto handshake",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (err, expected) in cases {
|
||||||
|
assert_eq!(expected_handshake_close_description(&err), Some(expected));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_proxy_links(host: &str, port: u16, config: &ProxyConfig) {
|
pub(crate) fn print_proxy_links(host: &str, port: u16, config: &ProxyConfig) {
|
||||||
@@ -443,30 +507,65 @@ pub(crate) async fn wait_until_admission_open(admission_rx: &mut watch::Receiver
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_expected_handshake_eof(err: &crate::error::ProxyError) -> bool {
|
pub(crate) fn is_expected_handshake_eof(err: &crate::error::ProxyError) -> bool {
|
||||||
matches!(
|
expected_handshake_close_description(err).is_some()
|
||||||
err,
|
}
|
||||||
crate::error::ProxyError::Io(ioe)
|
|
||||||
if matches!(
|
pub(crate) fn peer_close_description(err: &crate::error::ProxyError) -> Option<&'static str> {
|
||||||
ioe.kind(),
|
fn from_kind(kind: std::io::ErrorKind) -> Option<&'static str> {
|
||||||
std::io::ErrorKind::UnexpectedEof
|
match kind {
|
||||||
| std::io::ErrorKind::ConnectionReset
|
std::io::ErrorKind::ConnectionReset => Some("Peer reset TCP connection (RST)"),
|
||||||
| std::io::ErrorKind::ConnectionAborted
|
std::io::ErrorKind::ConnectionAborted => {
|
||||||
| std::io::ErrorKind::BrokenPipe
|
Some("Peer aborted TCP connection during transport")
|
||||||
| std::io::ErrorKind::NotConnected
|
}
|
||||||
)
|
std::io::ErrorKind::BrokenPipe => Some("Peer closed write side (broken pipe)"),
|
||||||
) || matches!(err, crate::error::ProxyError::Stream(crate::error::StreamError::UnexpectedEof))
|
std::io::ErrorKind::NotConnected => Some("Socket was already closed by peer"),
|
||||||
|| matches!(
|
_ => None,
|
||||||
err,
|
}
|
||||||
crate::error::ProxyError::Stream(crate::error::StreamError::Io(ioe))
|
}
|
||||||
if matches!(
|
|
||||||
ioe.kind(),
|
match err {
|
||||||
std::io::ErrorKind::UnexpectedEof
|
crate::error::ProxyError::Io(ioe) => from_kind(ioe.kind()),
|
||||||
| std::io::ErrorKind::ConnectionReset
|
crate::error::ProxyError::Stream(crate::error::StreamError::Io(ioe)) => {
|
||||||
| std::io::ErrorKind::ConnectionAborted
|
from_kind(ioe.kind())
|
||||||
| std::io::ErrorKind::BrokenPipe
|
}
|
||||||
| std::io::ErrorKind::NotConnected
|
_ => None,
|
||||||
)
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expected_handshake_close_description(
|
||||||
|
err: &crate::error::ProxyError,
|
||||||
|
) -> Option<&'static str> {
|
||||||
|
fn from_kind(kind: std::io::ErrorKind) -> Option<&'static str> {
|
||||||
|
match kind {
|
||||||
|
std::io::ErrorKind::UnexpectedEof => {
|
||||||
|
Some("Peer closed before sending full 64-byte MTProto handshake")
|
||||||
|
}
|
||||||
|
std::io::ErrorKind::ConnectionReset => {
|
||||||
|
Some("Peer reset TCP connection during initial MTProto handshake")
|
||||||
|
}
|
||||||
|
std::io::ErrorKind::ConnectionAborted => {
|
||||||
|
Some("Peer aborted TCP connection during initial MTProto handshake")
|
||||||
|
}
|
||||||
|
std::io::ErrorKind::BrokenPipe => {
|
||||||
|
Some("Peer closed write side before MTProto handshake completed")
|
||||||
|
}
|
||||||
|
std::io::ErrorKind::NotConnected => {
|
||||||
|
Some("Handshake socket was already closed by peer")
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match err {
|
||||||
|
crate::error::ProxyError::Io(ioe) => from_kind(ioe.kind()),
|
||||||
|
crate::error::ProxyError::Stream(crate::error::StreamError::UnexpectedEof) => {
|
||||||
|
Some("Peer closed before sending full 64-byte MTProto handshake")
|
||||||
|
}
|
||||||
|
crate::error::ProxyError::Stream(crate::error::StreamError::Io(ioe)) => {
|
||||||
|
from_kind(ioe.kind())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn load_startup_proxy_config_snapshot(
|
pub(crate) async fn load_startup_proxy_config_snapshot(
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ use crate::transport::middle_proxy::MePool;
|
|||||||
use crate::transport::socket::set_linger_zero;
|
use crate::transport::socket::set_linger_zero;
|
||||||
use crate::transport::{ListenOptions, UpstreamManager, create_listener, find_listener_processes};
|
use crate::transport::{ListenOptions, UpstreamManager, create_listener, find_listener_processes};
|
||||||
|
|
||||||
use super::helpers::{is_expected_handshake_eof, print_proxy_links};
|
use super::helpers::{
|
||||||
|
expected_handshake_close_description, is_expected_handshake_eof, peer_close_description,
|
||||||
|
print_proxy_links,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) struct BoundListeners {
|
pub(crate) struct BoundListeners {
|
||||||
pub(crate) listeners: Vec<(TcpListener, bool)>,
|
pub(crate) listeners: Vec<(TcpListener, bool)>,
|
||||||
@@ -485,29 +488,9 @@ pub(crate) fn spawn_tcp_accept_loops(
|
|||||||
Ok(guard) => *guard,
|
Ok(guard) => *guard,
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
};
|
};
|
||||||
let peer_closed = matches!(
|
let peer_close_reason = peer_close_description(&e);
|
||||||
&e,
|
let handshake_close_reason =
|
||||||
crate::error::ProxyError::Io(ioe)
|
expected_handshake_close_description(&e);
|
||||||
if matches!(
|
|
||||||
ioe.kind(),
|
|
||||||
std::io::ErrorKind::ConnectionReset
|
|
||||||
| std::io::ErrorKind::ConnectionAborted
|
|
||||||
| std::io::ErrorKind::BrokenPipe
|
|
||||||
| std::io::ErrorKind::NotConnected
|
|
||||||
)
|
|
||||||
) || matches!(
|
|
||||||
&e,
|
|
||||||
crate::error::ProxyError::Stream(
|
|
||||||
crate::error::StreamError::Io(ioe)
|
|
||||||
)
|
|
||||||
if matches!(
|
|
||||||
ioe.kind(),
|
|
||||||
std::io::ErrorKind::ConnectionReset
|
|
||||||
| std::io::ErrorKind::ConnectionAborted
|
|
||||||
| std::io::ErrorKind::BrokenPipe
|
|
||||||
| std::io::ErrorKind::NotConnected
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let me_closed = matches!(
|
let me_closed = matches!(
|
||||||
&e,
|
&e,
|
||||||
@@ -518,12 +501,23 @@ pub(crate) fn spawn_tcp_accept_loops(
|
|||||||
crate::error::ProxyError::Proxy(msg) if msg == ROUTE_SWITCH_ERROR_MSG
|
crate::error::ProxyError::Proxy(msg) if msg == ROUTE_SWITCH_ERROR_MSG
|
||||||
);
|
);
|
||||||
|
|
||||||
match (peer_closed, me_closed) {
|
match (peer_close_reason, me_closed) {
|
||||||
(true, _) => {
|
(Some(reason), _) => {
|
||||||
if let Some(real_peer) = real_peer {
|
if let Some(real_peer) = real_peer {
|
||||||
debug!(peer = %peer_addr, real_peer = %real_peer, error = %e, "Connection closed by client");
|
debug!(
|
||||||
|
peer = %peer_addr,
|
||||||
|
real_peer = %real_peer,
|
||||||
|
error = %e,
|
||||||
|
close_reason = reason,
|
||||||
|
"Connection closed by peer"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
debug!(peer = %peer_addr, error = %e, "Connection closed by client");
|
debug!(
|
||||||
|
peer = %peer_addr,
|
||||||
|
error = %e,
|
||||||
|
close_reason = reason,
|
||||||
|
"Connection closed by peer"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(_, true) => {
|
(_, true) => {
|
||||||
@@ -541,10 +535,24 @@ pub(crate) fn spawn_tcp_accept_loops(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if is_expected_handshake_eof(&e) => {
|
_ if is_expected_handshake_eof(&e) => {
|
||||||
|
let reason = handshake_close_reason.unwrap_or(
|
||||||
|
"Peer closed during initial handshake",
|
||||||
|
);
|
||||||
if let Some(real_peer) = real_peer {
|
if let Some(real_peer) = real_peer {
|
||||||
info!(peer = %peer_addr, real_peer = %real_peer, error = %e, "Connection closed during initial handshake");
|
info!(
|
||||||
|
peer = %peer_addr,
|
||||||
|
real_peer = %real_peer,
|
||||||
|
error = %e,
|
||||||
|
close_reason = reason,
|
||||||
|
"Connection closed during initial handshake"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
info!(peer = %peer_addr, error = %e, "Connection closed during initial handshake");
|
info!(
|
||||||
|
peer = %peer_addr,
|
||||||
|
error = %e,
|
||||||
|
close_reason = reason,
|
||||||
|
"Connection closed during initial handshake"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
Reference in New Issue
Block a user