Skip to content

Add support for LWTUNNEL_ENCAP_IP6 #148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions src/route/lwtunnel.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
// SPDX-License-Identifier: MIT

use std::net::Ipv6Addr;

use anyhow::Context;
use byteorder::{BigEndian, ByteOrder, NetworkEndian};
use netlink_packet_utils::{
nla::{DefaultNla, Nla, NlaBuffer, NlasIterator},
parsers::{parse_u16_be, parse_u8},
traits::{Emitable, Parseable, ParseableParametrized},
DecodeError,
};

use crate::ip::parse_ipv6_addr;

use super::RouteMplsIpTunnel;

const LWTUNNEL_ENCAP_NONE: u16 = 0;
Expand All @@ -21,6 +27,20 @@ const LWTUNNEL_ENCAP_RPL: u16 = 8;
const LWTUNNEL_ENCAP_IOAM6: u16 = 9;
const LWTUNNEL_ENCAP_XFRM: u16 = 10;

const LWTUNNEL_IP6_UNSPEC: u16 = 0;
const LWTUNNEL_IP6_ID: u16 = 1;
const LWTUNNEL_IP6_DST: u16 = 2;
const LWTUNNEL_IP6_SRC: u16 = 3;
const LWTUNNEL_IP6_HOPLIMIT: u16 = 4;
const LWTUNNEL_IP6_TC: u16 = 5;
const LWTUNNEL_IP6_FLAGS: u16 = 6;
//const LWTUNNEL_IP6_PAD: u16 = 7;
//const LWTUNNEL_IP6_OPTS: u16 = 8;

const TUNNEL_CSUM: u16 = 1;
const TUNNEL_KEY: u16 = 4;
const TUNNEL_SEQ: u16 = 8;

#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
#[non_exhaustive]
pub enum RouteLwEnCapType {
Expand Down Expand Up @@ -106,31 +126,160 @@ impl std::fmt::Display for RouteLwEnCapType {
}
}

#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum RouteIp6Tunnel {
#[default]
Unspecified,
Id(u64),
Destination(Ipv6Addr),
Source(Ipv6Addr),
Hoplimit(u8),
Tc(u8),
Flags(u16),
}

impl std::fmt::Display for RouteIp6Tunnel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unspecified => write!(f, "unspecified"),
Self::Id(id) => write!(f, "id {id}"),
Self::Destination(dst) => write!(f, "dst {dst}"),
Self::Source(src) => write!(f, "src, {src}"),
Self::Hoplimit(hoplimit) => write!(f, "hoplimit {hoplimit}"),
Self::Tc(tc) => write!(f, "tc {tc}"),
Self::Flags(flags) => {
if flags & TUNNEL_KEY != 0 {
write!(f, "key ")?;
}
if flags & TUNNEL_CSUM != 0 {
write!(f, "csum ")?;
}

if flags & TUNNEL_SEQ != 0 {
write!(f, "seq ")?;
}

Ok(())
}
}
}
}

impl Nla for RouteIp6Tunnel {
fn value_len(&self) -> usize {
match self {
Self::Unspecified => 0,
Self::Id(_) => const { size_of::<u64>() },
Self::Destination(_) => const { size_of::<Ipv6Addr>() },
Self::Source(_) => const { size_of::<Ipv6Addr>() },
Self::Hoplimit(_) => const { size_of::<u8>() },
Self::Tc(_) => const { size_of::<u8>() },
Self::Flags(_) => const { size_of::<u16>() },
}
}

fn kind(&self) -> u16 {
match self {
Self::Unspecified => LWTUNNEL_IP6_UNSPEC,
Self::Id(_) => LWTUNNEL_IP6_ID,
Self::Destination(_) => LWTUNNEL_IP6_DST,
Self::Source(_) => LWTUNNEL_IP6_SRC,
Self::Hoplimit(_) => LWTUNNEL_IP6_HOPLIMIT,
Self::Tc(_) => LWTUNNEL_IP6_TC,
Self::Flags(_) => LWTUNNEL_IP6_FLAGS,
}
}

fn emit_value(&self, buffer: &mut [u8]) {
match self {
Self::Unspecified => {}
Self::Id(id) => NetworkEndian::write_u64(buffer, *id),
Self::Destination(ip) | Self::Source(ip) => {
buffer.copy_from_slice(&ip.octets());
}
Self::Hoplimit(value) | Self::Tc(value) => buffer[0] = *value,
Self::Flags(flags) => BigEndian::write_u16(buffer, *flags),
}
}
}

// should probably be in utils
fn parse_u64_be(payload: &[u8]) -> Result<u64, DecodeError> {
if payload.len() != size_of::<u64>() {
return Err(format!("invalid u64: {payload:?}").into());
}
Ok(BigEndian::read_u64(payload))
}

impl<'a, T> Parseable<NlaBuffer<&'a T>> for RouteIp6Tunnel
where
T: AsRef<[u8]> + ?Sized,
{
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
let payload = buf.value();
Ok(match buf.kind() {
LWTUNNEL_IP6_UNSPEC => Self::Unspecified,
LWTUNNEL_IP6_ID => Self::Id(
parse_u64_be(payload)
.context("invalid LWTUNNEL_IP6_ID value")?,
),
LWTUNNEL_IP6_DST => Self::Destination(
parse_ipv6_addr(payload)
.context("invalid LWTUNNEL_IP6_DST value")?,
),
LWTUNNEL_IP6_SRC => Self::Source(
parse_ipv6_addr(payload)
.context("invalid LWTUNNEL_IP6_SRC value")?,
),
LWTUNNEL_IP6_HOPLIMIT => Self::Hoplimit(
parse_u8(payload)
.context("invalid LWTUNNEL_IP6_HOPLIMIT value")?,
),
LWTUNNEL_IP6_TC => Self::Tc(
parse_u8(payload).context("invalid LWTUNNEL_IP6_TC value")?,
),
LWTUNNEL_IP6_FLAGS => Self::Flags(
parse_u16_be(payload)
.context("invalid LWTUNNEL_IP6_FLAGS value")?,
),
_ => {
return Err(DecodeError::from(
"invalid NLA value (unknown type) value",
))
}
})
}
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum RouteLwTunnelEncap {
Mpls(RouteMplsIpTunnel),
Ip6(RouteIp6Tunnel),
Other(DefaultNla),
}

