Skip to content

Commit 1a33cd3

Browse files
Aperencecathay4t
authored andcommitted
Add support for Seg6 encapsulation type
This commit provide some basic support for Seg6 (https://segment-routing.org/). Support for Inline and Encap more are added. (Further support could be enhanced in the future to support the remaining type, but this is in my opinion a good starting point). Additional tests are also included by comparing the resulting messages with messaged captured in wireshark. Note: two expect() are called in the code, but only whenever it is logically impossible to have an exception (checked bounds for IPv6 and parsing of a static IPv6 address) Signed-off-by: Aperence <[email protected]>
1 parent 69c4e01 commit 1a33cd3

File tree

6 files changed

+355
-1
lines changed

6 files changed

+355
-1
lines changed

src/route/attribute.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,16 @@ impl Nla for RouteAttribute {
186186
Self::Other(ref attr) => attr.kind(),
187187
}
188188
}
189+
190+
fn is_nested(&self) -> bool {
191+
if let Self::Encap(encap) = self {
192+
encap
193+
.iter()
194+
.any(|e| matches!(e, RouteLwTunnelEncap::Seg6(_)))
195+
} else {
196+
false
197+
}
198+
}
189199
}
190200

191201
impl<'a, T: AsRef<[u8]> + ?Sized>

src/route/lwtunnel.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use netlink_packet_utils::{
77
DecodeError,
88
};
99

10-
use super::RouteMplsIpTunnel;
10+
use super::{RouteMplsIpTunnel, RouteSeg6IpTunnel};
1111

1212
const LWTUNNEL_ENCAP_NONE: u16 = 0;
1313
const LWTUNNEL_ENCAP_MPLS: u16 = 1;
@@ -110,27 +110,31 @@ impl std::fmt::Display for RouteLwEnCapType {
110110
#[non_exhaustive]
111111
pub enum RouteLwTunnelEncap {
112112
Mpls(RouteMplsIpTunnel),
113+
Seg6(RouteSeg6IpTunnel),
113114
Other(DefaultNla),
114115
}
115116

116117
impl Nla for RouteLwTunnelEncap {
117118
fn value_len(&self) -> usize {
118119
match self {
119120
Self::Mpls(v) => v.value_len(),
121+
Self::Seg6(v) => v.value_len(),
120122
Self::Other(v) => v.value_len(),
121123
}
122124
}
123125

124126
fn emit_value(&self, buffer: &mut [u8]) {
125127
match self {
126128
Self::Mpls(v) => v.emit_value(buffer),
129+
Self::Seg6(v) => v.emit_value(buffer),
127130
Self::Other(v) => v.emit_value(buffer),
128131
}
129132
}
130133

131134
fn kind(&self) -> u16 {
132135
match self {
133136
Self::Mpls(v) => v.kind(),
137+
Self::Seg6(v) => v.kind(),
134138
Self::Other(v) => v.kind(),
135139
}
136140
}
@@ -149,6 +153,9 @@ where
149153
RouteLwEnCapType::Mpls => {
150154
Self::Mpls(RouteMplsIpTunnel::parse(buf)?)
151155
}
156+
RouteLwEnCapType::Seg6 => {
157+
Self::Seg6(RouteSeg6IpTunnel::parse(buf)?)
158+
}
152159
_ => Self::Other(DefaultNla::parse(buf)?),
153160
})
154161
}

src/route/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod mpls;
1313
mod next_hops;
1414
mod preference;
1515
mod realm;
16+
mod seg6;
1617
mod via;
1718

1819
#[cfg(test)]
@@ -34,5 +35,6 @@ pub use self::next_hops::{
3435
};
3536
pub use self::preference::RoutePreference;
3637
pub use self::realm::RouteRealm;
38+
pub use self::seg6::{RouteSeg6IpTunnel, Seg6Header, Seg6Mode};
3739
pub use self::via::{RouteVia, RouteViaBuffer};
3840
pub use flags::RouteFlags;

