diff --git a/holo-isis/src/interface.rs b/holo-isis/src/interface.rs index 429d80d9..5fe6b77c 100644 --- a/holo-isis/src/interface.rs +++ b/holo-isis/src/interface.rs @@ -466,6 +466,7 @@ impl Interface { area_addrs, neighbors, ipv4_addrs, + [], ), )) } diff --git a/holo-isis/src/lsdb.rs b/holo-isis/src/lsdb.rs index 6e1814c9..e075ff80 100644 --- a/holo-isis/src/lsdb.rs +++ b/holo-isis/src/lsdb.rs @@ -257,6 +257,8 @@ fn lsp_build_tlvs( ipv4_internal_reach, [], ext_ipv4_reach, + [], + [], ) } @@ -301,7 +303,7 @@ fn lsp_build_tlvs_pseudo( } } - LspTlvs::new([], [], is_reach, ext_is_reach, [], [], [], []) + LspTlvs::new([], [], is_reach, ext_is_reach, [], [], [], [], [], []) } fn lsp_build_fragments( diff --git a/holo-isis/src/packet/consts.rs b/holo-isis/src/packet/consts.rs index 58b65048..46b39089 100644 --- a/holo-isis/src/packet/consts.rs +++ b/holo-isis/src/packet/consts.rs @@ -54,6 +54,8 @@ pub enum TlvType { Ipv4ExternalReach = 130, Ipv4Addresses = 132, ExtIpv4Reach = 135, + Ipv6Addresses = 232, + Ipv6Reach = 236, } // IS-IS Sub-TLVs for TLVs Advertising Neighbor Information. diff --git a/holo-isis/src/packet/pdu.rs b/holo-isis/src/packet/pdu.rs index 4d3c6f5f..1972e9b7 100644 --- a/holo-isis/src/packet/pdu.rs +++ b/holo-isis/src/packet/pdu.rs @@ -8,7 +8,7 @@ // use std::cell::{RefCell, RefMut}; -use std::net::Ipv4Addr; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::time::Instant; use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -24,8 +24,9 @@ use crate::packet::error::{DecodeError, DecodeResult}; use crate::packet::tlv::{ tlv_entries_split, tlv_take_max, AreaAddressesTlv, ExtIpv4Reach, ExtIpv4ReachTlv, ExtIsReach, ExtIsReachTlv, Ipv4AddressesTlv, Ipv4Reach, - Ipv4ReachTlv, IsReach, IsReachTlv, LspEntriesTlv, LspEntry, NeighborsTlv, - PaddingTlv, ProtocolsSupportedTlv, Tlv, UnknownTlv, TLV_HDR_SIZE, + Ipv4ReachTlv, Ipv6AddressesTlv, Ipv6Reach, Ipv6ReachTlv, IsReach, + IsReachTlv, LspEntriesTlv, LspEntry, NeighborsTlv, PaddingTlv, + ProtocolsSupportedTlv, Tlv, UnknownTlv, TLV_HDR_SIZE, }; use crate::packet::{AreaAddr, LanId, LevelNumber, LevelType, LspId, SystemId}; @@ -72,6 +73,7 @@ pub struct HelloTlvs { pub area_addrs: Vec, pub neighbors: Vec, pub ipv4_addrs: Vec, + pub ipv6_addrs: Vec, pub padding: Vec, pub unknown: Vec, } @@ -106,6 +108,8 @@ pub struct LspTlvs { pub ipv4_internal_reach: Vec, pub ipv4_external_reach: Vec, pub ext_ipv4_reach: Vec, + pub ipv6_addrs: Vec, + pub ipv6_reach: Vec, pub unknown: Vec, } @@ -400,6 +404,10 @@ impl Hello { let tlv = Ipv4AddressesTlv::decode(tlv_len, &mut buf_tlv)?; tlvs.ipv4_addrs.push(tlv); } + Some(TlvType::Ipv6Addresses) => { + let tlv = Ipv6AddressesTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.ipv6_addrs.push(tlv); + } _ => { // Save unknown top-level TLV. let value = buf_tlv.copy_to_bytes(tlv_len as usize); @@ -459,6 +467,9 @@ impl Hello { for tlv in &self.tlvs.ipv4_addrs { tlv.encode(&mut buf); } + for tlv in &self.tlvs.ipv6_addrs { + tlv.encode(&mut buf); + } for tlv in &self.tlvs.padding { tlv.encode(&mut buf); } @@ -474,6 +485,7 @@ impl HelloTlvs { area_addrs: impl IntoIterator, neighbors: impl IntoIterator, ipv4_addrs: impl IntoIterator, + ipv6_addrs: impl IntoIterator, ) -> Self { HelloTlvs { protocols_supported: Some(ProtocolsSupportedTlv::from( @@ -482,6 +494,7 @@ impl HelloTlvs { area_addrs: tlv_entries_split(area_addrs), neighbors: tlv_entries_split(neighbors), ipv4_addrs: tlv_entries_split(ipv4_addrs), + ipv6_addrs: tlv_entries_split(ipv6_addrs), padding: Default::default(), unknown: Default::default(), } @@ -609,6 +622,14 @@ impl Lsp { let tlv = ExtIpv4ReachTlv::decode(tlv_len, &mut buf_tlv)?; tlvs.ext_ipv4_reach.push(tlv); } + Some(TlvType::Ipv6Addresses) => { + let tlv = Ipv6AddressesTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.ipv6_addrs.push(tlv); + } + Some(TlvType::Ipv6Reach) => { + let tlv = Ipv6ReachTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.ipv6_reach.push(tlv); + } _ => { // Save unknown top-level TLV. let value = buf_tlv.copy_to_bytes(tlv_len as usize); @@ -670,6 +691,12 @@ impl Lsp { for tlv in &self.tlvs.ext_ipv4_reach { tlv.encode(&mut buf); } + for tlv in &self.tlvs.ipv6_addrs { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.ipv6_reach { + tlv.encode(&mut buf); + } // Compute LSP checksum. let cksum = Self::checksum(&buf[12..]); @@ -776,6 +803,8 @@ impl LspTlvs { ipv4_internal_reach: impl IntoIterator, ipv4_external_reach: impl IntoIterator, ext_ipv4_reach: impl IntoIterator, + ipv6_addrs: impl IntoIterator, + ipv6_reach: impl IntoIterator, ) -> Self { LspTlvs { protocols_supported: Some(ProtocolsSupportedTlv::from( @@ -788,6 +817,8 @@ impl LspTlvs { ipv4_internal_reach: tlv_entries_split(ipv4_internal_reach), ipv4_external_reach: tlv_entries_split(ipv4_external_reach), ext_ipv4_reach: tlv_entries_split(ext_ipv4_reach), + ipv6_addrs: tlv_entries_split(ipv6_addrs), + ipv6_reach: tlv_entries_split(ipv6_reach), unknown: Default::default(), } } @@ -808,6 +839,8 @@ impl LspTlvs { tlv_take_max(&mut self.ipv4_external_reach, &mut rem_len); let ext_ipv4_reach = tlv_take_max(&mut self.ext_ipv4_reach, &mut rem_len); + let ipv6_addrs = tlv_take_max(&mut self.ipv6_addrs, &mut rem_len); + let ipv6_reach = tlv_take_max(&mut self.ipv6_reach, &mut rem_len); if rem_len == max_len { return None; } @@ -821,6 +854,8 @@ impl LspTlvs { ipv4_internal_reach, ipv4_external_reach, ext_ipv4_reach, + ipv6_addrs, + ipv6_reach, unknown: Default::default(), }) } @@ -879,6 +914,17 @@ impl LspTlvs { pub(crate) fn ext_ipv4_reach(&self) -> impl Iterator { self.ext_ipv4_reach.iter().flat_map(|tlv| tlv.list.iter()) } + + // Returns an iterator over all IPv6 addresses from TLVs of type 232. + pub(crate) fn ipv6_addrs(&self) -> impl Iterator { + self.ipv6_addrs.iter().flat_map(|tlv| tlv.list.iter()) + } + + // Returns an iterator over all IPv6 reachability entries from TLVs of + // type 236. + pub(crate) fn ipv6_reach(&self) -> impl Iterator { + self.ipv6_reach.iter().flat_map(|tlv| tlv.list.iter()) + } } // ===== impl Snp ===== diff --git a/holo-isis/src/packet/tlv.rs b/holo-isis/src/packet/tlv.rs index d0b7782d..e4c7b42a 100644 --- a/holo-isis/src/packet/tlv.rs +++ b/holo-isis/src/packet/tlv.rs @@ -9,13 +9,13 @@ #![allow(clippy::match_single_binding)] -use std::net::Ipv4Addr; +use std::net::{Ipv4Addr, Ipv6Addr}; use bytes::{Buf, BufMut, Bytes, BytesMut}; use derive_new::new; use holo_utils::bytes::{BytesExt, BytesMutExt}; -use holo_utils::ip::Ipv4AddrExt; -use ipnetwork::Ipv4Network; +use holo_utils::ip::{Ipv4AddrExt, Ipv6AddrExt}; +use ipnetwork::{Ipv4Network, Ipv6Network}; use num_traits::{FromPrimitive, ToPrimitive}; use serde::{Deserialize, Serialize}; @@ -31,6 +31,7 @@ pub const TLV_MAX_LEN: usize = 255; // Network Layer Protocol IDs. pub enum Nlpid { Ipv4 = 0xCC, + Ipv6 = 0x8E, } // Trait for all TLVs. @@ -88,6 +89,12 @@ pub struct Ipv4AddressesTlv { pub list: Vec, } +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Ipv6AddressesTlv { + pub list: Vec, +} + #[derive(Clone, Debug, Eq, PartialEq)] #[derive(Deserialize, Serialize)] pub struct LspEntriesTlv { @@ -177,6 +184,28 @@ pub struct ExtIpv4ReachSubTlvs { pub unknown: Vec, } +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Ipv6ReachTlv { + pub list: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Ipv6Reach { + pub metric: u32, + pub up_down: bool, + pub external: bool, + pub prefix: Ipv6Network, + pub sub_tlvs: Ipv6ReachSubTlvs, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Ipv6ReachSubTlvs { + pub unknown: Vec, +} + #[derive(Clone, Debug, Eq, PartialEq)] #[derive(new)] #[derive(Deserialize, Serialize)] @@ -409,6 +438,58 @@ where } } +// ===== impl Ipv6AddressesTlv ===== + +impl Ipv6AddressesTlv { + pub(crate) fn decode(tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + // Validate the TLV length. + if tlv_len as usize % Ipv6Addr::LENGTH != 0 { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + while buf.remaining() >= Ipv6Addr::LENGTH { + // Parse IPv6 address. + let addr = buf.get_ipv6(); + list.push(addr); + } + + Ok(Ipv6AddressesTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::Ipv6Addresses); + for entry in &self.list { + buf.put_ipv6(entry); + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for Ipv6AddressesTlv { + type Entry = Ipv6Addr; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(_entry: &Ipv6Addr) -> usize { + Ipv6Addr::LENGTH + } +} + +impl From for Ipv6AddressesTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> Ipv6AddressesTlv { + Ipv6AddressesTlv { + list: iter.into_iter().collect(), + } + } +} + // ===== impl LspEntriesTlv ===== impl LspEntriesTlv { @@ -864,6 +945,140 @@ where } } +// ===== impl Ipv6ReachTlv ===== + +impl Ipv6ReachTlv { + const ENTRY_MIN_SIZE: usize = 6; + const FLAG_UPDOWN: u8 = 0x80; + const FLAG_EXTERNAL: u8 = 0x40; + const FLAG_SUBTLVS: u8 = 0x20; + + pub(crate) fn decode(_tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + while buf.remaining() >= Self::ENTRY_MIN_SIZE { + // Parse metric. + let metric = buf.get_u32(); + + // Parse flags field. + let flags = buf.get_u8(); + let up_down = (flags & Self::FLAG_UPDOWN) != 0; + let external = (flags & Self::FLAG_EXTERNAL) != 0; + let subtlvs = (flags & Self::FLAG_SUBTLVS) != 0; + + // Parse prefix length. + let plen = buf.get_u8(); + + // Parse prefix (variable length). + let mut prefix_bytes = [0; Ipv6Addr::LENGTH]; + let plen_wire = prefix_wire_len(plen); + buf.copy_to_slice(&mut prefix_bytes[..plen_wire]); + let prefix = Ipv6Addr::from(prefix_bytes); + + // Parse Sub-TLVs. + let mut sub_tlvs = Ipv6ReachSubTlvs::default(); + if subtlvs { + let mut sub_tlvs_len = buf.get_u8(); + while sub_tlvs_len >= TLV_HDR_SIZE as u8 { + // Parse TLV type. + let stlv_type = buf.get_u8(); + sub_tlvs_len -= 1; + let stlv_etype = PrefixSubTlvType::from_u8(stlv_type); + + // Parse and validate TLV length. + let stlv_len = buf.get_u8(); + sub_tlvs_len -= 1; + if stlv_len as usize > buf.remaining() { + return Err(DecodeError::InvalidTlvLength(stlv_len)); + } + + // Parse TLV value. + let mut buf_tlv = buf.copy_to_bytes(stlv_len as usize); + sub_tlvs_len -= stlv_len; + match stlv_etype { + _ => { + // Save unknown top-level TLV. + let value = + buf_tlv.copy_to_bytes(stlv_len as usize); + sub_tlvs.unknown.push(UnknownTlv::new( + stlv_type, stlv_len, value, + )); + } + } + } + } + + // Ignore malformed prefixes. + let Ok(prefix) = Ipv6Network::new(prefix, plen) else { + continue; + }; + + list.push(Ipv6Reach { + metric, + up_down, + external, + prefix, + sub_tlvs, + }); + } + + Ok(Ipv6ReachTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::Ipv6Reach); + for entry in &self.list { + // Encode metric. + buf.put_u32(entry.metric); + + // Encode flags field. + let mut flags = 0; + if entry.up_down { + flags |= Self::FLAG_UPDOWN; + } + if entry.external { + flags |= Self::FLAG_EXTERNAL; + } + buf.put_u8(flags); + + // Encode prefix length. + let plen = entry.prefix.prefix(); + buf.put_u8(plen); + + // Encode prefix (variable length). + let plen_wire = prefix_wire_len(plen); + buf.put(&entry.prefix.ip().octets()[0..plen_wire]); + + // Encode Sub-TLVs. + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for Ipv6ReachTlv { + type Entry = Ipv6Reach; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(entry: &Ipv6Reach) -> usize { + let plen = entry.prefix.prefix(); + Self::ENTRY_MIN_SIZE + prefix_wire_len(plen) + } +} + +impl From for Ipv6ReachTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> Ipv6ReachTlv { + Ipv6ReachTlv { + list: iter.into_iter().collect(), + } + } +} + // ===== blanket implementations ===== impl Tlv for T { diff --git a/holo-isis/tests/packet/mod.rs b/holo-isis/tests/packet/mod.rs index 0b88f4cf..a0a92aba 100644 --- a/holo-isis/tests/packet/mod.rs +++ b/holo-isis/tests/packet/mod.rs @@ -7,7 +7,7 @@ // See: https://nlnet.nl/NGI0 // -use std::net::Ipv4Addr; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; use std::sync::LazyLock as Lazy; @@ -18,13 +18,14 @@ use holo_isis::packet::pdu::{ }; use holo_isis::packet::tlv::{ AreaAddressesTlv, ExtIpv4Reach, ExtIpv4ReachTlv, ExtIsReach, ExtIsReachTlv, - Ipv4AddressesTlv, Ipv4Reach, Ipv4ReachTlv, IsReach, IsReachTlv, - LspEntriesTlv, LspEntry, NeighborsTlv, PaddingTlv, ProtocolsSupportedTlv, + Ipv4AddressesTlv, Ipv4Reach, Ipv4ReachTlv, Ipv6AddressesTlv, Ipv6Reach, + Ipv6ReachTlv, IsReach, IsReachTlv, LspEntriesTlv, LspEntry, NeighborsTlv, + PaddingTlv, ProtocolsSupportedTlv, }; use holo_isis::packet::{ AreaAddr, LanId, LevelNumber, LevelType, LspId, SystemId, }; -use ipnetwork::Ipv4Network; +use ipnetwork::{Ipv4Network, Ipv6Network}; // // Helper functions. @@ -216,6 +217,7 @@ static LAN_HELLO1: Lazy<(Vec, Pdu)> = Lazy::new(|| { ipv4_addrs: vec![Ipv4AddressesTlv { list: vec![Ipv4Addr::from_str("10.0.1.1").unwrap()], }], + ipv6_addrs: vec![], padding: vec![ PaddingTlv { length: 255 }, PaddingTlv { length: 255 }, @@ -390,6 +392,7 @@ static P2P_HELLO1: Lazy<(Vec, Pdu)> = Lazy::new(|| { ipv4_addrs: vec![Ipv4AddressesTlv { list: vec![Ipv4Addr::from_str("10.0.7.6").unwrap()], }], + ipv6_addrs: vec![], padding: vec![ PaddingTlv { length: 255 }, PaddingTlv { length: 255 }, @@ -520,13 +523,16 @@ static PSNP1: Lazy<(Vec, Pdu)> = Lazy::new(|| { static LSP1: Lazy<(Vec, Pdu)> = Lazy::new(|| { ( vec![ - 0x83, 0x1b, 0x01, 0x00, 0x12, 0x01, 0x00, 0x00, 0x00, 0x4a, 0x04, - 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x04, 0xd7, 0xd0, 0x01, 0x81, 0x01, 0xcc, 0x01, 0x04, 0x03, - 0x49, 0x00, 0x00, 0x16, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x03, 0x00, 0x00, 0x0a, 0x00, 0x84, 0x04, 0x01, 0x01, 0x01, 0x01, - 0x87, 0x11, 0x00, 0x00, 0x00, 0x0a, 0x18, 0x0a, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x0a, 0x20, 0x01, 0x01, 0x01, 0x01, + 0x83, 0x1b, 0x01, 0x00, 0x12, 0x01, 0x00, 0x00, 0x00, 0x69, 0x04, + 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x13, 0x06, 0xe2, 0x01, 0x81, 0x01, 0xcc, 0x01, 0x04, 0x03, + 0x49, 0x00, 0x00, 0x02, 0x17, 0x00, 0x0a, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0a, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x84, 0x04, 0x06, 0x06, 0x06, + 0x06, 0x80, 0x24, 0x0a, 0x80, 0x80, 0x80, 0x0a, 0x00, 0x07, 0x00, + 0xff, 0xff, 0xff, 0x00, 0x0a, 0x80, 0x80, 0x80, 0x0a, 0x00, 0x08, + 0x00, 0xff, 0xff, 0xff, 0x00, 0x0a, 0x80, 0x80, 0x80, 0x06, 0x06, + 0x06, 0x06, 0xff, 0xff, 0xff, 0xff, ], Pdu::Lsp(Lsp::new( LevelNumber::L1, @@ -574,6 +580,31 @@ static LSP1: Lazy<(Vec, Pdu)> = Lazy::new(|| { }, ], }], + ipv6_addrs: vec![Ipv6AddressesTlv { + list: vec![Ipv6Addr::from_str("2001:db8::1").unwrap()], + }], + ipv6_reach: vec![Ipv6ReachTlv { + list: vec![ + Ipv6Reach { + metric: 10, + up_down: false, + external: false, + prefix: Ipv6Network::from_str("2001:db8::1/128") + .unwrap(), + sub_tlvs: Default::default(), + }, + Ipv6Reach { + metric: 10, + up_down: false, + external: false, + prefix: Ipv6Network::from_str( + "2001:db8:1000::1/64", + ) + .unwrap(), + sub_tlvs: Default::default(), + }, + ], + }], unknown: vec![], }, )), @@ -666,6 +697,8 @@ static LSP2: Lazy<(Vec, Pdu)> = Lazy::new(|| { }], ipv4_external_reach: vec![], ext_ipv4_reach: vec![], + ipv6_addrs: vec![], + ipv6_reach: vec![], unknown: vec![], }, )),