Skip to main content

zlayer_proxy/stream/
config.rs

1//! Configuration types for stream (L4) proxy
2
3use serde::{Deserialize, Serialize};
4use std::time::Duration;
5
6/// Configuration for a TCP listener
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct TcpListenerConfig {
9    /// Listen port (proxy binds to 0.0.0.0:{port})
10    pub port: u16,
11
12    /// Protocol hint for metrics/logging (e.g., "postgresql", "mongodb", "minecraft")
13    #[serde(default)]
14    pub protocol_hint: Option<String>,
15
16    /// Enable TLS termination (auto-provision cert)
17    #[serde(default)]
18    pub tls: bool,
19
20    /// Enable PROXY protocol for passing client IP to backend
21    #[serde(default)]
22    pub proxy_protocol: bool,
23}
24
25/// Configuration for a UDP listener
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct UdpListenerConfig {
28    /// Listen port
29    pub port: u16,
30
31    /// Protocol hint for metrics/logging (e.g., "source-engine", "game-generic")
32    #[serde(default)]
33    pub protocol_hint: Option<String>,
34
35    /// Session timeout override (default uses global `udp_session_timeout`)
36    #[serde(default, with = "optional_duration_serde")]
37    pub session_timeout: Option<Duration>,
38}
39
40/// Default UDP session timeout (60 seconds)
41pub const DEFAULT_UDP_SESSION_TIMEOUT: Duration = Duration::from_secs(60);
42
43/// A decoded L4 health probe carried on a [`StreamProxyConfig`].
44///
45/// This is the runtime, proxy-local representation of the controlling
46/// `StreamHealthCheck` spec type (which lives in `zlayer-types`). The agent is
47/// responsible for translating the spec type into this enum (decoding hex
48/// escapes in the UDP request/expect payloads, etc.) before handing it to the
49/// proxy. The proxy itself never depends on `zlayer-types`.
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum StreamHealthProbe {
52    /// TCP connect probe — a successful `connect()` marks the backend healthy.
53    TcpConnect,
54    /// UDP probe — send `request`, mark healthy iff a reply arrives. When
55    /// `expect` is `Some`, the reply must additionally contain `expect` as a
56    /// (byte) substring.
57    UdpProbe {
58        /// Raw request payload to send to the backend.
59        request: Vec<u8>,
60        /// Optional expected substring in the reply.
61        expect: Option<Vec<u8>>,
62    },
63}
64
65/// Runtime configuration for a single L4 stream listener.
66///
67/// This is the proxy-local mirror of the `StreamEndpointConfig` spec type
68/// (declared in `zlayer-types`). Keeping a separate type here lets the proxy
69/// crate stay a leaf (no dependency on `zlayer-types`); the agent translates
70/// the spec type into this one when constructing listeners.
71#[derive(Debug, Clone, Default)]
72pub struct StreamProxyConfig {
73    /// Terminate TLS at the proxy (TCP only). When set, the listener performs
74    /// the TLS handshake using the shared SNI cert resolver and relays the
75    /// decrypted plaintext to the backend.
76    pub tls: bool,
77
78    /// Prepend a PROXY protocol v2 header to the upstream connection (TCP
79    /// only) so the backend can recover the real client address.
80    pub proxy_protocol: bool,
81
82    /// Per-listener session timeout override (UDP only).
83    pub session_timeout: Option<Duration>,
84
85    /// Optional decoded health probe applied to this service's backends.
86    pub health_check: Option<StreamHealthProbe>,
87}
88
89/// Serde helper for optional Duration serialization
90mod optional_duration_serde {
91    use serde::{Deserialize, Deserializer, Serialize, Serializer};
92    use std::time::Duration;
93
94    #[allow(clippy::ref_option)]
95    pub fn serialize<S>(value: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
96    where
97        S: Serializer,
98    {
99        match value {
100            Some(d) => d.as_secs().serialize(serializer),
101            None => serializer.serialize_none(),
102        }
103    }
104
105    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
106    where
107        D: Deserializer<'de>,
108    {
109        let opt: Option<u64> = Option::deserialize(deserializer)?;
110        Ok(opt.map(Duration::from_secs))
111    }
112}