Skip to main content

wireguard_control/backends/
kernel.rs

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 out non-link messages
174        .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        // // cleanup: clear out any empty peer lists.
271        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    /// Push a device attribute which will be optimally packed into 1 or more netlink messages
288    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 the NLA *still* doesn't fit...
297        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    /// A helper function to assist in breaking up large peer lists across multiple netlink messages
309    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 the peer *still* doesn't fit...
331        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}