use std::net::{Ipv4Addr, Ipv6Addr};
use super::{
attr::AttrIter,
builder::MessageBuilder,
connection::Connection,
error::{Error, Result},
interface_ref::InterfaceRef,
message::{NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_REQUEST, NLMSG_HDRLEN, NlMsgType},
protocol::Route,
types::{
mpls::lwtunnel_encap,
route::{RtMsg, RtaAttr},
srv6::{Ipv6SrHdr, seg6_iptunnel, seg6_local, seg6_local_action, seg6_mode},
},
};
const NLM_F_REPLACE: u16 = 0x100;
const AF_INET6: u8 = 10;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Srv6Mode {
Inline,
#[default]
Encap,
L2Encap,
EncapRed,
L2EncapRed,
}
impl Srv6Mode {
pub fn to_u32(self) -> u32 {
match self {
Self::Inline => seg6_mode::INLINE,
Self::Encap => seg6_mode::ENCAP,
Self::L2Encap => seg6_mode::L2ENCAP,
Self::EncapRed => seg6_mode::ENCAP_RED,
Self::L2EncapRed => seg6_mode::L2ENCAP_RED,
}
}
pub fn from_u32(val: u32) -> Option<Self> {
match val {
seg6_mode::INLINE => Some(Self::Inline),
seg6_mode::ENCAP => Some(Self::Encap),
seg6_mode::L2ENCAP => Some(Self::L2Encap),
seg6_mode::ENCAP_RED => Some(Self::EncapRed),
seg6_mode::L2ENCAP_RED => Some(Self::L2EncapRed),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Srv6Action {
End,
EndX { nexthop: Ipv6Addr },
EndT { table: u32 },
EndDX2,
EndDX4 { nexthop: Option<Ipv4Addr> },
EndDX6 { nexthop: Option<Ipv6Addr> },
EndDT4 { table: u32 },
EndDT6 { table: u32 },
EndDT46 { table: u32 },
EndB6 { segments: Vec<Ipv6Addr> },
EndB6Encaps { segments: Vec<Ipv6Addr> },
EndBPF,
Unknown { action_type: u32 },
}
impl Srv6Action {
pub fn action_type(&self) -> u32 {
match self {
Self::End => seg6_local_action::END,
Self::EndX { .. } => seg6_local_action::END_X,
Self::EndT { .. } => seg6_local_action::END_T,
Self::EndDX2 => seg6_local_action::END_DX2,
Self::EndDX4 { .. } => seg6_local_action::END_DX4,
Self::EndDX6 { .. } => seg6_local_action::END_DX6,
Self::EndDT4 { .. } => seg6_local_action::END_DT4,
Self::EndDT6 { .. } => seg6_local_action::END_DT6,
Self::EndDT46 { .. } => seg6_local_action::END_DT46,
Self::EndB6 { .. } => seg6_local_action::END_B6,
Self::EndB6Encaps { .. } => seg6_local_action::END_B6_ENCAPS,
Self::EndBPF => seg6_local_action::END_BPF,
Self::Unknown { action_type } => *action_type,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::End => "End",
Self::EndX { .. } => "End.X",
Self::EndT { .. } => "End.T",
Self::EndDX2 => "End.DX2",
Self::EndDX4 { .. } => "End.DX4",
Self::EndDX6 { .. } => "End.DX6",
Self::EndDT4 { .. } => "End.DT4",
Self::EndDT6 { .. } => "End.DT6",
Self::EndDT46 { .. } => "End.DT46",
Self::EndB6 { .. } => "End.B6",
Self::EndB6Encaps { .. } => "End.B6.Encaps",
Self::EndBPF => "End.BPF",
Self::Unknown { .. } => "Unknown",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Srv6Encap {
mode: Srv6Mode,
segments: Vec<Ipv6Addr>,
}
impl Srv6Encap {
pub fn new() -> Self {
Self::default()
}
pub fn encap() -> Self {
Self {
mode: Srv6Mode::Encap,
segments: Vec::new(),
}
}
pub fn inline() -> Self {
Self {
mode: Srv6Mode::Inline,
segments: Vec::new(),
}
}
pub fn l2_encap() -> Self {
Self {
mode: Srv6Mode::L2Encap,
segments: Vec::new(),
}
}
pub fn mode(mut self, mode: Srv6Mode) -> Self {
self.mode = mode;
self
}
pub fn segment(mut self, seg: Ipv6Addr) -> Self {
self.segments.push(seg);
self
}
pub fn segments(mut self, segs: &[Ipv6Addr]) -> Self {
self.segments.extend_from_slice(segs);
self
}
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
pub fn get_segments(&self) -> &[Ipv6Addr] {
&self.segments
}
pub fn get_mode(&self) -> Srv6Mode {
self.mode
}
fn build_srh(&self) -> Vec<u8> {
if self.segments.is_empty() {
return Vec::new();
}
let num_segments = self.segments.len() as u8;
let hdr = Ipv6SrHdr::new(num_segments);
let mut data = Vec::with_capacity(Ipv6SrHdr::SIZE + self.segments.len() * 16);
data.extend_from_slice(hdr.as_bytes());
for seg in self.segments.iter().rev() {
data.extend_from_slice(&seg.octets());
}
data
}
pub(crate) fn write_to(&self, builder: &mut MessageBuilder) {
if self.segments.is_empty() {
return;
}
builder.append_attr_u16(RtaAttr::EncapType as u16, lwtunnel_encap::SEG6);
let encap_nest = builder.nest_start(RtaAttr::Encap as u16);
let mut srh_data = Vec::new();
srh_data.extend_from_slice(&self.mode.to_u32().to_ne_bytes());
srh_data.extend_from_slice(&self.build_srh());
builder.append_attr(seg6_iptunnel::SRH, &srh_data);
builder.nest_end(encap_nest);
}
}
#[derive(Debug, Clone)]
pub struct Srv6LocalRoute {
pub sid: Ipv6Addr,
pub prefix_len: u8,
pub action: Srv6Action,
pub oif: Option<u32>,
pub iif: Option<u32>,
pub protocol: u8,
}
impl Srv6LocalRoute {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < RtMsg::SIZE {
return Err(Error::Truncated {
expected: RtMsg::SIZE,
actual: data.len(),
});
}
let rtmsg = RtMsg::from_bytes(data)?;
let attrs_data = &data[RtMsg::SIZE..];
let mut sid = Ipv6Addr::UNSPECIFIED;
let mut action_type = 0u32;
let mut table = None;
let mut nh4 = None;
let mut nh6 = None;
let mut oif = None;
let mut iif = None;
let mut srh_segments = Vec::new();
for (attr_type, payload) in AttrIter::new(attrs_data) {
match RtaAttr::from(attr_type) {
RtaAttr::Dst if payload.len() >= 16 => {
let bytes: [u8; 16] = payload[..16].try_into().unwrap_or([0; 16]);
sid = Ipv6Addr::from(bytes);
}
RtaAttr::Oif if payload.len() >= 4 => {
oif = Some(u32::from_ne_bytes(
payload[..4].try_into().unwrap_or([0; 4]),
));
}
RtaAttr::Iif if payload.len() >= 4 => {
iif = Some(u32::from_ne_bytes(
payload[..4].try_into().unwrap_or([0; 4]),
));
}
RtaAttr::Encap => {
for (local_attr, local_payload) in AttrIter::new(payload) {
match local_attr {
x if x == seg6_local::ACTION && local_payload.len() >= 4 => {
action_type = u32::from_ne_bytes(
local_payload[..4].try_into().unwrap_or([0; 4]),
);
}
x if x == seg6_local::TABLE && local_payload.len() >= 4 => {
table = Some(u32::from_ne_bytes(
local_payload[..4].try_into().unwrap_or([0; 4]),
));
}
x if x == seg6_local::NH4 && local_payload.len() >= 4 => {
let bytes: [u8; 4] =
local_payload[..4].try_into().unwrap_or([0; 4]);
nh4 = Some(Ipv4Addr::from(bytes));
}
x if x == seg6_local::NH6 && local_payload.len() >= 16 => {
let bytes: [u8; 16] =
local_payload[..16].try_into().unwrap_or([0; 16]);
nh6 = Some(Ipv6Addr::from(bytes));
}
x if x == seg6_local::SRH
&& local_payload.len() >= 12 =>
{
let srh_data = &local_payload[4..];
if let Some(hdr) = Ipv6SrHdr::from_bytes(srh_data) {
let seg_data = &srh_data[Ipv6SrHdr::SIZE..];
let num_segs = (hdr.first_segment as usize) + 1;
for i in 0..num_segs {
let offset = i * 16;
if offset + 16 <= seg_data.len() {
let bytes: [u8; 16] = seg_data[offset..offset + 16]
.try_into()
.unwrap_or([0; 16]);
srh_segments.push(Ipv6Addr::from(bytes));
}
}
srh_segments.reverse();
}
}
x if x == seg6_local::OIF && local_payload.len() >= 4 => {
oif = Some(u32::from_ne_bytes(
local_payload[..4].try_into().unwrap_or([0; 4]),
));
}
x if x == seg6_local::IIF && local_payload.len() >= 4 => {
iif = Some(u32::from_ne_bytes(
local_payload[..4].try_into().unwrap_or([0; 4]),
));
}
_ => {}
}
}
}
_ => {}
}
}
let action = match action_type {
seg6_local_action::END => Srv6Action::End,
seg6_local_action::END_X => Srv6Action::EndX {
nexthop: nh6.unwrap_or(Ipv6Addr::UNSPECIFIED),
},
seg6_local_action::END_T => Srv6Action::EndT {
table: table.unwrap_or(0),
},
seg6_local_action::END_DX2 => Srv6Action::EndDX2,
seg6_local_action::END_DX4 => Srv6Action::EndDX4 { nexthop: nh4 },
seg6_local_action::END_DX6 => Srv6Action::EndDX6 { nexthop: nh6 },
seg6_local_action::END_DT4 => Srv6Action::EndDT4 {
table: table.unwrap_or(0),
},
seg6_local_action::END_DT6 => Srv6Action::EndDT6 {
table: table.unwrap_or(0),
},
seg6_local_action::END_DT46 => Srv6Action::EndDT46 {
table: table.unwrap_or(0),
},
seg6_local_action::END_B6 => Srv6Action::EndB6 {
segments: srh_segments.clone(),
},
seg6_local_action::END_B6_ENCAPS => Srv6Action::EndB6Encaps {
segments: srh_segments,
},
seg6_local_action::END_BPF => Srv6Action::EndBPF,
_ => Srv6Action::Unknown { action_type },
};
Ok(Self {
sid,
prefix_len: rtmsg.rtm_dst_len,
action,
oif,
iif,
protocol: rtmsg.rtm_protocol,
})
}
}
#[derive(Debug, Clone)]
#[must_use = "builders do nothing unless used"]
pub struct Srv6LocalBuilder {
sid: Ipv6Addr,
action: Srv6Action,
dev: Option<InterfaceRef>,
}
impl Srv6LocalBuilder {
pub fn end(sid: Ipv6Addr) -> Self {
Self {
sid,
action: Srv6Action::End,
dev: None,
}
}
pub fn end_x(sid: Ipv6Addr, nexthop: Ipv6Addr) -> Self {
Self {
sid,
action: Srv6Action::EndX { nexthop },
dev: None,
}
}
pub fn end_t(sid: Ipv6Addr, table: u32) -> Self {
Self {
sid,
action: Srv6Action::EndT { table },
dev: None,
}
}
pub fn end_dx2(sid: Ipv6Addr) -> Self {
Self {
sid,
action: Srv6Action::EndDX2,
dev: None,
}
}
pub fn end_dx4(sid: Ipv6Addr) -> Self {
Self {
sid,
action: Srv6Action::EndDX4 { nexthop: None },
dev: None,
}
}
pub fn end_dx4_via(sid: Ipv6Addr, nexthop: Ipv4Addr) -> Self {
Self {
sid,
action: Srv6Action::EndDX4 {
nexthop: Some(nexthop),
},
dev: None,
}
}
pub fn end_dx6(sid: Ipv6Addr) -> Self {
Self {
sid,
action: Srv6Action::EndDX6 { nexthop: None },
dev: None,
}
}
pub fn end_dx6_via(sid: Ipv6Addr, nexthop: Ipv6Addr) -> Self {
Self {
sid,
action: Srv6Action::EndDX6 {
nexthop: Some(nexthop),
},
dev: None,
}
}
pub fn end_dt4(sid: Ipv6Addr, table: u32) -> Self {
Self {
sid,
action: Srv6Action::EndDT4 { table },
dev: None,
}
}
pub fn end_dt6(sid: Ipv6Addr, table: u32) -> Self {
Self {
sid,
action: Srv6Action::EndDT6 { table },
dev: None,
}
}
pub fn end_dt46(sid: Ipv6Addr, table: u32) -> Self {
Self {
sid,
action: Srv6Action::EndDT46 { table },
dev: None,
}
}
pub fn end_b6(sid: Ipv6Addr, segments: &[Ipv6Addr]) -> Self {
Self {
sid,
action: Srv6Action::EndB6 {
segments: segments.to_vec(),
},
dev: None,
}
}
pub fn end_b6_encaps(sid: Ipv6Addr, segments: &[Ipv6Addr]) -> Self {
Self {
sid,
action: Srv6Action::EndB6Encaps {
segments: segments.to_vec(),
},
dev: None,
}
}
pub fn dev(mut self, dev: impl Into<String>) -> Self {
self.dev = Some(InterfaceRef::Name(dev.into()));
self
}
pub fn oif(mut self, oif: u32) -> Self {
self.dev = Some(InterfaceRef::Index(oif));
self
}
pub fn device_ref(&self) -> Option<&InterfaceRef> {
self.dev.as_ref()
}
fn build_srh(segments: &[Ipv6Addr]) -> Vec<u8> {
if segments.is_empty() {
return Vec::new();
}
let num_segments = segments.len() as u8;
let hdr = Ipv6SrHdr::new(num_segments);
let mut data = Vec::with_capacity(Ipv6SrHdr::SIZE + segments.len() * 16);
data.extend_from_slice(hdr.as_bytes());
for seg in segments.iter().rev() {
data.extend_from_slice(&seg.octets());
}
data
}
pub(crate) fn write_to(&self, builder: &mut MessageBuilder, ifindex: Option<u32>) {
let rtmsg = RtMsg::new()
.with_family(AF_INET6)
.with_dst_len(128) .with_type(1);
builder.append(&rtmsg);
builder.append_attr(RtaAttr::Dst as u16, &self.sid.octets());
if let Some(idx) = ifindex {
builder.append_attr_u32(RtaAttr::Oif as u16, idx);
}
builder.append_attr_u16(RtaAttr::EncapType as u16, lwtunnel_encap::SEG6_LOCAL);
let encap_nest = builder.nest_start(RtaAttr::Encap as u16);
builder.append_attr_u32(seg6_local::ACTION, self.action.action_type());
match &self.action {
Srv6Action::EndX { nexthop } => {
builder.append_attr(seg6_local::NH6, &nexthop.octets());
}
Srv6Action::EndT { table } => {
builder.append_attr_u32(seg6_local::TABLE, *table);
}
Srv6Action::EndDX4 { nexthop } => {
if let Some(nh) = nexthop {
builder.append_attr(seg6_local::NH4, &nh.octets());
}
}
Srv6Action::EndDX6 { nexthop } => {
if let Some(nh) = nexthop {
builder.append_attr(seg6_local::NH6, &nh.octets());
}
}
Srv6Action::EndDT4 { table }
| Srv6Action::EndDT6 { table }
| Srv6Action::EndDT46 { table } => {
builder.append_attr_u32(seg6_local::TABLE, *table);
}
Srv6Action::EndB6 { segments } | Srv6Action::EndB6Encaps { segments } => {
if !segments.is_empty() {
let mut srh_data = Vec::new();
srh_data.extend_from_slice(&seg6_mode::ENCAP.to_ne_bytes());
srh_data.extend_from_slice(&Self::build_srh(segments));
builder.append_attr(seg6_local::SRH, &srh_data);
}
}
Srv6Action::End
| Srv6Action::EndDX2
| Srv6Action::EndBPF
| Srv6Action::Unknown { .. } => {}
}
builder.nest_end(encap_nest);
}
}
impl Connection<Route> {
async fn resolve_srv6_interface(&self, builder: &Srv6LocalBuilder) -> Result<Option<u32>> {
match builder.device_ref() {
Some(iface_ref) => Ok(Some(self.resolve_interface(iface_ref).await?)),
None => Ok(None),
}
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_srv6_local_routes"))]
pub async fn get_srv6_local_routes(&self) -> Result<Vec<Srv6LocalRoute>> {
let rtmsg = RtMsg::new().with_family(AF_INET6);
let mut builder = MessageBuilder::new(NlMsgType::RTM_GETROUTE, NLM_F_REQUEST | NLM_F_DUMP);
builder.append(&rtmsg);
let responses = self.send_dump(builder).await?;
let mut routes = Vec::new();
for data in responses {
if data.len() <= NLMSG_HDRLEN {
continue;
}
let payload = &data[NLMSG_HDRLEN..];
let mut is_srv6_local = false;
if payload.len() >= RtMsg::SIZE {
for (attr_type, attr_payload) in AttrIter::new(&payload[RtMsg::SIZE..]) {
if RtaAttr::from(attr_type) == RtaAttr::EncapType && attr_payload.len() >= 2 {
let encap_type =
u16::from_ne_bytes(attr_payload[..2].try_into().unwrap_or([0; 2]));
if encap_type == lwtunnel_encap::SEG6_LOCAL {
is_srv6_local = true;
break;
}
}
}
}
if is_srv6_local && let Ok(route) = Srv6LocalRoute::parse(payload) {
routes.push(route);
}
}
Ok(routes)
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_srv6_local"))]
pub async fn add_srv6_local(&self, builder: Srv6LocalBuilder) -> Result<()> {
let ifindex = self.resolve_srv6_interface(&builder).await?;
let mut msg = MessageBuilder::new(
NlMsgType::RTM_NEWROUTE,
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE,
);
builder.write_to(&mut msg, ifindex);
self.send_ack(msg)
.await
.map_err(|e| e.with_context("add_srv6_local"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_srv6_local"))]
pub async fn replace_srv6_local(&self, builder: Srv6LocalBuilder) -> Result<()> {
let ifindex = self.resolve_srv6_interface(&builder).await?;
let mut msg = MessageBuilder::new(
NlMsgType::RTM_NEWROUTE,
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE,
);
builder.write_to(&mut msg, ifindex);
self.send_ack(msg)
.await
.map_err(|e| e.with_context("replace_srv6_local"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_srv6_local"))]
pub async fn del_srv6_local(&self, sid: Ipv6Addr) -> Result<()> {
let mut builder = MessageBuilder::new(NlMsgType::RTM_DELROUTE, NLM_F_REQUEST | NLM_F_ACK);
let rtmsg = RtMsg::new().with_family(AF_INET6).with_dst_len(128);
builder.append(&rtmsg);
builder.append_attr(RtaAttr::Dst as u16, &sid.octets());
self.send_ack(builder)
.await
.map_err(|e| e.with_context("del_srv6_local"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_srv6_mode() {
assert_eq!(Srv6Mode::Encap.to_u32(), seg6_mode::ENCAP);
assert_eq!(Srv6Mode::Inline.to_u32(), seg6_mode::INLINE);
assert_eq!(Srv6Mode::from_u32(seg6_mode::ENCAP), Some(Srv6Mode::Encap));
}
#[test]
fn test_srv6_encap_empty() {
let encap = Srv6Encap::encap();
assert!(encap.is_empty());
}
#[test]
fn test_srv6_encap_single_segment() {
let seg: Ipv6Addr = "fc00:1::1".parse().unwrap();
let encap = Srv6Encap::encap().segment(seg);
assert!(!encap.is_empty());
assert_eq!(encap.get_segments().len(), 1);
assert_eq!(encap.get_segments()[0], seg);
}
#[test]
fn test_srv6_encap_multiple_segments() {
let seg1: Ipv6Addr = "fc00:1::1".parse().unwrap();
let seg2: Ipv6Addr = "fc00:2::1".parse().unwrap();
let encap = Srv6Encap::encap().segments(&[seg1, seg2]);
assert_eq!(encap.get_segments().len(), 2);
}
#[test]
fn test_srv6_encap_build_srh() {
let seg: Ipv6Addr = "fc00:1::1".parse().unwrap();
let encap = Srv6Encap::encap().segment(seg);
let srh = encap.build_srh();
assert_eq!(srh.len(), 24);
}
#[test]
fn test_srv6_action_names() {
assert_eq!(Srv6Action::End.name(), "End");
assert_eq!(
Srv6Action::EndX {
nexthop: Ipv6Addr::UNSPECIFIED
}
.name(),
"End.X"
);
assert_eq!(Srv6Action::EndDT4 { table: 100 }.name(), "End.DT4");
}
#[test]
fn test_srv6_local_builder_end() {
let sid: Ipv6Addr = "fc00:1::1".parse().unwrap();
let builder = Srv6LocalBuilder::end(sid);
assert_eq!(builder.sid, sid);
assert!(matches!(builder.action, Srv6Action::End));
}
#[test]
fn test_srv6_local_builder_end_dt4() {
let sid: Ipv6Addr = "fc00:1::100".parse().unwrap();
let builder = Srv6LocalBuilder::end_dt4(sid, 100);
assert!(matches!(builder.action, Srv6Action::EndDT4 { table: 100 }));
}
#[test]
fn test_srv6_local_builder_end_x() {
let sid: Ipv6Addr = "fc00:1::1".parse().unwrap();
let nh: Ipv6Addr = "fe80::1".parse().unwrap();
let builder = Srv6LocalBuilder::end_x(sid, nh);
assert!(matches!(builder.action, Srv6Action::EndX { nexthop } if nexthop == nh));
}
#[test]
fn test_srv6_local_builder_with_dev() {
let sid: Ipv6Addr = "fc00:1::1".parse().unwrap();
let builder = Srv6LocalBuilder::end(sid).dev("eth0");
assert_eq!(builder.dev, Some(InterfaceRef::Name("eth0".to_string())));
}
#[test]
fn test_srv6_local_builder_with_oif() {
let sid: Ipv6Addr = "fc00:1::1".parse().unwrap();
let builder = Srv6LocalBuilder::end(sid).oif(5);
assert_eq!(builder.dev, Some(InterfaceRef::Index(5)));
}
}