use core::net::Ipv4Addr;
use crate::field::Field;
pub const OSPF_AS_EXTERNAL_FLAG_E: u8 = 0x80;
const OSPF_AS_EXTERNAL_LSA_MASK_LEN: usize = 4;
const OSPF_AS_EXTERNAL_LSA_ENTRY_LEN: usize = 12;
const OSPF_AS_EXTERNAL_LSA_METRIC_MAX: u32 = 0x00ff_ffff;
#[derive(Debug, Clone)]
pub struct OspfExternalTos {
e_bit: bool,
tos: u8,
metric: u32,
forwarding_address: Ipv4Addr,
external_route_tag: u32,
}
impl OspfExternalTos {
pub fn new(
e_bit: bool,
tos: u8,
metric: u32,
forwarding_address: impl Into<Ipv4Addr>,
external_route_tag: u32,
) -> Self {
Self {
e_bit,
tos,
metric,
forwarding_address: forwarding_address.into(),
external_route_tag,
}
}
pub fn e_bit_value(&self) -> bool {
self.e_bit
}
pub fn tos_value(&self) -> u8 {
self.tos
}
pub fn metric_value(&self) -> u32 {
self.metric
}
pub fn forwarding_address_value(&self) -> Ipv4Addr {
self.forwarding_address
}
pub fn external_route_tag_value(&self) -> u32 {
self.external_route_tag
}
}
#[derive(Debug, Clone)]
pub struct OspfAsExternalLsa {
network_mask: Field<Ipv4Addr>,
entries: Vec<OspfExternalTos>,
}
impl OspfAsExternalLsa {
pub fn new() -> Self {
Self {
network_mask: Field::unset(),
entries: vec![OspfExternalTos::new(false, 0, 0, Ipv4Addr::UNSPECIFIED, 0)],
}
}
pub(crate) fn from_decoded_parts(
network_mask: Ipv4Addr,
entries: Vec<OspfExternalTos>,
) -> Self {
Self {
network_mask: Field::user(network_mask),
entries,
}
}
pub fn network_mask(mut self, network_mask: impl Into<Ipv4Addr>) -> Self {
self.network_mask.set_user(network_mask.into());
self
}
pub fn metric(mut self, metric: u32, e_bit: bool) -> Self {
match self.entries.first_mut() {
Some(entry) => {
entry.e_bit = e_bit;
entry.tos = 0;
entry.metric = metric;
}
None => self.entries.push(OspfExternalTos::new(
e_bit,
0,
metric,
Ipv4Addr::UNSPECIFIED,
0,
)),
}
self
}
pub fn external_entry(
mut self,
e_bit: bool,
tos: u8,
metric: u32,
forwarding_address: impl Into<Ipv4Addr>,
external_route_tag: u32,
) -> Self {
self.entries.push(OspfExternalTos::new(
e_bit,
tos,
metric,
forwarding_address,
external_route_tag,
));
self
}
pub fn network_mask_value(&self) -> Ipv4Addr {
self.network_mask
.value()
.copied()
.unwrap_or(Ipv4Addr::UNSPECIFIED)
}
pub fn entries_value(&self) -> &[OspfExternalTos] {
&self.entries
}
pub fn summary(&self) -> String {
let (metric, metric_type) = match self.entries.first() {
Some(entry) => (
(entry.metric & OSPF_AS_EXTERNAL_LSA_METRIC_MAX).to_string(),
if entry.e_bit { "E2" } else { "E1" },
),
None => ("none".to_string(), "E1"),
};
format!(
"mask={} metric={} type={} tos={}",
self.network_mask_value(),
metric,
metric_type,
self.entries.len(),
)
}
pub(crate) fn encoded_len(&self) -> usize {
OSPF_AS_EXTERNAL_LSA_MASK_LEN + self.entries.len() * OSPF_AS_EXTERNAL_LSA_ENTRY_LEN
}
pub(crate) fn encode(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.network_mask_value().octets());
for entry in &self.entries {
let combined = (if entry.e_bit {
OSPF_AS_EXTERNAL_FLAG_E
} else {
0
}) | (entry.tos & 0x7f);
out.push(combined);
let metric = entry.metric & OSPF_AS_EXTERNAL_LSA_METRIC_MAX;
out.push((metric >> 16) as u8);
out.push((metric >> 8) as u8);
out.push(metric as u8);
out.extend_from_slice(&entry.forwarding_address.octets());
out.extend_from_slice(&entry.external_route_tag.to_be_bytes());
}
}
}
impl Default for OspfAsExternalLsa {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::checksum::fletcher16_valid;
use crate::protocols::ospf::lsa::{
OspfLsa, OspfLsaBody, OspfLsaHeader, OSPF_LSA_AS_EXTERNAL, OSPF_LSA_HEADER_LEN,
};
use crate::protocols::ospf::packet::link_state_update::OspfLinkStateUpdate;
#[test]
fn ospf_as_external_lsa_e2_entry_round_trips_in_lsu() {
let metric = 0x000a_0b0c;
let external = OspfAsExternalLsa::new()
.network_mask(Ipv4Addr::new(255, 255, 255, 0))
.external_entry(true, 5, metric, Ipv4Addr::new(192, 0, 2, 9), 0x1234_5678);
assert_eq!(
external.network_mask_value(),
Ipv4Addr::new(255, 255, 255, 0)
);
assert_eq!(external.entries_value().len(), 2);
let entry = &external.entries_value()[1];
assert!(entry.e_bit_value());
assert_eq!(entry.tos_value(), 5);
assert_eq!(entry.metric_value(), metric);
assert_eq!(
entry.forwarding_address_value(),
Ipv4Addr::new(192, 0, 2, 9)
);
assert_eq!(entry.external_route_tag_value(), 0x1234_5678);
let single = OspfAsExternalLsa::new()
.network_mask(Ipv4Addr::new(255, 255, 255, 0))
.metric(metric, true);
assert_eq!(single.entries_value().len(), 1);
assert!(single.entries_value()[0].e_bit_value());
assert_eq!(single.entries_value()[0].tos_value(), 0);
assert_eq!(single.entries_value()[0].metric_value(), metric);
let mut body = Vec::new();
single.encode(&mut body);
assert_eq!(body.len(), single.encoded_len());
let expected: Vec<u8> = vec![
255, 255, 255, 0, 0x80, 0x0a, 0x0b, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0,
];
assert_eq!(body, expected);
assert_eq!(&body[0..4], &[255, 255, 255, 0]);
assert_eq!(body[4], OSPF_AS_EXTERNAL_FLAG_E);
assert_eq!(body[4] & OSPF_AS_EXTERNAL_FLAG_E, OSPF_AS_EXTERNAL_FLAG_E);
assert_eq!(body[4] & 0x7f, 0);
assert_eq!(&body[5..8], &[0x0a, 0x0b, 0x0c]);
assert_eq!(&body[8..12], &[0, 0, 0, 0]);
assert_eq!(&body[12..16], &[0, 0, 0, 0]);
let mut packed = Vec::new();
OspfAsExternalLsa::new()
.network_mask(Ipv4Addr::new(255, 255, 255, 255))
.external_entry(true, 5, metric, Ipv4Addr::new(192, 0, 2, 9), 0x1234_5678)
.encode(&mut packed);
let second_octet = packed[4 + OSPF_AS_EXTERNAL_LSA_ENTRY_LEN];
assert_eq!(second_octet, OSPF_AS_EXTERNAL_FLAG_E | 5);
assert_eq!(second_octet & 0x7f, 5);
let entry_start = 4 + 2 * OSPF_AS_EXTERNAL_LSA_ENTRY_LEN - OSPF_AS_EXTERNAL_LSA_ENTRY_LEN;
assert_eq!(
&packed[entry_start + 4..entry_start + 8],
&Ipv4Addr::new(192, 0, 2, 9).octets()
);
assert_eq!(
&packed[entry_start + 8..entry_start + 12],
&0x1234_5678u32.to_be_bytes()
);
let lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_AS_EXTERNAL)
.link_state_id(Ipv4Addr::new(198, 51, 100, 0))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
OspfLsaBody::AsExternal(single),
);
let lsu = OspfLinkStateUpdate::new().lsa(lsa);
let mut update = Vec::new();
lsu.encode(&mut update);
assert_eq!(&update[0..4], &1u32.to_be_bytes());
let lsa_bytes = &update[4..];
assert_eq!(lsa_bytes.len(), OSPF_LSA_HEADER_LEN + expected.len());
let expected_lsa_len = (OSPF_LSA_HEADER_LEN + expected.len()) as u16;
assert_eq!(&lsa_bytes[18..20], &expected_lsa_len.to_be_bytes());
assert_eq!(&lsa_bytes[OSPF_LSA_HEADER_LEN..], expected.as_slice());
assert!(
fletcher16_valid(lsa_bytes),
"auto-filled Fletcher checksum should validate over the AS-External-LSA"
);
}
#[test]
fn ospf_as_external_lsa_three_entries_round_trip_byte_for_byte() {
use crate::protocols::ospf::decode::append_ospf_packet;
use crate::protocols::ospf::{OspfBody, Ospfv2};
use crate::Packet;
let external = OspfAsExternalLsa::new()
.network_mask(Ipv4Addr::new(255, 255, 255, 0))
.metric(0x0000_000a, false)
.external_entry(
true,
1,
0x00b1_b2b3,
Ipv4Addr::new(192, 0, 2, 11),
0xdead_beef,
)
.external_entry(
false,
2,
0x00ff_ffff,
Ipv4Addr::new(192, 0, 2, 12),
0x1234_5678,
);
let built = external.entries_value();
assert_eq!(built.len(), 3);
assert!(!built[0].e_bit_value());
assert_eq!(built[0].tos_value(), 0);
assert_eq!(built[0].metric_value(), 0x0000_000a);
assert!(built[1].e_bit_value());
assert_eq!(built[1].tos_value(), 1);
assert_eq!(built[2].tos_value(), 2);
let lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_AS_EXTERNAL)
.link_state_id(Ipv4Addr::new(198, 51, 100, 0))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
OspfLsaBody::AsExternal(external.clone()),
);
let bytes = Packet::from_layer(
Ospfv2::link_state_update()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_link_state_update(|u| {
*u = u.clone().lsa(lsa.clone());
}),
)
.compile()
.expect("a Link State Update with a three-entry AS-External-LSA compiles");
let decoded = append_ospf_packet(Packet::new(), bytes.as_bytes())
.expect("the Link State Update decodes");
let ospf = decoded
.layer::<Ospfv2>()
.expect("the decoded packet exposes a typed Ospfv2 layer");
let lsu = match &ospf.body {
OspfBody::LinkStateUpdate(lsu) => lsu,
other => panic!("expected a typed Link State Update body, got {other:?}"),
};
let decoded_lsas = lsu.lsas_value();
assert_eq!(decoded_lsas.len(), 1);
assert_eq!(decoded_lsas[0].header.ls_type_value(), OSPF_LSA_AS_EXTERNAL);
let decoded_external = match &decoded_lsas[0].body {
OspfLsaBody::AsExternal(external) => external,
other => panic!("expected a typed AS-External-LSA body, got {other:?}"),
};
assert_eq!(
decoded_external.network_mask_value(),
Ipv4Addr::new(255, 255, 255, 0)
);
let decoded_entries = decoded_external.entries_value();
assert_eq!(decoded_entries.len(), built.len());
for (decoded_entry, built_entry) in decoded_entries.iter().zip(built.iter()) {
assert_eq!(decoded_entry.e_bit_value(), built_entry.e_bit_value());
assert_eq!(decoded_entry.tos_value(), built_entry.tos_value());
assert_eq!(decoded_entry.metric_value(), built_entry.metric_value());
assert_eq!(
decoded_entry.forwarding_address_value(),
built_entry.forwarding_address_value()
);
assert_eq!(
decoded_entry.external_route_tag_value(),
built_entry.external_route_tag_value()
);
}
let recompiled = decoded
.compile()
.expect("decoded Link State Update re-compiles");
assert_eq!(recompiled.as_bytes(), bytes.as_bytes());
}
#[test]
fn ospf_as_external_lsa_tos_above_seven_bits_round_trips_masked() {
use crate::protocols::ospf::decode::append_ospf_packet;
use crate::protocols::ospf::{OspfBody, Ospfv2};
use crate::Packet;
let raw_tos = 0xff_u8;
let masked_tos = raw_tos & 0x7f;
let external = OspfAsExternalLsa::new()
.network_mask(Ipv4Addr::new(255, 255, 255, 0))
.metric(0x0000_002a, false)
.external_entry(false, raw_tos, 0x0000_0064, Ipv4Addr::new(192, 0, 2, 13), 0);
assert_eq!(external.entries_value()[1].tos_value(), raw_tos);
let mut body = Vec::new();
external.encode(&mut body);
let entry_octet = body[OSPF_AS_EXTERNAL_LSA_MASK_LEN + OSPF_AS_EXTERNAL_LSA_ENTRY_LEN];
assert_eq!(entry_octet & OSPF_AS_EXTERNAL_FLAG_E, 0);
assert_eq!(entry_octet & 0x7f, masked_tos);
assert_eq!(entry_octet, masked_tos);
let lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_AS_EXTERNAL)
.link_state_id(Ipv4Addr::new(198, 51, 100, 0))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
OspfLsaBody::AsExternal(external),
);
let bytes = Packet::from_layer(
Ospfv2::link_state_update()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_link_state_update(|u| {
*u = u.clone().lsa(lsa.clone());
}),
)
.compile()
.expect("a Link State Update with a wide-TOS AS-External-LSA compiles");
let decoded = append_ospf_packet(Packet::new(), bytes.as_bytes())
.expect("the Link State Update decodes");
let ospf = decoded
.layer::<Ospfv2>()
.expect("the decoded packet exposes a typed Ospfv2 layer");
let lsu = match &ospf.body {
OspfBody::LinkStateUpdate(lsu) => lsu,
other => panic!("expected a typed Link State Update body, got {other:?}"),
};
let decoded_external = match &lsu.lsas_value()[0].body {
OspfLsaBody::AsExternal(external) => external,
other => panic!("expected a typed AS-External-LSA body, got {other:?}"),
};
let decoded_entry = &decoded_external.entries_value()[1];
assert!(!decoded_entry.e_bit_value());
assert_eq!(decoded_entry.tos_value(), masked_tos);
assert_eq!(decoded_entry.metric_value(), 0x0000_0064);
assert_eq!(
decoded_entry.forwarding_address_value(),
Ipv4Addr::new(192, 0, 2, 13)
);
let recompiled = decoded
.compile()
.expect("decoded Link State Update re-compiles");
assert_eq!(recompiled.as_bytes(), bytes.as_bytes());
}
}