Skip to content

Commit 04ecc20

Browse files
author
Julius Rüberg
committed
Add support for LWTUNNEL_ENCAP_IP6
Signed-off-by: Julius Rüberg <[email protected]>
1 parent 8ac7c2a commit 04ecc20

File tree

4 files changed

+236
-1
lines changed

4 files changed

+236
-1
lines changed

src/route/lwtunnel.rs

+150
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
// SPDX-License-Identifier: MIT
22

3+
use std::net::Ipv6Addr;
4+
35
use anyhow::Context;
6+
use byteorder::{BigEndian, ByteOrder, NetworkEndian};
47
use netlink_packet_utils::{
58
nla::{DefaultNla, Nla, NlaBuffer, NlasIterator},
9+
parsers::{parse_u16_be, parse_u8},
610
traits::{Emitable, Parseable, ParseableParametrized},
711
DecodeError,
812
};
913

14+
use crate::ip::parse_ipv6_addr;
15+
1016
use super::RouteMplsIpTunnel;
1117

1218
const LWTUNNEL_ENCAP_NONE: u16 = 0;
@@ -21,6 +27,20 @@ const LWTUNNEL_ENCAP_RPL: u16 = 8;
2127
const LWTUNNEL_ENCAP_IOAM6: u16 = 9;
2228
const LWTUNNEL_ENCAP_XFRM: u16 = 10;
2329

30+
const LWTUNNEL_IP6_UNSPEC: u16 = 0;
31+
const LWTUNNEL_IP6_ID: u16 = 1;
32+
const LWTUNNEL_IP6_DST: u16 = 2;
33+
const LWTUNNEL_IP6_SRC: u16 = 3;
34+
const LWTUNNEL_IP6_HOPLIMIT: u16 = 4;
35+
const LWTUNNEL_IP6_TC: u16 = 5;
36+
const LWTUNNEL_IP6_FLAGS: u16 = 6;
37+
//const LWTUNNEL_IP6_PAD: u16 = 7;
38+
//const LWTUNNEL_IP6_OPTS: u16 = 8;
39+
40+
const TUNNEL_CSUM: u16 = 1;
41+
const TUNNEL_KEY: u16 = 4;
42+
const TUNNEL_SEQ: u16 = 8;
43+
2444
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
2545
#[non_exhaustive]
2646
pub enum RouteLwEnCapType {
@@ -106,31 +126,160 @@ impl std::fmt::Display for RouteLwEnCapType {
106126
}
107127
}
108128

