use core::net::Ipv4Addr;
use super::external::{OspfAsExternalLsa, OspfExternalTos};
pub use crate::protocols::ospf::constants::OSPF_OPTIONS_NP;
#[derive(Debug, Clone)]
pub struct OspfNssaLsa {
external: OspfAsExternalLsa,
}
impl OspfNssaLsa {
pub fn new() -> Self {
Self {
external: OspfAsExternalLsa::new(),
}
}
pub(crate) fn from_decoded_parts(
network_mask: Ipv4Addr,
entries: Vec<OspfExternalTos>,
) -> Self {
Self {
external: OspfAsExternalLsa::from_decoded_parts(network_mask, entries),
}
}
pub fn network_mask(mut self, network_mask: impl Into<Ipv4Addr>) -> Self {
self.external = self.external.network_mask(network_mask);
self
}
pub fn metric(mut self, metric: u32, e_bit: bool) -> Self {
self.external = self.external.metric(metric, e_bit);
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.external = self.external.external_entry(
e_bit,
tos,
metric,
forwarding_address,
external_route_tag,
);
self
}
pub fn network_mask_value(&self) -> Ipv4Addr {
self.external.network_mask_value()
}
pub fn entries_value(&self) -> &[OspfExternalTos] {
self.external.entries_value()
}
pub fn summary(&self) -> String {
self.external.summary()
}
pub(crate) fn encoded_len(&self) -> usize {
self.external.encoded_len()
}
pub(crate) fn encode(&self, out: &mut Vec<u8>) {
self.external.encode(out);
}
}
impl Default for OspfNssaLsa {
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_AS_EXTERNAL_FLAG_E, OSPF_LSA_HEADER_LEN,
OSPF_LSA_NSSA,
};
use crate::protocols::ospf::packet::link_state_update::OspfLinkStateUpdate;
#[test]
fn ospf_nssa_lsa_with_p_bit_round_trips_in_lsu() {
let metric = 0x000a_0b0c;
let nssa = OspfNssaLsa::new()
.network_mask(Ipv4Addr::new(255, 255, 255, 0))
.metric(metric, true);
assert_eq!(nssa.network_mask_value(), Ipv4Addr::new(255, 255, 255, 0));
assert_eq!(nssa.entries_value().len(), 1);
assert!(nssa.entries_value()[0].e_bit_value());
assert_eq!(nssa.entries_value()[0].tos_value(), 0);
assert_eq!(nssa.entries_value()[0].metric_value(), metric);
let mut body = Vec::new();
nssa.encode(&mut body);
assert_eq!(body.len(), nssa.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] & 0x7f, 0);
assert_eq!(&body[5..8], &[0x0a, 0x0b, 0x0c]);
let lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_NSSA)
.options(OSPF_OPTIONS_NP)
.link_state_id(Ipv4Addr::new(198, 51, 100, 0))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
OspfLsaBody::Nssa(nssa),
);
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());
assert_eq!(lsa_bytes[3], OSPF_LSA_NSSA);
assert_eq!(lsa_bytes[2], OSPF_OPTIONS_NP);
assert_eq!(lsa_bytes[2] & OSPF_OPTIONS_NP, OSPF_OPTIONS_NP);
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 NSSA-LSA"
);
}
#[test]
fn ospf_nssa_lsa_with_p_bit_decodes_and_round_trips_byte_for_byte() {
use crate::protocols::ospf::decode::append_ospf_packet;
use crate::protocols::ospf::{OspfBody, Ospfv2};
use crate::Packet;
let nssa = OspfNssaLsa::new()
.network_mask(Ipv4Addr::new(255, 255, 255, 0))
.metric(0x000a_0b0c, true);
let lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_NSSA)
.options(OSPF_OPTIONS_NP)
.link_state_id(Ipv4Addr::new(198, 51, 100, 0))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
OspfLsaBody::Nssa(nssa),
);
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 P-bit NSSA-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_NSSA);
let decoded_nssa = match &decoded_lsas[0].body {
OspfLsaBody::Nssa(nssa) => nssa,
other => panic!("expected a typed NSSA-LSA body, got {other:?}"),
};
let options = decoded_lsas[0].header.options_value();
assert_eq!(options & OSPF_OPTIONS_NP, OSPF_OPTIONS_NP);
assert_eq!(
decoded_nssa.network_mask_value(),
Ipv4Addr::new(255, 255, 255, 0)
);
assert_eq!(decoded_nssa.entries_value().len(), 1);
let entry = &decoded_nssa.entries_value()[0];
assert!(entry.e_bit_value());
assert_eq!(entry.tos_value(), 0);
assert_eq!(entry.metric_value(), 0x000a_0b0c);
let recompiled = decoded
.compile()
.expect("decoded Link State Update re-compiles");
assert_eq!(recompiled.as_bytes(), bytes.as_bytes());
}
#[test]
fn ospf_nssa_lsa_trailing_partial_entry_is_invalid_field() {
use crate::error::CrafterError;
use crate::protocols::ospf::decode::append_ospf_packet;
use crate::protocols::ospf::Ospfv2;
use crate::Packet;
let raw_body = vec![
255, 255, 255, 0, 0x80, 0x0a, 0x0b, 0x0c, 0, 0, 0, 0,
];
assert_eq!(raw_body.len(), 12);
let lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_NSSA)
.link_state_id(Ipv4Addr::new(198, 51, 100, 0))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001)
.length((OSPF_LSA_HEADER_LEN + raw_body.len()) as u16),
OspfLsaBody::Raw(raw_body),
);
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 malformed NSSA-LSA body compiles");
let err = append_ospf_packet(Packet::new(), bytes.as_bytes())
.expect_err("a trailing partial NSSA-LSA entry must error");
match err {
CrafterError::InvalidFieldValue { field, .. } => {
assert_eq!(field, "ospf.nssa_lsa.entries");
}
other => panic!("expected invalid_field_value, got {other:?}"),
}
}
}