//! The active side: the server dials its configured peers and keeps //! retrying. use std::net::{Ipv4Addr, SocketAddr}; use std::time::Duration; use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_server::TestServer; use bgp_packet::errors::{BgpError, OpenMsgSubcode}; use bgp_packet::message::Message; use bgp_primitives::ip_prefix::IpPrefix; use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_server::config::PeerConfig; use bgp_server::session::DialState; use eyre::Result; use tokio::net::TcpListener; const LOCAL_ASN: u32 = 65000; const PEER_ASN: u32 = 65001; const TIMEOUT: Duration = Duration::from_secs(5); /// Starts a server configured to dial `listener`'s address as peer "lab". async fn server_dialing(listener: &TcpListener, remote_asn: u32) -> Result { let endpoint: SocketAddr = listener.local_addr()?; TestServer::start_with(LOCAL_ASN, |c| { c.connect_retry = Duration::from_millis(200); c.peers = vec![PeerConfig { name: "lab".into(), endpoint, remote_asn, hold_time_secs: 90, local_address: None }]; }) .await } async fn accept(listener: &TcpListener) -> Result { let (stream, _) = tokio::time::timeout(TIMEOUT, listener.accept()).await??; Ok(TestPeer::from_stream(stream)) } #[tokio::test] async fn test_server_dials_configured_peer_and_establishes() -> Result<()> { let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let server = server_dialing(&listener, PEER_ASN).await?; let mut peer = accept(&listener).await?; // Both sides send OPEN on connect; the test peer's handshake order works // whichever side opened TCP. let open = peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90).await?; assert_eq!(open.asn, LOCAL_ASN as u16); let mut events = server.registry.subscribe(); // Either the Up event is still queued or the session is already listed. let listed = tokio::time::timeout(TIMEOUT, async { loop { if let Some(s) = server.registry.list().first().cloned() { return s; } let _ = events.recv().await; } }) .await?; assert_eq!(listed.name.as_deref(), Some("lab")); assert_eq!(listed.peer.asn, PEER_ASN); assert_eq!(listed.peer.addr, listener.local_addr()?); // Configured view reflects it; the dialer is not in backoff. let (cfg, state) = server.registry.configured_peers().into_iter().next().unwrap(); assert_eq!(cfg.name, "lab"); assert!(matches!(state, DialState::Connecting | DialState::Idle), "{state:?}"); // Local routes flow over the dialled session too. server .local_rib .announce(LocalRoute::new(IpPrefix::try_from("10.100.1.0/24")?, "alice", LocalRouteAttrs::default())) .await?; let update = tokio::time::timeout(TIMEOUT, peer.recv_update()).await??; assert_eq!(update.announced_routes[0].to_string(), "10.100.1.0/24"); Ok(()) } #[tokio::test] async fn test_server_reconnects_after_peer_closes() -> Result<()> { let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let server = server_dialing(&listener, PEER_ASN).await?; let mut first = accept(&listener).await?; first.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90).await?; tokio::time::timeout(TIMEOUT, async { while server.registry.list().is_empty() { tokio::time::sleep(Duration::from_millis(10)).await; } }) .await?; drop(first); // Session goes away, then the dialer comes back. tokio::time::timeout(TIMEOUT, async { while !server.registry.list().is_empty() { tokio::time::sleep(Duration::from_millis(10)).await; } }) .await?; let (_, state) = server.registry.configured_peers().into_iter().next().unwrap(); assert!(matches!(state, DialState::Backoff { .. }), "{state:?}"); let mut second = accept(&listener).await?; second.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90).await?; tokio::time::timeout(TIMEOUT, async { while server.registry.list().is_empty() { tokio::time::sleep(Duration::from_millis(10)).await; } }) .await?; Ok(()) } #[tokio::test] async fn test_wrong_asn_is_refused_with_bad_peer_as() -> Result<()> { let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let _server = server_dialing(&listener, PEER_ASN).await?; let mut peer = accept(&listener).await?; peer.send_open(65099, Ipv4Addr::new(10, 0, 0, 2), 90).await?; let _ = peer.recv_open().await?; let notif = tokio::time::timeout(TIMEOUT, async { loop { match peer.recv().await? { Message::Notification(n) => return Ok::<_, eyre::Error>(n), Message::KeepAlive => {} other => eyre::bail!("unexpected {other:?}"), } } }) .await??; assert!(matches!(notif.error, BgpError::OpenMsg(OpenMsgSubcode::BadPeerAs)), "{notif:?}"); // The dialer retries rather than giving up. let _again = accept(&listener).await?; Ok(()) } /// An inbound connection from a configured peer's address is also checked /// against the configured ASN and gets the configured name. #[tokio::test] async fn test_inbound_from_configured_address_uses_config() -> Result<()> { // Configure a peer at the loopback address on a port nobody listens on, // so the dialer never succeeds; then connect inbound from loopback. let server = TestServer::start_with(LOCAL_ASN, |c| { c.connect_retry = Duration::from_secs(60); c.peers = vec![PeerConfig { name: "lab".into(), endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1), remote_asn: PEER_ASN, hold_time_secs: 90, local_address: None, }]; }) .await?; // Wrong ASN inbound → refused. let mut bad = TestPeer::connect(server.addr).await?; bad.send_open(65099, Ipv4Addr::new(10, 0, 0, 2), 90).await?; let _ = bad.recv_open().await?; let msg = tokio::time::timeout(TIMEOUT, bad.recv()).await??; assert!(matches!(&msg, Message::Notification(n) if matches!(n.error, BgpError::OpenMsg(OpenMsgSubcode::BadPeerAs))), "{msg:?}"); // Right ASN inbound → established, named. let mut good = TestPeer::connect(server.addr).await?; good.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90).await?; let listed = tokio::time::timeout(TIMEOUT, async { loop { if let Some(s) = server.registry.list().first().cloned() { return s; } tokio::time::sleep(Duration::from_millis(10)).await; } }) .await?; assert_eq!(listed.name.as_deref(), Some("lab")); Ok(()) } /// With `local_address` set the connection must come from that address (the /// peer may identify us by it), and an address the host does not have is /// reported as the reason for backing off rather than silently ignored. #[tokio::test] async fn test_local_address_is_used_as_the_source() -> Result<()> { let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let endpoint: SocketAddr = listener.local_addr()?; let _server = TestServer::start_with(LOCAL_ASN, |c| { c.connect_retry = Duration::from_millis(200); c.peers = vec![PeerConfig { name: "lab".into(), endpoint, remote_asn: PEER_ASN, hold_time_secs: 90, local_address: Some(Ipv4Addr::LOCALHOST.into()), }]; }) .await?; let (_stream, from) = tokio::time::timeout(TIMEOUT, listener.accept()).await??; assert_eq!(from.ip(), std::net::IpAddr::from(Ipv4Addr::LOCALHOST)); // 192.0.2.1 is documentation space; no host has it. let server = TestServer::start_with(LOCAL_ASN, |c| { c.connect_retry = Duration::from_millis(200); c.peers = vec![PeerConfig { name: "lab".into(), endpoint, remote_asn: PEER_ASN, hold_time_secs: 90, local_address: Some(Ipv4Addr::new(192, 0, 2, 1).into()), }]; }) .await?; let reason = tokio::time::timeout(TIMEOUT, async { loop { if let Some((_, DialState::Backoff { last_error, .. })) = server.registry.configured_peers().into_iter().next() { return last_error; } tokio::time::sleep(Duration::from_millis(20)).await; } }) .await?; assert!(reason.contains("local_address 192.0.2.1"), "{reason}"); Ok(()) }