diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 243fd755..d60c905d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [1.65, stable, beta, nightly] + rust: [1.71, stable, beta, nightly] steps: - name: Checkout repository uses: actions/checkout@v1 diff --git a/Cargo.toml b/Cargo.toml index 8ce48814..abce2e57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ categories = ["network-programming"] description = "A Library with Building Blocks for BGP Routing" documentation = "https://docs.rs/routecore/" edition = "2021" -rust-version = "1.65" +rust-version = "1.71" keywords = ["routing", "bgp"] license = "BSD-3-Clause" @@ -23,11 +23,12 @@ chrono = { version = "0.4", optional = true } const-str = { version = "0.5.3", optional = true, features = ["case"] } log = { version = "0.4.17", optional = true } -octseq = { version = "0.2.0", features = ["bytes"], optional = true } +octseq = { version = "0.3.1", features = ["bytes"], optional = true } tokio = { version = "1", features = ["sync", "rt"], optional = true } [dev-dependencies] serde_test = "1.0" +memmap2 = "0.6" [features] default = ["octseq"] diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 00000000..1a45eee7 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 00000000..b40951e8 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,430 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "const-str" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aca749d3d3f5b87a0d6100509879f9cf486ab510803a4a4e1001da1ff61c2bd6" +dependencies = [ + "const-str-proc-macro", +] + +[[package]] +name = "const-str-proc-macro" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51a6bdde073d75ff29c639a6bfcee435483f73f9a7426249825e2542e089727c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "derive_arbitrary" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "octseq" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190e3482f38446ee3f3ab50b049a16b072b6111cba008381b816598478ba65d" +dependencies = [ + "bytes", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "routecore" +version = "0.4.0-dev" +dependencies = [ + "arbitrary", + "bytes", + "chrono", + "const-str", + "log", + "octseq", +] + +[[package]] +name = "routecore-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "routecore", +] + +[[package]] +name = "syn" +version = "2.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 00000000..e48bcf6e --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "routecore-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.routecore] +path = ".." +features = [ "arbitrary", "bmp" ] + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "parse_bmp_message" +path = "fuzz_targets/parse_bmp_message.rs" +test = false +doc = false + + +[[bin]] +name = "parse_bgp_message" +path = "fuzz_targets/parse_bgp_message.rs" +test = false +doc = false + +[[bin]] +name = "parse_update_message" +path = "fuzz_targets/parse_update_message.rs" +test = false +doc = false + +[[bin]] +name = "parse_open_message" +path = "fuzz_targets/parse_open_message.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/parse_bgp_message.rs b/fuzz/fuzz_targets/parse_bgp_message.rs new file mode 100644 index 00000000..aa10c522 --- /dev/null +++ b/fuzz/fuzz_targets/parse_bgp_message.rs @@ -0,0 +1,11 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use routecore::bgp::message::{Message, SessionConfig}; + +fuzz_target!(|data: (&[u8], Option)| { + let _ = Message::from_octets(data.0, data.1); +}); + + + diff --git a/fuzz/fuzz_targets/parse_bmp_message.rs b/fuzz/fuzz_targets/parse_bmp_message.rs new file mode 100644 index 00000000..d62bfff8 --- /dev/null +++ b/fuzz/fuzz_targets/parse_bmp_message.rs @@ -0,0 +1,10 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use routecore::bmp::message::Message; + + +fuzz_target!(|data: &[u8]| { + let _ = Message::from_octets(data); +}); + diff --git a/fuzz/fuzz_targets/parse_open_message.rs b/fuzz/fuzz_targets/parse_open_message.rs new file mode 100644 index 00000000..dd0615a5 --- /dev/null +++ b/fuzz/fuzz_targets/parse_open_message.rs @@ -0,0 +1,13 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use routecore::bgp::message::OpenMessage; + +fuzz_target!(|data: &[u8]| { + if let Ok(open) = OpenMessage::from_octets(data) { + open.capabilities().count(); + } +}); + + + diff --git a/fuzz/fuzz_targets/parse_update_message.rs b/fuzz/fuzz_targets/parse_update_message.rs new file mode 100644 index 00000000..9c72a15b --- /dev/null +++ b/fuzz/fuzz_targets/parse_update_message.rs @@ -0,0 +1,29 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use routecore::bgp::message::{UpdateMessage, SessionConfig}; +use routecore::bgp::message::update_builder::UpdateBuilder; + +fuzz_target!(|data: (&[u8], SessionConfig)| { + if let Ok(upd) = UpdateMessage::from_octets(data.0, data.1) { + if let Ok(pas) = upd.path_attributes() { + for pa in pas.into_iter() { + if let Ok(pa) = pa { + let _ = pa.to_owned(); + } + } + } + /* + if let Ok(builder) = UpdateBuilder::from_update_message( + &upd, + data.1, + target, + ) { + let _ = builder.into_message(); //.unwrap(); + } + */ + } +}); + + + diff --git a/src/bgp/aspath.rs b/src/bgp/aspath.rs index 17b76d08..41b94606 100644 --- a/src/bgp/aspath.rs +++ b/src/bgp/aspath.rs @@ -17,7 +17,9 @@ use crate::asn::{Asn, LargeAsnError}; use octseq::builder::{infallible, EmptyBuilder, FromBuilder, OctetsBuilder}; use octseq::octets::{Octets, OctetsFrom, OctetsInto}; -use octseq::parse::{Parser, ShortInput}; +use octseq::parse::Parser; + +use crate::util::parser::ParseError; pub type OwnedHop = Hop>; @@ -421,7 +423,7 @@ impl> AsPath { pub fn new( octets: Octs, four_byte_asns: bool, - ) -> Result { + ) -> Result { AsPath::check(octets.as_ref(), four_byte_asns)?; Ok(unsafe { Self::new_unchecked(octets, four_byte_asns) @@ -441,6 +443,19 @@ impl> AsPath { ) -> Self { AsPath { octets, four_byte_asns } } + + pub fn into_inner(self) -> Octs { + self.octets + } + + pub(crate) fn compose_len(&self) -> usize { + let len = self.octets.as_ref().len(); + if len > 255 { + len + 4 + } else { + len + 3 + } + } } impl AsPath> { @@ -459,11 +474,10 @@ impl AsPath<()> { /// Checks whether `octets` validly represents an AS_PATH attribute. pub fn check( octets: &[u8], four_byte_asns: bool - ) -> Result<(), ShortInput> { + ) -> Result<(), ParseError> { let mut parser = Parser::from_ref(octets); while parser.remaining() > 0 { - // XXX Should this error on an unknown segment type? - parser.advance(1)?; // segment type + SegmentType::try_from(parser.parse_u8()?)?; let len = usize::from(parser.parse_u8()?); // segment length parser.advance(len * asn_size(four_byte_asns))?; // ASNs. } @@ -651,7 +665,7 @@ impl<'a, Octs: Octets> Iterator for PathHops<'a, Octs> { //----------- PathSegments --------------------------------------------------- -/// Iterates over [`Segment]`s in an [`AsPath`]. +/// Iterates over [`Segment`]s in an [`AsPath`]. pub struct PathSegments<'a, Octs> { parser: Parser<'a, Octs>, four_byte_asns: bool, @@ -1139,6 +1153,12 @@ impl fmt::Display for InvalidSegmentType { impl error::Error for InvalidSegmentType { } +impl From for ParseError { + fn from(_: InvalidSegmentType) -> ParseError { + ParseError::form_error("invalid segment type") + } +} + //------------ ToPathError --------------------------------------------------- #[derive(Debug)] diff --git a/src/bgp/communities.rs b/src/bgp/communities.rs index 611da1cf..d40dd5c0 100644 --- a/src/bgp/communities.rs +++ b/src/bgp/communities.rs @@ -534,10 +534,15 @@ impl ExtendedCommunity { Self(raw) } + #[deprecated = "use to_raw"] pub fn raw(self) -> [u8; 8] { self.0 } + pub fn to_raw(self) -> [u8; 8] { + self.0 + } + pub fn type_raw(self) -> u8 { self.0[0] } @@ -768,6 +773,12 @@ impl From<[u8; 8]> for Community { } } +impl From<[u8; 8]> for ExtendedCommunity { + fn from(raw: [u8; 8]) -> ExtendedCommunity { + ExtendedCommunity(raw) + } +} + impl FromStr for ExtendedCommunity { type Err = ParseError; @@ -896,7 +907,7 @@ impl Display for ExtendedCommunity { (_,_) => { write!(f, "0x")?; - for b in &self.raw() { + for b in &self.to_raw() { write!(f, "{:02X}", b)?; } Ok(()) @@ -919,10 +930,15 @@ impl Ipv6ExtendedCommunity { Self(raw) } + #[deprecated = "use to_raw"] pub fn raw(self) -> [u8; 20] { self.0 } + pub fn to_raw(self) -> [u8; 20] { + self.0 + } + pub fn type_raw(self) -> u8 { self.0[0] } @@ -965,6 +981,12 @@ impl From<[u8; 20]> for Community { } } +impl From<[u8; 20]> for Ipv6ExtendedCommunity { + fn from(raw: [u8; 20]) -> Ipv6ExtendedCommunity { + Ipv6ExtendedCommunity(raw) + } +} + // XXX with ':' being part of IPv6 addresses, it is unclear what the canonical // format is to write out IPv6 Extended Communities. // We limit to parsing a hex value only for now. @@ -1014,7 +1036,7 @@ impl Display for Ipv6ExtendedCommunity { ), _ => { write!(f, "0x")?; - self.raw().iter().for_each(|b|{ + self.to_raw().iter().for_each(|b|{ let _ = write!(f, "{:02x}", b); }); Ok(()) @@ -1040,10 +1062,15 @@ impl LargeCommunity { Self(raw) } + #[deprecated = "use to_raw"] pub fn raw(self) -> [u8; 12] { self.0 } + pub fn to_raw(self) -> [u8; 12] { + self.0 + } + pub fn global(self) -> u32 { u32::from_be_bytes(self.0[0..4].try_into().unwrap()) } @@ -1074,6 +1101,12 @@ impl From<[u8; 12]> for Community { } } +impl From<[u8; 12]> for LargeCommunity { + fn from(raw: [u8; 12]) -> LargeCommunity { + LargeCommunity(raw) + } +} + impl FromStr for LargeCommunity { type Err = ParseError; diff --git a/src/bgp/message/attr_change_set.rs b/src/bgp/message/attr_change_set.rs index fda0ee9d..d38c215a 100644 --- a/src/bgp/message/attr_change_set.rs +++ b/src/bgp/message/attr_change_set.rs @@ -6,7 +6,7 @@ use crate::bgp::communities::{ }; use crate::bgp::message::update::{ - Aggregator, LocalPref, MultiExitDisc, NextHop, OriginType, + /*Aggregator,*/ LocalPref, MultiExitDisc, NextHop, OriginType, }; use crate::bgp::message::nlri::Nlri; @@ -95,3 +95,37 @@ pub struct AttrChangeSet { pub attr_set: Todo, pub rsrvd_development: Todo, } + +impl AttrChangeSet { + pub fn empty() -> Self { + AttrChangeSet { + nlri: ChangedOption::new(), + withdrawals: ChangedOption::new(), + + as_path: ChangedOption::new(), + as4_path: ChangedOption::new(), + + standard_communities: ChangedOption::new(), + extended_communities: ChangedOption::new(), + ipv6_extended_communities: ChangedOption::new(), + large_communities: ChangedOption::new(), + + origin_type: ChangedOption::new(), + next_hop: ChangedOption::new(), + multi_exit_discriminator: ChangedOption::new(), + local_pref: ChangedOption::new(), + atomic_aggregate: ChangedOption::new(), + aggregator: ChangedOption::new(), + + originator_id: (), + cluster_list: (), + as4_aggregator: (), + connector: (), + as_path_limit: (), + pmsi_tunnel: (), + bgpsec_as_path: (), + attr_set: (), + rsrvd_development: (), + } + } +} diff --git a/src/bgp/message/keepalive.rs b/src/bgp/message/keepalive.rs index 452ce6ba..660bbb2c 100644 --- a/src/bgp/message/keepalive.rs +++ b/src/bgp/message/keepalive.rs @@ -15,7 +15,7 @@ impl KeepaliveMessage { } pub fn check(octets: &Octs) -> Result<(), ParseError> { - let mut parser = Parser::from_ref(octets.as_ref()); + let mut parser = Parser::from_ref(octets); Header::check(&mut parser)?; if parser.remaining() > 0 { return Err(ParseError::form_error("KEEPALIVE of >19 bytes")); diff --git a/src/bgp/message/mod.rs b/src/bgp/message/mod.rs index 5d26ae1a..6133e4c9 100644 --- a/src/bgp/message/mod.rs +++ b/src/bgp/message/mod.rs @@ -1,17 +1,22 @@ pub mod open; pub mod update; +pub mod update_builder; pub mod nlri; pub mod notification; pub mod keepalive; -pub mod attr_change_set; +pub mod routerefresh; +//pub mod attr_change_set; use octseq::{Octets, Parser}; use crate::util::parser::ParseError; -use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; use std::error::Error; +use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; +use std::io::Read; use crate::addr::PrefixError; use crate::typeenum; // from util::macros +use log::debug; + #[cfg(feature = "serde")] use serde::{Serialize, Deserialize}; @@ -19,6 +24,7 @@ pub use open::OpenMessage; pub use update::{UpdateMessage, SessionConfig}; pub use notification::NotificationMessage; pub use keepalive::KeepaliveMessage; +pub use routerefresh::RouteRefreshMessage; //--- Generic ---------------------------------------------------------------- @@ -46,6 +52,7 @@ pub enum Message { Update(UpdateMessage), Notification(NotificationMessage), Keepalive(KeepaliveMessage), + RouteRefresh(RouteRefreshMessage), } impl AsRef<[u8]> for Message { @@ -55,6 +62,7 @@ impl AsRef<[u8]> for Message { Message::Update(m) => m.as_ref(), Message::Notification(m) => m.as_ref(), Message::Keepalive(m) => m.as_ref(), + Message::RouteRefresh(m) => m.as_ref(), } } } @@ -66,6 +74,7 @@ impl Message { Message::Update(m) => m.octets(), Message::Notification(m) => m.octets(), Message::Keepalive(m) => m.octets(), + Message::RouteRefresh(m) => m.octets(), } } } @@ -128,11 +137,46 @@ impl Message { Ok(Message::Keepalive( KeepaliveMessage::from_octets(octets)? )), - t => panic!("not implemented yet: {:?}", t) + MsgType::RouteRefresh => { + debug!("Unimplemented BGP message type ROUTEREFRESH"); + Err(ParseError::Unsupported) + } + MsgType::Unimplemented(t) => { + debug!("Unimplemented BGP message type {t}"); + Err(ParseError::Unsupported) + } } } } +/// Read a BGP message into `buf` and return the slice based on the length. +/// +/// No parsing or validation is performed. This function jumps over the BGP +/// marker to read the length, and reads bytes accordingly. +/// The returned slice can be used with `Message::from_octets` to actually get +/// a BGP message. +pub fn read_message<'a, T: Read>(bytes: &mut T, buf: &'a mut [u8; 4096]) + -> Result, &'a str> +{ + + if let Err(e) = bytes.read_exact(&mut buf[..18]) { + match e.kind() { + std::io::ErrorKind::UnexpectedEof => { return Ok(None) } + _ => return Err("io error") + } + } + + let len = u16::from_be_bytes([buf[16], buf[17]]) as usize; + if len > 4096 { + println!("jumbo? (len: {len}) {:x?}", &buf[..20]); + } + + // including marker+length+type + let _ = bytes.read_exact(&mut buf[18..(len)]); + Ok(Some(&buf[..len])) +} + + //--- From / TryFrom --------------------------------------------------------- @@ -192,7 +236,7 @@ impl TryFrom> for NotificationMessage { #[derive(Clone, Copy, Default)] pub struct Header(Octs); -impl> Header { +impl> Header { pub fn set_type(&mut self, typ: MsgType) { self.0.as_mut()[18] = typ.into(); } @@ -208,6 +252,12 @@ impl AsRef<[u8]> for Header { } } +impl Header<&mut [u8]> { + pub fn for_slice_mut(s: &mut [u8]) -> Header<&mut [u8]> { + Header(s) + } +} + impl Header { pub fn new() -> Header> { @@ -237,7 +287,7 @@ impl Header { 3 => MsgType::Notification, 4 => MsgType::Keepalive, 5 => MsgType::RouteRefresh, - u => panic!("illegal Message Type {}", u) + u => MsgType::Unimplemented(u) } } } @@ -257,8 +307,8 @@ impl Header { } } -impl Header<()> { - pub fn check(parser: &mut Parser<[u8]>) -> Result<(), ParseError> { +impl Header { + pub fn check(parser: &mut Parser<'_, Octs>) -> Result<(), ParseError> { Marker::check(parser)?; let len = parser.parse_u16_be()? as usize; if len != parser.len() { @@ -273,7 +323,7 @@ impl Header<()> { struct Marker; impl Marker { - fn check(parser: &mut Parser<'_, R>) + fn check(parser: &mut Parser<'_, R>) -> Result<(), ParseError> { let mut buf = [0u8; 16]; diff --git a/src/bgp/message/nlri.rs b/src/bgp/message/nlri.rs index 368a1446..6d20a0ce 100644 --- a/src/bgp/message/nlri.rs +++ b/src/bgp/message/nlri.rs @@ -1,22 +1,428 @@ -use crate::bgp::types::{AFI, SAFI, NextHop}; +use crate::bgp::types::{AFI, SAFI, AfiSafi, NextHop}; use crate::addr::Prefix; use crate::util::parser::{parse_ipv4addr, parse_ipv6addr, ParseError}; -use crate::bgp::message::update::{AddPath, SessionConfig}; +use crate::bgp::message::update::{SessionConfig}; use crate::flowspec::Component; -use octseq::{Octets, Parser}; -use log::warn; +use crate::typeenum; +use octseq::{Octets, OctetsBuilder, OctetsFrom, Parser}; +use log::debug; +use std::fmt; use std::net::IpAddr; -use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; +use std::str::FromStr; #[cfg(feature = "serde")] use serde::{Serialize, Deserialize}; + +//------------ FixedNlriIter ------------------------------------------------- +// +// This is an alternative iterator for parsing wireformat encoded NLRI. Where +// the 'normal' iterator will match on afi/safi every time next() is called, +// the FixedNlriIter is generic over a type implementing AfiSafiParse. That +// trait has explicit implementations for every afi/safi pair, so the whole +// matching is lifted up one level. +// The normal iterator is nicer in terms of API, the fixed one might be a bit +// more performant in certain cases. + +pub struct FixedNlriIter<'a, T, AS> { + parser: Parser<'a, T>, + afisafi: std::marker::PhantomData, +} + +impl<'a, T: 'a + Octets, AS: AfiSafiParse > FixedNlriIter<'a, T, AS> { + pub fn new(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter { parser: *parser, afisafi: std::marker::PhantomData} + } + + pub fn validate(&mut self) -> Result<(), ParseError> { + let pos = self.parser.pos(); + while self.parser.remaining() > 0 { + self.skip_nlri()? + } + self.parser.seek(pos)?; + Ok(()) + } + + fn get_nlri(&mut self) -> Result>, ParseError> { + AS::parse_nlri(&mut self.parser) + } + + fn skip_nlri(&mut self) -> Result<(), ParseError> { + AS::skip_nlri(&mut self.parser) + } +} + +//------------ Ipv4Unicast --------------------------------------------------- + +pub(crate) struct Ipv4Unicast; +impl AfiSafiParse for Ipv4Unicast { + type Item = BasicNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + Ok( + BasicNlri::new(parse_prefix(parser, AFI::Ipv4)?) + ) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + skip_prefix(parser) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv4Unicast> { + pub(crate) fn ipv4unicast(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv4UnicastAddPath -------------------------------------------- + +pub(crate) struct Ipv4UnicastAddPath; +impl AfiSafiParse for Ipv4UnicastAddPath { + type Item = BasicNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + let path_id = PathId::parse(parser)?; + Ok( + BasicNlri::with_path_id( + parse_prefix(parser, AFI::Ipv4)?, + path_id + ) + ) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + skip_prefix_addpath(parser) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv4UnicastAddPath> { + pub(crate) fn ipv4unicast_addpath(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv6Unicast --------------------------------------------------- + +pub(crate) struct Ipv6Unicast; +impl AfiSafiParse for Ipv6Unicast { + type Item = BasicNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + Ok( + BasicNlri::new(parse_prefix(parser, AFI::Ipv6)?) + ) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + skip_prefix(parser) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv6Unicast> { + pub(crate) fn ipv6unicast(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv6UnicastAddPath -------------------------------------------- + +pub(crate) struct Ipv6UnicastAddPath; +impl AfiSafiParse for Ipv6UnicastAddPath { + type Item = BasicNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + let path_id = PathId::parse(parser)?; + Ok( + BasicNlri::with_path_id( + parse_prefix(parser, AFI::Ipv6)?, + path_id + ) + ) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + skip_prefix_addpath(parser) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv6UnicastAddPath> { + pub(crate) fn ipv6unicast_addpath(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv4Multicast ------------------------------------------------- + +pub(crate) struct Ipv4Multicast; +impl AfiSafiParse for Ipv4Multicast { + type Item = BasicNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + Ok( + BasicNlri::new(parse_prefix(parser, AFI::Ipv4)?) + ) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + skip_prefix(parser) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv4Multicast> { + pub(crate) fn ipv4multicast(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv4MulticastAddPath ------------------------------------------ + +pub(crate) struct Ipv4MulticastAddPath; +impl AfiSafiParse for Ipv4MulticastAddPath { + type Item = BasicNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + let path_id = PathId::parse(parser)?; + Ok( + BasicNlri::with_path_id( + parse_prefix(parser, AFI::Ipv4)?, + path_id + ) + ) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + skip_prefix_addpath(parser) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv4MulticastAddPath> { + pub(crate) fn ipv4multicast_addpath(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv6Multicast ------------------------------------------------- + +pub(crate) struct Ipv6Multicast; +impl AfiSafiParse for Ipv6Multicast { + type Item = BasicNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + Ok( + BasicNlri::new(parse_prefix(parser, AFI::Ipv6)?) + ) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + skip_prefix(parser) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv6Multicast> { + pub(crate) fn ipv6multicast(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv6MulticastAddPath ------------------------------------------ + +pub(crate) struct Ipv6MulticastAddPath; +impl AfiSafiParse for Ipv6MulticastAddPath { + type Item = BasicNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + let path_id = PathId::parse(parser)?; + Ok( + BasicNlri::with_path_id( + parse_prefix(parser, AFI::Ipv6)?, + path_id + ) + ) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + skip_prefix_addpath(parser) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv6MulticastAddPath> { + pub(crate) fn ipv6multicast_addpath(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv4MplsUnicast ------------------------------------------------- + +pub(crate) struct Ipv4MplsUnicast; +impl AfiSafiParse for Ipv4MplsUnicast { + type Item = MplsNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + MplsNlri::parse_no_addpath(parser, AfiSafi::Ipv4MplsUnicast) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + MplsNlri::skip_labels_and_prefix(parser)?; + Ok(()) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv4MplsUnicast> { + pub(crate) fn ipv4mpls_unicast(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv4MplsUnicastAddPath ------------------------------------------ + +pub(crate) struct Ipv4MplsUnicastAddPath; +impl AfiSafiParse for Ipv4MplsUnicastAddPath { + type Item = MplsNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + MplsNlri::parse_addpath(parser, AfiSafi::Ipv4MplsUnicast) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + parser.advance(4)?; + MplsNlri::skip_labels_and_prefix(parser)?; + Ok(()) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv4MplsUnicastAddPath> { + pub(crate) fn ipv4mpls_unicast_addpath(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv6MplsUnicast ------------------------------------------------- + +pub(crate) struct Ipv6MplsUnicast; +impl AfiSafiParse for Ipv6MplsUnicast { + type Item = MplsNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + MplsNlri::parse_no_addpath(parser, AfiSafi::Ipv6MplsUnicast) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + MplsNlri::skip_labels_and_prefix(parser)?; + Ok(()) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv6MplsUnicast> { + pub(crate) fn ipv6mpls_unicast(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//------------ Ipv6MplsUnicastAddPath ------------------------------------------ + +pub(crate) struct Ipv6MplsUnicastAddPath; +impl AfiSafiParse for Ipv6MplsUnicastAddPath { + type Item = MplsNlri; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError> { + MplsNlri::parse_addpath(parser, AfiSafi::Ipv6MplsUnicast) + } + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError> + { + parser.advance(4)?; + MplsNlri::skip_labels_and_prefix(parser)?; + Ok(()) + } +} + +impl<'a, T: 'a + Octets> FixedNlriIter<'a, T, Ipv6MplsUnicastAddPath> { + pub(crate) fn ipv6mpls_unicast_addpath(parser: &mut Parser<'a, T>) -> Self { + FixedNlriIter::::new(parser) + } +} + +//TODO: +// +// Ipv4MplsVpnUnicast, +// Ipv6MplsVpnUnicast, +// +// Ipv4RouteTarget, +// +// Ipv4FlowSpec, +// Ipv6FlowSpec, +// +// L2VpnVpls, +// L2VpnEvpn, + +//------------ AfiSafiParse -------------------------------------------------- + +pub trait AfiSafiParse { + type Item; + fn parse_nlri<'a, Octs: Octets>( + parser: &mut Parser<'a, Octs> + ) -> Result>, ParseError>; + + fn skip_nlri(parser: &mut Parser<'_, Octs>) + -> Result<(), ParseError>; +} + +//------------ Iterator ------------------------------------------------------ + +impl<'a, T: Octets, AS: AfiSafiParse> Iterator for FixedNlriIter<'a, T, AS> { + type Item = Result>, ParseError>; + + fn next(&mut self) -> Option { + if self.parser.remaining() == 0 { + return None; + } + Some(self.get_nlri()) + } +} + //--- NextHop in MP_REACH_NLRI ----------------------------------------------- impl NextHop { - pub fn check(parser: &mut Parser<[u8]>, afi: AFI, safi: SAFI) + // XXX remove? at least get rid of code repetition with fn parse below + pub fn _check(parser: &mut Parser, afi: AFI, safi: SAFI) -> Result<(), ParseError> { let len = parser.parse_u8()?; @@ -49,7 +455,11 @@ impl NextHop { { }, _ => { parser.advance(len.into())?; - warn!("Unimplemented NextHop AFI/SAFI {}/{}", afi, safi); + debug!("Unimplemented NextHop AFI/SAFI {}/{} len {}", + afi, safi, len); + return Err(ParseError::form_error( + "unimplemented AFI/SAFI in NextHop" + )) } } @@ -62,7 +472,9 @@ impl NextHop { let len = parser.parse_u8()?; let res = match (len, afi, safi) { (16, AFI::Ipv6, SAFI::Unicast | SAFI::MplsUnicast) => - NextHop::Ipv6(parse_ipv6addr(parser)?), + NextHop::Unicast(parse_ipv6addr(parser)?.into()), + (16, AFI::Ipv6, SAFI::Multicast) => + NextHop::Multicast(parse_ipv6addr(parser)?.into()), (32, AFI::Ipv6, SAFI::Unicast | SAFI::Multicast) => NextHop::Ipv6LL( parse_ipv6addr(parser)?, @@ -73,8 +485,10 @@ impl NextHop { RouteDistinguisher::parse(parser)?, parse_ipv6addr(parser)? ), - (4, AFI::Ipv4, SAFI::Unicast | SAFI::Multicast | SAFI::MplsUnicast ) => - NextHop::Ipv4(parse_ipv4addr(parser)?), + (4, AFI::Ipv4, SAFI::Unicast | SAFI::MplsUnicast ) => + NextHop::Unicast(parse_ipv4addr(parser)?.into()), + (4, AFI::Ipv4, SAFI::Multicast) => + NextHop::Multicast(parse_ipv4addr(parser)?.into()), (12, AFI::Ipv4, SAFI::MplsVpnUnicast) => NextHop::Ipv4MplsVpnUnicast( RouteDistinguisher::parse(parser)?, @@ -83,14 +497,27 @@ impl NextHop { // RouteTarget is always AFI/SAFI 1/132, so, IPv4, // but the Next Hop can be IPv6. (4, AFI::Ipv4, SAFI::RouteTarget) => - NextHop::Ipv4(parse_ipv4addr(parser)?), + NextHop::Unicast(parse_ipv4addr(parser)?.into()), (16, AFI::Ipv4, SAFI::RouteTarget) => - NextHop::Ipv6(parse_ipv6addr(parser)?), + NextHop::Unicast(parse_ipv6addr(parser)?.into()), (0, AFI::Ipv4, SAFI::FlowSpec) => NextHop::Empty, + (4, AFI::L2Vpn, SAFI::Evpn) => + NextHop::Evpn(parse_ipv4addr(parser)?.into()), + (16, AFI::L2Vpn, SAFI::Evpn) => + NextHop::Evpn(parse_ipv6addr(parser)?.into()), + + (4, AFI::Ipv6, SAFI::MplsUnicast) => + // IPv6 MPLS with IPv4 Nexthop (RFC4684) + NextHop::Unicast(parse_ipv4addr(parser)?.into()), + (16, AFI::Ipv4, SAFI::MplsUnicast) => + // IPv4 MPLS with IPv6 Nexthop (RFC4684) + NextHop::Unicast(parse_ipv6addr(parser)?.into()), _ => { + debug!("Unimplemented NextHop AFI/SAFI {}/{} len {}", + afi, safi, len); parser.advance(len.into())?; - NextHop::Unimplemented( afi, safi) + NextHop::Unimplemented(afi, safi) } }; Ok(res) @@ -111,11 +538,12 @@ impl NextHop { /// Path Identifier for BGP Multiple Paths (RFC7911). /// /// Optionally used in [`BasicNlri`]. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct PathId(u32); impl PathId { - pub fn check(parser: &mut Parser<[u8]>) + pub fn check(parser: &mut Parser) -> Result<(), ParseError> { parser.advance(4)?; @@ -127,32 +555,68 @@ impl PathId { { Ok(PathId(parser.parse_u32_be()?)) } + + pub fn from_u32(id: u32) -> Self { + PathId(id) + } + + pub fn to_raw(self) -> [u8; 4] { + self.0.to_be_bytes() + } +} + +impl fmt::Display for PathId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } } /// MPLS labels, part of [`MplsNlri`] and [`MplsVpnNlri`]. -#[derive(Copy, Clone, Eq, Debug, PartialEq)] -pub struct Labels { - octets: Octets +#[derive(Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct Labels { + octets: Octs } -impl Labels { - fn len(&self) -> usize { +impl PartialEq> for Labels +where Octs: AsRef<[u8]>, + Other: AsRef<[u8]> +{ + fn eq(&self, other: &Labels) -> bool { + self.octets.as_ref() == other.octets.as_ref() + } +} + +impl> Eq for Labels { } + + +impl> Labels { + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { self.octets.as_ref().len() } } +impl> AsRef<[u8]> for Labels { + fn as_ref(&self) -> &[u8] { + self.octets.as_ref() + } +} + impl Labels { // XXX check all this Label stuff again - fn _check<'a, R>(parser: &mut Parser<'a, R>) -> Result<(), ParseError> + fn skip<'a, R>(parser: &mut Parser<'a, R>) -> Result where R: Octets = Octs> { + let mut res = 0; let mut stop = false; let mut buf = [0u8; 3]; while !stop { //20bits label + 3bits rsvd + S bit parser.parse_buf(&mut buf)?; + res += 3; if buf[2] & 0x01 == 0x01 || // actual label with stop bit buf == [0x80, 0x00, 0x00] || // Compatibility value @@ -162,7 +626,7 @@ impl Labels { } } - Ok(()) + Ok(res) } // There are two cases for Labels: // - in an announcement, it describes one or more MPLS labels @@ -214,7 +678,7 @@ pub struct RouteDistinguisher { } impl RouteDistinguisher { - pub fn check(parser: &mut Parser<[u8]>) + pub fn check(parser: &mut Parser) -> Result<(), ParseError> { parser.advance(8)?; @@ -255,8 +719,8 @@ impl RouteDistinguisher { } } -impl std::fmt::Display for RouteDistinguisher { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { +impl fmt::Display for RouteDistinguisher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:#?}", self.bytes) } } @@ -273,30 +737,68 @@ pub enum RouteDistinguisherType { /// NLRI comprised of a [`Prefix`] and an optional [`PathId`]. /// /// The `BasicNlri` is extended in [`MplsNlri`] and [`MplsVpnNlri`]. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct BasicNlri { - prefix: Prefix, - path_id: Option, + pub prefix: Prefix, + pub path_id: Option, } /// NLRI comprised of a [`BasicNlri`] and MPLS `Labels`. -#[derive(Debug)] -pub struct MplsNlri { +#[derive(Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct MplsNlri { basic: BasicNlri, - labels: Labels, + labels: Labels, +} + +impl PartialEq> for MplsNlri +where Octs: AsRef<[u8]>, + Other: AsRef<[u8]> +{ + fn eq(&self, other: &MplsNlri) -> bool { + self.basic == other.basic && self.labels == other.labels + } } /// NLRI comprised of a [`BasicNlri`], MPLS `Labels` and a VPN /// `RouteDistinguisher`. -#[derive(Debug)] -pub struct MplsVpnNlri { +#[derive(Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct MplsVpnNlri { basic: BasicNlri, - labels: Labels, + labels: Labels, rd: RouteDistinguisher, } +impl MplsVpnNlri { + pub fn basic(&self) -> BasicNlri { + self.basic + } + + pub fn labels(&self) -> &Labels { + &self.labels + } + + pub fn rd(&self) -> RouteDistinguisher { + self.rd + } +} + +impl PartialEq> for MplsVpnNlri +where Octs: AsRef<[u8]>, + Other: AsRef<[u8]> +{ + fn eq(&self, other: &MplsVpnNlri) -> bool { + self.basic == other.basic + && self.labels == other.labels + && self.rd == other.rd + } +} + /// VPLS Information as defined in RFC4761. -#[derive(Debug)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct VplsNlri { rd: RouteDistinguisher, ve_id: u16, @@ -308,74 +810,376 @@ pub struct VplsNlri { /// NLRI containing a FlowSpec v1 specification. /// /// Also see [`crate::flowspec`]. -#[derive(Debug)] -pub struct FlowSpecNlri { +#[derive(Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct FlowSpecNlri { #[allow(dead_code)] - raw: Octets, + afi: AFI, + raw: Octs, +} + +impl PartialEq> for FlowSpecNlri +where Octs: AsRef<[u8]>, + Other: AsRef<[u8]> +{ + fn eq(&self, other: &FlowSpecNlri) -> bool { + self.raw.as_ref() == other.raw.as_ref() + } } /// NLRI containing a Route Target membership as defined in RFC4684. /// /// **TODO**: implement accessor methods for the contents of this NLRI. -#[derive(Debug)] -pub struct RouteTargetNlri { +#[derive(Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct RouteTargetNlri { + #[allow(dead_code)] + raw: Octs, +} + +impl PartialEq> for RouteTargetNlri +where Octs: AsRef<[u8]>, + Other: AsRef<[u8]> +{ + fn eq(&self, other: &RouteTargetNlri) -> bool { + self.raw.as_ref() == other.raw.as_ref() + } +} + +/// NLRI containing a EVPN NLRI as defined in RFC7432. +/// +/// **TODO**: implement accessor methods for the contents of this NLRI. +#[derive(Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct EvpnNlri { #[allow(dead_code)] - raw: Octets, + route_type: EvpnRouteType, + raw: Octs, +} + +impl EvpnNlri { + pub fn route_type(&self) -> EvpnRouteType { + self.route_type + } +} + +impl> EvpnNlri { + fn compose_len(&self) -> usize { + 1 + self.raw.as_ref().len() + } +} + +typeenum!( + EvpnRouteType, u8, + { + 1 => EthernetAutoDiscovery, + 2 => MacIpAdvertisement, + 3 => InclusiveMulticastEthernetTag, + 4 => EthernetSegment, + 5 => IpPrefix, + } +); + + +impl PartialEq> for EvpnNlri +where Octs: AsRef<[u8]>, + Other: AsRef<[u8]> +{ + fn eq(&self, other: &EvpnNlri) -> bool { + self.raw.as_ref() == other.raw.as_ref() + } +} + +/// Conventional and BGP-MP NLRI variants. +#[derive(Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub enum Nlri { // (AFIs , SAFIs): + Unicast(BasicNlri), // (v4/v6, unicast) + Multicast(BasicNlri), // (v4/v6, multicast) + Mpls(MplsNlri), // (v4/v6, mpls unicast) + MplsVpn(MplsVpnNlri), // (v4/v6, mpls vpn unicast) + Vpls(VplsNlri), // (l2vpn, vpls) + FlowSpec(FlowSpecNlri), // (v4/v6, flowspec) + RouteTarget(RouteTargetNlri), // (v4, route target) + Evpn(EvpnNlri), +} +// XXX some thoughts on if and how we need to redesign Nlri: +// +// Looking at which enum variants represent which afis/safis, currently only +// the Basic variant represents more than one SAFI, i.e. unicast and +// multicast. The other variants represent a single SAFI, and many of them do +// that for both v4 and v6. +// +// This means that when an Iterator is created, the user +// determines the SAFI by matching on the Nlri variant coming from next(), but +// for the BasicNlri it is uncertain whether we deal with unicast or +// multicast. +// +// It would be nice if the user could query the UpdateMessage to find out what +// AFI/SAFI is in the announcements/withdrawals. Currently, `fn nlris()` and +// `fn withdrawals()` return a struct with getters for both afi and safi, and +// a `fn iter()` to get an iterator over the actual NLRI. But because nlris() +// needs an overhaul to (correctly) return both conventional and MP_* NLRI, it +// might actually return an iterator over _two_ AFI/SAFI tuples: one +// conventional v4/unicast, and one MP tuple. +// +// The AFI could be derived from the Prefix in all cases but FlowSpec. For +// the Vpls and RouteTarget variants, there can only be one (valid) AFI, i.e. +// l2vpn and v4, respectively. +// +// We also need to take into account the creation of messages, i.e. adding +// NLRI for announcement/withdrawal to an instance of UpdateBuilder. The +// UpdateBuilder needs to be able to determine from the NLRI which AFI/SAFI it +// is dealing with. Currently a BasicNlri could be both unicast and multicast, +// but there is no way to know which one. That must be fixed. +// +// Questions: +// - are we ok with deriving the AFI from the Prefix, or do we want to +// explicitly embed the AFI in the variant? (NB that would add 4 variants for +// now, possibly more later). +// - should we simply add a Multicast variant (and perhaps rename Basic to +// Unicast)? +// - should we remove methods from the enum level to force a user to pattern +// match on the exact variant? e.g. calling Nlri.prefix() hides the exact +// variant from the user, possibly causing confusion. They might unknowingly +// storing MplsVpn prefixes thinking it was a 'Basic unicast' thing. + + +impl fmt::Display for Nlri { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Nlri::Unicast(b) => { + write!(f, "{}", b.prefix())?; + if let Some(path_id) = b.path_id() { + write!(f, " (path_id {})", path_id)? + } + } + Nlri::Multicast(b) => { + write!(f, "{} (mcast)", b.prefix())?; + if let Some(path_id) = b.path_id() { + write!(f, " (path_id {})", path_id)? + } + } + Nlri::Mpls(m) => { + write!(f, "MPLS-{}-{:?}", + m.basic.prefix(), m.labels.as_ref() + )? + } + Nlri::MplsVpn(m) => { + write!(f, "MPLSVPN-{}-{:?}-{:?}", + m.basic.prefix(), m.labels.as_ref(), m.rd + )? + } + Nlri::Vpls(n) => write!(f, "VPLS-{:?}", n.rd)?, + Nlri::FlowSpec(_) => write!(f, "FlowSpec-NLRI")?, + Nlri::RouteTarget(r) => { + write!(f, "RouteTarget-NLRI-{:?}", r.raw.as_ref())? + } + Nlri::Evpn(r) => { + write!(f, "Evpn-NLRI-{:?}", r.raw.as_ref())? + } + } + Ok(()) + } +} + +impl OctetsFrom> for Nlri + where Octs: OctetsFrom, +{ + type Error = Octs::Error; + + fn try_octets_from(source: Nlri) -> Result { + match source { + Nlri::Unicast(b) => Ok(Nlri::Unicast(b)), + Nlri::Multicast(b) => Ok(Nlri::Multicast(b)), + Nlri::FlowSpec(m) => Ok(Nlri::FlowSpec(FlowSpecNlri { + afi: m.afi, + raw: Octs::try_octets_from(m.raw)? + })), + _ => todo!() + } + } +} + +/* +impl<'a, Octs, SrcOcts: 'a + Octets> OctetsFrom<&'a Nlri> for Nlri +where + Octs: OctetsFrom, + //SrcOcts: Clone, + //Infallible: From +{ + type Error = Octs::Error; + //type Error = std::convert::Infallible; + + fn try_octets_from(source: &'a Nlri) -> Result { + match source { + Nlri::Unicast(b) => Ok(Nlri::Unicast(*b)), + Nlri::Multicast(b) => Ok(Nlri::Multicast(*b)), + //Nlri::FlowSpec(m) => Ok(Nlri::FlowSpec(FlowSpecNlri::try_octets_from(m)?)), + //Nlri::FlowSpec(_) => Ok(Nlri::try_octets_from(*source)?), + Nlri::FlowSpec(m) => Ok(Nlri::FlowSpec(FlowSpecNlri { + afi: m.afi, + raw: Octs::try_octets_from(m.raw.as_ref())? + })), + _ => todo!() + } + } +} +*/ + +impl<'a, SrcOcts: 'a + Octets> OctetsFrom<&'a Nlri> for Nlri> + where Vec: OctetsFrom, +{ + type Error = as OctetsFrom>::Error; + + fn try_octets_from(source: &'a Nlri) -> Result { + match source { + Nlri::Unicast(b) => Ok(Nlri::Unicast(*b)), + Nlri::Multicast(b) => Ok(Nlri::Multicast(*b)), + Nlri::Mpls(m) => Ok(Nlri::Mpls(MplsNlri { + basic: m.basic, + labels: Labels{ octets: m.labels.as_ref().to_vec() }, + })), + Nlri::MplsVpn(m) => Ok(Nlri::MplsVpn(MplsVpnNlri { + basic: m.basic, + labels: Labels{ octets: m.labels.as_ref().to_vec() }, + rd: m.rd + })), + Nlri::Vpls(v) => Ok(Nlri::Vpls(*v)), + Nlri::FlowSpec(m) => Ok(Nlri::FlowSpec(FlowSpecNlri{ + afi: m.afi, + raw: m.raw.as_ref().to_vec() + })), + Nlri::RouteTarget(r) => Ok(Nlri::RouteTarget(RouteTargetNlri{ + raw: r.raw.as_ref().to_vec(), + })), + Nlri::Evpn(e) => Ok(Nlri::Evpn( EvpnNlri{ + route_type: e.route_type, + raw: e.raw.as_ref().to_vec(), + })), + } + } +} + +impl OctetsFrom> for FlowSpecNlri + where Octs: OctetsFrom, +{ + type Error = Octs::Error; + + fn try_octets_from( + source: FlowSpecNlri + ) -> Result { + Ok(FlowSpecNlri { afi: source.afi, raw: Octs::try_octets_from(source.raw)? } ) + } +} + +/* +impl<'a, Octs, SrcOcts: 'a + Octets> OctetsFrom<&'a FlowSpecNlri> for FlowSpecNlri + where Octs: OctetsFrom, +{ + type Error = Octs::Error; + + fn try_octets_from( + source: &'a FlowSpecNlri + ) -> Result { + Ok(FlowSpecNlri { afi: source.afi, raw: Octs::try_octets_from(source.raw)? } ) + } +} +*/ + +impl<'a, SrcOcts: 'a + Octets> OctetsFrom<&'a FlowSpecNlri> for FlowSpecNlri> + where Vec: OctetsFrom, + Vec: From +{ + type Error = as OctetsFrom>::Error; + + fn try_octets_from( + source: &'a FlowSpecNlri + ) -> Result { + Ok(FlowSpecNlri { afi: source.afi, raw: source.raw.as_ref().to_vec() } ) + } +} + +impl PartialEq> for Nlri +where Octs: AsRef<[u8]>, + Other: AsRef<[u8]> +{ + fn eq(&self, other: &Nlri) -> bool { + match (self, other) { + (Self::Unicast(s), Nlri::Unicast(o)) | + (Self::Multicast(s), Nlri::Multicast(o)) => s == o, + (Self::Mpls(s), Nlri::Mpls(o)) => s == o, + (Self::MplsVpn(s), Nlri::MplsVpn(o)) => s == o, + (Self::Vpls(s), Nlri::Vpls(o)) => s == o, + (Self::FlowSpec(s), Nlri::FlowSpec(o)) => s == o, + (Self::RouteTarget(s), Nlri::RouteTarget(o)) => s == o, + _ => false + } + } } -/// Conventional and BGP-MP NLRI variants. -#[derive(Debug)] -pub enum Nlri { - Basic(BasicNlri), - Mpls(MplsNlri), - MplsVpn(MplsVpnNlri), - Vpls(VplsNlri), - FlowSpec(FlowSpecNlri), - RouteTarget(RouteTargetNlri), -} - -impl Display for Nlri { - fn fmt(&self, f: &mut Formatter) -> FmtResult { +impl> Eq for Nlri { } + +impl Nlri { + /// Returns true if this NLRI contains a Path Id. + pub fn is_addpath(&self) -> bool { match self { - Nlri::Vpls(n) => write!(f, "VPLS-{:?}", n.rd), - _ => write!(f, "{}", self.prefix().unwrap()) + Self::Unicast(b) | Self::Multicast(b) => b.is_addpath(), + Self::Mpls(m) => m.basic.is_addpath(), + Self::MplsVpn(m) => m.basic.is_addpath(), + Self::Vpls(_) | Self::FlowSpec(_) | Self::RouteTarget(_) => false, + Self::Evpn(_) => false, } } } -impl Nlri { - fn basic(&self) -> Option { + +impl Nlri { + /// Returns the tuple of (AFI, SAFI) for this Nlri. + pub fn afi_safi(&self) -> (AFI, SAFI) { match self { - Nlri::Basic(n) => Some(*n), - Nlri::Mpls(n) => Some(n.basic), - Nlri::MplsVpn(n) => Some(n.basic), - Nlri::Vpls(_) => None, - Nlri::FlowSpec(_) => None, - Nlri::RouteTarget(_) => None, + Self::Unicast(b) => { + if b.is_v4() { + (AFI::Ipv4, SAFI::Unicast) + } else { + (AFI::Ipv6, SAFI::Unicast) + } + } + Self::Multicast(b) => { + if b.is_v4() { + (AFI::Ipv4, SAFI::Multicast) + } else { + (AFI::Ipv6, SAFI::Multicast) + } + } + Self::Mpls(n) => { + if n.basic.is_v4() { + (AFI::Ipv4, SAFI::MplsUnicast) + } else { + (AFI::Ipv6, SAFI::MplsUnicast) + } + } + Self::MplsVpn(n) => { + if n.basic.is_v4() { + (AFI::Ipv4, SAFI::MplsVpnUnicast) + } else { + (AFI::Ipv6, SAFI::MplsVpnUnicast) + } + } + Self::Vpls(_) => (AFI::L2Vpn, SAFI::Vpls), + Self::FlowSpec(n) => (n.afi, SAFI::FlowSpec) , + Self::RouteTarget(_) => (AFI::Ipv4, SAFI::RouteTarget), + Self::Evpn(_) => (AFI::L2Vpn, SAFI::Evpn) } } - /// Returns the [`Prefix`] for this NLRI, if any. - /// - /// Since the NLRI in Multiprotocol BGP can contain many different types - /// of information depending on the AFI/SAFI, there might be no prefix at - /// all. - pub fn prefix(&self) -> Option { - self.basic().map(|b| b.prefix) - } +} - /// Returns the PathId for AddPath enabled prefixes. - pub fn path_id(&self) -> Option { - if let Some(b) = self.basic() { - b.path_id - } else { - None - } - } +impl> Nlri { /// Returns the MPLS [`Labels`], if any. /// /// Applicable to MPLS and MPLS-VPN NLRI. - pub fn labels(&self) -> Option<&Labels> { + pub fn labels(&self) -> Option<&Labels> { match &self { Nlri::Mpls(n) => Some(&n.labels), Nlri::MplsVpn(n) => Some(&n.labels), @@ -428,6 +1232,40 @@ impl Nlri { _ => None } } + + //--- Compose methods + + pub fn compose_len(&self) -> usize { + match self { + Nlri::Unicast(b) | Nlri::Multicast(b) => b.compose_len(), + Nlri::Mpls(m) => m.compose_len(), + Nlri::MplsVpn(m) => m.compose_len(), + Nlri::Vpls(v) => v.compose_len(), + Nlri::FlowSpec(f) => f.compose_len(), + Nlri::RouteTarget(r) => r.compose_len(), + Nlri::Evpn(e) => e.compose_len(), + } + } +} + + +// XXX While Nlri<()> might make more sense, it clashes with trait bounds +// like Vec: OctetsFrom elsewhere, as, From<()> is not implemented for +// Vec. Similarly, () is not AsRef<[u8]>. +impl Nlri<&[u8]> { + /// Creates a `Nlri::Unicast` for `prefix`. + /// + /// This returns the error thrown by `Prefix::from_str` if `prefix` does + /// not represent a valid IPv6 or IPv4 prefix. + pub fn unicast_from_str(prefix: &str) + -> Result, ::Err> + { + Ok( + Nlri::Unicast(BasicNlri::new( + Prefix::from_str(prefix)? + )) + ) + } } // Calculate the number of bytes we need to parse for a certain prefix length @@ -440,8 +1278,8 @@ fn prefix_bits_to_bytes(bits: u8) -> usize { } } -fn check_prefix( - parser: &mut Parser<[u8]>, +fn check_prefix( + parser: &mut Parser, prefix_bits: u8, afi: AFI ) -> Result<(), ParseError> { @@ -462,14 +1300,20 @@ fn check_prefix( }, (AFI::Ipv6, _) => { parser.advance(prefix_bytes)?; }, (_, _) => { - panic!("unimplemented") + return Err( + ParseError::form_error("unknown prefix format") + ) } }; Ok(()) } -fn parse_prefix(parser: &mut Parser<'_, R>, prefix_bits: u8, afi: AFI) +fn parse_prefix_for_len( + parser: &mut Parser<'_, R>, + prefix_bits: u8, + afi: AFI +) -> Result { let prefix_bytes = prefix_bits_to_bytes(prefix_bits); @@ -478,49 +1322,86 @@ fn parse_prefix(parser: &mut Parser<'_, R>, prefix_bits: u8, afi: AFI Prefix::new_v4(0.into(), 0)? }, (AFI::Ipv4, _b @ 5..) => { - return Err(ParseError::form_error("illegal byte size for IPv4 NLRI")) + return Err( + ParseError::form_error("illegal byte size for IPv4 NLRI") + ) }, (AFI::Ipv4, _) => { let mut b = [0u8; 4]; b[..prefix_bytes].copy_from_slice(parser.peek(prefix_bytes)?); parser.advance(prefix_bytes)?; - Prefix::new(IpAddr::from(b), prefix_bits).map_err(|_e| - ParseError::form_error("prefix parsing failed") + Prefix::new(IpAddr::from(b), prefix_bits).map_err(|e| + ParseError::form_error(e.static_description()) )? } (AFI::Ipv6, 0) => { Prefix::new_v6(0.into(), 0)? }, (AFI::Ipv6, _b @ 17..) => { - return Err(ParseError::form_error("illegal byte size for IPv6 NLRI")) + return Err( + ParseError::form_error("illegal byte size for IPv6 NLRI") + ) }, (AFI::Ipv6, _) => { let mut b = [0u8; 16]; b[..prefix_bytes].copy_from_slice(parser.peek(prefix_bytes)?); parser.advance(prefix_bytes)?; - Prefix::new(IpAddr::from(b), prefix_bits).map_err(|_e| - ParseError::form_error("prefix parsing failed") + Prefix::new(IpAddr::from(b), prefix_bits).map_err(|e| + ParseError::form_error(e.static_description()) )? }, (_, _) => { - panic!("unimplemented") + return Err( + ParseError::form_error("unknown prefix format") + ) } }; Ok(prefix) } +fn skip_prefix(parser: &mut Parser<'_, R>) + -> Result<(), ParseError> +{ + let prefix_bits = parser.parse_u8()?; + let prefix_bytes = prefix_bits_to_bytes(prefix_bits); + Ok(parser.advance(prefix_bytes)?) +} + +fn skip_prefix_for_len(parser: &mut Parser<'_, R>, prefix_bits: u8) + -> Result<(), ParseError> +{ + let prefix_bytes = prefix_bits_to_bytes(prefix_bits); + Ok(parser.advance(prefix_bytes)?) +} + +fn skip_prefix_addpath(parser: &mut Parser<'_, R>) + -> Result<(), ParseError> +{ + let prefix_bits = parser.parse_u8()?; + parser.advance(4)?; + let prefix_bytes = prefix_bits_to_bytes(prefix_bits); + Ok(parser.advance(prefix_bytes)?) +} + +fn parse_prefix(parser: &mut Parser<'_, R>, afi: AFI) + -> Result +{ + let prefix_bits = parser.parse_u8()?; + parse_prefix_for_len(parser, prefix_bits, afi) +} + impl BasicNlri { - pub fn check( - parser: &mut Parser<[u8]>, + pub fn check( + parser: &mut Parser, config: SessionConfig, - afi: AFI + afisafi: AfiSafi, ) -> Result<(), ParseError> { - if config.add_path == AddPath::Enabled { + if config.rx_addpath(afisafi) { PathId::check(parser)? } let prefix_bits = parser.parse_u8()?; - check_prefix(parser, prefix_bits, afi)?; + check_prefix(parser, prefix_bits, afisafi.afi())?; Ok(()) } @@ -528,14 +1409,20 @@ impl BasicNlri { pub fn parse( parser: &mut Parser<'_, R>, config: SessionConfig, - afi: AFI + afisafi: AfiSafi, ) -> Result { - let path_id = match config.add_path { - AddPath::Enabled => Some(PathId::parse(parser)?), - _ => None + let path_id = if config.rx_addpath(afisafi) { + Some(PathId::parse(parser)?) + } else { + None }; + let prefix_bits = parser.parse_u8()?; - let prefix = parse_prefix(parser, prefix_bits, afi)?; + let prefix = parse_prefix_for_len( + parser, + prefix_bits, + afisafi.afi() + )?; Ok( BasicNlri { @@ -544,16 +1431,93 @@ impl BasicNlri { } ) } + + pub fn new(prefix: Prefix) -> BasicNlri { + BasicNlri { prefix, path_id: None } + } + + pub fn with_path_id(prefix: Prefix, path_id: PathId) -> BasicNlri { + BasicNlri { prefix, path_id: Some(path_id) } + } + + pub fn prefix(&self) -> Prefix { + self.prefix + } + + /// Returns the PathId for AddPath enabled prefixes, if some. + pub fn path_id(&self) -> Option { + self.path_id + } + + /// Returns true if this NLRI contains a Path Id. + pub fn is_addpath(&self) -> bool { + self.path_id.is_some() + } + + pub(crate) fn compose_len(&self) -> usize { + let mut res = if self.path_id.is_some() { + 4 + } else { + 0 + }; + // 1 byte for the length itself + res += 1 + prefix_bits_to_bytes(self.prefix.len()); + res + } + + pub(crate) fn compose(&self, target: &mut Target) + -> Result<(), Target::AppendError> { + let len = self.prefix.len(); + if let Some(path_id) = self.path_id { + target.append_slice(&path_id.to_raw())?; + } + + target.append_slice(&[len])?; + let prefix_bytes = prefix_bits_to_bytes(len); + + match self.prefix.addr() { + IpAddr::V4(a) => { + target.append_slice(&a.octets()[..prefix_bytes])?; + } + IpAddr::V6(a) => { + target.append_slice(&a.octets()[..prefix_bytes])?; + } + } + Ok(()) + } + + pub fn is_v4(&self) -> bool { + self.prefix.is_v4() + } +} + +impl From for BasicNlri { + fn from(prefix: Prefix) -> BasicNlri { + BasicNlri { prefix, path_id: None } + } +} + +impl From<(Prefix, PathId)> for BasicNlri { + fn from(tuple: (Prefix, PathId)) -> BasicNlri { + BasicNlri { prefix: tuple.0, path_id: Some(tuple.1) } + } } -impl MplsVpnNlri<()> { +impl From<(Prefix, Option)> for BasicNlri { + fn from(tuple: (Prefix, Option)) -> BasicNlri { + BasicNlri { prefix: tuple.0, path_id: tuple.1 } + } +} + + +impl MplsVpnNlri { pub fn check( - parser: &mut Parser<[u8]>, + parser: &mut Parser, config: SessionConfig, - afi: AFI + afisafi: AfiSafi, ) -> Result<(), ParseError> { - if config.add_path == AddPath::Enabled { + if config.rx_addpath(afisafi) { parser.advance(4)?; } @@ -573,7 +1537,7 @@ impl MplsVpnNlri<()> { RouteDistinguisher::check(parser)?; prefix_bits -= 8 * 8_u8; - check_prefix(parser, prefix_bits, afi)?; + check_prefix(parser, prefix_bits, afisafi.afi())?; Ok(()) } @@ -583,46 +1547,62 @@ impl MplsVpnNlri { pub fn parse<'a, R>( parser: &mut Parser<'a, R>, config: SessionConfig, - afi: AFI) -> Result + afisafi: AfiSafi, + ) -> Result where R: Octets = Octs> { - let path_id = match config.add_path { - AddPath::Enabled => Some(PathId::parse(parser)?), - _ => None + let path_id = if config.rx_addpath(afisafi) { + Some(PathId::parse(parser)?) + } else { + None }; let mut prefix_bits = parser.parse_u8()?; let labels = Labels::parse(parser)?; - // Check whether we can safely subtract the labels length from the - // prefix size. If there is an unexpected path id, we might silently - // subtract too much, because there is no 'subtract with overflow' - // warning when built in release mode. - if 8 * labels.len() as u8 > prefix_bits { + // Check whether we can safely subtract both the labels length and the + // route Distinguisher length from the prefix size. If there is an + // unexpected path id, we might silently subtract too much, because + // there is no 'subtract with overflow' warning when built in release + // mode. + + if + u8::try_from(8 * (8 + labels.len())) + .map_err(|_| ParseError::form_error( + "MplsVpnNlri labels/rd too long" + ))? > prefix_bits + { return Err(ParseError::ShortInput); } prefix_bits -= 8 * labels.len() as u8; let rd = RouteDistinguisher::parse(parser)?; - prefix_bits -= 8*std::mem::size_of::() as u8; - let prefix = parse_prefix(parser, prefix_bits, afi)?; + prefix_bits -= 8*8; + + let prefix = parse_prefix_for_len(parser, prefix_bits, afisafi.afi())?; let basic = BasicNlri{ prefix, path_id }; Ok(MplsVpnNlri{ basic, labels, rd }) } } -impl MplsNlri<()> { +impl> MplsVpnNlri { + fn compose_len(&self) -> usize { + self.basic.compose_len() + self.labels.len() + self.rd.bytes.len() + } +} + +impl MplsNlri { pub fn check( - parser: &mut Parser<[u8]>, + parser: &mut Parser, config: SessionConfig, - afi: AFI) -> Result<(), ParseError> - { - if config.add_path == AddPath::Enabled { - parser.advance(4)?; + afisafi: AfiSafi, + ) -> Result<(), ParseError> { + if config.rx_addpath(afisafi) { + PathId::check(parser)? } let mut prefix_bits = parser.parse_u8()?; @@ -637,24 +1617,85 @@ impl MplsNlri<()> { } prefix_bits -= 8 * labels.len() as u8; - check_prefix(parser, prefix_bits, afi)?; + check_prefix(parser, prefix_bits, afisafi.afi())?; Ok(()) } + + pub fn basic(&self) -> BasicNlri { + self.basic + } } impl MplsNlri { pub fn parse<'a, R>( parser: &mut Parser<'a, R>, config: SessionConfig, - afi: AFI) -> Result + afisafi: AfiSafi, + ) -> Result where R: Octets = Octs> { - let path_id = match config.add_path { - AddPath::Enabled => Some(PathId::parse(parser)?), - _ => None + let path_id = if config.rx_addpath(afisafi) { + Some(PathId::parse(parser)?) + } else { + None }; + let (prefix, labels) = Self::parse_labels_and_prefix(parser, afisafi)?; + let basic = BasicNlri { prefix, path_id }; + + Ok( + MplsNlri { + basic, + labels, + } + ) + } + + pub fn parse_no_addpath<'a, R>( + parser: &mut Parser<'a, R>, + afisafi: AfiSafi, + ) -> Result + where + R: Octets = Octs> + { + let (prefix, labels) = Self::parse_labels_and_prefix(parser, afisafi)?; + let basic = BasicNlri::new(prefix); + + Ok( + MplsNlri { + basic, + labels, + } + ) + } + + pub fn parse_addpath<'a, R>( + parser: &mut Parser<'a, R>, + afisafi: AfiSafi, + ) -> Result + where + R: Octets = Octs> + { + let path_id = PathId::parse(parser)?; + let (prefix, labels) = Self::parse_labels_and_prefix(parser, afisafi)?; + let basic = BasicNlri::with_path_id(prefix, path_id); + + Ok( + MplsNlri { + basic, + labels, + } + ) + } + + fn parse_labels_and_prefix<'a, R>( + parser: &mut Parser<'a, R>, + afisafi: AfiSafi, + ) -> Result<(Prefix,Labels), ParseError> + where + R: Octets = Octs> + { let mut prefix_bits = parser.parse_u8()?; let labels = Labels::::parse(parser)?; @@ -662,25 +1703,66 @@ impl MplsNlri { // prefix size. If there is an unexpected path id, we might silently // subtract too much, because there is no 'subtract with overflow' // warning when built in release mode. - if 8 * labels.len() as u8 > prefix_bits { + + if u8::try_from(8 * labels.len()) + .map_err(|_| ParseError::form_error("MplsNlri labels too long"))? + > prefix_bits { return Err(ParseError::ShortInput); } prefix_bits -= 8 * labels.len() as u8; - let prefix = parse_prefix(parser, prefix_bits, afi)?; - let basic = BasicNlri { prefix, path_id }; - Ok( - MplsNlri { - basic, - labels, - } - ) + let prefix = parse_prefix_for_len( + parser, + prefix_bits, + afisafi.afi() + )?; + + Ok((prefix, labels)) + } + + fn skip_labels_and_prefix<'a, R>( + parser: &mut Parser<'a, R>, + ) -> Result<(), ParseError> + where + R: Octets = Octs> + { + let mut prefix_bits = parser.parse_u8()?; + let labels_len = Labels::::skip(parser)?; + + // Check whether we can safely subtract the labels length from the + // prefix size. If there is an unexpected path id, we might silently + // subtract too much, because there is no 'subtract with overflow' + // warning when built in release mode. + + if u8::try_from(8 * labels_len) + .map_err(|_| ParseError::form_error("MplsNlri labels too long"))? + > prefix_bits { + return Err(ParseError::ShortInput); + } + + prefix_bits -= 8 * labels_len as u8; + + skip_prefix_for_len(parser, prefix_bits)?; + + Ok(()) + } + + + +} + +impl> MplsNlri { + fn compose_len(&self) -> usize { + self.basic.compose_len() + self.labels.len() + } + pub fn labels(&self) -> &Labels { + &self.labels } } impl VplsNlri { - pub fn check(parser: &mut Parser<[u8]>) + pub fn check(parser: &mut Parser) -> Result<(), ParseError> { parser.advance(2)?; // length, u16 @@ -712,10 +1794,14 @@ impl VplsNlri { } ) } + + fn compose_len(&self) -> usize { + 8 + 2 + 2 + 2 + 4 + } } -impl FlowSpecNlri<()> { - pub fn check(parser: &mut Parser<[u8]>) +impl FlowSpecNlri { + pub fn check(parser: &mut Parser, afi: AFI) -> Result<(), ParseError> { let len1 = parser.parse_u8()?; @@ -725,23 +1811,30 @@ impl FlowSpecNlri<()> { } else { len1 as u16 }; - let pp = parser.parse_parser(len.into())?; - while pp.remaining() > 0 { - // TODO implement Component::check() - Component::parse(parser)?; - } + let mut pp = parser.parse_parser(len.into())?; + match afi { + AFI::Ipv4 => { + while pp.remaining() > 0 { + Component::parse(&mut pp)?; + } + Ok(()) + } + AFI::Ipv6 => { + debug!("FlowSpec v6 not implemented yet"); + Ok(()) + } + _ => Err(ParseError::form_error("illegal AFI for FlowSpec")) - Ok(()) + } } } impl FlowSpecNlri { - pub fn parse<'a, R>(parser: &mut Parser<'a, R>) + pub fn parse<'a, R>(parser: &mut Parser<'a, R>, afi: AFI) -> Result where R: Octets = Octs> { - let pos = parser.pos(); let len1 = parser.parse_u8()?; let len: u16 = if len1 >= 0xf0 { let len2 = parser.parse_u8()? as u16; @@ -749,25 +1842,71 @@ impl FlowSpecNlri { } else { len1 as u16 }; - while parser.pos() < pos + len as usize { - Component::parse(parser)?; + let pos = parser.pos(); + + if usize::from(len) > parser.remaining() { + return Err(ParseError::form_error( + "invalid length of FlowSpec NLRI" + )); } - - let raw_len = parser.pos() - pos; + match afi { + AFI::Ipv4 => { + while parser.pos() < pos + len as usize { + Component::parse(parser)?; + } + } + AFI::Ipv6 => { + debug!("FlowSpec v6 not implemented yet, \ + returning unchecked NLRI" + ); + } + _ => { + return Err(ParseError::form_error("illegal AFI for FlowSpec")) + } + } + parser.seek(pos)?; - let raw = parser.parse_octets(raw_len)?; + let raw = parser.parse_octets(len as usize)?; Ok( FlowSpecNlri { + afi, raw } ) } + + pub(crate) fn compose(&self, target: &mut Target) + -> Result<(), Target::AppendError> { + let len = self.raw.as_ref().len(); + if len >= 240 { + todo!(); //FIXME properly encode into 0xfnnn for 239 < len < 4095 + /* + target.append_slice( + &u16::try_from(self.compose_len()).unwrap_or(u16::MAX) + .to_be_bytes() + )?; + */ + } else { + // We know len < 255 so we can safely unwrap. + target.append_slice(&[u8::try_from(len).unwrap()])?; + } + target.append_slice(self.raw.as_ref()) + } +} + + +impl> FlowSpecNlri { + pub(crate) fn compose_len(&self) -> usize { + let value_len = self.raw.as_ref().len(); + let len_len = if value_len >= 240 { 2 } else { 1 } ; + len_len + value_len + } } -impl RouteTargetNlri<()> { - pub fn check(parser: &mut Parser<[u8]>) +impl RouteTargetNlri { + pub fn check(parser: &mut Parser) -> Result<(), ParseError> { let prefix_bits = parser.parse_u8()?; @@ -795,3 +1934,67 @@ impl RouteTargetNlri { ) } } + +impl> RouteTargetNlri { + fn compose_len(&self) -> usize { + self.raw.as_ref().len() + } +} + +impl EvpnNlri { + pub fn parse<'a, R>(parser: &mut Parser<'a, R>) + -> Result + where + R: Octets = Octs> + { + let route_type = parser.parse_u8()?.into(); + let route_len = parser.parse_u8()?; + let raw = parser.parse_octets(route_len.into())?; + + Ok( + EvpnNlri { + route_type, + raw + } + ) + } +} + +//------------ Tests ---------------------------------------------------------- + +#[cfg(test)] +mod tests { + + use super::*; + use std::str::FromStr; + + #[test] + fn compose_len() { + fn test(p: (&str, Option), expected_len: usize) { + let prefix = Prefix::from_str(p.0).unwrap(); + let b: BasicNlri = (prefix, p.1).into(); + assert_eq!(b.compose_len(), expected_len); + } + + [ + (("10.0.0.0/24", None), 4 ), + (("10.0.0.0/23", None), 4 ), + (("10.0.0.0/25", None), 5 ), + (("0.0.0.0/0", None), 1 ), + + (("10.0.0.0/24", Some(PathId(1))), 4 + 4 ), + (("10.0.0.0/23", Some(PathId(1))), 4 + 4 ), + (("10.0.0.0/25", Some(PathId(1))), 5 + 4 ), + (("0.0.0.0/0", Some(PathId(1))), 1 + 4 ), + + (("2001:db8::/32", None), 5 ), + (("::/0", None), 1 ), + + (("2001:db8::/32", Some(PathId(1))), 5 + 4 ), + (("::/0", Some(PathId(1))), 1 + 4 ), + + ].iter().for_each(|e| test(e.0, e.1)); + + } +} + diff --git a/src/bgp/message/open.rs b/src/bgp/message/open.rs index 1356b396..55b7b658 100644 --- a/src/bgp/message/open.rs +++ b/src/bgp/message/open.rs @@ -1,6 +1,6 @@ use crate::bgp::message::{Header, MsgType}; use crate::asn::Asn; -use crate::bgp::types::{AFI, SAFI}; +use crate::bgp::types::{AFI, SAFI, AfiSafi, AddpathFamDir, AddpathDirection}; use crate::typeenum; // from util::macros use crate::util::parser::ParseError; use log::warn; @@ -11,6 +11,8 @@ use serde::{Serialize, Deserialize}; const COFF: usize = 19; // XXX replace this with .skip()'s? +const AS_TRANS: u16 = 23456; + /// BGP OPEN message, variant of the [`Message`] enum. #[derive(Clone, Debug, Eq, PartialEq)] pub struct OpenMessage { @@ -178,6 +180,75 @@ impl OpenMessage { ) } + /* + pub fn addpath_families(&self) -> impl Iterator + '_ { + self.capabilities().filter(|c| + c.typ() == CapabilityType::AddPath + ).map(|c| { + &c.value().chunks(4) + .map(|c| { + let mut parser = Parser::from_ref(c); + + let afi = parser.parse_u16_be().unwrap().into(); + let safi = parser.parse_u8().unwrap().into(); + let dir = AddpathDirection::try_from(parser.parse_u8().unwrap()); + (AfiSafi::try_from((afi, safi)).unwrap(), dir.unwrap()) + }) + }).flatten().into_iter() + } + */ + + pub fn addpath_families_vec(&self) + -> Result, ParseError> + { + let mut res = vec![]; + for c in self.capabilities().filter(|c| + c.typ() == CapabilityType::AddPath + ) { + for c in c.value().chunks(4) { + let mut parser = Parser::from_ref(c); + + let afi = parser.parse_u16_be()?.into(); + let safi = parser.parse_u8()?.into(); + let dir = AddpathDirection::try_from(parser.parse_u8()?) + .map_err(|_| ParseError::Unsupported)?; + res.push(( + AfiSafi::try_from((afi, safi)) + .map_err(|_| ParseError::Unsupported)?, + dir + )); + } + } + Ok(res) + } + + /// Merge exchanged ADDPATH capabilities from our perspective. + /// + /// Note that depending on the directions (Send, Receive, or SendReceive) + /// for the address families, we might up with a unidirectional ADDPATH + /// enabled session. For example, if we announce only Receive, and the + /// other side announces SendReceive, we must not send out prefixes with + /// Path IDs. + /// + /// Calling `msg1.addpath_intersection(msg2)` might give a different + /// result than `msg2.addpath_intersection(msg1)`. + pub fn addpath_intersection(&self, other: &OpenMessage) + -> Vec + { + let (Ok(mine), Ok(other)) = + (self.addpath_families_vec(), other.addpath_families_vec()) else { + return vec![]; + }; + + mine.iter().filter_map(|(my_fam, my_dir)| { + other.iter().find(|(f, _d)| { + my_fam == f + }).and_then(|(_f, other_dir)| { + my_dir.merge(*other_dir) + }).map(|m| AddpathFamDir::new(*my_fam, m)) + }).collect::>() + } + /// Returns an iterator over `(AFI, SAFI)` tuples listed as /// MultiProtocol Capabilities in the Optional Parameters of this message. pub fn multiprotocol_ids(&self) -> impl Iterator + '_ { @@ -204,9 +275,9 @@ impl OpenMessage { } } -impl OpenMessage<()> { - fn check(octets: &[u8]) -> Result<(), ParseError> { - let mut parser = Parser::from_ref(octets); +impl OpenMessage { + fn check(octets: Octs) -> Result<(), ParseError> { + let mut parser = Parser::from_ref(&octets); Header::check(&mut parser)?; // jump over version, 2-octet ASN, Hold timer and BGP ID parser.advance(1 + 2 + 2 + 4)?; @@ -295,8 +366,8 @@ impl Parameter { } } -impl Parameter<()> { - fn check(parser: &mut Parser<[u8]>) -> Result<(), ParseError> { +impl Parameter { + fn check(parser: &mut Parser) -> Result<(), ParseError> { let typ = parser.parse_u8()?; let len = parser.parse_u8()? as usize; if typ == 2 { @@ -315,8 +386,8 @@ impl Parameter<()> { } } -impl Capability<()> { - fn check(parser: &mut Parser<[u8]>) -> Result<(), ParseError> { +impl Capability { + fn check(parser: &mut Parser) -> Result<(), ParseError> { let _typ = parser.parse_u8()?; let len = parser.parse_u8()? as usize; parser.advance(len)?; @@ -517,6 +588,10 @@ impl Capability { Capability { octets } } + pub fn new(octets: Octs) -> Self { + Capability { octets } + } + /// Returns the [`CapabilityType`] of this capability. pub fn typ(&self) -> CapabilityType { self.octets.as_ref()[0].into() @@ -683,6 +758,7 @@ typeenum!( pub struct OpenBuilder { target: Target, capabilities: Vec>>, + addpath_families: Vec<(AfiSafi, AddpathDirection)>, } use core::convert::Infallible; @@ -699,11 +775,19 @@ where // BGP version let _ =target.append_slice(&[4]); + + // Prepare space for the mandatory ASN, holdtime, bgp_id. let _ =target.append_slice(&[0; 8]); // opt param len is set in finish() - Ok(OpenBuilder { target, capabilities: Vec::>::new() }) + Ok( + OpenBuilder { + target, + capabilities: Vec::>::new(), + addpath_families: Vec::new(), + } + ) } // TODO fn header_mut(&self) -> &mut Header { @@ -714,8 +798,8 @@ where impl> OpenBuilder { pub fn set_asn(&mut self, asn: Asn) { - // FIXME properly check / convert for 4-octet ASNs - let asn = asn.into_u32() as u16; + // XXX should we call set_four_octet_asn from here as well? + let asn = u16::try_from(asn.into_u32()).unwrap_or(AS_TRANS); self.target.as_mut()[COFF+1..=COFF+2] .copy_from_slice(&asn.to_be_bytes()); } @@ -757,12 +841,36 @@ impl> OpenBuilder { self.add_capability(Capability::>::for_slice(s.to_vec())) } + pub fn add_addpath(&mut self, afisafi: AfiSafi, dir: AddpathDirection) { + self.addpath_families.push((afisafi, dir)); + } } impl> OpenBuilder where Infallible: From<::AppendError> { pub fn finish(mut self) -> Target { + + if !self.addpath_families.is_empty() { + let addpath_cap_len = 4 * self.addpath_families.len(); + let mut addpath_cap = Vec::::with_capacity(addpath_cap_len); + + addpath_cap.extend_from_slice( + // XXX throw a ComposeError or equivalent after refactoring the + // builder. + &[69, + addpath_cap_len.try_into().unwrap_or(u8::MAX) + ] + ); + + for (fam, dir) in self.addpath_families.iter() { + let (afi, safi) = fam.split(); + addpath_cap.extend_from_slice(&u16::from(afi).to_be_bytes()); + addpath_cap.extend_from_slice(&[safi.into(), *dir as u8]); + } + self.add_capability(Capability::new(addpath_cap)); + } + let mut cap_len = 0u8; for c in &self.capabilities { cap_len += c.as_ref().len() as u8; @@ -970,11 +1078,60 @@ mod tests { } + #[test] + fn multiple_addpath_single_cap() { + // BGP OPEN with 1 optional parameter, Capability ADDPATH + let buf = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 41, 0x01, 0x04, 0x5b, 0xa0, 0x00, 0xb4, + 0x0a, 0x00, 0x00, 0x03, + 0x0c, + 0x02, 0x0a, 69, 0x08, + 0x00, 0x01, 0x01, 0x03, + 0x00, 0x02, 0x01, 0x03, + ]; + + let open = OpenMessage::from_octets(buf).unwrap(); + + assert_eq!(open.capabilities().count(), 1); + assert!(open.addpath_families_vec().unwrap().iter().eq( + &[(AfiSafi::Ipv4Unicast, AddpathDirection::SendReceive), + (AfiSafi::Ipv6Unicast, AddpathDirection::SendReceive)] + ) + ); + } + + #[test] + fn multiple_addpath_multi_cap() { + // BGP OPEN with 2 optional parameters, all Capability ADDPATH + // XXX note that this isn't actually allowed, not sure if it occurs in + // the wild. + let buf = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 45, 0x01, 0x04, 0x5b, 0xa0, 0x00, 0xb4, + 0x0a, 0x00, 0x00, 0x03, + 0x10, + 0x02, 0x06, 69, 0x04, 0x00, 0x01, 0x01, 0x03, + 0x02, 0x06, 69, 0x04, 0x00, 0x02, 0x01, 0x03, + ]; + + let open = OpenMessage::from_octets(buf).unwrap(); + + assert_eq!(open.capabilities().count(), 2); + assert!(open.addpath_families_vec().unwrap().iter().eq( + &[(AfiSafi::Ipv4Unicast, AddpathDirection::SendReceive), + (AfiSafi::Ipv6Unicast, AddpathDirection::SendReceive)] + ) + ); + } + mod builder { use super::*; #[test] - fn builder() { + fn builder_16bit_asn() { let mut open = OpenBuilder::new_vec(); open.set_asn(Asn::from_u32(1234)); open.set_holdtime(180); @@ -984,7 +1141,43 @@ mod builder { open.add_mp(AFI::Ipv6, SAFI::Unicast); let res = open.into_message(); - println!("{:?}", res); + + assert_eq!(res.my_asn(), Asn::from_u32(1234)); + } + + #[test] + fn builder_32bit_asn() { + let mut open = OpenBuilder::new_vec(); + open.set_asn(Asn::from_u32(123123)); + open.set_holdtime(180); + open.set_bgp_id([1, 2, 3, 4]); + + open.add_mp(AFI::Ipv4, SAFI::Unicast); + open.add_mp(AFI::Ipv6, SAFI::Unicast); + + let res = open.into_message(); + + assert_eq!(res.my_asn(), Asn::from_u32(AS_TRANS.into())); + } + + #[test] + fn builder_addpath() { + let mut open = OpenBuilder::new_vec(); + open.set_asn(Asn::from_u32(123123)); + open.set_holdtime(180); + open.set_bgp_id([1, 2, 3, 4]); + + open.add_mp(AFI::Ipv4, SAFI::Unicast); + open.add_mp(AFI::Ipv6, SAFI::Unicast); + open.add_addpath(AfiSafi::Ipv4Unicast, AddpathDirection::SendReceive); + open.add_addpath(AfiSafi::Ipv6Unicast, AddpathDirection::SendReceive); + + let res = open.into_message(); + + assert_eq!(res.my_asn(), Asn::from_u32(AS_TRANS.into())); + for ap in res.addpath_families_vec().unwrap().iter() { + eprintln!("{:?}", ap); + } } diff --git a/src/bgp/message/routerefresh.rs b/src/bgp/message/routerefresh.rs new file mode 100644 index 00000000..87b442ab --- /dev/null +++ b/src/bgp/message/routerefresh.rs @@ -0,0 +1,94 @@ +use octseq::{Octets, Parser}; + +use crate::bgp::message::Header; +use crate::bgp::types::{AFI, SAFI, RouteRefreshSubtype}; +use crate::util::parser::ParseError; + +/// BGP RouteRefresh message, variant of the [`Message`] enum. +#[derive(Clone, Debug)] +pub struct RouteRefreshMessage { + octets: Octets, + afi: AFI, + safi: SAFI, + subtype: RouteRefreshSubtype, +} + +impl RouteRefreshMessage { + pub fn from_octets(octets: Octs) -> Result { + let mut parser = Parser::from_ref(&octets); + { + let header = Header::parse(&mut parser)?; + if header.length() != 23 || parser.remaining() != 4 { + return Err(ParseError::form_error( + "ROUTEREFRESH of invalid size" + )); + } + } + let afi = parser.parse_u16_be()?.into(); + let subtype = parser.parse_u8()?.into(); + let safi = parser.parse_u8()?.into(); + + Ok(RouteRefreshMessage { octets, afi, safi, subtype }) + } + + pub fn octets(&self) -> &Octs { + &self.octets + } +} + +impl RouteRefreshMessage { + /// Returns the `AFI` for this Route Refresh message. + pub fn afi(&self) -> AFI { + self.afi + } + + /// Returns the `SAFI` for this Route Refresh message. + pub fn safi(&self) -> SAFI { + self.safi + } + + /// Returns the `RouteRefreshSubtype` for this Route Refresh message. + /// + /// The subtype, as defined in RFC7313 (Enhanced Route Refresh + /// Capability), is put in the 'reserved' byte as specified in the + /// original RFC2918. That reserved byte should be set to 0 in the + /// original, non-enhanced case, which translates to the + /// RouteRefreshSubtype::Normal variant. + /// This method does not distinguish between whether this message was + /// received over a session with vs without Enhanced Route Refresh + /// Capability. + pub fn subtype(&self) -> RouteRefreshSubtype { + self.subtype + } +} + +impl AsRef<[u8]> for RouteRefreshMessage { + fn as_ref(&self) -> &[u8] { + self.octets.as_ref() + } +} + + +//------------ Tests --------------------------------------------------------- + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn from_octets() { + + let raw = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x17, 0x05, 0x00, 0x01, 0x01, 0x01 + ]; + + let rr = RouteRefreshMessage::from_octets(&raw).unwrap(); + assert_eq!(rr.afi(), AFI::Ipv4); + assert_eq!(rr.safi(), SAFI::Unicast); + assert_eq!(rr.subtype(), RouteRefreshSubtype::Begin); + + } +} diff --git a/src/bgp/message/update.rs b/src/bgp/message/update.rs index 646d57f7..b23e3bc3 100644 --- a/src/bgp/message/update.rs +++ b/src/bgp/message/update.rs @@ -1,35 +1,43 @@ use crate::bgp::message::Header; use octseq::{Octets, Parser}; -use log::{debug, error, warn}; +//use log::debug; use crate::asn::Asn; use crate::bgp::aspath::AsPath; +use crate::bgp::path_attributes::{ + AggregatorInfo, + PathAttributes, PathAttributeType, WireformatPathAttribute +}; pub use crate::bgp::types::{ - AFI, SAFI, LocalPref, MultiExitDisc, NextHop, OriginType, PathAttributeType + AFI, SAFI, LocalPref, MultiExitDisc, NextHop, OriginType, + AfiSafi, AddpathDirection, AddpathFamDir }; -use crate::bgp::message::nlri::{ - Nlri, BasicNlri, MplsNlri, MplsVpnNlri, VplsNlri, FlowSpecNlri, - RouteTargetNlri +use crate::bgp::message::nlri::{self, + Nlri, BasicNlri, EvpnNlri, MplsNlri, MplsVpnNlri, VplsNlri, FlowSpecNlri, + RouteTargetNlri, + FixedNlriIter, }; +use core::ops::Range; use std::net::Ipv4Addr; -use crate::util::parser::{parse_ipv4addr, ParseError}; +use crate::util::parser::ParseError; use crate::bgp::communities::{ - Community, StandardCommunity, + Community, ExtendedCommunity, Ipv6ExtendedCommunity, LargeCommunity }; -const COFF: usize = 19; // XXX replace this with .skip()'s? - /// BGP UPDATE message, variant of the [`Message`] enum. -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct UpdateMessage { octets: Octs, + withdrawals: Range, + attributes: Range, + announcements: Range, session_config: SessionConfig, } @@ -40,14 +48,21 @@ impl UpdateMessage { &self.octets } - /// Returns the [`Header`] for this message. - pub fn header(&self) -> Header<&Octs> { - Header::for_slice(&self.octets) - } + ///// Returns the [`Header`] for this message. + //pub fn header(&self) -> Header<&Octs> { + // Header::for_slice(&self.octets) + //} /// Returns the length in bytes of the entire BGP message. - pub fn length(&self) -> u16 { - self.header().length() + pub fn length(&self) -> usize { + //// marker, length, type + 16 + 2 + 1 + // length of withdrawals + + 2 + self.withdrawals.len() + // length of path attributes + + 2 + self.attributes.len() + // remainder is announcements, no explicit length field + + self.announcements.len() } } @@ -105,128 +120,435 @@ impl AsRef<[u8]> for UpdateMessage { // +-----------------------------------------------------+ impl UpdateMessage { - pub fn for_slice(s: Octs, config: SessionConfig) -> Self { - Self { - octets: s, - session_config: config - } - } + //pub fn for_slice_old(s: Octs, config: SessionConfig) -> Self { + // Self { + // octets: s, + // session_config: config + // } + //} } impl UpdateMessage { - /// Print the Message in a `text2pcap` compatible way. + /// Print the UpdateMessage in a `text2pcap` compatible way. pub fn print_pcap(&self) { - print!("000000 "); - for b in self.octets.as_ref() { - print!("{:02x} ", b); + println!("{}", self.fmt_pcap_string()); + } + + /// Format the UpdateMessage in a `text2pcap` compatible way. + // Note that UpdateMessages can be created using from_octets, which will + // contain the 16 byte marker, or via `parse`, which will include the + // octets only from after the length+msgtype onwards. + // We use the start range for the withdrawals (the first part of the + // actual content) and the end range of the conventional announcements + // (the last part of the actual content). + pub fn fmt_pcap_string(&self) -> String { + let mut res = String::with_capacity( + 7 + ((19 + self.octets.as_ref().len()) * 3) + ); + + res.push_str( + "000000 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff " + ); + + let len = u16::try_from(self.length()) + .unwrap_or(u16::MAX) + .to_be_bytes(); + + res.push_str(&format!("{:02x} {:02x} 02 ", len[0], len[1])); + + for b in &self.octets.as_ref()[ + self.withdrawals.start..self.announcements.end + ] { + res.push_str(&format!("{:02x} ", b)); } - println!(); + + res } - pub fn withdrawn_routes_len(&self) -> u16 { - u16::from_be_bytes([ - self.octets.as_ref()[COFF], - self.octets.as_ref()[COFF+1] - ]) + pub fn withdrawn_routes_len(&self) -> usize { + self.withdrawals.len() } } impl UpdateMessage { - pub fn withdrawals(&self) -> Withdrawals<'_, Octs> { - if let Some(ref mut pa) = self.path_attributes().into_iter().find(|pa| - pa.type_code() == PathAttributeType::MpUnreachNlri - ) { - Withdrawals::parse( - &mut pa.value_into_parser(), - self.session_config - ).expect("parsed before") - } else { - let len = self.withdrawn_routes_len() as usize; - let mut parser = Parser::from_ref(self.octets()); - parser.advance(COFF+2).expect("parsed before"); - let pp = Parser::parse_parser(&mut parser, len) - .expect("parsed before"); - - Withdrawals { - parser: pp, + + /// Returns the conventional withdrawals. + pub fn conventional_withdrawals(&self) -> Result, ParseError> + { + let pp = Parser::with_range(self.octets(), self.withdrawals.clone()); + + let iter = Nlris { + parser: pp, + session_config: self.session_config, + afisafi: AfiSafi::Ipv4Unicast + }; + + Ok(iter) + + } + + /// Returns the withdrawals from the MP_UNREACH_NLRI attribute, if any. + pub fn mp_withdrawals(&self) -> Result>, ParseError> + { + if let Some(WireformatPathAttribute::MpUnreachNlri(epa)) = self.path_attributes()?.get( + PathAttributeType::MpUnreachNlri + ){ + let mut parser = epa.value_into_parser(); + let afi = parser.parse_u16_be()?.into(); + let safi = parser.parse_u8()?.into(); + let afisafi = AfiSafi::try_from((afi, safi)) + .map_err(|_| ParseError::Unsupported)?; + + return Ok(Some(Nlris{ + parser, session_config: self.session_config, - afi: AFI::Ipv4, - safi: SAFI::Unicast - } + afisafi, + })) } + + Ok(None) + } + + /// Returns a combined iterator of conventional and MP_UNREACH_NLRI. + /// + /// Note that this iterator might contain NLRI of different AFI/SAFI + /// types. + pub fn withdrawals(&self) + -> Result< + impl Iterator>, ParseError>>, + ParseError + > + { + let mp_iter = self.mp_withdrawals()?.map(|i| i.iter()); + let conventional_iter = self.conventional_withdrawals()?.iter(); + + Ok(mp_iter.into_iter().flatten().chain(conventional_iter)) + + } + + /// Creates a vec of all withdrawals in this message. + /// + /// If any of the NLRI, in the conventional part or the MP_UNREACH_NLRI + /// attribute, is invalid in any way, returns an Error. + /// This means the result is either the complete (possibly empty) + /// collection of all the announced NLRI, or none at all. + /// + /// For more fine-grained control, consider using the + /// `unicast_withdrawals` method. + pub fn withdrawals_vec(&self) + -> Result>>, ParseError> + { + let conv = self.conventional_withdrawals()?.iter(); + let mp = self.mp_withdrawals()?.map(|mp| mp.iter()); + + conv.chain(mp.into_iter().flatten()).collect() } // RFC4271: A value of 0 indicates that neither the Network Layer // Reachability Information field nor the Path Attribute field is present // in this UPDATE message. - fn total_path_attribute_len(&self) -> u16 { - let wrl = self.withdrawn_routes_len() as usize; - u16::from_be_bytes([ - self.octets.as_ref()[COFF+2+wrl], - self.octets.as_ref()[COFF+2+wrl+1] - ]) - } + pub fn total_path_attribute_len(&self) -> usize { + self.attributes.len() + } - pub fn path_attributes(&self) -> PathAttributes<'_, Octs> { - let wrl = self.withdrawn_routes_len() as usize; - let tpal = self.total_path_attribute_len() as usize; - - let mut parser = Parser::from_ref(&self.octets); - parser.advance(COFF+2+wrl+2).unwrap(); - let pp = Parser::parse_parser(&mut parser, tpal).expect("parsed before"); + pub fn path_attributes(&self) + -> Result, ParseError> + { + let pp = Parser::with_range(self.octets(), self.attributes.clone()); - PathAttributes { + Ok(PathAttributes::new(pp, self.session_config)) + } + + /// Returns the conventional announcements. + pub fn conventional_announcements(&self) + -> Result, ParseError> + { + let pp = Parser::with_range(self.octets(), self.announcements.clone()); + + let iter = Nlris { parser: pp, - session_config: self.session_config + session_config: self.session_config, + afisafi: AfiSafi::Ipv4Unicast, + }; + + Ok(iter) + } + + /// Returns the announcements from the MP_UNREACH_NLRI attribute, if any. + pub fn mp_announcements(&self) -> Result>, ParseError> + { + if let Some(WireformatPathAttribute::MpReachNlri(epa)) = self.path_attributes()?.get( + PathAttributeType::MpReachNlri + ){ + let mut parser = epa.value_into_parser(); + let afi = parser.parse_u16_be()?.into(); + let safi = parser.parse_u8()?.into(); + let afisafi = AfiSafi::try_from((afi, safi)) + .map_err(|_| ParseError::Unsupported)?; + + NextHop::skip(&mut parser)?; + parser.advance(1)?; // 1 reserved byte + let res = Nlris{ + parser, + session_config: self.session_config, + afisafi, + }; + + /* XXX try an alternative config here, perhaps based on a setting + * in session_config + if res.validate().is_err() { + let mut alt_sc = self.session_config; + alt_sc.inverse_addpaths(); + let alt_res = Nlris { + parser, + session_config: alt_sc, + afisafi + }; + match alt_res.validate() { + Ok(()) => return Ok(Some(alt_res)), + Err(e) => return Err(e) + } + } + */ + // XXX or, perhaps just return the Error here? + // although it might make more sense to do that earlier, i.e. when + // the PDU is parsed/validated + + return Ok(Some(res)) } + + Ok(None) } - /// Iterator over the reachable NLRIs. + /// Returns a combined iterator of conventional and MP_REACH_NLRI. /// - /// If present, the NLRIs are taken from the MP_REACH_NLRI path attribute. - /// Otherwise, they are taken from their conventional place at the end of - /// the message. - pub fn nlris(&self) -> Nlris { - if let Some(ref mut pa) = self.path_attributes().into_iter().find(|pa| - pa.type_code() == PathAttributeType::MpReachNlri - ) { - let mut p = pa.value_into_parser(); - Nlris::parse(&mut p, self.session_config).expect("parsed before") - } else { - let wrl = self.withdrawn_routes_len() as usize; - let tpal = self.total_path_attribute_len() as usize; - let mut parser = Parser::from_ref(self.octets()); - parser.advance(COFF+2+wrl+2+tpal).expect("parsed before"); - Nlris::parse_conventional(&mut parser, self.session_config).expect("parsed before") - } + /// Consuming the returned iterator requires care. The `Item` is a + /// Result, containing either a successfully parsed `Nlri`, or an Error + /// describing why it failed to parse. After one such error, the iterator + /// will return None on the next call to `next()`. + /// + /// This means that, if at any point an Error is returned from the + /// iterator, there likely is unparsed data in the PDU in either the + /// conventional part at the end of the PDU, or in the MP_REACH_NLRI + /// attribute. So, at best, one has an incomplete view of the announced + /// NLRI, but possibly even that incomplete view is not 100% correct + /// depending on the exact reason the parsing failed. + /// + /// With the above in mind, using `.count()` on this iterator to get the + /// number of announced prefixes might give the wrong impression, as it + /// will count the Error case, after which the iterator fuses. + /// + /// To retrieve all announcements if and only if all are validly parsed, + /// consder using `fn announcements_vec`. + /// + /// Note that this iterator might contain NLRI of different AFI/SAFI + /// types. + pub fn announcements(&self) + -> Result< + impl Iterator>, ParseError>>, + ParseError + > + { + let mp_iter = self.mp_announcements()?.map(|i| i.iter()); + let conventional_iter = self.conventional_announcements()?.iter(); + + Ok(mp_iter.into_iter().flatten().chain(conventional_iter)) + } + + /// Creates a vec of all announcements in this message. + /// + /// If any of the NLRI, in the conventional part or the MP_REACH_NLRI + /// attribute, is invalid in any way, returns an Error. + /// This means the result is either the complete (possibly empty) + /// collection of all the announced NLRI, or none at all. + /// + /// For more fine-grained control, consider using the + /// `unicast_announcements` method. + pub fn announcements_vec(&self) + -> Result>>, ParseError> + { + let conv = self.conventional_announcements()?.iter(); + let mp = self.mp_announcements()?.map(|mp| mp.iter()); + + conv.chain(mp.into_iter().flatten()).collect() + } + + /// Returns a combined iterator of conventional and unicast MP_REACH_NLRI. + /// + /// If at any point an error occurs, the iterator returns that error and + /// fuses itself, i.e. any following call to `next()` will return None. + pub fn unicast_announcements(&self) + -> Result< + impl Iterator> + '_, + ParseError + > + { + let mp_iter = self.mp_announcements()?.filter(|nlris| + matches!((nlris.afi(), nlris.safi()), + (AFI::Ipv4 | AFI::Ipv6, SAFI::Unicast) + ) + ).map(|nlris| nlris.iter()); + + let conventional_iter = self.conventional_announcements()?.iter(); + + Ok(mp_iter.into_iter().flatten().chain(conventional_iter) + .map(|n| + match n { + Ok(Nlri::Unicast(b)) => Ok(b), + Ok(_) => unreachable!(), + Err(e) => Err(e), + } + ) + ) + } + + /// Creates a vec of all unicast announcements in this message. + /// + /// If any of the NLRI, in the conventional part or the MP_REACH_NLRI + /// attribute, is invalid in any way, returns an Error. + /// This means the result is either the complete (possibly empty) + /// collection of all the announced NLRI, or none at all. + /// + /// For more fine-grained control, consider using the + /// `unicast_announcements` method. + pub fn unicast_announcements_vec(&self) + -> Result, ParseError> + { + let conv = self.conventional_announcements()? + .iter().map(|n| + if let Ok(Nlri::Unicast(b)) = n { + Ok(b) + } else { + Err(ParseError::form_error( + "invalid announced conventional unicast NLRI" + )) + } + ) + ; + + let mp = self.mp_announcements()?.map(|mp| mp.iter() + .filter_map(|n| + match n { + Ok(Nlri::Unicast(b)) => Some(Ok(b)), + Ok(_) => None, + _ => { + Some(Err(ParseError::form_error( + "invalid announced MP unicast NLRI" + ))) + } + } + )) + ; + + conv.chain(mp.into_iter().flatten()).collect() + } + + + /// Returns a combined iterator of conventional and unicast + /// MP_UNREACH_NLRI. + /// + /// If at any point an error occurs, the iterator returns that error and + /// fuses itself, i.e. any following call to `next()` will return None. + pub fn unicast_withdrawals(&self) + -> Result< + impl Iterator> + '_, + ParseError + > + { + let mp_iter = self.mp_withdrawals()?.filter(|nlris| + matches!((nlris.afi(), nlris.safi()), + (AFI::Ipv4 | AFI::Ipv6, SAFI::Unicast) + ) + ).map(|nlris| nlris.iter()); + + let conventional_iter = self.conventional_withdrawals()?.iter(); + + Ok(mp_iter.into_iter().flatten().chain(conventional_iter) + .map(|n| + match n { + Ok(Nlri::Unicast(b)) => Ok(b), + Ok(_) => unreachable!(), + Err(e) => Err(e), + } + ) + ) + } + + /// Creates a vec of all unicast withdrawals in this message. + /// + /// If any of the NLRI, in the conventional part or the MP_UNREACH_NLRI + /// attribute, is invalid in any way, returns an Error. + /// This means the result is either the complete (possibly empty) + /// collection of all the withdrawn NLRI, or none at all. + /// + /// For more fine-grained control, consider using the + /// `unicast_withdrawals` method. + pub fn unicast_withdrawals_vec(&self) + -> Result, ParseError> + { + let conv = self.conventional_withdrawals()? + .iter().map(|n| + if let Ok(Nlri::Unicast(b)) = n { + Ok(b) + } else { + Err(ParseError::form_error( + "invalid withdrawn conventional unicast NLRI" + )) + } + ) + ; + + let mp = self.mp_withdrawals()?.map(|mp| mp.iter() + .filter_map(|n| + match n { + Ok(Nlri::Unicast(b)) => Some(Ok(b)), + Ok(_) => None, + _ => { + Some(Err(ParseError::form_error( + "invalid withdrawn MP unicast NLRI" + ))) + } + } + )) + ; + + conv.chain(mp.into_iter().flatten()).collect() + } + + pub fn has_conventional_nlri(&self) -> bool { + !self.announcements.is_empty() + } + + pub fn has_mp_nlri(&self) -> Result { + Ok( + self.path_attributes()? + .get(PathAttributeType::MpReachNlri).is_some() + ) } /// Returns `Option<(AFI, SAFI)>` if this UPDATE represents the End-of-RIB /// marker for a AFI/SAFI combination. - pub fn is_eor(&self) -> Option<(AFI, SAFI)> { + pub fn is_eor(&self) -> Result, ParseError> { // Conventional BGP if self.length() == 23 { // minimum length for a BGP UPDATE indicates EOR - // (no annoucements, no withdrawals) - return Some((AFI::Ipv4, SAFI::Unicast)); + // (no announcements, no withdrawals) + return Ok(Some((AFI::Ipv4, SAFI::Unicast))); } // Based on MP_UNREACH_NLRI - if self.total_path_attribute_len() > 0 - && self.path_attributes().iter().all(|pa| - pa.type_code() == PathAttributeType::MpUnreachNlri - && pa.length() == 3 // only AFI/SAFI, no NLRI - ) { - let pa = self.path_attributes().into_iter().next().unwrap(); - return Some(( - u16::from_be_bytes( - [pa.value().as_ref()[0], pa.value().as_ref()[1]] - ).into(), - pa.value().as_ref()[2].into() - )); + + let mut pas = self.path_attributes()?; + if let Some(Ok(WireformatPathAttribute::MpUnreachNlri(epa))) = pas.next() { + let mut pa = epa.value_into_parser(); + if pa.remaining() == 3 && pas.next().is_none() { + let afi = pa.parse_u16_be()?.into(); + let safi = pa.parse_u8()?.into(); + return Ok(Some((afi, safi))) + } } - None + Ok(None) } //--- Methods to access mandatory path attributes ------------------------ @@ -236,105 +558,150 @@ impl UpdateMessage { // Also note that these are only present in announced routes. A BGP UPDATE // with only withdrawals will not have any of these mandatory path // attributes present. - pub fn origin(&self) -> Option { - self.path_attributes().iter().find(|pa| - pa.type_code() == PathAttributeType::Origin - ).map(|pa| - match pa.value().as_ref()[0] { - 0 => OriginType::Igp, - 1 => OriginType::Egp, - 2 => OriginType::Incomplete, - n => OriginType::Unknown(n), - } - ) + pub fn origin(&self) -> Result, ParseError> { + if let Some(WireformatPathAttribute::Origin(epa)) = self.path_attributes()?.get(PathAttributeType::Origin) { + Ok(Some(epa.value_into_parser().parse_u8()?.into())) + } else { + Ok(None) + } } /// Returns the AS4_PATH attribute. - pub fn as4path(&self) -> Option>> { - self.path_attributes().into_iter().find(|pa| - pa.type_code() == PathAttributeType::As4Path - ).map(|pa| { - unsafe { - AsPath::new_unchecked(pa.into_value(), true) - } - }) + pub fn as4path(&self) -> Result< + Option>>, + ParseError + > { + if let Some(WireformatPathAttribute::As4Path(epa)) = self.path_attributes()?.get(PathAttributeType::As4Path) { + let mut p = epa.value_into_parser(); + Ok(Some(AsPath::new(p.parse_octets(p.remaining())?, true)?)) + } else { + Ok(None) + } } /// Returns the AS_PATH path attribute. // // NOTE: This is now the AS PATH and only the AS_PATH. - pub fn aspath(&self) -> Option>> { - self.path_attributes().into_iter().find(|pa| - pa.type_code() == PathAttributeType::AsPath - ).map(|pa| { - unsafe { - AsPath::new_unchecked( - pa.into_value(), - self.session_config.has_four_octet_asn(), - ) - } - }) + pub fn aspath(&self) + -> Result>>, ParseError> + { + if let Some(WireformatPathAttribute::AsPath(epa)) = self.path_attributes()?.get(PathAttributeType::AsPath) { + let mut p = epa.value_into_parser(); + Ok(Some(AsPath::new(p.parse_octets(p.remaining())?, + epa.session_config().has_four_octet_asn())?)) + } else { + Ok(None) + } } - /// Returns the NEXT_HOP path attribute, or the equivalent from - /// MP_REACH_NLRI. - pub fn next_hop(&self) -> Option { - if let Some(pa) = self.path_attributes().iter().find(|pa| - pa.type_code() == PathAttributeType::MpReachNlri - ) { - // TODO value_into_parser ? - let v = pa.value(); - let mut parser = Parser::from_ref(&v); - let afi: AFI = parser.parse_u16_be().expect("parsed before").into(); - let safi: SAFI = parser.parse_u8().expect("parsed before").into(); - - return Some(NextHop::parse(&mut parser, afi, safi).expect("parsed before")); - } - - self.path_attributes().iter().find(|pa| - pa.type_code() == PathAttributeType::NextHop - ).map(|pa| - NextHop::Ipv4( - Ipv4Addr::new( - pa.value().as_ref()[0], - pa.value().as_ref()[1], - pa.value().as_ref()[2], - pa.value().as_ref()[3], - ) - ) - ) + /// Returns NextHop information from the NEXT_HOP path attribute, if any. + pub fn conventional_next_hop(&self) + -> Result, ParseError> + { + if let Some(WireformatPathAttribute::NextHop(epa)) = self.path_attributes()?.get(PathAttributeType::NextHop) { + Ok(Some(NextHop::Unicast(Ipv4Addr::from(epa.value_into_parser().parse_u32_be()?).into()))) + } else { + Ok(None) + } + } + + /// Returns NextHop information from the MP_REACH_NLRI, if any. + pub fn mp_next_hop(&self) -> Result, ParseError> { + if let Some(WireformatPathAttribute::MpReachNlri(epa)) = self.path_attributes()?.get( + PathAttributeType::MpReachNlri + ){ + let mut p = epa.value_into_parser(); + let afi = p.parse_u16_be()?.into(); + let safi = p.parse_u8()?.into(); + Ok(Some(NextHop::parse(&mut p, afi, safi)?)) + } else { + Ok(None) + } + + } + + pub fn find_next_hop(&self, afi: AFI, safi: SAFI) -> Result { + match (afi, safi) { + (AFI::Ipv4, SAFI::Unicast) => { + if let Ok(Some(mp)) = self.mp_next_hop() { + if mp.afi_safi() == (AFI::Ipv4, SAFI::Unicast) { + return Err(ParseError::form_error( + "ambiguous IPv4 Unincast nexthop" + )) + } + } + + if let Ok(maybe_nh) = self.conventional_next_hop() { + if let Some(nh) = maybe_nh { + Ok(nh) + } else { + Err(ParseError::form_error( + "no conventional NEXT_HOP" + )) + } + } else { + Err(ParseError::form_error( + "invalid conventional NEXT_HOP" + )) + } + } + (..) => { + if let Ok(maybe_mp) = self.mp_next_hop() { + if let Some(mp) = maybe_mp { + if mp.afi_safi() != (afi, safi) { + return Err(ParseError::form_error( + "MP_REACH_NLRI for different AFI/SAFI" + )) + } + Ok(mp) + } else { + Err(ParseError::form_error( + "no MP_REACH_NLRI / nexthop" + )) + } + } else { + Err(ParseError::form_error( + "invalid MP_REACH_NLRI / nexthop" + )) + } + } + } } //--- Non-mandatory path attribute helpers ------------------------------- /// Returns the Multi-Exit Discriminator value, if any. - pub fn multi_exit_desc(&self) -> Option { - self.path_attributes().iter().find(|pa| - pa.type_code() == PathAttributeType::MultiExitDisc - ).map(|pa| { - MultiExitDisc(u32::from_be_bytes( - pa.value().as_ref()[0..4].try_into().expect("parsed before") - )) - }) + pub fn multi_exit_disc(&self) + -> Result, ParseError> + { + if let Some(WireformatPathAttribute::MultiExitDisc(epa)) = self.path_attributes()?.get( + PathAttributeType::MultiExitDisc + ){ + Ok(Some(MultiExitDisc(epa.value_into_parser().parse_u32_be()?))) + } else { + Ok(None) + } + } /// Returns the Local Preference value, if any. - pub fn local_pref(&self) -> Option { - self.path_attributes().iter().find(|pa| - pa.type_code() == PathAttributeType::LocalPref - ).map(|pa| - LocalPref(u32::from_be_bytes( - pa.value().as_ref()[0..4].try_into().expect("parsed before") - )) - ) + pub fn local_pref(&self) -> Result, ParseError> { + if let Some(WireformatPathAttribute::LocalPref(epa)) = self.path_attributes()?.get( + PathAttributeType::LocalPref + ){ + Ok(Some(LocalPref(epa.value_into_parser().parse_u32_be()?))) + } else { + Ok(None) + } } /// Returns true if this UPDATE contains the ATOMIC_AGGREGATE path /// attribute. - pub fn is_atomic_aggregate(&self) -> bool { - self.path_attributes().iter().any(|pa| - pa.type_code() == PathAttributeType::AtomicAggregate + pub fn is_atomic_aggregate(&self) -> Result { + Ok( + self.path_attributes()? + .get(PathAttributeType::AtomicAggregate).is_some() ) } @@ -347,70 +714,107 @@ impl UpdateMessage { // As such, we can determine whether there is a 2-octet or 4-octet ASN // based on the size of the attribute itself. // - pub fn aggregator(&self) -> Option { - self.path_attributes().iter().find(|pa| { - pa.type_code() == PathAttributeType::Aggregator - }).map(|mut pa| { - Aggregator::parse( - &mut pa.value_into_parser(), - self.session_config - ).expect("parsed before") - }) + pub fn aggregator(&self) -> Result, ParseError> { + + if let Some(WireformatPathAttribute::Aggregator(epa)) = self.path_attributes()?.get( + PathAttributeType::Aggregator + ){ + // XXX not nice that we have to do this here, also it is exactly + // the same as in the fn parse in path_attributes.rs + use crate::util::parser::parse_ipv4addr; + let mut pa = epa.value_into_parser(); + let asn = if self.session_config.has_four_octet_asn() { + Asn::from_u32(pa.parse_u32_be()?) + } else { + Asn::from_u32(pa.parse_u16_be()?.into()) + }; + + let address = parse_ipv4addr(&mut pa)?; + Ok(Some(AggregatorInfo::new(asn, address))) + //Ok(Some(Aggregator::parse2(&mut epa.value_into_parser(), epa.session_config())?.inner())) + } else { + Ok(None) + } } //--- Communities -------------------------------------------------------- /// Returns an iterator over Standard Communities (RFC1997), if any. - pub fn communities(&self) -> Option>> { - self.path_attributes().into_iter().find(|pa| - pa.type_code() == PathAttributeType::Communities - ).map(|pa| CommunityIter::new(pa.into_value())) + pub fn communities(&self) + -> Result>>, ParseError> + { + if let Some(WireformatPathAttribute::Communities(epa)) = self.path_attributes()?.get(PathAttributeType::Communities) { + let mut p = epa.value_into_parser(); + Ok(Some(CommunityIter::new(p.parse_octets(p.remaining())?))) + } else { + Ok(None) + } } /// Returns an iterator over Extended Communities (RFC4360), if any. - pub fn ext_communities(&self) -> Option>> + pub fn ext_communities(&self) + -> Result>>, ParseError> + { + if let Some(WireformatPathAttribute::ExtendedCommunities(epa)) = self.path_attributes()?.get(PathAttributeType::ExtendedCommunities) { + let mut p = epa.value_into_parser(); + Ok(Some(ExtCommunityIter::new(p.parse_octets(p.remaining())?))) + } else { + Ok(None) + } + } + + /// Returns an iterator over IPv6 Address Extended Communities (RFC5701), + /// if any. + pub fn ipv6_ext_communities(&self) + -> Result>>, ParseError> { - self.path_attributes().into_iter().find(|pa| - pa.type_code() == PathAttributeType::ExtendedCommunities - ).map(|pa| ExtCommunityIter::new(pa.into_value())) + if let Some(WireformatPathAttribute::Ipv6ExtendedCommunities(epa)) = self.path_attributes()?.get(PathAttributeType::Ipv6ExtendedCommunities) { + let mut p = epa.value_into_parser(); + Ok(Some(Ipv6ExtCommunityIter::new(p.parse_octets(p.remaining())?))) + } else { + Ok(None) + } } + /// Returns an iterator over Large Communities (RFC8092), if any. pub fn large_communities(&self) - -> Option>> + -> Result>>, ParseError> { - self.path_attributes().into_iter().find(|pa| - pa.type_code() == PathAttributeType::LargeCommunities - ).map(|pa| LargeCommunityIter::new(pa.into_value())) + if let Some(WireformatPathAttribute::LargeCommunities(epa)) = self.path_attributes()?.get(PathAttributeType::LargeCommunities) { + let mut p = epa.value_into_parser(); + Ok(Some(LargeCommunityIter::new(p.parse_octets(p.remaining())?))) + } else { + Ok(None) + } } /// Returns an optional `Vec` containing all conventional, Extended and /// Large communities, if any, or None if none of the three appear in the /// path attributes of this message. - pub fn all_communities(&self) -> Option> { + pub fn all_communities(&self) -> Result>, ParseError> { let mut res = Vec::::new(); - // We can use unwrap safely because the is_some check. - - if self.communities().is_some() { - res.append(&mut self.communities().unwrap().collect::>()); + if let Some(c) = self.communities()? { + res.append(&mut c.collect::>()); } - if self.ext_communities().is_some() { - res.append(&mut self.ext_communities().unwrap() - .map(Community::Extended) - .collect::>()); + if let Some(c) = self.ext_communities()? { + res.append(&mut c.map(Community::Extended).collect::>()); } - if self.large_communities().is_some() { - res.append(&mut self.large_communities().unwrap() - .map(Community::Large) - .collect::>()); + if let Some(c) = self.ipv6_ext_communities()? { + res.append( + &mut c.map(Community::Ipv6Extended).collect::>() + ); + } + if let Some(c) = self.large_communities()? { + res.append(&mut c.map(Community::Large).collect::>()); } if res.is_empty() { - None + Ok(None) } else { - Some(res) + Ok(Some(res)) } } @@ -420,93 +824,131 @@ impl UpdateMessage { impl UpdateMessage { /// Create an UpdateMessage from an octets sequence. /// + /// The 16 byte marker, length and type byte must be present when parsing, + /// and will be included from `octets`. + /// /// As parsing of BGP UPDATE messages requires stateful information /// signalled by the BGP OPEN messages, this function requires a /// [`SessionConfig`]. pub fn from_octets(octets: Octs, config: SessionConfig) -> Result - { - Self::check(octets.as_ref(), config)?; - Ok(UpdateMessage { - octets, - session_config: config - }) + { + let mut parser = Parser::from_ref(&octets); + let UpdateMessage{withdrawals, attributes, announcements, ..} = UpdateMessage::<_>::parse(&mut parser, config)?; + let res = + Self { + octets, + withdrawals: (withdrawals.start +19..withdrawals.end + 19), + attributes: (attributes.start +19..attributes.end + 19), + announcements: (announcements.start +19..announcements.end + 19), + session_config: config + } + ; + + Ok(res) } - fn check(octets: &[u8], config: SessionConfig) -> Result<(), ParseError> { - let mut parser = Parser::from_ref(octets); - Header::check(&mut parser)?; + /// Parses an UpdateMessage from `parser`. + /// + /// The 16 byte marker, length and type byte must be present when parsing, + /// but will not be included in the resulting `Octs`. + pub fn parse<'a, R: Octets>( + parser: &mut Parser<'a, R>, + config: SessionConfig + ) -> Result>, ParseError> + where + R: Octets = Octs>, + { + + let header = Header::parse(parser)?; + if header.length() < 19 { + return Err(ParseError::form_error("message length <19")) + } + let start_pos = parser.pos(); let withdrawals_len = parser.parse_u16_be()?; + let withdrawals_start = parser.pos() - start_pos; if withdrawals_len > 0 { let mut wdraw_parser = parser.parse_parser( withdrawals_len.into() )?; while wdraw_parser.remaining() > 0 { // conventional withdrawals are always IPv4 - BasicNlri::check(&mut wdraw_parser, config, AFI::Ipv4)?; + BasicNlri::check( + &mut wdraw_parser, + config, + AfiSafi::Ipv4Unicast + )?; } } + let withdrawals_end = parser.pos() - start_pos; + let withdrawals = if withdrawals_start == withdrawals_end { + 0..0 + } else { + withdrawals_start..withdrawals_end + }; - let path_attributes_len = parser.parse_u16_be()?; - if path_attributes_len > 0 { - let mut pas_parser = parser.parse_parser( - path_attributes_len.into() + + let attributes_len = parser.parse_u16_be()?; + let attributes_start = parser.pos() - start_pos; + if attributes_len > 0 { + let pas_parser = parser.parse_parser( + attributes_len.into() )?; - PathAttributes::check(&mut pas_parser, config)?; + // XXX this calls `validate` on every attribute, do we want to + // error on that level here? + for pa in PathAttributes::new(pas_parser, config) { + pa?; + } } + let attributes_end = parser.pos() - start_pos; + let attributes = if attributes_start == attributes_end { + 0..0 + } else { + attributes_start..attributes_end + }; - while parser.remaining() > 0 { + let announcements_start = parser.pos() - start_pos; + while parser.pos() < start_pos + header.length() as usize - 19 { // conventional announcements are always IPv4 - BasicNlri::check(&mut parser, config, AFI::Ipv4)?; + BasicNlri::check( + parser, + config, + AfiSafi::Ipv4Unicast + )?; } - Ok(()) - } - - // still used in bmp/message.rs - pub fn parse<'a, R>(parser: &mut Parser<'a, R>, config: SessionConfig) - -> Result - where - R: Octets = Octs>, - { - // parse header - let pos = parser.pos(); - let hdr = Header::parse(parser)?; + let end_pos = parser.pos() - start_pos; - let withdrawn_len = parser.parse_u16_be()?; - if withdrawn_len > 0 { - let mut wdraw_parser = parser.parse_parser(withdrawn_len.into())?; - while wdraw_parser.remaining() > 0 { - // conventional withdrawals are always IPv4 - BasicNlri::parse(&mut wdraw_parser, config, AFI::Ipv4)?; - } - } - let total_path_attributes_len = parser.parse_u16_be()?; - if total_path_attributes_len > 0 { - let mut pas_parser = parser.parse_parser(total_path_attributes_len.into())?; - PathAttributes::parse(&mut pas_parser, config)?; - } + let announcements = if announcements_start == end_pos { + 0..0 + } else { + announcements_start..end_pos + }; - // conventional NLRI, if any - while parser.remaining() > 0 { - // conventional announcements are always IPv4 - BasicNlri::parse(parser, config, AFI::Ipv4)?; - } - let end = parser.pos(); - if end - pos != hdr.length() as usize { + if end_pos != (header.length() as usize) - 19 { return Err(ParseError::form_error( "message length and parsed bytes do not match" )); } - parser.seek(pos)?; - Ok(Self::for_slice( - parser.parse_octets(hdr.length().into())?, - config - )) + parser.seek(start_pos)?; + + Ok(UpdateMessage { + octets: parser.parse_octets((header.length() - 19).into())?, + withdrawals, + attributes, + announcements, + session_config: config + }) + } + + pub fn into_octets(self) -> Octs { + self.octets } + + } //--- Enums for passing config / state --------------------------------------- @@ -521,781 +963,153 @@ impl UpdateMessage { /// BGP OPEN messages when the session was established. /// #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct SessionConfig { pub four_octet_asn: FourOctetAsn, - pub add_path: AddPath, + addpath_fams: SessionAddpaths, } -impl SessionConfig { - pub fn new(four_octet_asn: FourOctetAsn, add_path: AddPath) -> Self { - Self { four_octet_asn, add_path } + +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +struct SessionAddpaths([Option; 16]); +impl SessionAddpaths { + const fn new() -> Self { + Self([None; 16]) } - pub fn modern() -> Self { - Self { - four_octet_asn: FourOctetAsn::Enabled, - add_path: AddPath::Disabled, - } + const fn new_all_enabled() -> Self { + Self([Some(AddpathDirection::SendReceive); 16]) } - pub fn legacy() -> Self { - Self { - four_octet_asn: FourOctetAsn::Disabled, - add_path: AddPath::Disabled, - } + + fn set(&mut self, afisafi: AfiSafi, dir: AddpathDirection) { + self.0[afisafi as usize] = Some(dir); + } + fn get(&self, afisafi: AfiSafi) -> Option { + self.0[afisafi as usize] } - pub fn modern_addpath() -> Self { - Self { - four_octet_asn: FourOctetAsn::Enabled, - add_path: AddPath::Enabled, - } + fn enabled_addpaths(&self) + -> impl Iterator + '_ + { + self.0.iter() + .enumerate() + .filter_map(|(idx, apd)| apd.map(|apd| (idx, apd))) } - pub fn legacy_addpath() -> Self { - Self { - four_octet_asn: FourOctetAsn::Disabled, - add_path: AddPath::Enabled, + fn inverse(&self) -> Self { + let mut res = [None; 16]; + for (i, apd) in self.0.iter().enumerate() { + if apd.is_none() { + res[i] = Some(AddpathDirection::SendReceive); + } } + Self(res) } - pub fn has_four_octet_asn(&self) -> bool { - matches!(self.four_octet_asn, FourOctetAsn::Enabled) - } - - pub fn enable_four_octet_asn(&mut self) { - self.four_octet_asn = FourOctetAsn::Enabled - } - - pub fn disable_four_octet_asn(&mut self) { - self.four_octet_asn = FourOctetAsn::Disabled - } - - pub fn set_four_octet_asn(&mut self, v: FourOctetAsn) { - self.four_octet_asn = v; - } - - pub fn enable_addpath(&mut self) { - self.add_path = AddPath::Enabled - } - - pub fn disable_addpath(&mut self) { - self.add_path = AddPath::Disabled - } - - pub fn set_addpath(&mut self, v: AddPath) { - self.add_path = v; - } -} - -/// Indicates whether this session is Four Octet capable. -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -pub enum FourOctetAsn { - Enabled, - Disabled, -} - -/// Indicates whether AddPath is enabled for this session. -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -pub enum AddPath { - Enabled, - Disabled, -} - -// XXX do we want an intermediary struct PathAttributes with an fn iter() to -// return a PathAttributesIter ? -pub struct PathAttributes<'a, Octs: Octets + ?Sized> { - parser: Parser<'a, Octs>, - session_config: SessionConfig, -} - -impl<'a, Octs: Octets> PathAttributes<'a, Octs> { - pub fn iter(&'a self) -> PathAttributesIter<'a, Octs> { - PathAttributesIter { - parser: self.parser, - session_config: self.session_config + // used for parsing retries + fn inverse_fam(&mut self, afisafi: AfiSafi) { + if self.0[afisafi as usize].is_some() { + self.0[afisafi as usize] = None; + } else { + self.0[afisafi as usize] = Some(AddpathDirection::Receive); } } } -/// Iterator over all [`PathAttribute`]s in a BGP UPDATE message. -pub struct PathAttributesIter<'a, Ref> { - parser: Parser<'a, Ref>, - session_config: SessionConfig, -} - -impl<'a, R: 'a + Octets> PathAttributesIter<'a, R>{ - // XXX remove? - fn _new(path_attributes: &'a R, config: SessionConfig) -> Self { - PathAttributesIter { - parser: Parser::from_ref(path_attributes), - session_config: config, - } - } -} -impl<'a> PathAttributes<'a, [u8]> { - fn check(parser: &mut Parser<'a, [u8]>, config: SessionConfig) - -> Result<(), ParseError> - { - while parser.remaining() > 0 { - PathAttribute::check(parser, config)?; +impl SessionConfig { + pub const fn modern() -> Self { + Self { + four_octet_asn: FourOctetAsn::Enabled, + addpath_fams: SessionAddpaths::new(), } - - Ok(()) } -} - -impl<'a, Octs: Octets> PathAttributes<'a, Octs> { - fn parse(parser: &mut Parser<'a, Octs>, config: SessionConfig) - -> Result - { - let pos = parser.pos(); - while parser.remaining() > 0 { - let _pa = PathAttribute::parse(parser, config)?; + pub fn legacy() -> Self { + Self { + four_octet_asn: FourOctetAsn::Disabled, + addpath_fams: SessionAddpaths::new(), } - let end = parser.pos(); - parser.seek(pos)?; - - Ok(PathAttributes { - parser: Parser::parse_parser(parser, end - pos).unwrap(), - session_config: config - }) - } -} - -/// BGP Path Attribute, carried in BGP UPDATE messages. -#[derive(Debug)] -pub struct PathAttribute<'a, Octs: Octets + ?Sized> { - parser: Parser<'a, Octs> -} - -impl<'a, Octs: Octets> PathAttribute<'a, Octs> { - /// Returns the flags as a raw byte. - pub fn flags(&self) -> u8 { - self.parser.peek(1).expect("parsed before")[0] - } - - /// Returns true if the optional flag is set. - pub fn is_optional(&self) -> bool { - self.flags() & 0x80 == 0x80 } - /// Returns true if the transitive bit is set. - pub fn is_transitive(&self) -> bool { - self.flags() & 0x40 == 0x40 - } - - /// Returns true if the partial flag is set. - pub fn is_partial(&self) -> bool { - self.flags() & 0x20 == 0x20 + pub fn has_four_octet_asn(&self) -> bool { + matches!(self.four_octet_asn, FourOctetAsn::Enabled) } - /// Returns true if the extended length flag is set. - pub fn is_extended_length(&self) -> bool { - self.flags() & 0x10 == 0x10 + pub fn set_four_octet_asn(&mut self, v: FourOctetAsn) { + self.four_octet_asn = v; } - /// Returns the type of this path attribute. - pub fn type_code(&self) -> PathAttributeType { - self.parser.peek(2).expect("parsed before")[1].into() + pub fn enable_four_octet_asn(&mut self) { + self.four_octet_asn = FourOctetAsn::Enabled } - /// Returns the length of the value of this path attribute. - pub fn length(&self) -> u16 { - match self.is_extended_length() { - true => { - let lenbytes = self.parser.peek(4).expect("parsed before"); - u16::from_be_bytes([lenbytes[2], lenbytes[3]]) - } - false => { - let lenbytes = self.parser.peek(3).expect("parsed before"); - lenbytes[2] as u16 - } - } + pub fn disable_four_octet_asn(&mut self) { + self.four_octet_asn = FourOctetAsn::Disabled } - fn hdr_len(&self) -> usize { - match self.is_extended_length() { - true => 2+2, // 2 byte flags+codes, 2 byte value length - false => 2+1, // 2 byte flags+codes, 1 byte value length - } + pub fn add_addpath(&mut self, fam: AfiSafi, dir: AddpathDirection) { + self.addpath_fams.set(fam, dir); } -} -impl<'a, Octs: Octets> PathAttribute<'a, Octs> { - /// Returns the raw value of this path attribute. - pub fn value(&self) -> Octs::Range<'_> { - let start = self.parser.pos() + self.hdr_len(); - let end = start + self.length() as usize; - self.parser.octets_ref().range(start..end) + pub fn add_famdir(&mut self, famdir: AddpathFamDir) { + self.addpath_fams.set(famdir.fam(), famdir.dir()); } - pub fn into_value(mut self) -> Octs::Range<'a> { - self.parser.advance(self.hdr_len()).expect("parsed before"); - self.parser.parse_octets(self.parser.remaining()).expect("parsed before") + pub fn add_addpath_rxtx(&mut self, fam: AfiSafi) { + self.addpath_fams.set(fam, AddpathDirection::SendReceive); } - fn value_into_parser(&mut self) -> Parser<'a, Octs> { - let start = self.hdr_len(); - //let mut p = Parser::from_ref(&self.octets); - //p.advance(start).expect("parsed before"); - self.parser.advance(start).expect("parsed before"); - - //Parser::parse_parser(&mut p, self.length() as usize).expect("parsed before") - self.parser + pub fn get_addpath(&self, fam: AfiSafi) -> Option { + self.addpath_fams.get(fam) } -} - -impl<'a> PathAttribute<'a, [u8]> { - fn check( - parser: &mut Parser<[u8]>, config: SessionConfig - ) -> Result<(), ParseError> { - let flags = parser.parse_u8()?; - let typecode = parser.parse_u8()?; - let len = match flags & 0x10 == 0x10 { - true => { - parser.parse_u16_be()? as usize - }, - false => parser.parse_u8()? as usize, - }; - // now, check the specific type of path attribute - match typecode.into() { - PathAttributeType::Origin => { - if len != 1 { - return Err( - ParseError::form_error("expected len 1 for Origin pa") - ); - } - parser.advance(1)?; - }, - PathAttributeType::AsPath => { - AsPath::check( - parser.parse_octets(len)?, - config.has_four_octet_asn(), - )?; - }, - PathAttributeType::NextHop => { - // conventional NEXT_HOP, just an IPv4 address - if len != 4 { - return Err( - ParseError::form_error("expected len 4 for NEXT_HOP pa") - ); - } - parser.advance(4)?; - } - PathAttributeType::MultiExitDisc => { - if len != 4 { - return Err( - ParseError::form_error("expected len 4 for MULTI_EXIT_DISC pa") - ); - } - parser.advance(4)?; + pub fn rx_addpath(&self, fam: AfiSafi) -> bool { + if let Some(dir) = self.get_addpath(fam) { + match dir { + AddpathDirection::Receive | + AddpathDirection::SendReceive => true, + AddpathDirection::Send => false } - PathAttributeType::LocalPref => { - if len != 4 { - return Err( - ParseError::form_error("expected len 4 for LOCAL_PREF pa") - ); - } - parser.advance(4)?; - }, - PathAttributeType::AtomicAggregate => { - if len != 0 { - return Err( - ParseError::form_error("expected len 0 for ATOMIC_AGGREGATE pa") - ); - } - }, - PathAttributeType::Aggregator => { - let pp = parser.parse_parser(len)?; - Aggregator::check(&pp, config)?; - }, - PathAttributeType::Communities => { - let mut pp = parser.parse_parser(len)?; - while pp.remaining() > 0 { - StandardCommunity::check(&mut pp)?; - } - }, - PathAttributeType::OriginatorId => { - parser.advance(4)?; - }, - PathAttributeType::ClusterList => { - let mut pp = parser.parse_parser(len)?; - while pp.remaining() > 0 { - pp.advance(4)?; - } - }, - PathAttributeType::MpReachNlri => { - let mut pp = parser.parse_parser(len)?; - Nlris::check(&mut pp, config)?; - }, - PathAttributeType::MpUnreachNlri => { - let mut pp = parser.parse_parser(len)?; - Withdrawals::check(&mut pp, config)?; - }, - PathAttributeType::ExtendedCommunities => { - let mut pp = parser.parse_parser(len)?; - while pp.remaining() > 0 { - ExtendedCommunity::check(&mut pp)?; - } - }, - PathAttributeType::As4Path => { - let mut pp = parser.parse_parser(len)?; - while pp.remaining() > 0 { - let _stype = pp.parse_u8()?; - // segment length describes the number of ASNs - let slen = pp.parse_u8()?; - for _ in 0..slen { - pp.advance(4)?; - } - } - } - PathAttributeType::As4Aggregator => { - // Asn + Ipv4Addr - parser.advance(4 + 4)?; - } - PathAttributeType::Connector => { - // Should be an Ipv4Addr according to - // https://www.rfc-editor.org/rfc/rfc6037.html#section-5.2.1 - if len != 4 { - warn!( - "Connector PA, expected len == 4 but found {}", - len - ); - } - parser.advance(len)?; - }, - PathAttributeType::AsPathLimit => { - // u8 limit + Ipv4Addr - parser.advance(5)?; - }, - PathAttributeType::PmsiTunnel => { - let _flags = parser.parse_u8()?; - let _tunnel_type = parser.parse_u8()?; - let _mpls_label_1 = parser.parse_u8()?; - let _mpls_label_2 = parser.parse_u16_be()?; - let tunnel_id_len = len - 5; - parser.advance(tunnel_id_len)?; - }, - PathAttributeType::Ipv6ExtendedCommunities => { - let mut pp = parser.parse_parser(len)?; - while pp.remaining() > 0 { - Ipv6ExtendedCommunity::check(&mut pp)?; - } - }, - PathAttributeType::LargeCommunities => { - let mut pp = parser.parse_parser(len)?; - while pp.remaining() > 0 { - LargeCommunity::check(&mut pp)?; - } - }, - PathAttributeType::BgpsecAsPath => { - let mut pp = parser.parse_parser(len)?; - // SecurePath block - // - // Signature Block 1 - // Signature Block 2? - // - //Secure_Path - // [2 bytes length (octets of total Secure_Path) , 1..N Segments] - //$( - //Segment - // [1byte pCount (prependCount?), 1 byte flags, 4byte ASN] - //)+ - - let len_path = pp.parse_u16_be()?; - if (len_path - 2) % 6 != 0 { - warn!("BGPsec_Path Path Segments not a multiple of 6 bytes") - } - pp.advance(len_path as usize - 2)?; - - //Signature_Block - // [2 bytes length , 1 byte algo suite, 1..N segments] - //$( - //Segment - // [20 bytes SKI, 2 bytes sig length, sig] - //)+ - - let len_sigs = pp.parse_u16_be()?; - let algo_id = pp.parse_u8()?; - if algo_id != 0x01 { - warn!("BGPsec_Path Signature Block containing unknown\ - Algorithm Suite ID {algo_id}"); - } - - let mut sb1_parser = pp.parse_parser(len_sigs as usize - 3)?; - while sb1_parser.remaining() > 0 { - // SKI - sb1_parser.advance(20)?; - let sig_len = sb1_parser.parse_u16_be()?; - sb1_parser.advance(sig_len as usize)?; - } - - // check for another Signature Block: - if pp.remaining() > 0 { - debug!("{} bytes in BgpsecAsPath,\ - assuming a second Signature Block", - pp.remaining() - ); - - let mut sb2_parser = pp.parse_parser(len_sigs as usize - 3)?; - while sb2_parser.remaining() > 0 { - // SKI - sb2_parser.advance(20)?; - let sig_len = sb2_parser.parse_u16_be()?; - sb2_parser.advance(sig_len as usize)?; - } - } - if pp.remaining() > 0 { - warn!("{} bytes left in BgpsecAsPath\ - after two Signature Blocks", - pp.remaining() - ); - } - - }, - PathAttributeType::AttrSet => { - parser.advance(4)?; - let mut pp = parser.parse_parser(len - 4)?; - PathAttributes::check(&mut pp, config)?; - }, - PathAttributeType::Reserved => { - warn!("Path Attribute type 0 'Reserved' observed"); - }, - PathAttributeType::RsrvdDevelopment => { - // This could be anything. - // As long as we do the resetting seek() + parse_octets() - // after the match, we do not need to do anything here. - parser.advance(len)?; - }, - PathAttributeType::Unimplemented(_) => { - debug!("Unimplemented PA: {}", typecode); - parser.advance(len)?; - }, - //_ => { - // panic!("unimplemented: {}", >::from(typecode)); - //}, + } else { + false } - - Ok(()) } -} -impl<'a, Octs: Octets> PathAttribute<'a, Octs> { - fn parse(parser: &mut Parser<'a, Octs>, config: SessionConfig) - -> Result, ParseError> + pub fn enabled_addpaths(&self) + -> impl Iterator + '_ { - let pos = parser.pos(); - let flags = parser.parse_u8()?; - let typecode = parser.parse_u8()?; - let mut headerlen = 3; - let len = match flags & 0x10 == 0x10 { - true => { - headerlen += 1; - parser.parse_u16_be()? as usize - }, - false => parser.parse_u8()? as usize, - }; - - - //parser.seek(pos)?; - // now, parse the specific type of path attribute - match typecode.into() { - PathAttributeType::Origin => { - if len != 1 { - return Err( - ParseError::form_error("expected len 1 for Origin pa") - ); - } - let _origin = parser.parse_u8()?; - }, - PathAttributeType::AsPath => { - let pa = parser.parse_octets(len)?; - let mut p = Parser::from_ref(&pa); - while p.remaining() > 0 { - let _stype = p.parse_u8()?; - // segment length describes the number of ASNs - let slen = p.parse_u8()?; - for _ in 0..slen { - match config.four_octet_asn { - FourOctetAsn::Enabled => { p.parse_u32_be()?; } - FourOctetAsn::Disabled => { p.parse_u16_be()?; } - } - } - } - }, - PathAttributeType::NextHop => { - // conventional NEXT_HOP, just an IPv4 address - if len != 4 { - return Err( - ParseError::form_error("expected len 4 for NEXT_HOP pa") - ); - } - let _next_hop = parse_ipv4addr(parser)?; - } - PathAttributeType::MultiExitDisc => { - if len != 4 { - return Err( - ParseError::form_error("expected len 4 for MULTI_EXIT_DISC pa") - ); - } - let _med = parser.parse_u32_be()?; - } - PathAttributeType::LocalPref => { - if len != 4 { - return Err( - ParseError::form_error("expected len 4 for LOCAL_PREF pa") - ); - } - let _localpref = parser.parse_u32_be()?; - }, - PathAttributeType::AtomicAggregate => { - if len != 0 { - return Err( - ParseError::form_error("expected len 0 for ATOMIC_AGGREGATE pa") - ); - } - }, - PathAttributeType::Aggregator => { - let pa = parser.parse_octets(len)?; - let mut p = Parser::from_ref(&pa); - Aggregator::parse(&mut p, config)?; - }, - PathAttributeType::Communities => { - let pos = parser.pos(); - while parser.pos() < pos + len { - StandardCommunity::parse(parser)?; - } - }, - PathAttributeType::OriginatorId => { - let _bgp_id = parser.parse_u32_be()?; - }, - PathAttributeType::ClusterList => { - let pos = parser.pos(); - while parser.pos() < pos + len { - parser.parse_u32_be()?; - } - }, - PathAttributeType::MpReachNlri => { - let mut pa_parser = parser.parse_parser(len)?; - Nlris::parse(&mut pa_parser, config)?; - }, - PathAttributeType::MpUnreachNlri => { - let mut pa_parser = parser.parse_parser(len)?; - Withdrawals::parse(&mut pa_parser, config)?; - }, - PathAttributeType::ExtendedCommunities => { - let pos = parser.pos(); - while parser.pos() < pos + len { - ExtendedCommunity::parse(parser)?; - } - }, - PathAttributeType::As4Path => { - let mut pa_parser = parser.parse_parser(len)?; - while pa_parser.remaining() > 0 { - let _stype = pa_parser.parse_u8()?; - // segment length describes the number of ASNs - let slen = pa_parser.parse_u8()?; - for _ in 0..slen { - pa_parser.parse_u32_be()?; - } - } - } - PathAttributeType::As4Aggregator => { - let _asn = parser.parse_u32_be()?; - let _addr = parse_ipv4addr(parser)?; - } - PathAttributeType::Connector => { - //let _addr = parse_ipv4addr(parser)?; - // based on - // https://www.rfc-editor.org/rfc/rfc6037.html#section-5.2.1 - // we expect an IPv4 address. We have seen contents of length - // 14 instead of 4 though, so let's be lenient for now. - parser.advance(len)?; - }, - PathAttributeType::AsPathLimit => { - let _limit = parser.parse_u8()?; - let _asn = parser.parse_u32_be()?; - }, - PathAttributeType::PmsiTunnel => { - let _flags = parser.parse_u8()?; - let _tunnel_type = parser.parse_u8()?; - let _mpls_label_1 = parser.parse_u8()?; - let _mpls_label_2 = parser.parse_u16_be()?; - let tunnel_id_len = len - 5; - parser.advance(tunnel_id_len)?; - }, - PathAttributeType::Ipv6ExtendedCommunities => { - let mut pp = parser.parse_parser(len)?; - while pp.remaining() > 0 { - Ipv6ExtendedCommunity::parse(&mut pp)?; - } - }, - PathAttributeType::LargeCommunities => { - let pos = parser.pos(); - while parser.pos() < pos + len { - LargeCommunity::parse(parser)?; - } - }, - PathAttributeType::BgpsecAsPath => { - let mut pp = parser.parse_parser(len)?; - // SecurePath block - // - // Signature Block 1 - // Signature Block 2? - // - //Secure_Path - // [2 bytes length (octets of total Secure_Path) , 1..N Segments] - //$( - //Segment - // [1byte pCount (prependCount?), 1 byte flags, 4byte ASN] - //)+ - - let len_path = pp.parse_u16_be()?; - if (len_path - 2) % 6 != 0 { - warn!("BGPsec_Path Path Segments not a multiple of 6 bytes") - } - pp.advance(len_path as usize - 2)?; - - //Signature_Block - // [2 bytes length , 1 byte algo suite, 1..N segments] - //$( - //Segment - // [20 bytes SKI, 2 bytes sig length, sig] - //)+ - - let len_sigs = pp.parse_u16_be()?; - let algo_id = pp.parse_u8()?; - if algo_id != 0x01 { - warn!("BGPsec_Path Signature Block containing unknown\ - Algorithm Suite ID {algo_id}"); - } - - let mut sb1_parser = pp.parse_parser(len_sigs as usize - 3)?; - while sb1_parser.remaining() > 0 { - // SKI - sb1_parser.advance(20)?; - let sig_len = sb1_parser.parse_u16_be()?; - sb1_parser.advance(sig_len as usize)?; - } - - // check for another Signature Block: - if pp.remaining() > 0 { - debug!("{} bytes in BgpsecAsPath,\ - assuming a second Signature Block", - pp.remaining() - ); - - let mut sb2_parser = pp.parse_parser(len_sigs as usize - 3)?; - while sb2_parser.remaining() > 0 { - // SKI - sb2_parser.advance(20)?; - let sig_len = sb2_parser.parse_u16_be()?; - sb2_parser.advance(sig_len as usize)?; - } - } - if pp.remaining() > 0 { - warn!("{} bytes left in BgpsecAsPath\ - after two Signature Blocks", - pp.remaining() - ); - } - }, - PathAttributeType::AttrSet => { - let _origin_as = parser.parse_u32_be()?; - let mut set_parser = parser.parse_parser(len - 4)?; - //while set_parser.remaining() > 0 { - // PathAttribute::parse(&mut set_parser)?; - //} - PathAttributes::parse(&mut set_parser, config)?; - }, - PathAttributeType::Reserved => { - warn!("Path Attribute type 0 'Reserved' observed"); - }, - PathAttributeType::RsrvdDevelopment => { - // This could be anything. - // As long as we do the resetting seek() + parse_octets() - // after the match, we do not need to do anything here. - }, - PathAttributeType::Unimplemented(_) => { - debug!("Unimplemented PA: {}", typecode); - parser.advance(len)?; - }, - //_ => { - // panic!("unimplemented: {}", >::from(typecode)); - //}, - } - - parser.seek(pos)?; - - let pp = Parser::parse_parser(parser, headerlen+len)?; - Ok(PathAttribute { parser: pp }) + self.addpath_fams.enabled_addpaths() } -} - - -impl<'a, Ref: Octets> Iterator for PathAttributesIter<'a, Ref> { - type Item = PathAttribute<'a, Ref>; - - fn next(&mut self) -> Option { - if self.parser.remaining() == 0 { - return None - } - Some( - PathAttribute::parse(&mut self.parser, self.session_config) - .expect("parsed before") - ) + + pub fn clear_addpaths(&mut self) { + self.addpath_fams = SessionAddpaths::new() } -} -impl<'a, Ref: Octets> IntoIterator for PathAttributes<'a, Ref> { - type Item = PathAttribute<'a, Ref>; - type IntoIter = PathAttributesIter<'a, Ref>; - fn into_iter(self) -> Self::IntoIter { - PathAttributesIter { - parser: self.parser, - session_config: self.session_config - } + pub fn enable_all_addpaths(&mut self) { + self.addpath_fams = SessionAddpaths::new_all_enabled() } -} -//--- Aggregator ------------------------------------------------------------- -/// Path Attribute (7). -#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -pub struct Aggregator { - asn: Asn, - speaker: Ipv4Addr, -} - -impl Aggregator { - /// Creates a new Aggregator. - pub fn new(asn: Asn, speaker: Ipv4Addr) -> Self { - Aggregator{ asn, speaker } - } - - /// Returns the `Asn`. - pub fn asn(&self) -> Asn { - self.asn + pub fn inverse_addpaths(&mut self) { + self.addpath_fams = self.addpath_fams.inverse(); } - /// Returns the speaker IPv4 address. - pub fn speaker(&self) -> Ipv4Addr { - self.speaker + pub fn inverse_addpath(&mut self, fam: AfiSafi) { + self.addpath_fams.inverse_fam(fam); } } -impl std::fmt::Display for Aggregator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "AS{} Speaker {}", self.asn, self.speaker) - } +/// Indicates whether this session is Four Octet capable. +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub enum FourOctetAsn { + Enabled, + Disabled, } - /// Iterator for BGP UPDATE Communities. /// /// Returns values of enum [`Community`], wrapping [`StandardCommunity`], @@ -1360,6 +1174,37 @@ impl Iterator for ExtCommunityIter { } } +/// Iterator over [`Ipv6ExtendedCommunity`]s. +pub struct Ipv6ExtCommunityIter { + slice: Octs, + pos: usize, +} + +impl Ipv6ExtCommunityIter { + fn new(slice: Octs) -> Self { + Ipv6ExtCommunityIter { slice, pos: 0 } + } + + fn get_community(&mut self) -> Ipv6ExtendedCommunity { + let res = Ipv6ExtendedCommunity::from_raw( + self.slice.as_ref()[self.pos..self.pos+20].try_into().expect("parsed before") + ); + self.pos += 8; + res + } +} + +impl Iterator for Ipv6ExtCommunityIter { + type Item = Ipv6ExtendedCommunity; + + fn next(&mut self) -> Option { + if self.pos == self.slice.as_ref().len() { + return None + } + Some(self.get_community()) + } +} + /// Iterator over [`LargeCommunity`]s. pub struct LargeCommunityIter { slice: Octs, @@ -1391,282 +1236,49 @@ impl Iterator for LargeCommunityIter { } } -impl Aggregator { - fn check(parser: &Parser<[u8]>, config: SessionConfig) - -> Result<(), ParseError> - { - let len = parser.remaining(); // XXX is this always correct? - match (len, config.four_octet_asn) { - (8, FourOctetAsn::Enabled) => { - Ok(()) - }, - (6, FourOctetAsn::Disabled) => { - Ok(()) - }, - (_, FourOctetAsn::Enabled) => { - Err( - ParseError::form_error("expected len 8 for AGGREGATOR pa") - ) - }, - (_, FourOctetAsn::Disabled) => { - Err( - ParseError::form_error("expected len 6 for AGGREGATOR pa") - ) - }, - } - } - - fn parse(parser: &mut Parser<'_, R>, config: SessionConfig) - -> Result - { - let len = parser.remaining(); // XXX is this always correct? - match (len, config.four_octet_asn) { - (8, FourOctetAsn::Enabled) => { - let asn = Asn::from_u32(parser.parse_u32_be()?); - let addr = parse_ipv4addr(parser)?; - Ok(Self::new(asn, addr)) - }, - (6, FourOctetAsn::Disabled) => { - let asn = Asn::from_u32(parser.parse_u16_be()?.into()); - let addr = parse_ipv4addr(parser)?; - Ok(Self::new(asn, addr)) - }, - (_, FourOctetAsn::Enabled) => { - Err( - ParseError::form_error("expected len 8 for AGGREGATOR pa") - ) - }, - (_, FourOctetAsn::Disabled) => { - Err( - ParseError::form_error("expected len 6 for AGGREGATOR pa") - ) - }, - } - - } -} - -pub struct Withdrawals<'a, Octs: Octets + ?Sized> { +/// Represents the announced NLRI in a BGP UPDATE message. +#[derive(Debug)] +pub struct Nlris<'a, Octs: Octets> { parser: Parser<'a, Octs>, session_config: SessionConfig, - afi: AFI, - safi: SAFI, + afisafi: AfiSafi, } -impl<'a, Octs: Octets> Withdrawals<'a, Octs> { - pub fn iter(&self) -> WithdrawalsIterMp<'a, Octs> { - WithdrawalsIterMp { - parser: self.parser, - session_config: self.session_config, - afi: self.afi, - safi: self.safi - } - } - - /// Returns the AFI for these withdrawals. - pub fn afi(&self) -> AFI { - self.afi - } - - /// Returns the SAFI for these withdrawals - pub fn safi(&self) -> SAFI { - self.safi - } -} - -/// Iterator over the withdrawn NLRIs. -/// -/// Returns items of the enum [`Nlri`], thus both conventional and -/// BGP MultiProtocol (RFC4760) withdrawn NLRIs. -pub struct WithdrawalsIterMp<'a, Ref: ?Sized> { - parser: Parser<'a, Ref>, - session_config: SessionConfig, - afi: AFI, - safi: SAFI, -} - -impl<'a, Octs: 'a + Octets> WithdrawalsIterMp<'a, Octs> { - fn get_nlri(&mut self) -> Nlri> { - match (self.afi, self.safi) { - (_, SAFI::Unicast | SAFI::Multicast) => { - Nlri::Basic(BasicNlri::parse( - &mut self.parser, - self.session_config, - self.afi - ).expect("parsed before")) - } - (_, SAFI::MplsVpnUnicast) => { - Nlri::MplsVpn(MplsVpnNlri::parse( - &mut self.parser, - self.session_config, - self.afi - ).expect("parsed before")) - }, - (_, SAFI::MplsUnicast) => { - Nlri::Mpls(MplsNlri::parse( - &mut self.parser, - self.session_config, - self.afi - ).expect("parsed before")) - }, - (_, _) => { - error!("trying to iterate over withdrawals \ - for unknown AFI/SAFI combination {}/{}", - self.afi, self.safi - ); - panic!("unsupported AFI/SAFI in withdrawals get_nlri()") - } - } - } -} - -impl<'a> Withdrawals<'a, [u8]> { - fn check(parser: &mut Parser<'a, [u8]>, config: SessionConfig) - -> Result<(), ParseError> - { - // NLRIs from MP_UNREACH_NLRI. - // Length is given in the Path Attribute length field. - // AFI, SAFI, are also in this Path Attribute. - - let afi: AFI = parser.parse_u16_be()?.into(); - let safi: SAFI = parser.parse_u8()?.into(); - - while parser.remaining() > 0 { - match (afi, safi) { - (_, SAFI::Unicast | SAFI::Multicast) => { - BasicNlri::check(parser, config, afi)? - } - (_, SAFI::MplsVpnUnicast) => { - MplsVpnNlri::check(parser, config, afi)? - } - (_, SAFI::MplsUnicast) => { - MplsNlri::check(parser, config, afi)? - }, - (_, _) => { - debug!( - "unimplemented AFI/SAFI {}/{} for Withdrawals", - afi, safi - ); - return Err(ParseError::form_error( - "unimplemented AFI/SAFI for withdrawal" - )) - } - } - } - - Ok(()) - } -} - -impl<'a, Octs: Octets> Withdrawals<'a, Octs> { - // XXX remove? - fn _parse_conventional(parser: &mut Parser<'a, Octs>, config: SessionConfig) - -> Result - { - let pos = parser.pos(); - while parser.remaining() > 0 { - BasicNlri::parse(parser, config, AFI::Ipv4)?; - } - let len = parser.pos() - pos; - parser.seek(pos)?; - - let pp = Parser::parse_parser(parser, len).expect("parsed before"); - - Ok( - Withdrawals { - parser: pp, - session_config: config, - afi: AFI::Ipv4, - safi: SAFI::Unicast, - } - ) - } - - fn parse(parser: &mut Parser<'a, Octs>, config: SessionConfig) - -> Result - { - // NLRIs from MP_UNREACH_NLRI. - // Length is given in the Path Attribute length field. - // AFI, SAFI, are also in this Path Attribute. - - let afi: AFI = parser.parse_u16_be()?.into(); - let safi: SAFI = parser.parse_u8()?.into(); - let pos = parser.pos(); - - while parser.remaining() > 0 { - match (afi, safi) { - (_, SAFI::Unicast | SAFI::Multicast) => { - BasicNlri::parse(parser, config, afi)?; - } - (_, SAFI::MplsVpnUnicast) => { - MplsVpnNlri::parse(parser, config, afi)?; - }, - (_, SAFI::MplsUnicast) => { - MplsNlri::parse(parser, config, afi)?; - }, - (_, _) => { - debug!( - "unimplemented AFI/SAFI {}/{} for Withdrawals", - afi, safi - ); - return Err(ParseError::form_error( - "unimplemented AFI/SAFI for withdrawal" - )) - } - } - } - - let len = parser.pos() - pos; - parser.seek(pos)?; - let pp = Parser::parse_parser(parser, len).expect("parsed before"); - Ok( - Withdrawals { - parser: pp, - session_config: config, - afi, - safi, - } - ) +impl<'a, Octs: Octets> Nlris<'a, Octs> { + pub fn new( + parser: Parser<'a, Octs>, + session_config: SessionConfig, + afisafi: AfiSafi, + ) -> Nlris<'a, Octs> { + Nlris { parser, session_config, afisafi } } -} -impl<'a, Octs: Octets> Iterator for WithdrawalsIterMp<'a, Octs> { - type Item = Nlri>; - - fn next(&mut self) -> Option { - if self.parser.remaining() == 0 { - return None; + pub fn iter(&self) -> NlriIter<'a, Octs> { + NlriIter { + parser: self.parser, + session_config: self.session_config, + afisafi: self.afisafi, } - Some(self.get_nlri()) } -} -/// Represents the announced NLRI in a BGP UPDATE message. -pub struct Nlris<'a, Octs: Octets + ?Sized> { - parser: Parser<'a, Octs>, - session_config: SessionConfig, - afi: AFI, - safi: SAFI, -} + // should this be a thing, here? + fn _validate(&self) -> Result<(), ParseError> { + use AfiSafi::*; + match self.afisafi { + Ipv4Unicast => FixedNlriIter::ipv4unicast(&mut self.parser.clone()).validate(), -impl<'a, Octs: Octets> Nlris<'a, Octs> { - pub fn iter(&self) -> NlriIterMp<'a, Octs> { - NlriIterMp { - parser: self.parser, - session_config: self.session_config, - afi: self.afi, - safi: self.safi, + _ => todo!() } } /// Returns the AFI for these NLRI. pub fn afi(&self) -> AFI { - self.afi + self.afisafi.afi() } /// Returns the SAFI for these NLRI. pub fn safi(&self) -> SAFI { - self.safi + self.afisafi.safi() } } @@ -1674,269 +1286,133 @@ impl<'a, Octs: Octets> Nlris<'a, Octs> { /// /// Returns items of the enum [`Nlri`], thus both conventional and /// BGP MultiProtocol (RFC4760) NLRIs. -pub struct NlriIterMp<'a, Ref: ?Sized> { - parser: Parser<'a, Ref>, +/// +/// If at any point an error occurs, the iterator returns that error and fuses +/// itself, i.e. any following call to `next()` will return None. +pub struct NlriIter<'a, Octs> { + parser: Parser<'a, Octs>, session_config: SessionConfig, - afi: AFI, - safi: SAFI, + afisafi: AfiSafi, } -impl<'a, Octs: Octets> NlriIterMp<'a, Octs> { - fn get_nlri(&mut self) -> Nlri> { - match (self.afi, self.safi) { - (_, SAFI::Unicast | SAFI::Multicast) => { - Nlri::Basic(BasicNlri::parse( +impl<'a, Octs: Octets> NlriIter<'a, Octs> { + pub fn afisafi(&self) -> AfiSafi { + self.afisafi + } + + fn into_parser(self) -> Parser<'a, Octs> { + self.parser + } + + fn get_nlri(&mut self) -> Result>, ParseError> { + use AfiSafi::*; + let res = match self.afisafi { + Ipv4Unicast | Ipv6Unicast => { + Nlri::Unicast(BasicNlri::parse( &mut self.parser, self.session_config, - self.afi - ).expect("parsed before")) - }, - (_, SAFI::MplsVpnUnicast) => { + self.afisafi + + )?) + } + Ipv4Multicast | Ipv6Multicast => { + Nlri::Multicast(BasicNlri::parse( + &mut self.parser, + self.session_config, + self.afisafi, + )?) + } + Ipv4MplsVpnUnicast | Ipv6MplsVpnUnicast => { Nlri::MplsVpn(MplsVpnNlri::parse( &mut self.parser, self.session_config, - self.afi - ).expect("parsed before")) + self.afisafi, + )?) }, - (_, SAFI::MplsUnicast) => { + Ipv4MplsUnicast | Ipv6MplsUnicast => { Nlri::Mpls(MplsNlri::parse( &mut self.parser, self.session_config, - self.afi - ).expect("parsed before")) + self.afisafi, + )?) }, - (AFI::L2Vpn, SAFI::Vpls) => { + L2VpnVpls => { Nlri::Vpls(VplsNlri::parse( &mut self.parser - ).expect("parsed before")) + )?) }, - (AFI::Ipv4, SAFI::FlowSpec) => { + Ipv4FlowSpec | Ipv6FlowSpec => { Nlri::FlowSpec(FlowSpecNlri::parse( - &mut self.parser - ).expect("parsed before")) + &mut self.parser, + self.afisafi.afi() + )?) }, - (AFI::Ipv4, SAFI::RouteTarget) => { + Ipv4RouteTarget => { Nlri::RouteTarget(RouteTargetNlri::parse( &mut self.parser - ).expect("parsed before")) + )?) + }, + L2VpnEvpn => { + Nlri::Evpn(EvpnNlri::parse( + &mut self.parser + )?) }, - (_, _) => { - error!("trying to iterate over NLRI \ - for unknown AFI/SAFI combination {}/{}", - self.afi, self.safi + /* not a thing anymore since we match on AfiSafi variants instead + * of arbitrary combinations of (AFI::, SAFI::) variants. + _ => { + debug!("trying to iterate over NLRI \ + for unknown AFI/SAFI combination {:?}", + self.afisafi ); - panic!("unsupported AFI/SAFI in NLRI get_nlri()") - } - } - } -} - -impl<'a> Nlris<'a, [u8]> { - fn check(parser: &mut Parser<'a, [u8]>, config: SessionConfig) - -> Result<(), ParseError> - { - let afi: AFI = parser.parse_u16_be()?.into(); - let safi: SAFI = parser.parse_u8()?.into(); - - NextHop::check(parser, afi, safi)?; - parser.advance(1)?; // 1 reserved byte - - while parser.remaining() > 0 { - match (afi, safi) { - (_, SAFI::Unicast | SAFI::Multicast) => { - BasicNlri::check(parser, config, afi)?; - } - (_, SAFI::MplsVpnUnicast) => { - MplsVpnNlri::check(parser, config, afi)?; - }, - (_, SAFI::MplsUnicast) => { - MplsNlri::check(parser, config, afi)?; - }, - (AFI::L2Vpn, SAFI::Vpls) => { - VplsNlri::check(parser)?; - } - (AFI::Ipv4, SAFI::FlowSpec) => { - FlowSpecNlri::check(parser)?; - }, - (AFI::Ipv4, SAFI::RouteTarget) => { - RouteTargetNlri::check(parser)?; - }, - (_, _) => { - debug!("unknown AFI/SAFI {}/{}", afi, safi); - return Err( - ParseError::form_error("unimplemented AFI/SAFI") - ) - } - } - } - Ok(()) - } -} - -impl<'a, Octs: Octets> Nlris<'a, Octs> { - // XXX remove, make like Withdrawals - fn parse_conventional(parser: &mut Parser<'a, Octs>, config: SessionConfig) -> Result - { - let pos = parser.pos(); - while parser.remaining() > 0 { - BasicNlri::parse(parser, config, AFI::Ipv4)?; - } - let len = parser.pos() - pos; - parser.seek(pos)?; - - let pp = Parser::parse_parser(parser, len).expect("parsed before"); - - Ok( - Nlris { - parser: pp, - session_config: config, - afi: AFI::Ipv4, - safi: SAFI::Unicast, - } - ) - } - - fn parse(parser: &mut Parser<'a, Octs>, config: SessionConfig) - -> Result - { - // NLRIs from MP_REACH_NLRI. - // Length is given in the Path Attribute length field. - // AFI, SAFI, Nexthop are also in this Path Attribute. - - let afi: AFI = parser.parse_u16_be()?.into(); - let safi: SAFI = parser.parse_u8()?.into(); - - NextHop::skip(parser)?; - parser.advance(1)?; // 1 reserved byte - - let pos = parser.pos(); - - while parser.remaining() > 0 { - match (afi, safi) { - (_, SAFI::Unicast| SAFI::Multicast) => { - BasicNlri::parse(parser, config, afi)?; - } - (_, SAFI::MplsVpnUnicast) => { - MplsVpnNlri::parse(parser, config, afi)?; - }, - (_, SAFI::MplsUnicast) => { - MplsNlri::parse(parser, config, afi)?; - }, - (AFI::L2Vpn, SAFI::Vpls) => { - VplsNlri::parse(parser)?; - } - (AFI::Ipv4, SAFI::FlowSpec) => { - FlowSpecNlri::parse(parser)?; - }, - (AFI::Ipv4, SAFI::RouteTarget) => { - RouteTargetNlri::parse(parser)?; - }, - (_, _) => { - debug!("unknown AFI/SAFI {}/{}", afi, safi); - return Err( - ParseError::form_error("unimplemented AFI/SAFI") - ) - } + // As all the NLRI in the iterator are of the same AFI/SAFI + // type, we will not be able to make sense of anything in this + // blob of NLRI. We advance the parser so the next call to + // next() on this iterator will return None, and be done with + // it. + self.parser.advance(self.parser.remaining())?; + return Err(ParseError::Unsupported) } - } - - let len = parser.pos() - pos; - parser.seek(pos)?; - let pp = Parser::parse_parser(parser, len).expect("parsed before"); - - Ok(Nlris { - parser: pp, - session_config: config, - afi, - safi, - }) - } -} - - -impl<'a, Octs: Octets> Iterator for NlriIterMp<'a, Octs> { - type Item = Nlri>; - - fn next(&mut self) -> Option { - if self.parser.remaining() == 0 { - return None; - } - Some(self.get_nlri()) - } -} - -//--- Communities ------------------------------------------------------------ -// - -impl StandardCommunity { - fn check(parser: &mut Parser<[u8]>) - -> Result<(), ParseError> - { - parser.advance(4)?; - Ok(()) - } - - fn parse(parser: &mut Parser<'_, R>) - -> Result - { - let mut buf = [0u8; 4]; - parser.parse_buf(&mut buf)?; - Ok( Self::from_raw(buf) ) - } -} - -impl ExtendedCommunity { - fn check(parser: &mut Parser<[u8]>) - -> Result<(), ParseError> - { - parser.advance(8)?; - Ok(()) - } - - fn parse(parser: &mut Parser<'_, R>) - -> Result - { - let mut buf = [0u8; 8]; - parser.parse_buf(&mut buf)?; - Ok( Self::from_raw(buf) ) + */ + }; + Ok(res) } } -impl Ipv6ExtendedCommunity { - fn check(parser: &mut Parser<[u8]>) - -> Result<(), ParseError> - { - parser.advance(20)?; - Ok(()) - } +impl<'a, Octs: Octets> Iterator for NlriIter<'a, Octs> { + type Item = Result>, ParseError>; - fn parse(parser: &mut Parser<'_, R>) - -> Result - { - let mut buf = [0u8; 20]; - parser.parse_buf(&mut buf)?; - Ok( Self::from_raw(buf) ) + fn next(&mut self) -> Option { + if self.parser.remaining() == 0 { + return None; + } + match self.get_nlri() { + Ok(n) => Some(Ok(n)), + Err(e) => { + // Whenever an error occured, e.g. because the NLRI could not + // be parsed, we return the error and 'fuse' the iterator by + // advancing the parser, ensuring the next call to `next()` + // returns a None. + self.parser.advance(self.parser.remaining()).ok()?; + Some(Err(e)) + } + } } } -impl LargeCommunity { - fn check(parser: &mut Parser<[u8]>) - -> Result<(), ParseError> - { - parser.advance(12)?; - Ok(()) - } - - fn parse(parser: &mut Parser<'_, R>) - -> Result - { - let mut buf = [0u8; 12]; - parser.parse_buf(&mut buf)?; - Ok( Self::from_raw(buf) ) +impl<'a, Octs: Octets> TryFrom> + for FixedNlriIter<'a, Octs, nlri::Ipv4Unicast> +{ + type Error = &'static str; + fn try_from(iter: NlriIter<'a, Octs>) -> Result { + if iter.afisafi() == AfiSafi::Ipv4Unicast { + return Ok(FixedNlriIter::new(&mut iter.into_parser())) + } + Err("can not convert into FixedNlriIter for Ipv4Unicast") } } + //--- Tests ------------------------------------------------------------------ #[cfg(test)] @@ -1944,12 +1420,32 @@ mod tests { use super::*; use std::str::FromStr; - use crate::bgp::communities::*; - use crate::bgp::message::Message; + use std::net::Ipv6Addr; + use crate::bgp::communities::{ + StandardCommunity, + ExtendedCommunityType, + ExtendedCommunitySubType, + Tag, Wellknown, + }; + use crate::bgp::message::{Message, nlri::{ + PathId, RouteDistinguisher + }}; use crate::addr::Prefix; + + use bytes::Bytes; + #[allow(dead_code)] + fn print_pcap>(buf: T) { + print!("000000 "); + for b in buf.as_ref() { + print!("{:02x} ", b); + } + println!(); + } + + //TODO: // X generic // - incomplete msg @@ -1971,11 +1467,11 @@ mod tests { // x chained iter // - MP NLRI types: // announcements: - // - v4 mpls unicast + // x v4 mpls unicast // - v4 mpls unicast unreach **missing** // - v4 mpls vpn unicast // - v6 mpls unicast addpath - // - v6 mpls vpn unicast + // X v6 mpls vpn unicast // - multicast **missing // - vpls // - flowspec @@ -2024,71 +1520,134 @@ mod tests { assert_eq!(update.total_path_attribute_len(), 27); //let mut pa_iter = update.path_attributes().iter(); - let pas = update.path_attributes(); - let mut pa_iter = pas.iter(); + let pas = update.path_attributes().unwrap(); + let mut pa_iter = pas.into_iter(); - let pa1 = pa_iter.next().unwrap(); + let pa1 = pa_iter.next().unwrap().unwrap(); assert_eq!(pa1.type_code(), PathAttributeType::Origin); - assert_eq!(pa1.flags(), 0x40); - assert!(!pa1.is_optional()); - assert!(pa1.is_transitive()); - assert!(!pa1.is_partial()); - assert!(!pa1.is_extended_length()); + assert_eq!(pa1.flags(), 0x40.into()); + assert!(!pa1.flags().is_optional()); + assert!(pa1.flags().is_transitive()); + assert!(!pa1.flags().is_partial()); + assert!(!pa1.flags().is_extended_length()); assert_eq!(pa1.length(), 1); - assert_eq!(pa1.value().as_ref(), &[0x00]); // TODO enumify Origin types + //assert_eq!(pa1.as_ref(), &[0x00]); // TODO get inner, into + // // OriginType - let pa2 = pa_iter.next().unwrap(); + let pa2 = pa_iter.next().unwrap().unwrap(); assert_eq!(pa2.type_code(), PathAttributeType::AsPath); - assert_eq!(pa2.flags(), 0x40); + assert_eq!(pa2.flags(), 0x40.into()); assert_eq!(pa2.length(), 6); - let asp = pa2.value(); - assert_eq!(asp.as_ref(), [0x02, 0x01, 0x00, 0x01, 0x00, 0x00]); + //assert_eq!(pa2.as_ref(), [0x02, 0x01, 0x00, 0x01, 0x00, 0x00]); - /* - let mut pb = AsPathBuilder::new(); - pb.push(Asn::from_u32(65536)).unwrap(); - let asp: AsPath> = pb.finalize(); - */ + let mut pb = crate::bgp::aspath::HopPath::new(); + pb.prepend(Asn::from_u32(65536)); + let asp: AsPath = pb.to_as_path().unwrap(); - //assert_eq!(update.aspath().unwrap(), asp); + assert_eq!(update.aspath().unwrap().unwrap(), asp); - let pa3 = pa_iter.next().unwrap(); + let pa3 = pa_iter.next().unwrap().unwrap(); assert_eq!(pa3.type_code(), PathAttributeType::NextHop); - assert_eq!(pa3.flags(), 0x40); + assert_eq!(pa3.flags(), 0x40.into()); assert_eq!(pa3.length(), 4); - assert_eq!(pa3.value().as_ref(), &[10, 255, 0, 101]); + //assert_eq!(pa3.as_ref(), &[10, 255, 0, 101]); assert_eq!( - update.next_hop(), - Some(NextHop::Ipv4(Ipv4Addr::new(10, 255, 0, 101))) + update.conventional_next_hop().unwrap(), + Some(NextHop::Unicast(Ipv4Addr::new(10, 255, 0, 101).into())) ); - let pa4 = pa_iter.next().unwrap(); + let pa4 = pa_iter.next().unwrap().unwrap(); assert_eq!(pa4.type_code(), PathAttributeType::MultiExitDisc); - assert_eq!(pa4.flags(), 0x80); - assert!(pa4.is_optional()); - assert!(!pa4.is_transitive()); - assert!(!pa4.is_partial()); - assert!(!pa4.is_extended_length()); + assert_eq!(pa4.flags(), 0x80.into()); + assert!( pa4.flags().is_optional()); + assert!(!pa4.flags().is_transitive()); + assert!(!pa4.flags().is_partial()); + assert!(!pa4.flags().is_extended_length()); assert_eq!(pa4.length(), 4); - assert_eq!(pa4.value().as_ref(), &[0x00, 0x00, 0x00, 0x01]); - assert_eq!(update.multi_exit_desc(), Some(MultiExitDisc(1))); + //assert_eq!(pa4.as_ref(), &[0x00, 0x00, 0x00, 0x01]); + assert_eq!(update.multi_exit_disc().unwrap(), Some(MultiExitDisc(1))); assert!(pa_iter.next().is_none()); - //let mut nlri_iter = update.nlris().iter(); - let nlris = update.nlris(); - let mut nlri_iter = nlris.iter(); + let mut nlri_iter = update.announcements().unwrap(); let nlri1 = nlri_iter.next().unwrap(); - assert!(matches!(nlri1, Nlri::Basic(_))); - assert_eq!( - nlri1.prefix(), - Some(Prefix::from_str("10.10.10.2/32").unwrap()) - ); + assert_eq!(nlri1.unwrap(), Nlri::unicast_from_str("10.10.10.2/32").unwrap()); assert!(nlri_iter.next().is_none()); } + #[test] + fn conventional_parsed() { + let buf = vec![ + // Two BGP UPDATEs + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x37, 0x02, + 0x00, 0x00, 0x00, 0x1b, 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, + 0x06, 0x02, 0x01, 0x00, 0x01, 0x00, 0x00, 0x40, 0x03, 0x04, + 0x0a, 0xff, 0x00, 0x65, 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x20, 0x0a, 0x0a, 0x0a, 0x02, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x3c, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x40, + 0x01, 0x01, 0x00, 0x40, 0x02, 0x06, 0x02, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x40, 0x03, 0x04, 0x0a, + 0xff, 0x00, 0x65, 0x80, 0x04, 0x04, 0x00, 0x00, + 0x07, 0x6c, 0x20, 0x0a, 0x0a, 0x0a, 0x09, 0x1e, + 0xc0, 0xa8, 0x61, 0x00 + ]; + + let bytes = Bytes::from(buf); + let mut parser = Parser::from_ref(&bytes); + let update = UpdateMessage::parse( + &mut parser, + SessionConfig::modern() + ).unwrap(); + + update.print_pcap(); + assert_eq!(update.length(), 55); + assert_eq!(update.total_path_attribute_len(), 27); + + let update = UpdateMessage::parse( + &mut parser, + SessionConfig::modern() + ).unwrap(); + + update.print_pcap(); + assert_eq!(update.total_path_attribute_len(), 27); + assert_eq!(update.announcements().unwrap().count(), 2); + + } + + use std::fs::File; + use memmap2::Mmap; + + #[test] + #[ignore] + fn parse_bulk() { + let filename = "examples/raw_bgp_updates"; + let file = File::open(filename).unwrap(); + let mmap = unsafe { Mmap::map(&file).unwrap() }; + let fh = &mmap[..]; + let mut parser = Parser::from_ref(&fh); + + let mut n = 0; + const MAX: usize = 10_000_000; + + while parser.remaining() > 0 && n < MAX { + if let Err(e) = UpdateMessage::<_>::parse( + &mut parser, SessionConfig::modern() + ) { + eprintln!("failed to parse: {e}"); + } + n += 1; + eprint!("\r{n} "); + } + eprintln!("parsed {n}"); + dbg!(parser); + } + + #[test] fn conventional_multiple_nlri() { let buf = vec![ @@ -2109,15 +1668,12 @@ mod tests { ).unwrap().try_into().unwrap(); assert_eq!(update.total_path_attribute_len(), 27); - assert_eq!(update.nlris().iter().count(), 2); + assert_eq!(update.announcements().unwrap().count(), 2); + let prefixes = ["10.10.10.9/32", "192.168.97.0/30"] + .map(|p| Nlri::unicast_from_str(p).unwrap()); - let prefixes = ["10.10.10.9/32", "192.168.97.0/30"].map(|p| - Prefix::from_str(p).unwrap() - ); + assert!(prefixes.into_iter().eq(update.announcements().unwrap().map(|n| n.unwrap()))); - for (nlri, prefix) in update.nlris().iter().zip(prefixes.iter()) { - assert_eq!(nlri.prefix(), Some(*prefix)) - } } #[test] @@ -2143,19 +1699,19 @@ mod tests { 0x40, 0x02, 0x06, 0x02, 0x01, 0x00, 0x00, 0x00, 0xc8, 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00 ]; - //let update: UpdateMessage<_> = parse_msg(&buf); - let update: UpdateMessage<_> = Message::from_octets( + let update = UpdateMessage::from_octets( &buf, - Some(SessionConfig::modern()) - ).unwrap().try_into().unwrap(); + SessionConfig::modern() + ).unwrap(); assert_eq!(update.withdrawn_routes_len(), 0); assert_eq!(update.total_path_attribute_len(), 113); - let nlris = update.nlris(); - let nlri_iter = nlris.iter(); + assert!(!update.has_conventional_nlri()); + assert!(update.has_mp_nlri().unwrap()); + + let nlri_iter = update.announcements().unwrap(); assert_eq!(nlri_iter.count(), 5); - //assert_eq!(update.nlris().iter().count(), 5); let prefixes = [ "fc00::10/128", @@ -2163,13 +1719,28 @@ mod tests { "2001:db8:ffff:1::/64", "2001:db8:ffff:2::/64", "2001:db8:ffff:3::/64", - ].map(|p| - Prefix::from_str(p).unwrap() - ); + ].map(|p| Nlri::unicast_from_str(p).unwrap()); - for (nlri, prefix) in update.nlris().iter().zip(prefixes.iter()) { - assert_eq!(nlri.prefix(), Some(*prefix)) - } + assert!(prefixes.into_iter().eq( + update.announcements().unwrap().map(|n| n.unwrap()) + )); + + assert!(prefixes.into_iter().eq( + update.announcements_vec().unwrap().into_iter() + )); + + assert!(update.unicast_announcements().unwrap() + .map(|b| Nlri::<&[u8]>::Unicast(b.unwrap())) + .eq(prefixes) + ); + + assert!( + update.unicast_announcements_vec().unwrap().into_iter() + .map(Nlri::<&[u8]>::Unicast) + .eq(prefixes) + ); + + assert!(update.find_next_hop(AFI::Ipv6, SAFI::Multicast).is_err()); } @@ -2195,7 +1766,7 @@ mod tests { Some(SessionConfig::modern()) ).unwrap().try_into().unwrap(); - assert_eq!(update.withdrawals().iter().count(), 12); + assert_eq!(update.withdrawals().unwrap().count(), 12); let ws = [ "10.10.10.10/32", @@ -2210,14 +1781,9 @@ mod tests { "192.168.98.0/30", "192.168.0.16/30", "192.168.99.0/30", - ].map(|w| - Prefix::from_str(w).unwrap() - ); - - for (nlri, w) in update.withdrawals().iter().zip(ws.iter()) { - assert_eq!(nlri.prefix(), Some(*w)) - } + ].map(|w| Ok(Nlri::unicast_from_str(w).unwrap())); + assert!(ws.into_iter().eq(update.withdrawals().unwrap())); } #[test] @@ -2240,20 +1806,15 @@ mod tests { Some(SessionConfig::modern()) ).unwrap().try_into().unwrap(); - assert_eq!(update.withdrawals().iter().count(), 4); + assert_eq!(update.withdrawals().unwrap().count(), 4); let ws = [ "2001:db8:ffff::/64", "2001:db8:ffff:1::/64", "2001:db8:ffff:2::/64", "2001:db8:ffff:3::/64", - ].map(|w| - Prefix::from_str(w).unwrap() - ); - - for (nlri, w) in update.withdrawals().iter().zip(ws.iter()) { - assert_eq!(nlri.prefix(), Some(*w)) - } + ].map(|w| Ok(Nlri::unicast_from_str(w).unwrap())); + assert!(ws.into_iter().eq(update.withdrawals().unwrap())); } //--- Path Attributes ------------------------------------------------ @@ -2281,8 +1842,8 @@ mod tests { &buf, Some(SessionConfig::modern()) ).unwrap().try_into().unwrap(); - assert_eq!(update.multi_exit_desc(), Some(MultiExitDisc(0))); - assert_eq!(update.local_pref(), Some(LocalPref(100))); + assert_eq!(update.multi_exit_disc().unwrap(), Some(MultiExitDisc(0))); + assert_eq!(update.local_pref().unwrap(), Some(LocalPref(100))); } #[test] @@ -2306,10 +1867,10 @@ mod tests { ).unwrap().try_into().unwrap(); let aggr = update.aggregator().unwrap(); - assert!(update.is_atomic_aggregate()); - assert_eq!(aggr.asn(), Asn::from(101)); + assert!(update.is_atomic_aggregate().unwrap()); + assert_eq!(aggr.unwrap().asn(), Asn::from(101)); assert_eq!( - aggr.speaker(), + aggr.unwrap().address(), Ipv4Addr::from_str("198.51.100.1").unwrap() ); } @@ -2341,22 +1902,22 @@ mod tests { .unwrap().try_into().unwrap(); update.path_attributes().iter();//.count(); - if let Some(aspath) = update.path_attributes().iter().find(|pa| - pa.type_code() == PathAttributeType::AsPath - ){ - assert_eq!(aspath.flags(), 0x50); - assert!(aspath.is_transitive()); - assert!(aspath.is_extended_length()); + if let Some(Ok(aspath)) = update.path_attributes().unwrap() + .find(|pa| pa.as_ref().unwrap().type_code() == PathAttributeType::AsPath) + { + assert_eq!(aspath.flags(), 0x50.into()); + assert!(aspath.flags().is_transitive()); + assert!(aspath.flags().is_extended_length()); assert_eq!(aspath.length(), 22); //TODO check actual aspath } else { panic!("ASPATH path attribute not found") } - if let Some(as4path) = update.path_attributes().iter().find(|pa| - pa.type_code() == PathAttributeType::As4Path - ){ - assert_eq!(as4path.flags(), 0xd0); + if let Some(Ok(as4path)) = update.path_attributes().unwrap() + .find(|pa| pa.as_ref().unwrap().type_code() == PathAttributeType::As4Path) + { + assert_eq!(as4path.flags(), 0xd0.into()); assert_eq!(as4path.length(), 42); //TODO check actual aspath } else { @@ -2364,14 +1925,14 @@ mod tests { } assert_eq!( - update.aspath().unwrap().hops().collect::>(), + update.aspath().unwrap().unwrap().hops().collect::>(), AsPath::vec_from_asns([ 0xfbf0, 0xfbf1, 0xfbf2, 0xfbf3, 0x5ba0, 0x5ba0, 0x5ba0, 0x5ba0, 0x5ba0, 0x5ba0 ]).hops().collect::>(), ); assert_eq!( - update.as4path().unwrap().hops().collect::>(), + update.as4path().unwrap().unwrap().hops().collect::>(), AsPath::vec_from_asns([ 0xfbf0, 0xfbf1, 0xfbf2, 0xfbf3, 0x10000, 0x10000, 0x10000, 0x10000, 0x10001, 0x1000a, @@ -2404,27 +1965,34 @@ mod tests { 0x03, 0x00, 0x00, 0x00, 0x01, 0x19, 0xc6, 0x33, 0x64, 0x00 ]; - let sc = SessionConfig::modern_addpath(); + let mut sc = SessionConfig::modern(); + sc.add_addpath(AfiSafi::Ipv4Unicast, AddpathDirection::Receive); let upd: UpdateMessage<_> = Message::from_octets(&buf, Some(sc)) .unwrap().try_into().unwrap(); + let nlri1 = upd.announcements().unwrap().next().unwrap(); assert_eq!( - upd.nlris().iter().next().unwrap().prefix(), - Some(Prefix::from_str("198.51.100.0/25").unwrap()) - ); + nlri1.unwrap(), + Nlri::<&[u8]>::Unicast(BasicNlri::with_path_id( + Prefix::from_str("198.51.100.0/25").unwrap(), + PathId::from_u32(1) + )) + ); - assert!(upd.communities().is_some()); - for c in upd.communities().unwrap() { + assert!(upd.communities().unwrap().is_some()); + for c in upd.communities().unwrap().unwrap() { println!("{:?}", c); } - assert!(upd.communities().unwrap().eq([ - Community::Standard(StandardCommunity::new(42.into(), Tag::new(518))), - Wellknown::NoExport.into(), - Wellknown::NoExportSubconfed.into() - ])); - - assert!(upd.ext_communities().is_some()); - let mut ext_comms = upd.ext_communities().unwrap(); + assert!(upd.communities().unwrap().unwrap() + .eq([ + StandardCommunity::new(42.into(), Tag::new(518)).into(), + Wellknown::NoExport.into(), + Wellknown::NoExportSubconfed.into() + ]) + ); + + assert!(upd.ext_communities().unwrap().is_some()); + let mut ext_comms = upd.ext_communities().unwrap().unwrap(); let ext_comm1 = ext_comms.next().unwrap(); assert!(ext_comm1.is_transitive()); @@ -2472,7 +2040,7 @@ mod tests { Some(SessionConfig::modern()) ).unwrap().try_into().unwrap(); - let mut lcs = update.large_communities().unwrap(); + let mut lcs = update.large_communities().unwrap().unwrap(); let lc1 = lcs.next().unwrap(); assert_eq!(lc1.global(), 65536); assert_eq!(lc1.local1(), 1); @@ -2515,21 +2083,22 @@ mod tests { 0x03, 0x00, 0x00, 0x00, 0x01, 0x19, 0xc6, 0x33, 0x64, 0x00 ]; - let sc = SessionConfig::modern_addpath(); + let mut sc = SessionConfig::modern(); + sc.add_addpath(AfiSafi::Ipv4Unicast, AddpathDirection::Receive); let upd: UpdateMessage<_> = Message::from_octets(&buf, Some(sc)) .unwrap().try_into().unwrap(); - for c in upd.all_communities().unwrap() { + for c in upd.all_communities().unwrap().unwrap() { println!("{}", c); } - assert!(upd.all_communities().unwrap().eq(&[ - Community::Standard(StandardCommunity::new(42.into(), Tag::new(518))), - Wellknown::NoExport.into(), - Wellknown::NoExportSubconfed.into(), - [0x00, 0x06, 0x00, 0x00, 0x44, 0x9c, 0x40, 0x00].into(), - [0x40, 0x04, 0x00, 0x00, 0x44, 0x9c, 0x40, 0x00].into(), + assert!(upd.all_communities().unwrap().unwrap() + .eq(&[ + StandardCommunity::new(42.into(), Tag::new(518)).into(), + Wellknown::NoExport.into(), + Wellknown::NoExportSubconfed.into(), + [0x00, 0x06, 0x00, 0x00, 0x44, 0x9c, 0x40, 0x00].into(), + [0x40, 0x04, 0x00, 0x00, 0x44, 0x9c, 0x40, 0x00].into(), ])) - } #[test] @@ -2559,7 +2128,8 @@ mod tests { 0x1b, 0x7e, 0xbf ]; - let sc = SessionConfig::modern_addpath(); + let mut sc = SessionConfig::modern(); + sc.add_addpath(AfiSafi::Ipv4Unicast, AddpathDirection::Receive); let _upd: UpdateMessage<_> = Message::from_octets(&buf, Some(sc)) .unwrap().try_into().unwrap(); //for pa in upd.path_attributes() { @@ -2586,14 +2156,16 @@ mod tests { let sc = SessionConfig::modern(); let upd: UpdateMessage<_> = Message::from_octets(&buf, Some(sc)) .unwrap().try_into().unwrap(); - assert_eq!(upd.nlris().afi(), AFI::Ipv4); - assert_eq!(upd.nlris().safi(), SAFI::Multicast); - assert!(upd.nlris().iter().map(|n| n.prefix().unwrap()).eq([ - Prefix::from_str("198.51.100.0/26").unwrap(), - Prefix::from_str("198.51.100.64/26").unwrap(), - Prefix::from_str("198.51.100.128/26").unwrap(), - Prefix::from_str("198.51.100.192/26").unwrap(), - ])); + assert_eq!(upd.mp_announcements().unwrap().unwrap().afi(), AFI::Ipv4); + assert_eq!(upd.mp_announcements().unwrap().unwrap().safi(), SAFI::Multicast); + let prefixes = [ + "198.51.100.0/26", + "198.51.100.64/26", + "198.51.100.128/26", + "198.51.100.192/26", + ].map(|p| Nlri::<&[u8]>::Multicast(Prefix::from_str(p).unwrap().into())); + + assert!(prefixes.into_iter().eq(upd.announcements().unwrap().map(|n| n.unwrap()))); } #[test] @@ -2608,9 +2180,483 @@ mod tests { let sc = SessionConfig::modern(); let upd: UpdateMessage<_> = Message::from_octets(&buf, Some(sc)) .unwrap().try_into().unwrap(); - assert_eq!(upd.withdrawals().afi(), AFI::Ipv4); - assert_eq!(upd.withdrawals().safi(), SAFI::Multicast); - assert_eq!(upd.withdrawals().iter().count(), 1); + assert_eq!(upd.mp_withdrawals().unwrap().unwrap().afi(), AFI::Ipv4); + assert_eq!(upd.mp_withdrawals().unwrap().unwrap().safi(), SAFI::Multicast); + assert_eq!(upd.mp_withdrawals().unwrap().iter().count(), 1); + } + + #[test] + fn evpn() { + let buf = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x89, 0x02, 0x00, 0x00, 0x00, 0x72, 0x40, + 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x05, + 0x04, 0x00, 0x00, 0x00, 0x64, 0xc0, 0x10, 0x08, + 0x00, 0x02, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, + 0x80, 0x09, 0x04, 0x78, 0x00, 0x02, 0x05, 0x80, + 0x0a, 0x04, 0x78, 0x00, 0x01, 0x01, 0x90, 0x0e, + 0x00, 0x47, 0x00, 0x19, 0x46, 0x04, 0x78, 0x00, + 0x02, 0x05, 0x00, 0x01, 0x19, 0x00, 0x01, 0x78, + 0x00, 0x02, 0x05, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x49, 0x35, 0x01, 0x02, 0x21, + 0x00, 0x01, 0x78, 0x00, 0x02, 0x05, 0x00, 0x64, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x30, 0x00, + 0x0c, 0x29, 0x82, 0xc2, 0xa9, 0x00, 0x49, 0x30, + 0x01 + ]; + + use crate::bgp::message::nlri::EvpnRouteType; + + let upd = UpdateMessage::from_octets(&buf, SessionConfig::modern()) + .unwrap(); + + for n in upd.announcements().unwrap() { + println!("{:?}", n.unwrap()); + } + let mut announcements = upd.announcements().unwrap(); + if let Some(Ok(Nlri::Evpn(e))) = announcements.next() { + assert_eq!( + e.route_type(), + EvpnRouteType::EthernetAutoDiscovery + ) + } else { panic!() } + if let Some(Ok(Nlri::Evpn(e))) = announcements.next() { + assert_eq!( + e.route_type(), + EvpnRouteType::MacIpAdvertisement) + } else { panic!() } + assert!(announcements.next().is_none()); + + assert_eq!( + upd.mp_next_hop().unwrap(), + Some(NextHop::Evpn(Ipv4Addr::from_str("120.0.2.5").unwrap().into())) + ); + } + + // the MP_REACH_NLRI currently ends up as a ::Invalid path attribute + // variant, so the call to .mp_announcements() yields a Ok(None) and thus + // the second unwrap fails. Therefor, ignore for now: + #[ignore = "need to rethink this one because of API change"] + #[test] + fn unknown_afi_safi_announcements() { + // botched BGP UPDATE message containing MP_REACH_NLRI path attribute, + // comprising 5 (originally) IPv6 unicast NLRIs, but with the AFI/SAFI + // changed to 255/1 + // and + // 2 conventional nlri + let buf = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x88 + 6, 0x02, 0x00, 0x00, 0x00, 0x71, 0x80, + 0x0e, 0x5a, + //0x00, 0x02, + 0x00, 0xff, + 0x01, + 0x20, 0xfc, 0x00, + 0x00, 0x10, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, + 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x40, 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, + 0x00, 0x40, 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, + 0x00, 0x01, 0x40, 0x20, 0x01, 0x0d, 0xb8, 0xff, + 0xff, 0x00, 0x02, 0x40, 0x20, 0x01, 0x0d, 0xb8, + 0xff, 0xff, 0x00, 0x03, 0x40, 0x01, 0x01, 0x00, + 0x40, 0x02, 0x06, 0x02, 0x01, 0x00, 0x00, 0x00, + 0xc8, 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, + // conventional NLRI + 16, 10, 10, // 10.10.0.0/16 + 16, 10, 11, // 10.11.0.0/16 + ]; + //let update: UpdateMessage<_> = parse_msg(&buf); + let update = UpdateMessage::from_octets( + &buf, + SessionConfig::modern() + ).unwrap(); + + assert_eq!(update.mp_announcements().unwrap().unwrap().iter().count(), 1); + assert!(update.mp_announcements().unwrap().unwrap().iter().next().unwrap().is_err()); + + // We expect only the two conventional announcements here: + assert_eq!(update.unicast_announcements().unwrap().count(), 2); + + } + + #[test] + fn invalid_nlri_length_in_announcements() { + // botched BGP UPDATE message containing MP_REACH_NLRI path attribute, + // comprising 5 (originally) IPv6 unicast NLRIs, with the second one + // having a prefix len of 129 + // and + // 2 conventional nlri + let buf = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x88 + 6, 0x02, 0x00, 0x00, 0x00, 0x71, 0x80, + 0x0e, 0x5a, + 0x00, 0x02, // AFI + 0x01, // SAFI + // NextHop: + 0x20, + 0xfc, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, // reserved byte + 0x80, + 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x81, // was 0x40, changed to 0x81 (/129) + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x00, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x01, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x02, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x03, + 0x40, + 0x01, 0x01, 0x00, 0x40, 0x02, 0x06, 0x02, 0x01, 0x00, 0x00, 0x00, + 0xc8, 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, + // conventional NLRI + 16, 10, 10, // 10.10.0.0/16 + 16, 10, 11, // 10.11.0.0/16 + ]; + + let update = UpdateMessage::from_octets( + &buf, + SessionConfig::modern() + ).unwrap(); + + assert!(matches!( + update.announcements_vec(), + Err(ParseError::Form(..)) + )); + + assert!(matches!( + update.unicast_announcements_vec(), + Err(ParseError::Form(..)) + )); + + assert_eq!(update.announcements().unwrap().count(), 4); + assert_eq!(update.unicast_announcements().unwrap().count(), 4); + + assert!( + update.unicast_announcements().unwrap().eq( + [ + Ok(BasicNlri::new(Prefix::from_str("fc00::10/128").unwrap())), + Err(ParseError::form_error("illegal byte size for IPv6 NLRI")), + Ok(BasicNlri::new(Prefix::from_str("10.10.0.0/16").unwrap())), + Ok(BasicNlri::new(Prefix::from_str("10.11.0.0/16").unwrap())), + ] + ) + ); + } + + #[test] + fn unknown_afi_safi_withdrawals() { + // botched BGP UPDATE with 4 MP_UNREACH_NLRI + let buf = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x41, 0x02, 0x00, 0x00, 0x00, 0x2a, 0x80, + 0x0f, 0x27, + //0x00, 0x02, // AFI + 0x00, 0xff, // changed to unknown 255 + 0x01, // SAFI + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x00, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x01, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x02, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x03 + ]; + //let update: UpdateMessage<_> = parse_msg(&buf); + let update = UpdateMessage::from_octets( + &buf, + SessionConfig::modern() + ).unwrap(); + + assert!(matches!( + update.unicast_announcements_vec(), + Ok(Vec { .. }) + )); + + assert!(matches!( + update.unicast_withdrawals_vec(), + Err(ParseError::Unsupported), + )); + + } + + #[test] + fn invalid_withdrawals() { + // botched BGP UPDATE with 4 MP_UNREACH_NLRI + let buf = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x41, 0x02, 0x00, 0x00, 0x00, 0x2a, 0x80, + 0x0f, 0x27, + 0x00, 0x02, + 0x01, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x00, + //0x40, + 0x41, // changed to 0x41, leading to a parse error somewhere in + // the remainder of the attribute. + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x01, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x02, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x03 + ]; + + let update = UpdateMessage::from_octets( + &buf, + SessionConfig::modern() + ).unwrap(); + + assert!(matches!( + update.unicast_announcements_vec(), + Ok(Vec { .. }) + )); + + assert!(matches!( + update.unicast_withdrawals_vec(), + Err(ParseError::Form(..)) + )); + + assert_eq!(update.withdrawals().unwrap().count(), 2); + assert_eq!(update.unicast_withdrawals().unwrap().count(), 2); + + assert!( + update.unicast_withdrawals().unwrap().eq( + [ + Ok(BasicNlri::new( + Prefix::from_str("2001:db8:ffff::/64").unwrap()) + ), + Err(ParseError::form_error("non-zero host portion")), + ] + ) + ); + + } + + #[test] + fn format_as_pcap() { + let buf = vec![ + // Two identical BGP UPDATEs + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x37, 0x02, + 0x00, 0x00, 0x00, 0x1b, 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, + 0x06, 0x02, 0x01, 0x00, 0x01, 0x00, 0x00, 0x40, 0x03, 0x04, + 0x0a, 0xff, 0x00, 0x65, 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x20, 0x0a, 0x0a, 0x0a, 0x02, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x37, 0x02, + 0x00, 0x00, 0x00, 0x1b, 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, + 0x06, 0x02, 0x01, 0x00, 0x01, 0x00, 0x00, 0x40, 0x03, 0x04, + 0x0a, 0xff, 0x00, 0x65, 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x20, 0x0a, 0x0a, 0x0a, 0x02, + ]; + + let bytes = Bytes::from(buf); + let mut parser = Parser::from_ref(&bytes); + let update = UpdateMessage::parse( + &mut parser, + SessionConfig::modern() + ).unwrap(); + + let update2 = UpdateMessage::from_octets( + parser.peek_all(), + SessionConfig::modern() + ).unwrap(); + + assert_eq!(update.fmt_pcap_string(), update2.fmt_pcap_string()); + } + + #[test] + fn v4_mpls_unicast() { + let raw = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x5c, 0x02, 0x00, 0x00, 0x00, 0x45, 0x80, + 0x0e, 0x31, 0x00, 0x01, 0x04, 0x04, 0x0a, 0x07, + 0x08, 0x08, 0x00, 0x38, 0x01, 0xf4, 0x01, 0x0a, + 0x00, 0x00, 0x09, 0x32, 0x01, 0xf4, 0x11, 0xc6, + 0x33, 0x64, 0x00, 0x32, 0x01, 0xf4, 0x21, 0xc6, + 0x33, 0x64, 0x40, 0x32, 0x01, 0xf4, 0x31, 0xc6, + 0x33, 0x64, 0x80, 0x32, 0x01, 0xf4, 0x91, 0xc6, + 0x33, 0x64, 0xc0, 0x40, 0x01, 0x01, 0x00, 0x40, + 0x02, 0x0a, 0x02, 0x02, 0x00, 0x00, 0x01, 0x2c, + 0x00, 0x00, 0x01, 0xf4 + ]; + + let upd = UpdateMessage::from_octets( + &raw, + SessionConfig::modern() + ).unwrap(); + if let Ok(Some(NextHop::Unicast(a))) = upd.mp_next_hop() { + assert_eq!(a, Ipv4Addr::from_str("10.7.8.8").unwrap()); + } else { + panic!("wrong"); + } + let mut ann = upd.mp_announcements().unwrap().unwrap().iter(); + if let Some(Ok(Nlri::Mpls(n1))) = ann.next() { + assert_eq!( + n1.basic().prefix(), + Prefix::from_str("10.0.0.9/32").unwrap() + ); + assert_eq!( + n1.labels().as_ref(), + &[0x01, 0xf4, 0x01] // single label: [2012] + //Labels::from(..), + ); + } else { + panic!("wrong"); + } + + // and 4 more: + assert_eq!(ann.count(), 4); + } + + #[test] + fn v6_mpls_vpn_unicast() { + + // BGP UPDATE for 2/128, one single announced NLRI + let raw = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x9a, 0x02, 0x00, 0x00, 0x00, 0x83, 0x80, + 0x0e, 0x39, 0x00, 0x02, 0x80, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x0a, 0x00, 0x00, 0x02, 0x00, 0xd8, + 0x00, 0x7d, 0xc1, 0x00, 0x00, 0x00, 0x64, 0x00, + 0x00, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x01, 0x01, 0x00, 0x40, + 0x02, 0x06, 0x02, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x05, 0x04, 0x00, 0x00, 0x00, 0x64, 0xc0, 0x10, + 0x18, 0x00, 0x02, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x09, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x0b, 0x0a, 0x00, 0x00, 0x02, 0x00, + 0x01, 0xc0, 0x14, 0x0e, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, + 0x00, 0x02 + ]; + + let upd = UpdateMessage::from_octets( + &raw, + SessionConfig::modern() + ).unwrap(); + if let Ok(Some(NextHop::Ipv6MplsVpnUnicast(rd, a))) = upd.mp_next_hop() { + assert_eq!(rd, RouteDistinguisher::new(&[0; 8])); + assert_eq!(a, Ipv6Addr::from_str("::ffff:10.0.0.2").unwrap()); + } else { + panic!("wrong"); + } + let mut ann = upd.mp_announcements().unwrap().unwrap().iter(); + if let Some(Ok(Nlri::MplsVpn(n1))) = ann.next() { + assert_eq!( + n1.basic().prefix(), + Prefix::from_str("fc00::1/128").unwrap() + ); + assert_eq!( + n1.labels().as_ref(), + &[0x00, 0x7d, 0xc1] // single label: [2012] + //Labels::from([2012]), + ); + assert_eq!( + n1.rd(), + //RouteDistinguisher::from_str("100:1".unwrap()) + RouteDistinguisher::new(&[0, 0, 0, 100, 0, 0, 0, 1]) + ); + } else { + panic!("wrong"); + } + + assert!(ann.next().is_none()); + } + + #[test] + fn debug_fuzz_unknown_afi_safi() { + let raw = vec![ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 0, 36, 0, 0, 0, 0, 13, 255, 255, 0, 0, 0, 14, 6, 1, + 0, 4, 0, 1, 0, 0 + ]; + + let upd = UpdateMessage::from_octets(&raw, SessionConfig::modern()) + .unwrap(); + + if let Ok(pas) = upd.path_attributes() { + for pa in pas.into_iter().flatten() { + let _ = pa.to_owned(); + } + } + } + + + #[test] + fn debug_fuzz_assert_flowspecnlri() { + let raw = vec![ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 0, 36, 0, 0, 0, 0, 13, 255, 255, 0, 0, 0, 15, 6, 0, + 2, 133, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 255, 255, 255, 254 + ]; + + let upd = UpdateMessage::from_octets(&raw, SessionConfig::modern()) + .unwrap(); + + if let Ok(pas) = upd.path_attributes() { + for pa in pas.into_iter().flatten() { + let _ = pa.to_owned(); + } + } + } + + #[test] + fn session_addpaths() { + let mut aps = SessionAddpaths::new(); + aps.set(AfiSafi::Ipv4Unicast, AddpathDirection::SendReceive); + aps.set(AfiSafi::L2VpnEvpn, AddpathDirection::Receive); + assert_eq!( + aps.get(AfiSafi::Ipv4Unicast), Some(AddpathDirection::SendReceive) + ); + assert_eq!( + aps.get(AfiSafi::L2VpnEvpn), Some(AddpathDirection::Receive) + ); + assert_eq!( + aps.get(AfiSafi::Ipv6Unicast), None + ); + + assert_eq!(aps.enabled_addpaths().count(), 2); + + let inv_aps = aps.inverse(); + assert_eq!(inv_aps.enabled_addpaths().count(), 16 - 2); } + #[test] + fn session_config_addpaths() { + let mut sc = SessionConfig::modern(); + sc.add_addpath_rxtx(AfiSafi::Ipv4Unicast); + sc.add_addpath_rxtx(AfiSafi::Ipv6MplsUnicast); + assert_eq!(sc.enabled_addpaths().count(), 2); + sc.inverse_addpath(AfiSafi::Ipv4Unicast); + assert_eq!(sc.enabled_addpaths().count(), 1); + sc.inverse_addpath(AfiSafi::Ipv4Unicast); + sc.inverse_addpaths(); + assert_eq!(sc.enabled_addpaths().count(), 14); + sc.inverse_addpath(AfiSafi::Ipv4Unicast); + assert_eq!(sc.enabled_addpaths().count(), 15); + } } diff --git a/src/bgp/message/update_builder.rs b/src/bgp/message/update_builder.rs new file mode 100644 index 00000000..fd80456d --- /dev/null +++ b/src/bgp/message/update_builder.rs @@ -0,0 +1,2651 @@ +use std::fmt; +use std::net::{IpAddr, Ipv6Addr}; +use std::collections::BTreeMap; + +use bytes::BytesMut; +use octseq::{FreezeBuilder, Octets, OctetsBuilder, OctetsFrom, OctetsInto, ShortBuf}; +use log::warn; + +use crate::bgp::aspath::HopPath; +use crate::bgp::communities::StandardCommunity; +use crate::bgp::message::{Header, MsgType, SessionConfig, UpdateMessage}; +use crate::bgp::message::nlri::Nlri; +use crate::bgp::message::update::{AFI, SAFI, NextHop}; +use crate::bgp::types::OriginType; +use crate::util::parser::ParseError; + +//use rotonda_fsm::bgp::session::AgreedConfig; + +use crate::bgp::path_attributes::{ + AsPath, + Communities, + LocalPref, + MpReachNlri, + MpUnreachNlri, + MultiExitDisc, + NextHop as NextHopAttribute, + Origin, + PathAttribute, PathAttributeType +}; + +//------------ UpdateBuilder ------------------------------------------------- +#[derive(Debug)] +pub struct UpdateBuilder { + target: Target, + //config: AgreedConfig, //FIXME this lives in rotonda_fsm, but that + //depends on routecore. Sounds like a depedency hell. + announcements: Vec>>, + withdrawals: Vec>>, + addpath_enabled: Option, // for conventional nlri (unicast v4) + // attributes: + attributes: BTreeMap, +} + +impl UpdateBuilder { + const MAX_PDU: usize = 4096; // XXX should come from NegotiatedConfig +} + +impl UpdateBuilder +where Target: octseq::Truncate +{ + + pub fn from_target(mut target: Target) -> Result { + target.truncate(0); + let mut h = Header::<&[u8]>::new(); + h.set_length(19 + 2 + 2); + h.set_type(MsgType::Update); + let _ =target.append_slice(h.as_ref()); + + Ok(UpdateBuilder { + target, + announcements: Vec::new(), + withdrawals: Vec::new(), + addpath_enabled: None, + attributes: BTreeMap::new(), + }) + } + + /// Creates an UpdateBuilder with Path Attributes from an UpdateMessage + /// + /// + pub fn from_update_message<'a, Octs: 'a + Octets>( + pdu: &'a UpdateMessage, + _session_config: SessionConfig, + target: Target + ) -> Result, ComposeError> + where + Vec: OctetsFrom> + { + + let mut builder = UpdateBuilder::from_target(target) + .map_err(|_| ComposeError::ShortBuf)?; + + // Add all path attributes, except for MP_(UN)REACH_NLRI, ordered by + // their type_code. + for pa in pdu.path_attributes()? { + if let Ok(pa) = pa { + if pa.type_code() != PathAttributeType::MpReachNlri + && pa.type_code() != PathAttributeType::MpUnreachNlri + { + if let PathAttributeType::Invalid(n) = pa.type_code() { + warn!("invalid PA {}:\n{}", n, pdu.fmt_pcap_string()); + } + builder.add_attribute(pa.to_owned()?)?; + } + } else { + return Err(ComposeError::InvalidAttribute); + } + } + + Ok(builder) + } + + //--- Withdrawals + + pub fn add_withdrawal( + &mut self, + withdrawal: &Nlri + ) -> Result<(), ComposeError> + where + Vec: OctetsFrom, + T: Octets, + { + match *withdrawal { + Nlri::Unicast(b) => { + if b.is_v4() { + if let Some(addpath_enabled) = self.addpath_enabled { + if addpath_enabled != b.is_addpath() { + return Err(ComposeError::IllegalCombination) + } + } else { + self.addpath_enabled = Some(b.is_addpath()); + } + + self.withdrawals.push( + <&Nlri as OctetsInto>>>::try_octets_into(withdrawal).map_err(|_| ComposeError::todo() )? + ); + } else { + // Nlri::Unicast only holds IPv4 and IPv6, so this must be + // IPv6. + + // Check if we have an attribute in self.attributes + // Again, like with Communities, we cant rely on + // entry().or_insert(), because that does not do a max pdu + // length check and it does not allow us to error out. + + if !self.attributes.contains_key( + &PathAttributeType::MpUnreachNlri + ) { + self.add_attribute(MpUnreachNlri::new( + MpUnreachNlriBuilder::new( + AFI::Ipv6, SAFI::Unicast, + b.is_addpath() + ) + ).into())?; + } + let pa = self.attributes.get_mut(&PathAttributeType::MpUnreachNlri) + .unwrap(); // Just added it, so we know it is there. + + if let PathAttribute::MpUnreachNlri(ref mut pa) = pa { + let builder = pa.as_mut(); + + if !builder.valid_combination( + AFI::Ipv6, SAFI::Unicast, b.is_addpath() + ) { + // We are already constructing a + // MP_UNREACH_NLRI but for a different + // AFI,SAFI than the prefix in `withdrawal`, + // or we are mixing addpath with non-addpath. + return Err(ComposeError::IllegalCombination); + } + + builder.add_withdrawal(withdrawal); + + } else { + unreachable!() + } + } + + } + _ => todo!() // TODO + }; + Ok(()) + } + + pub fn withdrawals_from_iter(&mut self, withdrawals: I) + -> Result<(), ComposeError> + where I: IntoIterator>> + { + withdrawals.into_iter().try_for_each(|w| self.add_withdrawal(&w) )?; + Ok(()) + } + + pub fn append_withdrawals(&mut self, withdrawals: &mut Vec>) + -> Result<(), ComposeError> + where + Vec: OctetsFrom, + T: Octets, + { + for (idx, w) in withdrawals.iter().enumerate() { + if let Err(e) = self.add_withdrawal(w) { + withdrawals.drain(..idx); + return Err(e); + } + } + Ok(()) + } + + //--- Path Attributes + + + /// Upsert a Path Attribute. + /// + /// Insert a new, or update an existing Path Attribute in this builder. If + /// the new Path Attribute would cause the total PDU length to exceed the + /// maximum, a `ComposeError::PduTooLarge` is returned. + + pub fn add_attribute(&mut self, pa: PathAttribute) + -> Result<(), ComposeError> + { + if let PathAttribute::Invalid(..) = pa { + warn!( + "adding Invalid attribute to UpdateBuilder: {}", + &pa.type_code() + ); + } + if let Some(existing_pa) = self.attributes.get_mut(&pa.type_code()) { + *existing_pa = pa; + } else { + self.attributes.insert(pa.type_code(), pa); + } + + Ok(()) + } + + pub fn set_origin(&mut self, origin: OriginType) + -> Result<(), ComposeError> + { + self.add_attribute(Origin::new(origin).into()) + } + + pub fn set_aspath(&mut self , aspath: HopPath) + -> Result<(), ComposeError> + { + // XXX there should be a HopPath::compose_len really, instead of + // relying on .to_as_path() first. + if let Ok(wireformat) = aspath.to_as_path::>() { + if wireformat.compose_len() > u16::MAX.into() { + return Err(ComposeError::AttributeTooLarge( + PathAttributeType::AsPath, + wireformat.compose_len() + )); + } + } else { + return Err(ComposeError::InvalidAttribute) + } + + self.add_attribute(AsPath::new(aspath).into()) + } + + pub fn set_nexthop( + &mut self, + nexthop: NextHop + ) -> Result<(), ComposeError> { + // Depending on the variant of `addr` we add/update either: + // - the conventional NEXT_HOP path attribute (IPv4); or + // - we update/create a MpReachNlriBuilder (IPv6) + + match nexthop { + NextHop::Unicast(IpAddr::V4(a)) => { + self.add_attribute(NextHopAttribute::new(a).into())?; + } + n => { + if let Some(PathAttribute::MpReachNlri(ref mut pa)) = self.attributes.get_mut( + &PathAttributeType::MpReachNlri + ) { + let builder = pa.as_mut(); + builder.set_nexthop(n)?; + } else { + self.add_attribute(MpReachNlri::new( + MpReachNlriBuilder::new_for_nexthop(n) + ).into())?; + } + } + + } + Ok(()) + } + pub fn set_nexthop_unicast(&mut self, addr: IpAddr) -> Result<(), ComposeError> { + self.set_nexthop(NextHop::Unicast(addr)) + } + + pub fn set_nexthop_ll_addr(&mut self, addr: Ipv6Addr) + -> Result<(), ComposeError> + { + // We could/should check for addr.is_unicast_link_local() once that + // lands in stable. + + if let Some(ref mut pa) = self.attributes.get_mut( + &PathAttributeType::MpReachNlri + ) { + if let PathAttribute::MpReachNlri(ref mut pa) = pa { + let builder = pa.as_mut(); + match builder.get_nexthop() { + NextHop::Unicast(a) if a.is_ipv6() => { } , + NextHop::Ipv6LL(_,_) => { }, + _ => return Err(ComposeError::IllegalCombination), + } + + builder.set_nexthop_ll_addr(addr); + } else { + unreachable!() + } + } else { + self.add_attribute(MpReachNlri::new( + MpReachNlriBuilder::new( + AFI::Ipv6, SAFI::Unicast, NextHop::Ipv6LL(0.into(), addr), + false + ) + ).into())?; + } + + Ok(()) + } + + pub fn set_multi_exit_disc(&mut self, med: MultiExitDisc) + -> Result<(), ComposeError> + { + self.add_attribute(med.into()) + } + + pub fn set_local_pref(&mut self, local_pref: LocalPref) + -> Result<(), ComposeError> + { + self.add_attribute(local_pref.into()) + } + + + //--- Announcements + + pub fn add_announcement(&mut self, announcement: &Nlri) + -> Result<(), ComposeError> + where + Vec: OctetsFrom, + T: Octets, + { + match announcement { + Nlri::Unicast(b) if b.is_v4() => { + // These go in the conventional NLRI part at the end of the + // PDU. + if let Some(addpath_enabled) = self.addpath_enabled { + if addpath_enabled != b.is_addpath() { + return Err(ComposeError::IllegalCombination) + } + } else { + self.addpath_enabled = Some(b.is_addpath()); + } + + self.announcements.push( + <&Nlri as OctetsInto>>>::try_octets_into(announcement).map_err(|_| ComposeError::todo() ).unwrap() + ); + } + n => { + if !self.attributes.contains_key(&PathAttributeType::MpReachNlri) { + self.add_attribute(MpReachNlri::new( + MpReachNlriBuilder::new_for_nlri(n) + ).into())?; + } + + let pa = self.attributes.get_mut(&PathAttributeType::MpReachNlri) + .unwrap(); // Just added it, so we know it is there. + + if let PathAttribute::MpReachNlri(ref mut pa) = pa { + let builder = pa.as_mut(); + + if !builder.valid_combination(n) { + // We are already constructing a + // MP_UNREACH_NLRI but for a different + // AFI,SAFI than the prefix in `announcement`, + // or we are mixing addpath with non-addpath. + return Err(ComposeError::IllegalCombination); + } + + builder.add_announcement(announcement); + } else { + unreachable!() + } + } + } + + Ok(()) + } + + pub fn announcements_from_iter( + &mut self, announcements: I + ) -> Result<(), ComposeError> + where + I: IntoIterator>, + Vec: OctetsFrom, + T: Octets, + { + announcements.into_iter().try_for_each(|w| self.add_announcement(&w) )?; + Ok(()) + } + + //--- Standard communities + + pub fn add_community(&mut self, community: StandardCommunity) + -> Result<(), ComposeError> + { + if !self.attributes.contains_key(&PathAttributeType::Communities) { + self.add_attribute(Communities::new( + StandardCommunitiesBuilder::new() + ).into())?; + } + let pa = self.attributes.get_mut(&PathAttributeType::Communities) + .unwrap(); // Just added it, so we know it is there. + + if let PathAttribute::Communities(ref mut pa) = pa { + let builder = pa.as_mut(); + builder.add_community(community); + Ok(()) + } else { + unreachable!() + } + } +} + +/* +impl> UpdateBuilder +where Infallible: From<::AppendError> +{ + #[deprecated] + pub fn build_acs(mut self, acs: AttrChangeSet) + -> Result + { + // Withdrawals + let withdraw_len = 0_usize; + // placeholder + let _ = self.target.append_slice(&(withdraw_len as u16).to_be_bytes()); + //self.target.as_mut()[19..=20].copy_from_slice( + // &(withdraw_len as u16).to_be_bytes() + //); + // TODO actual withdrawals + + + // Path Attributes + // flags (from msb to lsb): + // optional + // transitive + // partial + // extended_length (2 octet length) + + let mut total_pa_len = 0_usize; + // Total Path Attribute len place holder: + let _ = self.target.append_slice(&[0x00, 0x00]); + + if let Some(origin) = acs.origin_type.into_opt() { + let attr_flags = 0b0100_0000; + let attr_type_code = PathAttributeType::Origin.into(); + let attr_len = 1_u8; + let _ = self.target.append_slice( + &[attr_flags, attr_type_code, attr_len, origin.into()]); + total_pa_len += 2 + 1 + usize::from(attr_len); + } + + if let Some(as_path) = acs.as_path.into_opt() { + let attr_flags = 0b0101_0000; + let attr_type_code = PathAttributeType::AsPath.into(); + let asp = as_path.into_inner(); + let attr_len = asp.len(); + if u16::try_from(attr_len).is_err() { + return Err(ComposeError::AttributeTooLarge( + PathAttributeType::AsPath, + attr_len + )); + } + let _ = self.target.append_slice(&[attr_flags, attr_type_code]); + let _ = self.target.append_slice(&(attr_len as u16).to_be_bytes()); + let _ = self.target.append_slice(&asp); + + total_pa_len += 2 + 2 + attr_len; + } + + + // XXX the next_hop is either a (conventional, for v4/unicast) path + // attribute, or, it is part of MP_REACH_NLRI. + // Should/must v4/unicast always go in MP_REACH_NLRI when both peers + // sent such capability though? + if let Some(next_hop) = acs.next_hop.into_opt() { + match next_hop { + NextHop::Ipv4(v4addr) => { + let attr_flags = 0b0100_0000; + let attr_type_code = PathAttributeType::NextHop.into(); + let attr_len = 4_u8; + + let _ = self.target.append_slice( + &[attr_flags, attr_type_code, attr_len] + ); + let _ = self.target.append_slice(&v4addr.octets()); + + total_pa_len += 2 + 1 + usize::from(attr_len); + } + _ => todo!() // this is MP_REACH_NLRI territory + } + } + + + if let Some(comms) = acs.standard_communities.into_opt() { + let attr_flags = 0b0100_0000; + let attr_type_code = PathAttributeType::Communities.into(); + let attr_len = match u8::try_from(4 * comms.len()) { + Ok(n) => n, + Err(..) => { + return Err(ComposeError::AttributeTooLarge( + PathAttributeType::Communities, + 4 * comms.len() + )); + } + }; + + let _ = self.target.append_slice( + &[attr_flags, attr_type_code, attr_len] + ); + + for c in comms { + let _ = self.target.append_slice(&c.to_raw()); + } + total_pa_len += 2 + 1 + usize::from(attr_len); + } + + + if u16::try_from(total_pa_len).is_err() { + return Err(ComposeError::AttributesTooLarge(total_pa_len)); + } + + // update total path attribute len: + self.target.as_mut()[21+withdraw_len..21+withdraw_len+2] + .copy_from_slice(&(total_pa_len as u16).to_be_bytes()); + + + // NLRI + // TODO this all needs to be a lot more sophisticated: + // - prefixes can not occur in both withdrawals and nlris, so check + // for that; + // - non v4/unicast NLRI should go in MP_REACH_NLRI, not here (at the + // end of the PDU); + // - we should be able to put multiple NLRI in one UPDATE, though + // currently the AttrChangeSet only holds one; + // - probably more + + let mut nlri_len = 0; + + if let Some(nlri) = acs.nlri.into_opt() { + match nlri { + Nlri::Unicast(b) => { + if let Some(id) = b.path_id() { + let _ = self.target.append_slice(&id.to_raw()); + nlri_len += 4; + } + match b.prefix().addr_and_len() { + (std::net::IpAddr::V4(addr), len) => { + let _ = self.target.append_slice(&[len]); + let len_bytes = (usize::from(len)-1) / 8 + 1; + let _ = self.target.append_slice( + &addr.octets()[0..len_bytes] + ); + nlri_len += 1 + len_bytes; + } + _ => todo!() + } + } + _ => todo!() + } + } + + // update pdu len + let msg_len = 19 + + 2 + withdraw_len + + 2 + total_pa_len + + nlri_len + ; + + if msg_len > 4096 { + // TODO handle Extended Messages (max pdu size 65535) + return Err(ComposeError::PduTooLarge(msg_len)); + } + + //if u16::try_from(msg_len).is_err() { + // return Err(ComposeError::PduTooLarge(msg_len)); + //} + + self.target.as_mut()[16..=17].copy_from_slice( + &(msg_len as u16).to_be_bytes() + ); + + Ok(self.target) + } +} +*/ + +impl UpdateBuilder +{ + pub fn into_message(self) -> + Result::Octets>, ComposeError> + where + Target: OctetsBuilder + FreezeBuilder + AsMut<[u8]> + octseq::Truncate, + ::Octets: Octets, + { + self.is_valid()?; + // FIXME this SessionConfig::modern should come from self + Ok(UpdateMessage::from_octets( + self.finish().map_err(|_| ShortBuf)?, SessionConfig::modern() + )?) + } + + // Check whether the combination of NLRI and attributes would produce a + // valid UPDATE pdu. + fn is_valid(&self) -> Result<(), ComposeError> { + // If we have builders for MP_(UN)REACH_NLRI, they should carry + // prefixes. + + if let Some(pa) = self.attributes.get( + &PathAttributeType::MpReachNlri + ) { + if let PathAttribute::MpReachNlri(pa) = pa { + if pa.as_ref().is_empty() { + return Err(ComposeError::EmptyMpReachNlri); + } + } else { + unreachable!() + } + } + + if let Some(pa) = self.attributes.get( + &PathAttributeType::MpUnreachNlri + ) { + if let PathAttribute::MpUnreachNlri(pa) = pa { + // FIXME an empty MP_UNREACH_NLRI can be valid when signaling + // EoR, but then it has to be the only path attribute. + if pa.as_ref().is_empty() { + return Err(ComposeError::EmptyMpUnreachNlri); + } + } else { + unreachable!() + } + } + + Ok(()) + } + + fn calculate_pdu_length(&self) -> usize { + // Marker, length and type. + let mut res: usize = 16 + 2 + 1; + + // Withdrawals, 2 bytes for length + N bytes for NLRI: + res += 2 + self.withdrawals.iter() + .fold(0, |sum, w| sum + w.compose_len()); + + // Path attributes, 2 bytes for length + N bytes for attributes: + res += 2 + self.attributes.values() + .fold(0, |sum, pa| sum + pa.compose_len()); + + // Announcements, no length bytes: + res += self.announcements.iter() + .fold(0, |sum, a| sum + a.compose_len()); + + res + } + + fn larger_than(&self, max: usize) -> bool { + // TODO add more 'quick returns' here, e.g. for MpUnreachNlri or + // conventional withdrawals/announcements. + + if let Some(PathAttribute::MpReachNlri(b)) = self.attributes.get(&PathAttributeType::MpReachNlri) { + if b.as_ref().announcements.len() * 2 > max { + return true + } + } + self.calculate_pdu_length() > max + } + + /// Compose the PDU, returns the builder if it exceeds the max PDU size. + /// + /// Note that `UpdateBuilder` implements `IntoIterator`, which is perhaps + /// more appropriate for many use cases. + pub fn take_message(mut self) -> ( + Result::Octets>, ComposeError>, + Option + ) + where + Target: Clone + OctetsBuilder + FreezeBuilder + AsMut<[u8]> + octseq::Truncate, + ::Octets: Octets + { + if !self.larger_than(Self::MAX_PDU) { + return (self.into_message(), None) + + } + + // It does not fit in a single PDU. Figure out where to split things. + // + // Scenarios where we can expect large PDUs: + // - many withdrawals of an entire RIB (e.g. when a session goes down) + // - many announcements when a new session is established, or doing a + // route refresh + // + // There might be specific scenarios where both many withdrawals and + // announcements need to be built. But we should be able to split + // those up in withdrawal-only and announcement-only PDUs, presumably? + + // Withdrawals come without path attributes. But, MP_UNREACH_NLRI + // contains (non v4-unicast) withdrawals which are... represented as a + // path attribute. + + + // Scenario 1: many conventional withdrawals + // If we have any withdrawals, they can go by themselves. + // First naive approach: we split off at most 450 NLRI. In the extreme + // case of AddPathed /32's this would still fit in a 4096 PDU. + + if !self.withdrawals.is_empty() { + //let withdrawal_len = self.withdrawals.iter() + // .fold(0, |sum, w| sum + w.compose_len()); + + let split_at = std::cmp::min(self.withdrawals.len() / 2, 450); + let this_batch = self.withdrawals.drain(..split_at); + let mut builder = Self::from_target(self.target.clone()).unwrap(); + + builder.withdrawals = this_batch.collect(); + + return (builder.into_message(), Some(self)); + } + + // Scenario 2: many withdrawals in MP_UNREACH_NLRI + // At this point, we have no conventional withdrawals anymore. + + let maybe_pdu = + if let Some(PathAttribute::MpUnreachNlri(b)) = self.attributes.get_mut(&PathAttributeType::MpUnreachNlri) { + let unreach_builder = b.as_mut(); + let mut split_at = 0; + if !unreach_builder.withdrawals.is_empty() { + let mut compose_len = 0; + for (idx, w) in unreach_builder.withdrawals.iter().enumerate() { + compose_len += w.compose_len(); + if compose_len > 4000 { + split_at = idx; + break; + } + } + + let this_batch = unreach_builder.split(split_at); + let mut builder = Self::from_target(self.target.clone()).unwrap(); + builder.add_attribute(MpUnreachNlri::new(this_batch).into()).unwrap(); + + Some(builder.into_message()) + } else { + None + } + } else { + None + } + ; + // Bit of a clumsy workaround as we can not return Some(self) from + // within the if let ... self.attributes.get_mut above + if let Some(pdu) = maybe_pdu { + return (pdu, Some(self)) + } + + // Scenario 3: many conventional announcements + // Similar but different to the scenario of many withdrawals, as + // announcements are tied to the attributes. At this point, we know we + // have no conventional withdrawals left, and no MP_UNREACH_NLRI. + + if !self.announcements.is_empty() { + //let announcement_len = self.announcements.iter() + // .fold(0, |sum, a| sum + a.compose_len()); + + let split_at = std::cmp::min(self.announcements.len() / 2, 450); + let this_batch = self.announcements.drain(..split_at); + let mut builder = Self::from_target(self.target.clone()).unwrap(); + + builder.announcements = this_batch.collect(); + builder.attributes = self.attributes.clone(); + + return (builder.into_message(), Some(self)); + + } + + // Scenario 4: many MP_REACH_NLRI announcements + // This is tricky: we need to split the MP_REACH_NLRI attribute, and + // clone the other attributes. + // At this point, we have no conventional withdrawals/announcements or + // MP_UNREACH_NLRI path attribute. + + // FIXME this works, but is still somewhat slow for very large input + // sets. The first PDUs take longer to construct than the later ones. + // Flamegraph currently hints at the fn split on MpReachNlriBuilder. + + let maybe_pdu = + if let Some(PathAttribute::MpReachNlri(b)) = self.attributes.remove(&PathAttributeType::MpReachNlri) { + let mut reach_builder = b.inner(); + let mut split_at = 0; + + let other_attrs_len = self.attributes.values().fold(0, |sum, pa| sum + pa.compose_len()); + let limit = Self::MAX_PDU + // marker/len/type, wdraw len, total pa len + - (16 + 2 + 1 + 2 + 2) + // MP_REACH_NLRI flags/type/len/afi/safi/rsrved, next_hop + - 8 - reach_builder.get_nexthop().compose_len() + - other_attrs_len; + + if !reach_builder.announcements.is_empty() { + let mut compose_len = 0; + for (idx, a) in reach_builder.announcements.iter().enumerate() { + compose_len += a.compose_len(); + if compose_len > limit { + split_at = idx; + break; + } + } + + let this_batch = reach_builder.split(split_at); + let mut builder = Self::from_target(self.target.clone()).unwrap(); + builder.attributes = self.attributes.clone(); + self.add_attribute(MpReachNlri::new(reach_builder).into()).unwrap(); + builder.add_attribute(MpReachNlri::new(this_batch).into()).unwrap(); + + Some(builder.into_message()) + } else { + None + } + } else { + None + } + ; + if let Some(pdu) = maybe_pdu { + return (pdu, Some(self)) + } + + + // If we end up here, there is something other than + // announcements/withdrawals causing very large PDUs. The only thing + // that comes to mind is any type of Communities, but we can not split + // those without altering the information that the user intends to + // convey. So, we error out. + + let pdu_len = self.calculate_pdu_length(); + (Err(ComposeError::PduTooLarge(pdu_len)), None) + + } + + + /// Turn the builder into a vec of one or more UpdateMessages. + /// + /// Note that `UpdateBuilder` implements `IntoIterator`, which is perhaps + /// more appropriate for many use cases. + pub fn into_messages(self) -> Result< + Vec::Octets>>, + ComposeError + > + where + Target: Clone + OctetsBuilder + FreezeBuilder + AsMut<[u8]> + octseq::Truncate, + ::Octets: Octets + { + let mut res = Vec::new(); + let mut remainder = Some(self); + loop { + if remainder.is_none() { + return Ok(res); + } + let (pdu, new_remainder) = remainder.take().unwrap().take_message(); + match pdu { + Ok(pdu) => { + res.push(pdu); + remainder = new_remainder; + } + Err(e) => { + warn!("error in into_messages(): {}", e); + return Err(e) + } + } + } + } + + fn finish(mut self) + -> Result<::Octets, Target::AppendError> + where + Target: OctetsBuilder + FreezeBuilder + AsMut<[u8]> + octseq::Truncate + { + let total_pdu_len = self.calculate_pdu_length(); + let mut header = Header::for_slice_mut(self.target.as_mut()); + header.set_length(u16::try_from(total_pdu_len).unwrap()); + + // `withdrawals_len` is checked to be <= 4096 or <= 65535 + // so it will always fit in a u16. + let withdrawals_len = self.withdrawals.iter() + .fold(0, |sum, w| sum + w.compose_len()); + self.target.append_slice( + &u16::try_from(withdrawals_len).unwrap().to_be_bytes() + )?; + + for w in &self.withdrawals { + match w { + Nlri::Unicast(b) if b.is_v4() => { + b.compose(&mut self.target)?; + }, + _ => todo!(), + } + } + + + // We can do these unwraps because of the checks in the add/append + // methods. + + // attributes_len` is checked to be <= 4096 or <= 65535 + // so it will always fit in a u16. + let attributes_len = self.attributes.values() + .fold(0, |sum, a| sum + a.compose_len()); + let _ = self.target.append_slice( + &u16::try_from(attributes_len).unwrap().to_be_bytes() + ); + + self.attributes.iter().try_for_each( + |(_tc, pa)| pa.compose(&mut self.target) + )?; + + // XXX Here, in the conventional NLRI field at the end of the PDU, we + // write IPv4 Unicast announcements. But what if we have agreed to do + // MP for v4/unicast, should these announcements go in the + // MP_REACH_NLRI attribute then instead? + for a in &self.announcements { + match a { + Nlri::Unicast(b) if b.is_v4() => { + b.compose(&mut self.target)?; + }, + _ => unreachable!(), + } + } + + Ok(self.target.freeze()) + } +} + +pub struct PduIterator { + builder: Option> +} + +impl Iterator for PduIterator +where + Target: Clone + OctetsBuilder + FreezeBuilder + AsMut<[u8]> + octseq::Truncate, + ::Octets: Octets +{ + type Item = Result< + UpdateMessage<::Octets>, + ComposeError + >; + + fn next(&mut self) -> Option { + self.builder.as_ref()?; + let (res, remainder) = self.builder.take().unwrap().take_message(); + self.builder = remainder; + Some(res) + } + +} +impl IntoIterator for UpdateBuilder + where + Target: Clone + OctetsBuilder + FreezeBuilder + AsMut<[u8]> + octseq::Truncate, + ::Octets: Octets +{ + type Item = Result< + UpdateMessage<::Octets>, + ComposeError + >; + type IntoIter = PduIterator; + + fn into_iter(self) -> Self::IntoIter { + PduIterator { builder: Some(self) } + } + +} + +impl UpdateBuilder> { + pub fn new_vec() -> Self { + Self::from_target(Vec::with_capacity(23)).unwrap() + } +} + +impl UpdateBuilder { + pub fn new_bytes() -> Self { + Self::from_target(BytesMut::new()).unwrap() + } +} + + +//------------ MpReachNlriBuilder -------------------------------------------- +// See notes at MpUnreachNlriBuilder, these also apply here. +// +// Additionally, the MpReachNlri attribute contains the nexthop information. +// The nexthop semantics can not always be derived from the AFI/SAFI tuple, +// i.e. for IPv6 unicast the nexthop might contain two addresses (one global +// and one link-local). The global address will always be there, the +// link-local is optional. +// +// Now whether or not the additional link-local is necessary might not be +// known at the time we build the UPDATE PDU. Therefore we reserve 16 bytes +// for a LL address, to prevent ending up with a PDU larger than the max pdu +// size allowed on the BGP session. + + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct MpReachNlriBuilder { + announcements: Vec>>, + len: usize, // size of value, excluding path attribute flags+type_code+len + extended: bool, + afi: AFI, + safi: SAFI, + nexthop: NextHop, + addpath_enabled: bool, +} + +impl MpReachNlriBuilder { + // MP_REACH_NLRI and MP_UNREACH_NLRI can only occur once (like any path + // attribute), and can carry only a single tuple of (AFI, SAFI). + // My interpretation of RFC4760 means one can mix conventional + // NLRI/withdrawals (so, v4 unicast) with one other (AFI, SAFI) in an + // MP_(UN)REACH_NLRI path attribute. + // Question is: can one also put (v4, unicast) in an MP_* attribute, and, + // then also in the conventional part (at the end of the PDU)? + + // Minimal required size for a meaningful MP_REACH_NLRI. This comprises + // the attribute flags/size/type (3 bytes), a IPv6 nexthop (17), reserved + // byte (1) and then space for at least an IPv6 /48 announcement (7) + + pub(crate) fn new( + afi: AFI, + safi: SAFI, + nexthop: NextHop, + addpath_enabled: bool, + ) -> Self { + MpReachNlriBuilder { + announcements: vec![], + // 3 bytes for AFI+SAFI, nexthop len, reserved byte + len: 3 + nexthop.compose_len() + 1, + extended: false, + afi, + safi, + nexthop, + addpath_enabled + } + } + + pub(crate) fn split(&mut self, n: usize) -> Self { + let this_batch = self.announcements.drain(..n).collect(); + MpReachNlriBuilder { + announcements: this_batch, + ..*self + } + } + + fn new_for_nlri( nlri: &Nlri) -> Self + where T: Octets, + Vec: OctetsFrom + { + let (afi, safi) = nlri.afi_safi(); + let addpath_enabled = nlri.is_addpath(); + let nexthop = NextHop::new(afi, safi); + Self::new(afi, safi, nexthop, addpath_enabled) + } + + fn new_for_nexthop(nexthop: NextHop) -> Self { + let (afi, safi) = nexthop.afi_safi(); + let addpath_enabled = false; + Self::new(afi, safi, nexthop, addpath_enabled) + } + + pub(crate) fn value_len(&self) -> usize { + 2 + 1 + 1 // afi, safi + 1 reserved byte + + self.nexthop.compose_len() + + self.announcements.iter().fold(0, |sum, w| sum + w.compose_len()) + } + + pub(crate) fn is_empty(&self) -> bool { + self.announcements.is_empty() + } + + pub(crate) fn get_nexthop(&self) -> &NextHop { + &self.nexthop + } + + pub(crate) fn set_nexthop(&mut self, nexthop: NextHop) -> Result<(), ComposeError> { + + if !self.announcements.is_empty() && + self.nexthop.afi_safi() != nexthop.afi_safi() + { + return Err(ComposeError::IllegalCombination); + } + + self.len -= self.nexthop.compose_len(); + self.nexthop = nexthop; + self.len += self.nexthop.compose_len(); + Ok(()) + } + + pub(crate) fn set_nexthop_ll_addr(&mut self, addr: Ipv6Addr) { + match self.nexthop { + NextHop::Unicast(IpAddr::V6(a)) => { + self.nexthop = NextHop::Ipv6LL(a, addr); + self.len += 16; + } + NextHop::Ipv6LL(a, _ll) => { + self.nexthop = NextHop::Ipv6LL(a, addr); + } + _ => unreachable!() + } + } + + fn valid_combination( + &self, nlri: &Nlri + ) -> bool { + (self.afi, self.safi) == nlri.afi_safi() + && (self.announcements.is_empty() + || self.addpath_enabled == nlri.is_addpath()) + } + + pub(crate) fn add_announcement(&mut self, announcement: &Nlri) + where + T: Octets, + Vec: OctetsFrom, + { + let announcement_len = announcement.compose_len(); + if !self.extended && self.len + announcement_len > 255 { + self.extended = true; + } + self.len += announcement_len; + self.announcements.push( + <&Nlri as OctetsInto>>>::try_octets_into(announcement).map_err(|_| ComposeError::todo() ).unwrap() + ); + } + + pub(crate) fn compose_value( + &self, + target: &mut Target + ) -> Result<(), Target::AppendError> + { + target.append_slice(&u16::from(self.afi).to_be_bytes())?; + target.append_slice(&[self.safi.into()])?; + self.nexthop.compose(target)?; + + // Reserved byte: + target.append_slice(&[0x00])?; + + for a in &self.announcements { + match a { + Nlri::Unicast(b) => { + if !b.is_v4() { + b.compose(target)?; + } else { + // v4 unicast does not go in the MP_REACH_NLRI path + // attribute but at the end of the UDPATE PDU. The + // MpReachNlriBuilder should never contain v4 unicast + // announcements in its current implementation. + unreachable!(); + } + } + Nlri::Multicast(b) => b.compose(target)?, + Nlri::FlowSpec(f) => f.compose(target)?, + _ => todo!("{:?}", a) + } + } + + Ok(()) + } + +} + +// **NB:** this is bgp::message::update::NextHop +impl NextHop { + fn compose_len(&self) -> usize { + // 1 byte for the length, plus: + 1 + match *self { + NextHop::Unicast(IpAddr::V4(_)) | NextHop::Multicast(IpAddr::V4(_)) => 4, + NextHop::Unicast(IpAddr::V6(_)) | NextHop::Multicast(IpAddr::V6(_)) => 16, + NextHop::Ipv6LL(_, _) => 32, + NextHop::Ipv4MplsVpnUnicast(_rd, _ip4) => 8 + 4, + NextHop::Ipv6MplsVpnUnicast(_rd, _ip6) => 8 + 16, + NextHop::Empty => 0, // FlowSpec + NextHop::Evpn(IpAddr::V4(_)) => 4, + NextHop::Evpn(IpAddr::V6(_)) => 16, + NextHop::Unimplemented(_afi, _safi) => { + warn!( + "unexpected compose_len called on NextHop::Unimplemented \ + returning usize::MAX, this will cause failure." + ); + usize::MAX + } + } + } + + pub(crate) fn compose(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&[u8::try_from(self.compose_len()).unwrap() - 1])?; + match *self { + NextHop::Unicast(IpAddr::V4(a)) | NextHop::Multicast(IpAddr::V4(a)) => + target.append_slice(&a.octets())?, + NextHop::Unicast(IpAddr::V6(a)) | NextHop::Multicast(IpAddr::V6(a)) => + target.append_slice(&a.octets())?, + NextHop::Ipv6LL(a, ll) => { + target.append_slice(&a.octets())?; + target.append_slice(&ll.octets())?; + } + NextHop::Ipv4MplsVpnUnicast(_rd, _ip4) => todo!(), + NextHop::Ipv6MplsVpnUnicast(_rd, _ip6) => todo!(), + NextHop::Evpn(_a) => todo!(), + NextHop::Empty => { }, + NextHop::Unimplemented(_afi, _safi) => todo!(), + } + + Ok(()) + } + +} + + +//------------ MpUnreachNlriBuilder ------------------------------------------ + +// Note that all to-be-withdrawn NLRI should either have no PathID, or have a +// PathID. We can not mix non-addpath and addpath NLRI. +// Note that: +// - add path works on a afi/safi level for a session, so a peer could do +// addpath for v4 unicast but not for v6 unicast. +// - in the Capability in the BGP OPEN, a peer can signal to be able to +// receive, send, or receive+send add path NLRI. So, if we create methods +// that take a NegotiatedConfig, the modification methods like +// add_withdrawal should check whether the remote side is able to receive +// path ids if the Nlri passed to add_withdrawal contains Some(PathId). +// +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct MpUnreachNlriBuilder { + withdrawals: Vec>>, + afi: AFI, + safi: SAFI, + addpath_enabled: bool, +} + +impl MpUnreachNlriBuilder { + pub(crate) fn new(afi: AFI, safi: SAFI, addpath_enabled: bool) -> Self { + MpUnreachNlriBuilder { + withdrawals: vec![], + afi, + safi, + addpath_enabled + } + } + + pub(crate) fn split(&mut self, n: usize) -> Self { + let this_batch = self.withdrawals.drain(..n).collect(); + MpUnreachNlriBuilder { + withdrawals: this_batch, + ..*self + } + } + + pub(crate) fn value_len(&self) -> usize { + 3 + self.withdrawals.iter().fold(0, |sum, w| sum + w.compose_len()) + } + + pub(crate) fn is_empty(&self) -> bool { + self.withdrawals.is_empty() + } + + pub(crate) fn valid_combination( + &self, afi: AFI, safi: SAFI, is_addpath: bool + ) -> bool { + self.afi == afi + && self.safi == safi + && (self.withdrawals.is_empty() + || self.addpath_enabled == is_addpath) + } + + pub(crate) fn add_withdrawal(&mut self, withdrawal: &Nlri) + where + Vec: OctetsFrom, + T: Octets, + + { + self.withdrawals.push( + <&Nlri as OctetsInto>>>::try_octets_into(withdrawal).map_err(|_| ComposeError::todo() ).unwrap() + ); + } + + pub(crate) fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&u16::from(self.afi).to_be_bytes())?; + target.append_slice(&[self.safi.into()])?; + + for w in &self.withdrawals { + match w { + Nlri::Unicast(b) if b.is_v4() => { + unreachable!() + } + Nlri::Unicast(b) | Nlri::Multicast(b) => b.compose(target)?, + //Nlri::Mpls(m) => m.compose(target)?, + //Nlri::MplsVpn(m) => m.compose(target)?, + //Nlri::Vpls(v) => v.compose(target)?, + Nlri::FlowSpec(f) => f.compose(target)?, + //Nlri::RouteTarget(r) => r.compose(target)?, + _ => todo!() + } + } + + Ok(()) + } + +} + +//------------ StandardCommunitiesBuilder ------------------------------------ +// +// + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct StandardCommunitiesBuilder { + communities: Vec, + len: usize, // size of value, excluding path attribute flags+type_code+len + extended: bool, +} + +impl StandardCommunitiesBuilder { + pub(crate) fn new() -> StandardCommunitiesBuilder { + StandardCommunitiesBuilder { + communities: Vec::new(), + len: 0, + extended: false + } + } + + pub(crate) fn with_capacity(c: usize) -> StandardCommunitiesBuilder { + StandardCommunitiesBuilder { + communities: Vec::with_capacity(c), + len: 0, + extended: false + } + } + + pub(crate) fn communities(&self) -> &Vec { + &self.communities + } + + //pub(crate) fn compose_len(&self, _community: StandardCommunity) -> usize { + // if !self.extended && self.len + 4 > 255 { + // 4 +1 + // } else { + // 4 + // } + //} + + pub(crate) fn add_community(&mut self, community: StandardCommunity) { + if !self.extended && self.len + 4 > 255 { + self.extended = true; + } + self.len += 4; + self.communities.push(community); + } + + // TODO fn add_community_from_iter() +} + +//------------ Errors -------------------------------------------------------- + +#[derive(Debug)] +pub enum ComposeError{ + /// Exceeded maximum PDU size, data field carries the violating length. + PduTooLarge(usize), + + // TODO proper docstrings, first see how/if we actually use these. + AttributeTooLarge(PathAttributeType, usize), + AttributesTooLarge(usize), + IllegalCombination, + EmptyMpReachNlri, + EmptyMpUnreachNlri, + WrongAddressType, + + InvalidAttribute, // XXX perhaps carry the type_code here? + + /// Variant for `octseq::builder::ShortBuf` + ShortBuf, + /// Wrapper for util::parser::ParseError, used in `fn into_message` + ParseError(ParseError), + + Todo, +} + +impl ComposeError { + fn todo() -> Self { + ComposeError::Todo + } +} + +impl From for ComposeError { + fn from(_: ShortBuf) -> ComposeError { + ComposeError::ShortBuf + } +} + + +impl From for ComposeError { + fn from(pe: ParseError) -> ComposeError { + ComposeError::ParseError(pe) + } +} + + +impl std::error::Error for ComposeError { } +impl fmt::Display for ComposeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ComposeError::PduTooLarge(n) => { + write!(f, "oversized PDU: {n} bytes") + } + ComposeError::AttributeTooLarge(attr, n) => { + write!(f, "oversized attribute {attr}: {n} bytes") + } + ComposeError::AttributesTooLarge(n) => { + write!(f, "total path attributes too large: {n} bytes") + } + ComposeError::IllegalCombination => { + write!(f, "illegal combination of prefixes/attributes") + } + ComposeError::EmptyMpReachNlri => { + write!(f, "missing NLRI in MP_REACH_NLRI") + } + ComposeError::EmptyMpUnreachNlri => { + write!(f, "missing NLRI in MP_UNREACH_NLRI") + } + ComposeError::WrongAddressType => { + write!(f, "wrong address type") + } + ComposeError::InvalidAttribute => { + write!(f, "invalid attribute") + } + ComposeError::ShortBuf => { + ShortBuf.fmt(f) + } + ComposeError::ParseError(pe) => { + write!(f, "parse error in builder: {}", pe) + } + ComposeError::Todo => { + write!(f, "not implemented yet") + } + + } + } +} + + +//------------ Tests ---------------------------------------------------------- +#[cfg(test)] +mod tests { + + use std::collections::BTreeSet; + use std::net::Ipv4Addr; + use std::str::FromStr; + + use octseq::Parser; + + use crate::addr::Prefix; + use crate::asn::Asn; + //use crate::bgp::communities::Wellknown; + use crate::bgp::message::nlri::BasicNlri; + use crate::bgp::message::update::AfiSafi; + use super::*; + + fn print_pcap>(buf: T) { + print!("000000 "); + for b in buf.as_ref() { + print!("{:02x} ", b); + } + println!(); + } + + + #[test] + fn empty_nlri_iterators() { + let mut builder = UpdateBuilder::new_vec(); + builder.add_withdrawal( + &Nlri::unicast_from_str("2001:db8::/32").unwrap() + ).unwrap(); + + let msg = builder.into_message().unwrap(); + print_pcap(msg.as_ref()); + assert_eq!(msg.withdrawals().unwrap().count(), 1); + + let mut builder2 = UpdateBuilder::new_vec(); + builder2.add_withdrawal( + &Nlri::unicast_from_str("10.0.0.0/8").unwrap() + ).unwrap(); + + let msg2 = builder2.into_message().unwrap(); + print_pcap(msg2.as_ref()); + assert_eq!(msg2.withdrawals().unwrap().count(), 1); + } + + #[test] + fn build_empty() { + let builder = UpdateBuilder::new_vec(); + let msg = builder.finish().unwrap(); + //print_pcap(&msg); + assert!( + UpdateMessage::from_octets(msg, SessionConfig::modern()).is_ok() + ); + } + + #[test] + fn build_withdrawals_basic_v4() { + let mut builder = UpdateBuilder::new_vec(); + + let withdrawals = [ + "0.0.0.0/0", + "10.2.1.0/24", + "10.2.2.0/24", + "10.2.0.0/23", + "10.2.4.0/25", + "10.0.0.0/7", + "10.0.0.0/8", + "10.0.0.0/9", + ].map(|s| Nlri::unicast_from_str(s).unwrap()) + .into_iter() + .collect::>(); + + let _ = builder.append_withdrawals(&mut withdrawals.clone()); + let msg = builder.finish().unwrap(); + assert!( + UpdateMessage::from_octets(&msg, SessionConfig::modern()) + .is_ok() + ); + print_pcap(&msg); + + + let mut builder2 = UpdateBuilder::new_vec(); + for w in &withdrawals { + builder2.add_withdrawal(w).unwrap(); + } + + let msg2 = builder2.finish().unwrap(); + assert!( + UpdateMessage::from_octets(&msg2, SessionConfig::modern()) + .is_ok() + ); + print_pcap(&msg2); + + assert_eq!(msg, msg2); + } + + #[test] + fn build_withdrawals_from_iter() { + let mut builder = UpdateBuilder::new_vec(); + + let withdrawals = [ + "0.0.0.0/0", + "10.2.1.0/24", + "10.2.2.0/24", + "10.2.0.0/23", + "10.2.4.0/25", + "10.0.0.0/7", + "10.0.0.0/8", + "10.0.0.0/9", + ].map(|s| Nlri::unicast_from_str(s).unwrap().octets_into()) + .into_iter() + .collect::>(); + + let _ = builder.withdrawals_from_iter(withdrawals); + let msg = builder.into_message().unwrap(); + print_pcap(msg); + } + + #[test] + fn take_message_many_withdrawals() { + let mut builder = UpdateBuilder::new_vec(); + let mut prefixes: Vec>> = vec![]; + for i in 1..1500_u32 { + prefixes.push( + Nlri::Unicast( + Prefix::new_v4( + Ipv4Addr::from((i << 10).to_be_bytes()), + 22 + ).unwrap().into() + ) + ); + } + let prefixes_len = prefixes.len(); + builder.withdrawals_from_iter(prefixes).unwrap(); + + let mut w_cnt = 0; + let remainder = if let (pdu1, Some(remainder)) = builder.take_message() { + match pdu1 { + Ok(pdu) => { + w_cnt += pdu.withdrawals().unwrap().count(); + remainder + } + Err(e) => panic!("{}", e) + } + } else { + panic!("wrong"); + }; + + let remainder2 = if let (pdu2, Some(remainder2)) = remainder.take_message() { + match pdu2 { + Ok(pdu) => { + w_cnt += pdu.withdrawals().unwrap().count(); + remainder2 + } + Err(e) => panic!("{}", e) + } + } else { + panic!("wrong"); + }; + + if let (pdu3, None) = remainder2.take_message() { + match pdu3 { + Ok(pdu) => { + w_cnt += pdu.withdrawals().unwrap().count(); + } + Err(e) => panic!("{}", e) + } + } else { + panic!("wrong"); + }; + + assert_eq!(w_cnt, prefixes_len); + } + + #[test] + fn take_message_many_withdrawals_2() { + let mut builder = UpdateBuilder::new_vec(); + let mut prefixes: Vec>> = vec![]; + for i in 1..1500_u32 { + prefixes.push( + Nlri::Unicast( + Prefix::new_v4( + Ipv4Addr::from((i << 10).to_be_bytes()), + 22 + ).unwrap().into() + ) + ); + } + let prefixes_len = prefixes.len(); + builder.append_withdrawals(&mut prefixes).unwrap(); + + let mut w_cnt = 0; + let mut remainder = Some(builder); + loop { + if remainder.is_none() { + break + } + let (pdu, new_remainder) = remainder.take().unwrap().take_message(); + match pdu { + Ok(pdu) => { + w_cnt += pdu.withdrawals().unwrap().count(); + remainder = new_remainder; + } + Err(e) => panic!("{}", e) + } + } + + assert_eq!(w_cnt, prefixes_len); + } + + #[test] + fn into_messages_many_withdrawals() { + let mut builder = UpdateBuilder::new_vec(); + let mut prefixes: Vec>> = vec![]; + for i in 1..1500_u32 { + prefixes.push( + Nlri::Unicast( + Prefix::new_v4( + Ipv4Addr::from((i << 10).to_be_bytes()), + 22 + ).unwrap().into() + ) + ); + } + let prefixes_len = prefixes.len(); + builder.append_withdrawals(&mut prefixes).unwrap(); + + let mut w_cnt = 0; + for pdu in builder.into_messages().unwrap() { + w_cnt += pdu.withdrawals().unwrap().count(); + } + + assert_eq!(w_cnt, prefixes_len); + } + + #[test] + fn into_messages_many_announcements() { + let mut builder = UpdateBuilder::new_vec(); + let mut prefixes: Vec>> = vec![]; + for i in 1..1500_u32 { + prefixes.push( + Nlri::Unicast( + Prefix::new_v4( + Ipv4Addr::from((i << 10).to_be_bytes()), + 22 + ).unwrap().into() + ) + ); + } + let prefixes_len = prefixes.len(); + prefixes.iter().by_ref().for_each(|p| + builder.add_announcement(p).unwrap() + ); + + let mut w_cnt = 0; + for pdu in builder.into_messages().unwrap() { + w_cnt += pdu.announcements().unwrap().count(); + } + + assert_eq!(w_cnt, prefixes_len); + } + + #[test] + fn into_messages_many_withdrawals_mp() { + let mut builder = UpdateBuilder::new_vec(); + let prefixes_num = 1024; + for i in 0..prefixes_num { + builder.add_withdrawal( + &Nlri::unicast_from_str(&format!("2001:db:{:04}::/48", i)) + .unwrap() + ).unwrap(); + } + + let mut w_cnt = 0; + for pdu in builder.into_messages().unwrap() { + w_cnt += pdu.withdrawals().unwrap().count(); + } + + assert_eq!(w_cnt, prefixes_num); + } + + use crate::bgp::communities::{StandardCommunity, Tag}; + #[test] + fn into_messages_many_announcements_mp() { + let mut builder = UpdateBuilder::new_vec(); + let prefixes_num = 1_000_000; + for i in 0..prefixes_num { + builder.add_announcement( + &Nlri::<&[u8]>::Unicast(BasicNlri::new(Prefix::new_v6((i << 96).into(), 32).unwrap())) + //&Nlri::unicast_from_str(&format!("2001:db:{:04}::/48", i)) + // .unwrap() + ).unwrap(); + } + builder.set_local_pref(LocalPref::new(123)).unwrap(); + builder.set_multi_exit_disc(MultiExitDisc::new(123)).unwrap(); + (1..=300).for_each(|n| { + builder.add_community(StandardCommunity::new(n.into(), Tag::new(123))).unwrap(); + }); + + let mut a_cnt = 0; + for pdu in builder { + //eprint!("."); + let pdu = pdu.unwrap(); + assert!(pdu.as_ref().len() <= UpdateBuilder::<()>::MAX_PDU); + a_cnt += pdu.announcements().unwrap().count(); + assert!(pdu.local_pref().unwrap().is_some()); + assert!(pdu.multi_exit_disc().unwrap().is_some()); + assert_eq!(pdu.communities().unwrap().unwrap().count(), 300); + } + + assert_eq!(a_cnt, prefixes_num.try_into().unwrap()); + } + + + + #[test] + fn build_withdrawals_basic_v4_addpath() { + use crate::bgp::message::nlri::PathId; + let mut builder = UpdateBuilder::new_vec(); + let mut withdrawals = [ + "0.0.0.0/0", + "10.2.1.0/24", + "10.2.2.0/24", + "10.2.0.0/23", + "10.2.4.0/25", + "10.0.0.0/7", + "10.0.0.0/8", + "10.0.0.0/9", + ].iter().enumerate().map(|(idx, s)| Nlri::<&[u8]>::Unicast(BasicNlri { + prefix: Prefix::from_str(s).unwrap(), + path_id: Some(PathId::from_u32(idx.try_into().unwrap()))}) + ).collect::>(); + let _ = builder.append_withdrawals(&mut withdrawals); + let msg = builder.finish().unwrap(); + let mut sc = SessionConfig::modern(); + sc.add_addpath_rxtx(AfiSafi::Ipv4Unicast); + assert!( + UpdateMessage::from_octets(&msg, sc) + .is_ok() + ); + print_pcap(&msg); + } + + #[test] + fn build_withdrawals_basic_v6_single() { + let mut builder = UpdateBuilder::new_vec(); + let mut withdrawals = vec![ + Nlri::unicast_from_str("2001:db8::/32").unwrap() + ]; + + let _ = builder.append_withdrawals(&mut withdrawals); + + let msg = builder.finish().unwrap(); + println!("msg raw len: {}", &msg.len()); + print_pcap(&msg); + + UpdateMessage::from_octets(&msg, SessionConfig::modern()).unwrap(); + } + + #[test] + fn build_withdrawals_basic_v6_from_iter() { + let mut builder = UpdateBuilder::new_vec(); + + let mut withdrawals: Vec>> = vec![]; + for i in 1..512_u128 { + withdrawals.push( + Nlri::Unicast( + Prefix::new_v6( + Ipv6Addr::from((i << 64).to_be_bytes()), + 64 + ).unwrap().into() + ) + ); + } + + let _ = builder.withdrawals_from_iter(withdrawals); + let raw = builder.finish().unwrap(); + print_pcap(&raw); + UpdateMessage::from_octets(&raw, SessionConfig::modern()).unwrap(); + } + + #[test] + fn build_mixed_withdrawals() { + let mut builder = UpdateBuilder::new_vec(); + builder.add_withdrawal( + &Nlri::unicast_from_str("10.0.0.0/8").unwrap() + ).unwrap(); + builder.add_withdrawal( + &Nlri::unicast_from_str("2001:db8::/32").unwrap() + ).unwrap(); + let msg = builder.into_message().unwrap(); + print_pcap(msg.as_ref()); + + assert_eq!(msg.withdrawals().unwrap().count(), 2); + } + + #[test] + fn build_mixed_addpath_conventional() { + use crate::bgp::message::nlri::PathId; + let mut builder = UpdateBuilder::new_vec(); + builder.add_withdrawal( + &Nlri::unicast_from_str("10.0.0.0/8").unwrap() + ).unwrap(); + let res = builder.add_withdrawal( + &Nlri::<&[u8]>::Unicast( + (Prefix::from_str("10.0.0.0/8").unwrap(), + Some(PathId::from_u32(123)) + ).into()) + ); + assert!(matches!(res, Err(ComposeError::IllegalCombination))); + } + + #[test] + fn build_mixed_addpath_mp() { + use crate::bgp::message::nlri::PathId; + let mut builder = UpdateBuilder::new_vec(); + builder.add_withdrawal( + &Nlri::unicast_from_str("2001:db8::/32").unwrap() + ).unwrap(); + let res = builder.add_withdrawal( + &Nlri::<&[u8]>::Unicast( + (Prefix::from_str("2001:db8::/32").unwrap(), + Some(PathId::from_u32(123)) + ).into()) + ); + assert!(matches!(res, Err(ComposeError::IllegalCombination))); + } + + #[test] + fn build_mixed_addpath_ok() { + use crate::bgp::message::nlri::PathId; + let mut builder = UpdateBuilder::new_vec(); + builder.add_withdrawal( + &Nlri::unicast_from_str("10.0.0.0/8").unwrap() + ).unwrap(); + let res = builder.add_withdrawal( + &Nlri::<&[u8]>::Unicast( + (Prefix::from_str("2001:db8::/32").unwrap(), + Some(PathId::from_u32(123)) + ).into()) + ); + assert!(res.is_ok()); + print_pcap(builder.finish().unwrap()); + } + + #[test] + fn build_conv_mp_mix() { + let buf = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x88 + 8, // adding length for the conv NLRI + 0x02, 0x00, 0x00, 0x00, 0x71, 0x80, + 0x0e, 0x5a, 0x00, 0x02, 0x01, 0x20, 0xfc, 0x00, + 0x00, 0x10, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, + 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x40, 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, + 0x00, 0x40, 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, + 0x00, 0x01, 0x40, 0x20, 0x01, 0x0d, 0xb8, 0xff, + 0xff, 0x00, 0x02, 0x40, 0x20, 0x01, 0x0d, 0xb8, + 0xff, 0xff, 0x00, 0x03, 0x40, 0x01, 0x01, 0x00, + 0x40, 0x02, 0x06, 0x02, 0x01, 0x00, 0x00, 0x00, + 0xc8, 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, + // manually adding two conv NLRI here + 24, 1, 1, 1, + 24, 1, 1, 2 + + ]; + + let upd = UpdateMessage::from_octets(&buf, SessionConfig::modern()).unwrap(); + print_pcap(upd.as_ref()); + + assert!(upd.has_conventional_nlri() && upd.has_mp_nlri().unwrap()); + assert_eq!(upd.unicast_announcements().unwrap().count(), 7); + } + + #[test] + fn build_announcements_conventional() { + use crate::bgp::aspath::HopPath; + let mut builder = UpdateBuilder::new_vec(); + let prefixes = [ + "1.0.1.0/24", + "1.0.2.0/24", + "1.0.3.0/24", + "1.0.4.0/24", + ].map(|p| Nlri::unicast_from_str(p).unwrap()); + builder.announcements_from_iter(prefixes).unwrap(); + builder.set_origin(OriginType::Igp).unwrap(); + builder.set_nexthop_unicast(Ipv4Addr::from_str("1.2.3.4").unwrap().into()).unwrap(); + let path = HopPath::from([ + Asn::from_u32(123); 70 + ]); + + //builder.set_aspath::>(path.to_as_path().unwrap()).unwrap(); + builder.set_aspath(path).unwrap(); + + let raw = builder.finish().unwrap(); + print_pcap(raw); + + //let pdu = builder.into_message().unwrap(); + //print_pcap(pdu); + } + + #[test] + fn build_announcements_mp() { + use crate::bgp::aspath::HopPath; + + let mut builder = UpdateBuilder::new_vec(); + let prefixes = [ + "2001:db8:1::/48", + "2001:db8:2::/48", + "2001:db8:3::/48", + ].map(|p| Nlri::unicast_from_str(p).unwrap()); + builder.announcements_from_iter(prefixes).unwrap(); + builder.set_origin(OriginType::Igp).unwrap(); + builder.set_nexthop_unicast(Ipv6Addr::from_str("fe80:1:2:3::").unwrap().into()).unwrap(); + let path = HopPath::from([ + Asn::from_u32(100), + Asn::from_u32(200), + Asn::from_u32(300), + ]); + //builder.set_aspath::>(path.to_as_path().unwrap()).unwrap(); + builder.set_aspath(path).unwrap(); + + let raw = builder.finish().unwrap(); + print_pcap(raw); + } + + #[test] + fn build_announcements_mp_missing_nlri() { + use crate::bgp::aspath::HopPath; + + let mut builder = UpdateBuilder::new_vec(); + builder.set_origin(OriginType::Igp).unwrap(); + builder.set_nexthop_unicast(Ipv6Addr::from_str("fe80:1:2:3::").unwrap().into()).unwrap(); + let path = HopPath::from([ + Asn::from_u32(100), + Asn::from_u32(200), + Asn::from_u32(300), + ]); + builder.set_aspath(path).unwrap(); + + assert!(matches!( + builder.into_message(), + Err(ComposeError::EmptyMpReachNlri) + )); + } + + #[test] + fn build_announcements_mp_link_local() { + use crate::bgp::aspath::HopPath; + + let mut builder = UpdateBuilder::new_vec(); + + let prefixes = [ + "2001:db8:1::/48", + "2001:db8:2::/48", + "2001:db8:3::/48", + ].map(|p| Nlri::unicast_from_str(p).unwrap()); + + builder.announcements_from_iter(prefixes).unwrap(); + builder.set_origin(OriginType::Igp).unwrap(); + builder.set_nexthop_ll_addr("fe80:1:2:3::".parse().unwrap()).unwrap(); + + + let path = HopPath::from([ + Asn::from_u32(100), + Asn::from_u32(200), + Asn::from_u32(300), + ]); + //builder.set_aspath::>(path.to_as_path().unwrap()).unwrap(); + builder.set_aspath(path).unwrap(); + + //let unchecked = builder.finish().unwrap(); + //print_pcap(unchecked); + let msg = builder.into_message().unwrap(); + msg.print_pcap(); + } + + #[test] + fn build_announcements_mp_ll_no_nlri() { + use crate::bgp::aspath::HopPath; + + let mut builder = UpdateBuilder::new_vec(); + builder.set_origin(OriginType::Igp).unwrap(); + //builder.set_nexthop("2001:db8::1".parse().unwrap()).unwrap(); + builder.set_nexthop_ll_addr("fe80:1:2:3::".parse().unwrap()).unwrap(); + let path = HopPath::from([ + Asn::from_u32(100), + Asn::from_u32(200), + Asn::from_u32(300), + ]); + builder.set_aspath(path).unwrap(); + + assert!(matches!( + builder.into_message(), + Err(ComposeError::EmptyMpReachNlri) + )); + } + + #[test] + fn build_standard_communities() { + use crate::bgp::aspath::HopPath; + let mut builder = UpdateBuilder::new_vec(); + let prefixes = [ + "1.0.1.0/24", + "1.0.2.0/24", + "1.0.3.0/24", + "1.0.4.0/24", + ].map(|p| Nlri::unicast_from_str(p).unwrap()); + builder.announcements_from_iter(prefixes).unwrap(); + builder.set_origin(OriginType::Igp).unwrap(); + builder.set_nexthop_unicast("1.2.3.4".parse::().unwrap().into()).unwrap(); + let path = HopPath::from([ + Asn::from_u32(100), + Asn::from_u32(200), + Asn::from_u32(300), + ]); + //builder.set_aspath::>(path.to_as_path().unwrap()).unwrap(); + builder.set_aspath(path).unwrap(); + + + builder.add_community("AS1234:666".parse().unwrap()).unwrap(); + builder.add_community("NO_EXPORT".parse().unwrap()).unwrap(); + for n in 1..100 { + builder.add_community(format!("AS999:{n}").parse().unwrap()).unwrap(); + } + + builder.into_message().unwrap(); + //let raw = builder.finish().unwrap(); + //print_pcap(&raw); + } + + #[test] + fn build_other_attributes() { + use crate::bgp::aspath::HopPath; + let mut builder = UpdateBuilder::new_vec(); + let prefixes = [ + "1.0.1.0/24", + "1.0.2.0/24", + "1.0.3.0/24", + "1.0.4.0/24", + ].map(|p| Nlri::unicast_from_str(p).unwrap()); + builder.announcements_from_iter(prefixes).unwrap(); + builder.set_origin(OriginType::Igp).unwrap(); + builder.set_nexthop_unicast(Ipv4Addr::from_str("1.2.3.4").unwrap().into()).unwrap(); + let path = HopPath::from([ + Asn::from_u32(100), + Asn::from_u32(200), + Asn::from_u32(300), + ]); + //builder.set_aspath::>(path.to_as_path().unwrap()).unwrap(); + builder.set_aspath(path).unwrap(); + + builder.set_multi_exit_disc(MultiExitDisc::new(1234)).unwrap(); + builder.set_local_pref(LocalPref::new(9876)).unwrap(); + + let msg = builder.into_message().unwrap(); + msg.print_pcap(); + } + + #[test] + fn from_update_message() { + let raw = vec![ + // BGP UPDATE, single conventional announcement, MultiExitDisc + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x37, 0x02, + 0x00, 0x00, 0x00, 0x1b, 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, + 0x06, 0x02, 0x01, 0x00, 0x01, 0x00, 0x00, 0x40, 0x03, 0x04, + 0x0a, 0xff, 0x00, 0x65, 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x20, 0x0a, 0x0a, 0x0a, 0x02 + ]; + let sc = SessionConfig::modern(); + let upd = UpdateMessage::from_octets(&raw, sc).unwrap(); + let target = BytesMut::new(); + let mut builder = UpdateBuilder::from_update_message(&upd, sc, target).unwrap(); + + assert_eq!(builder.attributes.len(), 4); + + builder.add_announcement( + &Nlri::unicast_from_str("10.10.10.2/32").unwrap() + ).unwrap(); + + let upd2 = builder.into_message().unwrap(); + assert_eq!(&raw, upd2.as_ref()); + } + + #[test] + fn build_overwriting_attributes() { + // TODO test that when setting and overwriting the same attribute in + // an UpdateBuilder, the resulting PDU contains the lastly set + // attribute and that all the lengths are correct etc + + let raw = vec![ + // BGP UPDATE, single conventional announcement, MultiExitDisc + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x37, 0x02, + 0x00, 0x00, 0x00, 0x1b, 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, + 0x06, 0x02, 0x01, 0x00, 0x01, 0x00, 0x00, 0x40, 0x03, 0x04, + 0x0a, 0xff, 0x00, 0x65, 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x20, 0x0a, 0x0a, 0x0a, 0x02 + ]; + let sc = SessionConfig::modern(); + let upd = UpdateMessage::from_octets(&raw, sc).unwrap(); + for pa in upd.clone().path_attributes().unwrap() { + eprintln!("{:?}", pa.unwrap().to_owned().unwrap()); + } + let target = BytesMut::new(); + let mut builder = UpdateBuilder::from_update_message(&upd, sc, target).unwrap(); + + + assert_eq!(builder.attributes.len(), 4); + + builder.set_origin(OriginType::Igp).unwrap(); + builder.set_origin(OriginType::Egp).unwrap(); + builder.set_origin(OriginType::Igp).unwrap(); + + assert_eq!(builder.attributes.len(), 4); + + builder.add_announcement( + &Nlri::unicast_from_str("10.10.10.2/32").unwrap() + ).unwrap(); + + let upd2 = builder.into_message().unwrap(); + assert_eq!(&raw, upd2.as_ref()); + } + + #[test] + fn build_ordered_attributes() { + let mut builder = UpdateBuilder::new_vec(); + builder.add_community( + StandardCommunity::from_str("AS1234:999").unwrap() + ).unwrap(); + builder.set_origin(OriginType::Igp).unwrap(); + builder.add_community( + StandardCommunity::from_str("AS1234:1000").unwrap() + ).unwrap(); + + assert_eq!(builder.attributes.len(), 2); + + let pdu = builder.into_message().unwrap(); + let mut prev_type_code = 0_u8; + for pa in pdu.path_attributes().unwrap() { + let type_code = u8::from(pa.unwrap().type_code()); + assert!(prev_type_code < type_code); + prev_type_code = type_code; + } + assert_eq!(pdu.communities().unwrap().unwrap().count(), 2); + } + + // TODO also do fn check(raw: Bytes) + fn parse_build_compare(raw: &[u8]) { + let sc = SessionConfig::modern(); + let original = + match UpdateMessage::from_octets(&raw, sc) { + Ok(msg) => msg,// UpdateMessage::from_octets(&raw, sc) { + Err(_e) => { + //TODO get the ShortInput ones (and retry with a different + //SessionConfig) + //eprintln!("failed to parse input: {e:?}"); + //print_pcap(&raw); + //panic!(); + return; + } + }; + + let target = BytesMut::new(); + let mut builder = UpdateBuilder::from_update_message( + &original, sc, target + ).unwrap(); + + for w in original.withdrawals().unwrap() { + builder.add_withdrawal(&w.unwrap()).unwrap(); + } + + for a in original.announcements().unwrap() { + builder.add_announcement(&a.unwrap()).unwrap(); + } + + if let Some(nh) = original.conventional_next_hop().unwrap() { + builder.set_nexthop(nh).unwrap(); + } + if let Some(nh) = original.mp_next_hop().unwrap() { + builder.set_nexthop(nh).unwrap(); + } + + //eprintln!("--"); + //print_pcap(&raw); + //print_pcap(builder.finish().unwrap()); + //eprintln!("--"); + //panic!("hard stop"); + + + let calculated_len = builder.calculate_pdu_length(); + + let composed = match builder.take_message() { + (Ok(msg), None) => msg, + (Err(e), _) => { + print_pcap(raw); + panic!("error: {e}"); + } + (_, Some(_)) => { + unimplemented!("builder returning remainder") + } + }; + + assert_eq!(composed.as_ref().len(), calculated_len); + + // XXX there are several possible reasons why our composed pdu + // differs from the original input, especially if the attributes + // in the original were not correctly ordered, or when attributes + // had the extended-length bit set while not being >255 in size. + //assert_eq!(raw, composed.as_ref()); + + + // compare as much as possible: + #[allow(clippy::blocks_in_if_conditions)] + if std::panic::catch_unwind(|| { + assert_eq!(original.origin(), composed.origin()); + //assert_eq!(original.aspath(), composed.aspath()); + assert_eq!(original.conventional_next_hop(), composed.conventional_next_hop()); + assert_eq!(original.mp_next_hop(), composed.mp_next_hop()); + assert_eq!(original.multi_exit_disc(), composed.multi_exit_disc()); + assert_eq!(original.local_pref(), composed.local_pref()); + + /* + assert_eq!( + original.path_attributes().iter().count(), + composed.path_attributes().unwrap().count() + ); + */ + + let orig_pas = original.path_attributes().unwrap() + .map(|pa| pa.unwrap().type_code()).collect::>(); + + let composed_pas = composed.path_attributes().unwrap() + .map(|pa| pa.unwrap().type_code()).collect::>(); + + let diff_pas: Vec<_> = orig_pas.symmetric_difference( + &composed_pas + ).collect(); + if !diff_pas.is_empty() { + for d in &diff_pas { + match d { + // FIXME: check if MPU is the _only_ attribute, + // perhaps we are dealing with an EoR here? + PathAttributeType::MpUnreachNlri => { + // XXX RIS data contains empty-but-non-EoR + // MP_UNREACH_NLRI for some reason. + //assert!(original.is_eor().is_some()); + //}); + + } + _ => { + dbg!(&diff_pas); + panic!("unclear why PAs differ") + } + } + } + } + + assert_eq!( + original.announcements().unwrap().count(), + composed.announcements().unwrap().count(), + ); + assert_eq!( + original.withdrawals().iter().count(), + composed.withdrawals().iter().count(), + ); + + }).is_err() { + eprintln!("--"); + print_pcap(raw); + print_pcap(composed.as_ref()); + + eprintln!("--"); + panic!("tmp"); + } + + if raw == composed.as_ref() { + eprint!("✓"); + } else { + eprint!("×"); + } + } + + #[test] + fn parse_build_compare_1() { + eprintln!(); + parse_build_compare(&[ + // BGP UPDATE, single conventional announcement, MultiExitDisc + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x37, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x40, + 0x01, 0x01, 0x00, 0x40, 0x02, 0x06, 0x02, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x40, 0x03, 0x04, 0x0a, + 0xff, 0x00, 0x65, 0x80, 0x04, 0x04, 0x00, 0x00, + 0x00, 0x01, 0x20, 0x0a, 0x0a, 0x0a, 0x02 + ] + ); + + parse_build_compare(&[ + // BGP UPDATE, Ipv4 FlowSpec, empty AS_PATH, ext communities + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x46, 0x02, 0x00, 0x00, 0x00, 0x2f, 0x90, + 0x0e, 0x00, 0x12, 0x00, 0x01, 0x85, 0x00, 0x00, + 0x0c, 0x07, 0x81, 0x08, 0x08, 0x81, 0x00, 0x0b, + 0x81, 0x08, 0x0c, 0x81, 0x01, 0x40, 0x01, 0x01, + 0x00, 0x40, 0x02, 0x00, 0x40, 0x05, 0x04, 0x00, + 0x00, 0x00, 0x64, 0xc0, 0x10, 0x08, 0x80, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 + ] + ); + + // FIXME: the NextHop in MP_REACH_NLRI gets lost + parse_build_compare(&[ + // BGP UPDATE, IPv4 Multicast, NEXT_HOP, et al. + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x52, 0x02, 0x00, 0x00, 0x00, 0x3b, 0x80, + 0x0e, 0x1d, 0x00, 0x01, 0x02, 0x04, 0x0a, 0x09, + 0x0a, 0x09, 0x00, 0x1a, 0xc6, 0x33, 0x64, 0x00, + 0x1a, 0xc6, 0x33, 0x64, 0x40, 0x1a, 0xc6, 0x33, + 0x64, 0x80, 0x1a, 0xc6, 0x33, 0x64, 0xc0, 0x40, + 0x01, 0x01, 0x00, 0x40, 0x02, 0x06, 0x02, 0x01, + 0x00, 0x00, 0x01, 0xf4, 0x40, 0x03, 0x04, 0x0a, + 0x09, 0x0a, 0x09, 0x80, 0x04, 0x04, 0x00, 0x00, + 0x00, 0x00 + ] + ); + eprintln!(); + + } + + + use std::fs::File; + use memmap2::Mmap; + + #[test] + #[ignore] + fn parse_build_compare_bulk() { + let filename = "examples/raw_bgp_updates"; + let file = File::open(filename).unwrap(); + let mmap = unsafe { Mmap::map(&file).unwrap() }; + let fh = &mmap[..]; + let mut parser = Parser::from_ref(&fh); + + let mut n = 0; + const MAX: usize = usize::MAX; + + while parser.remaining() > 0 && n < MAX { + let pos = parser.pos(); + parser.advance(16).unwrap(); + let len = parser.parse_u16_be().unwrap(); + parser.seek(pos).unwrap(); + parse_build_compare( + parser.parse_octets(len.into()).unwrap() + ); + n += 1; + eprint!("\r{n} "); + } + eprintln!("parse_build_compare'd {n}"); + } + + #[test] + fn build_mp_unreach_extended_length() { + let raw = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x02, 0xd1, 0x02, 0x00, 0x00, 0x02, 0xba, 0x40, + 0x01, 0x01, 0x00, 0x40, 0x02, 0x1a, 0x02, 0x06, + 0x00, 0x00, 0x1a, 0x0b, 0x00, 0x00, 0x51, 0x1c, + 0x00, 0x00, 0xbc, 0xa5, 0x00, 0x00, 0x1b, 0x1b, + 0x00, 0x00, 0xf3, 0x20, 0x00, 0x03, 0x2e, 0xa9, + 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, 0x08, 0xd0, + 0x08, 0x02, 0x60, 0x00, 0x00, 0x0b, 0x26, 0x00, + 0x00, 0x0c, 0xa3, 0x00, 0x00, 0x1a, 0x10, 0x00, + 0x00, 0x1a, 0x29, 0x00, 0x00, 0x20, 0x01, 0x00, + 0x00, 0x21, 0x16, 0x00, 0x00, 0x21, 0x2c, 0x00, + 0x00, 0x21, 0x93, 0x00, 0x00, 0x21, 0xbc, 0x00, + 0x00, 0x21, 0xc1, 0x00, 0x00, 0x23, 0xa3, 0x00, + 0x00, 0x31, 0x0b, 0x00, 0x00, 0x31, 0x6e, 0x00, + 0x00, 0x32, 0xca, 0x00, 0x00, 0x33, 0xf6, 0x00, + 0x00, 0x3c, 0x12, 0x00, 0x00, 0x3c, 0x59, 0x00, + 0x00, 0x3d, 0x38, 0x00, 0x00, 0x3d, 0xe8, 0x00, + 0x00, 0x52, 0x26, 0x00, 0x00, 0x53, 0x12, 0x00, + 0x00, 0x53, 0x33, 0x00, 0x00, 0x53, 0xcd, 0x00, + 0x00, 0x60, 0x3e, 0x00, 0x00, 0x60, 0xa3, 0x00, + 0x00, 0x61, 0x05, 0x00, 0x00, 0x67, 0x2f, 0x00, + 0x00, 0x71, 0x79, 0x00, 0x00, 0x71, 0xc4, 0x00, + 0x00, 0x71, 0xfe, 0x00, 0x00, 0x73, 0x94, 0x00, + 0x00, 0x78, 0x87, 0x00, 0x00, 0x78, 0x9d, 0x00, + 0x00, 0x78, 0xd8, 0x00, 0x00, 0x78, 0xf8, 0x00, + 0x00, 0x7a, 0x3e, 0x00, 0x00, 0x7a, 0x8a, 0x00, + 0x00, 0x7a, 0x90, 0x00, 0x00, 0x7a, 0xc6, 0x00, + 0x00, 0x7b, 0xd7, 0x00, 0x00, 0x84, 0x70, 0x00, + 0x00, 0x84, 0xb4, 0x00, 0x00, 0x86, 0x98, 0x00, + 0x00, 0x87, 0x2a, 0x00, 0x00, 0x88, 0x8f, 0x00, + 0x00, 0x88, 0xb8, 0x00, 0x00, 0x88, 0xd8, 0x00, + 0x00, 0x89, 0x44, 0x00, 0x00, 0x8a, 0x7f, 0x00, + 0x00, 0x8a, 0xb4, 0x00, 0x00, 0x8b, 0x0e, 0x00, + 0x00, 0x8b, 0xdf, 0x00, 0x00, 0x8b, 0xe2, 0x00, + 0x00, 0x98, 0xaf, 0x00, 0x00, 0x9b, 0x37, 0x00, + 0x00, 0xa0, 0xa2, 0x00, 0x00, 0xa2, 0x81, 0x00, + 0x00, 0xa2, 0xfa, 0x00, 0x00, 0xa5, 0x0d, 0x00, + 0x00, 0xa5, 0x33, 0x00, 0x00, 0xa6, 0x16, 0x00, + 0x00, 0xa6, 0xac, 0x00, 0x00, 0xa8, 0xae, 0x00, + 0x00, 0xa9, 0x35, 0x00, 0x00, 0xaa, 0x0a, 0x00, + 0x00, 0xaa, 0xcf, 0x00, 0x00, 0xab, 0xfe, 0x00, + 0x00, 0xac, 0xee, 0x00, 0x00, 0xad, 0x62, 0x00, + 0x00, 0xaf, 0x2b, 0x00, 0x00, 0xaf, 0x71, 0x00, + 0x00, 0xaf, 0xe5, 0x00, 0x00, 0xb8, 0x9a, 0x00, + 0x00, 0xb9, 0xca, 0x00, 0x00, 0xb9, 0xe2, 0x00, + 0x00, 0xba, 0x1d, 0x00, 0x00, 0xba, 0x6b, 0x00, + 0x00, 0xba, 0x83, 0x00, 0x00, 0xba, 0x9f, 0x00, + 0x00, 0xbb, 0xe0, 0x00, 0x00, 0xbc, 0x26, 0x00, + 0x00, 0xbc, 0x81, 0x00, 0x00, 0xbc, 0xa5, 0x00, + 0x00, 0xbc, 0xdb, 0x00, 0x00, 0xbd, 0x0f, 0x00, + 0x00, 0xbd, 0x25, 0x00, 0x00, 0xbd, 0x61, 0x00, + 0x00, 0xbd, 0x83, 0x00, 0x00, 0xbe, 0x82, 0x00, + 0x00, 0xbe, 0xca, 0x00, 0x00, 0xbf, 0x5d, 0x00, + 0x00, 0xbf, 0xa7, 0x00, 0x00, 0xc1, 0x61, 0x00, + 0x00, 0xc1, 0x86, 0x00, 0x00, 0xc1, 0xa1, 0x00, + 0x00, 0xc1, 0xe9, 0x00, 0x00, 0xc2, 0x73, 0x00, + 0x00, 0xc2, 0x95, 0x00, 0x00, 0xc4, 0x52, 0x00, + 0x00, 0xc4, 0xa4, 0x00, 0x00, 0xc5, 0x29, 0x00, + 0x00, 0xc6, 0x53, 0x00, 0x00, 0xc6, 0xc9, 0x00, + 0x00, 0xc7, 0x03, 0x00, 0x00, 0xc7, 0x0e, 0x00, + 0x00, 0xc7, 0x47, 0x00, 0x00, 0xc7, 0x94, 0x00, + 0x00, 0xc7, 0x95, 0x00, 0x00, 0xc8, 0x61, 0x00, + 0x00, 0xc8, 0x8d, 0x00, 0x00, 0xc9, 0xcb, 0x00, + 0x00, 0xc9, 0xd5, 0x00, 0x00, 0xcb, 0xbc, 0x00, + 0x00, 0xcb, 0xe1, 0x00, 0x00, 0xdc, 0xd6, 0x00, + 0x00, 0xdd, 0x10, 0x00, 0x00, 0xdd, 0x67, 0x00, + 0x00, 0xdd, 0x71, 0x00, 0x00, 0xdd, 0x7d, 0x00, + 0x00, 0xdd, 0xee, 0x00, 0x00, 0xde, 0x8b, 0x00, + 0x00, 0xde, 0xb2, 0x00, 0x00, 0xdf, 0x5e, 0x00, + 0x00, 0xdf, 0xc1, 0x00, 0x00, 0xe0, 0x31, 0x00, + 0x00, 0xe0, 0x8f, 0x00, 0x00, 0xe0, 0xb9, 0x00, + 0x00, 0xe1, 0x70, 0x00, 0x00, 0xe3, 0x2e, 0x00, + 0x00, 0xe8, 0x7c, 0x00, 0x00, 0xe8, 0xeb, 0x00, + 0x00, 0xe9, 0x53, 0x00, 0x00, 0xe9, 0x94, 0x00, + 0x00, 0xeb, 0x02, 0x00, 0x00, 0xeb, 0x05, 0x00, + 0x00, 0xeb, 0x55, 0x00, 0x00, 0xeb, 0xc5, 0x00, + 0x00, 0xed, 0xf7, 0x00, 0x00, 0xf2, 0x97, 0x00, + 0x00, 0xf2, 0xfd, 0x00, 0x00, 0xf3, 0x21, 0x00, + 0x00, 0xf3, 0xaa, 0x1a, 0x0b, 0x0b, 0xb9, 0x1a, + 0x0b, 0x0f, 0xa5, 0x1a, 0x0b, 0x14, 0x57, 0x1a, + 0x0b, 0x17, 0x70, 0x1a, 0x0b, 0x22, 0xbb, 0x51, + 0x1c, 0x0b, 0xba, 0x51, 0x1c, 0x0b, 0xc3, 0x51, + 0x1c, 0x0b, 0xcd, 0x71, 0x94, 0x03, 0x85, 0x71, + 0x94, 0xfc, 0x67, 0x90, 0x0e, 0x00, 0x2a, 0x00, + 0x02, 0x01, 0x20, 0x20, 0x01, 0x07, 0xf8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x0b, 0x00, + 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xe6, 0xfc, 0x82, 0xff, 0xfe, + 0xa3, 0xbf, 0xc3, 0x00, 0x1d, 0x2a, 0x10, 0xcb, + 0xc0 + ]; + + parse_build_compare(&raw); + + } + + #[test] + fn build_invalid_attribute() { + // If the builder contains an Invalid attribute, apparently the user + // wants that, so it should be encoded on the wire. + let raw = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x5e, 0x02, 0x00, 0x00, 0x00, 0x43, 0x40, + 0x01, 0x01, 0x00, 0x40, 0x02, 0x16, 0x02, 0x05, + 0x00, 0x00, 0xbf, 0xee, 0x00, 0x00, 0xd0, 0x6c, + 0x00, 0x00, 0x1b, 0x1b, 0x00, 0x00, 0x1d, 0x97, + 0x00, 0x00, 0x5c, 0xa7, 0x40, 0x03, 0x04, 0x17, + 0x81, 0x20, 0x3d, 0x40, 0x06, 0x00, 0xc0, 0x07, + 0x08, 0x00, 0x00, 0xfe, 0x57, 0x0a, 0x40, 0x01, + 0xf6, 0xe0, 0x14, 0x0e, 0x00, 0x01, 0x00, 0x01, + 0xac, 0x15, 0x09, 0xf6, 0x00, 0x09, 0x0a, 0x40, + 0x01, 0xf6, 0x18, 0xcb, 0x20, 0x6b + ]; + parse_build_compare(&raw); + } + + #[test] + fn build_mp_reach_nlri_ll() { + let raw = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x9d, 0x02, 0x00, 0x00, 0x00, 0x86, 0x40, + 0x01, 0x01, 0x00, 0x40, 0x02, 0x16, 0x02, 0x05, + 0x00, 0x00, 0x5f, 0xa2, 0x00, 0x00, 0x19, 0x35, + 0x00, 0x00, 0x0d, 0x1c, 0x00, 0x00, 0x4f, 0xf9, + 0x00, 0x03, 0x3f, 0xf9, 0x80, 0x04, 0x04, 0x00, + 0x01, 0x57, 0xd4, 0xc0, 0x08, 0x24, 0x19, 0x35, + 0x00, 0x56, 0x19, 0x35, 0x03, 0xe8, 0x19, 0x35, + 0x05, 0x78, 0x19, 0x35, 0x05, 0x7c, 0x5f, 0xa2, + 0x00, 0x01, 0x5f, 0xa2, 0x32, 0xdc, 0x5f, 0xa2, + 0x32, 0xdd, 0x5f, 0xa2, 0x4f, 0x4c, 0x5f, 0xa2, + 0xfc, 0x59, 0xc0, 0x10, 0x08, 0x00, 0x02, 0x5f, + 0xa2, 0x00, 0x00, 0x01, 0x36, 0x90, 0x0e, 0x00, + 0x2c, 0x00, 0x02, 0x01, 0x20, 0x20, 0x01, 0x07, + 0xf8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa5, + 0x02, 0x44, 0x82, 0x00, 0x01, 0xfe, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xc3, 0xd6, + 0x00, 0x51, 0x03, 0xe7, 0xc0, 0x00, 0x30, 0x2a, + 0x0e, 0x9b, 0x43, 0x00, 0x00 + ]; + parse_build_compare(&raw); + + } + + #[test] + fn build_from_pdu_with_empty_unreach() { + // An empty MP_UNREACH_NLRI, i.e. no actual prefixes, should not + // be written to the wire. When creating a builder from an existing + // PDU with such an empty MP_UNREACH_NLRI, the resulting PDU will have + // one fewer path attribute. + let raw = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x63, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x80, + 0x0f, 0x03, 0x00, 0x02, 0x01, 0x40, 0x01, 0x01, + 0x00, 0x40, 0x02, 0x12, 0x02, 0x04, 0x00, 0x00, + 0x1b, 0x1b, 0x00, 0x00, 0x95, 0x0e, 0x00, 0x00, + 0xe7, 0x8e, 0x00, 0x00, 0x46, 0x88, 0x80, 0x0e, + 0x2a, 0x00, 0x02, 0x01, 0x20, 0x20, 0x01, 0x07, + 0xf8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa5, + 0x00, 0x69, 0x39, 0x00, 0x01, 0xfe, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x88, 0x2f, + 0xff, 0xfe, 0xbc, 0x52, 0xdd, 0x00, 0x20, 0x24, + 0x02, 0x82, 0xc0 + ]; + + let sc = SessionConfig::modern(); + let original = UpdateMessage::from_octets(&raw, sc).unwrap(); + let mut builder = UpdateBuilder::from_update_message( + &original, + sc, + Vec::new() + ).unwrap(); + + for w in original.withdrawals().unwrap() { + builder.add_withdrawal(&w.unwrap()).unwrap(); + } + + for a in original.announcements().unwrap() { + builder.add_announcement(&a.unwrap()).unwrap(); + } + + let composed = builder.into_message().unwrap(); + + assert_eq!(original.path_attributes().unwrap().count(), 4); + assert_eq!(composed.path_attributes().unwrap().count(), 3); + } +/* + #[test] + #[allow(deprecated)] + fn build_acs() { + use crate::bgp::aspath::HopPath; + use crate::bgp::message::nlri::PathId; + + let builder = UpdateBuilder::new_vec(); + let mut acs = AttrChangeSet::empty(); + + // ORIGIN + acs.origin_type.set(OriginType::Igp); + + // AS_PATH + let mut hp = HopPath::new(); + hp.prepend(Asn::from_u32(100)); + hp.prepend(Asn::from_u32(101)); + acs.as_path.set(hp.to_as_path().unwrap()); + + // NEXT_HOP + acs.next_hop.set(NextHop::Ipv4(Ipv4Addr::from_str("192.0.2.1").unwrap())); + + + // now for some NLRI + // XXX currently ACS only holds one single Nlri + acs.nlri.set(Nlri::Unicast(BasicNlri{ + prefix: Prefix::from_str("1.2.0.0/25").unwrap(), + path_id: Some(PathId::from_u32(123)) + })); + + acs.standard_communities.set(vec![ + Wellknown::NoExport.into(), + Wellknown::Blackhole.into(), + ]); + + let _msg = builder.build_acs(acs).unwrap(); + //print_pcap(&msg); + } +*/ +} diff --git a/src/bgp/mod.rs b/src/bgp/mod.rs index 1afb4299..036bd11b 100644 --- a/src/bgp/mod.rs +++ b/src/bgp/mod.rs @@ -2,6 +2,7 @@ pub mod aspath; pub mod communities; +pub mod path_attributes; pub mod types; pub mod message; diff --git a/src/bgp/path_attributes.rs b/src/bgp/path_attributes.rs new file mode 100644 index 00000000..2ba873d3 --- /dev/null +++ b/src/bgp/path_attributes.rs @@ -0,0 +1,2186 @@ +use std::fmt; +use std::net::Ipv4Addr; + +use log::debug; +use octseq::{Octets, OctetsBuilder, OctetsFrom, Parser}; + +use crate::asn::Asn; +use crate::bgp::aspath::HopPath; +use crate::bgp::message::{ + nlri::FixedNlriIter, + SessionConfig +}; +use crate::bgp::message::update_builder::{ + MpReachNlriBuilder, + MpUnreachNlriBuilder, + StandardCommunitiesBuilder +}; +use crate::bgp::types::{AFI, SAFI, AfiSafi}; +use crate::util::parser::{ParseError, parse_ipv4addr}; + + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct Flags(u8); + +impl Flags { + // 0 1 2 3 4 5 6 7 + // + // 0: optional (1 == optional) + // 1: transitive (1 == transitive) (well-known attr are transitive) + // 2: partial + // 3: extended length (0 -> 1 byte length, 1 -> 2 byte length) + // 4-7: MUST be 0 when sent, ignored when received + const OPT_NON_TRANS: u8 = 0b1000_0000; + const OPT_TRANS: u8 = 0b1100_0000; + const WELLKNOWN: u8 = 0b0100_0000; + + const EXTENDED_LEN: u8 = 0b0001_0000; + #[allow(dead_code)] + const PARTIAL: u8 = 0b0010_0000; + + /// Returns true if the optional flag is set. + pub fn is_optional(self) -> bool { + self.0 & 0x80 == 0x80 + } + + /// Returns true if the transitive bit is set. + pub fn is_transitive(self) -> bool { + self.0 & 0x40 == 0x40 + } + + /// Returns true if the partial flag is set. + pub fn is_partial(self) -> bool { + self.0 & 0x20 == 0x20 + } + + /// Returns true if the extended length flag is set. + pub fn is_extended_length(self) -> bool { + self.0 & 0x10 == 0x10 + } + +} + +impl From for Flags { + fn from(u: u8) -> Flags { + Flags(u) + } +} + +impl From for u8 { + fn from(f: Flags) -> u8 { + f.0 + } +} + + +pub trait AttributeHeader { + const FLAGS: u8; + const TYPE_CODE: u8; +} + +macro_rules! attribute { + ($name:ident($data:ty), + $flags:expr, + $type_code:expr + ) => { + + // TODO Serialize + #[derive(Clone, Debug, Eq, Hash, PartialEq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize))] + pub struct $name($data); + impl $name { + pub fn new(data: $data) -> $name { + $name(data) + } + + pub fn inner(self) -> $data { + self.0 + } + } + + impl std::convert::AsRef<$data> for $name { + fn as_ref(&self) -> &$data { + &self.0 + } + } + + impl std::convert::AsMut<$data> for $name { + fn as_mut(&mut self) -> &mut $data { + &mut self.0 + } + } + + /* + impl std::ops::Deref for $name { + type Target = $data; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl std::ops::DerefMut for $name { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + */ + + impl AttributeHeader for $name { + const FLAGS: u8 = $flags; + const TYPE_CODE: u8 = $type_code; + } + } +} + +macro_rules! path_attributes { + ( + $( + $type_code:expr => $name:ident($data:ty), $flags:expr + ),+ $(,)* + ) => { + +//------------ PathAttribute ------------------------------------------------- + + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum PathAttribute { + $( $name($name) ),+, + Unimplemented(UnimplementedPathAttribute), + Invalid(Flags, u8, Vec), + } + + impl PathAttribute { + pub fn compose( + &self, + target: &mut Target + ) -> Result<(), Target::AppendError> { + + match self { + $( + PathAttribute::$name(i) => { + i.compose(target) + } + ),+ + PathAttribute::Unimplemented(u) => { + u.compose(target) + } + PathAttribute::Invalid(flags, tc, val) => { + debug!("composing invalid path attribute {tc}"); + target.append_slice(&[flags.0 | Flags::PARTIAL, *tc])?; + if val.len() > 255 { + target.append_slice(&u16::try_from(val.len()).unwrap_or(u16::MAX).to_be_bytes())?; + } else { + target.append_slice(&u8::try_from(val.len()).unwrap_or(u8::MAX).to_be_bytes())?; + } + target.append_slice(&val) + } + } + } + + pub fn compose_len(&self) -> usize { + match self { + $( + PathAttribute::$name(i) => i.compose_len() + ),+, + PathAttribute::Unimplemented(u) => u.compose_len(), + PathAttribute::Invalid(_, _, val) => if val.len() > 255 { + 2 + 2 + val.len() + } else { + 2 + 1 + val.len() + + } + } + } + + pub fn type_code(&self) -> PathAttributeType { + match self { + $( + PathAttribute::$name(_) => + PathAttributeType::$name + ),+, + PathAttribute::Unimplemented(u) => { + PathAttributeType::Unimplemented(u.type_code()) + } + PathAttribute::Invalid(_, tc, _) => { + PathAttributeType::Invalid(*tc) + } + } + } + } + + $( + impl From<$name> for PathAttribute { + fn from(pa: $name) -> PathAttribute { + PathAttribute::$name(pa) + } + } + )+ + + impl From for PathAttribute { + fn from(u: UnimplementedPathAttribute) -> PathAttribute { + PathAttribute::Unimplemented(u) + } + } +//------------ WireformatPathAttribute -------------------------------------- + + #[derive(Debug)] + pub struct EncodedPathAttribute<'a, Octs: Octets> { + parser: Parser<'a, Octs>, + session_config: SessionConfig, + } + impl<'a, Octs: Octets> EncodedPathAttribute<'a, Octs> { + fn new( + parser: Parser<'a, Octs>, + session_config: SessionConfig + ) -> Self { + Self { parser, session_config } + } + + pub fn session_config(&self) -> SessionConfig { + self.session_config + } + + fn flags(&self) -> Flags { + self.parser.peek_all()[0].into() + } + + fn length(&self) -> usize { + if self.flags().is_extended_length() { + let raw = self.parser.peek(4).unwrap(); + u16::from_be_bytes([raw[2], raw[3]]).into() + } else { + self.parser.peek_all()[2].into() + } + } + + //pub fn value(&self) -> Octs::Range<'_> { + // let mut p = self.value_into_parser(); + // p.parse_octets(p.remaining()).unwrap() + //} + + pub fn value_into_parser(&self) -> Parser<'a, Octs> { + let mut res = self.parser; + if self.flags().is_extended_length() { + res.advance(4).unwrap(); + } else { + res.advance(3).unwrap(); + } + res + } + + } + + impl<'a, Octs: Octets> AsRef<[u8]> for EncodedPathAttribute<'a, Octs> { + fn as_ref(&self) -> &[u8] { + self.parser.peek_all() + } + } + + #[derive(Debug)] + pub enum WireformatPathAttribute<'a, Octs: Octets> { + //$( $name(Parser<'a, Octs>, SessionConfig) ),+, + $( $name(EncodedPathAttribute<'a, Octs>) ),+, + Unimplemented(UnimplementedWireformat<'a, Octs>), + Invalid(Flags, u8, Parser<'a, Octs>), + } + + + impl<'a, Octs: Octets> WireformatPathAttribute<'a, Octs> { + fn parse(parser: &mut Parser<'a, Octs>, sc: SessionConfig) + -> Result, ParseError> + { + let start_pos = parser.pos(); + let flags = parser.parse_u8()?; + let type_code = parser.parse_u8()?; + let (header_len, len) = match flags & 0x10 == 0x10 { + true => (4, parser.parse_u16_be()? as usize), + false => (3, parser.parse_u8()? as usize), + }; + + let mut pp = parser.parse_parser(len)?; + + let res = match type_code { + $( + $type_code => { + if let Err(e) = $name::validate( + flags.into(), &mut pp, sc + ) { + debug!("failed to parse path attribute: {e}"); + pp.seek(start_pos + header_len)?; + WireformatPathAttribute::Invalid( + $flags.into(), $type_code, pp + ) + } else { + pp.seek(start_pos/* + header_len */)?; + //WireformatPathAttribute::$name(pp, sc) + WireformatPathAttribute::$name( + EncodedPathAttribute::new(pp, sc) + ) + } + } + ),+ + , + _ => { + pp.seek(start_pos /* + header_len */)?; + WireformatPathAttribute::Unimplemented( + UnimplementedWireformat::new( + flags.into(), type_code, pp + ) + ) + } + }; + + Ok(res) + } + + // XXX this method is the reason we have fn parse as part of + // the trait, forcing us the pass a SessionConfig to all of + // the parse() implementations. + pub fn to_owned(&self) -> Result + where + Vec: OctetsFrom> + { + match self { + $( + //WireformatPathAttribute::$name(p, sc) => { + WireformatPathAttribute::$name(epa) => { + Ok(PathAttribute::$name( + //$name::parse(&mut p.clone(), *sc)? + $name::parse( + &mut epa.value_into_parser(), + epa.session_config() + )? + )) + } + ),+, + WireformatPathAttribute::Unimplemented(u) => { + Ok(PathAttribute::Unimplemented( + UnimplementedPathAttribute::new( + u.flags(), + u.type_code(), + u.value().to_vec() + ) + )) + }, + WireformatPathAttribute::Invalid(f, tc, p) => { + Ok(PathAttribute::Invalid( + *f, *tc, p.peek_all().to_vec() + )) + } + } + } + + pub fn type_code(&self) -> PathAttributeType { + match self { + $( + WireformatPathAttribute::$name(..) => + PathAttributeType::$name + ),+, + WireformatPathAttribute::Unimplemented(u) => { + PathAttributeType::Unimplemented(u.type_code()) + } + WireformatPathAttribute::Invalid(_, tc, _) => { + PathAttributeType::Invalid(*tc) + } + } + } + + pub fn flags(&self) -> Flags { + match self { + $( Self::$name(epa) => { epa.flags() }),+, + Self::Unimplemented(u) => u.flags, + Self::Invalid(f, _, _) => *f, + } + } + + /// Returns the length of the value. + pub fn length(&self) -> usize { + match self { + $( + //Self::$name(pa, _sc) => pa.remaining() + Self::$name(epa) => epa.length() + ),+, + Self::Unimplemented(u) => u.value.len(), + Self::Invalid(_, _, v) => v.remaining() //FIXME incorrect + } + } + + /* + pub fn _into_value_parser(self) -> Result, ParseError> { + match self { + $( + Self::$name(p, _) => { + Ok(p) + } + ),+, + _ => todo!() + } + } + */ + } + + impl<'a, Octs: Octets> AsRef<[u8]> for WireformatPathAttribute<'a, Octs> { + fn as_ref(&self) -> &[u8] { + match self { + $( + WireformatPathAttribute::$name(epa) => { + // this one is maybe fixed this way? + //p.peek_all() + epa.as_ref() + } + ),+, + WireformatPathAttribute::Unimplemented(u) => { + u.value() + } + WireformatPathAttribute::Invalid(_, _, pp) => { + pp.peek_all() + } + + } + } + } + +//------------ PathAttributeType --------------------------------------------- + + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub enum PathAttributeType { + $( $name ),+, + Unimplemented(u8), + Invalid(u8), + } + + impl fmt::Display for PathAttributeType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + $( + PathAttributeType::$name => { + write!(f, stringify!($name)) + } + )+ + PathAttributeType::Unimplemented(tc) => { + write!(f, "Unimplemented-PA-{}", tc) + } + PathAttributeType::Invalid(tc) => { + write!(f, "Invalid-PA-{}", tc) + } + } + } + } + + + impl From for PathAttributeType { + fn from(code: u8) -> PathAttributeType { + match code { + $( $type_code => PathAttributeType::$name ),+, + u => PathAttributeType::Unimplemented(u) + } + } + } + + impl From for u8 { + fn from(pat: PathAttributeType) -> u8 { + match pat { + $( PathAttributeType::$name => $type_code ),+, + PathAttributeType::Unimplemented(i) => i, + PathAttributeType::Invalid(i) => i, + } + } + } + + $( + attribute!($name($data), $flags, $type_code); + )+ + } +} + +path_attributes!( + 1 => Origin(OriginType), Flags::WELLKNOWN, + 2 => AsPath(HopPath), Flags::WELLKNOWN, + 3 => NextHop(Ipv4Addr), Flags::WELLKNOWN, + 4 => MultiExitDisc(u32), Flags::OPT_NON_TRANS, + 5 => LocalPref(u32), Flags::WELLKNOWN, + 6 => AtomicAggregate(()), Flags::WELLKNOWN, + 7 => Aggregator(AggregatorInfo), Flags::OPT_TRANS, + 8 => Communities(StandardCommunitiesBuilder), Flags::OPT_TRANS, + 9 => OriginatorId(Ipv4Addr), Flags::OPT_NON_TRANS, + 10 => ClusterList(ClusterIds), Flags::OPT_NON_TRANS, + 14 => MpReachNlri(MpReachNlriBuilder), Flags::OPT_NON_TRANS, + 15 => MpUnreachNlri(MpUnreachNlriBuilder), Flags::OPT_NON_TRANS, + 16 => ExtendedCommunities(ExtendedCommunitiesList), Flags::OPT_TRANS, + 17 => As4Path(HopPath), Flags::OPT_TRANS, + 18 => As4Aggregator(AggregatorInfo), Flags::OPT_TRANS, + 20 => Connector(Ipv4Addr), Flags::OPT_TRANS, + 21 => AsPathLimit(AsPathLimitInfo), Flags::OPT_TRANS, + //22 => PmsiTunnel(todo), Flags::OPT_TRANS, + 25 => Ipv6ExtendedCommunities(Ipv6ExtendedCommunitiesList), Flags::OPT_TRANS, + 32 => LargeCommunities(LargeCommunitiesList), Flags::OPT_TRANS, + // 33 => BgpsecAsPath, + 35 => Otc(Asn), Flags::OPT_TRANS, + //36 => BgpDomainPath(TODO), Flags:: , // https://datatracker.ietf.org/doc/draft-ietf-bess-evpn-ipvpn-interworking/06/ + //40 => BgpPrefixSid(TODO), Flags::OPT_TRANS, // https://datatracker.ietf.org/doc/html/rfc8669#name-bgp-prefix-sid-attribute + 128 => AttrSet(AttributeSet), Flags::OPT_TRANS, + 255 => Reserved(ReservedRaw), Flags::OPT_TRANS, + +); + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UnimplementedPathAttribute { + flags: Flags, + type_code: u8, + value: Vec, +} + +impl UnimplementedPathAttribute { + pub fn new(flags: Flags, type_code: u8, value: Vec) -> Self { + Self { flags, type_code, value } + } + + pub fn type_code(&self) -> u8 { + self.type_code + } + + pub fn flags(&self) -> Flags { + self.flags + } + + pub fn value(&self) -> &Vec { + &self.value + } + + pub fn value_len(&self) -> usize { + self.value.len() + } + + pub fn compose_len(&self) -> usize { + let value_len = self.value_len(); + if value_len > 255 { + 4 + value_len + } else { + 3 + value_len + } + } +} + +pub struct UnimplementedWireformat<'a, Octs: Octets> { + flags: Flags, + type_code: u8, + value: Parser<'a, Octs>, +} + +impl<'a, Octs: Octets> fmt::Debug for UnimplementedWireformat<'a, Octs> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:08b} {} {:02x?}", + u8::from(self.flags()), self.type_code(), self.value() + ) + } +} + +impl<'a, Octs: Octets> UnimplementedWireformat<'a, Octs> { + pub fn new(flags: Flags, type_code: u8, value: Parser<'a, Octs>) -> Self { + Self { flags, type_code, value } + } + pub fn type_code(&self) -> u8 { + self.type_code + } + + pub fn flags(&self) -> Flags { + self.flags + } + + pub fn value(&self) -> &[u8] { + if self.flags().is_extended_length() { + &self.value.peek_all()[4..] + } else { + &self.value.peek_all()[3..] + } + } +} + + +pub trait Attribute: AttributeHeader + Clone { + + fn compose_len(&self) -> usize { + self.header_len() + self.value_len() + } + + fn is_extended(&self) -> bool { + self.value_len() > 255 + } + + fn header_len(&self) -> usize { + if self.is_extended() { + 4 + } else { + 3 + } + } + + fn value_len(&self) -> usize; + + fn compose(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + self.compose_header(target)?; + self.compose_value(target) + } + + fn compose_header(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + if self.is_extended() { + target.append_slice(&[ + Self::FLAGS | Flags::EXTENDED_LEN, + Self::TYPE_CODE, + ])?; + target.append_slice( + &u16::try_from(self.value_len()).unwrap_or(u16::MAX) + .to_be_bytes() + ) + } else { + target.append_slice(&[ + Self::FLAGS, + Self::TYPE_CODE, + u8::try_from(self.value_len()).unwrap_or(u8::MAX) + ]) + } + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError>; + + fn validate( + flags: Flags, + parser: &mut Parser<'_, Octs>, + sc: SessionConfig + ) + -> Result<(), ParseError>; + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, sc: SessionConfig) + -> Result + where + Self: Sized, + Vec: OctetsFrom> + ; + +} + +#[derive(Debug)] +pub struct PathAttributes<'a, Octs> { + pub parser: Parser<'a, Octs>, + pub session_config: SessionConfig, +} + +impl<'a, Octs> Clone for PathAttributes<'a, Octs> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, Octs> Copy for PathAttributes<'a, Octs> { } + +impl<'a, Octs: Octets> PathAttributes<'a, Octs> { + pub fn new(parser: Parser<'_, Octs>, session_config: SessionConfig) + -> PathAttributes<'_, Octs> + { + PathAttributes { parser, session_config } + } + + pub fn get(&self, pat: PathAttributeType) + -> Option> + { + let mut iter = *self; + iter.find(|pa| + //XXX We need Rust 1.70 for is_ok_and() + //pa.as_ref().is_ok_and(|pa| pa.type_code() == pat) + if let Ok(pa) = pa.as_ref() { + pa.type_code() == pat + } else { + false + } + ).map(|res| res.unwrap()) // res is Ok(pa), so we can unwrap. + } + +} + +impl<'a, Octs: Octets> Iterator for PathAttributes<'a, Octs> { + type Item = Result, ParseError>; + fn next(&mut self) -> Option { + if self.parser.remaining() == 0 { + return None; + } + + let res = WireformatPathAttribute::parse( + &mut self.parser, + self.session_config + ); + Some(res) + } +} + +macro_rules! check_len_exact { + ($p:expr, $len:expr, $name:expr) => { + if $p.remaining() != $len { + Err(ParseError::form_error( + "wrong length for $name, expected $len " + )) + } else { + Ok(()) + } + } +} + +//--- Origin + +use crate::bgp::message::update::OriginType; + +impl Attribute for Origin { + fn value_len(&self) -> usize { 1 } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&[self.0.into()]) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'_, Octs>, _sc: SessionConfig) + -> Result + { + Ok(Origin(parser.parse_u8()?.into())) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + check_len_exact!(parser, 1, "ORIGIN") + } +} + + +//--- AsPath (see bgp::aspath) + +impl Attribute for AsPath { + fn value_len(&self) -> usize { + self.0.to_as_path::>().unwrap().into_inner().len() + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice( + self.0.to_as_path::>().unwrap().into_inner().as_ref() + ) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, sc: SessionConfig) + -> Result + { + // XXX reusing the old/existing AsPath here for the time being + let asp = crate::bgp::aspath::AsPath::new( + parser.peek_all().to_vec(), + sc.has_four_octet_asn() + ).map_err(|_| ParseError::form_error("invalid AS_PATH"))?; + + Ok(AsPath(asp.to_hop_path())) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + session_config: SessionConfig + ) -> Result<(), ParseError> { + let asn_size = if session_config.has_four_octet_asn() { + 4 + } else { + 2 + }; + while parser.remaining() > 0 { + let segment_type = parser.parse_u8()?; + if !(1..=4).contains(&segment_type) { + return Err(ParseError::form_error( + "illegal segment type in AS_PATH" + )); + } + let len = usize::from(parser.parse_u8()?); // ASNs in segment + parser.advance(len * asn_size)?; // ASNs. + } + Ok(()) + } +} + +//--- NextHop + +impl Attribute for NextHop { + fn value_len(&self) -> usize { 4 } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.octets()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + Ok(NextHop(parse_ipv4addr(parser)?)) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + check_len_exact!(parser, 4, "NEXT_HOP") + } +} + +//--- MultiExitDisc + +impl Attribute for MultiExitDisc { + fn value_len(&self) -> usize { 4 } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.to_be_bytes()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + Ok(MultiExitDisc(parser.parse_u32_be()?)) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + check_len_exact!(parser, 4, "MULTI_EXIT_DISC") + } +} + +//--- LocalPref + +impl Attribute for LocalPref { + fn value_len(&self) -> usize { 4 } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.to_be_bytes()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + Ok(LocalPref(parser.parse_u32_be()?)) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + check_len_exact!(parser, 4, "LOCAL_PREF") + } +} + +//--- AtomicAggregate + +impl Attribute for AtomicAggregate { + fn value_len(&self) -> usize { 0 } + + fn compose_value(&self, _target: &mut Target) + -> Result<(), Target::AppendError> + { + Ok(()) + } + + fn parse<'a, Octs: 'a + Octets>(_parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + Ok(AtomicAggregate(())) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + check_len_exact!(parser, 0, "ATOMIC_AGGREGATE") + } +} + +impl Copy for AtomicAggregate { } + +//--- Aggregator + +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct AggregatorInfo { + asn: Asn, + address: Ipv4Addr, +} + +impl AggregatorInfo { + pub fn new(asn: Asn, address: Ipv4Addr) -> AggregatorInfo { + AggregatorInfo { asn, address } + } + pub fn asn(&self) -> Asn { + self.asn + } + pub fn address(&self) -> Ipv4Addr { + self.address + } +} + + +impl Attribute for Aggregator { + // FIXME for legacy 2-byte ASNs, this should be 6. + // Should we pass a (&)SessionConfig to this method as well? + // Note that `fn compose_len` would then also need a SessionConfig, + // which, sort of makes sense anyway. + fn value_len(&self) -> usize { + 8 + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.asn().to_raw())?; + target.append_slice(&self.0.address().octets()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, sc: SessionConfig) + -> Result + { + let asn = if sc.has_four_octet_asn() { + Asn::from_u32(parser.parse_u32_be()?) + } else { + Asn::from_u32(parser.parse_u16_be()?.into()) + }; + + let address = parse_ipv4addr(parser)?; + Ok(Aggregator(AggregatorInfo::new(asn, address))) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + session_config: SessionConfig + ) -> Result<(), ParseError> { + //if flags != Self::FLAGS.into() { + // return Err(ParseError::form_error("invalid flags")); + //} + if session_config.has_four_octet_asn() { + check_len_exact!(parser, 8, "AGGREGATOR")?; + } else { + check_len_exact!(parser, 6, "AGGREGATOR")?; + } + Ok(()) + } +} + +//--- Communities + +impl Attribute for Communities { + fn value_len(&self) -> usize { + self.0.communities().len() * 4 + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + for c in self.0.communities() { + target.append_slice(&c.to_raw())?; + } + Ok(()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + let mut builder = StandardCommunitiesBuilder::with_capacity( + parser.remaining() / 4 + ); + while parser.remaining() > 0 { + builder.add_community(parser.parse_u32_be()?.into()); + } + + Ok(Communities(builder)) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + if parser.remaining() % 4 != 0 { + return Err(ParseError::form_error( + "unexpected length for COMMUNITIES" + )); + } + Ok(()) + } +} + +//--- OriginatorId + +impl Attribute for OriginatorId { + fn value_len(&self) -> usize { 4 } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.octets()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + Ok(OriginatorId(parse_ipv4addr(parser)?)) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + check_len_exact!(parser, 4, "ORIGINATOR_ID") + } +} + +//--- ClusterList + +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct BgpIdentifier([u8; 4]); + +impl From<[u8; 4]> for BgpIdentifier { + fn from(raw: [u8; 4]) -> BgpIdentifier { + BgpIdentifier(raw) + } +} + +/* +impl From for BgpIdentifier { + fn from(raw: u32) -> BgpIdentifier { + BgpIdentifier(raw.to_be_bytes()) + } +} +*/ + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct ClusterIds { + cluster_ids: Vec +} + +impl ClusterIds { + fn new(cluster_ids: Vec) -> ClusterIds { + ClusterIds {cluster_ids } + } + pub fn cluster_ids(&self) -> &Vec { + &self.cluster_ids + } +} + + +impl Attribute for ClusterList { + fn value_len(&self) -> usize { + self.0.cluster_ids.len() * 4 + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + for c in &self.0.cluster_ids { + target.append_slice(&c.0)?; + } + Ok(()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + let mut cluster_ids = Vec::with_capacity(parser.remaining() / 4); + while parser.remaining() > 0 { + cluster_ids.push(parser.parse_u32_be()?.to_be_bytes().into()); + } + Ok(ClusterList(ClusterIds::new(cluster_ids))) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + if parser.remaining() % 4 != 0 { + return Err(ParseError::form_error( + "unexpected length for CLUSTER_LIST" + )); + } + Ok(()) + } +} + +//--- MpReachNlri +impl Attribute for MpReachNlri { + fn value_len(&self) -> usize { + self.0.value_len() + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + self.0.compose_value(target) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, sc: SessionConfig) + -> Result + where + Vec: OctetsFrom> + { + let afi: AFI = parser.parse_u16_be()?.into(); + let safi: SAFI = parser.parse_u8()?.into(); + let nexthop = crate::bgp::types::NextHop::parse(parser, afi, safi)?; + if let crate::bgp::types::NextHop::Unimplemented(..) = nexthop { + debug!("Unsupported NextHop: {:?}", nexthop); + return Err(ParseError::Unsupported); + } + parser.advance(1)?; // reserved byte + + let afisafi = AfiSafi::try_from((afi, safi)) + .map_err(|_| ParseError::Unsupported)?; + + let mut builder = MpReachNlriBuilder::new( + afi, + safi, + nexthop, + sc.rx_addpath(afisafi) + ); + + // This is what using the FixedNlriIter would look like: + /* + match(afi, safi, sc.addpath_enabled()) { + (AFI::Ipv4, SAFI::Unicast, false) => { + for n in FixedNlriIter::ipv4unicast(parser) { + builder.add_announcement(&n?) + } + } + (AFI::Ipv4, SAFI::Unicast, true) => { + for n in FixedNlriIter::ipv4unicast_addpath(parser) { + builder.add_announcement(&n?) + } + } + (AFI::Ipv6, SAFI::Unicast, false) => { + for n in FixedNlriIter::ipv6unicast(parser) { + builder.add_announcement(&n?) + } + } + (AFI::Ipv6, SAFI::Unicast, true) => { + for n in FixedNlriIter::ipv6unicast_addpath(parser) { + builder.add_announcement(&n?) + } + } + _ => { + todo!("afi safi {:?} {:?}", afi, safi); + } + } + */ + let nlri_iter = crate::bgp::message::update::Nlris::new( + *parser, + sc, + afisafi + ).iter(); + + for nlri in nlri_iter { + builder.add_announcement(&nlri?); + } + + Ok(MpReachNlri(builder)) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + session_config: SessionConfig + ) -> Result<(), ParseError> { + // We only check for the bare minimum here, as most checks are + // better done upon (creation of the) Nlri iterator based on the + // value of this path attribute. + if parser.remaining() < 5 { + return Err(ParseError::form_error( + "length for MP_REACH_NLRI less than minimum" + )) + } + + + let afi: AFI = parser.parse_u16_be()?.into(); + let safi: SAFI = parser.parse_u8()?.into(); + let _nexthop = crate::bgp::types::NextHop::parse(parser, afi, safi)?; + //if let crate::bgp::types::NextHop::Unimplemented(..) = nexthop { + // debug!("Unsupported NextHop: {:?}", nexthop); + // return Err(ParseError::Unsupported); + //} + parser.advance(1)?; // reserved byte + + let afisafi = AfiSafi::try_from((afi, safi)) + .map_err(|_| ParseError::Unsupported)?; + let expect_path_id = session_config.rx_addpath(afisafi); + + use AfiSafi::*; + match (afisafi, expect_path_id) { + (Ipv4Unicast, false) => FixedNlriIter::ipv4unicast(parser).validate(), + (Ipv4Unicast, true) => FixedNlriIter::ipv4unicast_addpath(parser).validate(), + (Ipv6Unicast, false) => FixedNlriIter::ipv6unicast(parser).validate(), + (Ipv6Unicast, true) => FixedNlriIter::ipv6unicast_addpath(parser).validate(), + + + (Ipv4Multicast, false) => FixedNlriIter::ipv4multicast(parser).validate(), + (Ipv4Multicast, true) => FixedNlriIter::ipv4multicast_addpath(parser).validate(), + (Ipv6Multicast, false) => FixedNlriIter::ipv6multicast(parser).validate(), + (Ipv6Multicast, true) => FixedNlriIter::ipv6multicast_addpath(parser).validate(), + + + (Ipv4MplsUnicast, false) => FixedNlriIter::ipv4mpls_unicast(parser).validate(), + (Ipv4MplsUnicast, true) => FixedNlriIter::ipv4mpls_unicast_addpath(parser).validate(), + (Ipv6MplsUnicast, false) => FixedNlriIter::ipv6mpls_unicast(parser).validate(), + (Ipv6MplsUnicast, true) => FixedNlriIter::ipv6mpls_unicast_addpath(parser).validate(), + + _ => { debug!("TODO implement validation for this afi/safi"); Ok(()) } + /* TODO + + (Ipv4MplsVpnUnicast, false) => FixedNlriIter::ipv4mpls_vpn_unicast(parser).validate()?, + (Ipv4MplsVpnUnicast, true) => FixedNlriIter::ipv4mpls_vpn_unicast_addpath(parser).validate()?, + (Ipv6MplsVpnUnicast, false) => FixedNlriIter::ipv6mpls_vpn_unicast(parser).validate()?, + (Ipv6MplsVpnUnicast, true) => FixedNlriIter::ipv6mpls_vpn_unicast_addpath(parser).validate()?, + + // XXX does addpath come into play here? + (Ipv4RouteTarget, false) => FixedNlriIter::ipv4routetarget(parser).validate()?, + (Ipv4RouteTarget, true) => FixedNlriIter::ipv4routetarget_addpath(parser).validate()?, + + (Ipv4FlowSpec, _) => FixedNlriIter::ipv4flowspec(parser).validate()?, + (Ipv6FlowSpec, _) => FixedNlriIter::ipv6flowspec(parser).validate()?, + + // XXX does addpath come into play here? + (L2VpnVpls, false) => FixedNlriIter::l2vpn_vpls(parser).validate()?, + (L2VpnVpls, true) => FixedNlriIter::l2vpn_vpls_addpath(parser).validate()?, + (L2VpnEvpn, false) => FixedNlriIter::l2vpn_evpn(parser).validate()?, + (L2VpnEvpn, true) => FixedNlriIter::l2vpn_evpn_addpath(parser).validate()?, + */ + } + } +} + +//--- MpUnreachNlri +impl Attribute for MpUnreachNlri { + fn value_len(&self) -> usize { + self.0.value_len() + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + self.0.compose_value(target) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, sc: SessionConfig) + -> Result + where + Vec: OctetsFrom> + { + let afi: AFI = parser.parse_u16_be()?.into(); + let safi: SAFI = parser.parse_u8()?.into(); + + let afisafi = AfiSafi::try_from((afi, safi)) + .map_err(|_| ParseError::Unsupported)?; + + let mut builder = MpUnreachNlriBuilder::new( + afi, + safi, + sc.rx_addpath(afisafi) + ); + let nlri_iter = crate::bgp::message::update::Nlris::new( + *parser, + sc, + afisafi, + ).iter(); + + for nlri in nlri_iter { + builder.add_withdrawal(&nlri?); + } + Ok(MpUnreachNlri(builder)) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + // We only check for the bare minimum here, as most checks are + // better done upon (creation of the) Nlri iterator based on the + // value of this path attribute. + if parser.remaining() < 3 { + return Err(ParseError::form_error( + "length for MP_UNREACH_NLRI less than minimum" + )) + } + Ok(()) + } +} + +//--- ExtendedCommunities + +use crate::bgp::communities::ExtendedCommunity; +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct ExtendedCommunitiesList { + communities: Vec +} + +impl ExtendedCommunitiesList { + fn new(communities: Vec) + -> ExtendedCommunitiesList + { + ExtendedCommunitiesList {communities } + } + + pub fn communities(&self) -> &Vec { + &self.communities + } +} + + +impl Attribute for ExtendedCommunities { + fn value_len(&self) -> usize { + self.0.communities.len() * 8 + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + for c in &self.0.communities { + target.append_slice(&c.to_raw())?; + } + Ok(()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + let mut communities = Vec::with_capacity(parser.remaining() / 8); + let mut buf = [0u8; 8]; + while parser.remaining() > 0 { + parser.parse_buf(&mut buf)?; + communities.push(buf.into()); + } + Ok(ExtendedCommunities(ExtendedCommunitiesList::new(communities))) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + if parser.remaining() % 8 != 0 { + return Err(ParseError::form_error( + "unexpected length for EXTENDED_COMMUNITIES" + )); + } + Ok(()) + } +} + +//--- As4Path (see bgp::aspath) + +impl Attribute for As4Path { + fn value_len(&self) -> usize { + self.0.to_as_path::>().unwrap().into_inner().len() + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice( + self.0.to_as_path::>().unwrap().into_inner().as_ref() + ) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, sc: SessionConfig) + -> Result + { + // XXX Same as with AsPath, reusing the old/existing As4Path here + let asp = crate::bgp::aspath::AsPath::new( + parser.peek_all().to_vec(), + sc.has_four_octet_asn() + ).map_err(|_| ParseError::form_error("invalid AS4_PATH"))?; + Ok(As4Path(asp.to_hop_path())) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + while parser.remaining() > 0 { + let segment_type = parser.parse_u8()?; + if !(1..=4).contains(&segment_type) { + return Err(ParseError::form_error( + "illegal segment type in AS4_PATH" + )); + } + let len = usize::from(parser.parse_u8()?); // ASNs in segment + parser.advance(len * 4)?; // ASNs. + } + Ok(()) + } + +} + +//--- As4Aggregator + +impl Attribute for As4Aggregator { + fn value_len(&self) -> usize { 8 } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.asn().to_raw())?; + target.append_slice(&self.0.address().octets()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + let asn = Asn::from_u32(parser.parse_u32_be()?); + let address = parse_ipv4addr(parser)?; + Ok(As4Aggregator(AggregatorInfo::new(asn, address))) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + check_len_exact!(parser, 8, "AS4_AGGREGATOR") + } +} + + +//--- Connector (deprecated) + +impl Attribute for Connector { + fn value_len(&self) -> usize { 4 } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.octets()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + Ok(Connector(parse_ipv4addr(parser)?)) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _sc: SessionConfig + ) + -> Result<(), ParseError> + { + check_len_exact!(parser, 4, "CONNECTOR") + } +} + +//--- AsPathLimit (deprecated) + +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct AsPathLimitInfo { + upper_bound: u8, + attacher: Asn, +} + +impl AsPathLimitInfo { + pub fn new(upper_bound: u8, attacher: Asn) -> AsPathLimitInfo { + AsPathLimitInfo { upper_bound, attacher } + } +} + +impl Attribute for AsPathLimit { + fn value_len(&self) -> usize { 5 } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&[self.0.upper_bound])?; + target.append_slice(&self.0.attacher.to_raw()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + let info = AsPathLimitInfo { + upper_bound: parser.parse_u8()?, + attacher: Asn::from_u32(parser.parse_u32_be()?) + }; + + Ok(AsPathLimit(info)) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _sc: SessionConfig + ) + -> Result<(), ParseError> + { + check_len_exact!(parser, 5, "AS_PATHLIMIT") + } +} + +//--- Ipv6ExtendedCommunities + +use crate::bgp::communities::Ipv6ExtendedCommunity; +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct Ipv6ExtendedCommunitiesList { + communities: Vec +} + +impl Ipv6ExtendedCommunitiesList { + fn new(communities: Vec) + -> Ipv6ExtendedCommunitiesList + { + Ipv6ExtendedCommunitiesList {communities } + } + + pub fn communities(&self) -> &Vec { + &self.communities + } +} + + +impl Attribute for Ipv6ExtendedCommunities { + fn value_len(&self) -> usize { + self.0.communities.len() * 20 + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + for c in &self.0.communities { + target.append_slice(&c.to_raw())?; + } + Ok(()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + let mut communities = Vec::with_capacity(parser.remaining() / 20); + let mut buf = [0u8; 20]; + while parser.remaining() > 0 { + parser.parse_buf(&mut buf)?; + communities.push(buf.into()); + } + Ok(Ipv6ExtendedCommunities(Ipv6ExtendedCommunitiesList::new(communities))) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + if parser.remaining() % 20 != 0 { + return Err(ParseError::form_error( + "unexpected length for IPV6_EXTENDED_COMMUNITIES" + )); + } + Ok(()) + } +} + +//--- LargeCommunities + +use crate::bgp::communities::LargeCommunity; +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct LargeCommunitiesList { + communities: Vec +} + +impl LargeCommunitiesList { + fn new(communities: Vec) + -> LargeCommunitiesList + { + LargeCommunitiesList {communities } + } + + pub fn communities(&self) -> &Vec { + &self.communities + } +} + + +impl Attribute for LargeCommunities { + fn value_len(&self) -> usize { + self.0.communities.len() * 12 + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + for c in &self.0.communities { + target.append_slice(&c.to_raw())?; + } + Ok(()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + let mut communities = Vec::with_capacity(parser.remaining() / 12); + let mut buf = [0u8; 12]; + while parser.remaining() > 0 { + parser.parse_buf(&mut buf)?; + communities.push(buf.into()); + } + Ok(LargeCommunities(LargeCommunitiesList::new(communities))) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _session_config: SessionConfig + ) -> Result<(), ParseError> { + if parser.remaining() % 12 != 0 { + return Err(ParseError::form_error( + "unexpected length for LARGE_COMMUNITIES" + )); + } + Ok(()) + } +} + +//--- Otc + +impl Attribute for Otc { + fn value_len(&self) -> usize { 4 } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.to_raw()) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + Ok(Otc(Asn::from_u32(parser.parse_u32_be()?))) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _sc: SessionConfig + ) + -> Result<(), ParseError> + { + check_len_exact!(parser, 4, "OTC") + } +} + +//--- AttributeSet + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct AttributeSet { + origin: Asn, + attributes: Vec, +} + +impl AttributeSet { + pub fn new(origin: Asn, attributes: Vec) -> AttributeSet { + AttributeSet { origin, attributes } + } +} + +impl Attribute for AttrSet { + fn value_len(&self) -> usize { + 4 + self.0.attributes.len() + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.origin.to_raw())?; + target.append_slice(&self.0.attributes) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + let origin = Asn::from_u32(parser.parse_u32_be()?); + let attributes = parser.peek_all().to_vec(); + Ok(AttrSet(AttributeSet::new(origin, attributes))) + } + + fn validate( + _flags: Flags, + parser: &mut Parser<'_, Octs>, + _sc: SessionConfig + ) + -> Result<(), ParseError> + { + // XXX we do not validate the actual content (i.e. the attributes) + // here. Whoever wishes to process these will need to iterate over + // them with a PathAttributes anyway. + if parser.remaining() < 4 { + return Err(ParseError::form_error( + "length for ATTR_SET less than minimum" + )) + } + Ok(()) + } +} + +//--- ReservedRaw + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct ReservedRaw { + raw: Vec, +} + +impl ReservedRaw { + pub fn new(raw: Vec) -> ReservedRaw { + ReservedRaw { raw } + } +} + +impl Attribute for Reserved { + fn value_len(&self) -> usize { + self.0.raw.len() + } + + fn compose_value(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + target.append_slice(&self.0.raw) + } + + fn parse<'a, Octs: 'a + Octets>(parser: &mut Parser<'a, Octs>, _sc: SessionConfig) + -> Result + { + let raw = parser.peek_all().to_vec(); + Ok(Reserved(ReservedRaw::new(raw))) + } + + fn validate( + _flags: Flags, + _parser: &mut Parser<'_, Octs>, + _sc: SessionConfig + ) + -> Result<(), ParseError> + { + // Not anything we can validate here, really. + Ok(()) + } +} + + +//--- Unimplemented +// +// XXX implementing the Attribute trait requires to implement the +// AttributeHeader trait to be implemented as well. But, we have no const +// FLAGS and type_code for an UnimplementedPathAttribute, so that does not +// fly. +// +// Let's try to go without the trait. + +impl UnimplementedPathAttribute { + fn compose(&self, target: &mut Target) + -> Result<(), Target::AppendError> + { + let len = self.value().len(); + target.append_slice( + &[self.flags().into(), self.type_code()] + )?; + if self.flags().is_extended_length() { + target.append_slice( + &u16::try_from(len).unwrap_or(u16::MAX) + .to_be_bytes() + )?; + } else { + target.append_slice(&[ + u8::try_from(len).unwrap_or(u8::MAX) + ])?; + } + target.append_slice(self.value()) + } +} + + +//------------ Tests --------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + use crate::asn::Asn; + use crate::bgp::communities::Wellknown; + use crate::bgp::message::nlri::Nlri; + use crate::bgp::message::update::NextHop; + + #[test] + fn wireformat_to_owned_and_back() { + use super::PathAttribute as PA; + fn check(raw: Vec, owned: PathAttribute) { + let mut parser = Parser::from_ref(&raw); + let sc = SessionConfig::modern(); + let pa = WireformatPathAttribute::parse(&mut parser, sc) + .unwrap(); + assert_eq!(owned, pa.to_owned().unwrap()); + let mut target = Vec::new(); + owned.compose(&mut target).unwrap(); + assert_eq!(target, raw); + } + + check(vec![0x40, 0x01, 0x01, 0x00], PA::Origin(Origin::new(0.into()))); + + check( + vec![0x40, 0x02, 10, + 0x02, 0x02, // SEQUENCE of length 2 + 0x00, 0x00, 0x00, 100, + 0x00, 0x00, 0x00, 200, + ], + PA::AsPath(AsPath(HopPath::from(vec![ + Asn::from_u32(100), + Asn::from_u32(200)] + ))) + ); + + check( + vec![0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04], + PA::NextHop(NextHop("1.2.3.4".parse().unwrap())) + ); + + check( + vec![0x80, 0x04, 0x04, 0x00, 0x00, 0x00, 0xff], + PA::MultiExitDisc(MultiExitDisc::new(255)) + ); + + check( + vec![0x40, 0x05, 0x04, 0x00, 0x00, 0x00, 0x0a], + PA::LocalPref(LocalPref::new(10)) + ); + + check( + vec![0x40, 0x06, 0x00], + PA::AtomicAggregate(AtomicAggregate::new(())) + ); + + check( + vec![ + 0xc0, 0x07, 0x08, 0x00, 0x00, 0x00, 0x65, 0xc6, + 0x33, 0x64, 0x01 + ], + PA::Aggregator(Aggregator(AggregatorInfo::new( + Asn::from_u32(101), + "198.51.100.1".parse().unwrap() + ))) + ); + + check( + vec![ + 0xc0, 0x08, 0x10, 0x00, 0x2a, 0x02, 0x06, 0xff, + 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0x02, 0xff, + 0xff, 0xff, 0x03 + ], + { + let mut builder = StandardCommunitiesBuilder::new(); + builder.add_community("AS42:518".parse().unwrap()); + builder.add_community(Wellknown::NoExport.into()); + builder.add_community(Wellknown::NoAdvertise.into()); + builder.add_community(Wellknown::NoExportSubconfed.into()); + PA::Communities(Communities(builder)) + } + ); + + check( + vec![0x80, 0x09, 0x04, 0x0a, 0x00, 0x00, 0x04], + OriginatorId("10.0.0.4".parse().unwrap()).into() + ); + + check( + vec![0x80, 0x0a, 0x04, 0x0a, 0x00, 0x00, 0x03], + ClusterList(ClusterIds::new( + vec![[10, 0, 0, 3].into()] + )).into() + ); + + check( + vec![ + 0x80, 0x0e, 0x1c, + 0x00, 0x02, 0x01, + 0x10, + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34, + 0x00, + 0x30, 0x20, 0x01, 0x0d, 0xb8, 0xaa, 0xbb + ], + { + let mut builder = MpReachNlriBuilder::new( + AFI::Ipv6, + SAFI::Unicast, + NextHop::Unicast("2001:db8::1234".parse().unwrap()), + false // no addpath + ); + builder.add_announcement( + &Nlri::unicast_from_str("2001:db8:aabb::/48").unwrap() + ); + + MpReachNlri(builder).into() + } + ); + + check( + vec![ + 0x80, 0x0f, 0x27, 0x00, 0x02, 0x02, 0x40, 0x20, + 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x00, 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, 0x01, + 0x40, 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0x00, + 0x02, 0x40, 0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, + 0x00, 0x03 + ], + { + use crate::addr::Prefix; + use std::str::FromStr; + let mut builder = MpUnreachNlriBuilder::new( + AFI::Ipv6, + SAFI::Multicast, + false // no addpath + ); + [ + "2001:db8:ffff::/64", + "2001:db8:ffff:1::/64", + "2001:db8:ffff:2::/64", + "2001:db8:ffff:3::/64", + ].into_iter().for_each(|s|{ + builder.add_withdrawal( + &Nlri::Multicast::<&[u8]>( + Prefix::from_str(s).unwrap().into() + ) + ); + }); + + MpUnreachNlri(builder).into() + } + ); + + check( + vec![ + 0xc0, 0x10, 0x08, 0x00, 0x02, 0xfc, 0x85, 0x00, + 0x00, 0xcf, 0x08 + ], + ExtendedCommunities(ExtendedCommunitiesList::new(vec![ + "rt:64645:53000".parse().unwrap() + ])).into() + ); + + check( + vec![ + 0xc0, 0x11, 10, + 0x02, 0x02, // SEQUENCE of length 2 + 0x00, 0x00, 0x00, 100, + 0x00, 0x00, 0x00, 200, + ], + PA::As4Path(As4Path::new(HopPath::from(vec![ + Asn::from_u32(100), + Asn::from_u32(200)] + ))) + ); + + check( + vec![ + 0xc0, 0x12, 0x08, 0x00, 0x00, 0x04, 0xd2, + 10, 0, 0, 99 + ], + As4Aggregator(AggregatorInfo::new( + Asn::from_u32(1234), + "10.0.0.99".parse().unwrap() + )).into() + ); + + check( + vec![0xc0, 0x14, 0x04, 1, 2, 3, 4], + Connector("1.2.3.4".parse().unwrap()).into() + ); + + check( + vec![0xc0, 0x15, 0x05, 0x14, 0x00, 0x00, 0x04, 0xd2], + AsPathLimit( + AsPathLimitInfo::new(20, Asn::from_u32(1234)) + ).into() + ); + + //TODO 22 PmsiTunnel + //TODO 25 Ipv6ExtendedCommunities + + check( + vec![ + 0xc0, 0x20, 0x3c, 0x00, 0x00, 0x20, 0x5b, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0f, 0x00, + 0x00, 0xe2, 0x0a, 0x00, 0x00, 0x00, 0x64, 0x00, + 0x00, 0x0b, 0x62, 0x00, 0x00, 0xe2, 0x0a, 0x00, + 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x64, 0x00, + 0x00, 0xe2, 0x0a, 0x00, 0x00, 0x00, 0x67, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0xe2, 0x0a, 0x00, + 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x1f + ], + LargeCommunities(LargeCommunitiesList::new( + vec![ + "AS8283:6:15".parse().unwrap(), + "AS57866:100:2914".parse().unwrap(), + "AS57866:101:100".parse().unwrap(), + "AS57866:103:1".parse().unwrap(), + "AS57866:104:31".parse().unwrap(), + ] + )).into() + ); + + check( + vec![0xc0, 0x23, 0x04, 0x00, 0x00, 0x04, 0xd2], + Otc::new(Asn::from_u32(1234)).into() + ); + + // TODO AttrSet + // TODO Reserved? + + // UnimplementedPathAttribute + check( + vec![0xc0, 254, 0x04, 0x01, 0x02, 0x03, 0x04], + UnimplementedPathAttribute::new( + Flags::OPT_TRANS.into(), + 254, + vec![0x01, 0x02, 0x03, 0x04] + ).into() + ); + + + + } + + #[test] + fn iter_and_find() { + let raw = vec![ + 0x40, 0x01, 0x01, 0x00, // ORIGIN + 0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, // NEXTHOP + 0x80, 0x04, 0x04, 0x00, 0x00, 0x00, 0xff // MED + ]; + let pas = PathAttributes::new( + Parser::from_ref(&raw), SessionConfig::modern() + ); + //for _ in 0..4 { + // let pa = pas.next(); + // println!("{pa:?}"); + //} + + assert!(pas.get(PathAttributeType::Origin).is_some()); + assert!(pas.get(PathAttributeType::AsPath).is_none()); + assert!(pas.get(PathAttributeType::MultiExitDisc).is_some()); + assert!(pas.get(PathAttributeType::NextHop).is_some()); + } + + #[test] + fn unimplemented_path_attributes() { + let raw = vec![ + 0xc0, 254, 0x04, 0x01, 0x02, 0x03, 0x04 + ]; + let mut parser = Parser::from_ref(&raw); + let sc = SessionConfig::modern(); + let pa = WireformatPathAttribute::parse(&mut parser, sc); + + if let Ok(WireformatPathAttribute::Unimplemented(u)) = pa { + assert_eq!(u.type_code(), 254); + assert!(u.flags().is_optional()); + assert!(u.flags().is_transitive()); + assert_eq!(u.value(), &[0x01, 0x02, 0x03, 0x04]); + } else { + panic!("fail"); + } + + } + + #[test] + fn parse_unexpected_two_octet_asn() { + let raw = vec![ + 0xc0, 0x07, 0x06, 0x00, 0x65, 0xc6, + 0x33, 0x64, 0x01 + ]; + let mut parser = Parser::from_ref(&raw); + let pa = WireformatPathAttribute::parse( + &mut parser, SessionConfig::modern() + ).unwrap(); + assert!(matches!(pa, WireformatPathAttribute::Invalid(_,_,_))); + } + + /* + #[test] + fn deref_mut() { + // AS_PATH: AS_SEQUENCE(AS100, AS200) + let raw = vec![0x40, 0x02, 10, + 0x02, 0x02, // SEQUENCE of length 2 + 0x00, 0x00, 0x00, 100, + 0x00, 0x00, 0x00, 200, + ]; + + let pa = WireformatPathAttribute::parse( + &mut Parser::from_ref(&raw), SessionConfig::modern() + ).unwrap(); + let mut owned = pa.to_owned().unwrap(); + if let PathAttribute::AsPath(ref mut asp) = owned { + assert_eq!(format!("{}", **asp), "AS100 AS200"); + asp.prepend(Asn::from_u32(50)); + assert_eq!(format!("{}", **asp), "AS50 AS100 AS200"); + assert_eq!(3, asp.hop_count()); + } + + let mut composed = Vec::new(); + owned.compose(&mut composed).unwrap(); + assert!(composed != raw); + } + */ + + #[test] + fn as_ref_as_mut() { + // AS_PATH: AS_SEQUENCE(AS100, AS200) + let raw = vec![0x40, 0x02, 10, + 0x02, 0x02, // SEQUENCE of length 2 + 0x00, 0x00, 0x00, 100, + 0x00, 0x00, 0x00, 200, + ]; + + let pa = WireformatPathAttribute::parse( + &mut Parser::from_ref(&raw), SessionConfig::modern() + ).unwrap(); + let mut owned = pa.to_owned().unwrap(); + if let PathAttribute::AsPath(ref mut asp) = owned { + assert_eq!(format!("{}", asp.as_ref()), "AS100 AS200"); + asp.as_mut().prepend(Asn::from_u32(50)); + assert_eq!(format!("{}", asp.as_ref()), "AS50 AS100 AS200"); + assert_eq!(3, asp.as_ref().hop_count()); + } + + let mut composed = Vec::new(); + owned.compose(&mut composed).unwrap(); + assert!(composed != raw); + } + +} diff --git a/src/bgp/types.rs b/src/bgp/types.rs index 7e0470d1..8a4b42c6 100644 --- a/src/bgp/types.rs +++ b/src/bgp/types.rs @@ -1,5 +1,5 @@ use crate::typeenum; // from util::macros -use std::net::{Ipv4Addr, Ipv6Addr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use crate::bgp::message::nlri::RouteDistinguisher; @@ -8,6 +8,7 @@ use serde::{Serialize, Deserialize}; typeenum!( /// AFI as used in BGP OPEN and UPDATE messages. +#[cfg_attr(feature = "serde", serde(from = "u16"))] AFI, u16, { 1 => Ipv4, @@ -17,6 +18,7 @@ typeenum!( typeenum!( /// SAFI as used in BGP OPEN and UPDATE messages. +#[cfg_attr(feature = "serde", serde(from = "u8"))] SAFI, u8, { 1 => Unicast, @@ -30,27 +32,180 @@ typeenum!( 134 => FlowSpecVpn }); -/// BGP Origin types as used in BGP UPDATE messages. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -pub enum OriginType { - Igp, - Egp, - Incomplete, - Unknown(u8), +/// Valid/supported pair of `AFI` and `SAFI`. +/// +/// Not all combinations of the `AFI` and `SAFI` variants make sense. This +/// enum explicitly comprises combinations which are described in standards +/// documents. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum AfiSafi { + Ipv4Unicast, + Ipv6Unicast, + Ipv4Multicast, + Ipv6Multicast, + + Ipv4MplsUnicast, + Ipv6MplsUnicast, + + Ipv4MplsVpnUnicast, + Ipv6MplsVpnUnicast, + + Ipv4RouteTarget, + + Ipv4FlowSpec, + Ipv6FlowSpec, + + L2VpnVpls, + L2VpnEvpn, } -impl std::fmt::Display for OriginType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl TryFrom<(AFI, SAFI)> for AfiSafi { + type Error = &'static str; + fn try_from(t: (AFI, SAFI)) -> Result { + + use AfiSafi::*; + match t { + (AFI::Ipv4, SAFI::Unicast) => Ok(Ipv4Unicast), + (AFI::Ipv6, SAFI::Unicast) => Ok(Ipv6Unicast), + + (AFI::Ipv4, SAFI::Multicast) => Ok(Ipv4Multicast), + (AFI::Ipv6, SAFI::Multicast) => Ok(Ipv6Multicast), + + (AFI::Ipv4, SAFI::MplsUnicast) => Ok(Ipv4MplsUnicast), + (AFI::Ipv6, SAFI::MplsUnicast) => Ok(Ipv6MplsUnicast), + + (AFI::Ipv4, SAFI::MplsVpnUnicast) => Ok(Ipv4MplsVpnUnicast), + (AFI::Ipv6, SAFI::MplsVpnUnicast) => Ok(Ipv6MplsVpnUnicast), + + (AFI::Ipv4, SAFI::RouteTarget) => Ok(Ipv4RouteTarget), + + (AFI::Ipv4, SAFI::FlowSpec) => Ok(Ipv4FlowSpec), + (AFI::Ipv6, SAFI::FlowSpec) => Ok(Ipv6FlowSpec), + + (AFI::L2Vpn, SAFI::Vpls) => Ok(L2VpnVpls), + (AFI::L2Vpn, SAFI::Evpn) => Ok(L2VpnEvpn), + _ => Err("unsupported AFI/SAFI combination") + } + } +} + +impl AfiSafi { + pub fn afi(&self) -> AFI { + self.split().0 + } + + pub fn safi(&self) -> SAFI { + self.split().1 + } + + pub fn split(&self) -> (AFI, SAFI) { match self { - OriginType::Igp => write!(f, "IGP"), - OriginType::Egp => write!(f, "EGP"), - OriginType::Incomplete => write!(f, "Incomplete"), - OriginType::Unknown(val) => write!(f, "Unknown: {}", val), + Self::Ipv4Unicast => (AFI::Ipv4, SAFI::Unicast), + Self::Ipv6Unicast => (AFI::Ipv6, SAFI::Unicast), + Self::Ipv4Multicast => (AFI::Ipv4, SAFI::Multicast), + Self::Ipv6Multicast => (AFI::Ipv6, SAFI::Multicast), + + Self::Ipv4MplsUnicast => (AFI::Ipv4, SAFI::MplsUnicast), + Self::Ipv6MplsUnicast => (AFI::Ipv6, SAFI::MplsUnicast), + + Self::Ipv4MplsVpnUnicast => (AFI::Ipv4, SAFI::MplsVpnUnicast), + Self::Ipv6MplsVpnUnicast => (AFI::Ipv6, SAFI::MplsVpnUnicast), + + Self::Ipv4RouteTarget => (AFI::Ipv4, SAFI::RouteTarget), + + Self::Ipv4FlowSpec => (AFI::Ipv4, SAFI::FlowSpec), + Self::Ipv6FlowSpec => (AFI::Ipv6, SAFI::FlowSpec), + + Self::L2VpnVpls => (AFI::L2Vpn, SAFI::Vpls), + Self::L2VpnEvpn => (AFI::L2Vpn, SAFI::Evpn), + } + } +} + + +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct AddpathFamDir(AfiSafi, AddpathDirection); +impl AddpathFamDir { + pub fn new(afisafi: AfiSafi, apd: AddpathDirection) -> Self { + Self(afisafi, apd) + } + + pub fn merge(&self, other: Self) -> Option { + if self.0 != other.0 { + return None; + } + self.1.merge(other.1).map(|dir| Self::new(self.0, dir)) + } + + pub fn fam(&self) -> AfiSafi { + self.0 + } + + pub fn dir(&self) -> AddpathDirection { + self.1 + } +} + +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum AddpathDirection { + Receive = 1, + Send = 2, + SendReceive = 3, +} + +impl AddpathDirection { + pub fn merge(&self, other: Self) -> Option { + match (self, other) { + (Self::Receive, Self::Receive) => None, + (Self::Send, Self::Send) => None, + (Self::SendReceive, Self::SendReceive) => Some(Self::SendReceive), + (Self::Send, Self::Receive | Self::SendReceive) => Some(Self::Send), + (Self::Receive, Self::Send | Self::SendReceive) => Some(Self::Receive), + (Self::SendReceive, Self::Send) => Some(Self::Receive), + (Self::SendReceive, Self::Receive) => Some(Self::Send), } } } +impl TryFrom for AddpathDirection { + type Error = &'static str; + fn try_from(u: u8) -> Result { + match u { + 1 => Ok(Self::Receive), + 2 => Ok(Self::Send), + 3 => Ok(Self::SendReceive), + _ => Err("invalid ADDPATH send/receive value") + } + } + +} + + +typeenum!( +/// BGP Origin types as used in BGP UPDATE messages. + OriginType, u8, + { + 0 => Igp, + 1 => Egp, + 2 => Incomplete, + } +); + +typeenum!( +/// Enhanced Route Refresh subtypes. + RouteRefreshSubtype, u8, + { + 0 => Normal, + 1 => Begin, + 2 => End, + 255 => Reserved + } +); + typeenum!( /// PathAttributeType /// @@ -110,26 +265,61 @@ impl std::fmt::Display for LocalPref { /// Conventional and BGP-MP Next Hop variants. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +// XXX introduce v4/v6 specific Empty for FlowSpec? it does not carry +// anything, but when creating the NextHop in +// MpReachNlriBuilder::new_for_nexthop() we need both an AFI and a SAFI. pub enum NextHop { - Ipv4(Ipv4Addr), - Ipv6(Ipv6Addr), - Ipv6LL(Ipv6Addr, Ipv6Addr), + Unicast(IpAddr), + Multicast(IpAddr), + Ipv6LL(Ipv6Addr, Ipv6Addr), // is this always unicast? + + // XXX can we consolidate these two into one with IpAddr? Ipv4MplsVpnUnicast(RouteDistinguisher, Ipv4Addr), Ipv6MplsVpnUnicast(RouteDistinguisher, Ipv6Addr), + Empty, // FlowSpec + Evpn(IpAddr), Unimplemented(AFI, SAFI), } +impl NextHop { + pub fn new(afi: AFI, safi: SAFI) -> Self { + match (afi, safi) { + (AFI::Ipv4, SAFI::Unicast) => Self::Unicast(Ipv4Addr::from(0).into()), + (AFI::Ipv6, SAFI::Unicast) => Self::Unicast(Ipv6Addr::from(0).into()), + (AFI::Ipv4, SAFI::Multicast) => Self::Multicast(Ipv4Addr::from(0).into()), + (AFI::Ipv6, SAFI::Multicast) => Self::Multicast(Ipv6Addr::from(0).into()), + (AFI::Ipv4 | AFI::Ipv6, SAFI::FlowSpec) => Self::Empty, + + (_, _) => Self::Unimplemented(afi, safi) + } + } + + pub fn afi_safi(&self) -> (AFI, SAFI) { + match self { + Self::Unicast(IpAddr::V4(_)) => (AFI::Ipv4, SAFI::Unicast), + Self::Unicast(IpAddr::V6(_)) => (AFI::Ipv6, SAFI::Unicast), + Self::Multicast(IpAddr::V4(_)) => (AFI::Ipv4, SAFI::Multicast), + Self::Multicast(IpAddr::V6(_)) => (AFI::Ipv6, SAFI::Multicast), + Self::Ipv6LL(..) => (AFI::Ipv6, SAFI::Unicast), // always unicast? + Self::Empty => (AFI::Ipv4, SAFI::FlowSpec), + Self::Evpn(IpAddr::V4(_)) => (AFI::Ipv4, SAFI::Unicast), + Self::Evpn(IpAddr::V6(_)) => (AFI::Ipv6, SAFI::Unicast), + _ => todo!("{}", &self) + } + } +} + impl std::fmt::Display for NextHop { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - NextHop::Ipv4(ip) => write!(f, "{}", ip), - NextHop::Ipv6(ip) => write!(f, "{}", ip), - NextHop::Ipv6LL(ip1, ip2) => write!(f, "{} {} ", ip1, ip2), - NextHop::Ipv4MplsVpnUnicast(rd, ip) => write!(f, "rd {} {}", rd, ip), - NextHop::Ipv6MplsVpnUnicast(rd, ip) => write!(f, "rd {} {}", rd, ip), - NextHop::Empty => write!(f, "empty"), - NextHop::Unimplemented(afi, safi) => write!(f, "unimplemented for AFI {} /SAFI {}", afi, safi), + Self::Unicast(ip) | Self::Multicast(ip) => write!(f, "{}", ip), + Self::Ipv6LL(ip1, ip2) => write!(f, "{} {} ", ip1, ip2), + Self::Ipv4MplsVpnUnicast(rd, ip) => write!(f, "rd {} {}", rd, ip), + Self::Ipv6MplsVpnUnicast(rd, ip) => write!(f, "rd {} {}", rd, ip), + Self::Empty => write!(f, "empty"), + Self::Evpn(ip) => write!(f, "evpn-{}", ip), + Self::Unimplemented(afi, safi) => write!(f, "unimplemented for AFI {} /SAFI {}", afi, safi), } } } diff --git a/src/bmp/message.rs b/src/bmp/message.rs index ba19ad80..d9ec0dfa 100644 --- a/src/bmp/message.rs +++ b/src/bmp/message.rs @@ -7,7 +7,7 @@ use crate::asn::Asn; use crate::bgp::message::{Message as BgpMsg, OpenMessage as BgpOpen, UpdateMessage as BgpUpdate, NotificationMessage as BgpNotification}; use crate::bgp::types::{AFI, SAFI}; -use crate::bgp::message::update::SessionConfig; +use crate::bgp::message::update::{SessionConfig, FourOctetAsn}; use crate::bgp::message::open::CapabilityType; use crate::util::parser::ParseError; use crate::typeenum; // from util::macros @@ -59,9 +59,8 @@ impl Error for MessageError { } /// including the [`CommonHeader`], possibly a [`PerPeerHeader`] and the /// additional payload. The payload often comprises one or multiple /// [`bgp::Message`](crate::bgp::Message)s. - +#[derive(Clone, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Eq)] pub enum Message> { RouteMonitoring(RouteMonitoring), StatisticsReport(StatisticsReport), @@ -169,7 +168,7 @@ impl<'a, Octs: Octets + 'a> Message { } impl Message { - pub fn check(src: &mut Cursor<&[u8]>) -> Result { + pub fn check(src: &mut Cursor) -> Result { if src.remaining() >= 5 { let _version = src.get_u8(); let len = src.get_u32(); @@ -488,7 +487,7 @@ impl> PerPeerHeader { ); DateTime::::MIN_UTC } - } + } } impl> Display for PerPeerHeader { @@ -553,7 +552,7 @@ typeenum!( /// Route Monitoring message. #[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct RouteMonitoring> { octets: Octets @@ -603,9 +602,8 @@ impl RouteMonitoring { } /// Statistics Report message. - #[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] pub struct StatisticsReport { octets: Octs, } @@ -668,7 +666,7 @@ impl Debug for StatisticsReport { /// Peer Down Notification. #[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct PeerDownNotification> { octets: Octets, } @@ -777,7 +775,7 @@ impl PeerDownNotification { /// Peer Up Notification. #[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct PeerUpNotification> { octets: Octets, } @@ -863,26 +861,83 @@ impl PeerUpNotification { (sent, rcvd) } - /// Create a [`SessionConfig`] describing the parameters for the BGP - /// session between the monitored router and the remote peer. + /// Create a [`SessionConfig`] to parse encapsulated BGP data based on the + /// PerPeerHeader. /// /// The information in this `SessionConfig` is necessary for correctly /// parsing future messages, specifically BGP UPDATEs carried in /// RouteMonitoring BMP messages. See [`SessionConfig`] for an example /// using it in that way. - pub fn session_config(&self) -> SessionConfig { + /// + /// Note that this function returns the four octet capability set in the + /// per peer header, *not* the same capability in the encapsulated BGP + /// OPEN message. This method should normally be used by a BMP monitoring + /// station, when receiving a PeerUpNotification. + /// + /// Returns the SessionConfig and an optional tuple if the BGP OPEN four + /// octet ASN capability and the one in the Per Peer Header are not the + /// same. + pub fn pph_session_config(&self) -> (SessionConfig, Option<(FourOctetAsn, FourOctetAsn)>) { let (sent, rcvd) = self.bgp_open_sent_rcvd(); let mut conf = SessionConfig::modern(); // The 'modern' SessionConfig has four octet capability set to // enabled, so we need to disable it if any of both of the peers do // not support it. - if !sent.four_octet_capable() || !rcvd.four_octet_capable() { - conf.disable_four_octet_asn(); + let bgp_four_octet = match sent.four_octet_capable() && rcvd.four_octet_capable() { + true => FourOctetAsn::Enabled, + false => FourOctetAsn::Disabled + }; + + let four_octet_asn = match self.per_peer_header().is_legacy_format() { + false => FourOctetAsn::Enabled, + true => FourOctetAsn::Disabled + }; + + conf.set_four_octet_asn(four_octet_asn); + + for famdir in sent.addpath_intersection(&rcvd) { + conf.add_famdir(famdir); } - if sent.add_path_capable() && rcvd.add_path_capable() { - conf.enable_addpath() + let inconsistent = + if four_octet_asn == bgp_four_octet { + None + } else { + Some((four_octet_asn, bgp_four_octet)) + }; + + (conf, inconsistent) + } + + /// Create a [`SessionConfig`] to parse encapsulated BGP data based on the + /// exchanged BGP OPENs. session between the monitored router and the + /// remote peer. + /// + /// The information in this `SessionConfig` is necessary for correctly + /// parsing future messages, specifically BGP UPDATEs carried in + /// RouteMonitoring BMP messages. See [`SessionConfig`] for an example + /// using it in that way. + /// + /// Note that this function does not consider the Per Peer Header four + /// octet ASN capability. Use `pph_session_config()` for that. This method + /// should probably not be used by a BMP monitoring station by default. + pub fn session_config(&self) -> SessionConfig { + let (sent, rcvd) = self.bgp_open_sent_rcvd(); + let mut conf = SessionConfig::modern(); + + // The 'modern' SessionConfig has four octet capability set to + // enabled, so we need to disable it if any of both of the peers do + // not support it. + let bgp_four_octet = match sent.four_octet_capable() && rcvd.four_octet_capable() { + true => FourOctetAsn::Enabled, + false => FourOctetAsn::Disabled + }; + + conf.set_four_octet_asn(bgp_four_octet); + + for famdir in sent.addpath_intersection(&rcvd) { + conf.add_famdir(famdir); } conf @@ -961,7 +1016,7 @@ impl PeerUpNotification { /// Initiation Message. #[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct InitiationMessage> { octets: Octets, } @@ -1002,7 +1057,7 @@ impl InitiationMessage { /// Termination message. #[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct TerminationMessage> { octets: Octets, } @@ -1048,7 +1103,7 @@ impl TerminationMessage { /// /// NB: Not well tested/supported at this moment! #[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct RouteMirroring { octets: Octs, } @@ -1621,8 +1676,10 @@ mod tests { use bytes::Bytes; use std::str::FromStr; use crate::addr::Prefix; - use crate::bgp::types::{AFI, SAFI, PathAttributeType}; - use crate::bgp::message::update::{AddPath, FourOctetAsn, SessionConfig}; + use crate::bgp::types::{AFI, SAFI}; + use crate::bgp::path_attributes::PathAttributeType; + use crate::bgp::message::nlri::Nlri; + use crate::bgp::message::update::{FourOctetAsn, SessionConfig}; // Helper for generating a .pcap, pass output to `text2pcap`. #[allow(dead_code)] @@ -1732,43 +1789,47 @@ mod tests { //-- from here on, this actually tests the bgp parsing functionality // rather than the bmp one, but let's leave it for now --------------- - assert_eq!(bgp_update.as_ref().len(), 55); + assert_eq!(bgp_update.as_ref().len(), 55 - 19); assert_eq!(bgp_update.withdrawn_routes_len(), 0); - let pas = bgp_update.path_attributes(); - let mut pas = pas.iter(); - let pa1 = pas.next().unwrap(); + let mut pas = bgp_update.path_attributes().unwrap().into_iter(); + let pa1 = pas.next().unwrap().unwrap(); assert_eq!(pa1.type_code(), PathAttributeType::Origin); - assert_eq!(pa1.flags(), 0x40); - assert!(pa1.is_transitive()); - assert!(!pa1.is_optional()); + assert_eq!(pa1.flags(), 0x40.into()); + assert!( pa1.flags().is_transitive()); + assert!(!pa1.flags().is_optional()); //TODO implement enum for Origins - assert_eq!(pa1.value().as_ref(), [0x00]); + //assert_eq!(pa1.as_ref(), [0x00]); - let pa2 = pas.next().unwrap(); + let pa2 = pas.next().unwrap().unwrap(); assert_eq!(pa2.type_code(), PathAttributeType::AsPath); - assert_eq!(pa2.flags(), 0x40); + assert_eq!(pa2.flags(), 0x40.into()); // TODO check actual AS_PATH contents - let pa3 = pas.next().unwrap(); + let pa3 = pas.next().unwrap().unwrap(); assert_eq!(pa3.type_code(), PathAttributeType::NextHop); - assert_eq!(pa3.flags(), 0x40); - assert_eq!(pa3.value().as_ref(), [10, 255, 0, 101]); + assert_eq!(pa3.flags(), 0x40.into()); + //assert_eq!(pa3.as_ref(), [10, 255, 0, 101]); - let pa4 = pas.next().unwrap(); + let pa4 = pas.next().unwrap().unwrap(); assert_eq!(pa4.type_code(), PathAttributeType::MultiExitDisc); - assert_eq!(pa4.flags(), 0x80); - assert!(pa4.is_optional()); - assert_eq!(pa4.value().as_ref(), [0, 0, 0, 1]); + assert_eq!(pa4.flags(), 0x80.into()); + assert!(pa4.flags().is_optional()); + //assert_eq!(pa4.as_ref(), [0, 0, 0, 1]); assert!(pas.next().is_none()); // NLRI - let nlris = bgp_update.nlris(); - let mut nlris = nlris.iter(); - let n1 = nlris.next().unwrap().prefix(); - assert_eq!(n1, Some(Prefix::from_str("10.10.10.2/32").unwrap())); + let mut nlris = bgp_update.announcements().unwrap(); + if let Some(Ok(Nlri::Unicast(n1))) = nlris.next() { + assert_eq!( + n1.prefix(), + Prefix::from_str("10.10.10.2/32").unwrap() + ); + } else { + panic!() + } assert!(nlris.next().is_none()); } @@ -1968,9 +2029,10 @@ mod tests { assert_eq!(sent.as_ref(), bgp_open_sent.as_ref()); assert_eq!(rcvd.as_ref(), bgp_open_rcvd.as_ref()); - let sc = bmp.session_config(); - assert_eq!(sc.four_octet_asn, FourOctetAsn::Enabled); - assert_eq!(sc.add_path, AddPath::Disabled); + let sc = bmp.pph_session_config(); + assert_eq!(sc.1, None); + assert_eq!(sc.0.four_octet_asn, FourOctetAsn::Enabled); + assert_eq!(sc.0.enabled_addpaths().count(), 0); assert_eq!( bmp.supported_protocols(), diff --git a/src/flowspec.rs b/src/flowspec.rs index a760ab7a..3090c587 100644 --- a/src/flowspec.rs +++ b/src/flowspec.rs @@ -3,7 +3,7 @@ use crate::addr::Prefix; use crate::bgp::types::AFI; use crate::util::parser::ParseError; -use log::warn; +use log::debug; use octseq::{Octets, Parser}; use std::net::IpAddr; @@ -286,10 +286,12 @@ impl Component { parser.parse_octets(octets_len)? ) }, - _ => { warn!("unimplemented typ {}", typ); unimplemented!() } + _ => { + debug!("unimplemented flowspec type {}", typ); + return Err(ParseError::Unsupported) + } }; - //debug!("flowspec component res: {:?}", res); Ok(res) } } diff --git a/src/util/macros.rs b/src/util/macros.rs index a1b64421..cf9e55e0 100644 --- a/src/util/macros.rs +++ b/src/util/macros.rs @@ -61,9 +61,9 @@ macro_rules! typeenum { $( $x1:pat => $y1:ident ),* $(,)* })? ) => { - $(#[$attr])* #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + $(#[$attr])* pub enum $name { $($y),+, $($($y1($ty),)?)? diff --git a/src/util/parser.rs b/src/util/parser.rs index 7e481915..dc53f52c 100644 --- a/src/util/parser.rs +++ b/src/util/parser.rs @@ -53,6 +53,8 @@ pub enum ParseError { /// Required stateful information was not provided. StateRequired, + + Unsupported, } impl ParseError { @@ -83,7 +85,8 @@ impl fmt::Display for ParseError { match *self { ParseError::ShortInput => f.write_str("unexpected end of input"), ParseError::Form(ref err) => err.fmt(f), - ParseError::StateRequired => f.write_str("required stateful parsing info missing") + ParseError::StateRequired => f.write_str("required stateful parsing info missing"), + ParseError::Unsupported => f.write_str("parsing unsupported") } } }