netlink_packet_route/route/
seg6.rs

1// SPDX-License-Identifier: MIT
2
3use std::net::{IpAddr, Ipv6Addr};
4
5use netlink_packet_core::{
6    DecodeError, DefaultNla, ErrorContext, Nla, NlaBuffer, Parseable,
7};
8
9use crate::ip::{emit_ip_addr, parse_ipv6_addr};
10
11const SEG6_IPTUN_MODE_INLINE: u32 = 0;
12const SEG6_IPTUN_MODE_ENCAP: u32 = 1;
13//const SEG6_IPTUN_MODE_L2ENCAP: u32 = 2;
14//const SEG6_IPTUN_MODE_ENCAP_RED: u32 = 3;
15//const SEG6_IPTUN_MODE_L2ENCAP_RED: u32 = 4;
16
17#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18#[non_exhaustive]
19pub enum Seg6Mode {
20    // Inline mode for Seg6
21    Inline,
22    // Encapsulation mode for Seg6
23    Encap,
24    // L2ENCAP = 2,
25    // ENCAP_RED = 3,
26    // L2ENCAP_RED = 4
27    Other(u32),
28}
29
30impl From<Seg6Mode> for u32 {
31    fn from(value: Seg6Mode) -> Self {
32        match value {
33            Seg6Mode::Inline => SEG6_IPTUN_MODE_INLINE,
34            Seg6Mode::Encap => SEG6_IPTUN_MODE_ENCAP,
35            Seg6Mode::Other(i) => i,
36        }
37    }
38}
39
40impl From<u32> for Seg6Mode {
41    fn from(value: u32) -> Self {
42        match value {
43            SEG6_IPTUN_MODE_INLINE => Seg6Mode::Inline,
44            SEG6_IPTUN_MODE_ENCAP => Seg6Mode::Encap,
45            v => Seg6Mode::Other(v),
46        }
47    }
48}
49
50const SEG6_IPTUNNEL_SRH: u16 = 1;
51
52/// Netlink attributes for `RTA_ENCAP` with `RTA_ENCAP_TYPE` set to
53/// `LWTUNNEL_ENCAP_SEG6`.
54#[derive(Debug, PartialEq, Eq, Clone)]
55#[non_exhaustive]
56pub enum RouteSeg6IpTunnel {
57    // Use an IPv6 segment routing header
58    Seg6(Seg6Header),
59    Other(DefaultNla),
60}
61
62impl Nla for RouteSeg6IpTunnel {
63    fn value_len(&self) -> usize {
64        match self {
65            RouteSeg6IpTunnel::Seg6(v) => v.value_len(),
66            RouteSeg6IpTunnel::Other(v) => v.value_len(),
67        }
68    }
69
70    fn kind(&self) -> u16 {
71        match self {
72            RouteSeg6IpTunnel::Seg6(v) => v.kind(),
73            RouteSeg6IpTunnel::Other(v) => v.kind(),
74        }
75    }
76
77    fn emit_value(&self, buffer: &mut [u8]) {
78        match self {
79            RouteSeg6IpTunnel::Seg6(v) => v.emit_value(buffer),
80            RouteSeg6IpTunnel::Other(v) => v.emit_value(buffer),
81        }
82    }
83}
84
85const SEG6_HEADER_LEN: usize = 12;
86
87buffer!(Seg6MessageBuffer(SEG6_HEADER_LEN) {
88    mode: (u32, 0..4),
89    nexthdr: (u8, 4),
90    hdrlen: (u8, 5),
91    seg_type: (u8, 6),
92    segments_left: (u8, 7),
93    first_segment: (u8, 8),
94    flags: (u8, 9),
95    tag: (u16, 10..12),
96    segments: (slice, SEG6_HEADER_LEN..),
97});
98
99const SEG6_SEGMENT_LEN: usize = 16;
100
101buffer!(Seg6SegmentBuffer(SEG6_SEGMENT_LEN) {
102    segment: (slice, 0..SEG6_SEGMENT_LEN),
103    rest: (slice, SEG6_SEGMENT_LEN..)
104});
105
106/// Netlink attributes for `RTA_ENCAP` with `RTA_ENCAP_TYPE` set to
107/// `LWTUNNEL_ENCAP_SEG6`.
108#[derive(Debug, PartialEq, Eq, Clone)]
109#[non_exhaustive]
110pub struct Seg6Header {
111    // Operation mode
112    pub mode: Seg6Mode,
113    // List of segments
114    pub segments: Vec<Ipv6Addr>,
115}
116
117impl Seg6Header {
118    fn push_segments(buf: &mut [u8], mut segments: Vec<Ipv6Addr>) {
119        if let Some(segment) = segments.pop() {
120            let mut segment_buffer = Seg6SegmentBuffer::new(buf);
121            emit_ip_addr(&IpAddr::V6(segment), segment_buffer.segment_mut());
122            Self::push_segments(segment_buffer.rest_mut(), segments);
123        }
124    }
125
126    fn get_segments(
127        buf: &[u8],
128        segments: &mut Vec<Ipv6Addr>,
129    ) -> Result<(), DecodeError> {
130        // are there any remaining segments ?
131        if buf.len() >= SEG6_SEGMENT_LEN {
132            let segment_buffer = Seg6SegmentBuffer::new(buf);
133            let segment = parse_ipv6_addr(segment_buffer.segment())?;
134            segments.push(segment);
135            Self::get_segments(segment_buffer.rest(), segments)?;
136        }
137        Ok(())
138    }
139}
140
141impl Nla for Seg6Header {
142    fn value_len(&self) -> usize {
143        let segments = match self.mode {
144            // in inline mode, seg6 add an additional segment (::) at the
145            // end of the segment list, thus must have one additional
146            // segment slot in the payload
147            Seg6Mode::Inline => self.segments.len() + 1,
148            Seg6Mode::Encap => self.segments.len(),
149            Seg6Mode::Other(_) => self.segments.len(),
150        };
151        12 + 16 * segments
152    }
153
154    fn kind(&self) -> u16 {
155        SEG6_IPTUNNEL_SRH
156    }
157
158    fn emit_value(&self, buffer: &mut [u8]) {
159        // Some sources for understanding the format of Seg6 in Netlink
160        //
161        // torvalds/linux:
162        //      include/uapi/linux/seg6.h
163        //      include/uapi/linux/seg6_iptunnel.h
164        //
165        // iproute2/iproute2
166        //      ip/iproute_lwtunnel.c parse_encap_seg6()
167
168        let mut seg6_header = Seg6MessageBuffer::new(buffer);
169
170        let mut number_segments = self.segments.len();
171        if matches!(self.mode, Seg6Mode::Inline) {
172            number_segments += 1 // last segment (::) added
173        }
174
175        let srhlen = 8 + 16 * number_segments;
176
177        seg6_header.set_mode(self.mode.into());
178        seg6_header.set_nexthdr(0);
179        seg6_header.set_hdrlen(((srhlen >> 3) - 1) as u8);
180        seg6_header.set_seg_type(4);
181        seg6_header.set_segments_left((number_segments - 1) as u8);
182        seg6_header.set_first_segment((number_segments - 1) as u8);
183        seg6_header.set_flags(0);
184        seg6_header.set_tag(0);
185
186        let mut segments = self.segments.clone();
187
188        // Add the last segment (::) if working in inline mode
189        if matches!(self.mode, Seg6Mode::Inline) {
190            segments.push("::".parse().expect("Impossible error"))
191        }
192
193        Seg6Header::push_segments(seg6_header.segments_mut(), segments);
194    }
195}
196
197impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
198    for RouteSeg6IpTunnel
199{
200    fn parse(
201        buf: &NlaBuffer<&'a T>,
202    ) -> Result<Self, netlink_packet_core::DecodeError> {
203        let payload = buf.value();
204        Ok(match buf.kind() {
205            SEG6_IPTUNNEL_SRH => {
206                let seg6_header = Seg6MessageBuffer::new(payload);
207
208                let mut segments: Vec<Ipv6Addr> = vec![];
209                Seg6Header::get_segments(
210                    seg6_header.segments(),
211                    &mut segments,
212                )?;
213
214                let mut segments: Vec<Ipv6Addr> =
215                    segments.into_iter().rev().collect();
216
217                if matches!(seg6_header.mode().into(), Seg6Mode::Inline) {
218                    segments.pop(); // remove last inline segment
219                }
220
221                RouteSeg6IpTunnel::Seg6(Seg6Header {
222                    mode: seg6_header.mode().into(),
223                    segments,
224                })
225            }
226            _ => Self::Other(
227                DefaultNla::parse(buf)
228                    .context("invalid NLA value (unknown type) value")?,
229            ),
230        })
231    }
232}