neli/
rtnl.rs

1//! This module provides an implementation of routing netlink
2//! structures and the routing attributes that are at the end of
3//! most routing netlink responses.
4//!
5//! # Design decisions
6//!
7//! This module is based very heavily on the information in
8//! `man 7 rtnetlink` so it is mainly a series of structs organized
9//! in a style similar to the rest of the library.
10
11use crate as neli;
12
13use std::io::Cursor;
14
15use crate::{
16    attr::{AttrHandle, AttrHandleMut, Attribute},
17    consts::rtnl::*,
18    err::{DeError, SerError},
19    types::{Buffer, RtBuffer},
20    FromBytes, FromBytesWithInput, Header, Size, ToBytes,
21};
22
23/// Struct representing interface information messages
24#[derive(Debug, Size, ToBytes, FromBytesWithInput, Header)]
25pub struct Ifinfomsg {
26    /// Interface address family
27    pub ifi_family: RtAddrFamily,
28    padding: u8,
29    /// Interface type
30    pub ifi_type: Arphrd,
31    /// Interface index
32    pub ifi_index: libc::c_int,
33    /// Interface flags
34    pub ifi_flags: IffFlags,
35    /// Interface change mask
36    pub ifi_change: IffFlags,
37    /// Payload of [`Rtattr`]s
38    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::UnexpectedEOB)?")]
39    pub rtattrs: RtBuffer<Ifla, Buffer>,
40}
41
42impl Ifinfomsg {
43    /// Create a fully initialized interface info struct
44    pub fn new(
45        ifi_family: RtAddrFamily,
46        ifi_type: Arphrd,
47        ifi_index: libc::c_int,
48        ifi_flags: IffFlags,
49        ifi_change: IffFlags,
50        rtattrs: RtBuffer<Ifla, Buffer>,
51    ) -> Self {
52        Ifinfomsg {
53            ifi_family,
54            padding: 0,
55            ifi_type,
56            ifi_index,
57            ifi_flags,
58            ifi_change,
59            rtattrs,
60        }
61    }
62
63    /// Set the link with the given index up (equivalent to
64    /// `ip link set dev DEV up`)
65    pub fn up(
66        ifi_family: RtAddrFamily,
67        ifi_type: Arphrd,
68        ifi_index: libc::c_int,
69        rtattrs: RtBuffer<Ifla, Buffer>,
70    ) -> Self {
71        Ifinfomsg {
72            ifi_family,
73            padding: 0,
74            ifi_type,
75            ifi_index,
76            ifi_flags: IffFlags::new(&[Iff::Up]),
77            ifi_change: IffFlags::new(&[Iff::Up]),
78            rtattrs,
79        }
80    }
81
82    /// Set the link with the given index down (equivalent to
83    /// `ip link set dev DEV down`)
84    pub fn down(
85        ifi_family: RtAddrFamily,
86        ifi_type: Arphrd,
87        ifi_index: libc::c_int,
88        rtattrs: RtBuffer<Ifla, Buffer>,
89    ) -> Self {
90        Ifinfomsg {
91            ifi_family,
92            padding: 0,
93            ifi_type,
94            ifi_index,
95            ifi_flags: IffFlags::empty(),
96            ifi_change: IffFlags::new(&[Iff::Up]),
97            rtattrs,
98        }
99    }
100}
101
102/// Struct representing interface address messages
103#[derive(Debug, Size, ToBytes, FromBytesWithInput, Header)]
104pub struct Ifaddrmsg {
105    /// Interface address family
106    pub ifa_family: RtAddrFamily,
107    /// Interface address prefix length
108    pub ifa_prefixlen: libc::c_uchar,
109    /// Interface address flags
110    pub ifa_flags: IfaFFlags,
111    /// Interface address scope
112    pub ifa_scope: libc::c_uchar,
113    /// Interface address index
114    pub ifa_index: libc::c_int,
115    /// Payload of [`Rtattr`]s
116    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::UnexpectedEOB)?")]
117    pub rtattrs: RtBuffer<Ifa, Buffer>,
118}
119
120/// General form of address family dependent message.  Used for
121/// requesting things from rtnetlink.
122#[derive(Debug, Size, ToBytes, FromBytes)]
123pub struct Rtgenmsg {
124    /// Address family for the request
125    pub rtgen_family: RtAddrFamily,
126}
127
128/// Route message
129#[derive(Debug, Size, ToBytes, FromBytesWithInput, Header)]
130pub struct Rtmsg {
131    /// Address family of route
132    pub rtm_family: RtAddrFamily,
133    /// Length of destination
134    pub rtm_dst_len: libc::c_uchar,
135    /// Length of source
136    pub rtm_src_len: libc::c_uchar,
137    /// TOS filter
138    pub rtm_tos: libc::c_uchar,
139    /// Routing table ID
140    pub rtm_table: RtTable,
141    /// Routing protocol
142    pub rtm_protocol: Rtprot,
143    /// Routing scope
144    pub rtm_scope: RtScope,
145    /// Routing type
146    pub rtm_type: Rtn,
147    /// Routing flags
148    pub rtm_flags: RtmFFlags,
149    /// Payload of [`Rtattr`]s
150    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::UnexpectedEOB)?")]
151    pub rtattrs: RtBuffer<Rta, Buffer>,
152}
153
154/// Represents an ARP (neighbor table) entry
155#[derive(Debug, Size, ToBytes, FromBytesWithInput, Header)]
156pub struct Ndmsg {
157    /// Address family of entry
158    pub ndm_family: RtAddrFamily,
159    pad1: u8,
160    pad2: u16,
161    /// Index of entry
162    pub ndm_index: libc::c_int,
163    /// State of entry
164    pub ndm_state: NudFlags,
165    /// Flags for entry
166    pub ndm_flags: NtfFlags,
167    /// Type of entry
168    pub ndm_type: Rtn,
169    /// Payload of [`Rtattr`]s
170    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::UnexpectedEOB)?")]
171    pub rtattrs: RtBuffer<Nda, Buffer>,
172}
173
174impl Ndmsg {
175    /// Create a fully initialized neighbor table struct
176    pub fn new(
177        ndm_family: RtAddrFamily,
178        ndm_index: libc::c_int,
179        ndm_state: NudFlags,
180        ndm_flags: NtfFlags,
181        ndm_type: Rtn,
182        rtattrs: RtBuffer<Nda, Buffer>,
183    ) -> Self {
184        Ndmsg {
185            ndm_family,
186            pad1: 0,
187            pad2: 0,
188            ndm_index,
189            ndm_state,
190            ndm_flags,
191            ndm_type,
192            rtattrs,
193        }
194    }
195}
196
197/// Struct representing ARP cache info
198#[derive(Debug, Size, ToBytes, FromBytes)]
199pub struct NdaCacheinfo {
200    /// Confirmed
201    pub ndm_confirmed: u32,
202    /// Used
203    pub ndm_used: u32,
204    /// Updated
205    pub ndm_updated: u32,
206    /// Reference count
207    pub ndm_refcnt: u32,
208}
209
210/// Message in response to queuing discipline operations
211#[derive(Debug, Size, ToBytes, FromBytesWithInput, Header)]
212pub struct Tcmsg {
213    /// Family
214    pub tcm_family: libc::c_uchar,
215    padding_char: libc::c_uchar,
216    padding_short: libc::c_ushort,
217    /// Interface index
218    pub tcm_ifindex: libc::c_int,
219    /// Queuing discipline handle
220    pub tcm_handle: u32,
221    /// Parent queuing discipline
222    pub tcm_parent: u32,
223    /// Info
224    pub tcm_info: u32,
225    /// Payload of [`Rtattr`]s
226    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::UnexpectedEOB)?")]
227    pub rtattrs: RtBuffer<Tca, Buffer>,
228}
229
230impl Tcmsg {
231    /// Create a new [`Tcmsg`] structure handling the necessary
232    /// padding.
233    pub fn new(
234        tcm_family: libc::c_uchar,
235        tcm_ifindex: libc::c_int,
236        tcm_handle: u32,
237        tcm_parent: u32,
238        tcm_info: u32,
239        rtattrs: RtBuffer<Tca, Buffer>,
240    ) -> Self {
241        Tcmsg {
242            tcm_family,
243            padding_char: 0,
244            padding_short: 0,
245            tcm_ifindex,
246            tcm_handle,
247            tcm_parent,
248            tcm_info,
249            rtattrs,
250        }
251    }
252}
253
254/// Struct representing route netlink attributes
255#[derive(Debug, Size, ToBytes, FromBytes, Header)]
256#[neli(header_bound = "T: RtaType")]
257#[neli(from_bytes_bound = "T: RtaType")]
258#[neli(from_bytes_bound = "P: FromBytesWithInput<Input = usize>")]
259#[neli(padding)]
260pub struct Rtattr<T, P> {
261    /// Length of the attribute
262    pub rta_len: libc::c_ushort,
263    /// Type of the attribute
264    pub rta_type: T,
265    /// Payload of the attribute
266    #[neli(
267        input = "(rta_len as usize).checked_sub(Self::header_size()).ok_or(DeError::UnexpectedEOB)?"
268    )]
269    pub rta_payload: P,
270}
271
272impl<T> Rtattr<T, Buffer>
273where
274    T: RtaType,
275{
276    /// Create a new [`Rtattr`].
277    pub fn new<P>(_: Option<u16>, rta_type: T, rta_payload: P) -> Result<Self, SerError>
278    where
279        P: Size + ToBytes,
280    {
281        let mut attr = Rtattr {
282            rta_len: Self::header_size() as u16,
283            rta_type,
284            rta_payload: Buffer::new(),
285        };
286        attr.set_payload(&rta_payload)?;
287        Ok(attr)
288    }
289
290    /// Add a nested attribute to the end of the payload.
291    pub fn add_nested_attribute<TT, P>(&mut self, attr: &Rtattr<TT, P>) -> Result<(), SerError>
292    where
293        TT: RtaType,
294        P: ToBytes,
295    {
296        let mut buffer = Cursor::new(Vec::new());
297        attr.to_bytes(&mut buffer)?;
298
299        self.rta_payload.extend_from_slice(buffer.get_ref());
300        self.rta_len += buffer.get_ref().len() as u16;
301        Ok(())
302    }
303
304    /// Return an [`AttrHandle`][crate::attr::AttrHandle] for
305    /// attributes nested in the given attribute payload.
306    pub fn get_attr_handle<R>(&self) -> Result<RtAttrHandle<R>, DeError>
307    where
308        R: RtaType,
309    {
310        Ok(AttrHandle::new(RtBuffer::from_bytes_with_input(
311            &mut Cursor::new(self.rta_payload.as_ref()),
312            self.rta_payload.len(),
313        )?))
314    }
315
316    /// Return an [`AttrHandleMut`][crate::attr::AttrHandleMut] for
317    /// attributes nested in the given attribute payload.
318    pub fn get_attr_handle_mut<R>(&mut self) -> Result<RtAttrHandleMut<R>, DeError>
319    where
320        R: RtaType,
321    {
322        Ok(AttrHandleMut::new(RtBuffer::from_bytes_with_input(
323            &mut Cursor::new(self.rta_payload.as_ref()),
324            self.rta_payload.len(),
325        )?))
326    }
327}
328
329impl<T> Attribute<T> for Rtattr<T, Buffer>
330where
331    T: RtaType,
332{
333    fn payload(&self) -> &Buffer {
334        &self.rta_payload
335    }
336
337    fn set_payload<P>(&mut self, payload: &P) -> Result<(), SerError>
338    where
339        P: Size + ToBytes,
340    {
341        let mut buffer = Cursor::new(Vec::new());
342        payload.to_bytes(&mut buffer)?;
343
344        // Update `Nlattr` with new length
345        self.rta_len -= self.rta_payload.unpadded_size() as u16;
346        self.rta_len += buffer.get_ref().len() as u16;
347
348        self.rta_payload = Buffer::from(buffer.into_inner());
349
350        Ok(())
351    }
352}
353
354type RtAttrHandle<'a, T> = AttrHandle<'a, RtBuffer<T, Buffer>, Rtattr<T, Buffer>>;
355type RtAttrHandleMut<'a, T> = AttrHandleMut<'a, RtBuffer<T, Buffer>, Rtattr<T, Buffer>>;
356
357impl<'a, T> AttrHandle<'a, RtBuffer<T, Buffer>, Rtattr<T, Buffer>>
358where
359    T: RtaType,
360{
361    /// Get the payload of an attribute as a handle for parsing
362    /// nested attributes.
363    pub fn get_nested_attributes<S>(&mut self, subattr: T) -> Result<RtAttrHandle<S>, DeError>
364    where
365        S: RtaType,
366    {
367        let payload = self
368            .get_attribute(subattr)
369            .ok_or_else(|| DeError::new("Couldn't find specified attribute"))?
370            .rta_payload
371            .as_ref();
372        Ok(AttrHandle::new(RtBuffer::from_bytes_with_input(
373            &mut Cursor::new(payload),
374            payload.len(),
375        )?))
376    }
377
378    /// Get nested attributes from a parsed handle.
379    pub fn get_attribute(&self, t: T) -> Option<&Rtattr<T, Buffer>> {
380        self.get_attrs().iter().find(|item| item.rta_type == t)
381    }
382
383    /// Parse binary payload as a type that implements [`FromBytes`].
384    pub fn get_attr_payload_as<'b, R>(&'b self, attr: T) -> Result<R, DeError>
385    where
386        R: FromBytes<'b>,
387    {
388        match self.get_attribute(attr) {
389            Some(a) => a.get_payload_as::<R>(),
390            _ => Err(DeError::new("Failed to find specified attribute")),
391        }
392    }
393
394    /// Parse binary payload as a type that implements [`FromBytesWithInput`].
395    pub fn get_attr_payload_as_with_len<'b, R>(&'b self, attr: T) -> Result<R, DeError>
396    where
397        R: FromBytesWithInput<'b, Input = usize>,
398    {
399        match self.get_attribute(attr) {
400            Some(a) => a.get_payload_as_with_len::<R>(),
401            _ => Err(DeError::new("Failed to find specified attribute")),
402        }
403    }
404}
405
406#[cfg(test)]
407mod test {
408    use super::*;
409
410    use crate::{
411        consts::{
412            nl::{NlmF, NlmFFlags},
413            socket::NlFamily,
414        },
415        nl::{NlPayload, Nlmsghdr},
416        socket::NlSocketHandle,
417        test::setup,
418    };
419
420    #[test]
421    fn test_rta_deserialize() {
422        setup();
423
424        let buf = &[4u8, 0, 0, 0] as &[u8];
425        Rtattr::<Rta, Buffer>::from_bytes(&mut Cursor::new(buf)).unwrap();
426    }
427
428    #[test]
429    fn test_rta_deserialize_err() {
430        setup();
431
432        // 3 bytes is below minimum length
433        let buf = &[3u8, 0, 0, 0] as &[u8];
434        Rtattr::<Rta, Buffer>::from_bytes(&mut Cursor::new(buf)).unwrap_err();
435    }
436
437    #[test]
438    fn test_rtattr_padding() {
439        setup();
440
441        let attr = Rtattr {
442            rta_len: 5,
443            rta_type: Rta::Unspec,
444            rta_payload: vec![0u8],
445        };
446        let mut buffer = Cursor::new(Vec::new());
447        let buf_res = attr.to_bytes(&mut buffer);
448
449        buf_res.unwrap();
450        // padding check
451        assert_eq!(buffer.into_inner().len(), 8);
452    }
453
454    #[test]
455    fn real_test_ifinfomsg() {
456        setup();
457
458        let mut sock = NlSocketHandle::new(NlFamily::Route).unwrap();
459        sock.send(Nlmsghdr::new(
460            None,
461            Rtm::Getlink,
462            NlmFFlags::new(&[NlmF::Dump, NlmF::Request, NlmF::Ack]),
463            None,
464            None,
465            NlPayload::Payload(Ifinfomsg::new(
466                RtAddrFamily::Unspecified,
467                Arphrd::None,
468                0,
469                IffFlags::empty(),
470                IffFlags::empty(),
471                RtBuffer::new(),
472            )),
473        ))
474        .unwrap();
475        let msgs = sock.recv_all::<Rtm, Ifinfomsg>().unwrap();
476        for msg in msgs {
477            let handle = msg.get_payload().unwrap().rtattrs.get_attr_handle();
478            handle
479                .get_attr_payload_as_with_len::<String>(Ifla::Ifname)
480                .unwrap();
481            // Assert length of ethernet address
482            assert_eq!(
483                handle
484                    .get_attr_payload_as_with_len::<Vec<u8>>(Ifla::Address)
485                    .unwrap()
486                    .len(),
487                6
488            );
489        }
490    }
491
492    #[test]
493    fn real_test_tcmsg() {
494        setup();
495
496        let mut sock = NlSocketHandle::new(NlFamily::Route).unwrap();
497        sock.send(Nlmsghdr::new(
498            None,
499            Rtm::Getqdisc,
500            NlmFFlags::new(&[NlmF::Dump, NlmF::Request, NlmF::Ack]),
501            None,
502            None,
503            NlPayload::Payload(Tcmsg::new(0, 0, 0, 0, 0, RtBuffer::new())),
504        ))
505        .unwrap();
506        let msgs = sock.recv_all::<Rtm, Tcmsg>().unwrap();
507        for msg in msgs {
508            assert!(matches!(msg.get_payload().unwrap(), Tcmsg { .. }));
509            assert_eq!(msg.nl_type, Rtm::Newqdisc);
510        }
511    }
512}