Skip to content
Merged
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
65 changes: 65 additions & 0 deletions examples/dump_routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT

use netlink_packet_core::{
NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST,
};
use netlink_packet_route::{route::RouteMessage, RouteNetlinkMessage};
use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr};

fn main() {
let mut socket = Socket::new(NETLINK_ROUTE).unwrap();
let _port_number = socket.bind_auto().unwrap().port_number();
socket.connect(&SocketAddr::new(0, 0)).unwrap();

let mut nl_hdr = NetlinkHeader::default();
nl_hdr.flags = NLM_F_REQUEST | NLM_F_DUMP;
let mut packet = NetlinkMessage::new(
nl_hdr,
NetlinkPayload::from(RouteNetlinkMessage::GetRoute(
RouteMessage::default(),
)),
);

packet.finalize();

let mut buf = vec![0; packet.header.length as usize];

// Before calling serialize, it is important to check that the buffer in
// which we're emitting is big enough for the packet, other
// `serialize()` panics.

assert!(buf.len() == packet.buffer_len());

packet.serialize(&mut buf[..]);

println!(">>> {packet:?}");
if let Err(e) = socket.send(&buf[..], 0) {
println!("SEND ERROR {e}");
}

let mut receive_buffer = vec![0; 4096];
let mut offset = 0;

// we set the NLM_F_DUMP flag so we expect a multipart rx_packet in
// response.
while let Ok(size) = socket.recv(&mut &mut receive_buffer[..], 0) {
loop {
let bytes = &receive_buffer[offset..];
let rx_packet =
<NetlinkMessage<RouteNetlinkMessage>>::deserialize(bytes)
.unwrap();
println!("<<< {rx_packet:?}");

if matches!(rx_packet.payload, NetlinkPayload::Done(_)) {
println!("Done!");
return;
}

offset += rx_packet.header.length as usize;
if offset == size || rx_packet.header.length == 0 {
offset = 0;
break;
}
}
}
}
165 changes: 165 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::{fmt::Debug, 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, RouteSeg6IpTunnel};

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 IP_TUNNEL_CSUM_BIT: u16 = 1;
const IP_TUNNEL_KEY_BIT: u16 = 4;
const IP_TUNNEL_SEQ_BIT: u16 = 8;

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

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

bitflags! {
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RouteIp6TunnelFlags : u16 {
const Key = IP_TUNNEL_KEY_BIT;
const Checksum = IP_TUNNEL_CSUM_BIT;
const Sequence = IP_TUNNEL_SEQ_BIT;
const _ = !0;
}
}

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.contains(RouteIp6TunnelFlags::Key) {
write!(f, "key ")?;
}
if flags.contains(RouteIp6TunnelFlags::Checksum) {
write!(f, "csum ")?;
}

if flags.contains(RouteIp6TunnelFlags::Sequence) {
write!(f, "seq ")?;
}

Ok(())
}
Self::Other(other) => other.fmt(f),
}
}
}

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>() },
Self::Other(_) => const { size_of::<DefaultNla>() },
}
}

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,
Self::Other(other) => other.kind(),
}
}

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.bits()),
Self::Other(other) => other.emit_value(buffer),
}
}
}

// 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(RouteIp6TunnelFlags::from_bits_retain(
parse_u16_be(payload)
.context("invalid LWTUNNEL_IP6_FLAGS value")?,
))
}
_ => Self::Other(DefaultNla::parse(buf)?),
})
}
}

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

Expand All @@ -119,6 +280,7 @@ impl Nla for RouteLwTunnelEncap {
match self {
Self::Mpls(v) => v.value_len(),
Self::Seg6(v) => v.value_len(),
Self::Ip6(v) => v.value_len(),
Self::Other(v) => v.value_len(),
}
}
Expand All @@ -127,6 +289,7 @@ impl Nla for RouteLwTunnelEncap {
match self {
Self::Mpls(v) => v.emit_value(buffer),
Self::Seg6(v) => v.emit_value(buffer),
Self::Ip6(v) => v.emit_value(buffer),
Self::Other(v) => v.emit_value(buffer),
}
}
Expand All @@ -135,6 +298,7 @@ impl Nla for RouteLwTunnelEncap {
match self {
Self::Mpls(v) => v.kind(),
Self::Seg6(v) => v.kind(),
Self::Ip6(v) => v.kind(),
Self::Other(v) => v.kind(),
}
}
Expand All @@ -156,6 +320,7 @@ where
RouteLwEnCapType::Seg6 => {
Self::Seg6(RouteSeg6IpTunnel::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 @@ -25,7 +25,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
86 changes: 86 additions & 0 deletions src/route/tests/ip6_tunnel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT

use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;

use netlink_packet_utils::{Emitable, Parseable};

use crate::route::lwtunnel::RouteIp6TunnelFlags;
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(
RouteIp6TunnelFlags::Checksum,
)),
]),
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
Loading