wg_config/
wg_peer.rs

1use std::{collections::HashMap, fmt::Debug, net::SocketAddr};
2
3use ipnetwork::IpNetwork;
4
5use crate::{WgConfError, WgKey};
6
7/// Peer tag
8pub const PEER_TAG: &'static str = "[Peer]";
9
10// Fields
11const PUBLIC_KEY: &'static str = "PublicKey";
12const ALLOWED_IPS: &'static str = "AllowedIPs";
13const ENDPOINT: &'static str = "Endpoint";
14const PRESHARED_KEY: &'static str = "PresharedKey";
15const PERSISTENT_KEEPALIVE: &'static str = "PersistentKeepalive";
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum SocketAddrExt {
19    Socket(SocketAddr),
20    Domain(String)    
21}
22
23impl std::str::FromStr for SocketAddrExt {
24    type Err = ();
25
26    fn from_str(input: &str) -> Result<Self, Self::Err> {
27        if let Ok(ip) = input.parse::<SocketAddr>() {
28            return Ok(Self::Socket(ip));
29        }
30        Ok(Self::Domain(input.to_string()))
31    }
32}
33
34impl ToString for SocketAddrExt {
35    fn to_string(&self) -> String {
36        match self {
37            SocketAddrExt::Socket(socket_addr) => socket_addr.to_string(),
38            SocketAddrExt::Domain(domain_name) => domain_name.clone(),
39        }
40    }
41}
42
43/// Represents WG \[Peer\] section
44#[derive(Clone, PartialEq, Eq)]
45pub struct WgPeer {
46    pub(crate) public_key: WgKey,
47    pub(crate) allowed_ips: Vec<IpNetwork>,
48    pub(crate) endpoint: Option<SocketAddrExt>,
49    pub(crate) preshared_key: Option<WgKey>,
50    pub(crate) persistent_keepalive: Option<u16>,
51}
52
53impl Debug for WgPeer {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        f.debug_struct("WgPeer")
56            .field("public_key", &self.public_key)
57            .field("allowed_ips", &self.allowed_ips)
58            .field("endpoint", &self.endpoint)
59            .field("preshared_key", &"***")
60            .field("persistent_keepalive", &self.persistent_keepalive)
61            .finish()
62    }
63}
64
65impl ToString for WgPeer {
66    fn to_string(&self) -> String {
67        let mut allowed_ips_raw = String::new();
68        for (i, ip) in self.allowed_ips.iter().enumerate() {
69            allowed_ips_raw += &ip.to_string();
70            if i != self.allowed_ips.len() - 1 {
71                allowed_ips_raw += ", ";
72            }
73        }
74
75        let endpoint = match &self.endpoint {
76            Some(val) => format!("\n{} = {}", ENDPOINT, val.to_string()),
77            None => "".to_string(),
78        };
79
80        let preshared_key = match &self.preshared_key {
81            Some(val) => format!("\n{} = {}", PRESHARED_KEY, val.to_string()),
82            None => "".to_string(),
83        };
84
85        let keepalive = match self.persistent_keepalive {
86            Some(val) => format!("\n{} = {}", PERSISTENT_KEEPALIVE, &val),
87            None => "".to_string(),
88        };
89
90        format!(
91            "{}
92{} = {}
93{} = {}{}{}{}
94",
95            PEER_TAG,
96            PUBLIC_KEY,
97            self.public_key.to_string(),
98            ALLOWED_IPS,
99            allowed_ips_raw,
100            endpoint,
101            preshared_key,
102            keepalive
103        )
104    }
105}
106
107impl WgPeer {
108    /// Creates new [`WgPeer`]
109    pub fn new(
110        public_key: WgKey,
111        allowed_ips: Vec<IpNetwork>,
112        endpoint: Option<SocketAddrExt>,
113        preshared_key: Option<WgKey>,
114        persistent_keepalive: Option<u16>,
115    ) -> WgPeer {
116        WgPeer {
117            public_key,
118            allowed_ips,
119            endpoint,
120            preshared_key,
121            persistent_keepalive,
122        }
123    }
124
125    pub fn from_raw_values(
126        public_key: String,
127        allowed_ips: Vec<String>,
128        endpoint: Option<String>,
129        preshared_key: Option<String>,
130        persistent_keepalive: Option<String>,
131    ) -> Result<WgPeer, WgConfError> {
132        let public_key: WgKey = public_key.parse()?;
133
134        let allowed_ips: Result<Vec<IpNetwork>, WgConfError> = allowed_ips
135            .iter()
136            .map(|ip| {
137                ip.parse::<IpNetwork>().map_err(|_| {
138                    WgConfError::ValidationFailed(
139                        "allowed IPs must be addresses with mask (e.g. 10.0.0.1/8)".to_string(),
140                    )
141                })
142            })
143            .collect();
144        let allowed_ips = allowed_ips?;
145
146        let endpoint: Option<SocketAddrExt> = endpoint
147            .map(|endpoint| {
148                endpoint.parse().map_err(|_| {
149                    WgConfError::ValidationFailed("invalid endpoint raw value".to_string())
150                })
151            })
152            .transpose()?;
153
154        let preshared_key: Option<WgKey> = preshared_key
155            .map(|key| {
156                key.parse().map_err(|_| {
157                    WgConfError::ValidationFailed("invalid preshared key raw value".to_string())
158                })
159            })
160            .transpose()?;
161
162        let persistent_keepalive: Option<u16> = persistent_keepalive
163            .map(|p| {
164                p.parse().map_err(|_| {
165                    WgConfError::ValidationFailed(
166                        "invalid persistent keepalive raw value".to_string(),
167                    )
168                })
169            })
170            .transpose()?;
171
172        Ok(WgPeer::new(
173            public_key,
174            allowed_ips,
175            endpoint,
176            preshared_key,
177            persistent_keepalive,
178        ))
179    }
180
181    // getters
182    pub fn public_key(&self) -> &WgKey {
183        &self.public_key
184    }
185    pub fn allowed_ips(&self) -> &[IpNetwork] {
186        &self.allowed_ips
187    }
188    pub fn endpoint(&self) -> Option<&SocketAddrExt> {
189        self.endpoint.as_ref()
190    }
191    pub fn preshared_key(&self) -> Option<&WgKey> {
192        self.preshared_key.as_ref()
193    }
194    pub fn persistent_keepalive(&self) -> Option<u16> {
195        self.persistent_keepalive
196    }
197
198    pub(crate) fn from_raw_key_values(
199        raw_key_values: HashMap<String, String>,
200    ) -> Result<WgPeer, WgConfError> {
201        let mut public_key = String::new();
202        let mut allowed_ips = Vec::<String>::new();
203        let mut endpoint: Option<String> = None;
204        let mut preshared_key: Option<String> = None;
205        let mut persistent_keepalive: Option<String> = None;
206
207        for (k, v) in raw_key_values {
208            match k {
209                _ if k == PUBLIC_KEY => public_key = v,
210                _ if k == ALLOWED_IPS => {
211                    let ips: Vec<&str> = v.split(", ").collect();
212                    for ip in ips {
213                        allowed_ips.push(ip.to_string());
214                    }
215                }
216                _ if k == ENDPOINT => endpoint = Some(v),
217                _ if k == PRESHARED_KEY => preshared_key = Some(v),
218                _ if k == PERSISTENT_KEEPALIVE => persistent_keepalive = Some(v),
219                _ => continue,
220            }
221        }
222
223        WgPeer::from_raw_values(
224            public_key,
225            allowed_ips,
226            endpoint,
227            preshared_key,
228            persistent_keepalive,
229        )
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn wg_peer_0_to_string_0_all_fields() {
239        // Arrange
240        let peer = WgPeer::new(
241            "6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8="
242                .parse()
243                .unwrap(),
244            vec![
245                "10.0.0.1/32".parse().unwrap(),
246                "10.0.0.2/32".parse().unwrap(),
247            ],
248            Some("127.0.0.2:8080".parse().unwrap()),
249            Some(
250                "6FyM4Sq5zanp+9UOXIygLJQBYvlLsfF5lYcrSoa3CX8="
251                    .parse()
252                    .unwrap(),
253            ),
254            Some(25),
255        );
256
257        // Act
258        let peer_raw = peer.to_string();
259
260        // Assert
261        assert_eq!(
262            "[Peer]
263PublicKey = 6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8=
264AllowedIPs = 10.0.0.1/32, 10.0.0.2/32
265Endpoint = 127.0.0.2:8080
266PresharedKey = 6FyM4Sq5zanp+9UOXIygLJQBYvlLsfF5lYcrSoa3CX8=
267PersistentKeepalive = 25
268",
269            peer_raw
270        );
271    }
272
273    #[test]
274    fn wg_peer_0_to_string_0_single_ip() {
275        // Arrange
276        let peer = WgPeer::new(
277            "6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8="
278                .parse()
279                .unwrap(),
280            vec!["10.0.0.1/32".parse().unwrap()],
281            Some("127.0.0.2:8080".parse().unwrap()),
282            Some(
283                "6FyM4Sq5zanp+9UOXIygLJQBYvlLsfF5lYcrSoa3CX8="
284                    .parse()
285                    .unwrap(),
286            ),
287            Some(25),
288        );
289
290        // Act
291        let peer_raw = peer.to_string();
292
293        // Assert
294        assert_eq!(
295            "[Peer]
296PublicKey = 6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8=
297AllowedIPs = 10.0.0.1/32
298Endpoint = 127.0.0.2:8080
299PresharedKey = 6FyM4Sq5zanp+9UOXIygLJQBYvlLsfF5lYcrSoa3CX8=
300PersistentKeepalive = 25
301",
302            peer_raw
303        );
304    }
305
306    #[test]
307    fn wg_peer_0_to_string_0_empty_optionals() {
308        // Arrange
309        let peer = WgPeer::new(
310            "6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8="
311                .parse()
312                .unwrap(),
313            vec![
314                "10.0.0.1/32".parse().unwrap(),
315                "10.0.0.2/32".parse().unwrap(),
316            ],
317            None,
318            None,
319            None,
320        );
321
322        // Act
323        let peer_raw = peer.to_string();
324
325        // Assert
326        assert_eq!(
327            "[Peer]
328PublicKey = 6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8=
329AllowedIPs = 10.0.0.1/32, 10.0.0.2/32
330",
331            peer_raw
332        );
333    }
334}