use crate::header::Header;
use crate::response::Response;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProxyError {
DnsError,
DestinationUnavailable,
DestinationIpUnroutable,
ConnectionRefused,
ConnectionTerminated,
ConnectionTimeout,
TlsProtocolError,
TlsCertificateError,
HttpResponseTimeout,
HttpResponseIncomplete,
HttpProtocolError,
ProxyConfigurationError,
ProxyInternalError,
}
impl ProxyError {
pub fn as_str(self) -> &'static str {
match self {
ProxyError::DnsError => "dns_error",
ProxyError::DestinationUnavailable => "destination_unavailable",
ProxyError::DestinationIpUnroutable => "destination_ip_unroutable",
ProxyError::ConnectionRefused => "connection_refused",
ProxyError::ConnectionTerminated => "connection_terminated",
ProxyError::ConnectionTimeout => "connection_timeout",
ProxyError::TlsProtocolError => "tls_protocol_error",
ProxyError::TlsCertificateError => "tls_certificate_error",
ProxyError::HttpResponseTimeout => "http_response_timeout",
ProxyError::HttpResponseIncomplete => "http_response_incomplete",
ProxyError::HttpProtocolError => "http_protocol_error",
ProxyError::ProxyConfigurationError => "proxy_configuration_error",
ProxyError::ProxyInternalError => "proxy_internal_error",
}
}
pub fn classify(failure: &str) -> ProxyError {
let f = failure.to_ascii_lowercase();
let has = |s: &str| f.contains(s);
if has("dns lookup") || has("no address resolved") || has("failed to lookup address") {
ProxyError::DnsError
} else if has("invalid upstream") || has("invalid tls server name") || has("cannot read") || has("cannot parse") || has("need the http2") {
ProxyError::ProxyConfigurationError
} else if has("tls") || has("handshake") || has("certificate") {
if has("certificate") || has("unknownissuer") || has("notvalidforname") || has("expired") {
ProxyError::TlsCertificateError
} else if has("timed out") {
ProxyError::ConnectionTimeout
} else {
ProxyError::TlsProtocolError
}
} else if has("connect to") || has("connect(") {
if has("timed out") {
ProxyError::ConnectionTimeout
} else if has("unreachable") {
ProxyError::DestinationIpUnroutable
} else {
ProxyError::ConnectionRefused
}
} else if has("mid-body") || has("premature eof") {
ProxyError::HttpResponseIncomplete
} else if has("timed out") || has("would block") || has("temporarily unavailable") {
ProxyError::HttpResponseTimeout
} else if has("closed connection") || has("reset") || has("broken pipe") || has("write to backend") || has("aborted") {
ProxyError::ConnectionTerminated
} else if has("interim") || has("parse") || has("invalid") || has("malformed") || has("chunk") || has("status line") {
ProxyError::HttpProtocolError
} else {
ProxyError::ProxyInternalError
}
}
}
fn mode() -> (bool, bool) {
let config = crate::server_config::ServerConfig::current();
match config.proxy_status.as_str() {
"off" => (false, false),
"errors" => (false, true),
_ => (true, true),
}
}
pub fn received(response: &mut Response, next_hop: Option<&str>) {
if mode().0 {
let status = response.status_code;
add(response, &format!("received-status={status}"), next_hop, None);
}
}
pub fn failed(response: &mut Response, error: ProxyError, next_hop: Option<&str>, details: Option<&str>) {
if mode().1 {
add(response, &format!("error={}", error.as_str()), next_hop, details);
}
}
pub fn failed_with(response: &mut Response, failure: &str, next_hop: Option<&str>) {
failed(response, ProxyError::classify(failure), next_hop, Some(failure));
}
fn add(response: &mut Response, parameter: &str, next_hop: Option<&str>, details: Option<&str>) {
let config = crate::server_config::ServerConfig::current();
let mut member = format!("{}; {parameter}", crate::header::sf::item(&config.proxy_name));
if config.proxy_status_next_hop {
if let Some(hop) = next_hop.filter(|h| !h.is_empty()) {
member.push_str(&format!("; next-hop={}", crate::header::sf::string(hop)));
}
if let Some(details) = details.filter(|d| !d.is_empty()) {
member.push_str(&format!("; details={}", crate::header::sf::string(details)));
}
}
match response.headers.iter_mut().find(|h| h.name.eq_ignore_ascii_case("proxy-status")) {
Some(existing) => existing.value = format!("{}, {member}", existing.value).into(),
None => response.headers.push(Header::new("Proxy-Status", member)),
}
}
#[cfg(test)]
mod tests;