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