From 7ed7e31f4d0f43d867eea6c9af68630cefd590ac Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Thu, 8 Jan 2026 22:36:41 -0800 Subject: [PATCH 01/11] Add Nom directives to only parse vectors out to the packet length to avoid swallowing the authentication signature. --- src/ospfv2.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ospfv2.rs b/src/ospfv2.rs index 4b6e8b3..43e4167 100644 --- a/src/ospfv2.rs +++ b/src/ospfv2.rs @@ -1,9 +1,14 @@ use crate::parser::{parse_ospf_external_tos_routes, parse_ospf_tos_routes, parse_ospf_vec_u32}; +use nom::bytes::complete::take; +use nom::combinator::complete; +use nom::combinator::map_parser; +use nom::multi::many0; use nom::number::streaming::be_u24; use nom_derive::*; use rusticata_macros::newtype_enum; use std::net::Ipv4Addr; + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, NomBE)] pub struct OspfPacketType(pub u8); @@ -143,6 +148,11 @@ pub struct OspfDatabaseDescriptionPacket { pub struct OspfLinkStateRequestPacket { #[nom(Verify = "header.packet_type == OspfPacketType::LinkStateRequest")] pub header: Ospfv2PacketHeader, + // Subtract the 24-byte OSPF header from total packet_length + #[nom(Parse = "{ + let len = (header.packet_length as usize).saturating_sub(24); + map_parser(take(len), many0(complete(OspfLinkStateRequest::parse_be))) + }")] pub requests: Vec, } @@ -213,6 +223,11 @@ pub struct OspfLinkStateUpdatePacket { pub struct OspfLinkStateAcknowledgmentPacket { #[nom(Verify = "header.packet_type == OspfPacketType::LinkStateAcknowledgment")] pub header: Ospfv2PacketHeader, + // Subtract the 24-byte OSPF header from total packet_length + #[nom(Parse = "{ + let len = (header.packet_length as usize).saturating_sub(24); + map_parser(take(len), many0(complete(OspfLinkStateAdvertisementHeader::parse_be))) + }")] pub lsa_headers: Vec, } From 93634fe3f575c56d43ae2cebd9b103f77fbdbfdf Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Thu, 8 Jan 2026 22:44:15 -0800 Subject: [PATCH 02/11] Add unit test for problematic link state requrest with authentication. --- tests/ospf_parser.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/ospf_parser.rs b/tests/ospf_parser.rs index 31793b6..bd93118 100644 --- a/tests/ospf_parser.rs +++ b/tests/ospf_parser.rs @@ -294,3 +294,32 @@ pub fn test_lsa_type7() { panic!("wrong lsa type"); } } + +#[test] +pub fn test_link_state_request_with_auth() { + let lsa_request_bytes: Vec = vec![ + 0x2, // version + 0x3, // packet type + 0x0, 0x24, // packet length (36) + 0x2, 0x2, 0x2, 0x2, // router + 0x0, 0x0, 0x0, 0x0, // area + 0x0, 0x0, // checksum + 0x0, 0x2, // au type + 0x0, 0x0, 0x1, 0x10, // authentication + 0x69, 0xa, 0xc0, 0xb2, + // ----- + 0x0, 0x0, 0x0, 0x1, // LS type + 0x1, 0x1, 0x1, 0x1, // link state ID + 0x1, 0x1, 0x1, 0x1, // adv. router + // ----- + // signature + 0x98, 0x35, 0xda, 0x13, 0xd5, 0x3f, 0xe9, 0x51, + 0xd8, 0x40, 0xf4, 0xab, 0x10, 0x17, 0xc0, 0x2c]; + + let (remaining, ospfv2_packet) = ospf_parser::parse_ospfv2_packet(&lsa_request_bytes).unwrap(); + let Ospfv2Packet::LinkStateRequest(lsa_request) = ospfv2_packet else { + panic!("failed to parse Ospfv2"); + }; + assert_eq!(*remaining, lsa_request_bytes[36..52]); + assert_eq!(lsa_request.requests.len(), 1); +} \ No newline at end of file From 99b349c4b65d15679e7a083c3dd001053e1a4443 Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Fri, 9 Jan 2026 09:01:10 -0800 Subject: [PATCH 03/11] Bump to 0.5.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 14dec4a..fd0f0c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ospf-parser" -version = "0.5.0" +version = "0.5.1" description = "Parser for the OSPF version 2 protocol" license = "MIT/Apache-2.0" keywords = ["OSPF","routing","protocol","parser","nom"] From 1ab4288b8aa1ed171c93e2359480b668509bf9e0 Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Fri, 9 Jan 2026 17:08:17 -0800 Subject: [PATCH 04/11] Add Display implementations that use human-readable formats. --- src/ospfv2.rs | 384 +++++++++++++++++++++++++++++++++++++++---- src/parser.rs | 18 +- tests/ospf_parser.rs | 174 +++++++++++++++++++- 3 files changed, 536 insertions(+), 40 deletions(-) diff --git a/src/ospfv2.rs b/src/ospfv2.rs index 43e4167..e77b948 100644 --- a/src/ospfv2.rs +++ b/src/ospfv2.rs @@ -1,3 +1,4 @@ +use std::fmt; use crate::parser::{parse_ospf_external_tos_routes, parse_ospf_tos_routes, parse_ospf_vec_u32}; use nom::bytes::complete::take; use nom::combinator::complete; @@ -57,6 +58,21 @@ impl Ospfv2PacketHeader { } } +impl fmt::Display for Ospfv2PacketHeader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Ospfv2PacketHeader {{ ")?; + write!(f, "version: {}, ", self.version)?; + write!(f, "packet_type: {} ({}), ", self.packet_type, self.packet_type.0)?; + write!(f, "packet_length: {}, ", self.packet_length)?; + write!(f, "router_id: {}, ", Ipv4Addr::from(self.router_id))?; + write!(f, "area_id: {}, ", Ipv4Addr::from(self.area_id))?; + write!(f, "checksum: {:#06X}, ", self.checksum)?; + write!(f, "au_type: {}, ", self.au_type)?; + write!(f, "authentication: {:#018X}", self.authentication)?; + write!(f, " }}") + } +} + /// The Hello packet /// /// Hello packets are OSPF packet type 1. These packets are sent @@ -103,6 +119,27 @@ impl OspfHelloPacket { } } +impl fmt::Display for OspfHelloPacket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let neighbors: Vec = self + .neighbor_list + .iter() + .map(|&n| Ipv4Addr::from(n).to_string()) + .collect(); + write!(f, "OspfHelloPacket {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "network_mask: {}, ", Ipv4Addr::from(self.network_mask))?; + write!(f, "hello_interval: {}, ", self.hello_interval)?; + write!(f, "options: {:#04X}, ", self.options)?; + write!(f, "router_priority: {}, ", self.router_priority)?; + write!(f, "router_dead_interval: {}, ", self.router_dead_interval)?; + write!(f, "designated_router: {}, ", Ipv4Addr::from(self.designated_router))?; + write!(f, "backup_designated_router: {}, ", Ipv4Addr::from(self.backup_designated_router))?; + write!(f, "neighbor_list: [{}]", neighbors.join(", "))?; + write!(f, " }}") + } +} + /// The Database Description packet /// /// Database Description packets are OSPF packet type 2. These packets @@ -125,6 +162,26 @@ pub struct OspfDatabaseDescriptionPacket { pub lsa_headers: Vec, } +impl fmt::Display for OspfDatabaseDescriptionPacket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfDatabaseDescriptionPacket {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "if_mtu: {}, ", self.if_mtu)?; + write!(f, "options: {:#04X}, ", self.options)?; + write!(f, "flags: {:#04X}, ", self.flags)?; + write!(f, "dd_sequence_number: {:#010X}, ", self.dd_sequence_number)?; + write!(f, "lsa_headers: [")?; + for (i, lsa) in self.lsa_headers.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", lsa)?; + } + write!(f, "]")?; + write!(f, " }}") + } +} + /// The Link State Request packet /// /// Link State Request packets are OSPF packet type 3. After exchanging @@ -156,6 +213,22 @@ pub struct OspfLinkStateRequestPacket { pub requests: Vec, } +impl fmt::Display for OspfLinkStateRequestPacket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfLinkStateRequestPacket {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "requests: [")?; + for (i, req) in self.requests.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", req)?; + } + write!(f, "]")?; + write!(f, " }}") + } +} + #[derive(Debug, NomBE)] pub struct OspfLinkStateRequest { // XXX should be a OspfLinkStateType, but it is only an u8 @@ -174,6 +247,16 @@ impl OspfLinkStateRequest { } } +impl fmt::Display for OspfLinkStateRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfLinkStateRequest {{ ")?; + write!(f, "link_state_type: {}, ", self.link_state_type)?; + write!(f, "link_state_id: {}, ", Ipv4Addr::from(self.link_state_id))?; + write!(f, "advertising_router: {}", Ipv4Addr::from(self.advertising_router))?; + write!(f, " }}") + } +} + /// The Link State Update packet /// /// Link State Update packets are OSPF packet type 4. These packets @@ -199,6 +282,23 @@ pub struct OspfLinkStateUpdatePacket { pub lsa: Vec, } +impl fmt::Display for OspfLinkStateUpdatePacket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfLinkStateUpdatePacket {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "num_advertisements: {}, ", self.num_advertisements)?; + write!(f, "lsa: [")?; + for (i, lsa) in self.lsa.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", lsa)?; + } + write!(f, "]")?; + write!(f, " }}") + } +} + /// The Link State Acknowledgment packet /// /// Link State Acknowledgment Packets are OSPF packet type 5. To make @@ -231,21 +331,53 @@ pub struct OspfLinkStateAcknowledgmentPacket { pub lsa_headers: Vec, } +impl fmt::Display for OspfLinkStateAcknowledgmentPacket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfLinkStateAcknowledgmentPacket {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "lsa_headers: [")?; + for (i, lsa) in self.lsa_headers.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", lsa)?; + } + write!(f, "]")?; + write!(f, " }}") + } +} + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, NomBE)] pub struct OspfLinkStateType(pub u8); -newtype_enum! { -impl display OspfLinkStateType { - RouterLinks = 1, - NetworkLinks = 2, - SummaryLinkIpNetwork = 3, - SummaryLinkAsbr = 4, - ASExternalLink = 5, - NSSAASExternal = 7, - OpaqueLinkLocalScope = 9, - OpaqueAreaLocalScope = 10, - OpaqueASWideScope = 11, -} +impl OspfLinkStateType { + pub const ROUTER_LINKS: OspfLinkStateType = OspfLinkStateType(1); + pub const NETWORK_LINKS: OspfLinkStateType = OspfLinkStateType(2); + pub const SUMMARY_LINK_IP_NETWORK: OspfLinkStateType = OspfLinkStateType(3); + pub const SUMMARY_LINK_ASBR: OspfLinkStateType = OspfLinkStateType(4); + pub const AS_EXTERNAL_LINK: OspfLinkStateType = OspfLinkStateType(5); + pub const NSSA_AS_EXTERNAL: OspfLinkStateType = OspfLinkStateType(7); + pub const OPAQUE_LINK_LOCAL_SCOPE: OspfLinkStateType = OspfLinkStateType(9); + pub const OPAQUE_AREA_LOCAL_SCOPE: OspfLinkStateType = OspfLinkStateType(10); + pub const OPAQUE_AS_WIDE_SCOPE: OspfLinkStateType = OspfLinkStateType(11); +} + +impl fmt::Display for OspfLinkStateType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match *self { + OspfLinkStateType::ROUTER_LINKS => "RouterLinks", + OspfLinkStateType::NETWORK_LINKS => "NetworkLinks", + OspfLinkStateType::SUMMARY_LINK_IP_NETWORK => "SummaryLinkIpNetwork", + OspfLinkStateType::SUMMARY_LINK_ASBR => "SummaryLinkAsbr", + OspfLinkStateType::AS_EXTERNAL_LINK => "ASExternalLink", + OspfLinkStateType::NSSA_AS_EXTERNAL => "NSSAASExternal", + OspfLinkStateType::OPAQUE_LINK_LOCAL_SCOPE => "OpaqueLinkLocalScope", + OspfLinkStateType::OPAQUE_AREA_LOCAL_SCOPE => "OpaqueAreaLocalScope", + OspfLinkStateType::OPAQUE_AS_WIDE_SCOPE => "OpaqueASWideScope", + _ => "Unknown", + }; + write!(f, "{}", s) + } } /// The Link State Advertisement header @@ -280,6 +412,21 @@ impl OspfLinkStateAdvertisementHeader { } } +impl fmt::Display for OspfLinkStateAdvertisementHeader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfLinkStateAdvertisementHeader {{ ")?; + write!(f, "ls_age: {}, ", self.ls_age)?; + write!(f, "options: {:#04X}, ", self.options)?; + write!(f, "link_state_type: {} ({}), ", self.link_state_type, self.link_state_type.0)?; + write!(f, "link_state_id: {}, ", Ipv4Addr::from(self.link_state_id))?; + write!(f, "advertising_router: {}, ", Ipv4Addr::from(self.advertising_router))?; + write!(f, "ls_seq_number: {:#010X}, ", self.ls_seq_number)?; + write!(f, "ls_checksum: {:#06X}, ", self.ls_checksum)?; + write!(f, "length: {}", self.length)?; + write!(f, " }}") + } +} + /// Link state advertisements #[derive(Debug)] pub enum OspfLinkStateAdvertisement { @@ -294,6 +441,22 @@ pub enum OspfLinkStateAdvertisement { OpaqueASWideScope(OspfOpaqueLinkAdvertisement), } +impl fmt::Display for OspfLinkStateAdvertisement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OspfLinkStateAdvertisement::RouterLinks(lsa) => write!(f, "{}", lsa), + OspfLinkStateAdvertisement::NetworkLinks(lsa) => write!(f, "{}", lsa), + OspfLinkStateAdvertisement::SummaryLinkIpNetwork(lsa) => write!(f, "{}", lsa), + OspfLinkStateAdvertisement::SummaryLinkAsbr(lsa) => write!(f, "{}", lsa), + OspfLinkStateAdvertisement::ASExternalLink(lsa) => write!(f, "{}", lsa), + OspfLinkStateAdvertisement::NSSAASExternal(lsa) => write!(f, "{}", lsa), + OspfLinkStateAdvertisement::OpaqueLinkLocalScope(lsa) => write!(f, "{}", lsa), + OspfLinkStateAdvertisement::OpaqueAreaLocalScope(lsa) => write!(f, "{}", lsa), + OspfLinkStateAdvertisement::OpaqueASWideScope(lsa) => write!(f, "{}", lsa), + } + } +} + /// Router links advertisements /// /// Router links advertisements are the Type 1 link state @@ -305,7 +468,7 @@ pub enum OspfLinkStateAdvertisement { /// router links advertisements, see Section 12.4.1. #[derive(Debug, NomBE)] pub struct OspfRouterLinksAdvertisement { - #[nom(Verify = "header.link_state_type == OspfLinkStateType::RouterLinks")] + #[nom(Verify = "header.link_state_type == OspfLinkStateType::ROUTER_LINKS")] pub header: OspfLinkStateAdvertisementHeader, pub flags: u16, pub num_links: u16, @@ -313,16 +476,45 @@ pub struct OspfRouterLinksAdvertisement { pub links: Vec, } +impl fmt::Display for OspfRouterLinksAdvertisement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfRouterLinksAdvertisement {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "flags: {}, ", self.flags)?; + write!(f, "num_links: {}, ", self.num_links)?; + write!(f, "links: [")?; + for (i, link) in self.links.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", link)?; + } + write!(f, "]")?; + write!(f, " }}") + } +} + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, NomBE)] pub struct OspfRouterLinkType(pub u8); -newtype_enum! { -impl display OspfRouterLinkType { - PointToPoint = 1, - Transit = 2, - Stub = 3, - Virtual = 4, -} +impl OspfRouterLinkType { + pub const POINT_TO_POINT: OspfRouterLinkType = OspfRouterLinkType(1); + pub const TRANSIT: OspfRouterLinkType = OspfRouterLinkType(2); + pub const STUB: OspfRouterLinkType = OspfRouterLinkType(3); + pub const VIRTUAL: OspfRouterLinkType = OspfRouterLinkType(4); +} + +impl fmt::Display for OspfRouterLinkType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match *self { + OspfRouterLinkType::POINT_TO_POINT => "PointToPoint", + OspfRouterLinkType::TRANSIT => "Transit", + OspfRouterLinkType::STUB => "Stub", + OspfRouterLinkType::VIRTUAL => "Virtual", + _ => "Unknown", + }; + write!(f, "{}", s) + } } /// OSPF router link (i.e., interface) @@ -337,6 +529,26 @@ pub struct OspfRouterLink { pub tos_list: Vec, } +impl fmt::Display for OspfRouterLink { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfRouterLink {{ ")?; + write!(f, "link_id: {}, ", Ipv4Addr::from(self.link_id))?; + write!(f, "link_data: {}, ", Ipv4Addr::from(self.link_data))?; + write!(f, "link_type: {} ({}), ", self.link_type, self.link_type.0)?; + write!(f, "num_tos: {}, ", self.num_tos)?; + write!(f, "tos_0_metric: {}, ", self.tos_0_metric)?; + write!(f, "tos_list: [")?; + for (i, tos) in self.tos_list.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", tos)?; + } + write!(f, "]")?; + write!(f, " }}") + } +} + impl OspfRouterLink { pub fn link_id(&self) -> Ipv4Addr { Ipv4Addr::from(self.link_id) @@ -355,6 +567,16 @@ pub struct OspfRouterTOS { pub metric: u16, } +impl fmt::Display for OspfRouterTOS { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfRouterTOS {{ ")?; + write!(f, "tos: {}, ", self.tos)?; + write!(f, "reserved: {}, ", self.reserved)?; + write!(f, "metric: {}", self.metric)?; + write!(f, " }}") + } +} + /// Network links advertisements /// /// Network links advertisements are the Type 2 link state @@ -374,7 +596,7 @@ pub struct OspfRouterTOS { /// Section 12.4.2. #[derive(Debug, NomBE)] pub struct OspfNetworkLinksAdvertisement { - #[nom(Verify = "header.link_state_type == OspfLinkStateType::NetworkLinks")] + #[nom(Verify = "header.link_state_type == OspfLinkStateType::NETWORK_LINKS")] pub header: OspfLinkStateAdvertisementHeader, pub network_mask: u32, // limit parsing to (length-xxx) bytes @@ -382,6 +604,21 @@ pub struct OspfNetworkLinksAdvertisement { pub attached_routers: Vec, } +impl fmt::Display for OspfNetworkLinksAdvertisement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let routers: Vec = self + .attached_routers + .iter() + .map(|&r| Ipv4Addr::from(r).to_string()) + .collect(); + write!(f, "OspfNetworkLinksAdvertisement {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "network_mask: {}, ", Ipv4Addr::from(self.network_mask))?; + write!(f, "attached_routers: [{}]", routers.join(", "))?; + write!(f, " }}") + } +} + impl OspfNetworkLinksAdvertisement { pub fn network_mask(&self) -> Ipv4Addr { Ipv4Addr::from(self.network_mask) @@ -414,8 +651,8 @@ impl OspfNetworkLinksAdvertisement { #[derive(Debug, NomBE)] pub struct OspfSummaryLinkAdvertisement { #[nom( - Verify = "header.link_state_type == OspfLinkStateType::SummaryLinkIpNetwork || - header.link_state_type == OspfLinkStateType::SummaryLinkAsbr" + Verify = "header.link_state_type == OspfLinkStateType::SUMMARY_LINK_IP_NETWORK || + header.link_state_type == OspfLinkStateType::SUMMARY_LINK_ASBR" )] pub header: OspfLinkStateAdvertisementHeader, pub network_mask: u32, @@ -427,6 +664,25 @@ pub struct OspfSummaryLinkAdvertisement { pub tos_routes: Vec, } +impl fmt::Display for OspfSummaryLinkAdvertisement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfSummaryLinkAdvertisement {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "network_mask: {}, ", Ipv4Addr::from(self.network_mask))?; + write!(f, "tos: {}, ", self.tos)?; + write!(f, "metric: {}, ", self.metric)?; + write!(f, "tos_routes: [")?; + for (i, route) in self.tos_routes.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", route)?; + } + write!(f, "]")?; + write!(f, " }}") + } +} + impl OspfSummaryLinkAdvertisement { pub fn network_mask(&self) -> Ipv4Addr { Ipv4Addr::from(self.network_mask) @@ -440,6 +696,15 @@ pub struct OspfTosRoute { pub metric: u32, } +impl fmt::Display for OspfTosRoute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfTosRoute {{ ")?; + write!(f, "tos: {}, ", self.tos)?; + write!(f, "metric: {}", self.metric)?; + write!(f, " }}") + } +} + /// AS external link advertisements /// /// AS external link advertisements are the Type 5 link state @@ -452,6 +717,7 @@ pub struct OspfTosRoute { /// AS external link advertisements usually describe a particular /// external destination. For these advertisements the Link State ID /// field specifies an IP network number (if necessary, the Link State + /// ID can also have one or more of the network's "host" bits set; see /// Appendix F for details). AS external link advertisements are also /// used to describe a default route. Default routes are used when no @@ -460,7 +726,7 @@ pub struct OspfTosRoute { /// (0.0.0.0) and the Network Mask is set to 0.0.0.0. #[derive(Debug, NomBE)] pub struct OspfASExternalLinkAdvertisement { - #[nom(Verify = "header.link_state_type == OspfLinkStateType::ASExternalLink")] + #[nom(Verify = "header.link_state_type == OspfLinkStateType::AS_EXTERNAL_LINK")] pub header: OspfLinkStateAdvertisementHeader, pub network_mask: u32, pub external_and_reserved: u8, @@ -473,6 +739,27 @@ pub struct OspfASExternalLinkAdvertisement { pub tos_list: Vec, } +impl fmt::Display for OspfASExternalLinkAdvertisement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfASExternalLinkAdvertisement {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "network_mask: {}, ", Ipv4Addr::from(self.network_mask))?; + write!(f, "external_and_reserved: {}, ", self.external_and_reserved)?; + write!(f, "metric: {}, ", self.metric)?; + write!(f, "forwarding_address: {}, ", Ipv4Addr::from(self.forwarding_address))?; + write!(f, "external_route_tag: {}, ", self.external_route_tag)?; + write!(f, "tos_list: [")?; + for (i, tos) in self.tos_list.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", tos)?; + } + write!(f, "]")?; + write!(f, " }}") + } +} + impl OspfASExternalLinkAdvertisement { pub fn forwarding_address(&self) -> Ipv4Addr { Ipv4Addr::from(self.forwarding_address) @@ -491,6 +778,17 @@ pub struct OspfExternalTosRoute { pub external_route_tag: u32, } +impl fmt::Display for OspfExternalTosRoute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfExternalTosRoute {{ ")?; + write!(f, "tos: {}, ", self.tos)?; + write!(f, "metric: {}, ", self.metric)?; + write!(f, "forwarding_address: {}, ", Ipv4Addr::from(self.forwarding_address))?; + write!(f, "external_route_tag: {}", self.external_route_tag)?; + write!(f, " }}") + } +} + impl OspfExternalTosRoute { pub fn forwarding_address(&self) -> Ipv4Addr { Ipv4Addr::from(self.forwarding_address) @@ -500,7 +798,7 @@ impl OspfExternalTosRoute { /// NSSA AS-External LSA (type 7, rfc1587, rfc3101) #[derive(Debug, NomBE)] pub struct OspfNSSAExternalLinkAdvertisement { - #[nom(Verify = "header.link_state_type == OspfLinkStateType::NSSAASExternal")] + #[nom(Verify = "header.link_state_type == OspfLinkStateType::NSSA_AS_EXTERNAL")] pub header: OspfLinkStateAdvertisementHeader, pub network_mask: u32, pub external_and_tos: u8, @@ -513,6 +811,27 @@ pub struct OspfNSSAExternalLinkAdvertisement { pub tos_list: Vec, } +impl fmt::Display for OspfNSSAExternalLinkAdvertisement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfNSSAExternalLinkAdvertisement {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "network_mask: {}, ", Ipv4Addr::from(self.network_mask))?; + write!(f, "external_and_tos: {}, ", self.external_and_tos)?; + write!(f, "metric: {}, ", self.metric)?; + write!(f, "forwarding_address: {}, ", Ipv4Addr::from(self.forwarding_address))?; + write!(f, "external_route_tag: {}, ", self.external_route_tag)?; + write!(f, "tos_list: [")?; + for (i, tos) in self.tos_list.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", tos)?; + } + write!(f, "]")?; + write!(f, " }}") + } +} + impl OspfNSSAExternalLinkAdvertisement { pub fn forwarding_address(&self) -> Ipv4Addr { Ipv4Addr::from(self.forwarding_address) @@ -541,14 +860,23 @@ impl OspfNSSAExternalLinkAdvertisement { #[derive(Debug, NomBE)] pub struct OspfOpaqueLinkAdvertisement { #[nom( - Verify = "header.link_state_type == OspfLinkStateType::OpaqueLinkLocalScope || - header.link_state_type == OspfLinkStateType::OpaqueAreaLocalScope || - header.link_state_type == OspfLinkStateType::OpaqueASWideScope" + Verify = "header.link_state_type == OspfLinkStateType::OPAQUE_LINK_LOCAL_SCOPE || + header.link_state_type == OspfLinkStateType::OPAQUE_AREA_LOCAL_SCOPE || + header.link_state_type == OspfLinkStateType::OPAQUE_AS_WIDE_SCOPE" )] pub header: OspfLinkStateAdvertisementHeader, pub data: Vec, } +impl fmt::Display for OspfOpaqueLinkAdvertisement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "OspfOpaqueLinkAdvertisement {{ ")?; + write!(f, "header: {}, ", self.header)?; + write!(f, "data: {:X?}", self.data)?; + write!(f, " }}") + } +} + impl OspfOpaqueLinkAdvertisement { pub fn opaque_type(&self) -> u8 { (self.header.link_state_id >> 24) as u8 diff --git a/src/parser.rs b/src/parser.rs index 7fa73d7..cd2b503 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -92,39 +92,39 @@ impl<'a> Parse<&'a [u8]> for OspfLinkStateAdvertisement { let (_, word) = peek(be_u32)(input)?; let ls_type = (word & 0xff) as u8; match OspfLinkStateType(ls_type) { - OspfLinkStateType::RouterLinks => map( + OspfLinkStateType::ROUTER_LINKS => map( OspfRouterLinksAdvertisement::parse, OspfLinkStateAdvertisement::RouterLinks, )(input), - OspfLinkStateType::NetworkLinks => map( + OspfLinkStateType::NETWORK_LINKS => map( OspfNetworkLinksAdvertisement::parse, OspfLinkStateAdvertisement::NetworkLinks, )(input), - OspfLinkStateType::SummaryLinkIpNetwork => map( + OspfLinkStateType::SUMMARY_LINK_IP_NETWORK => map( OspfSummaryLinkAdvertisement::parse, OspfLinkStateAdvertisement::SummaryLinkIpNetwork, )(input), - OspfLinkStateType::SummaryLinkAsbr => map( + OspfLinkStateType::SUMMARY_LINK_ASBR => map( OspfSummaryLinkAdvertisement::parse, OspfLinkStateAdvertisement::SummaryLinkAsbr, )(input), - OspfLinkStateType::ASExternalLink => map( + OspfLinkStateType::AS_EXTERNAL_LINK => map( OspfASExternalLinkAdvertisement::parse, OspfLinkStateAdvertisement::ASExternalLink, )(input), - OspfLinkStateType::NSSAASExternal => map( + OspfLinkStateType::NSSA_AS_EXTERNAL => map( OspfNSSAExternalLinkAdvertisement::parse, OspfLinkStateAdvertisement::NSSAASExternal, )(input), - OspfLinkStateType::OpaqueLinkLocalScope => map( + OspfLinkStateType::OPAQUE_LINK_LOCAL_SCOPE => map( OspfOpaqueLinkAdvertisement::parse, OspfLinkStateAdvertisement::OpaqueLinkLocalScope, )(input), - OspfLinkStateType::OpaqueAreaLocalScope => map( + OspfLinkStateType::OPAQUE_AREA_LOCAL_SCOPE => map( OspfOpaqueLinkAdvertisement::parse, OspfLinkStateAdvertisement::OpaqueAreaLocalScope, )(input), - OspfLinkStateType::OpaqueASWideScope => map( + OspfLinkStateType::OPAQUE_AS_WIDE_SCOPE => map( OspfOpaqueLinkAdvertisement::parse, OspfLinkStateAdvertisement::OpaqueASWideScope, )(input), diff --git a/tests/ospf_parser.rs b/tests/ospf_parser.rs index bd93118..2954d5e 100644 --- a/tests/ospf_parser.rs +++ b/tests/ospf_parser.rs @@ -160,7 +160,7 @@ pub fn test_ls_update() { assert_eq!(pkt.lsa.len(), 1); let lsa0 = &pkt.lsa[0]; if let OspfLinkStateAdvertisement::RouterLinks(lsa) = lsa0 { - assert_eq!(lsa.header.link_state_type, OspfLinkStateType::RouterLinks); + assert_eq!(lsa.header.link_state_type, OspfLinkStateType::ROUTER_LINKS); assert_eq!( lsa.header.advertising_router(), Ipv4Addr::new(192, 168, 170, 8) @@ -169,7 +169,7 @@ pub fn test_ls_update() { let link0 = &lsa.links[0]; assert_eq!(link0.link_id(), Ipv4Addr::new(192, 168, 170, 0)); assert_eq!(link0.link_data(), Ipv4Addr::new(255, 255, 255, 0)); - assert_eq!(link0.link_type, OspfRouterLinkType::Stub); + assert_eq!(link0.link_type, OspfRouterLinkType::STUB); assert_eq!(link0.tos_list.len(), 0); } else { panic!("wrong LSA type"); @@ -322,4 +322,172 @@ pub fn test_link_state_request_with_auth() { }; assert_eq!(*remaining, lsa_request_bytes[36..52]); assert_eq!(lsa_request.requests.len(), 1); -} \ No newline at end of file +} + +#[test] +pub fn test_ospfv2_packet_header_display() { + let header = Ospfv2PacketHeader { + version: 2, + packet_type: OspfPacketType::Hello, + packet_length: 42, + router_id: Ipv4Addr::new(10, 1, 1, 1).into(), + area_id: 0, + checksum: 0xABCD, + au_type: 2, + authentication: 0x0102030405060708, + }; + let s = format!("{}", header); + assert!(s.contains("version: 2")); + assert!(s.contains("packet_type: Hello (1)")); + assert!(s.contains("packet_length: 42")); + assert!(s.contains("router_id: 10.1.1.1")); + assert!(s.contains("area_id: 0.0.0.0")); + assert!(s.contains("checksum: 0xABCD")); + assert!(s.contains("au_type: 2")); + assert!(s.contains("authentication: 0x0102030405060708")); +} + +#[test] +pub fn test_ospf_hello_packet_display() { + let header = Ospfv2PacketHeader { + version: 2, + packet_type: OspfPacketType::Hello, + packet_length: 56, + router_id: Ipv4Addr::new(10, 1, 1, 1).into(), + area_id: 0, + checksum: 0xABCD, + au_type: 2, + authentication: 0x0102030405060708, + }; + let hello_packet = OspfHelloPacket { + header, + network_mask: Ipv4Addr::new(255, 255, 255, 0).into(), + hello_interval: 10, + options: 0x42, + router_priority: 1, + router_dead_interval: 40, + designated_router: Ipv4Addr::new(10, 1, 1, 2).into(), + backup_designated_router: Ipv4Addr::new(10, 1, 1, 3).into(), + neighbor_list: vec![ + Ipv4Addr::new(10, 1, 1, 4).into(), + Ipv4Addr::new(10, 1, 1, 5).into(), + ], + }; + let s = format!("{}", hello_packet); + assert!(s.contains("network_mask: 255.255.255.0")); + assert!(s.contains("options: 0x42")); + assert!(s.contains("designated_router: 10.1.1.2")); + assert!(s.contains("backup_designated_router: 10.1.1.3")); + assert!(s.contains(r#"neighbor_list: [10.1.1.4, 10.1.1.5]"#)); +} + +#[test] +pub fn test_ospf_database_description_packet_display() { + let header = Ospfv2PacketHeader { + version: 2, + packet_type: OspfPacketType::DatabaseDescription, + packet_length: 68, + router_id: Ipv4Addr::new(10, 1, 1, 1).into(), + area_id: 0, + checksum: 0xABCD, + au_type: 2, + authentication: 0x0102030405060708, + }; + let lsa_header = OspfLinkStateAdvertisementHeader { + ls_age: 3600, + options: 0x42, + link_state_type: OspfLinkStateType::ROUTER_LINKS, + link_state_id: Ipv4Addr::new(10, 1, 1, 2).into(), + advertising_router: Ipv4Addr::new(10, 1, 1, 1).into(), + ls_seq_number: 0x80000001, + ls_checksum: 0x1234, + length: 20, + }; + let db_packet = OspfDatabaseDescriptionPacket { + header, + if_mtu: 1500, + options: 0x42, + flags: 0x07, + dd_sequence_number: 12345, + lsa_headers: vec![lsa_header], + }; + let s = format!("{}", db_packet); + assert!(s.contains("if_mtu: 1500")); + assert!(s.contains("options: 0x42")); + assert!(s.contains("flags: 0x07")); + assert!(s.contains("dd_sequence_number: 0x00003039")); + assert!(s.contains("lsa_headers: [")); + assert!(s.contains("ls_age: 3600")); + assert!(s.contains("link_state_type: RouterLinks (1)")); + assert!(s.contains("link_state_id: 10.1.1.2")); + assert!(s.contains("advertising_router: 10.1.1.1")); + assert!(s.contains("ls_seq_number: 0x80000001")); + assert!(s.contains("ls_checksum: 0x1234")); +} + +#[test] +pub fn test_ospf_link_state_request_packet_display() { + let header = Ospfv2PacketHeader { + version: 2, + packet_type: OspfPacketType::LinkStateRequest, + packet_length: 48, + router_id: Ipv4Addr::new(10, 1, 1, 1).into(), + area_id: 0, + checksum: 0xABCD, + au_type: 2, + authentication: 0x0102030405060708, + }; + let request = OspfLinkStateRequest { + link_state_type: 1, + link_state_id: Ipv4Addr::new(10, 1, 1, 2).into(), + advertising_router: Ipv4Addr::new(10, 1, 1, 1).into(), + }; + let req_packet = OspfLinkStateRequestPacket { + header, + requests: vec![request], + }; + let s = format!("{}", req_packet); + assert!(s.contains("requests: [")); + assert!(s.contains("link_state_type: 1")); + assert!(s.contains("link_state_id: 10.1.1.2")); + assert!(s.contains("advertising_router: 10.1.1.1")); +} + +#[test] +pub fn test_ospf_link_state_update_packet_display() { + let header = Ospfv2PacketHeader { + version: 2, + packet_type: OspfPacketType::LinkStateUpdate, + packet_length: 68, + router_id: Ipv4Addr::new(10, 1, 1, 1).into(), + area_id: 0, + checksum: 0xABCD, + au_type: 2, + authentication: 0x0102030405060708, + }; + let lsa_header = OspfLinkStateAdvertisementHeader { + ls_age: 3600, + options: 0x42, + link_state_type: OspfLinkStateType::ROUTER_LINKS, + link_state_id: Ipv4Addr::new(10, 1, 1, 2).into(), + advertising_router: Ipv4Addr::new(10, 1, 1, 1).into(), + ls_seq_number: 0x80000001, + ls_checksum: 0x1234, + length: 20, + }; + let router_lsa = OspfRouterLinksAdvertisement { + header: lsa_header, + flags: 0, + num_links: 0, + links: vec![], + }; + let lsu_packet = OspfLinkStateUpdatePacket { + header, + num_advertisements: 1, + lsa: vec![OspfLinkStateAdvertisement::RouterLinks(router_lsa)], + }; + let s = format!("{}", lsu_packet); + assert!(s.contains("num_advertisements: 1")); + assert!(s.contains("lsa: [")); + assert!(s.contains("ls_age: 3600")); +} From ef1e2538a2fc42ad169d981d369aed888fb90175 Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Fri, 9 Jan 2026 17:32:50 -0800 Subject: [PATCH 05/11] undo enum changes to not break API. --- src/ospfv2.rs | 81 +++++++++++++++----------------------------- src/parser.rs | 18 +++++----- tests/ospf_parser.rs | 8 ++--- 3 files changed, 40 insertions(+), 67 deletions(-) diff --git a/src/ospfv2.rs b/src/ospfv2.rs index e77b948..aa63ff8 100644 --- a/src/ospfv2.rs +++ b/src/ospfv2.rs @@ -350,34 +350,18 @@ impl fmt::Display for OspfLinkStateAcknowledgmentPacket { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, NomBE)] pub struct OspfLinkStateType(pub u8); -impl OspfLinkStateType { - pub const ROUTER_LINKS: OspfLinkStateType = OspfLinkStateType(1); - pub const NETWORK_LINKS: OspfLinkStateType = OspfLinkStateType(2); - pub const SUMMARY_LINK_IP_NETWORK: OspfLinkStateType = OspfLinkStateType(3); - pub const SUMMARY_LINK_ASBR: OspfLinkStateType = OspfLinkStateType(4); - pub const AS_EXTERNAL_LINK: OspfLinkStateType = OspfLinkStateType(5); - pub const NSSA_AS_EXTERNAL: OspfLinkStateType = OspfLinkStateType(7); - pub const OPAQUE_LINK_LOCAL_SCOPE: OspfLinkStateType = OspfLinkStateType(9); - pub const OPAQUE_AREA_LOCAL_SCOPE: OspfLinkStateType = OspfLinkStateType(10); - pub const OPAQUE_AS_WIDE_SCOPE: OspfLinkStateType = OspfLinkStateType(11); -} - -impl fmt::Display for OspfLinkStateType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match *self { - OspfLinkStateType::ROUTER_LINKS => "RouterLinks", - OspfLinkStateType::NETWORK_LINKS => "NetworkLinks", - OspfLinkStateType::SUMMARY_LINK_IP_NETWORK => "SummaryLinkIpNetwork", - OspfLinkStateType::SUMMARY_LINK_ASBR => "SummaryLinkAsbr", - OspfLinkStateType::AS_EXTERNAL_LINK => "ASExternalLink", - OspfLinkStateType::NSSA_AS_EXTERNAL => "NSSAASExternal", - OspfLinkStateType::OPAQUE_LINK_LOCAL_SCOPE => "OpaqueLinkLocalScope", - OspfLinkStateType::OPAQUE_AREA_LOCAL_SCOPE => "OpaqueAreaLocalScope", - OspfLinkStateType::OPAQUE_AS_WIDE_SCOPE => "OpaqueASWideScope", - _ => "Unknown", - }; - write!(f, "{}", s) - } +newtype_enum! { +impl display OspfLinkStateType { + RouterLinks = 1, + NetworkLinks = 2, + SummaryLinkIpNetwork = 3, + SummaryLinkAsbr = 4, + ASExternalLink = 5, + NSSAASExternal = 7, + OpaqueLinkLocalScope = 9, + OpaqueAreaLocalScope = 10, + OpaqueASWideScope = 11, +} } /// The Link State Advertisement header @@ -468,7 +452,7 @@ impl fmt::Display for OspfLinkStateAdvertisement { /// router links advertisements, see Section 12.4.1. #[derive(Debug, NomBE)] pub struct OspfRouterLinksAdvertisement { - #[nom(Verify = "header.link_state_type == OspfLinkStateType::ROUTER_LINKS")] + #[nom(Verify = "header.link_state_type == OspfLinkStateType::RouterLinks")] pub header: OspfLinkStateAdvertisementHeader, pub flags: u16, pub num_links: u16, @@ -497,24 +481,13 @@ impl fmt::Display for OspfRouterLinksAdvertisement { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, NomBE)] pub struct OspfRouterLinkType(pub u8); -impl OspfRouterLinkType { - pub const POINT_TO_POINT: OspfRouterLinkType = OspfRouterLinkType(1); - pub const TRANSIT: OspfRouterLinkType = OspfRouterLinkType(2); - pub const STUB: OspfRouterLinkType = OspfRouterLinkType(3); - pub const VIRTUAL: OspfRouterLinkType = OspfRouterLinkType(4); +newtype_enum! { +impl display OspfRouterLinkType { + PointToPoint = 1, + Transit = 2, + Stub = 3, + Virtual = 4, } - -impl fmt::Display for OspfRouterLinkType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match *self { - OspfRouterLinkType::POINT_TO_POINT => "PointToPoint", - OspfRouterLinkType::TRANSIT => "Transit", - OspfRouterLinkType::STUB => "Stub", - OspfRouterLinkType::VIRTUAL => "Virtual", - _ => "Unknown", - }; - write!(f, "{}", s) - } } /// OSPF router link (i.e., interface) @@ -596,7 +569,7 @@ impl fmt::Display for OspfRouterTOS { /// Section 12.4.2. #[derive(Debug, NomBE)] pub struct OspfNetworkLinksAdvertisement { - #[nom(Verify = "header.link_state_type == OspfLinkStateType::NETWORK_LINKS")] + #[nom(Verify = "header.link_state_type == OspfLinkStateType::NetworkLinks")] pub header: OspfLinkStateAdvertisementHeader, pub network_mask: u32, // limit parsing to (length-xxx) bytes @@ -651,8 +624,8 @@ impl OspfNetworkLinksAdvertisement { #[derive(Debug, NomBE)] pub struct OspfSummaryLinkAdvertisement { #[nom( - Verify = "header.link_state_type == OspfLinkStateType::SUMMARY_LINK_IP_NETWORK || - header.link_state_type == OspfLinkStateType::SUMMARY_LINK_ASBR" + Verify = "header.link_state_type == OspfLinkStateType::SummaryLinkIpNetwork || + header.link_state_type == OspfLinkStateType::SummaryLinkAsbr" )] pub header: OspfLinkStateAdvertisementHeader, pub network_mask: u32, @@ -726,7 +699,7 @@ impl fmt::Display for OspfTosRoute { /// (0.0.0.0) and the Network Mask is set to 0.0.0.0. #[derive(Debug, NomBE)] pub struct OspfASExternalLinkAdvertisement { - #[nom(Verify = "header.link_state_type == OspfLinkStateType::AS_EXTERNAL_LINK")] + #[nom(Verify = "header.link_state_type == OspfLinkStateType::ASExternalLink")] pub header: OspfLinkStateAdvertisementHeader, pub network_mask: u32, pub external_and_reserved: u8, @@ -798,7 +771,7 @@ impl OspfExternalTosRoute { /// NSSA AS-External LSA (type 7, rfc1587, rfc3101) #[derive(Debug, NomBE)] pub struct OspfNSSAExternalLinkAdvertisement { - #[nom(Verify = "header.link_state_type == OspfLinkStateType::NSSA_AS_EXTERNAL")] + #[nom(Verify = "header.link_state_type == OspfLinkStateType::NSSAASExternal")] pub header: OspfLinkStateAdvertisementHeader, pub network_mask: u32, pub external_and_tos: u8, @@ -860,9 +833,9 @@ impl OspfNSSAExternalLinkAdvertisement { #[derive(Debug, NomBE)] pub struct OspfOpaqueLinkAdvertisement { #[nom( - Verify = "header.link_state_type == OspfLinkStateType::OPAQUE_LINK_LOCAL_SCOPE || - header.link_state_type == OspfLinkStateType::OPAQUE_AREA_LOCAL_SCOPE || - header.link_state_type == OspfLinkStateType::OPAQUE_AS_WIDE_SCOPE" + Verify = "header.link_state_type == OspfLinkStateType::OpaqueLinkLocalScope || + header.link_state_type == OspfLinkStateType::OpaqueAreaLocalScope || + header.link_state_type == OspfLinkStateType::OpaqueASWideScope" )] pub header: OspfLinkStateAdvertisementHeader, pub data: Vec, diff --git a/src/parser.rs b/src/parser.rs index cd2b503..7fa73d7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -92,39 +92,39 @@ impl<'a> Parse<&'a [u8]> for OspfLinkStateAdvertisement { let (_, word) = peek(be_u32)(input)?; let ls_type = (word & 0xff) as u8; match OspfLinkStateType(ls_type) { - OspfLinkStateType::ROUTER_LINKS => map( + OspfLinkStateType::RouterLinks => map( OspfRouterLinksAdvertisement::parse, OspfLinkStateAdvertisement::RouterLinks, )(input), - OspfLinkStateType::NETWORK_LINKS => map( + OspfLinkStateType::NetworkLinks => map( OspfNetworkLinksAdvertisement::parse, OspfLinkStateAdvertisement::NetworkLinks, )(input), - OspfLinkStateType::SUMMARY_LINK_IP_NETWORK => map( + OspfLinkStateType::SummaryLinkIpNetwork => map( OspfSummaryLinkAdvertisement::parse, OspfLinkStateAdvertisement::SummaryLinkIpNetwork, )(input), - OspfLinkStateType::SUMMARY_LINK_ASBR => map( + OspfLinkStateType::SummaryLinkAsbr => map( OspfSummaryLinkAdvertisement::parse, OspfLinkStateAdvertisement::SummaryLinkAsbr, )(input), - OspfLinkStateType::AS_EXTERNAL_LINK => map( + OspfLinkStateType::ASExternalLink => map( OspfASExternalLinkAdvertisement::parse, OspfLinkStateAdvertisement::ASExternalLink, )(input), - OspfLinkStateType::NSSA_AS_EXTERNAL => map( + OspfLinkStateType::NSSAASExternal => map( OspfNSSAExternalLinkAdvertisement::parse, OspfLinkStateAdvertisement::NSSAASExternal, )(input), - OspfLinkStateType::OPAQUE_LINK_LOCAL_SCOPE => map( + OspfLinkStateType::OpaqueLinkLocalScope => map( OspfOpaqueLinkAdvertisement::parse, OspfLinkStateAdvertisement::OpaqueLinkLocalScope, )(input), - OspfLinkStateType::OPAQUE_AREA_LOCAL_SCOPE => map( + OspfLinkStateType::OpaqueAreaLocalScope => map( OspfOpaqueLinkAdvertisement::parse, OspfLinkStateAdvertisement::OpaqueAreaLocalScope, )(input), - OspfLinkStateType::OPAQUE_AS_WIDE_SCOPE => map( + OspfLinkStateType::OpaqueASWideScope => map( OspfOpaqueLinkAdvertisement::parse, OspfLinkStateAdvertisement::OpaqueASWideScope, )(input), diff --git a/tests/ospf_parser.rs b/tests/ospf_parser.rs index 2954d5e..eb90dcc 100644 --- a/tests/ospf_parser.rs +++ b/tests/ospf_parser.rs @@ -160,7 +160,7 @@ pub fn test_ls_update() { assert_eq!(pkt.lsa.len(), 1); let lsa0 = &pkt.lsa[0]; if let OspfLinkStateAdvertisement::RouterLinks(lsa) = lsa0 { - assert_eq!(lsa.header.link_state_type, OspfLinkStateType::ROUTER_LINKS); + assert_eq!(lsa.header.link_state_type, OspfLinkStateType::RouterLinks); assert_eq!( lsa.header.advertising_router(), Ipv4Addr::new(192, 168, 170, 8) @@ -169,7 +169,7 @@ pub fn test_ls_update() { let link0 = &lsa.links[0]; assert_eq!(link0.link_id(), Ipv4Addr::new(192, 168, 170, 0)); assert_eq!(link0.link_data(), Ipv4Addr::new(255, 255, 255, 0)); - assert_eq!(link0.link_type, OspfRouterLinkType::STUB); + assert_eq!(link0.link_type, OspfRouterLinkType::Stub); assert_eq!(link0.tos_list.len(), 0); } else { panic!("wrong LSA type"); @@ -396,7 +396,7 @@ pub fn test_ospf_database_description_packet_display() { let lsa_header = OspfLinkStateAdvertisementHeader { ls_age: 3600, options: 0x42, - link_state_type: OspfLinkStateType::ROUTER_LINKS, + link_state_type: OspfLinkStateType::RouterLinks, link_state_id: Ipv4Addr::new(10, 1, 1, 2).into(), advertising_router: Ipv4Addr::new(10, 1, 1, 1).into(), ls_seq_number: 0x80000001, @@ -468,7 +468,7 @@ pub fn test_ospf_link_state_update_packet_display() { let lsa_header = OspfLinkStateAdvertisementHeader { ls_age: 3600, options: 0x42, - link_state_type: OspfLinkStateType::ROUTER_LINKS, + link_state_type: OspfLinkStateType::RouterLinks, link_state_id: Ipv4Addr::new(10, 1, 1, 2).into(), advertising_router: Ipv4Addr::new(10, 1, 1, 1).into(), ls_seq_number: 0x80000001, From e74249a165ab356f8d05efb63ef2220ce46a70d7 Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Fri, 9 Jan 2026 17:40:59 -0800 Subject: [PATCH 06/11] fix clippy nits. --- src/ospfv2.rs | 2 +- src/parser.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ospfv2.rs b/src/ospfv2.rs index aa63ff8..e99b47d 100644 --- a/src/ospfv2.rs +++ b/src/ospfv2.rs @@ -690,7 +690,7 @@ impl fmt::Display for OspfTosRoute { /// AS external link advertisements usually describe a particular /// external destination. For these advertisements the Link State ID /// field specifies an IP network number (if necessary, the Link State - +/// /// ID can also have one or more of the network's "host" bits set; see /// Appendix F for details). AS external link advertisements are also /// used to describe a default route. Default routes are used when no diff --git a/src/parser.rs b/src/parser.rs index 7fa73d7..732f43c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -87,7 +87,7 @@ pub fn parse_ospfv2_link_state_request_packet( OspfLinkStateRequestPacket::parse(input) } -impl<'a> Parse<&'a [u8]> for OspfLinkStateAdvertisement { +impl Parse<& [u8]> for OspfLinkStateAdvertisement { fn parse(input: &[u8]) -> IResult<&[u8], OspfLinkStateAdvertisement> { let (_, word) = peek(be_u32)(input)?; let ls_type = (word & 0xff) as u8; @@ -133,7 +133,7 @@ impl<'a> Parse<&'a [u8]> for OspfLinkStateAdvertisement { } } -impl<'a> Parse<&'a [u8]> for Ospfv3LinkStateAdvertisement { +impl Parse<&[u8]> for Ospfv3LinkStateAdvertisement { fn parse(input: &[u8]) -> IResult<&[u8], Ospfv3LinkStateAdvertisement> { let (_, word) = peek(be_u32)(input)?; let ls_type = (word & 0xffff) as u16; From e303718baa950e06efddd035ed3639d2d956df4f Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Thu, 26 Feb 2026 15:29:28 -0800 Subject: [PATCH 07/11] Add Eq, PartialEq, Hash to OspfLinkStateAdvertisementHeader --- src/ospfv2.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ospfv2.rs b/src/ospfv2.rs index e99b47d..9e16a64 100644 --- a/src/ospfv2.rs +++ b/src/ospfv2.rs @@ -347,7 +347,7 @@ impl fmt::Display for OspfLinkStateAcknowledgmentPacket { } } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, NomBE)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, NomBE)] pub struct OspfLinkStateType(pub u8); newtype_enum! { @@ -374,7 +374,7 @@ impl display OspfLinkStateType { /// which instance is more recent. This is accomplished by examining /// the LS age, LS sequence number and LS checksum fields that are also /// contained in the link state advertisement header. -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Eq, PartialEq, Hash)] pub struct OspfLinkStateAdvertisementHeader { pub ls_age: u16, pub options: u8, From c64203b6877b21b23e700b6189493d6fed8ec225 Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Thu, 26 Feb 2026 15:30:15 -0800 Subject: [PATCH 08/11] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fd0f0c1..f7e64c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ospf-parser" -version = "0.5.1" +version = "0.5.2" description = "Parser for the OSPF version 2 protocol" license = "MIT/Apache-2.0" keywords = ["OSPF","routing","protocol","parser","nom"] From 7b1f481424a0087b7a1bd6691475b150cfde8645 Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Thu, 26 Feb 2026 15:46:29 -0800 Subject: [PATCH 09/11] Add copy and clone to LSA header --- src/ospfv2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ospfv2.rs b/src/ospfv2.rs index 9e16a64..cb2e4a1 100644 --- a/src/ospfv2.rs +++ b/src/ospfv2.rs @@ -374,7 +374,7 @@ impl display OspfLinkStateType { /// which instance is more recent. This is accomplished by examining /// the LS age, LS sequence number and LS checksum fields that are also /// contained in the link state advertisement header. -#[derive(Debug, NomBE, Eq, PartialEq, Hash)] +#[derive(Debug, NomBE, Eq, PartialEq, Hash, Copy, Clone)] pub struct OspfLinkStateAdvertisementHeader { pub ls_age: u16, pub options: u8, From bf688dba79eca38220a4d8e16a51317435f3ff72 Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Mon, 30 Mar 2026 15:10:19 -0700 Subject: [PATCH 10/11] Add hash, eq, partial eq to LSAs. Bump to 0.5.3. --- Cargo.toml | 2 +- src/ospfv2.rs | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f7e64c1..8ef6e16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ospf-parser" -version = "0.5.2" +version = "0.5.3" description = "Parser for the OSPF version 2 protocol" license = "MIT/Apache-2.0" keywords = ["OSPF","routing","protocol","parser","nom"] diff --git a/src/ospfv2.rs b/src/ospfv2.rs index cb2e4a1..4ef3e2a 100644 --- a/src/ospfv2.rs +++ b/src/ospfv2.rs @@ -412,7 +412,7 @@ impl fmt::Display for OspfLinkStateAdvertisementHeader { } /// Link state advertisements -#[derive(Debug)] +#[derive(Debug, Hash, Eq, PartialEq)] pub enum OspfLinkStateAdvertisement { RouterLinks(OspfRouterLinksAdvertisement), NetworkLinks(OspfNetworkLinksAdvertisement), @@ -450,7 +450,7 @@ impl fmt::Display for OspfLinkStateAdvertisement { /// router's links to the area must be described in a single router /// links advertisement. For details concerning the construction of /// router links advertisements, see Section 12.4.1. -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfRouterLinksAdvertisement { #[nom(Verify = "header.link_state_type == OspfLinkStateType::RouterLinks")] pub header: OspfLinkStateAdvertisementHeader, @@ -478,7 +478,7 @@ impl fmt::Display for OspfRouterLinksAdvertisement { } } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, NomBE)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, NomBE, Hash)] pub struct OspfRouterLinkType(pub u8); newtype_enum! { @@ -491,7 +491,7 @@ impl display OspfRouterLinkType { } /// OSPF router link (i.e., interface) -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfRouterLink { pub link_id: u32, pub link_data: u32, @@ -533,7 +533,7 @@ impl OspfRouterLink { } /// OSPF Router Type Of Service (TOS) -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfRouterTOS { pub tos: u8, pub reserved: u8, @@ -567,7 +567,7 @@ impl fmt::Display for OspfRouterTOS { /// not be specified in the network links advertisement. For details /// concerning the construction of network links advertisements, see /// Section 12.4.2. -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfNetworkLinksAdvertisement { #[nom(Verify = "header.link_state_type == OspfLinkStateType::NetworkLinks")] pub header: OspfLinkStateAdvertisementHeader, @@ -621,7 +621,7 @@ impl OspfNetworkLinksAdvertisement { /// advertise the location of each ASBR, consult Section 16.4.) Other /// than the difference in the Link State ID field, the format of Type 3 /// and 4 link state advertisements is identical. -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfSummaryLinkAdvertisement { #[nom( Verify = "header.link_state_type == OspfLinkStateType::SummaryLinkIpNetwork || @@ -662,7 +662,7 @@ impl OspfSummaryLinkAdvertisement { } } -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfTosRoute { pub tos: u8, #[nom(Parse = "be_u24")] @@ -697,7 +697,7 @@ impl fmt::Display for OspfTosRoute { /// specific route exists to the destination. When describing a default /// route, the Link State ID is always set to DefaultDestination /// (0.0.0.0) and the Network Mask is set to 0.0.0.0. -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfASExternalLinkAdvertisement { #[nom(Verify = "header.link_state_type == OspfLinkStateType::ASExternalLink")] pub header: OspfLinkStateAdvertisementHeader, @@ -742,7 +742,7 @@ impl OspfASExternalLinkAdvertisement { } } -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfExternalTosRoute { pub tos: u8, #[nom(Parse = "be_u24")] @@ -769,7 +769,7 @@ impl OspfExternalTosRoute { } /// NSSA AS-External LSA (type 7, rfc1587, rfc3101) -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfNSSAExternalLinkAdvertisement { #[nom(Verify = "header.link_state_type == OspfLinkStateType::NSSAASExternal")] pub header: OspfLinkStateAdvertisementHeader, @@ -830,7 +830,7 @@ impl OspfNSSAExternalLinkAdvertisement { /// be link-local (type-9), area-local (type-10), or the entire OSPF /// routing domain (type-11). Section 3 of this document describes the /// flooding procedures for the Opaque LSA. -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq)] pub struct OspfOpaqueLinkAdvertisement { #[nom( Verify = "header.link_state_type == OspfLinkStateType::OpaqueLinkLocalScope || From 04ea081dd418a885c1bcce042fccd9333e8a5ade Mon Sep 17 00:00:00 2001 From: Marc Mosko Date: Mon, 30 Mar 2026 18:28:33 -0700 Subject: [PATCH 11/11] Implement clone for Link State update. Bump version. --- Cargo.toml | 2 +- src/ospfv2.rs | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ef6e16..599492f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ospf-parser" -version = "0.5.3" +version = "0.5.4" description = "Parser for the OSPF version 2 protocol" license = "MIT/Apache-2.0" keywords = ["OSPF","routing","protocol","parser","nom"] diff --git a/src/ospfv2.rs b/src/ospfv2.rs index 4ef3e2a..53c50a7 100644 --- a/src/ospfv2.rs +++ b/src/ospfv2.rs @@ -39,7 +39,7 @@ pub enum Ospfv2Packet { /// contains all the necessary information to determine whether the /// packet should be accepted for further processing. This /// determination is described in Section 8.2 of the specification. -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Clone)] pub struct Ospfv2PacketHeader { #[nom(Verify = "*version == 2")] pub version: u8, @@ -273,7 +273,7 @@ impl fmt::Display for OspfLinkStateRequest { /// always carried by unicast Link State Update packets. For more /// information on the reliable flooding of link state advertisements, /// consult Section 13. -#[derive(Debug, NomBE)] +#[derive(Debug, NomBE, Clone)] pub struct OspfLinkStateUpdatePacket { #[nom(Verify = "header.packet_type == OspfPacketType::LinkStateUpdate")] pub header: Ospfv2PacketHeader, @@ -412,7 +412,7 @@ impl fmt::Display for OspfLinkStateAdvertisementHeader { } /// Link state advertisements -#[derive(Debug, Hash, Eq, PartialEq)] +#[derive(Debug, Hash, Eq, PartialEq, Clone)] pub enum OspfLinkStateAdvertisement { RouterLinks(OspfRouterLinksAdvertisement), NetworkLinks(OspfNetworkLinksAdvertisement), @@ -450,7 +450,7 @@ impl fmt::Display for OspfLinkStateAdvertisement { /// router's links to the area must be described in a single router /// links advertisement. For details concerning the construction of /// router links advertisements, see Section 12.4.1. -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfRouterLinksAdvertisement { #[nom(Verify = "header.link_state_type == OspfLinkStateType::RouterLinks")] pub header: OspfLinkStateAdvertisementHeader, @@ -491,7 +491,7 @@ impl display OspfRouterLinkType { } /// OSPF router link (i.e., interface) -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfRouterLink { pub link_id: u32, pub link_data: u32, @@ -533,7 +533,7 @@ impl OspfRouterLink { } /// OSPF Router Type Of Service (TOS) -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfRouterTOS { pub tos: u8, pub reserved: u8, @@ -567,7 +567,7 @@ impl fmt::Display for OspfRouterTOS { /// not be specified in the network links advertisement. For details /// concerning the construction of network links advertisements, see /// Section 12.4.2. -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfNetworkLinksAdvertisement { #[nom(Verify = "header.link_state_type == OspfLinkStateType::NetworkLinks")] pub header: OspfLinkStateAdvertisementHeader, @@ -621,7 +621,7 @@ impl OspfNetworkLinksAdvertisement { /// advertise the location of each ASBR, consult Section 16.4.) Other /// than the difference in the Link State ID field, the format of Type 3 /// and 4 link state advertisements is identical. -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfSummaryLinkAdvertisement { #[nom( Verify = "header.link_state_type == OspfLinkStateType::SummaryLinkIpNetwork || @@ -662,7 +662,7 @@ impl OspfSummaryLinkAdvertisement { } } -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfTosRoute { pub tos: u8, #[nom(Parse = "be_u24")] @@ -697,7 +697,7 @@ impl fmt::Display for OspfTosRoute { /// specific route exists to the destination. When describing a default /// route, the Link State ID is always set to DefaultDestination /// (0.0.0.0) and the Network Mask is set to 0.0.0.0. -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfASExternalLinkAdvertisement { #[nom(Verify = "header.link_state_type == OspfLinkStateType::ASExternalLink")] pub header: OspfLinkStateAdvertisementHeader, @@ -742,7 +742,7 @@ impl OspfASExternalLinkAdvertisement { } } -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfExternalTosRoute { pub tos: u8, #[nom(Parse = "be_u24")] @@ -769,7 +769,7 @@ impl OspfExternalTosRoute { } /// NSSA AS-External LSA (type 7, rfc1587, rfc3101) -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfNSSAExternalLinkAdvertisement { #[nom(Verify = "header.link_state_type == OspfLinkStateType::NSSAASExternal")] pub header: OspfLinkStateAdvertisementHeader, @@ -830,7 +830,7 @@ impl OspfNSSAExternalLinkAdvertisement { /// be link-local (type-9), area-local (type-10), or the entire OSPF /// routing domain (type-11). Section 3 of this document describes the /// flooding procedures for the Opaque LSA. -#[derive(Debug, NomBE, Hash, Eq, PartialEq)] +#[derive(Debug, NomBE, Hash, Eq, PartialEq, Clone)] pub struct OspfOpaqueLinkAdvertisement { #[nom( Verify = "header.link_state_type == OspfLinkStateType::OpaqueLinkLocalScope ||