//! Interoperability with what real routers put on the wire. use std::net::Ipv4Addr; use std::time::Duration; use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_server::TestServer; use bgp_packet::message::{Message, PathAttribute}; use bgp_packet::open::{Capability, OpenOption}; use bgp_primitives::constants::{AddressFamilyId, SubsequentAfi}; use bgp_primitives::ip_prefix::IpPrefix; use bgp_rib::{LocalRoute, LocalRouteAttrs}; use eyre::Result; const TIMEOUT: Duration = Duration::from_secs(5); /// The OPEN a BIRD 2 router sends for an IPv6-only session from AS210036: /// one optional parameter carrying MP(IPv6 unicast), route refresh, /// extended message, graceful restart (aware only), 4-octet AS, enhanced /// route refresh and long-lived graceful restart (aware only). #[rustfmt::skip] const BIRD_OPEN: &[u8] = &[ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x37, 0x01, // length 55, type OPEN 0x04, // version 0x5b, 0xa0, // AS_TRANS 0x00, 0xf0, // hold time 240 0x0a, 0xff, 0x00, 0x01, // router id 10.255.0.1 0x1a, // optional parameters length 26 0x02, 0x18, // capabilities, 24 bytes 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, // MP: IPv6 unicast 0x02, 0x00, // route refresh 0x06, 0x00, // extended message 0x40, 0x02, 0x00, 0x00, // graceful restart, no address families 0x41, 0x04, 0x00, 0x03, 0x34, 0x74, // 4-octet AS 210036 0x46, 0x00, // enhanced route refresh 0x47, 0x00, // long-lived graceful restart, none ]; /// IPv6 End-of-RIB: an UPDATE whose only attribute is an empty /// MP_UNREACH_NLRI for IPv6 unicast (RFC 4724 ยง2). #[rustfmt::skip] const IPV6_END_OF_RIB: &[u8] = &[ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1e, 0x02, // length 30, type UPDATE 0x00, 0x00, // no withdrawn routes 0x00, 0x07, // 7 bytes of path attributes 0x90, 0x0f, 0x00, 0x03, // optional + extended length, MP_UNREACH_NLRI, len 3 0x00, 0x02, 0x01, // IPv6 unicast, no prefixes ]; #[tokio::test] async fn test_server_advertises_ipv4_and_ipv6_unicast() -> Result<()> { let server = TestServer::start(65000).await?; let mut peer = TestPeer::connect(server.addr).await?; let open = peer.recv_open().await?; let caps: Vec<&Capability> = open .options .iter() .flat_map(|o| match o { OpenOption::Capabilities(c) => c.iter(), }) .collect(); for afi in [AddressFamilyId::Ipv4, AddressFamilyId::Ipv6] { assert!( caps.iter().any(|c| matches!(c, Capability::MultiProtocol { afi: a, safi: SubsequentAfi::Unicast } if *a == afi)), "missing MP capability for {afi:?}: {caps:?}" ); } Ok(()) } #[tokio::test] async fn test_bird_open_and_end_of_rib_are_accepted() -> Result<()> { let server = TestServer::start(4200000001).await?; let mut peer = TestPeer::connect(server.addr).await?; peer.send_raw(BIRD_OPEN).await?; peer.recv_open().await?; match tokio::time::timeout(TIMEOUT, peer.recv()).await?? { Message::KeepAlive => {} other => eyre::bail!("server did not accept BIRD's OPEN: {other:?}"), } peer.send(&Message::KeepAlive).await?; tokio::time::timeout(TIMEOUT, async { while server.registry.list().is_empty() { tokio::time::sleep(Duration::from_millis(10)).await; } }) .await?; let session = server.registry.list().remove(0); assert_eq!(session.peer.asn, 210036, "4-octet ASN must come from the capability, not AS_TRANS"); assert!(session.four_octet_asn); // End-of-RIB must neither break the session nor create a route. peer.send_raw(IPV6_END_OF_RIB).await?; peer.send(&Message::KeepAlive).await?; tokio::time::sleep(Duration::from_millis(200)).await; assert_eq!(server.registry.list().len(), 1, "session dropped after End-of-RIB"); assert!(server.adj_rib_in.snapshot().await.is_empty()); Ok(()) } /// Over an IPv4 test transport an IPv6 route cannot be sent (no IPv6 next /// hop), so this only checks the builder the advertiser uses: the UPDATE for /// a lab /48 carries MP_REACH_NLRI with an IPv6 next hop and no NEXT_HOP. #[tokio::test] async fn test_ipv6_lab_prefix_update_shape() -> Result<()> { let route = LocalRoute::new(IpPrefix::try_from("2a0d:d741:1::/48")?, "student1", LocalRouteAttrs::default()); let nh: std::net::Ipv6Addr = "fd00:179::2".parse()?; let update = bgp_server::advertise::build_announce(&route, 4200000001, nh.into()).expect("ipv6 over ipv6"); assert!(update.announced_routes.is_empty()); assert!(!update.path_attributes.iter().any(|a| matches!(a, PathAttribute::NextHop(_)))); assert!(update.path_attributes.iter().any(|a| matches!(a, PathAttribute::MpReachNlri(m) if m.afi == AddressFamilyId::Ipv6 && m.prefixes[0].to_string() == "2a0d:d741:1::/48"))); let _ = Ipv4Addr::LOCALHOST; Ok(()) }