use core::any::Any;
use core::net::Ipv4Addr;
use core::ops::Div;
use crate::error::Result;
use crate::field::Field;
use crate::packet::{IntoPacket, Layer, LayerContext, Packet, TransportChecksumContext};
use crate::protocols::ip::shared::IPPROTO_OSPF;
use crate::protocols::ospf::{ospf_type_name, OspfChecksumStatus};
pub mod constants;
pub mod decode;
pub mod hello;
pub mod lsa;
pub mod packet;
pub(crate) use decode::append_ospfv3_packet_with_checksum_validation;
pub use constants::*;
pub use hello::Ospfv3Hello;
pub use lsa::{
Ospfv3LinkStateUpdate, Ospfv3Lsa, Ospfv3LsaBody, Ospfv3LsaHeader, Ospfv3NetworkLsa,
Ospfv3RouterInterface, Ospfv3RouterLsa, OSPFV3_LSA_HEADER_LEN,
};
pub use packet::{
Ospfv3DatabaseDescription, Ospfv3LinkStateAck, Ospfv3LinkStateRequest,
Ospfv3LinkStateRequestEntry,
};
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)]
pub enum Ospfv3Body {
Hello(Ospfv3Hello),
DatabaseDescription(Ospfv3DatabaseDescription),
LinkStateRequest(Ospfv3LinkStateRequest),
LinkStateUpdate(Ospfv3LinkStateUpdate),
LinkStateAck(Ospfv3LinkStateAck),
Unknown {
type_code: u8,
body: Vec<u8>,
},
}
impl Ospfv3Body {
fn encoded_len(&self) -> usize {
match self {
Ospfv3Body::Hello(hello) => hello.encoded_len(),
Ospfv3Body::DatabaseDescription(dd) => dd.encoded_len(),
Ospfv3Body::LinkStateRequest(lsr) => lsr.encoded_len(),
Ospfv3Body::LinkStateUpdate(lsu) => lsu.encoded_len(),
Ospfv3Body::LinkStateAck(ack) => ack.encoded_len(),
Ospfv3Body::Unknown { body, .. } => body.len(),
}
}
fn encode(&self, out: &mut Vec<u8>) {
match self {
Ospfv3Body::Hello(hello) => hello.encode(out),
Ospfv3Body::DatabaseDescription(dd) => dd.encode(out),
Ospfv3Body::LinkStateRequest(lsr) => lsr.encode(out),
Ospfv3Body::LinkStateUpdate(lsu) => lsu.encode(out),
Ospfv3Body::LinkStateAck(ack) => ack.encode(out),
Ospfv3Body::Unknown { body, .. } => out.extend_from_slice(body),
}
}
fn type_code(&self) -> u8 {
match self {
Ospfv3Body::Hello(_) => OSPFV3_TYPE_HELLO,
Ospfv3Body::DatabaseDescription(_) => OSPFV3_TYPE_DATABASE_DESCRIPTION,
Ospfv3Body::LinkStateRequest(_) => OSPFV3_TYPE_LINK_STATE_REQUEST,
Ospfv3Body::LinkStateUpdate(_) => OSPFV3_TYPE_LINK_STATE_UPDATE,
Ospfv3Body::LinkStateAck(_) => OSPFV3_TYPE_LINK_STATE_ACK,
Ospfv3Body::Unknown { type_code, .. } => *type_code,
}
}
fn set_type_code(&mut self, type_code: u8) {
match self {
Ospfv3Body::Hello(_)
| Ospfv3Body::DatabaseDescription(_)
| Ospfv3Body::LinkStateRequest(_)
| Ospfv3Body::LinkStateUpdate(_)
| Ospfv3Body::LinkStateAck(_) => {}
Ospfv3Body::Unknown { type_code: tc, .. } => *tc = type_code,
}
}
}
#[derive(Debug, Clone)]
pub struct Ospfv3 {
version: Field<u8>,
packet_type: Field<u8>,
packet_length: Field<u16>,
router_id: Field<Ipv4Addr>,
area_id: Field<Ipv4Addr>,
checksum: Field<u16>,
instance_id: Field<u8>,
reserved: Field<u8>,
body: Ospfv3Body,
checksum_status: OspfChecksumStatus,
}
impl Ospfv3 {
pub fn new() -> Self {
Self {
version: Field::defaulted(OSPF_VERSION_3),
packet_type: Field::unset(),
packet_length: Field::unset(),
router_id: Field::unset(),
area_id: Field::unset(),
checksum: Field::unset(),
instance_id: Field::defaulted(0),
reserved: Field::defaulted(0),
body: Ospfv3Body::Unknown {
type_code: 0,
body: Vec::new(),
},
checksum_status: OspfChecksumStatus::NotChecked,
}
}
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 instance_id(mut self, instance_id: u8) -> Self {
self.instance_id.set_user(instance_id);
self
}
pub fn reserved(mut self, reserved: u8) -> Self {
self.reserved.set_user(reserved);
self
}
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 = Ospfv3Body::Unknown {
type_code,
body: body.into(),
};
self
}
pub fn hello() -> Self {
Self::new().hello_body(Ospfv3Hello::new())
}
pub fn hello_body(mut self, hello: Ospfv3Hello) -> Self {
self.packet_type.set_user(OSPFV3_TYPE_HELLO);
self.body = Ospfv3Body::Hello(hello);
self
}
pub fn with_hello(mut self, configure: impl FnOnce(&mut Ospfv3Hello)) -> Self {
configure(self.hello_mut());
self
}
pub fn hello_mut(&mut self) -> &mut Ospfv3Hello {
if !matches!(self.body, Ospfv3Body::Hello(_)) {
self.packet_type.set_user(OSPFV3_TYPE_HELLO);
self.body = Ospfv3Body::Hello(Ospfv3Hello::new());
}
match &mut self.body {
Ospfv3Body::Hello(hello) => hello,
_ => unreachable!("hello body installed above"),
}
}
pub fn database_description() -> Self {
Self::new().database_description_body(Ospfv3DatabaseDescription::new())
}
pub fn database_description_body(mut self, dd: Ospfv3DatabaseDescription) -> Self {
self.packet_type.set_user(OSPFV3_TYPE_DATABASE_DESCRIPTION);
self.body = Ospfv3Body::DatabaseDescription(dd);
self
}
pub fn with_database_description(
mut self,
configure: impl FnOnce(&mut Ospfv3DatabaseDescription),
) -> Self {
configure(self.database_description_mut());
self
}
pub fn database_description_mut(&mut self) -> &mut Ospfv3DatabaseDescription {
if !matches!(self.body, Ospfv3Body::DatabaseDescription(_)) {
self.packet_type.set_user(OSPFV3_TYPE_DATABASE_DESCRIPTION);
self.body = Ospfv3Body::DatabaseDescription(Ospfv3DatabaseDescription::new());
}
match &mut self.body {
Ospfv3Body::DatabaseDescription(dd) => dd,
_ => unreachable!("database description body installed above"),
}
}
pub fn link_state_request() -> Self {
Self::new().link_state_request_body(Ospfv3LinkStateRequest::new())
}
pub fn link_state_request_body(mut self, lsr: Ospfv3LinkStateRequest) -> Self {
self.packet_type.set_user(OSPFV3_TYPE_LINK_STATE_REQUEST);
self.body = Ospfv3Body::LinkStateRequest(lsr);
self
}
pub fn with_link_state_request(
mut self,
configure: impl FnOnce(&mut Ospfv3LinkStateRequest),
) -> Self {
configure(self.link_state_request_mut());
self
}
pub fn link_state_request_mut(&mut self) -> &mut Ospfv3LinkStateRequest {
if !matches!(self.body, Ospfv3Body::LinkStateRequest(_)) {
self.packet_type.set_user(OSPFV3_TYPE_LINK_STATE_REQUEST);
self.body = Ospfv3Body::LinkStateRequest(Ospfv3LinkStateRequest::new());
}
match &mut self.body {
Ospfv3Body::LinkStateRequest(lsr) => lsr,
_ => unreachable!("link state request body installed above"),
}
}
pub fn link_state_update() -> Self {
Self::new().link_state_update_body(Ospfv3LinkStateUpdate::new())
}
pub fn link_state_update_body(mut self, lsu: Ospfv3LinkStateUpdate) -> Self {
self.packet_type.set_user(OSPFV3_TYPE_LINK_STATE_UPDATE);
self.body = Ospfv3Body::LinkStateUpdate(lsu);
self
}
pub fn with_link_state_update(
mut self,
configure: impl FnOnce(&mut Ospfv3LinkStateUpdate),
) -> Self {
configure(self.link_state_update_mut());
self
}
pub fn link_state_update_mut(&mut self) -> &mut Ospfv3LinkStateUpdate {
if !matches!(self.body, Ospfv3Body::LinkStateUpdate(_)) {
self.packet_type.set_user(OSPFV3_TYPE_LINK_STATE_UPDATE);
self.body = Ospfv3Body::LinkStateUpdate(Ospfv3LinkStateUpdate::new());
}
match &mut self.body {
Ospfv3Body::LinkStateUpdate(lsu) => lsu,
_ => unreachable!("link state update body installed above"),
}
}
pub fn link_state_ack() -> Self {
Self::new().link_state_ack_body(Ospfv3LinkStateAck::new())
}
pub fn link_state_ack_body(mut self, ack: Ospfv3LinkStateAck) -> Self {
self.packet_type.set_user(OSPFV3_TYPE_LINK_STATE_ACK);
self.body = Ospfv3Body::LinkStateAck(ack);
self
}
pub fn with_link_state_ack(mut self, configure: impl FnOnce(&mut Ospfv3LinkStateAck)) -> Self {
configure(self.link_state_ack_mut());
self
}
pub fn link_state_ack_mut(&mut self) -> &mut Ospfv3LinkStateAck {
if !matches!(self.body, Ospfv3Body::LinkStateAck(_)) {
self.packet_type.set_user(OSPFV3_TYPE_LINK_STATE_ACK);
self.body = Ospfv3Body::LinkStateAck(Ospfv3LinkStateAck::new());
}
match &mut self.body {
Ospfv3Body::LinkStateAck(ack) => ack,
_ => unreachable!("link state ack body installed above"),
}
}
pub fn version_value(&self) -> u8 {
self.version.value().copied().unwrap_or(OSPF_VERSION_3)
}
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 instance_id_value(&self) -> u8 {
self.instance_id.value().copied().unwrap_or(0)
}
pub fn reserved_value(&self) -> u8 {
self.reserved.value().copied().unwrap_or(0)
}
pub fn checksum_status(&self) -> OspfChecksumStatus {
self.checksum_status
}
fn checksum_context(ctx: &LayerContext<'_>) -> Option<TransportChecksumContext> {
(0..ctx.index()).rev().find_map(|index| {
ctx.packet()
.get(index)
.and_then(|layer| layer.transport_checksum_context(IPPROTO_OSPF))
})
}
}
impl Default for Ospfv3 {
fn default() -> Self {
Self::new()
}
}
impl Layer for Ospfv3 {
fn name(&self) -> &'static str {
"Ospfv3"
}
fn summary(&self) -> String {
let len = self
.packet_length_value()
.map(usize::from)
.unwrap_or(OSPFV3_HEADER_LEN + self.body.encoded_len());
let type_name = ospf_type_name(self.packet_type_value());
let rid = self.router_id_value();
let area = self.area_id_value();
let inst = self.instance_id_value();
match &self.body {
Ospfv3Body::Hello(hello) => format!(
"Ospfv3(type={type_name}, rid={rid}, area={area}, inst={inst}, dr={}, bdr={}, neighbors={})",
hello.designated_router_value(),
hello.backup_designated_router_value(),
hello.neighbors_value().len()
),
Ospfv3Body::DatabaseDescription(dd) => format!(
"Ospfv3(type={type_name}, rid={rid}, area={area}, inst={inst}, mtu={}, seq=0x{:08x}, flags=0x{:02x}, lsa_headers={})",
dd.interface_mtu_value(),
dd.dd_sequence_number_value(),
dd.flags_value(),
dd.lsa_headers_value().len()
),
Ospfv3Body::LinkStateRequest(lsr) => format!(
"Ospfv3(type={type_name}, rid={rid}, area={area}, inst={inst}, requests={})",
lsr.entries_value().len()
),
Ospfv3Body::LinkStateUpdate(lsu) => format!(
"Ospfv3(type={type_name}, rid={rid}, area={area}, inst={inst}, num_lsas={}, lsas={})",
lsu.num_lsas_value(),
lsu.lsas_value().len()
),
Ospfv3Body::LinkStateAck(ack) => format!(
"Ospfv3(type={type_name}, rid={rid}, area={area}, inst={inst}, lsa_headers={})",
ack.lsa_headers_value().len()
),
Ospfv3Body::Unknown { .. } => format!(
"Ospfv3(type={type_name}, rid={rid}, area={area}, inst={inst}, len={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()),
),
("instance_id", self.instance_id_value().to_string()),
("reserved", self.reserved_value().to_string()),
];
match &self.body {
Ospfv3Body::Hello(hello) => {
fields.push(("interface_id", hello.interface_id_value().to_string()));
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!("0x{:06x}", hello.options_value())));
fields.push(("priority", hello.router_priority_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()));
}
}
Ospfv3Body::DatabaseDescription(dd) => {
fields.push(("interface_mtu", dd.interface_mtu_value().to_string()));
fields.push(("options", format!("0x{:06x}", dd.options_value())));
fields.push(("dd_flags", format_v3_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()));
}
}
Ospfv3Body::LinkStateRequest(lsr) => {
fields.push(("request_count", lsr.entries_value().len().to_string()));
for entry in lsr.entries_value() {
fields.push((
"request",
format!(
"ls_type=0x{:04x}, id={}, adv={}",
entry.ls_type_value(),
entry.link_state_id_value(),
entry.advertising_router_value()
),
));
}
}
Ospfv3Body::LinkStateUpdate(lsu) => {
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()),
));
match &lsa.body {
Ospfv3LsaBody::Router(router) => {
fields.push((
"router_lsa",
format!(
"flags=0x{:02x} options=0x{:06x} interfaces={}",
router.flags_value(),
router.options_value(),
router.interfaces_value().len()
),
));
for interface in router.interfaces_value() {
fields.push((
"router_interface",
format!(
"type={} metric={} if_id={} nbr_if_id={} nbr_rid={}",
interface.if_type_value(),
interface.metric_value(),
interface.interface_id_value(),
interface.neighbor_interface_id_value(),
interface.neighbor_router_id_value()
),
));
}
}
Ospfv3LsaBody::Network(network) => {
fields.push((
"network_lsa",
format!(
"options=0x{:06x} attached_routers={}",
network.options_value(),
network.attached_routers_value().len()
),
));
for router in network.attached_routers_value() {
fields.push(("attached_router", router.to_string()));
}
}
Ospfv3LsaBody::Raw(_) => {}
}
}
}
Ospfv3Body::LinkStateAck(ack) => {
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()));
}
}
Ospfv3Body::Unknown { .. } => {}
}
fields
}
fn encoded_len(&self) -> usize {
OSPFV3_HEADER_LEN + self.body.encoded_len()
}
fn compile(&self, ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
let start = out.len();
let total_len = OSPFV3_HEADER_LEN + self.body.encoded_len();
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.push(self.instance_id_value());
out.push(self.reserved_value());
self.body.encode(out);
if pinned_checksum.is_none() {
if let Some(pseudo_header) = Self::checksum_context(ctx) {
let checksum = pseudo_header.checksum(&out[start..]);
out[start + 12..start + 14].copy_from_slice(&checksum.to_be_bytes());
}
}
Ok(())
}
impl_layer_object!(Ospfv3);
}
impl_layer_div!(Ospfv3);
fn format_v3_dd_flags(dd: &Ospfv3DatabaseDescription) -> 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})")
}
}