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 std::io::Cursor;
12
13use derive_builder::{Builder, UninitializedFieldError};
14use getset::Getters;
15
16use crate::{
17    self as neli, FromBytes, FromBytesWithInput, FromBytesWithInputBorrowed, Header, Size, ToBytes,
18    attr::{AttrHandle, Attribute},
19    consts::rtnl::*,
20    err::{DeError, SerError},
21    types::{Buffer, RtBuffer},
22};
23
24/// Struct representing interface information messages
25#[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytesWithInput, Header)]
26#[builder(pattern = "owned")]
27pub struct Ifinfomsg {
28    /// Interface address family
29    #[getset(get = "pub")]
30    ifi_family: RtAddrFamily,
31    #[builder(setter(skip))]
32    #[builder(default = "0")]
33    padding: u8,
34    /// Interface type
35    #[getset(get = "pub")]
36    #[builder(default = "Arphrd::from(0)")]
37    ifi_type: Arphrd,
38    /// Interface index
39    #[getset(get = "pub")]
40    #[builder(default = "0")]
41    ifi_index: libc::c_int,
42    /// Interface flags
43    #[getset(get = "pub")]
44    #[builder(default = "Iff::empty()")]
45    ifi_flags: Iff,
46    /// Interface change mask
47    #[getset(get = "pub")]
48    #[builder(default = "Iff::empty()")]
49    ifi_change: Iff,
50    /// Payload of [`Rtattr`]s
51    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")]
52    #[getset(get = "pub")]
53    #[builder(default = "RtBuffer::new()")]
54    rtattrs: RtBuffer<Ifla, Buffer>,
55}
56
57impl IfinfomsgBuilder {
58    /// Set the link with the given index up (equivalent to
59    /// `ip link set dev DEV up`)
60    pub fn up(mut self) -> Self {
61        self.ifi_flags = Some(self.ifi_flags.unwrap_or_else(Iff::empty) | Iff::UP);
62        self.ifi_change = Some(self.ifi_change.unwrap_or_else(Iff::empty) | Iff::UP);
63        self
64    }
65
66    /// Set the link with the given index down (equivalent to
67    /// `ip link set dev DEV down`)
68    pub fn down(mut self) -> Self {
69        self.ifi_flags = Some(self.ifi_flags.unwrap_or_else(Iff::empty) & !Iff::UP);
70        self.ifi_change = Some(self.ifi_change.unwrap_or_else(Iff::empty) | Iff::UP);
71        self
72    }
73}
74
75/// Struct representing interface address messages
76#[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytesWithInput, Header)]
77#[builder(pattern = "owned")]
78pub struct Ifaddrmsg {
79    /// Interface address family
80    #[getset(get = "pub")]
81    ifa_family: RtAddrFamily,
82    /// Interface address prefix length
83    #[getset(get = "pub")]
84    ifa_prefixlen: libc::c_uchar,
85    /// Interface address flags
86    #[getset(get = "pub")]
87    #[builder(default = "IfaF::empty()")]
88    ifa_flags: IfaF,
89    /// Interface address scope
90    #[getset(get = "pub")]
91    ifa_scope: RtScope,
92    /// Interface address index
93    #[getset(get = "pub")]
94    ifa_index: libc::c_uint,
95    /// Payload of [`Rtattr`]s
96    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")]
97    #[getset(get = "pub")]
98    #[builder(default = "RtBuffer::new()")]
99    rtattrs: RtBuffer<Ifa, Buffer>,
100}
101
102/// General form of address family dependent message.  Used for
103/// requesting things from rtnetlink.
104#[derive(Builder, Getters, Debug, Size, ToBytes, FromBytesWithInput, Header)]
105#[builder(pattern = "owned")]
106pub struct Rtgenmsg {
107    /// Address family for the request
108    #[getset(get = "pub")]
109    rtgen_family: RtAddrFamily,
110    /// Payload of [`Rtattr`]s
111    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")]
112    #[getset(get = "pub")]
113    #[builder(default = "RtBuffer::new()")]
114    rtattrs: RtBuffer<Ifa, Buffer>,
115}
116
117/// Route message
118#[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytesWithInput, Header)]
119#[builder(pattern = "owned")]
120pub struct Rtmsg {
121    /// Address family of route
122    #[getset(get = "pub")]
123    rtm_family: RtAddrFamily,
124    /// Length of destination
125    #[getset(get = "pub")]
126    rtm_dst_len: libc::c_uchar,
127    /// Length of source
128    #[getset(get = "pub")]
129    rtm_src_len: libc::c_uchar,
130    /// TOS filter
131    #[getset(get = "pub")]
132    rtm_tos: libc::c_uchar,
133    /// Routing table ID
134    #[getset(get = "pub")]
135    rtm_table: RtTable,
136    /// Routing protocol
137    #[getset(get = "pub")]
138    rtm_protocol: Rtprot,
139    /// Routing scope
140    #[getset(get = "pub")]
141    rtm_scope: RtScope,
142    /// Routing type
143    #[getset(get = "pub")]
144    rtm_type: Rtn,
145    /// Routing flags
146    #[builder(default = "RtmF::empty()")]
147    #[getset(get = "pub")]
148    rtm_flags: RtmF,
149    /// Payload of [`Rtattr`]s
150    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")]
151    #[getset(get = "pub")]
152    #[builder(default = "RtBuffer::new()")]
153    rtattrs: RtBuffer<Rta, Buffer>,
154}
155
156/// Represents an ARP (neighbor table) entry
157#[derive(Builder, Getters, Debug, Size, ToBytes, FromBytesWithInput, Header)]
158#[builder(pattern = "owned")]
159pub struct Ndmsg {
160    /// Address family of entry
161    #[getset(get = "pub")]
162    ndm_family: RtAddrFamily,
163    #[builder(setter(skip))]
164    #[builder(default = "0")]
165    pad1: u8,
166    #[builder(setter(skip))]
167    #[builder(default = "0")]
168    pad2: u16,
169    /// Index of entry
170    #[getset(get = "pub")]
171    ndm_index: libc::c_int,
172    /// State of entry
173    #[getset(get = "pub")]
174    ndm_state: Nud,
175    /// Flags for entry
176    #[getset(get = "pub")]
177    #[builder(default = "Ntf::empty()")]
178    ndm_flags: Ntf,
179    /// Type of entry
180    #[getset(get = "pub")]
181    ndm_type: Rtn,
182    /// Payload of [`Rtattr`]s
183    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")]
184    #[getset(get = "pub")]
185    #[builder(default = "RtBuffer::new()")]
186    rtattrs: RtBuffer<Nda, Buffer>,
187}
188
189/// Struct representing ARP cache info
190#[derive(Builder, Getters, Debug, Size, ToBytes, FromBytes)]
191#[builder(pattern = "owned")]
192pub struct NdaCacheinfo {
193    /// Confirmed
194    #[getset(get = "pub")]
195    ndm_confirmed: u32,
196    /// Used
197    #[getset(get = "pub")]
198    ndm_used: u32,
199    /// Updated
200    #[getset(get = "pub")]
201    ndm_updated: u32,
202    /// Reference count
203    #[getset(get = "pub")]
204    ndm_refcnt: u32,
205}
206
207/// Message in response to queuing discipline operations
208#[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytesWithInput, Header)]
209#[builder(pattern = "owned")]
210pub struct Tcmsg {
211    /// Family
212    #[getset(get = "pub")]
213    tcm_family: libc::c_uchar,
214    #[builder(setter(skip))]
215    #[builder(default = "0")]
216    padding_char: libc::c_uchar,
217    #[builder(setter(skip))]
218    #[builder(default = "0")]
219    padding_short: libc::c_ushort,
220    /// Interface index
221    #[getset(get = "pub")]
222    tcm_ifindex: libc::c_int,
223    /// Queuing discipline handle
224    #[getset(get = "pub")]
225    tcm_handle: u32,
226    /// Parent queuing discipline
227    #[getset(get = "pub")]
228    tcm_parent: u32,
229    /// Info
230    #[getset(get = "pub")]
231    tcm_info: u32,
232    /// Payload of [`Rtattr`]s
233    #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")]
234    #[getset(get = "pub")]
235    #[builder(default = "RtBuffer::new()")]
236    rtattrs: RtBuffer<Tca, Buffer>,
237}
238
239/// Struct representing route netlink attributes
240#[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytes, Header)]
241#[neli(header_bound = "T: RtaType")]
242#[neli(from_bytes_bound = "T: RtaType")]
243#[neli(from_bytes_bound = "P: FromBytesWithInput<Input = usize>")]
244#[neli(padding)]
245#[builder(pattern = "owned")]
246#[builder(build_fn(skip))]
247pub struct Rtattr<T, P> {
248    /// Length of the attribute
249    #[getset(get = "pub")]
250    #[builder(setter(skip))]
251    rta_len: libc::c_ushort,
252    /// Type of the attribute
253    #[getset(get = "pub")]
254    rta_type: T,
255    /// Payload of the attribute
256    #[neli(
257        input = "(rta_len as usize).checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(rta_len as usize))?"
258    )]
259    #[getset(get = "pub")]
260    rta_payload: P,
261}
262
263impl<T, P> RtattrBuilder<T, P>
264where
265    T: Size,
266    P: Size + ToBytes,
267{
268    /// Build an [`Rtattr`].
269    pub fn build(self) -> Result<Rtattr<T, Buffer>, RtattrBuilderError> {
270        let rta_type = self
271            .rta_type
272            .ok_or_else(|| RtattrBuilderError::from(UninitializedFieldError::new("rta_type")))?;
273        let rta_payload = self
274            .rta_payload
275            .ok_or_else(|| RtattrBuilderError::from(UninitializedFieldError::new("rta_payload")))?;
276        let mut buffer = Cursor::new(vec![0; rta_payload.unpadded_size()]);
277        rta_payload.to_bytes(&mut buffer).map_err(|_| {
278            RtattrBuilderError::ValidationError(
279                "Could not convert payload to binary representation".to_string(),
280            )
281        })?;
282
283        let mut rtattr = Rtattr {
284            rta_len: 0,
285            rta_type,
286            rta_payload: Buffer::from(buffer.into_inner()),
287        };
288        rtattr.rta_len = rtattr.unpadded_size() as libc::c_ushort;
289        Ok(rtattr)
290    }
291}
292
293impl<T> Rtattr<T, Buffer>
294where
295    T: RtaType,
296{
297    /// Builder method to add a nested attribute to the end of the payload.
298    ///
299    /// Use this to construct an attribute and nest attributes within it in one method chain.
300    pub fn nest<TT, P>(mut self, attr: &Rtattr<TT, P>) -> Result<Self, SerError>
301    where
302        TT: RtaType,
303        P: ToBytes,
304    {
305        self.add_nested_attribute(attr)?;
306        Ok(self)
307    }
308
309    /// Add a nested attribute to the end of the payload.
310    fn add_nested_attribute<TT, P>(&mut self, attr: &Rtattr<TT, P>) -> Result<(), SerError>
311    where
312        TT: RtaType,
313        P: ToBytes,
314    {
315        let mut buffer = Cursor::new(Vec::new());
316        attr.to_bytes(&mut buffer)?;
317
318        self.rta_payload.extend_from_slice(buffer.get_ref());
319        self.rta_len += buffer.get_ref().len() as u16;
320        Ok(())
321    }
322
323    /// Return an [`AttrHandle`] for
324    /// attributes nested in the given attribute payload.
325    pub fn get_attr_handle<R>(&self) -> Result<RtAttrHandle<R>, DeError>
326    where
327        R: RtaType,
328    {
329        Ok(AttrHandle::new(RtBuffer::from_bytes_with_input(
330            &mut Cursor::new(self.rta_payload.as_ref()),
331            self.rta_payload.len(),
332        )?))
333    }
334}
335
336impl<T> Attribute<T> for Rtattr<T, Buffer>
337where
338    T: RtaType,
339{
340    fn payload(&self) -> &Buffer {
341        &self.rta_payload
342    }
343
344    fn set_payload<P>(&mut self, payload: &P) -> Result<(), SerError>
345    where
346        P: Size + ToBytes,
347    {
348        let mut buffer = Cursor::new(Vec::new());
349        payload.to_bytes(&mut buffer)?;
350
351        // Update `Nlattr` with new length
352        self.rta_len -= self.rta_payload.unpadded_size() as u16;
353        self.rta_len += buffer.get_ref().len() as u16;
354
355        self.rta_payload = Buffer::from(buffer.into_inner());
356
357        Ok(())
358    }
359}
360
361/// Represents a routing netlink attribute handle.
362pub type RtAttrHandle<'a, T> = AttrHandle<'a, RtBuffer<T, Buffer>, Rtattr<T, Buffer>>;
363
364impl<'a, T> RtAttrHandle<'a, T>
365where
366    T: RtaType,
367{
368    /// Get the payload of an attribute as a handle for parsing
369    /// nested attributes.
370    pub fn get_nested_attributes<S>(&self, subattr: T) -> Result<RtAttrHandle<S>, DeError>
371    where
372        S: RtaType,
373    {
374        let payload = self
375            .get_attribute(subattr)
376            .ok_or_else(|| DeError::new("Couldn't find specified attribute"))?
377            .rta_payload
378            .as_ref();
379        Ok(AttrHandle::new(RtBuffer::from_bytes_with_input(
380            &mut Cursor::new(payload),
381            payload.len(),
382        )?))
383    }
384
385    /// Get nested attributes from a parsed handle.
386    pub fn get_attribute(&self, t: T) -> Option<&Rtattr<T, Buffer>> {
387        self.get_attrs().iter().find(|item| item.rta_type == t)
388    }
389
390    /// Parse binary payload as a type that implements [`FromBytes`].
391    pub fn get_attr_payload_as<R>(&self, attr: T) -> Result<R, DeError>
392    where
393        R: FromBytes,
394    {
395        match self.get_attribute(attr) {
396            Some(a) => a.get_payload_as::<R>(),
397            _ => Err(DeError::new("Failed to find specified attribute")),
398        }
399    }
400
401    /// Parse binary payload as a type that implements [`FromBytesWithInput`].
402    pub fn get_attr_payload_as_with_len<R>(&self, attr: T) -> Result<R, DeError>
403    where
404        R: FromBytesWithInput<Input = usize>,
405    {
406        match self.get_attribute(attr) {
407            Some(a) => a.get_payload_as_with_len::<R>(),
408            _ => Err(DeError::new("Failed to find specified attribute")),
409        }
410    }
411
412    /// Parse binary payload as a type that implements [`FromBytesWithInput`].
413    pub fn get_attr_payload_as_with_len_borrowed<R>(&'a self, attr: T) -> Result<R, DeError>
414    where
415        R: FromBytesWithInputBorrowed<'a, Input = usize>,
416    {
417        match self.get_attribute(attr) {
418            Some(a) => a.get_payload_as_with_len_borrowed::<R>(),
419            _ => Err(DeError::new("Failed to find specified attribute")),
420        }
421    }
422}
423
424#[cfg(test)]
425mod test {
426    use super::*;
427
428    use std::net::Ipv4Addr;
429
430    use byteorder::{NativeEndian, WriteBytesExt};
431
432    use crate::{
433        consts::{nl::NlmF, socket::NlFamily},
434        err::RouterError,
435        nl::NlPayload,
436        router::synchronous::NlRouter,
437        test::setup,
438        utils::Groups,
439    };
440
441    #[test]
442    fn test_rta_deserialize() {
443        setup();
444
445        let mut buf = Cursor::new(vec![]);
446        buf.write_u16::<NativeEndian>(4).unwrap();
447        buf.write_u16::<NativeEndian>(0).unwrap();
448        buf.set_position(0);
449        Rtattr::<Rta, Buffer>::from_bytes(&mut buf).unwrap();
450    }
451
452    #[test]
453    fn test_rta_deserialize_err() {
454        setup();
455
456        // 3 bytes is below minimum length
457        let mut buf = Cursor::new(vec![]);
458        buf.write_u16::<NativeEndian>(3).unwrap();
459        buf.write_u16::<NativeEndian>(0).unwrap();
460        buf.set_position(0);
461        Rtattr::<Rta, Buffer>::from_bytes(&mut buf).unwrap_err();
462    }
463
464    #[test]
465    fn test_rtattr_padding() {
466        setup();
467
468        let attr = Rtattr {
469            rta_len: 5,
470            rta_type: Rta::Unspec,
471            rta_payload: vec![0u8],
472        };
473        let mut buffer = Cursor::new(Vec::new());
474        let buf_res = attr.to_bytes(&mut buffer);
475
476        buf_res.unwrap();
477        // padding check
478        assert_eq!(buffer.into_inner().len(), 8);
479    }
480
481    #[test]
482    fn real_test_ifinfomsg() {
483        setup();
484
485        let (sock, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty()).unwrap();
486        sock.enable_strict_checking(true).unwrap();
487        let mut recv = sock
488            .send::<_, _, Rtm, Ifinfomsg>(
489                Rtm::Getlink,
490                NlmF::DUMP | NlmF::ACK,
491                NlPayload::Payload(
492                    IfinfomsgBuilder::default()
493                        .ifi_family(RtAddrFamily::Unspecified)
494                        .build()
495                        .unwrap(),
496                ),
497            )
498            .unwrap();
499        let all_msgs = recv
500            .try_fold(Vec::new(), |mut v, m| {
501                v.push(m?);
502                Result::<_, RouterError<Rtm, Ifinfomsg>>::Ok(v)
503            })
504            .unwrap();
505        let non_err_payloads = all_msgs.iter().fold(Vec::new(), |mut v, m| {
506            if let Some(p) = m.get_payload() {
507                v.push(p);
508            }
509            v
510        });
511        if non_err_payloads.is_empty() {
512            panic!("Only received done message and no additional information");
513        }
514        for payload in non_err_payloads {
515            let handle = payload.rtattrs.get_attr_handle();
516            handle
517                .get_attr_payload_as_with_len::<String>(Ifla::Ifname)
518                .unwrap();
519            // Assert length of ethernet address
520            if let Ok(attr) = handle.get_attr_payload_as_with_len::<Vec<u8>>(Ifla::Address) {
521                assert_eq!(attr.len(), 6);
522            }
523        }
524    }
525
526    #[test]
527    fn real_test_tcmsg() {
528        setup();
529
530        let (sock, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty()).unwrap();
531        sock.enable_strict_checking(true).unwrap();
532        let recv = sock
533            .send::<_, _, Rtm, Tcmsg>(
534                Rtm::Getqdisc,
535                NlmF::DUMP | NlmF::ACK,
536                NlPayload::Payload(
537                    TcmsgBuilder::default()
538                        .tcm_family(0)
539                        .tcm_ifindex(0)
540                        .tcm_handle(0)
541                        .tcm_parent(0)
542                        .tcm_info(0)
543                        .build()
544                        .unwrap(),
545                ),
546            )
547            .unwrap();
548        for msg in recv {
549            let msg = msg.unwrap();
550            assert!(matches!(msg.get_payload(), Some(Tcmsg { .. }) | None));
551            assert!(matches!(
552                msg.nl_type(),
553                Rtm::Newqdisc | Rtm::UnrecognizedConst(3)
554            ));
555        }
556    }
557
558    #[test]
559    #[cfg(target_env = "gnu")]
560    fn real_test_rtmsg_search() {
561        setup();
562
563        let dstip = Ipv4Addr::new(127, 0, 0, 1);
564        let raw_dstip = u32::from(dstip).to_be();
565        let route_attr = RtattrBuilder::default()
566            .rta_type(Rta::Dst)
567            .rta_payload(raw_dstip)
568            .build()
569            .unwrap();
570
571        let mut route_payload = RtBuffer::new();
572        route_payload.push(route_attr);
573
574        let (rtnl, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty()).unwrap();
575
576        let ifroutemsg = RtmsgBuilder::default()
577            .rtm_family(RtAddrFamily::Inet)
578            .rtm_dst_len(32)
579            .rtm_src_len(0)
580            .rtm_tos(0)
581            .rtm_table(RtTable::Unspec)
582            .rtm_protocol(Rtprot::Unspec)
583            .rtm_scope(RtScope::Universe)
584            .rtm_type(Rtn::Unspec)
585            .rtm_flags(RtmF::from(libc::RTM_F_LOOKUP_TABLE))
586            .rtattrs(route_payload)
587            .build()
588            .unwrap();
589
590        let recv = rtnl
591            .send::<_, _, Rtm, Rtmsg>(Rtm::Getroute, NlmF::REQUEST, NlPayload::Payload(ifroutemsg))
592            .unwrap();
593
594        assert!(recv.count() > 0);
595    }
596}