src/route/seg6.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
use anyhow::Context;
4+
use byteorder::{ByteOrder, NativeEndian};
5+
use netlink_packet_utils::{
6+
nla::{DefaultNla, Nla, NlaBuffer},
7+
Parseable,
8+
};
9+
use std::net::Ipv6Addr;
10+
11+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
12+
#[non_exhaustive]
13+
pub enum Seg6Mode {
14+
// Inline mode for Seg6
15+
Inline,
16+
// Encapsulation mode for Seg6
17+
Encap,
18+
// L2ENCAP = 2,
19+
// ENCAP_RED = 3,
20+
// L2ENCAP_RED = 4
21+
Other(u32),
22+
}
23+
24+
impl From<Seg6Mode> for u32 {
25+
fn from(value: Seg6Mode) -> Self {
26+
match value {
27+
Seg6Mode::Inline => 0,
28+
Seg6Mode::Encap => 1,
29+
Seg6Mode::Other(i) => i,
30+
}
31+
}
32+
}
33+
34+
impl From<u32> for Seg6Mode {
35+
fn from(value: u32) -> Self {
36+
match value {
37+
0 => Seg6Mode::Inline,
38+
1 => Seg6Mode::Encap,
39+
v => Seg6Mode::Other(v),
40+
}
41+
}
42+
}
43+
44+
const SEG6_IPTUNNEL_SRH: u16 = 1;
45+
46+
/// Netlink attributes for `RTA_ENCAP` with `RTA_ENCAP_TYPE` set to
47+
/// `LWTUNNEL_ENCAP_SEG6`.
48+
#[derive(Debug, PartialEq, Eq, Clone)]
49+
#[non_exhaustive]
50+
pub enum RouteSeg6IpTunnel {
51+
// Use an IPv6 segment routing header
52+
Seg6(Seg6Header),
53+
Other(DefaultNla),
54+
}
55+
56+
impl Nla for RouteSeg6IpTunnel {
57+
fn value_len(&self) -> usize {
58+
match self {
59+
RouteSeg6IpTunnel::Seg6(v) => v.value_len(),
60+
RouteSeg6IpTunnel::Other(v) => v.value_len(),
61+
}
62+
}
63+
64+
fn kind(&self) -> u16 {
65+
match self {
66+
RouteSeg6IpTunnel::Seg6(v) => v.kind(),
67+
RouteSeg6IpTunnel::Other(v) => v.kind(),
68+
}
69+
}
70+
71+
fn emit_value(&self, buffer: &mut [u8]) {
72+
match self {
73+
RouteSeg6IpTunnel::Seg6(v) => v.emit_value(buffer),
74+
RouteSeg6IpTunnel::Other(v) => v.emit_value(buffer),
75+
}
76+
}
77+
}
78+
79+
/// Netlink attributes for `RTA_ENCAP` with `RTA_ENCAP_TYPE` set to
80+
/// `LWTUNNEL_ENCAP_SEG6`.
81+
#[derive(Debug, PartialEq, Eq, Clone)]
82+
#[non_exhaustive]
83+
pub struct Seg6Header {
84+
// Operation mode
85+
pub mode: Seg6Mode,
86+
// List of segments
87+
pub segments: Vec<Ipv6Addr>,
88+
}
89+
90+
impl Nla for Seg6Header {
91+
fn value_len(&self) -> usize {
92+
let segments = match self.mode {
93+
Seg6Mode::Inline => self.segments.len() + 1,
94+
Seg6Mode::Encap => self.segments.len(),
95+
Seg6Mode::Other(_) => self.segments.len(),
96+
};
97+
12 + 16 * segments
98+
}
99+
100+
fn kind(&self) -> u16 {
101+
SEG6_IPTUNNEL_SRH
102+
}
103+
104+
fn emit_value(&self, buffer: &mut [u8]) {
105+
// Some sources for understanding the format of Seg6 in Netlink
106+
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/seg6.h
107+
// https://github.com/iproute2/iproute2/blob/main/include/uapi/linux/seg6_iptunnel.h#L27
108+
// https://github.com/iproute2/iproute2/blob/e3f9681d4a777fb2595a322b421abf0036ab1aae/ip/iproute_lwtunnel.c#L952
109+
110+
let mut number_segments = self.segments.len();
111+
if matches!(self.mode, Seg6Mode::Inline) {
112+
number_segments += 1 // last segment (::) added
113+
}
114+
115+
let srhlen = 8 + 16 * number_segments;
116+
117+
// mode : 4 bytes
118+
NativeEndian::write_u32(&mut buffer[..4], self.mode.into());
119+
// nexthdr : 1 bytes
120+
buffer[4] = 0;
121+
// hdrlen : 1 bytes
122+
buffer[5] = ((srhlen >> 3) - 1) as u8;
123+
// type : 1 byte
124+
buffer[6] = 4;
125+
// segments_left : 1 byte
126+
buffer[7] = (number_segments - 1) as u8;
127+
// first_segment : 1 byte
128+
buffer[8] = (number_segments - 1) as u8;
129+
// flags : 1 byte
130+
buffer[9] = 0;
131+
// tag : 2 bytes
132+
NativeEndian::write_u16(&mut buffer[10..12], 0);
133+
134+
let mut offset = 12;
135+
136+
// Add the last segment (::) if working in inline mode
137+
if matches!(self.mode, Seg6Mode::Inline) {
138+
let addr: Ipv6Addr = "::".parse().expect("Impossible error");
139+
buffer[offset..offset + 16].copy_from_slice(&addr.octets());
140+
offset += 16;
141+
}
142+
143+
// Add all segments in reverse order
144+
for addr in self.segments.iter().rev() {
145+
buffer[offset..offset + 16].copy_from_slice(&addr.octets());
146+
offset += 16;
147+
}
148+
}
149+
}
150+
151+
impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
152+
for RouteSeg6IpTunnel
153+
{
154+
fn parse(
155+
buf: &NlaBuffer<&'a T>,
156+
) -> Result<Self, netlink_packet_utils::DecodeError> {
157+
let payload = buf.value();
158+
Ok(match buf.kind() {
159+
SEG6_IPTUNNEL_SRH => {
160+
let mode = NativeEndian::read_u32(payload).into();
161+
162+
let number_segments = payload[7];
163+
164+
let mut offset = 12;
165+
let mut segments: Vec<Ipv6Addr> = vec![];
166+
for _ in 0..number_segments + 1 {
167+
let slice: [u8; 16] = payload[offset..offset + 16]
168+
.try_into()
169+
.expect("Impossible to fail");
170+
let ip_addr = Ipv6Addr::from(slice);
171+
segments.push(ip_addr);
172+
offset += 16;
173+
}
174+
175+
let mut segments: Vec<Ipv6Addr> =
176+
segments.into_iter().rev().collect();
177+
178+
if matches!(mode, Seg6Mode::Inline) {
179+
segments.pop(); // remove last inline segment
180+
}
181+
182+
RouteSeg6IpTunnel::Seg6(Seg6Header { mode, segments })
183+
}
184+
_ => Self::Other(
185+
DefaultNla::parse(buf)
186+
.context("invalid NLA value (unknown type) value")?,
187+
),
188+
})
189+
}
190+
}

src/route/tests/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ mod realm;
1515
#[cfg(test)]
1616
mod route_flags;
1717
#[cfg(test)]
18+
mod seg6;
19+
#[cfg(test)]
1820
mod uid;
1921
#[cfg(test)]
2022
mod via;

0 commit comments

Comments
 (0)