use core::any::Any;
use core::net::Ipv4Addr;
use core::ops::Div;
use crate::checksum::internet_checksum_chunks;
use crate::field::Field;
use crate::packet::{IntoPacket, Layer, LayerContext, Packet};
use crate::Result;
pub mod auth;
#[allow(unused_imports)]
pub mod constants;
pub(crate) mod decode;
pub mod lsa;
pub mod packet;
pub mod v3;
#[allow(unused_imports)]
pub use auth::{
OspfCryptoAlgorithm, OspfCryptoAuth, OSPF_HMAC_SHA1_DIGEST_LEN, OSPF_HMAC_SHA256_DIGEST_LEN,
OSPF_HMAC_SHA384_DIGEST_LEN, OSPF_HMAC_SHA512_DIGEST_LEN, OSPF_MD5_DIGEST_LEN,
};
#[allow(unused_imports)]
pub use constants::*;
pub use packet::{
OspfDatabaseDescription, OspfHello, OspfLinkStateAck, OspfLinkStateRequest,
OspfLinkStateRequestEntry, OspfLinkStateUpdate,
};
pub use v3::{
Ospfv3, Ospfv3Body, Ospfv3DatabaseDescription, Ospfv3Hello, Ospfv3LinkStateAck,
Ospfv3LinkStateRequest, Ospfv3LinkStateRequestEntry, Ospfv3LinkStateUpdate, Ospfv3Lsa,
Ospfv3LsaBody, Ospfv3LsaHeader, Ospfv3NetworkLsa, Ospfv3RouterInterface, Ospfv3RouterLsa,
OSPFV3_DD_FLAG_I, OSPFV3_DD_FLAG_M, OSPFV3_DD_FLAG_MS, OSPFV3_HEADER_LEN,
OSPFV3_LSA_HEADER_LEN, OSPFV3_TYPE_DATABASE_DESCRIPTION, OSPFV3_TYPE_HELLO,
OSPFV3_TYPE_LINK_STATE_ACK, OSPFV3_TYPE_LINK_STATE_REQUEST, OSPFV3_TYPE_LINK_STATE_UPDATE,
OSPF_VERSION_3,
};
macro_rules! impl_layer_object {
($type:ty) => {
fn clone_layer(&self) -> Box<dyn Layer> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
};
}
macro_rules! impl_layer_div {
($type:ty) => {
impl<R> Div<R> for $type
where
R: IntoPacket,
{
type Output = Packet;
fn div(self, rhs: R) -> Self::Output {
Packet::from_layer(self).concat(rhs)
}
}
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OspfChecksumStatus {
Valid,
Invalid,
NotChecked,
}
#[derive(Debug, Clone)]
pub enum OspfBody {
Hello(OspfHello),
DatabaseDescription(OspfDatabaseDescription),
LinkStateRequest(OspfLinkStateRequest),
LinkStateUpdate(OspfLinkStateUpdate),
LinkStateAck(OspfLinkStateAck),
Unknown {
type_code: u8,
body: Vec<u8>,
},
}
impl OspfBody {
fn encoded_len(&self) -> usize {
match self {
OspfBody::Hello(hello) => hello.encoded_len(),
OspfBody::DatabaseDescription(dd) => dd.encoded_len(),
OspfBody::LinkStateRequest(lsr) => lsr.encoded_len(),
OspfBody::LinkStateUpdate(lsu) => lsu.encoded_len(),
OspfBody::LinkStateAck(ack) => ack.encoded_len(),
OspfBody::Unknown { body, .. } => body.len(),
}
}
fn encode(&self, out: &mut Vec<u8>) {
match self {
OspfBody::Hello(hello) => hello.encode(out),
OspfBody::DatabaseDescription(dd) => dd.encode(out),
OspfBody::LinkStateRequest(lsr) => lsr.encode(out),
OspfBody::LinkStateUpdate(lsu) => lsu.encode(out),
OspfBody::LinkStateAck(ack) => ack.encode(out),
OspfBody::Unknown { body, .. } => out.extend_from_slice(body),
}
}
fn type_code(&self) -> u8 {
match self {
OspfBody::Hello(_) => OSPF_TYPE_HELLO,
OspfBody::DatabaseDescription(_) => OSPF_TYPE_DATABASE_DESCRIPTION,
OspfBody::LinkStateRequest(_) => OSPF_TYPE_LINK_STATE_REQUEST,
OspfBody::LinkStateUpdate(_) => OSPF_TYPE_LINK_STATE_UPDATE,
OspfBody::LinkStateAck(_) => OSPF_TYPE_LINK_STATE_ACK,
OspfBody::Unknown { type_code, .. } => *type_code,
}
}
fn set_type_code(&mut self, type_code: u8) {
match self {
OspfBody::Hello(_) => {}
OspfBody::DatabaseDescription(_) => {}
OspfBody::LinkStateRequest(_) => {}
OspfBody::LinkStateUpdate(_) => {}
OspfBody::LinkStateAck(_) => {}
OspfBody::Unknown { type_code: tc, .. } => *tc = type_code,
}
}
}
#[derive(Debug, Clone)]
pub struct Ospfv2 {
version: Field<u8>,
packet_type: Field<u8>,
packet_length: Field<u16>,
router_id: Field<Ipv4Addr>,
area_id: Field<Ipv4Addr>,
checksum: Field<u16>,
autype: Field<u16>,
authentication: Field<[u8; OSPF_AUTH_LEN]>,
checksum_status: OspfChecksumStatus,
crypto_auth: Option<OspfCryptoAuth>,
auth_trailer: Vec<u8>,
body: OspfBody,
}
impl Ospfv2 {
pub fn new() -> Self {
Self {
version: Field::defaulted(OSPF_VERSION_2),
packet_type: Field::unset(),
packet_length: Field::unset(),
router_id: Field::unset(),
area_id: Field::unset(),
checksum: Field::unset(),
autype: Field::defaulted(OSPF_AUTYPE_NULL),
authentication: Field::defaulted([0; OSPF_AUTH_LEN]),
checksum_status: OspfChecksumStatus::NotChecked,
crypto_auth: None,
auth_trailer: Vec::new(),
body: OspfBody::Unknown {
type_code: 0,
body: Vec::new(),
},
}
}
pub fn version(mut self, version: u8) -> Self {
self.version.set_user(version);
self
}
pub fn packet_type(mut self, packet_type: u8) -> Self {
self.packet_type.set_user(packet_type);
self.body.set_type_code(packet_type);
self
}
pub fn packet_length(mut self, packet_length: u16) -> Self {
self.packet_length.set_user(packet_length);
self
}
pub fn router_id(mut self, router_id: impl Into<Ipv4Addr>) -> Self {
self.router_id.set_user(router_id.into());
self
}
pub fn area_id(mut self, area_id: impl Into<Ipv4Addr>) -> Self {
self.area_id.set_user(area_id.into());
self
}
pub fn checksum(mut self, checksum: u16) -> Self {
self.checksum.set_user(checksum);
self
}
pub fn autype(mut self, autype: u16) -> Self {
self.autype.set_user(autype);
self
}
pub fn authentication(mut self, authentication: [u8; OSPF_AUTH_LEN]) -> Self {
self.authentication.set_user(authentication);
self
}
pub fn null_auth(mut self) -> Self {
self.autype.set_user(OSPF_AUTYPE_NULL);
self.authentication.set_user([0; OSPF_AUTH_LEN]);
self
}
pub fn simple_password(mut self, password: &[u8]) -> Self {
self.autype.set_user(OSPF_AUTYPE_SIMPLE);
self.authentication
.set_user(auth::simple_password_field(password));
self
}
pub fn crypto_auth(
mut self,
algorithm: OspfCryptoAlgorithm,
key_id: u8,
sequence_number: u32,
key: impl Into<Vec<u8>>,
) -> Self {
let crypto = OspfCryptoAuth::with_algorithm(algorithm, key_id, sequence_number, key);
self.autype.set_user(OSPF_AUTYPE_CRYPTOGRAPHIC);
self.authentication.set_user(crypto.structured_auth_field());
self.crypto_auth = Some(crypto);
self
}
pub fn crypto_md5_auth(
self,
key_id: u8,
sequence_number: u32,
key: impl Into<Vec<u8>>,
) -> Self {
self.crypto_auth(OspfCryptoAlgorithm::KeyedMd5, key_id, sequence_number, key)
}
pub fn raw_body(mut self, body: impl Into<Vec<u8>>) -> Self {
let type_code = self.packet_type.value().copied().unwrap_or(0);
self.body = OspfBody::Unknown {
type_code,
body: body.into(),
};
self
}
pub fn hello() -> Self {
Self::new()
.packet_type(OSPF_TYPE_HELLO)
.hello_body(OspfHello::new())
}
pub fn hello_body(mut self, hello: OspfHello) -> Self {
self.packet_type.set_user(OSPF_TYPE_HELLO);
self.body = OspfBody::Hello(hello);
self
}
pub fn with_hello(mut self, configure: impl FnOnce(&mut OspfHello)) -> Self {
configure(self.hello_mut());
self
}
pub fn hello_mut(&mut self) -> &mut OspfHello {
if !matches!(self.body, OspfBody::Hello(_)) {
self.packet_type.set_user(OSPF_TYPE_HELLO);
self.body = OspfBody::Hello(OspfHello::new());
}
match &mut self.body {
OspfBody::Hello(hello) => hello,
_ => unreachable!("hello body installed above"),
}
}
pub fn database_description() -> Self {
Self::new()
.packet_type(OSPF_TYPE_DATABASE_DESCRIPTION)
.database_description_body(OspfDatabaseDescription::new())
}
pub fn database_description_body(mut self, dd: OspfDatabaseDescription) -> Self {
self.packet_type.set_user(OSPF_TYPE_DATABASE_DESCRIPTION);
self.body = OspfBody::DatabaseDescription(dd);
self
}
pub fn with_database_description(
mut self,
configure: impl FnOnce(&mut OspfDatabaseDescription),
) -> Self {
configure(self.database_description_mut());
self
}
pub fn database_description_mut(&mut self) -> &mut OspfDatabaseDescription {
if !matches!(self.body, OspfBody::DatabaseDescription(_)) {
self.packet_type.set_user(OSPF_TYPE_DATABASE_DESCRIPTION);
self.body = OspfBody::DatabaseDescription(OspfDatabaseDescription::new());
}
match &mut self.body {
OspfBody::DatabaseDescription(dd) => dd,
_ => unreachable!("database description body installed above"),
}
}
pub fn link_state_request() -> Self {
Self::new()
.packet_type(OSPF_TYPE_LINK_STATE_REQUEST)
.link_state_request_body(OspfLinkStateRequest::new())
}
pub fn link_state_request_body(mut self, lsr: OspfLinkStateRequest) -> Self {
self.packet_type.set_user(OSPF_TYPE_LINK_STATE_REQUEST);
self.body = OspfBody::LinkStateRequest(lsr);
self
}
pub fn with_link_state_request(
mut self,
configure: impl FnOnce(&mut OspfLinkStateRequest),
) -> Self {
configure(self.link_state_request_mut());
self
}
pub fn link_state_request_mut(&mut self) -> &mut OspfLinkStateRequest {
if !matches!(self.body, OspfBody::LinkStateRequest(_)) {
self.packet_type.set_user(OSPF_TYPE_LINK_STATE_REQUEST);
self.body = OspfBody::LinkStateRequest(OspfLinkStateRequest::new());
}
match &mut self.body {
OspfBody::LinkStateRequest(lsr) => lsr,
_ => unreachable!("link state request body installed above"),
}
}
pub fn link_state_update() -> Self {
Self::new()
.packet_type(OSPF_TYPE_LINK_STATE_UPDATE)
.link_state_update_body(OspfLinkStateUpdate::new())
}
pub fn link_state_update_body(mut self, lsu: OspfLinkStateUpdate) -> Self {
self.packet_type.set_user(OSPF_TYPE_LINK_STATE_UPDATE);
self.body = OspfBody::LinkStateUpdate(lsu);
self
}
pub fn with_link_state_update(
mut self,
configure: impl FnOnce(&mut OspfLinkStateUpdate),
) -> Self {
configure(self.link_state_update_mut());
self
}
pub fn link_state_update_mut(&mut self) -> &mut OspfLinkStateUpdate {
if !matches!(self.body, OspfBody::LinkStateUpdate(_)) {
self.packet_type.set_user(OSPF_TYPE_LINK_STATE_UPDATE);
self.body = OspfBody::LinkStateUpdate(OspfLinkStateUpdate::new());
}
match &mut self.body {
OspfBody::LinkStateUpdate(lsu) => lsu,
_ => unreachable!("link state update body installed above"),
}
}
pub fn link_state_ack() -> Self {
Self::new()
.packet_type(OSPF_TYPE_LINK_STATE_ACK)
.link_state_ack_body(OspfLinkStateAck::new())
}
pub fn link_state_ack_body(mut self, ack: OspfLinkStateAck) -> Self {
self.packet_type.set_user(OSPF_TYPE_LINK_STATE_ACK);
self.body = OspfBody::LinkStateAck(ack);
self
}
pub fn with_link_state_ack(mut self, configure: impl FnOnce(&mut OspfLinkStateAck)) -> Self {
configure(self.link_state_ack_mut());
self
}
pub fn link_state_ack_mut(&mut self) -> &mut OspfLinkStateAck {
if !matches!(self.body, OspfBody::LinkStateAck(_)) {
self.packet_type.set_user(OSPF_TYPE_LINK_STATE_ACK);
self.body = OspfBody::LinkStateAck(OspfLinkStateAck::new());
}
match &mut self.body {
OspfBody::LinkStateAck(ack) => ack,
_ => unreachable!("link state ack body installed above"),
}
}
pub fn version_value(&self) -> u8 {
self.version.value().copied().unwrap_or(OSPF_VERSION_2)
}
pub fn packet_type_value(&self) -> u8 {
self.packet_type
.value()
.copied()
.unwrap_or_else(|| self.body.type_code())
}
pub fn packet_length_value(&self) -> Option<u16> {
self.packet_length.value().copied()
}
pub fn router_id_value(&self) -> Ipv4Addr {
self.router_id
.value()
.copied()
.unwrap_or(Ipv4Addr::UNSPECIFIED)
}
pub fn area_id_value(&self) -> Ipv4Addr {
self.area_id
.value()
.copied()
.unwrap_or(Ipv4Addr::UNSPECIFIED)
}
pub fn checksum_value(&self) -> Option<u16> {
self.checksum.value().copied()
}
pub fn autype_value(&self) -> u16 {
self.autype.value().copied().unwrap_or(OSPF_AUTYPE_NULL)
}
pub fn authentication_value(&self) -> [u8; OSPF_AUTH_LEN] {
self.authentication
.value()
.copied()
.unwrap_or([0; OSPF_AUTH_LEN])
}
pub fn checksum_status(&self) -> OspfChecksumStatus {
self.checksum_status
}
pub fn crypto_auth_params(&self) -> Option<&OspfCryptoAuth> {
self.crypto_auth.as_ref()
}
pub fn crypto_key_id(&self) -> Option<u8> {
if self.autype_value() == OSPF_AUTYPE_CRYPTOGRAPHIC {
Some(self.authentication_value()[2])
} else {
None
}
}
pub fn crypto_auth_data_length(&self) -> Option<u8> {
if self.autype_value() == OSPF_AUTYPE_CRYPTOGRAPHIC {
Some(self.authentication_value()[3])
} else {
None
}
}
pub fn crypto_sequence_number(&self) -> Option<u32> {
if self.autype_value() == OSPF_AUTYPE_CRYPTOGRAPHIC {
let field = self.authentication_value();
Some(u32::from_be_bytes([field[4], field[5], field[6], field[7]]))
} else {
None
}
}
pub fn auth_trailer(&self) -> &[u8] {
&self.auth_trailer
}
fn crypto_auth_for_trailer(&self) -> Option<&OspfCryptoAuth> {
match &self.crypto_auth {
Some(crypto) if self.autype_value() == OSPF_AUTYPE_CRYPTOGRAPHIC => Some(crypto),
_ => None,
}
}
fn crypto_trailer_len(&self) -> usize {
if let Some(crypto) = self.crypto_auth_for_trailer() {
usize::from(crypto.digest_len())
} else {
self.auth_trailer.len()
}
}
}
impl Default for Ospfv2 {
fn default() -> Self {
Self::new()
}
}
impl Layer for Ospfv2 {
fn name(&self) -> &'static str {
"Ospf"
}
fn summary(&self) -> String {
let len = self
.packet_length_value()
.map(usize::from)
.unwrap_or(OSPF_HEADER_LEN + self.body.encoded_len());
match &self.body {
OspfBody::Hello(hello) => format!(
"Ospf(type={}, rid={}, area={}, mask={}, dr={}, bdr={}, neighbors={})",
ospf_type_name(self.packet_type_value()),
self.router_id_value(),
self.area_id_value(),
hello.network_mask_value(),
hello.designated_router_value(),
hello.backup_designated_router_value(),
hello.neighbors_value().len()
),
OspfBody::DatabaseDescription(dd) => format!(
"Ospf(type={}, rid={}, area={}, mtu={}, seq=0x{:08x}, flags=0x{:02x}, lsa_headers={})",
ospf_type_name(self.packet_type_value()),
self.router_id_value(),
self.area_id_value(),
dd.interface_mtu_value(),
dd.dd_sequence_number_value(),
dd.flags_value(),
dd.lsa_headers_value().len()
),
OspfBody::LinkStateRequest(lsr) => format!(
"Ospf(type={}, rid={}, area={}, requests={})",
ospf_type_name(self.packet_type_value()),
self.router_id_value(),
self.area_id_value(),
lsr.entries_value().len()
),
OspfBody::LinkStateUpdate(lsu) => format!(
"Ospf(type={}, rid={}, area={}, num_lsas={}, lsas={})",
ospf_type_name(self.packet_type_value()),
self.router_id_value(),
self.area_id_value(),
lsu.num_lsas_value(),
lsu.lsas_value().len()
),
OspfBody::LinkStateAck(ack) => format!(
"Ospf(type={}, rid={}, area={}, lsa_headers={})",
ospf_type_name(self.packet_type_value()),
self.router_id_value(),
self.area_id_value(),
ack.lsa_headers_value().len()
),
OspfBody::Unknown { .. } => format!(
"Ospf(type={}, rid={}, area={}, len={})",
ospf_type_name(self.packet_type_value()),
self.router_id_value(),
self.area_id_value(),
len
),
}
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![
("version", self.version_value().to_string()),
("type", ospf_type_name(self.packet_type_value()).to_string()),
(
"length",
self.packet_length_value()
.map(|value| value.to_string())
.unwrap_or_else(|| "auto".to_string()),
),
("router_id", self.router_id_value().to_string()),
("area_id", self.area_id_value().to_string()),
(
"checksum",
self.checksum_value()
.map(|value| format!("0x{value:04x}"))
.unwrap_or_else(|| "auto".to_string()),
),
(
"autype",
format!(
"{} ({})",
self.autype_value(),
ospf_autype_name(self.autype_value())
),
),
(
"authentication",
format!("0x{}", hex_bytes(&self.authentication_value())),
),
];
if let OspfBody::Hello(hello) = &self.body {
fields.push(("hello_interval", hello.hello_interval_value().to_string()));
fields.push((
"dead_interval",
hello.router_dead_interval_value().to_string(),
));
fields.push(("options", format_options(hello.options_value())));
fields.push(("priority", hello.router_priority_value().to_string()));
fields.push(("network_mask", hello.network_mask_value().to_string()));
fields.push((
"designated_router",
hello.designated_router_value().to_string(),
));
fields.push((
"backup_designated_router",
hello.backup_designated_router_value().to_string(),
));
fields.push(("neighbor_count", hello.neighbors_value().len().to_string()));
for neighbor in hello.neighbors_value() {
fields.push(("neighbor", neighbor.to_string()));
}
}
if let OspfBody::DatabaseDescription(dd) = &self.body {
fields.push(("interface_mtu", dd.interface_mtu_value().to_string()));
fields.push(("options", format_options(dd.options_value())));
fields.push(("dd_flags", format_dd_flags(dd)));
fields.push((
"dd_sequence_number",
format!("0x{:08x}", dd.dd_sequence_number_value()),
));
fields.push(("lsa_header_count", dd.lsa_headers_value().len().to_string()));
for header in dd.lsa_headers_value() {
fields.push(("lsa_header", header.summary()));
}
}
if let OspfBody::LinkStateRequest(lsr) = &self.body {
fields.push(("request_count", lsr.entries_value().len().to_string()));
for entry in lsr.entries_value() {
fields.push((
"request",
format!(
"ls_type=0x{:08x}, id={}, adv={}",
entry.ls_type_value(),
entry.link_state_id_value(),
entry.advertising_router_value()
),
));
}
}
if let OspfBody::LinkStateUpdate(lsu) = &self.body {
fields.push(("num_lsas", lsu.num_lsas_value().to_string()));
fields.push(("lsa_count", lsu.lsas_value().len().to_string()));
for lsa in lsu.lsas_value() {
fields.push((
"lsa",
format!("{} body={}B", lsa.header.summary(), lsa.body.encoded_len()),
));
if let crate::protocols::ospf::lsa::OspfLsaBody::Router(router) = &lsa.body {
fields.push(("router_lsa", router.summary()));
for link in router.links_value() {
fields.push(("router_link", link.summary()));
}
}
if let crate::protocols::ospf::lsa::OspfLsaBody::Network(network) = &lsa.body {
fields.push(("network_lsa", network.summary()));
for router in network.attached_routers_value() {
fields.push(("attached_router", router.to_string()));
}
}
if let crate::protocols::ospf::lsa::OspfLsaBody::Summary(summary) = &lsa.body {
fields.push(("summary_lsa", summary.summary()));
for entry in summary.entries_value() {
fields.push((
"summary_tos",
format!("tos={} metric={}", entry.tos_value(), entry.metric_value()),
));
}
}
if let crate::protocols::ospf::lsa::OspfLsaBody::AsExternal(external) = &lsa.body {
fields.push(("as_external_lsa", external.summary()));
for entry in external.entries_value() {
fields.push((
"as_external_tos",
format!(
"type={} metric={} fwd={} tag=0x{:08x}",
if entry.e_bit_value() { "E2" } else { "E1" },
entry.metric_value(),
entry.forwarding_address_value(),
entry.external_route_tag_value()
),
));
}
}
if let crate::protocols::ospf::lsa::OspfLsaBody::Nssa(nssa) = &lsa.body {
let p_bit = lsa.header.options_value()
& crate::protocols::ospf::lsa::OSPF_OPTIONS_NP
!= 0;
fields.push((
"nssa_lsa",
format!(
"P={} {}",
if p_bit { "set" } else { "clear" },
nssa.summary()
),
));
for entry in nssa.entries_value() {
fields.push((
"nssa_tos",
format!(
"type={} metric={} fwd={} tag=0x{:08x}",
if entry.e_bit_value() { "E2" } else { "E1" },
entry.metric_value(),
entry.forwarding_address_value(),
entry.external_route_tag_value()
),
));
}
}
if let crate::protocols::ospf::lsa::OspfLsaBody::Opaque(opaque) = &lsa.body {
fields.push((
"opaque_lsa",
format!(
"type={} {}",
crate::protocols::ospf::lsa::opaque_type(&lsa.header),
opaque.summary()
),
));
for tlv in opaque.tlvs_value() {
fields.push((
"opaque_tlv",
format!("type={} value={}B", tlv.tlv_type(), tlv.value().len()),
));
}
}
}
}
if let OspfBody::LinkStateAck(ack) = &self.body {
fields.push((
"lsa_header_count",
ack.lsa_headers_value().len().to_string(),
));
for header in ack.lsa_headers_value() {
fields.push(("lsa_header", header.summary()));
}
}
fields
}
fn encoded_len(&self) -> usize {
OSPF_HEADER_LEN + self.body.encoded_len() + self.crypto_trailer_len()
}
fn compile(&self, _ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
let start = out.len();
let total_len = OSPF_HEADER_LEN + self.body.encoded_len();
let crypto = self.crypto_auth_for_trailer();
out.push(self.version_value());
out.push(self.packet_type_value());
let packet_length = self
.packet_length
.value()
.copied()
.unwrap_or(total_len as u16);
out.extend_from_slice(&packet_length.to_be_bytes());
out.extend_from_slice(&self.router_id_value().octets());
out.extend_from_slice(&self.area_id_value().octets());
let pinned_checksum = self.checksum.value().copied();
out.extend_from_slice(&pinned_checksum.unwrap_or(0).to_be_bytes());
out.extend_from_slice(&self.autype_value().to_be_bytes());
out.extend_from_slice(&self.authentication_value());
self.body.encode(out);
if let Some(crypto) = crypto {
let digest = crypto.digest(&out[start..]);
out.extend_from_slice(&digest);
} else if !self.auth_trailer.is_empty() {
out.extend_from_slice(&self.auth_trailer);
} else if pinned_checksum.is_none() {
let checksum = internet_checksum_chunks([&out[start..start + 16], &out[start + 24..]]);
out[start + 12..start + 14].copy_from_slice(&checksum.to_be_bytes());
}
Ok(())
}
impl_layer_object!(Ospfv2);
}
impl_layer_div!(Ospfv2);
#[deprecated(note = "renamed to Ospfv2")]
pub type Ospf = Ospfv2;
fn hex_bytes(bytes: &[u8]) -> String {
let mut output = String::with_capacity(bytes.len() * 2);
for byte in bytes {
output.push_str(&format!("{byte:02x}"));
}
output
}
fn format_options(options: u8) -> String {
let labels = ospf_options_summary(options);
if labels.is_empty() {
format!("0x{options:02x}")
} else {
format!("0x{options:02x} ({labels})")
}
}
fn format_dd_flags(dd: &OspfDatabaseDescription) -> String {
let flags = dd.flags_value();
let labels = dd.dd_flags_summary();
if labels.is_empty() {
format!("0x{flags:02x}")
} else {
format!("0x{flags:02x} ({labels})")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::Packet;
#[test]
fn ospf_new_defaults_common_header() {
let ospf = Ospfv2::new();
assert_eq!(ospf.name(), "Ospf");
assert_eq!(ospf.version_value(), OSPF_VERSION_2);
assert_eq!(ospf.autype_value(), OSPF_AUTYPE_NULL);
assert_eq!(ospf.authentication_value(), [0; OSPF_AUTH_LEN]);
assert_eq!(ospf.checksum_value(), None);
assert_eq!(ospf.packet_length_value(), None);
assert_eq!(ospf.encoded_len(), OSPF_HEADER_LEN);
}
#[test]
fn ospf_common_header_compiles_with_auto_length_and_checksum() {
let body = [0xde, 0xad, 0xbe, 0xef];
let ospf = Ospfv2::new()
.packet_type(OSPF_TYPE_HELLO)
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.raw_body(body);
let bytes = Packet::from_layer(ospf).compile().unwrap();
let total = OSPF_HEADER_LEN + body.len();
assert_eq!(bytes.len(), total);
assert_eq!(bytes[0], OSPF_VERSION_2);
assert_eq!(bytes[1], OSPF_TYPE_HELLO);
assert_eq!(&bytes[2..4], &(total as u16).to_be_bytes());
assert_eq!(&bytes[4..8], &[192, 0, 2, 1]);
assert_eq!(&bytes[8..12], &[0, 0, 0, 0]);
assert_eq!(&bytes[14..16], &OSPF_AUTYPE_NULL.to_be_bytes());
assert_eq!(&bytes[16..24], &[0u8; OSPF_AUTH_LEN]);
assert_eq!(&bytes[OSPF_HEADER_LEN..], &body);
let mut zeroed = bytes.as_bytes().to_vec();
zeroed[12] = 0;
zeroed[13] = 0;
let expected = internet_checksum_chunks([&zeroed[0..16], &zeroed[24..]]);
assert_eq!(&bytes[12..14], &expected.to_be_bytes());
assert_ne!(&bytes[12..14], &[0, 0]);
}
#[test]
fn ospf_caller_set_checksum_survives_compile() {
let ospf = Ospfv2::new()
.packet_type(OSPF_TYPE_HELLO)
.raw_body([0x01, 0x02])
.checksum(0x1234);
let bytes = Packet::from_layer(ospf).compile().unwrap();
assert_eq!(&bytes[12..14], &0x1234u16.to_be_bytes());
assert_eq!(bytes[1], OSPF_TYPE_HELLO);
assert_eq!(bytes[0], OSPF_VERSION_2);
}
#[test]
fn ospf_caller_set_packet_length_survives_compile() {
let ospf = Ospfv2::new()
.packet_type(OSPF_TYPE_HELLO)
.raw_body([0x00])
.packet_length(0xbeef);
let bytes = Packet::from_layer(ospf).compile().unwrap();
assert_eq!(&bytes[2..4], &0xbeefu16.to_be_bytes());
}
#[test]
fn ospf_summary_and_inspection_expose_the_common_header() {
let ospf = Ospfv2::new()
.packet_type(OSPF_TYPE_HELLO)
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0]);
let summary = ospf.summary();
assert!(
summary.contains("Hello"),
"summary missing type name: {summary}"
);
assert!(
summary.contains("192.0.2.1"),
"summary missing router id: {summary}"
);
let fields = ospf.inspection_fields();
let type_field = fields
.iter()
.find(|(name, _)| *name == "type")
.map(|(_, value)| value.as_str());
assert_eq!(type_field, Some(ospf_type_name(OSPF_TYPE_HELLO)));
assert_eq!(type_field, Some("Hello"));
let length = fields.iter().find(|(name, _)| *name == "length");
assert_eq!(length.map(|(_, v)| v.as_str()), Some("auto"));
let checksum = fields.iter().find(|(name, _)| *name == "checksum");
assert_eq!(checksum.map(|(_, v)| v.as_str()), Some("auto"));
}
#[test]
fn ospf_hello_summary_and_inspection_describe_the_body() {
let ospf = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_hello(|h| {
*h = OspfHello::new()
.network_mask(Ipv4Addr::new(255, 255, 255, 0))
.designated_router(Ipv4Addr::new(192, 0, 2, 1))
.backup_designated_router(Ipv4Addr::new(192, 0, 2, 2))
.neighbor(Ipv4Addr::new(192, 0, 2, 3))
.neighbor(Ipv4Addr::new(192, 0, 2, 4))
.neighbor(Ipv4Addr::new(192, 0, 2, 5));
});
let summary = ospf.summary();
assert!(
summary.contains("type=Hello"),
"summary missing type: {summary}"
);
assert!(
summary.contains("mask=255.255.255.0"),
"summary missing network mask: {summary}"
);
assert!(
summary.contains("dr=192.0.2.1"),
"summary missing DR: {summary}"
);
assert!(
summary.contains("bdr=192.0.2.2"),
"summary missing BDR: {summary}"
);
assert!(
summary.contains("neighbors=3"),
"summary missing neighbor count: {summary}"
);
let fields = ospf.inspection_fields();
let value_of = |name: &str| {
fields
.iter()
.find(|(field, _)| *field == name)
.map(|(_, value)| value.as_str())
};
assert_eq!(value_of("network_mask"), Some("255.255.255.0"));
assert_eq!(value_of("designated_router"), Some("192.0.2.1"));
assert_eq!(value_of("backup_designated_router"), Some("192.0.2.2"));
assert_eq!(value_of("hello_interval"), Some("10"));
assert_eq!(value_of("dead_interval"), Some("40"));
assert_eq!(value_of("priority"), Some("0"));
assert_eq!(value_of("options"), Some("0x00"));
assert_eq!(value_of("neighbor_count"), Some("3"));
let neighbors: Vec<&str> = fields
.iter()
.filter(|(field, _)| *field == "neighbor")
.map(|(_, value)| value.as_str())
.collect();
assert_eq!(neighbors, vec!["192.0.2.3", "192.0.2.4", "192.0.2.5"]);
}
#[test]
fn ospf_link_state_update_summary_and_inspection_describe_each_lsa() {
use crate::protocols::ospf::lsa::{
OspfLsa, OspfLsaBody, OspfLsaHeader, OSPF_LSA_NETWORK, OSPF_LSA_ROUTER,
};
let router_lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_ROUTER)
.link_state_id(Ipv4Addr::new(192, 0, 2, 1))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1)),
OspfLsaBody::Raw(vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06]),
);
let network_lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_NETWORK)
.link_state_id(Ipv4Addr::new(192, 0, 2, 2))
.advertising_router(Ipv4Addr::new(198, 51, 100, 7)),
OspfLsaBody::Raw(vec![0xaa, 0xbb, 0xcc, 0xdd]),
);
let ospf = Ospfv2::link_state_update()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_link_state_update(|u| {
*u = OspfLinkStateUpdate::new().lsa(router_lsa).lsa(network_lsa);
});
let summary = ospf.summary();
assert!(
summary.contains("type=LSUpdate"),
"summary missing type: {summary}"
);
assert!(
summary.contains("lsas=2"),
"summary missing carried-LSA count: {summary}"
);
let fields = ospf.inspection_fields();
let lsa_count = fields
.iter()
.find(|(field, _)| *field == "lsa_count")
.map(|(_, value)| value.as_str());
assert_eq!(lsa_count, Some("2"));
let lsa_entries: Vec<&str> = fields
.iter()
.filter(|(field, _)| *field == "lsa")
.map(|(_, value)| value.as_str())
.collect();
assert_eq!(lsa_entries.len(), 2);
assert!(
lsa_entries[0].contains("type=Router"),
"first lsa entry missing header summary: {}",
lsa_entries[0]
);
assert!(
lsa_entries[0].contains("body=6B"),
"first lsa entry missing body byte length: {}",
lsa_entries[0]
);
assert!(
lsa_entries[1].contains("type=Network"),
"second lsa entry missing header summary: {}",
lsa_entries[1]
);
assert!(
lsa_entries[1].contains("body=4B"),
"second lsa entry missing body byte length: {}",
lsa_entries[1]
);
}
#[test]
fn ospf_types_and_constants_reach_the_prelude() {
use crate::prelude::*;
let ospf = Ospfv2::new()
.packet_type(OSPF_TYPE_HELLO)
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.autype(OSPF_AUTYPE_NULL);
assert_eq!(ospf.version_value(), OSPF_VERSION_2);
assert_eq!(ospf.packet_type_value(), OSPF_TYPE_HELLO);
assert_eq!(ospf.autype_value(), OSPF_AUTYPE_NULL);
let _ = (
OSPF_TYPE_DATABASE_DESCRIPTION,
OSPF_TYPE_LINK_STATE_REQUEST,
OSPF_TYPE_LINK_STATE_UPDATE,
OSPF_TYPE_LINK_STATE_ACK,
OSPF_HEADER_LEN,
OSPF_AUTH_LEN,
OSPF_AUTYPE_SIMPLE,
OSPF_AUTYPE_CRYPTOGRAPHIC,
);
let body = OspfBody::Unknown {
type_code: OSPF_TYPE_HELLO,
body: Vec::new(),
};
assert_eq!(body.type_code(), OSPF_TYPE_HELLO);
#[allow(deprecated)]
let _alias: Ospf = Ospfv2::new();
let bytes = Packet::from_layer(ospf).compile().unwrap();
assert_eq!(bytes[1], OSPF_TYPE_HELLO);
}
#[test]
fn ospf_decode_records_valid_checksum_status() {
use crate::packet::NetworkLayer;
use crate::protocols::ip::v4::Ipv4;
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
/ Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0]))
.compile()
.expect("Ipv4 / Ospfv2 Hello compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.expect("the default registry decodes OSPF over IPv4");
let ospf = decoded
.layer::<Ospfv2>()
.expect("the decoded packet exposes a typed Ospfv2 layer");
assert_eq!(ospf.checksum_status(), OspfChecksumStatus::Valid);
let recompiled = decoded.compile().expect("decoded OSPF re-compiles");
assert_eq!(recompiled.as_bytes(), bytes.as_bytes());
}
#[test]
fn ospf_decode_records_invalid_checksum_status() {
use crate::packet::NetworkLayer;
use crate::protocols::ip::v4::Ipv4;
let mut raw = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
/ Ospfv2::hello().router_id([192, 0, 2, 1]))
.compile()
.expect("Ipv4 / Ospfv2 Hello compiles")
.as_bytes()
.to_vec();
let ipv4_header_len = (raw[0] & 0x0f) as usize * 4;
let ospf_checksum_octet = ipv4_header_len + 12;
raw[ospf_checksum_octet] ^= 0xff;
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, &raw)
.expect("a corrupted-OSPF-checksum buffer still decodes structurally");
let ospf = decoded
.layer::<Ospfv2>()
.expect("the decoded packet exposes a typed Ospfv2 layer");
assert_eq!(ospf.checksum_status(), OspfChecksumStatus::Invalid);
let mut ospf_only = Vec::new();
let recompiled_ospf = Packet::from_layer(ospf.clone())
.compile()
.expect("the decoded OSPF layer re-compiles");
ospf_only.extend_from_slice(recompiled_ospf.as_bytes());
assert_eq!(&raw[ipv4_header_len..], ospf_only.as_slice());
}
#[test]
fn ospf_decode_skips_checksum_validation_when_disabled() {
use crate::packet::NetworkLayer;
use crate::protocols::ip::v4::Ipv4;
use crate::registry::ProtocolRegistry;
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
/ Ospfv2::hello().router_id([192, 0, 2, 1]))
.compile()
.expect("Ipv4 / Ospfv2 Hello compiles");
let registry = ProtocolRegistry::new().checksum_validation(false);
let decoded = registry
.decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.expect("the registry decodes OSPF over IPv4");
let ospf = decoded
.layer::<Ospfv2>()
.expect("the decoded packet exposes a typed Ospfv2 layer");
assert_eq!(ospf.checksum_status(), OspfChecksumStatus::NotChecked);
let recompiled = decoded.compile().expect("decoded OSPF re-compiles");
assert_eq!(recompiled.as_bytes(), bytes.as_bytes());
}
#[test]
fn ospf_simple_password_encodes_into_the_auth_field_and_round_trips() {
use crate::packet::NetworkLayer;
use crate::protocols::ip::v4::Ipv4;
let ospf = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.simple_password(b"secret");
assert_eq!(ospf.autype_value(), OSPF_AUTYPE_SIMPLE);
assert_eq!(
ospf.authentication_value(),
[b's', b'e', b'c', b'r', b'e', b't', 0, 0]
);
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
/ ospf)
.compile()
.expect("Ipv4 / simple-password Hello compiles");
let raw = bytes.as_bytes();
let ipv4_header_len = (raw[0] & 0x0f) as usize * 4;
let ospf = ipv4_header_len;
assert_eq!(
&raw[ospf + 14..ospf + 16],
&OSPF_AUTYPE_SIMPLE.to_be_bytes()
);
assert_eq!(
&raw[ospf + 16..ospf + 24],
&[b's', b'e', b'c', b'r', b'e', b't', 0, 0]
);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, raw)
.expect("the default registry decodes the simple-password Hello");
let layer = decoded
.layer::<Ospfv2>()
.expect("the decoded packet exposes a typed Ospfv2 layer");
assert_eq!(layer.autype_value(), OSPF_AUTYPE_SIMPLE);
assert_eq!(
layer.authentication_value(),
[b's', b'e', b'c', b'r', b'e', b't', 0, 0]
);
assert_eq!(layer.checksum_status(), OspfChecksumStatus::Valid);
let recompiled = decoded.compile().expect("decoded OSPF re-compiles");
assert_eq!(recompiled.as_bytes(), raw);
}
#[test]
fn ospf_null_auth_zeroes_the_auth_field() {
let ospf = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.null_auth();
assert_eq!(ospf.autype_value(), OSPF_AUTYPE_NULL);
assert_eq!(ospf.authentication_value(), [0; OSPF_AUTH_LEN]);
let bytes = Packet::from_layer(ospf).compile().unwrap();
assert_eq!(&bytes[14..16], &OSPF_AUTYPE_NULL.to_be_bytes());
assert_eq!(&bytes[16..24], &[0u8; OSPF_AUTH_LEN]);
}
#[test]
fn ospf_inspection_surfaces_the_autype_name() {
let ospf = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.simple_password(b"secret");
let fields = ospf.inspection_fields();
let autype = fields
.iter()
.find(|(name, _)| *name == "autype")
.map(|(_, value)| value.as_str());
assert_eq!(autype, Some("1 (Simple)"));
let null = Ospfv2::hello().router_id([192, 0, 2, 1]).null_auth();
let null_autype = null
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "autype")
.map(|(_, value)| value);
assert_eq!(null_autype.as_deref(), Some("0 (Null)"));
}
#[test]
fn ospf_crypto_md5_auth_zeroes_checksum_and_appends_digest_trailer() {
let key = b"ospf-secret-key".to_vec();
let ospf = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.crypto_md5_auth(7, 0x0000_002a, key.clone());
assert_eq!(ospf.autype_value(), OSPF_AUTYPE_CRYPTOGRAPHIC);
assert_eq!(
ospf.authentication_value(),
[
0x00,
0x00,
0x07,
OSPF_MD5_DIGEST_LEN,
0x00,
0x00,
0x00,
0x2a
]
);
let packet_len = OSPF_HEADER_LEN + ospf.body.encoded_len();
let bytes = Packet::from_layer(ospf).compile().unwrap();
assert_eq!(bytes.len(), packet_len + OSPF_MD5_DIGEST_LEN as usize);
assert_eq!(&bytes[2..4], &(packet_len as u16).to_be_bytes());
assert_eq!(&bytes[12..14], &[0u8, 0u8]);
assert_eq!(
&bytes[16..24],
&[
0x00,
0x00,
0x07,
OSPF_MD5_DIGEST_LEN,
0x00,
0x00,
0x00,
0x2a
]
);
let trailer = &bytes.as_bytes()[packet_len..];
assert_eq!(trailer.len(), OSPF_MD5_DIGEST_LEN as usize);
let recomputed =
OspfCryptoAuth::new(7, 0x0000_002a, key).md5_digest(&bytes.as_bytes()[..packet_len]);
assert_eq!(trailer, recomputed);
}
#[test]
fn ospf_crypto_md5_auth_honors_pinned_checksum_and_authentication() {
let ospf = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.crypto_md5_auth(3, 1, b"k".to_vec())
.checksum(0xbeef)
.authentication([0xaa; OSPF_AUTH_LEN]);
let packet_len = OSPF_HEADER_LEN + ospf.body.encoded_len();
let bytes = Packet::from_layer(ospf).compile().unwrap();
assert_eq!(&bytes[12..14], &0xbeefu16.to_be_bytes());
assert_eq!(&bytes[16..24], &[0xaau8; OSPF_AUTH_LEN]);
assert_eq!(bytes.len(), packet_len + OSPF_MD5_DIGEST_LEN as usize);
}
#[test]
fn ospf_decode_crypto_md5_auth_exposes_fields_and_round_trips_trailer() {
use crate::packet::NetworkLayer;
use crate::protocols::ip::v4::Ipv4;
let key = b"ospf-secret-key".to_vec();
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
/ Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.crypto_md5_auth(7, 0x0000_002a, key.clone()))
.compile()
.expect("Ipv4 / crypto-MD5 Hello compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.expect("the default registry decodes the crypto-MD5 Hello");
let ospf = decoded
.layer::<Ospfv2>()
.expect("the decoded packet exposes a typed Ospfv2 layer");
assert_eq!(ospf.autype_value(), OSPF_AUTYPE_CRYPTOGRAPHIC);
assert_eq!(ospf.crypto_key_id(), Some(7));
assert_eq!(ospf.crypto_auth_data_length(), Some(OSPF_MD5_DIGEST_LEN));
assert_eq!(ospf.crypto_sequence_number(), Some(0x0000_002a));
let trailer = ospf.auth_trailer();
assert_eq!(trailer.len(), OSPF_MD5_DIGEST_LEN as usize);
let packet_len = OSPF_HEADER_LEN + ospf.body.encoded_len();
let ipv4_header_len = (bytes.as_bytes()[0] & 0x0f) as usize * 4;
let ospf_bytes = &bytes.as_bytes()[ipv4_header_len..];
let recomputed =
OspfCryptoAuth::new(7, 0x0000_002a, key).md5_digest(&ospf_bytes[..packet_len]);
assert_eq!(trailer, recomputed);
assert_eq!(ospf.checksum_status(), OspfChecksumStatus::NotChecked);
assert!(ospf.crypto_auth_params().is_none());
let recompiled = decoded.compile().expect("decoded crypto OSPF re-compiles");
assert_eq!(recompiled.as_bytes(), bytes.as_bytes());
}
#[test]
fn ospf_crypto_field_accessors_are_none_for_non_cryptographic_autype() {
let simple = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.simple_password(b"secret");
assert_eq!(simple.crypto_key_id(), None);
assert_eq!(simple.crypto_auth_data_length(), None);
assert_eq!(simple.crypto_sequence_number(), None);
assert!(simple.auth_trailer().is_empty());
}
#[test]
fn ospf_crypto_hmac_sha256_auth_yields_a_32_octet_trailer() {
let ospf = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.crypto_auth(
OspfCryptoAlgorithm::HmacSha256,
3,
0x0000_0010,
b"sha256-key",
);
assert_eq!(ospf.autype_value(), OSPF_AUTYPE_CRYPTOGRAPHIC);
assert_eq!(ospf.crypto_key_id(), Some(3));
assert_eq!(ospf.crypto_auth_data_length(), Some(32));
assert_eq!(ospf.crypto_sequence_number(), Some(0x0000_0010));
let packet_len = OSPF_HEADER_LEN + ospf.body.encoded_len();
let bytes = Packet::from_layer(ospf).compile().unwrap();
assert_eq!(&bytes[2..4], &(packet_len as u16).to_be_bytes());
assert_eq!(bytes.len(), packet_len + 32);
assert_eq!(&bytes[12..14], &[0, 0]);
let recomputed = OspfCryptoAuth::with_algorithm(
OspfCryptoAlgorithm::HmacSha256,
3,
0x0000_0010,
b"sha256-key".to_vec(),
)
.digest(&bytes[..packet_len]);
assert_eq!(&bytes[packet_len..], recomputed.as_slice());
}
#[test]
fn ospf_crypto_hmac_sha256_auth_round_trips_byte_for_byte() {
use crate::packet::NetworkLayer;
use crate::protocols::ip::v4::Ipv4;
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
/ Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.crypto_auth(
OspfCryptoAlgorithm::HmacSha256,
3,
0x0000_0010,
b"sha256-key",
))
.compile()
.expect("Ipv4 / HMAC-SHA-256 Hello compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.expect("the default registry decodes the HMAC-SHA-256 Hello");
let ospf = decoded
.layer::<Ospfv2>()
.expect("the decoded packet exposes a typed Ospfv2 layer");
assert_eq!(ospf.crypto_auth_data_length(), Some(32));
assert_eq!(ospf.auth_trailer().len(), 32);
assert!(ospf.crypto_auth_params().is_none());
let recompiled = decoded.compile().expect("decoded HMAC OSPF re-compiles");
assert_eq!(recompiled.as_bytes(), bytes.as_bytes());
}
#[test]
fn ospf_crypto_auth_each_algorithm_appends_its_digest_length() {
for (algorithm, digest_len) in [
(OspfCryptoAlgorithm::KeyedMd5, 16usize),
(OspfCryptoAlgorithm::HmacSha1, 20),
(OspfCryptoAlgorithm::HmacSha256, 32),
(OspfCryptoAlgorithm::HmacSha384, 48),
(OspfCryptoAlgorithm::HmacSha512, 64),
] {
let ospf = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.crypto_auth(algorithm, 1, 0x0000_0001, b"shared-secret");
assert_eq!(ospf.crypto_auth_data_length(), Some(digest_len as u8));
let packet_len = OSPF_HEADER_LEN + ospf.body.encoded_len();
let bytes = Packet::from_layer(ospf).compile().unwrap();
assert_eq!(bytes.len(), packet_len + digest_len);
assert_eq!(&bytes[2..4], &(packet_len as u16).to_be_bytes());
}
}
#[test]
fn ospf_auth_length_and_checksum_invariants_hold_across_all_auth_types() {
use crate::packet::NetworkLayer;
use crate::protocols::ip::v4::Ipv4;
const IPV4_HEADER_LEN: usize = 20;
enum Check {
Validated,
Crypto,
}
let cases: Vec<(&str, Ospfv2, usize, Check)> = vec![
(
"null",
Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.null_auth(),
0,
Check::Validated,
),
(
"simple",
Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.simple_password(b"secret"),
0,
Check::Validated,
),
(
"keyed-md5",
Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.crypto_md5_auth(7, 0x0000_002a, b"ospf-secret-key".to_vec()),
OSPF_MD5_DIGEST_LEN as usize,
Check::Crypto,
),
(
"hmac-sha256",
Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.crypto_auth(
OspfCryptoAlgorithm::HmacSha256,
3,
0x0000_0010,
b"sha256-key",
),
OSPF_HMAC_SHA256_DIGEST_LEN as usize,
Check::Crypto,
),
];
for (label, ospf, trailer_len, check) in cases {
let body_len = ospf.body.encoded_len();
let ospf_packet_len = OSPF_HEADER_LEN + body_len;
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
/ ospf)
.compile()
.unwrap_or_else(|e| panic!("{label}: Ipv4 / Ospfv2 Hello compiles: {e}"));
let raw = bytes.as_bytes();
let ipv4_header_len = (raw[0] & 0x0f) as usize * 4;
assert_eq!(ipv4_header_len, IPV4_HEADER_LEN, "{label}: IPv4 header len");
assert_eq!(
&raw[ipv4_header_len + 2..ipv4_header_len + 4],
&(ospf_packet_len as u16).to_be_bytes(),
"{label}: OSPF Packet Length must equal header+body"
);
let ospf_on_wire_len = ospf_packet_len + trailer_len;
assert_eq!(
raw.len() - ipv4_header_len,
ospf_on_wire_len,
"{label}: OSPF on-wire length includes the digest trailer"
);
let expected_ipv4_total = IPV4_HEADER_LEN + ospf_on_wire_len;
assert_eq!(
&raw[2..4],
&(expected_ipv4_total as u16).to_be_bytes(),
"{label}: IPv4 total length must include the digest trailer"
);
assert_eq!(
raw.len(),
expected_ipv4_total,
"{label}: total emitted bytes match the IPv4 total length"
);
let ospf_checksum = &raw[ipv4_header_len + 12..ipv4_header_len + 14];
match check {
Check::Validated => {
assert_ne!(
ospf_checksum,
&[0u8, 0u8],
"{label}: Internet checksum must be filled"
);
}
Check::Crypto => {
assert_eq!(
ospf_checksum,
&[0u8, 0u8],
"{label}: crypto-auth checksum field must be zero"
);
}
}
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, raw)
.unwrap_or_else(|e| panic!("{label}: default registry decodes OSPF: {e}"));
let layer = decoded
.layer::<Ospfv2>()
.unwrap_or_else(|| panic!("{label}: decoded packet exposes a typed Ospfv2 layer"));
let expected_status = match check {
Check::Validated => OspfChecksumStatus::Valid,
Check::Crypto => OspfChecksumStatus::NotChecked,
};
assert_eq!(
layer.checksum_status(),
expected_status,
"{label}: decode-time checksum status"
);
let recompiled = decoded
.compile()
.unwrap_or_else(|e| panic!("{label}: decoded OSPF re-compiles: {e}"));
assert_eq!(
recompiled.as_bytes(),
raw,
"{label}: OSPF re-compiles byte-for-byte"
);
}
}
#[test]
fn ospf_combined_options_and_flags_round_trip_with_labels() {
use crate::packet::NetworkLayer;
use crate::protocols::ip::v4::Ipv4;
use crate::protocols::ospf::lsa::{
OspfLsa, OspfLsaBody, OspfLsaHeader, OspfRouterLsa, OSPF_LSA_ROUTER,
OSPF_ROUTER_LSA_FLAG_B, OSPF_ROUTER_LSA_FLAG_E, OSPF_ROUTER_LSA_FLAG_V,
};
use crate::protocols::ospf::packet::database_description::{
OSPF_DD_FLAG_I, OSPF_DD_FLAG_M, OSPF_DD_FLAG_MS,
};
fn round_trip(ospf: Ospfv2) -> Ospfv2 {
let bytes = (Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 1))
.dst(Ipv4Addr::new(192, 0, 2, 2))
/ ospf)
.compile()
.expect("Ipv4 / Ospfv2 compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes.as_bytes())
.expect("the default registry decodes OSPF over IPv4");
let recompiled = decoded.compile().expect("decoded OSPF re-compiles");
assert_eq!(
recompiled.as_bytes(),
bytes.as_bytes(),
"OSPF re-compiles byte-for-byte"
);
decoded
.layer::<Ospfv2>()
.expect("the decoded packet exposes a typed Ospfv2 layer")
.clone()
}
let options = OSPF_OPTIONS_E | OSPF_OPTIONS_O | OSPF_OPTIONS_DC;
let hello = Ospfv2::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_hello(|h| {
*h = h.clone().options(options);
});
let decoded_hello = round_trip(hello);
let hello_body = match &decoded_hello.body {
OspfBody::Hello(hello) => hello,
other => panic!("expected a typed Hello body, got {other:?}"),
};
assert_eq!(hello_body.options_value(), options);
assert_eq!(hello_body.options_value() & OSPF_OPTIONS_E, OSPF_OPTIONS_E);
assert_eq!(hello_body.options_value() & OSPF_OPTIONS_O, OSPF_OPTIONS_O);
assert_eq!(
hello_body.options_value() & OSPF_OPTIONS_DC,
OSPF_OPTIONS_DC
);
assert_eq!(ospf_options_summary(hello_body.options_value()), "E|DC|O");
let dd = Ospfv2::database_description()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_database_description(|d| {
*d = d.clone().init(true).more(true).master(true);
});
let decoded_dd = round_trip(dd);
let dd_body = match &decoded_dd.body {
OspfBody::DatabaseDescription(dd) => dd,
other => panic!("expected a typed Database Description body, got {other:?}"),
};
assert_eq!(
dd_body.flags_value(),
OSPF_DD_FLAG_I | OSPF_DD_FLAG_M | OSPF_DD_FLAG_MS
);
assert!(dd_body.is_init());
assert!(dd_body.is_more());
assert!(dd_body.is_master());
assert_eq!(dd_body.dd_flags_summary(), "I|M|MS");
let router = OspfRouterLsa::new().virtual_link().external().border();
let lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_ROUTER)
.link_state_id(Ipv4Addr::new(192, 0, 2, 1))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
OspfLsaBody::Router(router),
);
let lsu = 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);
});
let decoded_lsu = round_trip(lsu);
let lsu_body = match &decoded_lsu.body {
OspfBody::LinkStateUpdate(lsu) => lsu,
other => panic!("expected a typed Link State Update body, got {other:?}"),
};
let decoded_lsas = lsu_body.lsas_value();
assert_eq!(decoded_lsas.len(), 1);
let decoded_router = match &decoded_lsas[0].body {
OspfLsaBody::Router(router) => router,
other => panic!("expected a typed Router-LSA body, got {other:?}"),
};
assert_eq!(
decoded_router.flags_value(),
OSPF_ROUTER_LSA_FLAG_V | OSPF_ROUTER_LSA_FLAG_E | OSPF_ROUTER_LSA_FLAG_B
);
assert!(decoded_router.is_virtual());
assert!(decoded_router.is_external());
assert!(decoded_router.is_border());
assert_eq!(decoded_router.router_flags_summary(), "V|E|B");
}
}