//! End-to-end tests for errors in UPDATE messages (RFC 7606): most of them //! cost the routes in the UPDATE, not the session. use std::net::Ipv4Addr; use std::time::Duration; use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_server::{ReceivedRoute, TestServer}; use bgp_packet::errors::{BgpError, UpdateMsgSubcode}; use bgp_packet::message::{LocalPrefPathAttribute, Message, PathAttribute}; use bgp_primitives::ip_prefix::IpPrefix; use bgp_server::metrics::UPDATE_ERRORS; use eyre::{Result, bail}; const LOCAL_ASN: u32 = 65000; const PEER_ASN: u32 = 65001; const TIMEOUT: Duration = Duration::from_secs(5); const WELL_KNOWN: u8 = 0x40; const OPTIONAL_TRANSITIVE: u8 = 0xc0; fn attr(flags: u8, type_code: u8, value: &[u8]) -> Vec { let mut wire = vec![flags, type_code, value.len() as u8]; wire.extend_from_slice(value); wire } fn origin() -> Vec { attr(WELL_KNOWN, 1, &[0]) } /// AS_SEQUENCE of the peer's AS, four octets wide. fn as_path() -> Vec { attr(WELL_KNOWN, 2, &[2, 1, 0, 0, 0xfd, 0xe9]) } fn next_hop() -> Vec { attr(WELL_KNOWN, 3, &[192, 0, 2, 1]) } /// A whole UPDATE message announcing `nlri` with `attributes`, as bytes, so /// that it can be wrong in ways the encoder would not write. fn update(attributes: &[Vec], nlri: &[u8]) -> Vec { let attributes = attributes.concat(); let mut body = vec![0, 0]; body.extend_from_slice(&(attributes.len() as u16).to_be_bytes()); body.extend_from_slice(&attributes); body.extend_from_slice(nlri); let mut wire = vec![0xff; 16]; wire.extend_from_slice(&((19 + body.len()) as u16).to_be_bytes()); wire.push(2); wire.extend_from_slice(&body); wire } /// 10.1.0.0/16 and 10.2.0.0/16. const ROUTE_A: [u8; 3] = [16, 10, 1]; const ROUTE_B: [u8; 3] = [16, 10, 2]; fn prefix(s: &str) -> IpPrefix { IpPrefix::try_from(s).unwrap() } async fn established(server: &TestServer, asn: u32) -> Result { let mut peer = TestPeer::connect(server.addr).await?; peer.handshake(asn, Ipv4Addr::new(10, 0, 0, 1), 90).await?; Ok(peer) } /// Waits until the server's Adj-RIB-In holds exactly `prefixes`. async fn wait_for_routes(server: &TestServer, prefixes: &[&str]) -> Result> { let mut expected: Vec = prefixes.iter().map(|p| prefix(p)).collect(); expected.sort_by_key(|p| p.to_string()); Ok(tokio::time::timeout(TIMEOUT, async { loop { let mut routes = server.received_routes(); routes.sort_by_key(|r| r.prefix.to_string()); if routes.iter().map(|r| &r.prefix).eq(expected.iter()) { return routes; } tokio::time::sleep(Duration::from_millis(10)).await; } }) .await?) } /// The situation RFC 7606 exists for: a route is in the table, and then an /// UPDATE for it arrives with a broken attribute. The route goes; the /// session, and the other routes learned over it, stay. #[tokio::test] async fn test_a_malformed_attribute_withdraws_the_route_and_keeps_the_session() -> Result<()> { let server = TestServer::start(LOCAL_ASN).await?; let mut peer = established(&server, PEER_ASN).await?; let handled_before = UPDATE_ERRORS .with_label_values(&["treat-as-withdraw"]) .get(); let good = [origin(), as_path(), next_hop()]; peer.send_raw(&update(&good, &[ROUTE_A, ROUTE_B].concat())) .await?; wait_for_routes(&server, &["10.1.0.0/16", "10.2.0.0/16"]).await?; // ORIGIN with a value that is not defined. let bad = [attr(WELL_KNOWN, 1, &[7]), as_path(), next_hop()]; peer.send_raw(&update(&bad, &ROUTE_A)).await?; wait_for_routes(&server, &["10.2.0.0/16"]).await?; // The session goes on: the route can be announced again. peer.send_raw(&update(&good, &ROUTE_A)).await?; wait_for_routes(&server, &["10.1.0.0/16", "10.2.0.0/16"]).await?; assert_eq!(server.registry.list().len(), 1); assert!( UPDATE_ERRORS .with_label_values(&["treat-as-withdraw"]) .get() > handled_before ); Ok(()) } /// An announcement without a mandatory attribute is not a route we can use. #[tokio::test] async fn test_a_route_without_as_path_is_not_accepted() -> Result<()> { let server = TestServer::start(LOCAL_ASN).await?; let mut peer = established(&server, PEER_ASN).await?; peer.send_raw(&update(&[origin(), next_hop()], &ROUTE_A)) .await?; // A well-formed one after it shows that the first was dealt with. peer.send_raw(&update(&[origin(), as_path(), next_hop()], &ROUTE_B)) .await?; wait_for_routes(&server, &["10.2.0.0/16"]).await?; assert_eq!(server.registry.list().len(), 1); Ok(()) } /// A broken attribute that plays no part in choosing a route is dropped, /// and the route is kept. #[tokio::test] async fn test_a_malformed_aggregator_is_dropped_and_the_route_kept() -> Result<()> { let server = TestServer::start(LOCAL_ASN).await?; let mut peer = established(&server, PEER_ASN).await?; let attributes = [ origin(), as_path(), next_hop(), // AGGREGATOR is eight octets on this session. attr(OPTIONAL_TRANSITIVE, 7, &[0, 0, 0xfd, 0xe9, 192]), ]; peer.send_raw(&update(&attributes, &ROUTE_A)).await?; let routes = wait_for_routes(&server, &["10.1.0.0/16"]).await?; assert_eq!(routes[0].attributes.len(), 3); assert!( !routes[0] .attributes .iter() .any(|a| matches!(a, PathAttribute::Aggregator(_))) ); Ok(()) } /// LOCAL_PREF is a matter between routers of one AS. From an external peer /// it is dropped; from an internal one it is kept. #[tokio::test] async fn test_local_pref_is_kept_only_from_an_internal_peer() -> Result<()> { let local_pref = attr(WELL_KNOWN, 5, &[0, 0, 0, 200]); let has_local_pref = |routes: &[ReceivedRoute]| { routes[0] .attributes .contains(&PathAttribute::LocalPref(LocalPrefPathAttribute(200))) }; let server = TestServer::start(LOCAL_ASN).await?; let mut external = established(&server, PEER_ASN).await?; let attributes = [origin(), as_path(), next_hop(), local_pref.clone()]; external.send_raw(&update(&attributes, &ROUTE_A)).await?; let routes = wait_for_routes(&server, &["10.1.0.0/16"]).await?; assert!(!has_local_pref(&routes)); let server = TestServer::start(LOCAL_ASN).await?; let mut internal = established(&server, LOCAL_ASN).await?; // An internal peer has not added its AS to the path. let attributes = [origin(), attr(WELL_KNOWN, 2, &[]), next_hop(), local_pref]; internal.send_raw(&update(&attributes, &ROUTE_A)).await?; let routes = wait_for_routes(&server, &["10.1.0.0/16"]).await?; assert!(has_local_pref(&routes)); Ok(()) } /// When the prefixes themselves cannot be read there is nothing to /// withdraw, and the session is reset as before. #[tokio::test] async fn test_an_unreadable_prefix_still_resets_the_session() -> Result<()> { let server = TestServer::start(LOCAL_ASN).await?; let mut peer = established(&server, PEER_ASN).await?; // An IPv4 prefix of length 33. peer.send_raw(&update( &[origin(), as_path(), next_hop()], &[33, 10, 1, 2, 3, 4], )) .await?; loop { match tokio::time::timeout(TIMEOUT, peer.recv()).await?? { Message::KeepAlive => {} Message::Notification(n) => { assert_eq!( n.error, BgpError::UpdateMsg(UpdateMsgSubcode::InvalidNetworkField) ); break; } other => bail!("expected NOTIFICATION, got {other:?}"), } } tokio::time::timeout(TIMEOUT, async { while !server.registry.list().is_empty() { tokio::time::sleep(Duration::from_millis(10)).await; } }) .await?; Ok(()) }