Skip to main content

zlayer_overlay/
config.rs

1//! Overlay network configuration
2
3#[cfg(feature = "nat")]
4use crate::nat::NatConfig;
5use serde::{Deserialize, Serialize};
6use std::net::{IpAddr, Ipv4Addr, SocketAddr};
7use std::time::Duration;
8
9/// Overlay network configuration
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct OverlayConfig {
12    /// Local overlay endpoint (`WireGuard` protocol)
13    pub local_endpoint: SocketAddr,
14
15    /// Private key (x25519)
16    pub private_key: String,
17
18    /// Public key (derived from private key)
19    #[serde(default = "OverlayConfig::default_public_key")]
20    pub public_key: String,
21
22    /// Overlay network CIDR (supports both IPv4 e.g. "10.0.0.0/8" and IPv6 e.g. "`fd00::/48`")
23    ///
24    /// Historically stores the per-node slice / host IP (e.g. `10.200.0.0/28`
25    /// or `10.200.0.1/32`) that the local TUN/Wintun adapter is assigned.
26    /// It is *not* the full cluster CIDR — use [`Self::cluster_cidr`] for that.
27    #[serde(default = "OverlayConfig::default_cidr")]
28    pub overlay_cidr: String,
29
30    /// Full cluster CIDR (e.g. `10.200.0.0/16`).
31    ///
32    /// Used on Windows to install a catch-all host route pointing the
33    /// entire cluster range at the Wintun adapter so traffic to remote-node
34    /// container IPs flows through the overlay (HCN auto-installs the more
35    /// specific local /28 → vSwitch route, and longest-prefix-match routes
36    /// local traffic to the vSwitch). `None` on pre-cluster-CIDR configs;
37    /// callers should fall back to skipping the route install in that case.
38    #[serde(default)]
39    pub cluster_cidr: Option<String>,
40
41    /// Peer discovery interval
42    #[serde(default = "OverlayConfig::default_discovery")]
43    pub peer_discovery_interval: Duration,
44
45    /// NAT traversal configuration (requires "nat" feature)
46    #[cfg(feature = "nat")]
47    #[serde(default)]
48    pub nat: NatConfig,
49}
50
51impl OverlayConfig {
52    fn default_public_key() -> String {
53        String::new()
54    }
55
56    fn default_cidr() -> String {
57        "10.0.0.0/8".to_string()
58    }
59
60    fn default_discovery() -> Duration {
61        Duration::from_secs(30)
62    }
63}
64
65impl Default for OverlayConfig {
66    fn default() -> Self {
67        Self {
68            local_endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 51820),
69            private_key: String::new(),
70            public_key: String::new(),
71            overlay_cidr: "10.0.0.0/8".to_string(),
72            cluster_cidr: None,
73            peer_discovery_interval: Duration::from_secs(30),
74            #[cfg(feature = "nat")]
75            nat: NatConfig::default(),
76        }
77    }
78}
79
80/// Peer information
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
82pub struct PeerInfo {
83    /// Peer public key
84    pub public_key: String,
85
86    /// Endpoint address
87    pub endpoint: SocketAddr,
88
89    /// Allowed IPs
90    pub allowed_ips: String,
91
92    /// Persistent keepalive interval
93    pub persistent_keepalive_interval: Duration,
94}
95
96impl PeerInfo {
97    /// Create a new peer info
98    #[must_use]
99    pub fn new(
100        public_key: String,
101        endpoint: SocketAddr,
102        allowed_ips: &str,
103        persistent_keepalive_interval: Duration,
104    ) -> Self {
105        Self {
106            public_key,
107            endpoint,
108            allowed_ips: allowed_ips.to_string(),
109            persistent_keepalive_interval,
110        }
111    }
112
113    /// Create a peer config block (`WireGuard` protocol format)
114    #[must_use]
115    pub fn to_peer_config(&self) -> String {
116        format!(
117            "[Peer]\n\
118             PublicKey = {}\n\
119             Endpoint = {}\n\
120             AllowedIPs = {}\n\
121             PersistentKeepalive = {}\n",
122            self.public_key,
123            self.endpoint,
124            self.allowed_ips,
125            self.persistent_keepalive_interval.as_secs()
126        )
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_peer_info_to_peer_config() {
136        let peer = PeerInfo::new(
137            "public_key_here".to_string(),
138            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 51820),
139            "10.0.0.2/32",
140            Duration::from_secs(25),
141        );
142
143        let config = peer.to_peer_config();
144        assert!(config.contains("PublicKey = public_key_here"));
145        assert!(config.contains("Endpoint = 192.168.1.1:51820"));
146    }
147
148    #[test]
149    fn test_overlay_config_default() {
150        let config = OverlayConfig::default();
151        assert_eq!(config.local_endpoint.port(), 51820);
152        assert_eq!(config.overlay_cidr, "10.0.0.0/8");
153    }
154
155    #[test]
156    fn test_peer_info_to_peer_config_v6() {
157        use std::net::Ipv6Addr;
158
159        let peer = PeerInfo::new(
160            "public_key_here".to_string(),
161            SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 51820),
162            "fd00::2/128",
163            Duration::from_secs(25),
164        );
165
166        let config = peer.to_peer_config();
167        assert!(config.contains("PublicKey = public_key_here"));
168        assert!(config.contains("Endpoint = [::1]:51820"));
169        assert!(config.contains("AllowedIPs = fd00::2/128"));
170    }
171
172    #[test]
173    fn test_overlay_config_accepts_ipv6_cidr() {
174        let config = OverlayConfig {
175            overlay_cidr: "fd00:200::/48".to_string(),
176            ..OverlayConfig::default()
177        };
178        assert_eq!(config.overlay_cidr, "fd00:200::/48");
179    }
180}