Skip to content

Commit 5f7c00d

Browse files
Julius RübergToorero
authored andcommitted
Add support for LWTUNNEL_ENCAP_IP6
Signed-off-by: Julius Rüberg <[email protected]>
1 parent a3d08cf commit 5f7c00d

File tree

4 files changed

+256
-1
lines changed

4 files changed

+256
-1
lines changed

src/route/lwtunnel.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
// SPDX-License-Identifier: MIT
22

3+
use std::{fmt::Debug, 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, RouteSeg6IpTunnel};
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 IP_TUNNEL_CSUM_BIT: u16 = 1;
41+
const IP_TUNNEL_KEY_BIT: u16 = 4;
42+
const IP_TUNNEL_SEQ_BIT: u16 = 8;
43+
2444
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
2545
#[non_exhaustive]
2646
pub enum RouteLwEnCapType {
@@ -106,11 +126,152 @@ impl std::fmt::Display for RouteLwEnCapType {
106126
}
107127
}
108128

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

@@ -119,6 +280,7 @@ impl Nla for RouteLwTunnelEncap {
119280
match self {
120281
Self::Mpls(v) => v.value_len(),
121282
Self::Seg6(v) => v.value_len(),
283+
Self::Ip6(v) => v.value_len(),
122284
Self::Other(v) => v.value_len(),
123285
}
124286
}
@@ -127,6 +289,7 @@ impl Nla for RouteLwTunnelEncap {
127289
match self {
128290
Self::Mpls(v) => v.emit_value(buffer),
129291
Self::Seg6(v) => v.emit_value(buffer),
292+
Self::Ip6(v) => v.emit_value(buffer),
130293
Self::Other(v) => v.emit_value(buffer),
131294
}
132295
}
@@ -135,6 +298,7 @@ impl Nla for RouteLwTunnelEncap {
135298
match self {
136299
Self::Mpls(v) => v.kind(),
137300
Self::Seg6(v) => v.kind(),
301+
Self::Ip6(v) => v.kind(),
138302
Self::Other(v) => v.kind(),
139303
}
140304
}
@@ -156,6 +320,7 @@ where
156320
RouteLwEnCapType::Seg6 => {
157321
Self::Seg6(RouteSeg6IpTunnel::parse(buf)?)
158322
}
323+
RouteLwEnCapType::Ip6 => Self::Ip6(RouteIp6Tunnel::parse(buf)?),
159324
_ => Self::Other(DefaultNla::parse(buf)?),
160325
})
161326
}

src/route/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ pub use self::cache_info::{RouteCacheInfo, RouteCacheInfoBuffer};
2525
pub use self::header::{
2626
RouteHeader, RouteMessageBuffer, RouteProtocol, RouteScope, RouteType,
2727
};
28-
pub use self::lwtunnel::{RouteLwEnCapType, RouteLwTunnelEncap};
28+
pub use self::lwtunnel::{
29+
RouteIp6Tunnel, RouteLwEnCapType, RouteLwTunnelEncap,
30+
};
2931
pub use self::message::RouteMessage;
3032
pub use self::metrics::RouteMetric;
3133
pub use self::mfc_stats::{RouteMfcStats, RouteMfcStatsBuffer};

src/route/tests/ip6_tunnel.rs

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

src/route/tests/mod.rs

Lines changed: 2 additions & 0 deletions
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)