wireguard_uapi/xplatform/
set.rs

1use crate::xplatform::protocol::SetKey;
2use std::fmt::Display;
3use std::net::IpAddr;
4use std::net::SocketAddr;
5
6/// Documentation of each field comes from:
7/// https://www.wireguard.com/xplatform/#configuration-protocol
8#[derive(Debug, Default, PartialEq, Eq)]
9pub struct Device {
10    /// The value for this key should be a lowercase hex-encoded private key of
11    /// the interface. The value may be an all zero string in the case of a set
12    /// operation, in which case it indicates that the private key should be
13    /// removed.
14    pub private_key: Option<[u8; 32]>,
15
16    /// The value for this is a decimal-string integer corresponding to the
17    /// listening port of the interface.
18    pub listen_port: Option<u16>,
19
20    /// The value for this is a decimal-string integer corresponding to the
21    /// fwmark of the interface. The value may 0 in the case of a set operation,
22    /// in which case it indicates that the fwmark should be removed.
23    pub fwmark: Option<u32>,
24
25    /// This key/value combo is only valid in a set operation, in which case it
26    /// indicates that the subsequent peers (perhaps an empty list) should
27    /// replace any existing peers, rather than append to the existing peer list.
28    pub replace_peers: Option<bool>,
29
30    pub peers: Vec<Peer>,
31}
32
33impl Display for Device {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        if let Some(private_key) = self.private_key {
36            let private_key = hex::encode(private_key);
37            writeln!(f, "{}={}", SetKey::PrivateKey, private_key)?;
38        }
39
40        if let Some(listen_port) = self.listen_port {
41            writeln!(f, "{}={}", SetKey::ListenPort, listen_port)?;
42        }
43
44        if let Some(fwmark) = self.fwmark {
45            writeln!(f, "{}={}", SetKey::Fwmark, fwmark)?;
46        }
47
48        if let Some(replace_peers) = self.replace_peers {
49            writeln!(f, "{}={}", SetKey::ReplacePeers, replace_peers)?;
50        }
51
52        for peer in &self.peers {
53            peer.fmt(f)?;
54        }
55
56        Ok(())
57    }
58}
59
60/// Documentation of each field comes from:
61/// https://www.wireguard.com/xplatform/#configuration-protocol
62#[derive(Clone, Debug, Default, PartialEq, Eq)]
63pub struct Peer {
64    /// The value for this key should be a lowercase hex-encoded public key of a
65    /// new peer entry, which this command adds. The same public key value may
66    /// not repeat during a single message.
67    pub public_key: [u8; 32],
68
69    /// This key/value combo is only valid in a set operation, in which case it
70    /// indicates that the previously added peer entry should be removed from the
71    /// interface.
72    pub remove: Option<bool>,
73
74    /// This key/value combo is only valid in a set operation, in which case it
75    /// causes the operation only occurs if the peer already exists as part of
76    /// the interface.
77    pub update_only: Option<bool>,
78
79    /// The value for this key should be a lowercase hex-encoded preshared-key of
80    /// the previously added peer entry. The value may be an all zero string in
81    /// the case of a set operation, in which case it indicates that the
82    /// preshared-key should be removed.
83    pub preshared_key: Option<[u8; 32]>,
84
85    /// The value for this key is either IP:port for IPv4 or \[IP\]:port for
86    /// IPv6, indicating the endpoint of the previously added peer entry.
87    pub endpoint: Option<SocketAddr>,
88
89    /// The value for this is a decimal-string integer corresponding to the
90    /// persistent keepalive interval of the previously added peer entry. The
91    /// value 0 disables it.
92    pub persistent_keepalive_interval: Option<u16>,
93
94    /// This key/value combo is only valid in a set operation, in which case it
95    /// indicates that the subsequent allowed IPs (perhaps an empty list) should
96    /// replace any existing ones of the previously added peer entry, rather than
97    /// append to the existing allowed IPs list.
98    pub replace_allowed_ips: Option<bool>,
99
100    /// The value for this is IP/cidr, indicating a new added allowed IP entry
101    /// for the previously added peer entry. If an identical value already exists
102    /// as part of a prior peer, the allowed IP entry will be removed from that
103    /// peer and added to this peer.
104    pub allowed_ips: Vec<AllowedIp>,
105}
106
107impl Peer {
108    pub fn from_public_key(public_key: [u8; 32]) -> Self {
109        Self {
110            public_key,
111            remove: None,
112            update_only: None,
113            preshared_key: None,
114            endpoint: None,
115            persistent_keepalive_interval: None,
116            replace_allowed_ips: None,
117            allowed_ips: vec![],
118        }
119    }
120
121    pub fn remove(mut self, remove: bool) -> Self {
122        self.remove = remove.then_some(true);
123        self
124    }
125
126    pub fn update_only(mut self, update_only: bool) -> Self {
127        self.update_only = update_only.then_some(true);
128        self
129    }
130
131    pub fn preshared_key(mut self, preshared_key: [u8; 32]) -> Self {
132        self.preshared_key = Some(preshared_key);
133        self
134    }
135
136    pub fn endpoint(mut self, endpoint: SocketAddr) -> Self {
137        self.endpoint = Some(endpoint);
138        self
139    }
140
141    pub fn persistent_keepalive_interval(mut self, persistent_keepalive_interval: u16) -> Self {
142        self.persistent_keepalive_interval = Some(persistent_keepalive_interval);
143        self
144    }
145
146    pub fn replace_allowed_ips(mut self, replace_allowed_ips: bool) -> Self {
147        self.replace_allowed_ips = replace_allowed_ips.then_some(true);
148        self
149    }
150
151    pub fn allowed_ips(mut self, allowed_ips: Vec<AllowedIp>) -> Self {
152        self.allowed_ips = allowed_ips;
153        self
154    }
155}
156
157impl Display for Peer {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        writeln!(f, "{}={}", SetKey::PublicKey, hex::encode(self.public_key))?;
160
161        if let Some(remove) = self.remove {
162            writeln!(f, "{}={}", SetKey::Remove, remove)?;
163        }
164
165        if let Some(update_only) = self.update_only {
166            writeln!(f, "{}={}", SetKey::UpdateOnly, update_only)?;
167        }
168
169        if let Some(preshared_key) = self.preshared_key {
170            let preshared_key = hex::encode(preshared_key);
171            writeln!(f, "{}={}", SetKey::PresharedKey, preshared_key)?;
172        }
173
174        if let Some(endpoint) = self.endpoint {
175            writeln!(f, "{}={}", SetKey::Endpoint, endpoint)?;
176        }
177
178        if let Some(interval) = self.persistent_keepalive_interval {
179            writeln!(f, "{}={}", SetKey::PersistentKeepaliveInterval, interval)?;
180        }
181
182        if let Some(replace_allowed_ips) = self.replace_allowed_ips {
183            writeln!(f, "{}={}", SetKey::ReplaceAllowedIps, replace_allowed_ips)?;
184        }
185
186        for allowed_ip in &self.allowed_ips {
187            allowed_ip.fmt(f)?;
188        }
189
190        Ok(())
191    }
192}
193
194#[derive(Clone, Debug, PartialEq, Eq)]
195pub struct AllowedIp {
196    pub ipaddr: IpAddr,
197    pub cidr_mask: u8,
198}
199
200impl Display for AllowedIp {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        writeln!(
203            f,
204            "{}={}/{}",
205            SetKey::AllowedIp,
206            self.ipaddr,
207            self.cidr_mask
208        )
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn serialize_website_example() {
218        // Some of the lines have been re-arranged from the website example to
219        // make testing easier.
220        let expected = "\
221            private_key=e84b5a6d2717c1003a13b431570353dbaca9146cf150c5f8575680feba52027a\n\
222            listen_port=12912\n\
223            fwmark=0\n\
224            replace_peers=true\n\
225            public_key=b85996fecc9c7f1fc6d2572a76eda11d59bcd20be8e543b15ce4bd85a8e75a33\n\
226            preshared_key=188515093e952f5f22e865cef3012e72f8b5f0b598ac0309d5dacce3b70fcf52\n\
227            endpoint=[abcd:23::33%2]:51820\n\
228            replace_allowed_ips=true\n\
229            allowed_ip=192.168.4.4/32\n\
230            public_key=58402e695ba1772b1cc9309755f043251ea77fdcf10fbe63989ceb7e19321376\n\
231            endpoint=182.122.22.19:3233\n\
232            persistent_keepalive_interval=111\n\
233            replace_allowed_ips=true\n\
234            allowed_ip=192.168.4.6/32\n\
235            public_key=662e14fd594556f522604703340351258903b64f35553763f19426ab2a515c58\n\
236            endpoint=5.152.198.39:51820\n\
237            replace_allowed_ips=true\n\
238            allowed_ip=192.168.4.10/32\n\
239            allowed_ip=192.168.4.11/32\n\
240            public_key=e818b58db5274087fcc1be5dc728cf53d3b5726b4cef6b9bab8f8f8c2452c25c\n\
241            remove=true\n";
242
243        let set_request = Device {
244            private_key: Some([
245                0xe8, 0x4b, 0x5a, 0x6d, 0x27, 0x17, 0xc1, 0x00, 0x3a, 0x13, 0xb4, 0x31, 0x57, 0x03,
246                0x53, 0xdb, 0xac, 0xa9, 0x14, 0x6c, 0xf1, 0x50, 0xc5, 0xf8, 0x57, 0x56, 0x80, 0xfe,
247                0xba, 0x52, 0x02, 0x7a,
248            ]),
249            listen_port: Some(12912),
250            fwmark: Some(0),
251            replace_peers: Some(true),
252            peers: vec![
253                Peer::from_public_key([
254                    0xb8, 0x59, 0x96, 0xfe, 0xcc, 0x9c, 0x7f, 0x1f, 0xc6, 0xd2, 0x57, 0x2a, 0x76,
255                    0xed, 0xa1, 0x1d, 0x59, 0xbc, 0xd2, 0x0b, 0xe8, 0xe5, 0x43, 0xb1, 0x5c, 0xe4,
256                    0xbd, 0x85, 0xa8, 0xe7, 0x5a, 0x33,
257                ])
258                .preshared_key([
259                    0x18, 0x85, 0x15, 0x09, 0x3e, 0x95, 0x2f, 0x5f, 0x22, 0xe8, 0x65, 0xce, 0xf3,
260                    0x01, 0x2e, 0x72, 0xf8, 0xb5, 0xf0, 0xb5, 0x98, 0xac, 0x03, 0x09, 0xd5, 0xda,
261                    0xcc, 0xe3, 0xb7, 0x0f, 0xcf, 0x52,
262                ])
263                .replace_allowed_ips(true)
264                .allowed_ips(vec![AllowedIp {
265                    ipaddr: "192.168.4.4".parse().unwrap(),
266                    cidr_mask: 32,
267                }])
268                .endpoint("[abcd:23::33%2]:51820".parse().unwrap()),
269                Peer::from_public_key([
270                    0x58, 0x40, 0x2e, 0x69, 0x5b, 0xa1, 0x77, 0x2b, 0x1c, 0xc9, 0x30, 0x97, 0x55,
271                    0xf0, 0x43, 0x25, 0x1e, 0xa7, 0x7f, 0xdc, 0xf1, 0x0f, 0xbe, 0x63, 0x98, 0x9c,
272                    0xeb, 0x7e, 0x19, 0x32, 0x13, 0x76,
273                ])
274                .replace_allowed_ips(true)
275                .allowed_ips(vec![AllowedIp {
276                    ipaddr: "192.168.4.6".parse().unwrap(),
277                    cidr_mask: 32,
278                }])
279                .persistent_keepalive_interval(111)
280                .endpoint("182.122.22.19:3233".parse().unwrap()),
281                Peer::from_public_key([
282                    0x66, 0x2e, 0x14, 0xfd, 0x59, 0x45, 0x56, 0xf5, 0x22, 0x60, 0x47, 0x03, 0x34,
283                    0x03, 0x51, 0x25, 0x89, 0x03, 0xb6, 0x4f, 0x35, 0x55, 0x37, 0x63, 0xf1, 0x94,
284                    0x26, 0xab, 0x2a, 0x51, 0x5c, 0x58,
285                ])
286                .endpoint("5.152.198.39:51820".parse().unwrap())
287                .replace_allowed_ips(true)
288                .allowed_ips(vec![
289                    AllowedIp {
290                        ipaddr: "192.168.4.10".parse().unwrap(),
291                        cidr_mask: 32,
292                    },
293                    AllowedIp {
294                        ipaddr: "192.168.4.11".parse().unwrap(),
295                        cidr_mask: 32,
296                    },
297                ]),
298                Peer::from_public_key([
299                    0xe8, 0x18, 0xb5, 0x8d, 0xb5, 0x27, 0x40, 0x87, 0xfc, 0xc1, 0xbe, 0x5d, 0xc7,
300                    0x28, 0xcf, 0x53, 0xd3, 0xb5, 0x72, 0x6b, 0x4c, 0xef, 0x6b, 0x9b, 0xab, 0x8f,
301                    0x8f, 0x8c, 0x24, 0x52, 0xc2, 0x5c,
302                ])
303                .remove(true),
304            ],
305        };
306        let actual = format!("{set_request}");
307
308        assert_eq!(expected, actual);
309    }
310
311    #[test]
312    fn serialize_update_only() {
313        let expected = [
314            "private_key=e84b5a6d2717c1003a13b431570353dbaca9146cf150c5f8575680feba52027a",
315            "public_key=b85996fecc9c7f1fc6d2572a76eda11d59bcd20be8e543b15ce4bd85a8e75a33",
316            "update_only=true",
317            "endpoint=[abcd:23::33%2]:51820",
318            "replace_allowed_ips=true",
319            "allowed_ip=192.168.4.4/32",
320            "",
321        ]
322        .join("\n");
323
324        let set_request = Device {
325            private_key: Some([
326                0xe8, 0x4b, 0x5a, 0x6d, 0x27, 0x17, 0xc1, 0x00, 0x3a, 0x13, 0xb4, 0x31, 0x57, 0x03,
327                0x53, 0xdb, 0xac, 0xa9, 0x14, 0x6c, 0xf1, 0x50, 0xc5, 0xf8, 0x57, 0x56, 0x80, 0xfe,
328                0xba, 0x52, 0x02, 0x7a,
329            ]),
330            peers: vec![Peer::from_public_key([
331                0xb8, 0x59, 0x96, 0xfe, 0xcc, 0x9c, 0x7f, 0x1f, 0xc6, 0xd2, 0x57, 0x2a, 0x76, 0xed,
332                0xa1, 0x1d, 0x59, 0xbc, 0xd2, 0x0b, 0xe8, 0xe5, 0x43, 0xb1, 0x5c, 0xe4, 0xbd, 0x85,
333                0xa8, 0xe7, 0x5a, 0x33,
334            ])
335            .update_only(true)
336            .replace_allowed_ips(true)
337            .allowed_ips(vec![AllowedIp {
338                ipaddr: "192.168.4.4".parse().unwrap(),
339                cidr_mask: 32,
340            }])
341            .endpoint("[abcd:23::33%2]:51820".parse().unwrap())],
342            ..Default::default()
343        };
344        let actual = format!("{set_request}");
345
346        assert_eq!(expected, actual);
347    }
348
349    // Simple comparisons to make default, partial_eq, and debug derive code covered.
350    #[test]
351    fn cover_derives() {
352        let device1 = Device::default();
353        let device2 = Device::default();
354        assert_eq!(device1, device2);
355        let _ = format!("{device1:?}");
356
357        let peer1 = Peer::from_public_key([
358            0xb8, 0x59, 0x96, 0xfe, 0xcc, 0x9c, 0x7f, 0x1f, 0xc6, 0xd2, 0x57, 0x2a, 0x76, 0xed,
359            0xa1, 0x1d, 0x59, 0xbc, 0xd2, 0x0b, 0xe8, 0xe5, 0x43, 0xb1, 0x5c, 0xe4, 0xbd, 0x85,
360            0xa8, 0xe7, 0x5a, 0x33,
361        ]);
362        let peer2 = Peer::from_public_key([
363            0xb8, 0x59, 0x96, 0xfe, 0xcc, 0x9c, 0x7f, 0x1f, 0xc6, 0xd2, 0x57, 0x2a, 0x76, 0xed,
364            0xa1, 0x1d, 0x59, 0xbc, 0xd2, 0x0b, 0xe8, 0xe5, 0x43, 0xb1, 0x5c, 0xe4, 0xbd, 0x85,
365            0xa8, 0xe7, 0x5a, 0x33,
366        ]);
367        assert_eq!(peer1, peer2);
368        let _ = format!("{peer1:?}");
369
370        let allowed_ip1 = AllowedIp {
371            ipaddr: "::1".parse().unwrap(),
372            cidr_mask: 64,
373        };
374        let allowed_ip2 = AllowedIp {
375            ipaddr: "::1".parse().unwrap(),
376            cidr_mask: 64,
377        };
378        assert_eq!(allowed_ip1, allowed_ip2);
379        let _ = format!("{allowed_ip1:?}");
380    }
381}