129+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
130+
pub enum RouteIp6Tunnel {
131+
#[default]
132+
Unspecified,
133+
Id(u64),
134+
Destination(Ipv6Addr),
135+
Source(Ipv6Addr),
136+
Hoplimit(u8),
137+
Tc(u8),
138+
Flags(u16),
139+
}
140+
141+
impl std::fmt::Display for RouteIp6Tunnel {
142+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143+
match self {
144+
Self::Unspecified => write!(f, "unspecified"),
145+
Self::Id(id) => write!(f, "id {id}"),
146+
Self::Destination(dst) => write!(f, "dst {dst}"),
147+
Self::Source(src) => write!(f, "src, {src}"),
148+
Self::Hoplimit(hoplimit) => write!(f, "hoplimit {hoplimit}"),
149+
Self::Tc(tc) => write!(f, "tc {tc}"),
150+
Self::Flags(flags) => {
151+
if flags & TUNNEL_KEY != 0 {
152+
write!(f, "key ")?;
153+
}
154+
if flags & TUNNEL_CSUM != 0 {
155+
write!(f, "csum ")?;
156+
}
157+
158+
if flags & TUNNEL_SEQ != 0 {
159+
write!(f, "seq ")?;
160+
}
161+
162+
Ok(())
163+
}
164+
}
165+
}
166+
}
167+
168+
impl Nla for RouteIp6Tunnel {
169+
fn value_len(&self) -> usize {
170+
match self {
171+
Self::Unspecified => 0,
172+
Self::Id(_) => const { size_of::<u64>() },
173+
Self::Destination(_) => const { size_of::<Ipv6Addr>() },
174+
Self::Source(_) => const { size_of::<Ipv6Addr>() },
175+
Self::Hoplimit(_) => const { size_of::<u8>() },
176+
Self::Tc(_) => const { size_of::<u8>() },
177+
Self::Flags(_) => const { size_of::<u16>() },
178+
}
179+
}
180+
181+
fn kind(&self) -> u16 {
182+
match self {
183+
Self::Unspecified => LWTUNNEL_IP6_UNSPEC,
184+
Self::Id(_) => LWTUNNEL_IP6_ID,
185+
Self::Destination(_) => LWTUNNEL_IP6_DST,
186+
Self::Source(_) => LWTUNNEL_IP6_SRC,
187+
Self::Hoplimit(_) => LWTUNNEL_IP6_HOPLIMIT,
188+
Self::Tc(_) => LWTUNNEL_IP6_TC,
189+
Self::Flags(_) => LWTUNNEL_IP6_FLAGS,
190+
}
191+
}
192+
193+
fn emit_value(&self, buffer: &mut [u8]) {
194+
match self {
195+
Self::Unspecified => {}
196+
Self::Id(id) => NetworkEndian::write_u64(buffer, *id),
197+
Self::Destination(ip) | Self::Source(ip) => {
198+
buffer.copy_from_slice(&ip.octets());
199+
}
200+
Self::Hoplimit(value) | Self::Tc(value) => buffer[0] = *value,
201+
Self::Flags(flags) => BigEndian::write_u16(buffer, *flags),
202+
}
203+
}
204+
}
205+
206+
// should probably be in utils
207+
fn parse_u64_be(payload: &[u8]) -> Result<u64, DecodeError> {
208+
if payload.len() != size_of::<u64>() {
209+
return Err(format!("invalid u64: {payload:?}").into());
210+
}
211+
Ok(BigEndian::read_u64(payload))
212+
}
213+
214+
impl<'a, T> Parseable<NlaBuffer<&'a T>> for RouteIp6Tunnel
215+
where
216+
T: AsRef<[u8]> + ?Sized,
217+
{
218+
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
219+
let payload = buf.value();
220+
Ok(match buf.kind() {
221+
LWTUNNEL_IP6_UNSPEC => Self::Unspecified,
222+
LWTUNNEL_IP6_ID => Self::Id(
223+
parse_u64_be(payload)
224+
.context("invalid LWTUNNEL_IP6_ID value")?,
225+
),
226+
LWTUNNEL_IP6_DST => Self::Destination(
227+
parse_ipv6_addr(payload)
228+
.context("invalid LWTUNNEL_IP6_DST value")?,
229+
),
230+
LWTUNNEL_IP6_SRC => Self::Source(
231+
parse_ipv6_addr(payload)
232+
.context("invalid LWTUNNEL_IP6_SRC value")?,
233+
),
234+
LWTUNNEL_IP6_HOPLIMIT => Self::Hoplimit(
235+
parse_u8(payload)
236+
.context("invalid LWTUNNEL_IP6_HOPLIMIT value")?,
237+
),
238+
LWTUNNEL_IP6_TC => Self::Tc(
239+
parse_u8(payload).context("invalid LWTUNNEL_IP6_TC value")?,
240+
),
241+
LWTUNNEL_IP6_FLAGS => Self::Flags(
242+
parse_u16_be(payload)
243+
.context("invalid LWTUNNEL_IP6_FLAGS value")?,
244+
),
245+
_ => {
246+
return Err(DecodeError::from(
247+
"invalid NLA value (unknown type) value",
248+
))
249+
}
250+
})
251+
}
252+
}
253+
109254
#[derive(Debug, PartialEq, Eq, Clone)]
110255
#[non_exhaustive]
111256
pub enum RouteLwTunnelEncap {
112257
Mpls(RouteMplsIpTunnel),
258+
Ip6(RouteIp6Tunnel),
113259
Other(DefaultNla),
114260
}
115261

116262
impl Nla for RouteLwTunnelEncap {
117263
fn value_len(&self) -> usize {
118264
match self {
119265
Self::Mpls(v) => v.value_len(),
266+
Self::Ip6(v) => v.value_len(),
120267
Self::Other(v) => v.value_len(),
121268
}
122269
}
123270

124271
fn emit_value(&self, buffer: &mut [u8]) {
125272
match self {
126273
Self::Mpls(v) => v.emit_value(buffer),
274+
Self::Ip6(v) => v.emit_value(buffer),
127275
Self::Other(v) => v.emit_value(buffer),
128276
}
129277
}
130278

131279
fn kind(&self) -> u16 {
132280
match self {
133281
Self::Mpls(v) => v.kind(),
282+
Self::Ip6(v) => v.kind(),
134283
Self::Other(v) => v.kind(),
135284
}
136285
}
@@ -149,6 +298,7 @@ where
149298
RouteLwEnCapType::Mpls => {
150299
Self::Mpls(RouteMplsIpTunnel::parse(buf)?)
151300
}
301+
RouteLwEnCapType::Ip6 => Self::Ip6(RouteIp6Tunnel::parse(buf)?),
152302
_ => Self::Other(DefaultNla::parse(buf)?),
153303
})
154304
}

