diff --git a/src/route/attribute.rs b/src/route/attribute.rs index b949fc9d..638e179b 100644 --- a/src/route/attribute.rs +++ b/src/route/attribute.rs @@ -184,9 +184,13 @@ impl Nla for RouteAttribute { fn is_nested(&self) -> bool { if let Self::Encap(encap) = self { - encap - .iter() - .any(|e| matches!(e, RouteLwTunnelEncap::Seg6(_))) + encap.iter().any(|e| { + matches!( + e, + RouteLwTunnelEncap::Seg6(_) + | RouteLwTunnelEncap::Seg6Local(_) + ) + }) } else { false } diff --git a/src/route/lwtunnel.rs b/src/route/lwtunnel.rs index 8fcf09f5..687678d4 100644 --- a/src/route/lwtunnel.rs +++ b/src/route/lwtunnel.rs @@ -8,7 +8,7 @@ use netlink_packet_core::{ NlasIterator, Parseable, ParseableParametrized, }; -use super::{RouteMplsIpTunnel, RouteSeg6IpTunnel}; +use super::{RouteMplsIpTunnel, RouteSeg6IpTunnel, RouteSeg6LocalIpTunnel}; use crate::ip::parse_ipv6_addr; const LWTUNNEL_ENCAP_NONE: u16 = 0; @@ -259,6 +259,7 @@ where pub enum RouteLwTunnelEncap { Mpls(RouteMplsIpTunnel), Seg6(RouteSeg6IpTunnel), + Seg6Local(RouteSeg6LocalIpTunnel), Ip6(RouteIp6Tunnel), Other(DefaultNla), } @@ -268,6 +269,7 @@ impl Nla for RouteLwTunnelEncap { match self { Self::Mpls(v) => v.value_len(), Self::Seg6(v) => v.value_len(), + Self::Seg6Local(v) => v.value_len(), Self::Ip6(v) => v.value_len(), Self::Other(v) => v.value_len(), } @@ -277,6 +279,7 @@ impl Nla for RouteLwTunnelEncap { match self { Self::Mpls(v) => v.emit_value(buffer), Self::Seg6(v) => v.emit_value(buffer), + Self::Seg6Local(v) => v.emit_value(buffer), Self::Ip6(v) => v.emit_value(buffer), Self::Other(v) => v.emit_value(buffer), } @@ -286,6 +289,7 @@ impl Nla for RouteLwTunnelEncap { match self { Self::Mpls(v) => v.kind(), Self::Seg6(v) => v.kind(), + Self::Seg6Local(v) => v.kind(), Self::Ip6(v) => v.kind(), Self::Other(v) => v.kind(), } @@ -308,6 +312,9 @@ where RouteLwEnCapType::Seg6 => { Self::Seg6(RouteSeg6IpTunnel::parse(buf)?) } + RouteLwEnCapType::Seg6Local => { + Self::Seg6Local(RouteSeg6LocalIpTunnel::parse(buf)?) + } RouteLwEnCapType::Ip6 => Self::Ip6(RouteIp6Tunnel::parse(buf)?), _ => Self::Other(DefaultNla::parse(buf)?), }) diff --git a/src/route/mod.rs b/src/route/mod.rs index 9322873b..863b5176 100644 --- a/src/route/mod.rs +++ b/src/route/mod.rs @@ -14,6 +14,7 @@ mod next_hops; mod preference; mod realm; mod seg6; +mod seg6local; mod via; #[cfg(test)] @@ -37,5 +38,6 @@ pub use self::{ preference::RoutePreference, realm::RouteRealm, seg6::{RouteSeg6IpTunnel, Seg6Header, Seg6Mode}, + seg6local::{RouteSeg6LocalIpTunnel, Seg6LocalAction, SRH}, via::{RouteVia, RouteViaBuffer}, }; diff --git a/src/route/seg6local.rs b/src/route/seg6local.rs new file mode 100644 index 00000000..1abd4397 --- /dev/null +++ b/src/route/seg6local.rs @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: MIT + +// Some sources for understanding the format of Seg6local in Netlink +// +// torvalds/linux: +// include/uapi/linux/seg6_local.h +// +// iproute2/iproute2 +// ip/iproute_lwtunnel.c parse_encap_seg6local() + +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use netlink_packet_core::{ + emit_u32, parse_u32, DecodeError, DefaultNla, ErrorContext, Nla, NlaBuffer, + Parseable, +}; + +use crate::ip::{emit_ip_addr, parse_ipv4_addr, parse_ipv6_addr}; + +// Netlink Encap sub-types +const SEG6_LOCAL_UNSPEC: u16 = 0; +const SEG6_LOCAL_ACTION: u16 = 1; +const SEG6_LOCAL_SRH: u16 = 2; +const SEG6_LOCAL_TABLE: u16 = 3; +const SEG6_LOCAL_NH4: u16 = 4; +const SEG6_LOCAL_NH6: u16 = 5; +const SEG6_LOCAL_IIF: u16 = 6; +const SEG6_LOCAL_OIF: u16 = 7; +// const SEG6_LOCAL_BPF: u16 = 8; +const SEG6_LOCAL_VRFTABLE: u16 = 9; +// const SEG6_LOCAL_COUNTERS: u16 = 10; +// const SEG6_LOCAL_FLAVORS: u16 = 11; + +// Seg6local action types +const SEG6_LOCAL_ACTION_UNSPEC: u32 = 0; +const SEG6_LOCAL_ACTION_END: u32 = 1; +const SEG6_LOCAL_ACTION_END_X: u32 = 2; +const SEG6_LOCAL_ACTION_END_T: u32 = 3; +const SEG6_LOCAL_ACTION_END_DX2: u32 = 4; +const SEG6_LOCAL_ACTION_END_DX6: u32 = 5; +const SEG6_LOCAL_ACTION_END_DX4: u32 = 6; +const SEG6_LOCAL_ACTION_END_DT6: u32 = 7; +const SEG6_LOCAL_ACTION_END_DT4: u32 = 8; +const SEG6_LOCAL_ACTION_END_B6: u32 = 9; +const SEG6_LOCAL_ACTION_END_B6_ENCAP: u32 = 10; +const SEG6_LOCAL_ACTION_END_BM: u32 = 11; +const SEG6_LOCAL_ACTION_END_S: u32 = 12; +const SEG6_LOCAL_ACTION_END_AS: u32 = 13; +const SEG6_LOCAL_ACTION_END_AM: u32 = 14; +const SEG6_LOCAL_ACTION_END_BPF: u32 = 15; +const SEG6_LOCAL_ACTION_END_DT46: u32 = 16; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub enum Seg6LocalAction { + Unspec, + End, + EndX, + EndT, + EndDX2, + EndDX6, + EndDX4, + EndDT6, + EndDT4, + EndB6, + EndB6Encap, + EndBM, + EndS, + EndAS, + EndAM, + EndBPF, + EndDT46, + Other(u32), +} + +impl From for u32 { + fn from(value: Seg6LocalAction) -> Self { + match value { + Seg6LocalAction::Unspec => SEG6_LOCAL_ACTION_UNSPEC, + Seg6LocalAction::End => SEG6_LOCAL_ACTION_END, + Seg6LocalAction::EndX => SEG6_LOCAL_ACTION_END_X, + Seg6LocalAction::EndT => SEG6_LOCAL_ACTION_END_T, + Seg6LocalAction::EndDX2 => SEG6_LOCAL_ACTION_END_DX2, + Seg6LocalAction::EndDX6 => SEG6_LOCAL_ACTION_END_DX6, + Seg6LocalAction::EndDX4 => SEG6_LOCAL_ACTION_END_DX4, + Seg6LocalAction::EndDT6 => SEG6_LOCAL_ACTION_END_DT6, + Seg6LocalAction::EndDT4 => SEG6_LOCAL_ACTION_END_DT4, + Seg6LocalAction::EndB6 => SEG6_LOCAL_ACTION_END_B6, + Seg6LocalAction::EndB6Encap => SEG6_LOCAL_ACTION_END_B6_ENCAP, + Seg6LocalAction::EndBM => SEG6_LOCAL_ACTION_END_BM, + Seg6LocalAction::EndS => SEG6_LOCAL_ACTION_END_S, + Seg6LocalAction::EndAS => SEG6_LOCAL_ACTION_END_AS, + Seg6LocalAction::EndAM => SEG6_LOCAL_ACTION_END_AM, + Seg6LocalAction::EndBPF => SEG6_LOCAL_ACTION_END_BPF, + Seg6LocalAction::EndDT46 => SEG6_LOCAL_ACTION_END_DT46, + Seg6LocalAction::Other(i) => i, + } + } +} + +impl From for Seg6LocalAction { + fn from(value: u32) -> Self { + match value { + SEG6_LOCAL_ACTION_UNSPEC => Seg6LocalAction::Unspec, + SEG6_LOCAL_ACTION_END => Seg6LocalAction::End, + SEG6_LOCAL_ACTION_END_X => Seg6LocalAction::EndX, + SEG6_LOCAL_ACTION_END_T => Seg6LocalAction::EndT, + SEG6_LOCAL_ACTION_END_DX2 => Seg6LocalAction::EndDX2, + SEG6_LOCAL_ACTION_END_DX6 => Seg6LocalAction::EndDX6, + SEG6_LOCAL_ACTION_END_DX4 => Seg6LocalAction::EndDX4, + SEG6_LOCAL_ACTION_END_DT6 => Seg6LocalAction::EndDT6, + SEG6_LOCAL_ACTION_END_DT4 => Seg6LocalAction::EndDT4, + SEG6_LOCAL_ACTION_END_B6 => Seg6LocalAction::EndB6, + SEG6_LOCAL_ACTION_END_B6_ENCAP => Seg6LocalAction::EndB6Encap, + SEG6_LOCAL_ACTION_END_BM => Seg6LocalAction::EndBM, + SEG6_LOCAL_ACTION_END_S => Seg6LocalAction::EndS, + SEG6_LOCAL_ACTION_END_AS => Seg6LocalAction::EndAS, + SEG6_LOCAL_ACTION_END_AM => Seg6LocalAction::EndAM, + SEG6_LOCAL_ACTION_END_BPF => Seg6LocalAction::EndBPF, + SEG6_LOCAL_ACTION_END_DT46 => Seg6LocalAction::EndDT46, + v => Seg6LocalAction::Other(v), + } + } +} + +/// Netlink attributes for `RTA_ENCAP` with `RTA_ENCAP_TYPE` set to +/// `LWTUNNEL_ENCAP_SEG6_LOCAL`. +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum RouteSeg6LocalIpTunnel { + Unspec, + Action(Seg6LocalAction), + SRH(SRH), + Table(u32), + Nh4(Ipv4Addr), + Nh6(Ipv6Addr), + Iif(u32), + Oif(u32), + // BPF(...), + VrfTable(u32), + // Counters(...), + // Flavors(...) + Other(DefaultNla), +} + +impl Nla for RouteSeg6LocalIpTunnel { + fn value_len(&self) -> usize { + match self { + RouteSeg6LocalIpTunnel::Unspec => 0, + RouteSeg6LocalIpTunnel::Action(_) => 4, + RouteSeg6LocalIpTunnel::SRH(v) => v.value_len(), + RouteSeg6LocalIpTunnel::Table(_) => 4, + RouteSeg6LocalIpTunnel::Nh4(_) => 4, + RouteSeg6LocalIpTunnel::Nh6(_) => 16, + RouteSeg6LocalIpTunnel::Iif(_) => 4, + RouteSeg6LocalIpTunnel::Oif(_) => 4, + // BPF(...) => ..., + RouteSeg6LocalIpTunnel::VrfTable(_) => 4, + // Counters(...) => ..., + // Flavors(...) => ..., + RouteSeg6LocalIpTunnel::Other(v) => v.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + RouteSeg6LocalIpTunnel::Unspec => SEG6_LOCAL_UNSPEC, + RouteSeg6LocalIpTunnel::Action(_) => SEG6_LOCAL_ACTION, + RouteSeg6LocalIpTunnel::SRH(v) => v.kind(), + RouteSeg6LocalIpTunnel::Table(_) => SEG6_LOCAL_TABLE, + RouteSeg6LocalIpTunnel::Nh4(_) => SEG6_LOCAL_NH4, + RouteSeg6LocalIpTunnel::Nh6(_) => SEG6_LOCAL_NH6, + RouteSeg6LocalIpTunnel::Iif(_) => SEG6_LOCAL_IIF, + RouteSeg6LocalIpTunnel::Oif(_) => SEG6_LOCAL_OIF, + // BPF(...) => SEG6_LOCAL_BPF, + RouteSeg6LocalIpTunnel::VrfTable(_) => SEG6_LOCAL_VRFTABLE, + // Counters(...) => SEG6_LOCAL_COUNTERS, + // Flavors(...) => SEG6_LOCAL_FLAVORS, + RouteSeg6LocalIpTunnel::Other(v) => v.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + RouteSeg6LocalIpTunnel::Unspec => (), + RouteSeg6LocalIpTunnel::Action(v) => { + emit_u32(buffer, (*v).into()).unwrap() + } + RouteSeg6LocalIpTunnel::SRH(v) => v.emit_value(buffer), + RouteSeg6LocalIpTunnel::Table(v) => emit_u32(buffer, *v).unwrap(), + RouteSeg6LocalIpTunnel::Nh4(v) => { + emit_ip_addr(&IpAddr::V4(*v), buffer) + } + RouteSeg6LocalIpTunnel::Nh6(v) => { + emit_ip_addr(&IpAddr::V6(*v), buffer) + } + RouteSeg6LocalIpTunnel::Iif(v) => emit_u32(buffer, *v).unwrap(), + RouteSeg6LocalIpTunnel::Oif(v) => emit_u32(buffer, *v).unwrap(), + // BPF(...) => ..., + RouteSeg6LocalIpTunnel::VrfTable(v) => { + emit_u32(buffer, *v).unwrap() + } + // Counters(...) => ..., + // Flavors(...) => ..., + RouteSeg6LocalIpTunnel::Other(v) => v.emit_value(buffer), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for RouteSeg6LocalIpTunnel +{ + fn parse( + buf: &NlaBuffer<&'a T>, + ) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + SEG6_LOCAL_UNSPEC => Self::Unspec, + SEG6_LOCAL_ACTION => Self::Action(parse_u32(payload)?.into()), + SEG6_LOCAL_SRH => Self::SRH(SRH::parse(buf)?), + SEG6_LOCAL_TABLE => Self::Table(parse_u32(payload)?), + SEG6_LOCAL_NH4 => Self::Nh4(parse_ipv4_addr(payload)?), + SEG6_LOCAL_NH6 => Self::Nh6(parse_ipv6_addr(payload)?), + SEG6_LOCAL_IIF => Self::Iif(parse_u32(payload)?), + SEG6_LOCAL_OIF => Self::Oif(parse_u32(payload)?), + // SEG6_LOCAL_BPF => Self::BPF(...), + SEG6_LOCAL_VRFTABLE => Self::VrfTable(parse_u32(payload)?), + // SEG6_LOCAL_COUNTERS => Self::Counters(...), + // SEG6_LOCAL_FLAVORS => Self::Flavors(...), + _ => Self::Other( + DefaultNla::parse(buf) + .context("invalid NLA value (unknown type) value")?, + ), + }) + } +} + +const SEG6_HEADER_LEN: usize = 8; + +buffer!(Seg6MessageBuffer(SEG6_HEADER_LEN) { + nexthdr: (u8, 0), + hdrlen: (u8, 1), + seg_type: (u8, 2), + segments_left: (u8, 3), + first_segment: (u8, 4), + flags: (u8, 5), + tag: (u16, 6..8), + segments: (slice, SEG6_HEADER_LEN..), +}); + +const SEG6_SEGMENT_LEN: usize = 16; + +buffer!(Seg6SegmentBuffer(SEG6_SEGMENT_LEN) { + segment: (slice, 0..SEG6_SEGMENT_LEN), + rest: (slice, SEG6_SEGMENT_LEN..) +}); + +#[derive(Debug, PartialEq, Eq, Clone, Default)] +#[non_exhaustive] +pub struct SRH { + // List of segments + pub segments: Vec, +} + +impl SRH { + fn push_segments(buf: &mut [u8], mut segments: Vec) { + if let Some(segment) = segments.pop() { + let mut segment_buffer = Seg6SegmentBuffer::new(buf); + emit_ip_addr(&IpAddr::V6(segment), segment_buffer.segment_mut()); + Self::push_segments(segment_buffer.rest_mut(), segments); + } + } + + fn get_segments( + buf: &[u8], + segments: &mut Vec, + ) -> Result<(), DecodeError> { + // are there any remaining segments ? + if buf.len() >= SEG6_SEGMENT_LEN { + let segment_buffer = Seg6SegmentBuffer::new(buf); + let segment = parse_ipv6_addr(segment_buffer.segment())?; + segments.push(segment); + Self::get_segments(segment_buffer.rest(), segments)?; + } + Ok(()) + } +} + +impl Nla for SRH { + fn value_len(&self) -> usize { + 8 + 16 * self.segments.len() + } + + fn kind(&self) -> u16 { + SEG6_LOCAL_SRH + } + + fn emit_value(&self, buffer: &mut [u8]) { + let mut seg6_header = Seg6MessageBuffer::new(buffer); + + let number_segments = self.segments.len(); + let srhlen = 8 + 16 * number_segments; + + seg6_header.set_nexthdr(0); + seg6_header.set_hdrlen(((srhlen >> 3) - 1) as u8); + seg6_header.set_seg_type(4); + seg6_header.set_segments_left((number_segments - 1) as u8); + seg6_header.set_first_segment((number_segments - 1) as u8); + seg6_header.set_flags(0); + seg6_header.set_tag(0); + + let segments = self.segments.clone(); + + SRH::push_segments(seg6_header.segments_mut(), segments); + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for SRH { + fn parse( + buf: &NlaBuffer<&'a T>, + ) -> Result { + let payload = buf.value(); + let seg6_header = Seg6MessageBuffer::new(payload); + + let mut segments: Vec = vec![]; + SRH::get_segments(seg6_header.segments(), &mut segments)?; + + let segments: Vec = segments.into_iter().rev().collect(); + + Ok(SRH { segments }) + } +} diff --git a/src/route/tests/mod.rs b/src/route/tests/mod.rs index 6ffa87de..8452fd13 100644 --- a/src/route/tests/mod.rs +++ b/src/route/tests/mod.rs @@ -19,6 +19,8 @@ mod route_flags; #[cfg(test)] mod seg6; #[cfg(test)] +mod seg6local; +#[cfg(test)] mod uid; #[cfg(test)] mod via; diff --git a/src/route/tests/seg6local.rs b/src/route/tests/seg6local.rs new file mode 100644 index 00000000..af2c9f68 --- /dev/null +++ b/src/route/tests/seg6local.rs @@ -0,0 +1,695 @@ +// SPDX-License-Identifier: MIT + +use std::{ + net::{Ipv4Addr, Ipv6Addr}, + str::FromStr, +}; + +use netlink_packet_core::{Emitable, Parseable}; + +use crate::{ + route::{ + seg6local::{RouteSeg6LocalIpTunnel, Seg6LocalAction, SRH}, + RouteAttribute, RouteFlags, RouteHeader, RouteLwEnCapType, + RouteLwTunnelEncap, RouteMessage, RouteMessageBuffer, RouteProtocol, + RouteScope, RouteType, + }, + AddressFamily, +}; + +// Setup: +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![RouteLwTunnelEncap::Seg6Local( + RouteSeg6LocalIpTunnel::Action(Seg6LocalAction::End), + )]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.X nh6 fe80:1:2:: dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_x() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x05, 0x00, + 0xfe, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndX, + )), + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Nh6( + Ipv6Addr::from_str("fe80:1:2::").unwrap().into(), + )), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: +// ip link add vrf-dummy type vrf table 10 +// ip link set vrf-dummy up +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.T table 10 dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_t() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndT, + )), + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Table( + 10, + )), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: + +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.B6 srh segs fe80:1:2::,fe80:2:3:: dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_b6() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x02, 0x00, + 0x00, 0x06, 0x04, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfe, 0x80, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndB6, + )), + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::SRH( + SRH { + segments: vec![ + Ipv6Addr::from_str("fe80:1:2::").unwrap().into(), + Ipv6Addr::from_str("fe80:2:3::").unwrap().into(), + // without encap, we have **must have** an + // additional segment + Ipv6Addr::from_str("::").unwrap().into(), + ], + }, + )), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: + +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.B6.Encaps srh segs fe80:1:2::,fe80:2:3:: dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_b6_encap() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x02, 0x00, + 0x00, 0x04, 0x04, 0x01, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x80, 0x00, 0x02, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfe, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndB6Encap, + )), + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::SRH( + SRH { + segments: vec![ + Ipv6Addr::from_str("fe80:1:2::").unwrap().into(), + Ipv6Addr::from_str("fe80:2:3::").unwrap().into(), + ], + }, + )), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.DX2 oif dummy1 dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_dx2() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndDX2, + )), + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Oif(2)), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.DX6 nh6 fe80:1:2:: dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_dx6() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0x00, 0x05, 0x00, + 0xfe, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndDX6, + )), + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Nh6( + Ipv6Addr::from_str("fe80:1:2::").unwrap().into(), + )), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.DX4 nh4 10.1.2.1 dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_dx4() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, + 0x0a, 0x01, 0x02, 0x01, 0x06, 0x00, 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndDX4, + )), + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Nh4( + Ipv4Addr::from_str("10.1.2.1").unwrap().into(), + )), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: +// ip link add vrf-dummy type vrf table 10 +// ip link set vrf-dummy up +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.DT6 table 10 dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_dt6() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndDT6, + )), + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Table( + 10, + )), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: +// modprobe vrf +// sysctl net.vrf.strict_mode=1 +// ip link add vrf-dummy type vrf table 10 +// ip link set vrf-dummy up +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.DT4 vrftable 10 dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_dt4() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x09, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndDT4, + )), + RouteLwTunnelEncap::Seg6Local( + RouteSeg6LocalIpTunnel::VrfTable(10), + ), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +} + +// Setup: +// modprobe vrf +// sysctl net.vrf.strict_mode=1 +// ip link add vrf-dummy type vrf table 10 +// ip link set vrf-dummy up +// ip link add dummy1 type dummy +// ip link set dummy1 up +// ip route add fe80::/32 encap seg6local \ +// action End.DT46 vrftable 10 dev dummy1 +// wireshark capture(netlink message header removed) of nlmon against command: +// ip -6 route show dev dummy1 +#[test] +fn test_end_dt46() { + let raw = [ + 0x0a, 0x20, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x80, + 0x08, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x09, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + ]; + + let expected = RouteMessage { + header: RouteHeader { + address_family: AddressFamily::Inet6, + destination_prefix_length: 32, + source_prefix_length: 0, + tos: 0, + table: 254, + protocol: RouteProtocol::Boot, + scope: RouteScope::Universe, + kind: RouteType::Unicast, + flags: RouteFlags::empty(), + }, + attributes: vec![ + RouteAttribute::Destination( + Ipv6Addr::from_str("fe80::").unwrap().into(), + ), + RouteAttribute::Encap(vec![ + RouteLwTunnelEncap::Seg6Local(RouteSeg6LocalIpTunnel::Action( + Seg6LocalAction::EndDT46, + )), + RouteLwTunnelEncap::Seg6Local( + RouteSeg6LocalIpTunnel::VrfTable(10), + ), + ]), + RouteAttribute::EncapType(RouteLwEnCapType::Seg6Local), + RouteAttribute::Oif(2), + ], + }; + + 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); +}