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}