impl Nla for RouteLwTunnelEncap {
fn value_len(&self) -> usize {
match self {
Self::Mpls(v) => v.value_len(),
Self::Ip6(v) => v.value_len(),
Self::Other(v) => v.value_len(),
}
}

fn emit_value(&self, buffer: &mut [u8]) {
match self {
Self::Mpls(v) => v.emit_value(buffer),
Self::Ip6(v) => v.emit_value(buffer),
Self::Other(v) => v.emit_value(buffer),
}
}

fn kind(&self) -> u16 {
match self {
Self::Mpls(v) => v.kind(),
Self::Ip6(v) => v.kind(),
Self::Other(v) => v.kind(),
}
}
Expand All @@ -149,6 +298,7 @@ where
RouteLwEnCapType::Mpls => {
Self::Mpls(RouteMplsIpTunnel::parse(buf)?)
}
RouteLwEnCapType::Ip6 => Self::Ip6(RouteIp6Tunnel::parse(buf)?),
_ => Self::Other(DefaultNla::parse(buf)?),
})
}
Expand Down
4 changes: 3 additions & 1 deletion src/route/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ pub use self::cache_info::{RouteCacheInfo, RouteCacheInfoBuffer};
pub use self::header::{
RouteHeader, RouteMessageBuffer, RouteProtocol, RouteScope, RouteType,
};
pub use self::lwtunnel::{RouteLwEnCapType, RouteLwTunnelEncap};
pub use self::lwtunnel::{
RouteIp6Tunnel, RouteLwEnCapType, RouteLwTunnelEncap,
};
pub use self::message::RouteMessage;
pub use self::metrics::RouteMetric;
pub use self::mfc_stats::{RouteMfcStats, RouteMfcStatsBuffer};
Expand Down
81 changes: 81 additions & 0 deletions src/route/tests/ip6_tunnel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;

use netlink_packet_utils::{Emitable, Parseable};

use crate::route::{
RouteAttribute, RouteFlags, RouteHeader, RouteIp6Tunnel, RouteLwEnCapType,
RouteLwTunnelEncap, RouteMessage, RouteMessageBuffer, RouteProtocol,
RouteScope, RouteType,
};
use crate::AddressFamily;

// Setup:
// ip link add dummy1 type dummy
// ip link set dummy1 up
// ip route add 192.0.2.0/24 encap ip6 \
// dst 2001:db8:1::1 src 2001:db8:1::2 \
// id 100 tc 7 hoplimit 253 csum dev dummy1
// wireshark capture(netlink message header removed) of nlmon against command:
// ip route show dev dummy1
#[test]
fn test_ip6_tunnel() {
let raw = vec![
0x02, 0x18, 0x00, 0x00, 0xfe, 0x03, 0xfd, 0x01, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x0f, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00,
0xc0, 0x00, 0x02, 0x00, 0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
0x50, 0x00, 0x16, 0x00, 0x0c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x64, 0x14, 0x00, 0x02, 0x00, 0x20, 0x01, 0x0d, 0xb8,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x14, 0x00, 0x03, 0x00, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x05, 0x00, 0x05, 0x00,
0x07, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0xfd, 0x00, 0x00, 0x00,
0x06, 0x00, 0x06, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00,
0x04, 0x00, 0x00, 0x00,
];

let expected = RouteMessage {
header: RouteHeader {
address_family: AddressFamily::Inet,
destination_prefix_length: 24,
source_prefix_length: 0,
tos: 0,
table: 254,
protocol: RouteProtocol::Boot,
scope: RouteScope::Link,
kind: RouteType::Unicast,
flags: RouteFlags::empty(),
},
attributes: vec![
RouteAttribute::Table(254),
RouteAttribute::Destination(
Ipv4Addr::from_str("192.0.2.0").unwrap().into(),
),
RouteAttribute::Oif(8),
RouteAttribute::Encap(vec![
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Id(100)),
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Destination(
Ipv6Addr::from_str("2001:db8:1::1").unwrap(),
)),
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Source(
Ipv6Addr::from_str("2001:db8:1::2").unwrap(),
)),
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Tc(7)),
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Hoplimit(253)),
RouteLwTunnelEncap::Ip6(RouteIp6Tunnel::Flags(1u16)),
]),
RouteAttribute::EncapType(RouteLwEnCapType::Ip6),
],
};

assert_eq!(
expected,
RouteMessage::parse(&RouteMessageBuffer::new(&raw)).unwrap()
);

let mut buf = vec![0; expected.buffer_len()];

expected.emit(&mut buf);

assert_eq!(buf, raw);
}
2 changes: 2 additions & 0 deletions src/route/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ mod cache_info;
#[cfg(test)]
mod expires;
#[cfg(test)]
mod ip6_tunnel;
#[cfg(test)]
mod loopback;
#[cfg(test)]
mod mpls;
Expand Down