1use crate::{
2 device::AllowedIp, Backend, Device, DeviceUpdate, InterfaceName, Key, PeerConfig,
3 PeerConfigBuilder, PeerInfo, PeerStats,
4};
5use netlink_packet_core::{
6 NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL, NLM_F_REQUEST,
7};
8use netlink_packet_generic::GenlMessage;
9use netlink_packet_route::{
10 link::{self, InfoKind, LinkInfo, LinkMessage},
11 RouteNetlinkMessage,
12};
13use netlink_packet_utils::traits::Emitable;
14use netlink_packet_wireguard::{
15 self,
16 constants::{
17 AF_INET, AF_INET6, WGDEVICE_F_REPLACE_PEERS, WGPEER_F_REMOVE_ME,
18 WGPEER_F_REPLACE_ALLOWEDIPS,
19 },
20 nlas::{WgAllowedIp, WgAllowedIpAttrs, WgDeviceAttrs, WgPeer, WgPeerAttrs},
21 Wireguard, WireguardCmd,
22};
23use netlink_request::{max_genl_payload_length, netlink_request_genl, netlink_request_rtnl};
24
25use std::{convert::TryFrom, io, time::UNIX_EPOCH};
26
27macro_rules! get_nla_value {
28 ($nlas:expr, $e:ident, $v:ident) => {
29 $nlas.iter().find_map(|attr| match attr {
30 $e::$v(value) => Some(value),
31 _ => None,
32 })
33 };
34}
35
36impl TryFrom<WgAllowedIp> for AllowedIp {
37 type Error = io::Error;
38
39 fn try_from(attrs: WgAllowedIp) -> Result<Self, Self::Error> {
40 let address = *get_nla_value!(attrs, WgAllowedIpAttrs, IpAddr)
41 .ok_or_else(|| io::ErrorKind::NotFound)?;
42 let cidr = *get_nla_value!(attrs, WgAllowedIpAttrs, Cidr)
43 .ok_or_else(|| io::ErrorKind::NotFound)?;
44 Ok(AllowedIp { address, cidr })
45 }
46}
47
48impl AllowedIp {
49 fn to_nla(&self) -> WgAllowedIp {
50 WgAllowedIp(vec![
51 WgAllowedIpAttrs::Family(if self.address.is_ipv4() {
52 AF_INET
53 } else {
54 AF_INET6
55 }),
56 WgAllowedIpAttrs::IpAddr(self.address),
57 WgAllowedIpAttrs::Cidr(self.cidr),
58 ])
59 }
60}
61
62impl PeerConfigBuilder {
63 fn to_nla(&self) -> WgPeer {
64 let mut attrs = vec![WgPeerAttrs::PublicKey(self.public_key.0)];
65 let mut flags = 0u32;
66 if let Some(endpoint) = self.endpoint {
67 attrs.push(WgPeerAttrs::Endpoint(endpoint));
68 }
69 if let Some(ref key) = self.preshared_key {
70 attrs.push(WgPeerAttrs::PresharedKey(key.0));
71 }
72 if let Some(i) = self.persistent_keepalive_interval {
73 attrs.push(WgPeerAttrs::PersistentKeepalive(i));
74 }
75 let allowed_ips: Vec<_> = self.allowed_ips.iter().map(AllowedIp::to_nla).collect();
76 attrs.push(WgPeerAttrs::AllowedIps(allowed_ips));
77 if self.remove_me {
78 flags |= WGPEER_F_REMOVE_ME;
79 }
80 if self.replace_allowed_ips {
81 flags |= WGPEER_F_REPLACE_ALLOWEDIPS;
82 }
83 if flags != 0 {
84 attrs.push(WgPeerAttrs::Flags(flags));
85 }
86 WgPeer(attrs)
87 }
88}
89
90impl TryFrom<WgPeer> for PeerInfo {
91 type Error = io::Error;
92
93 fn try_from(attrs: WgPeer) -> Result<Self, Self::Error> {
94 let public_key = get_nla_value!(attrs, WgPeerAttrs, PublicKey)
95 .map(|key| Key(*key))
96 .ok_or(io::ErrorKind::NotFound)?;
97 let preshared_key = get_nla_value!(attrs, WgPeerAttrs, PresharedKey).map(|key| Key(*key));
98 let endpoint = get_nla_value!(attrs, WgPeerAttrs, Endpoint).cloned();
99 let persistent_keepalive_interval =
100 get_nla_value!(attrs, WgPeerAttrs, PersistentKeepalive).cloned();
101 let allowed_ips = get_nla_value!(attrs, WgPeerAttrs, AllowedIps)
102 .cloned()
103 .unwrap_or_default()
104 .into_iter()
105 .map(AllowedIp::try_from)
106 .collect::<Result<Vec<_>, _>>()?;
107 let last_handshake_time = get_nla_value!(attrs, WgPeerAttrs, LastHandshake)
108 .filter(|&&time| time != UNIX_EPOCH)
109 .cloned();
110 let rx_bytes = get_nla_value!(attrs, WgPeerAttrs, RxBytes)
111 .cloned()
112 .unwrap_or_default();
113 let tx_bytes = get_nla_value!(attrs, WgPeerAttrs, TxBytes)
114 .cloned()
115 .unwrap_or_default();
116 Ok(PeerInfo {
117 config: PeerConfig {
118 public_key,
119 preshared_key,
120 endpoint,
121 persistent_keepalive_interval,
122 allowed_ips,
123 },
124 stats: PeerStats {
125 last_handshake_time,
126 rx_bytes,
127 tx_bytes,
128 },
129 })
130 }
131}
132
133impl<'a> TryFrom<&'a [WgDeviceAttrs]> for Device {
134 type Error = io::Error;
135
136 fn try_from(nlas: &'a [WgDeviceAttrs]) -> Result<Self, Self::Error> {
137 let name = get_nla_value!(nlas, WgDeviceAttrs, IfName)
138 .ok_or_else(|| io::ErrorKind::NotFound)?
139 .parse()?;
140 let public_key = get_nla_value!(nlas, WgDeviceAttrs, PublicKey).map(|key| Key(*key));
141 let private_key = get_nla_value!(nlas, WgDeviceAttrs, PrivateKey).map(|key| Key(*key));
142 let listen_port = get_nla_value!(nlas, WgDeviceAttrs, ListenPort).cloned();
143 let fwmark = get_nla_value!(nlas, WgDeviceAttrs, Fwmark).cloned();
144 let peers = nlas
145 .iter()
146 .filter_map(|nla| match nla {
147 WgDeviceAttrs::Peers(peers) => Some(peers.clone()),
148 _ => None,
149 })
150 .flatten()
151 .map(PeerInfo::try_from)
152 .collect::<Result<Vec<_>, _>>()?;
153 Ok(Device {
154 name,
155 public_key,
156 private_key,
157 listen_port,
158 fwmark,
159 peers,
160 linked_name: None,
161 backend: Backend::Kernel,
162 })
163 }
164}
165
166pub fn enumerate() -> Result<Vec<InterfaceName>, io::Error> {
167 let link_responses = netlink_request_rtnl(
168 RouteNetlinkMessage::GetLink(LinkMessage::default()),
169 Some(NLM_F_DUMP | NLM_F_REQUEST),
170 )?;
171 let links = link_responses
172 .into_iter()
173 .filter_map(|response| match response {
175 NetlinkMessage {
176 payload: NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewLink(link)),
177 ..
178 } => Some(link),
179 _ => None,
180 })
181 .filter(|link| {
182 for nla in link.attributes.iter() {
183 if let link::LinkAttribute::LinkInfo(infos) = nla {
184 return infos.iter().any(|info| info == &LinkInfo::Kind(InfoKind::Wireguard))
185 }
186 }
187 false
188 })
189 .filter_map(|link| link.attributes.iter().find_map(|nla| match nla {
190 link::LinkAttribute::IfName(name) => Some(name.clone()),
191 _ => None,
192 }))
193 .filter_map(|name| name.parse().ok())
194 .collect::<Vec<_>>();
195
196 Ok(links)
197}
198
199fn add_del(iface: &InterfaceName, add: bool) -> io::Result<()> {
200 let mut message = LinkMessage::default();
201 message.attributes.push(link::LinkAttribute::IfName(
202 iface.as_str_lossy().to_string(),
203 ));
204 message
205 .attributes
206 .push(link::LinkAttribute::LinkInfo(vec![LinkInfo::Kind(
207 link::InfoKind::Wireguard,
208 )]));
209 let extra_flags = if add { NLM_F_CREATE | NLM_F_EXCL } else { 0 };
210 let rtnl_message = if add {
211 RouteNetlinkMessage::NewLink(message)
212 } else {
213 RouteNetlinkMessage::DelLink(message)
214 };
215 match netlink_request_rtnl(rtnl_message, Some(NLM_F_REQUEST | NLM_F_ACK | extra_flags)) {
216 Err(e) if e.kind() != io::ErrorKind::AlreadyExists => Err(e),
217 _ => Ok(()),
218 }
219}
220
221pub fn apply(builder: &DeviceUpdate, iface: &InterfaceName) -> io::Result<()> {
222 add_del(iface, true)?;
223 let mut payload = ApplyPayload::new(iface);
224 if let Some(Key(k)) = builder.private_key {
225 payload.push(WgDeviceAttrs::PrivateKey(k))?;
226 }
227 if let Some(f) = builder.fwmark {
228 payload.push(WgDeviceAttrs::Fwmark(f))?;
229 }
230 if let Some(f) = builder.listen_port {
231 payload.push(WgDeviceAttrs::ListenPort(f))?;
232 }
233 if builder.replace_peers {
234 payload.push(WgDeviceAttrs::Flags(WGDEVICE_F_REPLACE_PEERS))?;
235 }
236
237 builder
238 .peers
239 .iter()
240 .map(|peer| payload.push_peer(peer.to_nla()))
241 .collect::<Result<Vec<_>, _>>()?;
242
243 for message in payload.finish() {
244 netlink_request_genl(message, Some(NLM_F_REQUEST | NLM_F_ACK))?;
245 }
246 Ok(())
247}
248
249struct ApplyPayload {
250 iface: String,
251 nlas: Vec<WgDeviceAttrs>,
252 current_buffer_len: usize,
253 queue: Vec<GenlMessage<Wireguard>>,
254}
255
256impl ApplyPayload {
257 fn new(iface: &InterfaceName) -> Self {
258 let iface_str = iface.as_str_lossy().to_string();
259 let nlas = vec![WgDeviceAttrs::IfName(iface_str.clone())];
260 let current_buffer_len = nlas.as_slice().buffer_len();
261 Self {
262 iface: iface_str,
263 nlas,
264 queue: vec![],
265 current_buffer_len,
266 }
267 }
268
269 fn flush_nlas(&mut self) {
270 self.nlas
272 .retain(|nla| !matches!(nla, WgDeviceAttrs::Peers(peers) if peers.is_empty()));
273
274 let name = WgDeviceAttrs::IfName(self.iface.clone());
275 let template = vec![name];
276
277 if !self.nlas.is_empty() && self.nlas != template {
278 self.current_buffer_len = template.as_slice().buffer_len();
279 let message = GenlMessage::from_payload(Wireguard {
280 cmd: WireguardCmd::SetDevice,
281 nlas: std::mem::replace(&mut self.nlas, template),
282 });
283 self.queue.push(message);
284 }
285 }
286
287 pub fn push(&mut self, nla: WgDeviceAttrs) -> io::Result<()> {
289 let max_payload_len = max_genl_payload_length();
290
291 let nla_buffer_len = nla.buffer_len();
292 if (self.current_buffer_len + nla_buffer_len) > max_payload_len {
293 self.flush_nlas();
294 }
295
296 if (self.current_buffer_len + nla_buffer_len) > max_payload_len {
298 return Err(io::Error::new(
299 io::ErrorKind::InvalidInput,
300 format!("encoded NLA ({nla_buffer_len} bytes) is too large: {nla:?}"),
301 ));
302 }
303 self.nlas.push(nla);
304 self.current_buffer_len += nla_buffer_len;
305 Ok(())
306 }
307
308 pub fn push_peer(&mut self, peer: WgPeer) -> io::Result<()> {
310 const EMPTY_PEERS: WgDeviceAttrs = WgDeviceAttrs::Peers(vec![]);
311 let max_payload_len = max_genl_payload_length();
312 let mut needs_peer_nla = !self
313 .nlas
314 .iter()
315 .any(|nla| matches!(nla, WgDeviceAttrs::Peers(_)));
316 let peer_buffer_len = peer.buffer_len();
317 let mut additional_buffer_len = peer_buffer_len;
318 if needs_peer_nla {
319 additional_buffer_len += EMPTY_PEERS.buffer_len();
320 }
321 if (self.current_buffer_len + additional_buffer_len) > max_payload_len {
322 self.flush_nlas();
323 needs_peer_nla = true;
324 }
325
326 if needs_peer_nla {
327 self.push(EMPTY_PEERS)?;
328 }
329
330 if (self.current_buffer_len + peer_buffer_len) > max_payload_len {
332 return Err(io::Error::new(
333 io::ErrorKind::InvalidInput,
334 format!("encoded peer ({peer_buffer_len} bytes) is too large: {peer:?}"),
335 ));
336 }
337
338 let peers_nla = self
339 .nlas
340 .iter_mut()
341 .find_map(|nla| match nla {
342 WgDeviceAttrs::Peers(peers) => Some(peers),
343 _ => None,
344 })
345 .expect("WgDeviceAttrs::Peers missing from NLAs when it should exist.");
346
347 peers_nla.push(peer);
348 self.current_buffer_len += peer_buffer_len;
349
350 Ok(())
351 }
352
353 pub fn finish(mut self) -> Vec<GenlMessage<Wireguard>> {
354 self.flush_nlas();
355 self.queue
356 }
357}
358
359pub fn get_by_name(name: &InterfaceName) -> Result<Device, io::Error> {
360 let genlmsg: GenlMessage<Wireguard> = GenlMessage::from_payload(Wireguard {
361 cmd: WireguardCmd::GetDevice,
362 nlas: vec![WgDeviceAttrs::IfName(name.as_str_lossy().to_string())],
363 });
364 let responses = netlink_request_genl(genlmsg, Some(NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK))?;
365 log::trace!(
366 "get_by_name: got {} response message(s) from netlink request",
367 responses.len()
368 );
369
370 let nlas = responses.into_iter().try_fold(vec![], |mut nlas, nlmsg| {
371 let mut message = match nlmsg {
372 NetlinkMessage {
373 payload: NetlinkPayload::InnerMessage(message),
374 ..
375 } => message,
376 _ => {
377 return Err(io::Error::new(
378 io::ErrorKind::InvalidData,
379 format!("unexpected netlink payload: {nlmsg:?}"),
380 ))
381 },
382 };
383 nlas.append(&mut message.payload.nlas);
384 Ok(nlas)
385 })?;
386 let device = Device::try_from(&nlas[..])?;
387 log::trace!(
388 "get_by_name: parsed wireguard device {} with {} peer(s)",
389 device.name,
390 device.peers.len(),
391 );
392 Ok(device)
393}
394
395pub fn delete_interface(iface: &InterfaceName) -> io::Result<()> {
396 add_del(iface, false)
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402 use netlink_packet_wireguard::nlas::WgAllowedIp;
403 use netlink_request::max_netlink_buffer_length;
404 use std::str::FromStr;
405
406 #[test]
407 fn test_simple_payload() {
408 let mut payload = ApplyPayload::new(&InterfaceName::from_str("wg0").unwrap());
409 payload.push(WgDeviceAttrs::PrivateKey([1u8; 32])).unwrap();
410 payload.push(WgDeviceAttrs::Fwmark(111)).unwrap();
411 payload.push(WgDeviceAttrs::ListenPort(12345)).unwrap();
412 payload
413 .push(WgDeviceAttrs::Flags(WGDEVICE_F_REPLACE_PEERS))
414 .unwrap();
415 payload
416 .push_peer(WgPeer(vec![
417 WgPeerAttrs::PublicKey([2u8; 32]),
418 WgPeerAttrs::PersistentKeepalive(25),
419 WgPeerAttrs::Endpoint("1.1.1.1:51820".parse().unwrap()),
420 WgPeerAttrs::Flags(WGPEER_F_REPLACE_ALLOWEDIPS),
421 WgPeerAttrs::AllowedIps(vec![WgAllowedIp(vec![
422 WgAllowedIpAttrs::Family(AF_INET),
423 WgAllowedIpAttrs::IpAddr([10, 1, 1, 1].into()),
424 WgAllowedIpAttrs::Cidr(24),
425 ])]),
426 ]))
427 .unwrap();
428 assert_eq!(payload.finish().len(), 1);
429 }
430
431 #[test]
432 fn test_massive_payload() {
433 let mut payload = ApplyPayload::new(&InterfaceName::from_str("wg0").unwrap());
434 payload.push(WgDeviceAttrs::PrivateKey([1u8; 32])).unwrap();
435 payload.push(WgDeviceAttrs::Fwmark(111)).unwrap();
436 payload.push(WgDeviceAttrs::ListenPort(12345)).unwrap();
437 payload
438 .push(WgDeviceAttrs::Flags(WGDEVICE_F_REPLACE_PEERS))
439 .unwrap();
440
441 for i in 0..10_000 {
442 payload
443 .push_peer(WgPeer(vec![
444 WgPeerAttrs::PublicKey([2u8; 32]),
445 WgPeerAttrs::PersistentKeepalive(25),
446 WgPeerAttrs::Endpoint("1.1.1.1:51820".parse().unwrap()),
447 WgPeerAttrs::Flags(WGPEER_F_REPLACE_ALLOWEDIPS),
448 WgPeerAttrs::AllowedIps(vec![WgAllowedIp(vec![
449 WgAllowedIpAttrs::Family(AF_INET),
450 WgAllowedIpAttrs::IpAddr([10, 1, 1, 1].into()),
451 WgAllowedIpAttrs::Cidr(24),
452 ])]),
453 WgPeerAttrs::Unspec(vec![1u8; (i % 256) as usize]),
454 ]))
455 .unwrap();
456 }
457
458 let messages = payload.finish();
459 println!("generated {} messages", messages.len());
460 assert!(messages.len() > 1);
461 let max_buffer_len = max_netlink_buffer_length();
462 for message in messages {
463 assert!(NetlinkMessage::from(message).buffer_len() <= max_buffer_len);
464 }
465 }
466}