use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::config::{
host::{parse_upstream_address, HostTls},
tcp::{TcpRoute, TcpTls},
RouteUpstream,
};
const DEFAULT_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TcpBlock {
pub listen: String,
pub tls: Option<String>,
#[serde(default)]
pub alpn: Vec<String>,
pub connect_timeout_secs: Option<u64>,
pub idle_timeout_secs: Option<u64>,
pub health_check: Option<crate::config::host::HealthCheck>,
#[serde(default)]
pub upstream: BTreeMap<String, TcpUpstreamBlock>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TcpUpstreamBlock {
pub weight: Option<i8>,
}
impl TcpBlock {
pub fn to_route(&self, name: &str, host: Option<(&str, &HostTls)>) -> Result<TcpRoute, String> {
let tls = match (host, self.tls.as_deref()) {
(None, Some(_)) => {
return Err("`tls` is only for tcp blocks inside a host".to_string());
}
(Some((_, host_tls)), None) if host_tls.enabled => TcpTls::Terminate,
(None | Some(_), None) => TcpTls::None,
(Some((_, host_tls)), Some("passthrough")) if host_tls.enabled => TcpTls::Passthrough,
(Some(_), Some("passthrough")) => {
return Err("`tls = \"passthrough\"` needs a host with TLS".to_string());
}
(Some(_), Some(other)) => {
return Err(format!("`tls` can only be \"passthrough\", not {other:?}"));
}
};
if !self.alpn.is_empty() && tls != TcpTls::Terminate {
return Err(
"`alpn` is only for tcp blocks in a host with TLS (not passthrough)".to_string(),
);
}
for protocol in &self.alpn {
if protocol == "h2" || protocol == "http/1.1" {
return Err("`alpn` can't be \"h2\" or \"http/1.1\" (used by HTTPS)".to_string());
}
if protocol.is_empty() || protocol.len() > 255 {
return Err("`alpn` protocols must be 1 to 255 bytes long".to_string());
}
}
let upstreams = self
.upstream
.iter()
.map(|(label, upstream)| {
let (ip, port) = parse_upstream_address(label)?;
Ok(RouteUpstream {
ip: ip.into(),
port,
weight: upstream.weight,
..RouteUpstream::default()
})
})
.collect::<Result<Vec<_>, String>>()?;
Ok(TcpRoute {
name: name.to_string(),
host: host.map(|(name, _)| name.to_string()),
listen: self.listen.clone(),
tls,
alpn: self.alpn.clone(),
connect_timeout: self
.connect_timeout_secs
.map_or(DEFAULT_CONNECT_TIMEOUT, std::time::Duration::from_secs),
idle_timeout: self.idle_timeout_secs.map(std::time::Duration::from_secs),
health_check: self.health_check,
upstreams,
})
}
}