src/route/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ pub use self::cache_info::{RouteCacheInfo, RouteCacheInfoBuffer};
2424
pub use self::header::{
2525
RouteHeader, RouteMessageBuffer, RouteProtocol, RouteScope, RouteType,
2626
};
27-
pub use self::lwtunnel::{RouteLwEnCapType, RouteLwTunnelEncap};
27+
pub use self::lwtunnel::{
28+
RouteIp6Tunnel, RouteLwEnCapType, RouteLwTunnelEncap,
29+
};
2830
pub use self::message::RouteMessage;
2931
pub use self::metrics::RouteMetric;
3032
pub use self::mfc_stats::{RouteMfcStats, RouteMfcStatsBuffer};

src/route/tests/ip6_tunnel.rs

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use std::net::{Ipv4Addr, Ipv6Addr};
2+
use std::str::FromStr;
3+
4+
use netlink_packet_utils::{Emitable, Parseable};
5+
6+
use crate::route::{
7+
RouteAttribute, RouteFlags, RouteHeader, RouteIp6Tunnel, RouteLwEnCapType,
8+
RouteLwTunnelEncap, RouteMessage, RouteMessageBuffer, RouteProtocol,
9+
RouteScope, RouteType,
10+
};
11+
use crate::AddressFamily;
12+
13+
// Setup:
14+
// ip link add dummy1 type dummy
15+
// ip link set dummy1 up
16+
// ip route add 192.0.2.0/24 encap ip6 \
17+
// dst 2001:db8:1::1 src 2001:db8:1::2 \
18+
// id 100 tc 7 hoplimit 253 csum dev dummy1
19+
// wireshark capture(netlink message header removed) of nlmon against command:
20+
// ip route show dev dummy1
21+
#[test]
22+
fn test_ip6_tunnel() {
23+
let raw = vec![
24+
0x02, 0x18, 0x00, 0x00, 0xfe, 0x03, 0xfd, 0x01, 0x00, 0x00, 0x00, 0x00,
25+
0x08, 0x00, 0x0f, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00,
26+
0xc0, 0x00, 0x02, 0x00, 0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
27+
0x50, 0x00, 0x16, 0x00, 0x0c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
28+
0x00, 0x00, 0x00, 0x64, 0x14, 0x00, 0x02, 0x00, 0x20, 0x01, 0x0d, 0xb8,
29+
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
30+
0x14, 0x00, 0x03, 0x00, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00,
31+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x05, 0x00, 0x05, 0x00,
32+
0x07, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0xfd, 0x00, 0x00, 0x00,
33+
0x06, 0x00, 0x06, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00,
34+
0x04, 0x00, 0x00, 0x00,
35+
];
36+
37+
let expected = RouteMessage {
38+
header: RouteHeader {
39+
address_family: AddressFamily::Inet,
40+
destination_prefix_length: 24,
41+
source_prefix_length: 0,
42+
tos: 0,
43+
table: 254,
44+
protocol: RouteProtocol::Boot,
45+
scope: RouteScope::Link,
46+
kind: RouteType::Unicast,
47+
flags: RouteFlags::empty(),
48+
},
49+
attributes: vec![
50+
RouteAttribute::Table(254),
51+
RouteAttribute::Destination(
52+
Ipv4Addr::from_str("192.0.2.0").unwrap().into(),
53+
),
54+
RouteAttribute::Oif(8),
55+
RouteAttribute::Encap(vec![
56+
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Id(100)),
57+
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Destination(
58+
Ipv6Addr::from_str("2001:db8:1::1").unwrap(),
59+
)),
60+
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Source(
61+
Ipv6Addr::from_str("2001:db8:1::2").unwrap(),
62+
)),
63+
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Tc(7)),
64+
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Hoplimit(253)),
65+
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Flags(1u16)),
66+
]),
67+
RouteAttribute::EncapType(RouteLwEnCapType::Ip6),
68+
],
69+
};
70+
71+
assert_eq!(
72+
expected,
73+
RouteMessage::parse(&RouteMessageBuffer::new(&raw)).unwrap()
74+
);
75+
76+
let mut buf = vec![0; expected.buffer_len()];
77+
78+
expected.emit(&mut buf);
79+
80+
assert_eq!(buf, raw);
81+
}

src/route/tests/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ mod cache_info;
55
#[cfg(test)]
66
mod expires;
77
#[cfg(test)]
8+
mod ip6_tunnel;
9+
#[cfg(test)]
810
mod loopback;
911
#[cfg(test)]
1012
mod mpls;

0 commit comments

Comments
 (0)