Integration tests: one copy of each helper

The test files had grown their own copies of the same helpers: prefix()
twelve times, established() seven, an UPDATE builder six, a seventeen
field PeerConfig literal in fifteen places, a timeout constant in twenty
one. Each is in crates/integration-tests/src once now: peer_config() and
TestServer::with_peer for a configured peer, Announcement for building
UPDATEs, established(), the waits (wait_until, wait_for, wait_for_routes,
wait_for_sessions), get_json, and TIMEOUT. A new field on PeerConfig is
one line to add, not fifteen.

TestPeer::recv has a deadline, so a test that gets nothing fails instead
of hanging; is_closed, which read one byte and took a KEEPALIVE for an
open connection, is closed_within and closed_unanswered.

Net 788 lines fewer; every test checks what it did.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-06 11:54:49 +02:00
1 parent 673920d48b
commit c218ea4d85
27 files changed
+989 -1667

No files matched your search

+5 -5
View File
@@ -18,13 +18,13 @@ bgp-server.workspace = true
bgp-web.workspace = true bgp-web.workspace = true
bytes.workspace = true bytes.workspace = true
eyre.workspace = true eyre.workspace = true
tokio.workspace = true
# Used by the examples and the tests only.
[dev-dependencies]
clap.workspace = true
reqwest.workspace = true reqwest.workspace = true
serde_json.workspace = true serde_json.workspace = true
tokio.workspace = true
# Used by the examples only.
[dev-dependencies]
clap.workspace = true
[lints] [lints]
workspace = true workspace = true
+14 -35
View File
@@ -16,11 +16,9 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::announcement::{Announcement, withdrawal};
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_packet::message::{ use bgp_packet::message::{Message, PathAttribute, UpdateMessage};
AsPathAttribute, CommunitiesPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute,
PathAttribute, UpdateMessage,
};
use bgp_primitives::ip_prefix::IpPrefix; use bgp_primitives::ip_prefix::IpPrefix;
use clap::Parser; use clap::Parser;
use eyre::Result; use eyre::Result;
@@ -88,8 +86,8 @@ async fn run(args: &Args, mut peer: TestPeer, addr: std::net::SocketAddr) -> Res
for p in &args.announce { for p in &args.announce {
let prefix = IpPrefix::try_from(p.as_str())?; let prefix = IpPrefix::try_from(p.as_str())?;
let update = announce(prefix, vec![args.asn], vec![(args.asn as u16, 1)]); peer.announce(announce(prefix, vec![args.asn], vec![(args.asn as u16, 1)]))
peer.send(&Message::Update(update)).await?; .await?;
println!("announced {prefix}"); println!("announced {prefix}");
} }
@@ -98,7 +96,8 @@ async fn run(args: &Args, mut peer: TestPeer, addr: std::net::SocketAddr) -> Res
loop { loop {
tokio::select! { tokio::select! {
_ = ticker.tick() => peer.send(&Message::KeepAlive).await?, _ = ticker.tick() => peer.send(&Message::KeepAlive).await?,
msg = peer.recv() => match msg? { // However long the server is quiet: this peer stays on the line.
msg = peer.recv_untimed() => match msg? {
Message::KeepAlive => {} Message::KeepAlive => {}
Message::Update(u) => { Message::Update(u) => {
for w in &u.withdrawn_routes { for w in &u.withdrawn_routes {
@@ -121,22 +120,10 @@ async fn run(args: &Args, mut peer: TestPeer, addr: std::net::SocketAddr) -> Res
} }
} }
fn announce(prefix: IpPrefix, as_path: Vec<u32>, communities: Vec<(u16, u16)>) -> UpdateMessage { fn announce(prefix: IpPrefix, as_path: Vec<u32>, communities: Vec<(u16, u16)>) -> Announcement {
let mut path_attributes = vec![ Announcement::of([prefix.into()], &as_path)
PathAttribute::Origin(OriginPathAttribute::IGP), .with_next_hop(Ipv4Addr::new(10, 0, 0, 2))
AsPathAttribute::from_asns(as_path), .with_communities(&communities)
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(10, 0, 0, 2))),
];
if !communities.is_empty() {
path_attributes.push(PathAttribute::Communities(CommunitiesPathAttribute(
communities,
)));
}
UpdateMessage {
withdrawn_routes: vec![],
path_attributes,
announced_routes: vec![prefix.into()],
}
} }
async fn echo_back(peer: &mut TestPeer, u: &UpdateMessage, asn: u32) -> Result<()> { async fn echo_back(peer: &mut TestPeer, u: &UpdateMessage, asn: u32) -> Result<()> {
@@ -157,21 +144,13 @@ async fn echo_back(peer: &mut TestPeer, u: &UpdateMessage, asn: u32) -> Result<(
} }
for nlri in &u.announced_routes { for nlri in &u.announced_routes {
let prefix = nlri.prefix; let prefix = nlri.prefix;
peer.send(&Message::Update(announce( peer.announce(announce(prefix, as_path.clone(), communities.clone()))
prefix, .await?;
as_path.clone(),
communities.clone(),
)))
.await?;
println!(" echoed {prefix} back with path {as_path:?}"); println!(" echoed {prefix} back with path {as_path:?}");
} }
if !u.withdrawn_routes.is_empty() { if !u.withdrawn_routes.is_empty() {
peer.send(&Message::Update(UpdateMessage { peer.announce(withdrawal(u.withdrawn_routes.iter().cloned()))
withdrawn_routes: u.withdrawn_routes.clone(), .await?;
path_attributes: vec![],
announced_routes: vec![],
}))
.await?;
println!( println!(
" echoed withdraw of {:?}", " echoed withdraw of {:?}",
u.withdrawn_routes u.withdrawn_routes
@@ -0,0 +1,110 @@
//! The UPDATEs a test peer sends, built from what a test has to say about
//! them and nothing more.
use std::net::Ipv4Addr;
use bgp_packet::message::{
AsPathAttribute, CommunitiesPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute,
PathAttribute, UpdateMessage,
};
use bgp_primitives::ip_prefix::Nlri;
use crate::prefix;
/// An UPDATE announcing IPv4 routes as the peers of these tests do: with
/// ORIGIN IGP, the AS path given, and 192.0.2.1 as the next hop unless
/// another is set. Attributes are written in order of type code.
#[derive(Debug, Clone)]
pub struct Announcement {
routes: Vec<Nlri>,
path: Vec<u32>,
next_hop: Ipv4Addr,
only_to_customer: Option<u32>,
communities: Vec<(u16, u16)>,
more: Vec<PathAttribute>,
}
/// Announces `prefixes`, written as text, with the AS path `path`.
pub fn announcement(prefixes: &[&str], path: &[u32]) -> Announcement {
Announcement::of(prefixes.iter().map(|p| prefix(p).into()), path)
}
/// Withdraws `routes`.
pub fn withdrawal(routes: impl IntoIterator<Item = Nlri>) -> UpdateMessage {
UpdateMessage {
withdrawn_routes: routes.into_iter().collect(),
path_attributes: vec![],
announced_routes: vec![],
}
}
impl Announcement {
/// Announces `routes`, prefixes with or without path identifiers, with
/// the AS path `path`.
pub fn of(routes: impl IntoIterator<Item = Nlri>, path: &[u32]) -> Self {
Self {
routes: routes.into_iter().collect(),
path: path.to_vec(),
next_hop: Ipv4Addr::new(192, 0, 2, 1),
only_to_customer: None,
communities: Vec::new(),
more: Vec::new(),
}
}
pub fn with_next_hop(mut self, next_hop: Ipv4Addr) -> Self {
self.next_hop = next_hop;
self
}
/// With a COMMUNITIES attribute carrying `communities`, if there are
/// any.
pub fn with_communities(mut self, communities: &[(u16, u16)]) -> Self {
self.communities = communities.to_vec();
self
}
/// Marked as only to go to customers, by `asn` (RFC 9234).
pub fn with_only_to_customer(mut self, asn: u32) -> Self {
self.only_to_customer = Some(asn);
self
}
/// With `attributes` after the mandatory ones, as given.
pub fn with_attributes(mut self, attributes: impl IntoIterator<Item = PathAttribute>) -> Self {
self.more.extend(attributes);
self
}
}
impl From<Announcement> for UpdateMessage {
fn from(announcement: Announcement) -> Self {
let mut path_attributes = vec![
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(announcement.path),
PathAttribute::NextHop(NextHopPathAttribute(announcement.next_hop)),
];
if !announcement.communities.is_empty() {
path_attributes.push(PathAttribute::Communities(CommunitiesPathAttribute(
announcement.communities,
)));
}
path_attributes.extend(
announcement
.only_to_customer
.map(PathAttribute::OnlyToCustomer),
);
path_attributes.extend(announcement.more);
UpdateMessage {
withdrawn_routes: vec![],
path_attributes,
announced_routes: announcement.routes,
}
}
}
impl From<Announcement> for Message {
fn from(announcement: Announcement) -> Self {
Message::Update(announcement.into())
}
}
+86
View File
@@ -1,7 +1,22 @@
pub mod announcement;
pub mod test_cache; pub mod test_cache;
pub mod test_peer; pub mod test_peer;
pub mod test_server; pub mod test_server;
use std::time::Duration;
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_server::address::Endpoint;
use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use eyre::{Result, bail};
use serde_json::Value;
/// How long a test waits for something the server does on its own: a
/// route reaching the table, a session coming up, a message arriving.
/// Everything happens on the loopback, so it is generous.
pub const TIMEOUT: Duration = Duration::from_secs(5);
/// The policy a peer has with nothing configured, but for one thing: the /// The policy a peer has with nothing configured, but for one thing: the
/// peers of these tests are all at 127.0.0.1, which no route can have as /// peers of these tests are all at 127.0.0.1, which no route can have as
/// its next hop, so their routes name another. /// its next hop, so their routes name another.
@@ -11,3 +26,74 @@ pub fn default_policy() -> bgp_server::policy::ImportPolicy {
..Default::default() ..Default::default()
} }
} }
/// A prefix written as text. The tests' prefixes are literals, so a bad
/// one is a mistake in the test.
pub fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
/// A configured peer with nothing said about it but where it is and what
/// AS it is in: the default hold time, every family, the default policy of
/// these tests, and no role, limits, groups or ADD-PATH. A test says what
/// else with struct update syntax: `PeerConfig { role: Some(..),
/// ..peer_config(..) }`.
pub fn peer_config(name: &str, endpoint: impl Into<Endpoint>, remote_asn: u32) -> PeerConfig {
PeerConfig {
name: name.into(),
endpoint: endpoint.into(),
remote_asn,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import: default_policy(),
role: None,
require_role: false,
export: Default::default(),
export_limits: None,
graceful_restart: Default::default(),
groups: Vec::new(),
reflector_client: false,
passive: false,
}
}
/// Waits until `done` holds, looking every few milliseconds, or fails
/// with `what` once [`TIMEOUT`] has passed.
pub async fn wait_until(what: &str, mut done: impl FnMut() -> bool) -> Result<()> {
let waited = tokio::time::timeout(TIMEOUT, async {
while !done() {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await;
if waited.is_err() {
bail!("timed out waiting until {what}");
}
Ok(())
}
/// Waits until `get` has something, and returns it; fails with `what`
/// once [`TIMEOUT`] has passed.
pub async fn wait_for<T>(what: &str, mut get: impl FnMut() -> Option<T>) -> Result<T> {
let mut got = None;
wait_until(what, || {
got = get();
got.is_some()
})
.await?;
Ok(got.expect("the wait ended with something"))
}
/// `GET`s `url` and reads the JSON it answers with. An error status is an
/// error.
pub async fn get_json(client: &reqwest::Client, url: &str) -> Result<Value> {
Ok(client
.get(url)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
+72 -10
View File
@@ -1,13 +1,16 @@
use std::net::{Ipv4Addr, SocketAddr}; use std::net::{Ipv4Addr, SocketAddr};
use std::time::Duration;
use bgp_packet::message::{BGP_HEADER_LEN, Message, UpdateMessage}; use bgp_packet::message::{BGP_HEADER_LEN, Message, NotificationMessage, UpdateMessage};
use bgp_packet::open::{Capability, OpenMessage, OpenOption}; use bgp_packet::open::{Capability, OpenMessage, OpenOption};
use bgp_primitives::constants::{Afi, AfiSafi, Safi}; use bgp_primitives::constants::{Afi, AfiSafi, Safi};
use bgp_primitives::parser::ParserContext; use bgp_primitives::parser::ParserContext;
use bytes::BytesMut; use bytes::BytesMut;
use eyre::{Result, bail, eyre}; use eyre::{Result, bail, eyre};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream; use tokio::net::{TcpListener, TcpStream};
use crate::TIMEOUT;
/// A minimal BGP peer for use in tests. Connects over TCP and can exchange /// A minimal BGP peer for use in tests. Connects over TCP and can exchange
/// framed BGP messages using the `bgp-packet` wire format. /// framed BGP messages using the `bgp-packet` wire format.
@@ -30,6 +33,13 @@ impl TestPeer {
Ok(Self::from_stream(stream)) Ok(Self::from_stream(stream))
} }
/// The next connection to `listener`, as the server dials it. It is
/// waited for at most [`TIMEOUT`].
pub async fn accept(listener: &TcpListener) -> Result<Self> {
let (stream, _) = tokio::time::timeout(TIMEOUT, listener.accept()).await??;
Ok(Self::from_stream(stream))
}
/// Wraps an already-connected stream, e.g. one accepted from a server /// Wraps an already-connected stream, e.g. one accepted from a server
/// that dialled us. /// that dialled us.
pub fn from_stream(stream: TcpStream) -> Self { pub fn from_stream(stream: TcpStream) -> Self {
@@ -41,6 +51,11 @@ impl TestPeer {
} }
} }
/// Our end of the connection: what the server sees the peer as.
pub fn local_addr(&self) -> Result<SocketAddr> {
Ok(self.stream.local_addr()?)
}
/// Send a BGP message. /// Send a BGP message.
pub async fn send(&mut self, msg: &Message) -> Result<()> { pub async fn send(&mut self, msg: &Message) -> Result<()> {
let send_ctx = ParserContext { let send_ctx = ParserContext {
@@ -53,8 +68,32 @@ impl TestPeer {
Ok(()) Ok(())
} }
/// Read exactly one BGP message. /// Send an UPDATE: an [`Announcement`](crate::announcement::Announcement),
/// a [`withdrawal`](crate::announcement::withdrawal), or one built by
/// hand.
pub async fn announce(&mut self, update: impl Into<UpdateMessage>) -> Result<()> {
self.send(&Message::Update(update.into())).await
}
/// Read exactly one BGP message, waiting at most [`TIMEOUT`] for it. A
/// test that expects nothing to arrive says for how long with
/// [`TestPeer::recv_within`].
pub async fn recv(&mut self) -> Result<Message> { pub async fn recv(&mut self) -> Result<Message> {
self.recv_within(TIMEOUT).await
}
/// Read exactly one BGP message, waiting at most `wait` for it.
pub async fn recv_within(&mut self, wait: Duration) -> Result<Message> {
match tokio::time::timeout(wait, self.recv_untimed()).await {
Ok(message) => message,
Err(_) => bail!("no message from the server within {wait:?}"),
}
}
/// Read exactly one BGP message, however long it takes to come. For a
/// peer that is to stay on the line, like the `fake_peer` example;
/// tests want [`TestPeer::recv`].
pub async fn recv_untimed(&mut self) -> Result<Message> {
let mut header = [0u8; BGP_HEADER_LEN as usize]; let mut header = [0u8; BGP_HEADER_LEN as usize];
self.stream.read_exact(&mut header).await?; self.stream.read_exact(&mut header).await?;
@@ -84,6 +123,18 @@ impl TestPeer {
} }
} }
/// Read messages until a NOTIFICATION arrives, skipping KEEPALIVEs. Any
/// other message type is an error.
pub async fn expect_notification(&mut self) -> Result<NotificationMessage> {
loop {
match self.recv().await? {
Message::Notification(n) => return Ok(n),
Message::KeepAlive => {}
other => bail!("expected NOTIFICATION, got {other:?}"),
}
}
}
/// Receive and unwrap as `OpenMessage`, failing if a different type arrives. /// Receive and unwrap as `OpenMessage`, failing if a different type arrives.
pub async fn recv_open(&mut self) -> Result<OpenMessage> { pub async fn recv_open(&mut self) -> Result<OpenMessage> {
match self.recv().await? { match self.recv().await? {
@@ -207,12 +258,23 @@ impl TestPeer {
Ok(()) Ok(())
} }
/// Returns true if the remote end has closed the connection. /// Whether the server closes the connection within `wait`. Whatever it
pub async fn is_closed(&mut self) -> bool { /// still sends first, KEEPALIVEs as a rule, is read past: the end of
let mut buf = [0u8; 1]; /// the stream, or anything unreadable, is what counts.
match self.stream.read(&mut buf).await { pub async fn closed_within(&mut self, wait: Duration) -> bool {
Ok(0) | Err(_) => true, tokio::time::timeout(wait, async { while self.recv_untimed().await.is_ok() {} })
Ok(_) => false, .await
} .is_ok()
}
/// Whether the server closes the connection within [`TIMEOUT`] without
/// having sent anything: what a peer that is not let in sees. A server
/// that sends its OPEN first, or that leaves the connection open, is
/// answering.
pub async fn closed_unanswered(&mut self) -> bool {
matches!(
tokio::time::timeout(TIMEOUT, self.recv_untimed()).await,
Ok(Err(_))
)
} }
} }
+131 -3
View File
@@ -1,23 +1,28 @@
use std::collections::BTreeSet;
use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc; use std::sync::Arc;
use bgp_fib::Fib; use bgp_fib::Fib;
use bgp_packet::message::{NlriNextHop, PathAttribute}; use bgp_packet::message::{NlriNextHop, PathAttribute};
use bgp_packet::open::Capability;
use bgp_primitives::ip_prefix::IpPrefix; use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::LocalRib; use bgp_rib::LocalRib;
use bgp_rib::table::{Event, PeerIndex, Reader, RibTable}; use bgp_rib::table::{Event, PeerIndex, Reader, RibTable};
use bgp_rpki::Rpki; use bgp_rpki::Rpki;
use bgp_server::config::Config; use bgp_server::config::{Config, PeerConfig};
use bgp_server::error::ServerError; use bgp_server::error::ServerError;
use bgp_server::refusals::RefusalLog; use bgp_server::refusals::RefusalLog;
use bgp_server::restart::Restarts; use bgp_server::restart::Restarts;
use bgp_server::server::BgpServer; use bgp_server::server::BgpServer;
use bgp_server::session::SessionRegistry; use bgp_server::session::{SessionHandle, SessionRegistry};
use bgp_web::{AppState, RouterInfo}; use bgp_web::{AppState, RouterInfo};
use eyre::Result; use eyre::{Result, eyre};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use crate::test_peer::TestPeer;
use crate::{peer_config, wait_for, wait_until};
/// A BGP server started on an ephemeral port for testing. /// A BGP server started on an ephemeral port for testing.
/// Aborts the server task when dropped. /// Aborts the server task when dropped.
pub struct TestServer { pub struct TestServer {
@@ -106,6 +111,129 @@ impl TestServer {
local_asn, local_asn,
}) })
} }
/// A server in `local_asn` that takes connections from one configured
/// peer only: "r1", in `remote_asn`, at the loopback address the test
/// connects from. `customize` edits the configuration and the peer's
/// before the server starts. The server dials the peer too; where that
/// goes is returned with it, for the test to answer or to leave alone.
pub async fn with_peer(
local_asn: u32,
remote_asn: u32,
customize: impl FnOnce(&mut Config, &mut PeerConfig),
) -> Result<ServerWithPeer> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?;
let endpoint = listener.local_addr()?;
let server = Self::start_with(local_asn, |config| {
config.accept_unconfigured_peers = false;
let mut peer = peer_config("r1", endpoint, remote_asn);
customize(config, &mut peer);
config.peers = vec![peer];
})
.await?;
Ok(ServerWithPeer { server, listener })
}
}
/// A server with one configured peer, and the listener at the peer's
/// address, which the server's dialling goes to.
pub struct ServerWithPeer {
pub server: TestServer,
pub listener: TcpListener,
}
impl TestServer {
/// A session with the server from a peer in `asn` with `router_id`,
/// offering the capabilities of a typical dual-stack speaker. Returns
/// once the server lists the session as established. Distinct peers
/// need distinct identifiers, or the second connection is (correctly)
/// treated as a collision with the first.
pub async fn established(&self, asn: u32, router_id: Ipv4Addr) -> Result<TestPeer> {
let mut peer = TestPeer::connect(self.addr).await?;
peer.handshake(asn, router_id, 90).await?;
self.listed(&peer).await?;
Ok(peer)
}
/// As [`TestServer::established`], with an OPEN carrying exactly
/// `capabilities`.
pub async fn established_with(
&self,
asn: u32,
router_id: Ipv4Addr,
capabilities: Vec<Capability>,
) -> Result<TestPeer> {
let mut peer = TestPeer::connect(self.addr).await?;
peer.handshake_with(asn, router_id, 90, capabilities)
.await?;
self.listed(&peer).await?;
Ok(peer)
}
/// Waits until the registry lists `peer`'s session.
async fn listed(&self, peer: &TestPeer) -> Result<()> {
let from = peer.local_addr()?;
wait_until("the session is established", || {
self.registry
.list()
.iter()
.any(|session| session.peer.addr == from)
})
.await
}
/// Waits until the registry lists `n` established sessions.
pub async fn wait_for_sessions(&self, n: usize) -> Result<()> {
wait_until(&format!("{n} sessions are established"), || {
self.registry.list().len() == n
})
.await
}
/// Waits until a session is established, and returns it.
pub async fn wait_for_session(&self) -> Result<SessionHandle> {
wait_for("a session is established", || {
self.registry.list().into_iter().next()
})
.await
}
/// Waits until the table holds `routes` routes.
pub async fn wait_for_routes(&self, routes: usize) -> Result<()> {
wait_until(&format!("the table holds {routes} routes"), || {
self.rib.len() == routes
})
.await
}
/// Waits until the table holds exactly the prefixes `expected` and the
/// peer has `held` routes held aside.
pub async fn table_becomes(&self, expected: &[&str], held: u64) -> Result<()> {
let expected: BTreeSet<String> = expected.iter().map(|p| p.to_string()).collect();
let table = || -> BTreeSet<String> {
self.received_routes()
.iter()
.map(|route| route.prefix.to_string())
.collect()
};
let held_now = || {
self.registry
.list()
.first()
.map_or(0, |session| session.counters.held())
};
wait_until("the table is as expected", || {
table() == expected && held_now() == held
})
.await
.map_err(|_| {
eyre!(
"the table has {:?} with {} held; expected {expected:?} with {held} held",
table(),
held_now()
)
})
}
} }
/// A route in the server's table, as a test wants to look at it. /// A route in the server's table, as a test wants to look at it.
+27 -78
View File
@@ -4,29 +4,23 @@
use std::net::{Ipv4Addr, SocketAddr}; use std::net::{Ipv4Addr, SocketAddr};
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::announcement::{Announcement, withdrawal};
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::TestServer;
use bgp_packet::message::{ use bgp_integration_tests::{peer_config, prefix, wait_until};
AsPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute, PathAttribute, use bgp_packet::message::PathAttribute;
UpdateMessage,
};
use bgp_packet::open::{AddPathEntry, AddPathSendRecv, Capability, OpenMessage}; use bgp_packet::open::{AddPathEntry, AddPathSendRecv, Capability, OpenMessage};
use bgp_primitives::constants::AfiSafi; use bgp_primitives::constants::AfiSafi;
use bgp_primitives::ip_prefix::{IpPrefix, Nlri}; use bgp_primitives::ip_prefix::Nlri;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
use bgp_server::config::PeerConfig; use bgp_server::config::PeerConfig;
use bgp_server::family::{FamilySet, SUPPORTED}; use bgp_server::family::SUPPORTED;
use bgp_server::policy::ImportPolicy; use bgp_server::policy::ImportPolicy;
use eyre::Result; use eyre::Result;
use serde_json::Value; use serde_json::Value;
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const TIMEOUT: Duration = Duration::from_secs(5);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
/// A server on which connections from this machine are those of a /// A server on which connections from this machine are those of a
/// configured peer, with ADD-PATH offered to it as `add_path` says. /// configured peer, with ADD-PATH offered to it as `add_path` says.
@@ -39,23 +33,11 @@ async fn server_offering(
.map(|p| LocalRoute::new(prefix(p), LocalRouteAttrs::default())) .map(|p| LocalRoute::new(prefix(p), LocalRouteAttrs::default()))
.collect(); .collect();
TestServer::start_with(LOCAL_ASN, |c| { TestServer::start_with(LOCAL_ASN, |c| {
let at = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1);
c.peers = vec![PeerConfig { c.peers = vec![PeerConfig {
graceful_restart: Default::default(),
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "route-server-client".into(),
endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1).into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path, add_path,
import: ImportPolicy::permissive(), import: ImportPolicy::permissive(),
role: None, ..peer_config("route-server-client", at, PEER_ASN)
require_role: false,
export: Default::default(),
}]; }];
c.originate = originate; c.originate = originate;
// Keep the dialer out of the way. // Keep the dialer out of the way.
@@ -85,12 +67,7 @@ async fn peer_offering(
let open = peer let open = peer
.handshake_with(PEER_ASN, Ipv4Addr::new(10, 0, 0, 1), 90, capabilities) .handshake_with(PEER_ASN, Ipv4Addr::new(10, 0, 0, 1), 90, capabilities)
.await?; .await?;
tokio::time::timeout(TIMEOUT, async { server.wait_for_session().await?;
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
Ok((peer, open)) Ok((peer, open))
} }
@@ -105,26 +82,8 @@ fn add_path_offered(open: &OpenMessage) -> Vec<(AfiSafi, AddPathSendRecv)> {
.collect() .collect()
} }
fn announcement(nlri: Nlri, as_path: &[u32]) -> Message { fn announcement(nlri: Nlri, as_path: &[u32]) -> Announcement {
Message::Update(UpdateMessage { Announcement::of([nlri], as_path)
withdrawn_routes: vec![],
path_attributes: vec![
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(as_path.to_vec()),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
],
announced_routes: vec![nlri],
})
}
async fn wait_for_routes(server: &TestServer, routes: usize) -> Result<()> {
tokio::time::timeout(TIMEOUT, async {
while server.rib.len() != routes {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
Ok(())
} }
/// The routes the server holds, as (path identifier, AS path, best). /// The routes the server holds, as (path identifier, AS path, best).
@@ -172,11 +131,11 @@ async fn test_several_routes_for_a_prefix_from_one_peer() -> Result<()> {
let p = prefix("203.0.113.0/24"); let p = prefix("203.0.113.0/24");
let path = |path_id| Nlri { prefix: p, path_id }; let path = |path_id| Nlri { prefix: p, path_id };
peer.send(&announcement(path(1), &[PEER_ASN, 64999, 64998])) peer.announce(announcement(path(1), &[PEER_ASN, 64999, 64998]))
.await?; .await?;
peer.send(&announcement(path(2), &[PEER_ASN, 64998])) peer.announce(announcement(path(2), &[PEER_ASN, 64998]))
.await?; .await?;
wait_for_routes(&server, 2).await?; server.wait_for_routes(2).await?;
assert_eq!( assert_eq!(
held(&server), held(&server),
[ [
@@ -209,34 +168,24 @@ async fn test_several_routes_for_a_prefix_from_one_peer() -> Result<()> {
); );
// Announcing a path again replaces that path only. // Announcing a path again replaces that path only.
peer.send(&announcement(path(1), &[PEER_ASN])).await?; peer.announce(announcement(path(1), &[PEER_ASN])).await?;
tokio::time::timeout(TIMEOUT, async { wait_until("the path is replaced", || held(&server)[0].1.len() == 1).await?;
while held(&server)[0].1.len() != 1 {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
assert_eq!( assert_eq!(
held(&server), held(&server),
[(1, vec![PEER_ASN], true), (2, vec![PEER_ASN, 64998], false),] [(1, vec![PEER_ASN], true), (2, vec![PEER_ASN, 64998], false),]
); );
// And withdrawing one leaves the other. // And withdrawing one leaves the other.
peer.send(&Message::Update(UpdateMessage { peer.announce(withdrawal([path(1)])).await?;
withdrawn_routes: vec![path(1)], server.wait_for_routes(1).await?;
path_attributes: vec![],
announced_routes: vec![],
}))
.await?;
wait_for_routes(&server, 1).await?;
assert_eq!(held(&server), [(2, vec![PEER_ASN, 64998], true)]); assert_eq!(held(&server), [(2, vec![PEER_ASN, 64998], true)]);
// When the session ends, all of its routes go. // When the session ends, all of its routes go.
peer.send(&announcement(path(3), &[PEER_ASN, 1, 2, 3])) peer.announce(announcement(path(3), &[PEER_ASN, 1, 2, 3]))
.await?; .await?;
wait_for_routes(&server, 2).await?; server.wait_for_routes(2).await?;
drop(peer); drop(peer);
wait_for_routes(&server, 0).await?; server.wait_for_routes(0).await?;
Ok(()) Ok(())
} }
@@ -252,18 +201,18 @@ async fn test_add_path_is_not_used_unless_both_sides_offer_it() -> Result<()> {
assert!(session.add_path_receive.is_empty() && session.add_path_send.is_empty()); assert!(session.add_path_receive.is_empty() && session.add_path_send.is_empty());
// Plain prefixes both ways. // Plain prefixes both ways.
let update = tokio::time::timeout(TIMEOUT, peer.recv_update()).await??; let update = peer.recv_update().await?;
assert_eq!(update.announced_routes, vec![prefix("10.100.0.0/16")]); assert_eq!(update.announced_routes, vec![prefix("10.100.0.0/16")]);
peer.send(&announcement(prefix("203.0.113.0/24").into(), &[PEER_ASN])) peer.announce(announcement(prefix("203.0.113.0/24").into(), &[PEER_ASN]))
.await?; .await?;
wait_for_routes(&server, 1).await?; server.wait_for_routes(1).await?;
assert_eq!(held(&server), [(0, vec![PEER_ASN], true)]); assert_eq!(held(&server), [(0, vec![PEER_ASN], true)]);
// And the other way round: offered by the server, not by the peer. // And the other way round: offered by the server, not by the peer.
let server = server_offering(Some(AddPathSendRecv::Both), &["10.100.0.0/16"]).await?; let server = server_offering(Some(AddPathSendRecv::Both), &["10.100.0.0/16"]).await?;
let (mut peer, open) = peer_offering(&server, None).await?; let (mut peer, open) = peer_offering(&server, None).await?;
assert_eq!(add_path_offered(&open).len(), SUPPORTED.len()); assert_eq!(add_path_offered(&open).len(), SUPPORTED.len());
let update = tokio::time::timeout(TIMEOUT, peer.recv_update()).await??; let update = peer.recv_update().await?;
assert_eq!(update.announced_routes, vec![prefix("10.100.0.0/16")]); assert_eq!(update.announced_routes, vec![prefix("10.100.0.0/16")]);
Ok(()) Ok(())
} }
@@ -284,15 +233,15 @@ async fn test_prefixes_are_sent_with_path_identifiers_when_agreed() -> Result<()
// Read as agreed: with identifiers. // Read as agreed: with identifiers.
peer.ctx.add_path = vec![AfiSafi::IPV4_UNICAST]; peer.ctx.add_path = vec![AfiSafi::IPV4_UNICAST];
let update = tokio::time::timeout(TIMEOUT, peer.recv_update()).await??; let update = peer.recv_update().await?;
assert_eq!(update.announced_routes.len(), 1); assert_eq!(update.announced_routes.len(), 1);
assert_eq!(update.announced_routes[0].prefix, prefix("10.100.0.0/16")); assert_eq!(update.announced_routes[0].prefix, prefix("10.100.0.0/16"));
// The peer did not offer to send, so the server reads its prefixes // The peer did not offer to send, so the server reads its prefixes
// plain. // plain.
peer.send(&announcement(prefix("203.0.113.0/24").into(), &[PEER_ASN])) peer.announce(announcement(prefix("203.0.113.0/24").into(), &[PEER_ASN]))
.await?; .await?;
wait_for_routes(&server, 1).await?; server.wait_for_routes(1).await?;
assert_eq!(held(&server), [(0, vec![PEER_ASN], true)]); assert_eq!(held(&server), [(0, vec![PEER_ASN], true)]);
Ok(()) Ok(())
} }
+18 -48
View File
@@ -5,42 +5,14 @@ use std::net::{Ipv4Addr, SocketAddr};
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::{ServerWithPeer, TestServer};
use bgp_integration_tests::{TIMEOUT, peer_config};
use bgp_server::config::PeerConfig; use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use bgp_server::policy::ImportPolicy; use bgp_server::policy::ImportPolicy;
use eyre::Result; use eyre::Result;
use tokio::net::TcpListener;
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const TIMEOUT: Duration = Duration::from_secs(5);
fn peer_at(endpoint: SocketAddr) -> PeerConfig {
PeerConfig {
graceful_restart: Default::default(),
groups: Vec::new(),
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import: ImportPolicy::permissive(),
role: None,
require_role: false,
export: Default::default(),
export_limits: None,
passive: false,
}
}
/// Whether the server closed the connection without sending anything.
async fn closed_unanswered(peer: &mut TestPeer) -> bool {
matches!(tokio::time::timeout(TIMEOUT, peer.recv()).await, Ok(Err(_)))
}
/// A host that is not a configured peer gets nothing: no OPEN to answer, /// A host that is not a configured peer gets nothing: no OPEN to answer,
/// no session, no routes taken from it. Any host used to be able to open a /// no session, no routes taken from it. Any host used to be able to open a
@@ -50,12 +22,16 @@ async fn test_a_host_that_is_not_a_configured_peer_is_refused() -> Result<()> {
// The only peer configured is somewhere else (documentation space). // The only peer configured is somewhere else (documentation space).
let server = TestServer::start_with(LOCAL_ASN, |c| { let server = TestServer::start_with(LOCAL_ASN, |c| {
c.accept_unconfigured_peers = false; c.accept_unconfigured_peers = false;
c.peers = vec![peer_at("192.0.2.9:179".parse().unwrap())]; let elsewhere: SocketAddr = "192.0.2.9:179".parse().unwrap();
c.peers = vec![PeerConfig {
import: ImportPolicy::permissive(),
..peer_config("r1", elsewhere, PEER_ASN)
}];
}) })
.await?; .await?;
let mut stranger = TestPeer::connect(server.addr).await?; let mut stranger = TestPeer::connect(server.addr).await?;
assert!(closed_unanswered(&mut stranger).await); assert!(stranger.closed_unanswered().await);
assert!(server.registry.list().is_empty()); assert!(server.registry.list().is_empty());
Ok(()) Ok(())
} }
@@ -64,24 +40,18 @@ async fn test_a_host_that_is_not_a_configured_peer_is_refused() -> Result<()> {
/// configured for that peer. /// configured for that peer.
#[tokio::test] #[tokio::test]
async fn test_a_configured_peer_is_let_in() -> Result<()> { async fn test_a_configured_peer_is_let_in() -> Result<()> {
// Somewhere for the server's own dialling to go; never answered. // The server's own dialling goes to the listener; it is never answered.
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer {
let endpoint = listener.local_addr()?; server,
let server = TestServer::start_with(LOCAL_ASN, |c| { listener: _listener,
c.accept_unconfigured_peers = false; } = TestServer::with_peer(LOCAL_ASN, PEER_ASN, |_, peer| {
c.peers = vec![peer_at(endpoint)]; peer.import = ImportPolicy::permissive();
}) })
.await?; .await?;
let mut peer = TestPeer::connect(server.addr).await?; let _peer = server
peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90) .established(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2))
.await?; .await?;
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
assert_eq!(server.registry.list()[0].name.as_deref(), Some("r1")); assert_eq!(server.registry.list()[0].name.as_deref(), Some("r1"));
Ok(()) Ok(())
} }
@@ -97,11 +67,11 @@ async fn test_one_address_may_open_only_a_few_connections() -> Result<()> {
let mut held = Vec::new(); let mut held = Vec::new();
for _ in 0..4 { for _ in 0..4 {
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
tokio::time::timeout(TIMEOUT, peer.recv_open()).await??; peer.recv_open().await?;
held.push(peer); held.push(peer);
} }
let mut one_too_many = TestPeer::connect(server.addr).await?; let mut one_too_many = TestPeer::connect(server.addr).await?;
assert!(closed_unanswered(&mut one_too_many).await); assert!(one_too_many.closed_unanswered().await);
// When one of them goes, there is room again. // When one of them goes, there is room again.
held.pop(); held.pop();
+19 -58
View File
@@ -4,25 +4,21 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::announcement::announcement;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::{RouteChange, TestServer}; use bgp_integration_tests::test_server::{RouteChange, TestServer};
use bgp_integration_tests::{TIMEOUT, prefix};
use bgp_packet::message::{ use bgp_packet::message::{
AsPathAttribute, CommunitiesPathAttribute, LargeCommunitiesPathAttribute, LargeCommunity, AsPathAttribute, CommunitiesPathAttribute, LargeCommunitiesPathAttribute, LargeCommunity,
LocalPrefPathAttribute, NextHopPathAttribute, OriginPathAttribute, PathAttribute, LocalPrefPathAttribute, NextHopPathAttribute, OriginPathAttribute, PathAttribute,
UpdateMessage, UpdateMessage,
}; };
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
use bgp_server::session::SessionEvent; use bgp_server::session::SessionEvent;
use eyre::Result; use eyre::Result;
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const TIMEOUT: Duration = Duration::from_secs(5);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
fn route(p: &str, attrs: LocalRouteAttrs) -> LocalRoute { fn route(p: &str, attrs: LocalRouteAttrs) -> LocalRoute {
LocalRoute::new(prefix(p), attrs) LocalRoute::new(prefix(p), attrs)
@@ -32,27 +28,12 @@ async fn established_peer(server: &TestServer) -> Result<TestPeer> {
established_peer_with_id(server, Ipv4Addr::new(10, 0, 0, 1)).await established_peer_with_id(server, Ipv4Addr::new(10, 0, 0, 1)).await
} }
/// Distinct routers need distinct BGP Identifiers, or the second connection
/// is (correctly) treated as a collision with the first.
async fn established_peer_with_id(server: &TestServer, router_id: Ipv4Addr) -> Result<TestPeer> { async fn established_peer_with_id(server: &TestServer, router_id: Ipv4Addr) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?; server.established(PEER_ASN, router_id).await
peer.handshake(PEER_ASN, router_id, 90).await?;
Ok(peer)
} }
async fn next_update(peer: &mut TestPeer) -> Result<UpdateMessage> { async fn next_update(peer: &mut TestPeer) -> Result<UpdateMessage> {
tokio::time::timeout(TIMEOUT, peer.recv_update()).await? peer.recv_update().await
}
/// Waits until the registry reports `n` established sessions.
async fn wait_for_sessions(server: &TestServer, n: usize) -> Result<()> {
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().len() != n {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
Ok(())
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -97,7 +78,7 @@ async fn test_registry_tracks_session_lifecycle() -> Result<()> {
async fn test_announce_after_session_up() -> Result<()> { async fn test_announce_after_session_up() -> Result<()> {
let server = TestServer::start(LOCAL_ASN).await?; let server = TestServer::start(LOCAL_ASN).await?;
let mut peer = established_peer(&server).await?; let mut peer = established_peer(&server).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
server server
.local_rib .local_rib
@@ -174,7 +155,7 @@ async fn test_announce_reaches_all_sessions() -> Result<()> {
let server = TestServer::start(LOCAL_ASN).await?; let server = TestServer::start(LOCAL_ASN).await?;
let mut a = established_peer_with_id(&server, Ipv4Addr::new(10, 0, 0, 1)).await?; let mut a = established_peer_with_id(&server, Ipv4Addr::new(10, 0, 0, 1)).await?;
let mut b = established_peer_with_id(&server, Ipv4Addr::new(10, 0, 0, 2)).await?; let mut b = established_peer_with_id(&server, Ipv4Addr::new(10, 0, 0, 2)).await?;
wait_for_sessions(&server, 2).await?; server.wait_for_sessions(2).await?;
server server
.local_rib .local_rib
@@ -192,7 +173,7 @@ async fn test_announce_reaches_all_sessions() -> Result<()> {
async fn test_withdraw_sends_withdrawn_route() -> Result<()> { async fn test_withdraw_sends_withdrawn_route() -> Result<()> {
let server = TestServer::start(LOCAL_ASN).await?; let server = TestServer::start(LOCAL_ASN).await?;
let mut peer = established_peer(&server).await?; let mut peer = established_peer(&server).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
server server
.local_rib .local_rib
@@ -213,7 +194,7 @@ async fn test_withdraw_sends_withdrawn_route() -> Result<()> {
async fn test_reannounce_with_prepend_and_med() -> Result<()> { async fn test_reannounce_with_prepend_and_med() -> Result<()> {
let server = TestServer::start(LOCAL_ASN).await?; let server = TestServer::start(LOCAL_ASN).await?;
let mut peer = established_peer(&server).await?; let mut peer = established_peer(&server).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
server server
.local_rib .local_rib
@@ -258,7 +239,7 @@ async fn test_keepalives_continue_alongside_updates() -> Result<()> {
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 1), 3) peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 1), 3)
.await?; .await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
server server
.local_rib .local_rib
@@ -289,22 +270,12 @@ async fn test_received_routes_removed_when_session_closes() -> Result<()> {
let mut rib_events = server.route_changes(); let mut rib_events = server.route_changes();
let mut session_events = server.registry.subscribe(); let mut session_events = server.registry.subscribe();
let mut peer = established_peer(&server).await?; let mut peer = established_peer(&server).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
peer.send(&bgp_packet::message::Message::Update( peer.announce(
bgp_packet::message::UpdateMessage { announcement(&["203.0.113.0/24", "198.51.100.0/24"], &[PEER_ASN])
withdrawn_routes: vec![], .with_next_hop(Ipv4Addr::new(10, 0, 0, 1)),
path_attributes: vec![ )
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(vec![PEER_ASN]),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(10, 0, 0, 1))),
],
announced_routes: vec![
prefix("203.0.113.0/24").into(),
prefix("198.51.100.0/24").into(),
],
},
))
.await?; .await?;
for _ in 0..2 { for _ in 0..2 {
let ev = tokio::time::timeout(TIMEOUT, rib_events.recv()).await??; let ev = tokio::time::timeout(TIMEOUT, rib_events.recv()).await??;
@@ -351,7 +322,7 @@ const ROUTE_REFRESH_IPV4: &[u8] = &[
async fn test_route_refresh_resends_local_routes() -> Result<()> { async fn test_route_refresh_resends_local_routes() -> Result<()> {
let server = TestServer::start(LOCAL_ASN).await?; let server = TestServer::start(LOCAL_ASN).await?;
let mut peer = established_peer(&server).await?; let mut peer = established_peer(&server).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
server server
.local_rib .local_rib
@@ -386,12 +357,11 @@ async fn test_route_refresh_resends_local_routes() -> Result<()> {
#[tokio::test] #[tokio::test]
async fn test_shutdown_sends_cease_to_every_peer() -> Result<()> { async fn test_shutdown_sends_cease_to_every_peer() -> Result<()> {
use bgp_packet::errors::{BgpError, CeaseSubcode}; use bgp_packet::errors::{BgpError, CeaseSubcode};
use bgp_packet::message::Message;
let server = TestServer::start(LOCAL_ASN).await?; let server = TestServer::start(LOCAL_ASN).await?;
let mut a = established_peer_with_id(&server, Ipv4Addr::new(10, 0, 0, 1)).await?; let mut a = established_peer_with_id(&server, Ipv4Addr::new(10, 0, 0, 1)).await?;
let mut b = established_peer_with_id(&server, Ipv4Addr::new(10, 0, 0, 2)).await?; let mut b = established_peer_with_id(&server, Ipv4Addr::new(10, 0, 0, 2)).await?;
wait_for_sessions(&server, 2).await?; server.wait_for_sessions(2).await?;
let left = server let left = server
.registry .registry
@@ -401,21 +371,12 @@ async fn test_shutdown_sends_cease_to_every_peer() -> Result<()> {
assert!(server.registry.is_shutting_down()); assert!(server.registry.is_shutting_down());
for peer in [&mut a, &mut b] { for peer in [&mut a, &mut b] {
let notif = tokio::time::timeout(TIMEOUT, async { let notif = peer.expect_notification().await?;
loop {
match peer.recv().await? {
Message::Notification(n) => return Ok::<_, eyre::Error>(n),
Message::KeepAlive => {}
other => eyre::bail!("unexpected {other:?}"),
}
}
})
.await??;
assert!( assert!(
matches!(notif.error, BgpError::Cease(CeaseSubcode::AdminShutdown)), matches!(notif.error, BgpError::Cease(CeaseSubcode::AdminShutdown)),
"{notif:?}" "{notif:?}"
); );
assert!(peer.is_closed().await); assert!(peer.closed_within(TIMEOUT).await);
} }
Ok(()) Ok(())
} }
@@ -470,7 +431,7 @@ async fn test_a_peer_in_our_own_as_gets_an_empty_path_and_a_local_pref() -> Resu
// The peer reads as a router in the server's AS does: one outside it // The peer reads as a router in the server's AS does: one outside it
// would drop the LOCAL_PREF. // would drop the LOCAL_PREF.
peer.ctx.ibgp = true; peer.ctx.ibgp = true;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
assert!(server.registry.list()[0].ibgp); assert!(server.registry.list()[0].ibgp);
server server
+44 -126
View File
@@ -6,37 +6,20 @@ use std::collections::BTreeSet;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::default_policy; use bgp_integration_tests::announcement::announcement;
use bgp_integration_tests::test_cache::TestCache; use bgp_integration_tests::test_cache::TestCache;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::{ServerWithPeer, TestServer};
use bgp_packet::message::{ use bgp_integration_tests::{default_policy, prefix, wait_until};
AsPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute, PathAttribute, use bgp_packet::message::PathAttribute;
UpdateMessage,
};
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rpki::{CacheState, RpkiConfig}; use bgp_rpki::{CacheState, RpkiConfig};
use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use bgp_server::policy::{ImportPolicy, PeerRole}; use bgp_server::policy::{ImportPolicy, PeerRole};
use eyre::Result; use eyre::Result;
use serde_json::Value; use serde_json::Value;
use tokio::net::TcpListener;
const LOCAL_ASN: u32 = 1299; const LOCAL_ASN: u32 = 1299;
const PEER_ASN: u32 = 3333; const PEER_ASN: u32 = 3333;
const TIMEOUT: Duration = Duration::from_secs(5); const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
/// What a test holds on to: the server, and the listener its dialling
/// goes to.
struct Setup {
server: TestServer,
_listener: TcpListener,
}
/// A server that validates against `cache`, with one configured peer at /// A server that validates against `cache`, with one configured peer at
/// the address the test connects from, which is `role` to it. Returns once /// the address the test connects from, which is `role` to it. Returns once
@@ -45,108 +28,33 @@ async fn server_with(
cache: &TestCache, cache: &TestCache,
role: Option<PeerRole>, role: Option<PeerRole>,
import: ImportPolicy, import: ImportPolicy,
) -> Result<Setup> { ) -> Result<ServerWithPeer> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?;
let endpoint = listener.local_addr()?;
let cache = cache.address(); let cache = cache.address();
let server = TestServer::start_with(LOCAL_ASN, |c| { let setup = TestServer::with_peer(LOCAL_ASN, PEER_ASN, |c, peer| {
c.accept_unconfigured_peers = false;
c.rpki = Some(RpkiConfig { c.rpki = Some(RpkiConfig {
caches: vec![cache], caches: vec![cache],
retry: Some(Duration::from_millis(100)), retry: Some(Duration::from_millis(100)),
..RpkiConfig::default() ..RpkiConfig::default()
}); });
c.peers = vec![PeerConfig { peer.import = import;
graceful_restart: Default::default(), peer.role = role;
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import,
role,
require_role: false,
export: Default::default(),
}];
}) })
.await?; .await?;
let rpki = server.rpki.clone().expect("validation is configured"); let rpki = setup.server.rpki.clone().expect("validation is configured");
tokio::time::timeout(TIMEOUT, async { wait_until("the server has the cache's data", || {
while rpki.status().state != CacheState::Synchronised { rpki.status().state == CacheState::Synchronised
tokio::time::sleep(Duration::from_millis(5)).await;
}
}) })
.await?; .await?;
Ok(Setup { Ok(setup)
server,
_listener: listener,
})
} }
async fn established(server: &TestServer) -> Result<TestPeer> { async fn established(server: &TestServer) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?; server.established(PEER_ASN, PEER_ID).await
peer.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(5)).await;
}
})
.await?;
Ok(peer)
} }
/// Announces `prefixes` with the AS path `path`. /// Announces `prefixes` with the AS path `path`.
async fn announce(peer: &mut TestPeer, prefixes: &[&str], path: &[u32]) -> Result<()> { async fn announce(peer: &mut TestPeer, prefixes: &[&str], path: &[u32]) -> Result<()> {
peer.send(&Message::Update(UpdateMessage { peer.announce(announcement(prefixes, path)).await
withdrawn_routes: vec![],
path_attributes: vec![
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(path.to_vec()),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
],
announced_routes: prefixes.iter().map(|p| prefix(p).into()).collect(),
}))
.await
}
/// Waits until the table holds exactly `expected` and the peer has `held`
/// routes held aside.
async fn table_becomes(server: &TestServer, expected: &[&str], held: u64) -> Result<()> {
let expected: BTreeSet<String> = expected.iter().map(|p| p.to_string()).collect();
let table = || -> BTreeSet<String> {
server
.received_routes()
.iter()
.map(|route| route.prefix.to_string())
.collect()
};
let held_now = || {
server
.registry
.list()
.first()
.map_or(0, |session| session.counters.held())
};
tokio::time::timeout(TIMEOUT, async {
while table() != expected || held_now() != held {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await
.map_err(|_| {
eyre::eyre!(
"the table has {:?} with {} held; expected {expected:?} with {held} held",
table(),
held_now()
)
})
} }
/// AS 15169 buys from 3356 and says so; 3356 and the peer, 3333, have no /// AS 15169 buys from 3356 and says so; 3356 and the peer, 3333, have no
@@ -161,7 +69,8 @@ fn aspas() -> Vec<(u32, Vec<u32>)> {
#[tokio::test] #[tokio::test]
async fn test_a_leaked_route_is_refused_and_the_rest_are_taken() -> Result<()> { async fn test_a_leaked_route_is_refused_and_the_rest_are_taken() -> Result<()> {
let cache = TestCache::start_with(vec![], aspas()).await; let cache = TestCache::start_with(vec![], aspas()).await;
let Setup { server, .. } = server_with(&cache, Some(PeerRole::Peer), default_policy()).await?; let ServerWithPeer { server, .. } =
server_with(&cache, Some(PeerRole::Peer), default_policy()).await?;
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
@@ -171,7 +80,9 @@ async fn test_a_leaked_route_is_refused_and_the_rest_are_taken() -> Result<()> {
announce(&mut peer, &["193.0.0.0/21"], &[PEER_ASN]).await?; announce(&mut peer, &["193.0.0.0/21"], &[PEER_ASN]).await?;
announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN, 2121]).await?; announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN, 2121]).await?;
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?; announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
table_becomes(&server, &["193.0.0.0/21", "194.0.0.0/24"], 1).await?; server
.table_becomes(&["193.0.0.0/21", "194.0.0.0/24"], 1)
.await?;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let get = |path: &str| { let get = |path: &str| {
@@ -217,13 +128,13 @@ async fn test_a_route_that_has_crossed_a_peering_is_taken_from_a_provider_only()
(None, true), (None, true),
] { ] {
let cache = TestCache::start_with(vec![], aspas()).await; let cache = TestCache::start_with(vec![], aspas()).await;
let Setup { server, .. } = server_with(&cache, role, default_policy()).await?; let ServerWithPeer { server, .. } = server_with(&cache, role, default_policy()).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?; announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
let expected = if taken { let expected = if taken {
table_becomes(&server, &["8.8.8.0/24"], 0).await server.table_becomes(&["8.8.8.0/24"], 0).await
} else { } else {
table_becomes(&server, &[], 1).await server.table_becomes(&[], 1).await
}; };
expected.map_err(|e| eyre::eyre!("from a peer that is {role:?}: {e}"))?; expected.map_err(|e| eyre::eyre!("from a peer that is {role:?}: {e}"))?;
} }
@@ -240,14 +151,15 @@ async fn test_paths_from_a_route_server_do_not_begin_with_its_as() -> Result<()>
accept_any_first_as: true, accept_any_first_as: true,
..default_policy() ..default_policy()
}; };
let Setup { server, .. } = server_with(&cache, Some(PeerRole::RouteServer), import).await?; let ServerWithPeer { server, .. } =
server_with(&cache, Some(PeerRole::RouteServer), import).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
// 3356's customer route, as 3356 gave it to the route server. // 3356's customer route, as 3356 gave it to the route server.
announce(&mut peer, &["8.8.8.0/24"], &[3356, 15169]).await?; announce(&mut peer, &["8.8.8.0/24"], &[3356, 15169]).await?;
// The same route leaked to the route server by another member. // The same route leaked to the route server by another member.
announce(&mut peer, &["8.8.4.0/24"], &[2121, 3356, 15169]).await?; announce(&mut peer, &["8.8.4.0/24"], &[2121, 3356, 15169]).await?;
table_becomes(&server, &["8.8.8.0/24"], 1).await server.table_becomes(&["8.8.8.0/24"], 1).await
} }
/// Validity changes with the ASPAs, and the routes follow: one that was /// Validity changes with the ASPAs, and the routes follow: one that was
@@ -256,19 +168,20 @@ async fn test_paths_from_a_route_server_do_not_begin_with_its_as() -> Result<()>
#[tokio::test] #[tokio::test]
async fn test_routes_follow_the_aspas() -> Result<()> { async fn test_routes_follow_the_aspas() -> Result<()> {
let cache = TestCache::start_with(vec![], aspas()).await; let cache = TestCache::start_with(vec![], aspas()).await;
let Setup { server, .. } = server_with(&cache, Some(PeerRole::Peer), default_policy()).await?; let ServerWithPeer { server, .. } =
server_with(&cache, Some(PeerRole::Peer), default_policy()).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN, 2121]).await?; announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN, 2121]).await?;
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?; announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
table_becomes(&server, &["194.0.0.0/24"], 1).await?; server.table_becomes(&["194.0.0.0/24"], 1).await?;
// 2121 says that its only provider is 1101: its route by way of 3333 // 2121 says that its only provider is 1101: its route by way of 3333
// is now invalid, and goes. // is now invalid, and goes.
let mut changed = aspas(); let mut changed = aspas();
changed.push((2121, vec![1101])); changed.push((2121, vec![1101]));
cache.set_aspas(changed); cache.set_aspas(changed);
table_becomes(&server, &[], 2).await?; server.table_becomes(&[], 2).await?;
let refused = &server.refusals.peers()[0].1; let refused = &server.refusals.peers()[0].1;
assert_eq!(refused.total(), 2); assert_eq!(refused.total(), 2);
assert_eq!(refused.recent()[0].prefix, prefix("194.0.0.0/24")); assert_eq!(refused.recent()[0].prefix, prefix("194.0.0.0/24"));
@@ -282,7 +195,9 @@ async fn test_routes_follow_the_aspas() -> Result<()> {
(PEER_ASN, vec![0]), (PEER_ASN, vec![0]),
(2121, vec![1101, PEER_ASN]), (2121, vec![1101, PEER_ASN]),
]); ]);
table_becomes(&server, &["194.0.0.0/24", "8.8.8.0/24"], 0).await?; server
.table_becomes(&["194.0.0.0/24", "8.8.8.0/24"], 0)
.await?;
let paths: BTreeSet<Vec<u32>> = server let paths: BTreeSet<Vec<u32>> = server
.received_routes() .received_routes()
.iter() .iter()
@@ -304,9 +219,11 @@ async fn test_routes_follow_the_aspas() -> Result<()> {
(3356, vec![0]), (3356, vec![0]),
(PEER_ASN, vec![0]), (PEER_ASN, vec![0]),
]); ]);
table_becomes(&server, &["194.0.0.0/24"], 1).await?; server.table_becomes(&["194.0.0.0/24"], 1).await?;
cache.set_aspas(vec![]); cache.set_aspas(vec![]);
table_becomes(&server, &["194.0.0.0/24", "8.8.8.0/24"], 0).await server
.table_becomes(&["194.0.0.0/24", "8.8.8.0/24"], 0)
.await
} }
/// A peer can be exempt: its routes with invalid paths are taken, and /// A peer can be exempt: its routes with invalid paths are taken, and
@@ -318,12 +235,12 @@ async fn test_a_peer_can_be_exempt_from_path_verification() -> Result<()> {
accept_aspa_invalid: true, accept_aspa_invalid: true,
..default_policy() ..default_policy()
}; };
let Setup { server, .. } = server_with(&cache, Some(PeerRole::Peer), import).await?; let ServerWithPeer { server, .. } = server_with(&cache, Some(PeerRole::Peer), import).await?;
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?; announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
table_becomes(&server, &["8.8.8.0/24"], 0).await?; server.table_becomes(&["8.8.8.0/24"], 0).await?;
let routes: Value = reqwest::get(format!("{base}/api/routes/received")) let routes: Value = reqwest::get(format!("{base}/api/routes/received"))
.await? .await?
.json() .json()
@@ -342,16 +259,17 @@ async fn test_a_route_is_held_until_origin_and_path_are_both_in_order() -> Resul
asn, asn,
}; };
let cache = TestCache::start_with(vec![google(13335)], aspas()).await; let cache = TestCache::start_with(vec![google(13335)], aspas()).await;
let Setup { server, .. } = server_with(&cache, Some(PeerRole::Peer), default_policy()).await?; let ServerWithPeer { server, .. } =
server_with(&cache, Some(PeerRole::Peer), default_policy()).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?; announce(&mut peer, &["8.8.8.0/24"], &[PEER_ASN, 3356, 15169]).await?;
table_becomes(&server, &[], 1).await?; server.table_becomes(&[], 1).await?;
// The origin is put right; the path is still a leak. // The origin is put right; the path is still a leak.
cache.set(vec![google(15169)]); cache.set(vec![google(15169)]);
tokio::time::sleep(Duration::from_millis(500)).await; tokio::time::sleep(Duration::from_millis(500)).await;
table_becomes(&server, &[], 1).await?; server.table_becomes(&[], 1).await?;
cache.set_aspas(vec![(15169, vec![3356]), (3356, vec![PEER_ASN])]); cache.set_aspas(vec![(15169, vec![3356]), (3356, vec![PEER_ASN])]);
table_becomes(&server, &["8.8.8.0/24"], 0).await server.table_becomes(&["8.8.8.0/24"], 0).await
} }
@@ -4,6 +4,7 @@ use std::time::Duration;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::RouteChange; use bgp_integration_tests::test_server::RouteChange;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::TestServer;
use bgp_integration_tests::{TIMEOUT, wait_until};
use bgp_packet::errors::{BgpError, CeaseSubcode, FsmSubcode, MsgHeaderSubcode, UpdateMsgSubcode}; use bgp_packet::errors::{BgpError, CeaseSubcode, FsmSubcode, MsgHeaderSubcode, UpdateMsgSubcode};
use bgp_packet::message::{ use bgp_packet::message::{
AsPathAttribute, Message, MpReachNlriPathAttribute, MpUnreachNlriPathAttribute, AsPathAttribute, Message, MpReachNlriPathAttribute, MpUnreachNlriPathAttribute,
@@ -14,8 +15,6 @@ use bgp_primitives::constants::{AfiSafi, IpVersion};
use bgp_primitives::ip_prefix::IpPrefix; use bgp_primitives::ip_prefix::IpPrefix;
use eyre::Result; use eyre::Result;
const TIMEOUT: Duration = Duration::from_secs(5);
/// The attributes every announcement of IPv4 routes must carry. An UPDATE /// The attributes every announcement of IPv4 routes must carry. An UPDATE
/// without them has its routes withdrawn instead (RFC 7606). /// without them has its routes withdrawn instead (RFC 7606).
fn mandatory_attributes() -> Vec<PathAttribute> { fn mandatory_attributes() -> Vec<PathAttribute> {
@@ -98,20 +97,6 @@ async fn test_bad_marker_does_not_crash_server() -> Result<()> {
Ok(()) Ok(())
} }
/// Reads until a NOTIFICATION arrives, skipping KEEPALIVEs.
async fn expect_notification(peer: &mut TestPeer) -> Result<NotificationMessage> {
tokio::time::timeout(TIMEOUT, async {
loop {
match peer.recv().await? {
Message::Notification(n) => return Ok(n),
Message::KeepAlive => {}
other => eyre::bail!("expected NOTIFICATION, got {other:?}"),
}
}
})
.await?
}
/// A header with the given length and type, followed by `body`. /// A header with the given length and type, followed by `body`.
fn framed(length: u16, msg_type: u8, body: &[u8]) -> Vec<u8> { fn framed(length: u16, msg_type: u8, body: &[u8]) -> Vec<u8> {
let mut buf = vec![0xFF; 16]; let mut buf = vec![0xFF; 16];
@@ -170,7 +155,7 @@ async fn test_malformed_messages_are_answered_with_the_matching_notification() -
peer.handshake(65001, Ipv4Addr::new(10, 0, 1, i as u8 + 1), 90) peer.handshake(65001, Ipv4Addr::new(10, 0, 1, i as u8 + 1), 90)
.await?; .await?;
peer.send_raw(&bytes).await?; peer.send_raw(&bytes).await?;
let notification = expect_notification(&mut peer).await?; let notification = peer.expect_notification().await?;
assert_eq!( assert_eq!(
(notification.error, notification.data), (notification.error, notification.data),
(error.clone(), data.clone()), (error.clone(), data.clone()),
@@ -181,7 +166,7 @@ async fn test_malformed_messages_are_answered_with_the_matching_notification() -
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
let _ = peer.recv_open().await?; let _ = peer.recv_open().await?;
peer.send_raw(&bytes).await?; peer.send_raw(&bytes).await?;
let notification = expect_notification(&mut peer).await?; let notification = peer.expect_notification().await?;
assert_eq!( assert_eq!(
(notification.error, notification.data), (notification.error, notification.data),
(error, data), (error, data),
@@ -194,7 +179,7 @@ async fn test_malformed_messages_are_answered_with_the_matching_notification() -
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
let _ = peer.recv_open().await?; let _ = peer.recv_open().await?;
peer.send(&Message::KeepAlive).await?; peer.send(&Message::KeepAlive).await?;
let notification = expect_notification(&mut peer).await?; let notification = peer.expect_notification().await?;
assert_eq!( assert_eq!(
notification.error, notification.error,
BgpError::FsmError(FsmSubcode::UnexpectedOpenSent) BgpError::FsmError(FsmSubcode::UnexpectedOpenSent)
@@ -228,13 +213,10 @@ async fn test_message_split_across_a_keepalive_tick_is_still_one_message() -> Re
tokio::time::sleep(Duration::from_millis(1300)).await; tokio::time::sleep(Duration::from_millis(1300)).await;
peer.send_raw(&update[10..]).await?; peer.send_raw(&update[10..]).await?;
tokio::time::timeout(TIMEOUT, async { wait_until("the split UPDATE reaches the RIB", || {
while !server.received_routes().iter().any(|e| e.prefix == prefix) { server.received_routes().iter().any(|e| e.prefix == prefix)
tokio::time::sleep(Duration::from_millis(20)).await;
}
}) })
.await .await?;
.map_err(|_| eyre::eyre!("the split UPDATE never reached the RIB"))?;
assert_eq!(server.registry.list().len(), 1, "session was dropped"); assert_eq!(server.registry.list().len(), 1, "session was dropped");
Ok(()) Ok(())
} }
@@ -266,7 +248,7 @@ async fn test_server_sends_keepalives() -> Result<()> {
peer.handshake(65001, Ipv4Addr::new(10, 0, 0, 1), 3).await?; peer.handshake(65001, Ipv4Addr::new(10, 0, 0, 1), 3).await?;
// We should receive a KEEPALIVE within ~1 second (hold/3). // We should receive a KEEPALIVE within ~1 second (hold/3).
let msg = tokio::time::timeout(Duration::from_secs(3), peer.recv()).await??; let msg = peer.recv_within(Duration::from_secs(3)).await?;
assert!( assert!(
matches!(msg, Message::KeepAlive), matches!(msg, Message::KeepAlive),
"Expected KEEPALIVE, got {:?}", "Expected KEEPALIVE, got {:?}",
@@ -286,16 +268,7 @@ async fn test_hold_timer_expires() -> Result<()> {
// Don't send any keepalives. The server will send keepalives until its own // Don't send any keepalives. The server will send keepalives until its own
// hold timer fires and it sends a NOTIFICATION. Drain messages until we // hold timer fires and it sends a NOTIFICATION. Drain messages until we
// see it (or the outer timeout fires). // see it (or the outer timeout fires).
let notif = tokio::time::timeout(Duration::from_secs(6), async { let notif = tokio::time::timeout(Duration::from_secs(6), peer.expect_notification()).await??;
loop {
match peer.recv().await? {
Message::Notification(n) => return Ok::<_, eyre::Error>(n),
Message::KeepAlive => {} // server keepalive — keep waiting
other => eyre::bail!("Unexpected message: {:?}", other),
}
}
})
.await??;
assert!( assert!(
matches!(notif.error, BgpError::HoldTimer), matches!(notif.error, BgpError::HoldTimer),
"Expected HoldTimer NOTIFICATION, got {:?}", "Expected HoldTimer NOTIFICATION, got {:?}",
@@ -319,8 +292,7 @@ async fn test_peer_sends_notification() -> Result<()> {
.await?; .await?;
// Server should close the connection cleanly. // Server should close the connection cleanly.
tokio::time::sleep(Duration::from_millis(100)).await; assert!(peer.closed_within(TIMEOUT).await);
assert!(peer.is_closed().await);
Ok(()) Ok(())
} }
+28 -70
View File
@@ -2,66 +2,34 @@
//! peers, which of them the server chooses, and how that is shown. //! peers, which of them the server chooses, and how that is shown.
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration;
use bgp_integration_tests::announcement::{Announcement, withdrawal as withdrawal_of};
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::{RouteChange, TestServer}; use bgp_integration_tests::test_server::{RouteChange, TestServer};
use bgp_packet::message::{ use bgp_integration_tests::{TIMEOUT, get_json, prefix};
AsPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute, PathAttribute, use bgp_packet::message::{PathAttribute, UpdateMessage};
UpdateMessage,
};
use bgp_primitives::ip_prefix::{IpPrefix, Nlri}; use bgp_primitives::ip_prefix::{IpPrefix, Nlri};
use eyre::Result; use eyre::Result;
use serde_json::Value; use serde_json::Value;
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const TIMEOUT: Duration = Duration::from_secs(5);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
/// A session from the peer numbered `number`, in AS 65000 + `number`. /// A session from the peer numbered `number`, in AS 65000 + `number`.
async fn peer(server: &TestServer, number: u8) -> Result<TestPeer> { async fn peer(server: &TestServer, number: u8) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?; server
peer.handshake( .established(
LOCAL_ASN + u32::from(number), LOCAL_ASN + u32::from(number),
Ipv4Addr::new(10, 0, 0, number), Ipv4Addr::new(10, 0, 0, number),
90, )
) .await
.await?;
Ok(peer)
} }
fn announcement(prefixes: &[IpPrefix], as_path: &[u32]) -> Message { fn announcement(prefixes: &[IpPrefix], as_path: &[u32]) -> Announcement {
Message::Update(UpdateMessage { Announcement::of(Nlri::plain(prefixes.iter().copied()), as_path)
withdrawn_routes: vec![],
path_attributes: vec![
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(as_path.to_vec()),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
],
announced_routes: Nlri::plain(prefixes.iter().copied()),
})
} }
fn withdrawal(prefixes: &[IpPrefix]) -> Message { fn withdrawal(prefixes: &[IpPrefix]) -> UpdateMessage {
Message::Update(UpdateMessage { withdrawal_of(Nlri::plain(prefixes.iter().copied()))
withdrawn_routes: Nlri::plain(prefixes.iter().copied()),
path_attributes: vec![],
announced_routes: vec![],
})
}
/// Waits until the table holds `routes` routes.
async fn wait_for_routes(server: &TestServer, routes: usize) -> Result<()> {
tokio::time::timeout(TIMEOUT, async {
while server.rib.len() != routes {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
Ok(())
} }
/// The AS path of the route the server holds as best for `prefix`. /// The AS path of the route the server holds as best for `prefix`.
@@ -78,16 +46,6 @@ fn best_path(server: &TestServer, prefix: &IpPrefix) -> Option<Vec<u32>> {
}) })
} }
async fn get_json(client: &reqwest::Client, url: &str) -> Result<Value> {
Ok(client
.get(url)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
/// Two peers offer the same prefix. The route with the shorter AS path is /// Two peers offer the same prefix. The route with the shorter AS path is
/// chosen; when it goes, the other is. /// chosen; when it goes, the other is.
#[tokio::test] #[tokio::test]
@@ -99,14 +57,14 @@ async fn test_the_best_route_is_chosen_and_replaced_when_it_goes() -> Result<()>
let p = prefix("203.0.113.0/24"); let p = prefix("203.0.113.0/24");
let mut far = peer(&server, 1).await?; let mut far = peer(&server, 1).await?;
far.send(&announcement(&[p], &[65001, 64999, 64998])) far.announce(announcement(&[p], &[65001, 64999, 64998]))
.await?; .await?;
wait_for_routes(&server, 1).await?; server.wait_for_routes(1).await?;
assert_eq!(best_path(&server, &p), Some(vec![65001, 64999, 64998])); assert_eq!(best_path(&server, &p), Some(vec![65001, 64999, 64998]));
let mut near = peer(&server, 2).await?; let mut near = peer(&server, 2).await?;
near.send(&announcement(&[p], &[65002, 64998])).await?; near.announce(announcement(&[p], &[65002, 64998])).await?;
wait_for_routes(&server, 2).await?; server.wait_for_routes(2).await?;
assert_eq!(best_path(&server, &p), Some(vec![65002, 64998])); assert_eq!(best_path(&server, &p), Some(vec![65002, 64998]));
// Both routes are kept and shown; one is marked. // Both routes are kept and shown; one is marked.
@@ -126,11 +84,11 @@ async fn test_the_best_route_is_chosen_and_replaced_when_it_goes() -> Result<()>
// The session with the best route ends: the other route takes over. // The session with the best route ends: the other route takes over.
drop(near); drop(near);
wait_for_routes(&server, 1).await?; server.wait_for_routes(1).await?;
assert_eq!(best_path(&server, &p), Some(vec![65001, 64999, 64998])); assert_eq!(best_path(&server, &p), Some(vec![65001, 64999, 64998]));
far.send(&withdrawal(&[p])).await?; far.announce(withdrawal(&[p])).await?;
wait_for_routes(&server, 0).await?; server.wait_for_routes(0).await?;
assert_eq!(best_path(&server, &p), None); assert_eq!(best_path(&server, &p), None);
// A follower of the best routes is told of each change: the first // A follower of the best routes is told of each change: the first
@@ -157,9 +115,9 @@ async fn test_changes_of_best_route_are_on_the_event_stream() -> Result<()> {
// A route that is there before the client connects is not an event. // A route that is there before the client connects is not an event.
let mut first = peer(&server, 1).await?; let mut first = peer(&server, 1).await?;
first first
.send(&announcement(&[prefix("198.51.100.0/24")], &[65001])) .announce(announcement(&[prefix("198.51.100.0/24")], &[65001]))
.await?; .await?;
wait_for_routes(&server, 1).await?; server.wait_for_routes(1).await?;
let mut stream = reqwest::Client::new() let mut stream = reqwest::Client::new()
.get(format!("{base}/api/events")) .get(format!("{base}/api/events"))
@@ -167,10 +125,10 @@ async fn test_changes_of_best_route_are_on_the_event_stream() -> Result<()> {
.await? .await?
.error_for_status()?; .error_for_status()?;
first.send(&announcement(&[p], &[65001, 64998])).await?; first.announce(announcement(&[p], &[65001, 64998])).await?;
wait_for_routes(&server, 2).await?; server.wait_for_routes(2).await?;
first.send(&withdrawal(&[p])).await?; first.announce(withdrawal(&[p])).await?;
wait_for_routes(&server, 1).await?; server.wait_for_routes(1).await?;
let mut text = String::new(); let mut text = String::new();
let mut events: Vec<Value> = Vec::new(); let mut events: Vec<Value> = Vec::new();
@@ -223,8 +181,8 @@ async fn test_received_routes_are_returned_up_to_a_limit() -> Result<()> {
let prefixes: Vec<IpPrefix> = (0..40).map(|n| prefix(&format!("10.{n}.0.0/16"))).collect(); let prefixes: Vec<IpPrefix> = (0..40).map(|n| prefix(&format!("10.{n}.0.0/16"))).collect();
let mut peer = peer(&server, 1).await?; let mut peer = peer(&server, 1).await?;
peer.send(&announcement(&prefixes, &[65001])).await?; peer.announce(announcement(&prefixes, &[65001])).await?;
wait_for_routes(&server, 40).await?; server.wait_for_routes(40).await?;
let count = |page: Value| page["routes"].as_array().unwrap().len(); let count = |page: Value| page["routes"].as_array().unwrap().len();
let url = format!("{base}/api/routes/received"); let url = format!("{base}/api/routes/received");
+45 -113
View File
@@ -3,56 +3,33 @@
//! The test server's BGP Identifier is 127.0.0.1. A test peer picks an //! The test server's BGP Identifier is 127.0.0.1. A test peer picks an
//! identifier above or below that to steer which connection must survive. //! identifier above or below that to steer which connection must survive.
use std::net::{Ipv4Addr, SocketAddr}; use std::net::Ipv4Addr;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::announcement::announcement;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::{ServerWithPeer, TestServer};
use bgp_integration_tests::{TIMEOUT, prefix, wait_until};
use bgp_packet::errors::{BgpError, CeaseSubcode, OpenMsgSubcode}; use bgp_packet::errors::{BgpError, CeaseSubcode, OpenMsgSubcode};
use bgp_packet::message::{ use bgp_packet::message::{Message, NotificationMessage};
AsPathAttribute, Message, NextHopPathAttribute, NotificationMessage, OriginPathAttribute,
PathAttribute, UpdateMessage,
};
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
use bgp_server::collision::Direction; use bgp_server::collision::Direction;
use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use bgp_server::policy::ImportPolicy; use bgp_server::policy::ImportPolicy;
use eyre::Result; use eyre::Result;
use tokio::net::TcpListener;
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const TIMEOUT: Duration = Duration::from_secs(5);
/// Above the server's 127.0.0.1. /// Above the server's 127.0.0.1.
const HIGH_ID: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 1); const HIGH_ID: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 1);
/// Below the server's 127.0.0.1. /// Below the server's 127.0.0.1.
const LOW_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1); const LOW_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1);
async fn server_dialing(listener: &TcpListener) -> Result<TestServer> { /// A server that dials its peer "r1" every 200 ms until it has a session.
let endpoint: SocketAddr = listener.local_addr()?; async fn server_dialing() -> Result<ServerWithPeer> {
TestServer::start_with(LOCAL_ASN, |c| { TestServer::with_peer(LOCAL_ASN, PEER_ASN, |c, peer| {
c.connect_retry = Duration::from_millis(200); c.connect_retry = Duration::from_millis(200);
c.peers = vec![PeerConfig { peer.import = ImportPolicy::permissive();
graceful_restart: Default::default(),
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import: ImportPolicy::permissive(),
role: None,
require_role: false,
export: Default::default(),
}];
}) })
.await .await
} }
@@ -67,27 +44,13 @@ async fn open_only(peer: &mut TestPeer, router_id: Ipv4Addr) -> Result<()> {
/// Finishes the handshake begun by [`open_only`]. /// Finishes the handshake begun by [`open_only`].
async fn confirm(peer: &mut TestPeer) -> Result<()> { async fn confirm(peer: &mut TestPeer) -> Result<()> {
match tokio::time::timeout(TIMEOUT, peer.recv()).await?? { match peer.recv().await? {
Message::KeepAlive => {} Message::KeepAlive => {}
other => eyre::bail!("expected KEEPALIVE, got {other:?}"), other => eyre::bail!("expected KEEPALIVE, got {other:?}"),
} }
peer.send(&Message::KeepAlive).await peer.send(&Message::KeepAlive).await
} }
/// Reads until a NOTIFICATION arrives, skipping KEEPALIVEs.
async fn expect_notification(peer: &mut TestPeer) -> Result<NotificationMessage> {
tokio::time::timeout(TIMEOUT, async {
loop {
match peer.recv().await? {
Message::Notification(n) => return Ok(n),
Message::KeepAlive => {}
other => eyre::bail!("expected NOTIFICATION, got {other:?}"),
}
}
})
.await?
}
fn is_collision_cease(n: &NotificationMessage) -> bool { fn is_collision_cease(n: &NotificationMessage) -> bool {
matches!( matches!(
n.error, n.error,
@@ -95,38 +58,26 @@ fn is_collision_cease(n: &NotificationMessage) -> bool {
) )
} }
async fn wait_for_sessions(server: &TestServer, n: usize) -> Result<()> {
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().len() != n {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
Ok(())
}
/// Peer has the higher identifier, so the connection the *peer* initiated /// Peer has the higher identifier, so the connection the *peer* initiated
/// must survive: the server closes its own outbound connection. /// must survive: the server closes its own outbound connection.
#[tokio::test] #[tokio::test]
async fn test_simultaneous_open_peer_higher_keeps_inbound() -> Result<()> { async fn test_simultaneous_open_peer_higher_keeps_inbound() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer { server, listener } = server_dialing().await?;
let server = server_dialing(&listener).await?;
// Server's outbound connection reaches OpenConfirm. // Server's outbound connection reaches OpenConfirm.
let (stream, _) = tokio::time::timeout(TIMEOUT, listener.accept()).await??; let mut dialled = TestPeer::accept(&listener).await?;
let mut dialled = TestPeer::from_stream(stream);
open_only(&mut dialled, HIGH_ID).await?; open_only(&mut dialled, HIGH_ID).await?;
// The peer now also dials the server. // The peer now also dials the server.
let mut inbound = TestPeer::connect(server.addr).await?; let mut inbound = TestPeer::connect(server.addr).await?;
open_only(&mut inbound, HIGH_ID).await?; open_only(&mut inbound, HIGH_ID).await?;
let notif = expect_notification(&mut dialled).await?; let notif = dialled.expect_notification().await?;
assert!(is_collision_cease(&notif), "{notif:?}"); assert!(is_collision_cease(&notif), "{notif:?}");
assert!(dialled.is_closed().await); assert!(dialled.closed_within(TIMEOUT).await);
confirm(&mut inbound).await?; confirm(&mut inbound).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
let session = server.registry.list().remove(0); let session = server.registry.list().remove(0);
assert_eq!(session.direction, Direction::Inbound); assert_eq!(session.direction, Direction::Inbound);
assert_eq!(session.name.as_deref(), Some("r1")); assert_eq!(session.name.as_deref(), Some("r1"));
@@ -135,11 +86,11 @@ async fn test_simultaneous_open_peer_higher_keeps_inbound() -> Result<()> {
server server
.local_rib .local_rib
.announce(LocalRoute::new( .announce(LocalRoute::new(
IpPrefix::try_from("10.100.1.0/24")?, prefix("10.100.1.0/24"),
LocalRouteAttrs::default(), LocalRouteAttrs::default(),
)) ))
.await?; .await?;
let update = tokio::time::timeout(TIMEOUT, inbound.recv_update()).await??; let update = inbound.recv_update().await?;
assert_eq!(update.announced_routes[0].to_string(), "10.100.1.0/24"); assert_eq!(update.announced_routes[0].to_string(), "10.100.1.0/24");
// The dialer notices the surviving session and does not open another. // The dialer notices the surviving session and does not open another.
@@ -155,22 +106,20 @@ async fn test_simultaneous_open_peer_higher_keeps_inbound() -> Result<()> {
/// must survive: the peer's inbound connection is refused. /// must survive: the peer's inbound connection is refused.
#[tokio::test] #[tokio::test]
async fn test_simultaneous_open_peer_lower_keeps_outbound() -> Result<()> { async fn test_simultaneous_open_peer_lower_keeps_outbound() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer { server, listener } = server_dialing().await?;
let server = server_dialing(&listener).await?;
let (stream, _) = tokio::time::timeout(TIMEOUT, listener.accept()).await??; let mut dialled = TestPeer::accept(&listener).await?;
let mut dialled = TestPeer::from_stream(stream);
open_only(&mut dialled, LOW_ID).await?; open_only(&mut dialled, LOW_ID).await?;
let mut inbound = TestPeer::connect(server.addr).await?; let mut inbound = TestPeer::connect(server.addr).await?;
open_only(&mut inbound, LOW_ID).await?; open_only(&mut inbound, LOW_ID).await?;
let notif = expect_notification(&mut inbound).await?; let notif = inbound.expect_notification().await?;
assert!(is_collision_cease(&notif), "{notif:?}"); assert!(is_collision_cease(&notif), "{notif:?}");
assert!(inbound.is_closed().await); assert!(inbound.closed_within(TIMEOUT).await);
confirm(&mut dialled).await?; confirm(&mut dialled).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
assert_eq!(server.registry.list()[0].direction, Direction::Outbound); assert_eq!(server.registry.list()[0].direction, Direction::Outbound);
Ok(()) Ok(())
} }
@@ -179,22 +128,20 @@ async fn test_simultaneous_open_peer_lower_keeps_outbound() -> Result<()> {
/// outcome: here the inbound connection reaches OpenConfirm first. /// outcome: here the inbound connection reaches OpenConfirm first.
#[tokio::test] #[tokio::test]
async fn test_outcome_is_independent_of_arrival_order() -> Result<()> { async fn test_outcome_is_independent_of_arrival_order() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer { server, listener } = server_dialing().await?;
let server = server_dialing(&listener).await?;
let (stream, _) = tokio::time::timeout(TIMEOUT, listener.accept()).await??; let mut dialled = TestPeer::accept(&listener).await?;
let mut dialled = TestPeer::from_stream(stream);
// Inbound first this time; peer is lower so outbound must still win. // Inbound first this time; peer is lower so outbound must still win.
let mut inbound = TestPeer::connect(server.addr).await?; let mut inbound = TestPeer::connect(server.addr).await?;
open_only(&mut inbound, LOW_ID).await?; open_only(&mut inbound, LOW_ID).await?;
open_only(&mut dialled, LOW_ID).await?; open_only(&mut dialled, LOW_ID).await?;
let notif = expect_notification(&mut inbound).await?; let notif = inbound.expect_notification().await?;
assert!(is_collision_cease(&notif), "{notif:?}"); assert!(is_collision_cease(&notif), "{notif:?}");
confirm(&mut dialled).await?; confirm(&mut dialled).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
assert_eq!(server.registry.list()[0].direction, Direction::Outbound); assert_eq!(server.registry.list()[0].direction, Direction::Outbound);
Ok(()) Ok(())
} }
@@ -207,11 +154,11 @@ async fn test_established_session_wins_by_default() -> Result<()> {
let mut first = TestPeer::connect(server.addr).await?; let mut first = TestPeer::connect(server.addr).await?;
first.handshake(PEER_ASN, HIGH_ID, 90).await?; first.handshake(PEER_ASN, HIGH_ID, 90).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
let mut second = TestPeer::connect(server.addr).await?; let mut second = TestPeer::connect(server.addr).await?;
open_only(&mut second, HIGH_ID).await?; open_only(&mut second, HIGH_ID).await?;
let notif = expect_notification(&mut second).await?; let notif = second.expect_notification().await?;
assert!(is_collision_cease(&notif), "{notif:?}"); assert!(is_collision_cease(&notif), "{notif:?}");
// The original is untouched. // The original is untouched.
@@ -219,11 +166,11 @@ async fn test_established_session_wins_by_default() -> Result<()> {
server server
.local_rib .local_rib
.announce(LocalRoute::new( .announce(LocalRoute::new(
IpPrefix::try_from("10.100.1.0/24")?, prefix("10.100.1.0/24"),
LocalRouteAttrs::default(), LocalRouteAttrs::default(),
)) ))
.await?; .await?;
let update = tokio::time::timeout(TIMEOUT, first.recv_update()).await??; let update = first.recv_update().await?;
assert_eq!(update.announced_routes[0].to_string(), "10.100.1.0/24"); assert_eq!(update.announced_routes[0].to_string(), "10.100.1.0/24");
Ok(()) Ok(())
} }
@@ -238,41 +185,26 @@ async fn test_restarted_peer_replaces_stale_session_when_enabled() -> Result<()>
let mut stale = TestPeer::connect(server.addr).await?; let mut stale = TestPeer::connect(server.addr).await?;
stale.handshake(PEER_ASN, HIGH_ID, 90).await?; stale.handshake(PEER_ASN, HIGH_ID, 90).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
stale stale
.send(&Message::Update(UpdateMessage { .announce(
withdrawn_routes: vec![], announcement(&["203.0.113.0/24"], &[PEER_ASN])
path_attributes: vec![ .with_next_hop(Ipv4Addr::new(10, 0, 0, 9)),
PathAttribute::Origin(OriginPathAttribute::IGP), )
AsPathAttribute::from_asns(vec![PEER_ASN]),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(10, 0, 0, 9))),
],
announced_routes: vec![IpPrefix::try_from("203.0.113.0/24")?.into()],
}))
.await?; .await?;
tokio::time::timeout(TIMEOUT, async { server.wait_for_routes(1).await?;
while server.received_routes().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
let stale_addr = server.registry.list()[0].peer.addr; let stale_addr = server.registry.list()[0].peer.addr;
// "Restart": a fresh connection from the same router. // "Restart": a fresh connection from the same router.
let mut fresh = TestPeer::connect(server.addr).await?; let mut fresh = TestPeer::connect(server.addr).await?;
fresh.handshake(PEER_ASN, HIGH_ID, 90).await?; fresh.handshake(PEER_ASN, HIGH_ID, 90).await?;
let notif = expect_notification(&mut stale).await?; let notif = stale.expect_notification().await?;
assert!(is_collision_cease(&notif), "{notif:?}"); assert!(is_collision_cease(&notif), "{notif:?}");
tokio::time::timeout(TIMEOUT, async { wait_until("the fresh session has replaced the stale one", || {
loop { let sessions = server.registry.list();
let sessions = server.registry.list(); sessions.len() == 1 && sessions[0].peer.addr != stale_addr
if sessions.len() == 1 && sessions[0].peer.addr != stale_addr {
break;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}) })
.await?; .await?;
assert!( assert!(
@@ -290,7 +222,7 @@ async fn test_distinct_identifiers_do_not_collide() -> Result<()> {
a.handshake(PEER_ASN, HIGH_ID, 90).await?; a.handshake(PEER_ASN, HIGH_ID, 90).await?;
let mut b = TestPeer::connect(server.addr).await?; let mut b = TestPeer::connect(server.addr).await?;
b.handshake(PEER_ASN, LOW_ID, 90).await?; b.handshake(PEER_ASN, LOW_ID, 90).await?;
wait_for_sessions(&server, 2).await?; server.wait_for_sessions(2).await?;
Ok(()) Ok(())
} }
@@ -300,13 +232,13 @@ async fn test_identifier_is_released_when_connection_ends() -> Result<()> {
let server = TestServer::start(LOCAL_ASN).await?; let server = TestServer::start(LOCAL_ASN).await?;
let mut first = TestPeer::connect(server.addr).await?; let mut first = TestPeer::connect(server.addr).await?;
first.handshake(PEER_ASN, HIGH_ID, 90).await?; first.handshake(PEER_ASN, HIGH_ID, 90).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
drop(first); drop(first);
wait_for_sessions(&server, 0).await?; server.wait_for_sessions(0).await?;
let mut second = TestPeer::connect(server.addr).await?; let mut second = TestPeer::connect(server.addr).await?;
second.handshake(PEER_ASN, HIGH_ID, 90).await?; second.handshake(PEER_ASN, HIGH_ID, 90).await?;
wait_for_sessions(&server, 1).await?; server.wait_for_sessions(1).await?;
Ok(()) Ok(())
} }
@@ -316,7 +248,7 @@ async fn test_peer_with_our_identifier_is_refused() -> Result<()> {
let server = TestServer::start(LOCAL_ASN).await?; let server = TestServer::start(LOCAL_ASN).await?;
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
open_only(&mut peer, Ipv4Addr::new(127, 0, 0, 1)).await?; open_only(&mut peer, Ipv4Addr::new(127, 0, 0, 1)).await?;
let notif = expect_notification(&mut peer).await?; let notif = peer.expect_notification().await?;
assert!( assert!(
matches!(notif.error, BgpError::OpenMsg(OpenMsgSubcode::BadBgpId)), matches!(notif.error, BgpError::OpenMsg(OpenMsgSubcode::BadBgpId)),
"{notif:?}" "{notif:?}"
+29 -83
View File
@@ -2,39 +2,13 @@
//! what was refused. //! what was refused.
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration;
use bgp_integration_tests::default_policy; use bgp_integration_tests::announcement::announcement;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_server::{ServerWithPeer, TestServer};
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::{default_policy, wait_until};
use bgp_packet::message::{
AsPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute, PathAttribute,
UpdateMessage,
};
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use bgp_server::policy::ImportPolicy; use bgp_server::policy::ImportPolicy;
use eyre::Result; use eyre::Result;
use serde_json::Value; use serde_json::Value;
use tokio::net::TcpListener;
const TIMEOUT: Duration = Duration::from_secs(5);
fn announcement(prefixes: &[&str], as_path: &[u32]) -> Message {
Message::Update(UpdateMessage {
withdrawn_routes: vec![],
path_attributes: vec![
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(as_path.to_vec()),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
],
announced_routes: prefixes
.iter()
.map(|p| IpPrefix::try_from(*p).unwrap().into())
.collect(),
})
}
/// The routes a query returns, as "prefix from AS, best or not". /// The routes a query returns, as "prefix from AS, best or not".
async fn routes(client: &reqwest::Client, base: &str, query: &str) -> Result<Vec<String>> { async fn routes(client: &reqwest::Client, base: &str, query: &str) -> Result<Vec<String>> {
@@ -67,24 +41,21 @@ async fn test_routes_can_be_looked_up() -> Result<()> {
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let mut one = TestPeer::connect(server.addr).await?; let mut one = server
one.handshake(65001, Ipv4Addr::new(10, 0, 0, 1), 90).await?; .established(65001, Ipv4Addr::new(10, 0, 0, 1))
let mut two = TestPeer::connect(server.addr).await?; .await?;
two.handshake(65002, Ipv4Addr::new(10, 0, 0, 2), 90).await?; let mut two = server
one.send(&announcement( .established(65002, Ipv4Addr::new(10, 0, 0, 2))
.await?;
one.announce(announcement(
&["10.0.0.0/8", "10.1.0.0/16", "10.1.2.0/24", "192.0.2.0/24"], &["10.0.0.0/8", "10.1.0.0/16", "10.1.2.0/24", "192.0.2.0/24"],
&[65001], &[65001],
)) ))
.await?; .await?;
// A longer path for one of the same prefixes: it loses. // A longer path for one of the same prefixes: it loses.
two.send(&announcement(&["10.1.0.0/16"], &[65002, 65003])) two.announce(announcement(&["10.1.0.0/16"], &[65002, 65003]))
.await?; .await?;
tokio::time::timeout(TIMEOUT, async { server.wait_for_routes(5).await?;
while server.rib.len() != 5 {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
// One prefix: every route for it, the best first. // One prefix: every route for it, the best first.
assert_eq!( assert_eq!(
@@ -162,31 +133,15 @@ async fn test_routes_can_be_looked_up() -> Result<()> {
/// something to alert on. /// something to alert on.
#[tokio::test] #[tokio::test]
async fn test_refused_routes_can_be_seen_and_counted() -> Result<()> { async fn test_refused_routes_can_be_seen_and_counted() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer {
let endpoint = listener.local_addr()?; server,
let server = TestServer::start_with(1299, |c| { listener: _listener,
c.accept_unconfigured_peers = false; } = TestServer::with_peer(1299, 3333, |_, peer| {
c.peers = vec![PeerConfig { peer.name = "refused-demo".into();
graceful_restart: Default::default(), peer.import = ImportPolicy {
groups: Vec::new(), max_prefixes: Some(100),
export_limits: None, ..default_policy()
passive: false, };
reflector_client: false,
name: "refused-demo".into(),
endpoint: endpoint.into(),
remote_asn: 3333,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import: ImportPolicy {
max_prefixes: Some(100),
..default_policy()
},
role: None,
require_role: false,
export: Default::default(),
}];
}) })
.await?; .await?;
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
@@ -199,27 +154,23 @@ async fn test_refused_routes_can_be_seen_and_counted() -> Result<()> {
let refused: Value = get("/api/routes/rejected").await?.json().await?; let refused: Value = get("/api/routes/rejected").await?.json().await?;
assert_eq!(refused, serde_json::json!([]), "nothing refused yet"); assert_eq!(refused, serde_json::json!([]), "nothing refused yet");
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = server.established(3333, Ipv4Addr::new(10, 0, 0, 2)).await?;
peer.handshake(3333, Ipv4Addr::new(10, 0, 0, 2), 90).await?; peer.announce(announcement(&["193.0.0.0/21", "10.0.0.0/8"], &[3333]))
peer.send(&announcement(&["193.0.0.0/21", "10.0.0.0/8"], &[3333]))
.await?; .await?;
peer.send(&announcement( peer.announce(announcement(
&["194.0.1.0/24", "194.0.2.0/24"], &["194.0.1.0/24", "194.0.2.0/24"],
&[3333, 64512], &[3333, 64512],
)) ))
.await?; .await?;
peer.send(&announcement(&["193.0.9.0/25"], &[3333, 2121])) peer.announce(announcement(&["193.0.9.0/25"], &[3333, 2121]))
.await?; .await?;
tokio::time::timeout(TIMEOUT, async { wait_until("four routes are refused", || {
while server server
.registry .registry
.list() .list()
.first() .first()
.map(|s| s.counters.rejected()) .map(|s| s.counters.rejected())
!= Some(4) == Some(4)
{
tokio::time::sleep(Duration::from_millis(5)).await;
}
}) })
.await?; .await?;
@@ -301,12 +252,7 @@ async fn test_refused_routes_can_be_seen_and_counted() -> Result<()> {
// The record outlives the session; the count of routes does not. // The record outlives the session; the count of routes does not.
drop(peer); drop(peer);
tokio::time::timeout(TIMEOUT, async { server.wait_for_sessions(0).await?;
while !server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
let refused: Value = get("/api/routes/rejected").await?.json().await?; let refused: Value = get("/api/routes/rejected").await?.json().await?;
assert_eq!(refused[0]["total"], 4); assert_eq!(refused[0]["total"], 4);
let metrics = get("/metricz").await?.text().await?; let metrics = get("/metricz").await?.text().await?;
+70 -207
View File
@@ -5,89 +5,47 @@ use std::net::{Ipv4Addr, SocketAddr};
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::{ServerWithPeer, TestServer};
use bgp_integration_tests::{TIMEOUT, peer_config, prefix, wait_for, wait_until};
use bgp_packet::errors::{BgpError, OpenMsgSubcode}; use bgp_packet::errors::{BgpError, OpenMsgSubcode};
use bgp_packet::message::Message; use bgp_packet::message::Message;
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
use bgp_server::config::PeerConfig; use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use bgp_server::policy::ImportPolicy; use bgp_server::policy::ImportPolicy;
use bgp_server::session::DialState; use bgp_server::session::DialState;
use eyre::Result; use eyre::Result;
use tokio::net::TcpListener;
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const TIMEOUT: Duration = Duration::from_secs(5); const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2);
/// Starts a server configured to dial `listener`'s address as peer "r1". /// A server that dials its peer "r1", in `remote_asn`, every 200 ms.
async fn server_dialing(listener: &TcpListener, remote_asn: u32) -> Result<TestServer> { async fn server_dialing(remote_asn: u32) -> Result<ServerWithPeer> {
server_with_peer(listener, remote_asn, false).await server_with_peer(remote_asn, false).await
} }
/// Starts a server with peer "r1" at `listener`'s address, which it dials /// A server with peer "r1" in `remote_asn`, which it dials every 200 ms
/// unless the peer is `passive`. /// unless the peer is `passive`.
async fn server_with_peer( async fn server_with_peer(remote_asn: u32, passive: bool) -> Result<ServerWithPeer> {
listener: &TcpListener, TestServer::with_peer(LOCAL_ASN, remote_asn, |c, peer| {
remote_asn: u32,
passive: bool,
) -> Result<TestServer> {
let endpoint: SocketAddr = listener.local_addr()?;
TestServer::start_with(LOCAL_ASN, |c| {
c.connect_retry = Duration::from_millis(200); c.connect_retry = Duration::from_millis(200);
c.accept_unconfigured_peers = !passive; peer.import = ImportPolicy::permissive();
c.peers = vec![PeerConfig { peer.passive = passive;
graceful_restart: Default::default(),
groups: Vec::new(),
export_limits: None,
passive,
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import: ImportPolicy::permissive(),
role: None,
require_role: false,
export: Default::default(),
}];
}) })
.await .await
} }
async fn accept(listener: &TcpListener) -> Result<TestPeer> {
let (stream, _) = tokio::time::timeout(TIMEOUT, listener.accept()).await??;
Ok(TestPeer::from_stream(stream))
}
#[tokio::test] #[tokio::test]
async fn test_server_dials_configured_peer_and_establishes() -> Result<()> { async fn test_server_dials_configured_peer_and_establishes() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer { server, listener } = server_dialing(PEER_ASN).await?;
let server = server_dialing(&listener, PEER_ASN).await?;
let mut peer = accept(&listener).await?; let mut peer = TestPeer::accept(&listener).await?;
// Both sides send OPEN on connect; the test peer's handshake order works // Both sides send OPEN on connect; the test peer's handshake order works
// whichever side opened TCP. // whichever side opened TCP.
let open = peer let open = peer.handshake(PEER_ASN, PEER_ID, 90).await?;
.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90)
.await?;
assert_eq!(open.asn, LOCAL_ASN as u16); assert_eq!(open.asn, LOCAL_ASN as u16);
let mut events = server.registry.subscribe(); let listed = server.wait_for_session().await?;
// 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("r1")); assert_eq!(listed.name.as_deref(), Some("r1"));
assert_eq!(listed.peer.asn, PEER_ASN); assert_eq!(listed.peer.asn, PEER_ASN);
assert_eq!(listed.peer.addr, listener.local_addr()?); assert_eq!(listed.peer.addr, listener.local_addr()?);
@@ -109,39 +67,26 @@ async fn test_server_dials_configured_peer_and_establishes() -> Result<()> {
server server
.local_rib .local_rib
.announce(LocalRoute::new( .announce(LocalRoute::new(
IpPrefix::try_from("10.100.1.0/24")?, prefix("10.100.1.0/24"),
LocalRouteAttrs::default(), LocalRouteAttrs::default(),
)) ))
.await?; .await?;
let update = tokio::time::timeout(TIMEOUT, peer.recv_update()).await??; let update = peer.recv_update().await?;
assert_eq!(update.announced_routes[0].to_string(), "10.100.1.0/24"); assert_eq!(update.announced_routes[0].to_string(), "10.100.1.0/24");
Ok(()) Ok(())
} }
#[tokio::test] #[tokio::test]
async fn test_server_reconnects_after_peer_closes() -> Result<()> { async fn test_server_reconnects_after_peer_closes() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer { server, listener } = server_dialing(PEER_ASN).await?;
let server = server_dialing(&listener, PEER_ASN).await?;
let mut first = accept(&listener).await?; let mut first = TestPeer::accept(&listener).await?;
first first.handshake(PEER_ASN, PEER_ID, 90).await?;
.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90) server.wait_for_session().await?;
.await?;
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
drop(first); drop(first);
// Session goes away, then the dialer comes back. // Session goes away, then the dialer comes back.
tokio::time::timeout(TIMEOUT, async { server.wait_for_sessions(0).await?;
while !server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
let (_, state) = server let (_, state) = server
.registry .registry
.configured_peers() .configured_peers()
@@ -150,45 +95,30 @@ async fn test_server_reconnects_after_peer_closes() -> Result<()> {
.unwrap(); .unwrap();
assert!(matches!(state, DialState::Backoff { .. }), "{state:?}"); assert!(matches!(state, DialState::Backoff { .. }), "{state:?}");
let mut second = accept(&listener).await?; let mut second = TestPeer::accept(&listener).await?;
second second.handshake(PEER_ASN, PEER_ID, 90).await?;
.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90) server.wait_for_session().await?;
.await?;
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
Ok(()) Ok(())
} }
#[tokio::test] #[tokio::test]
async fn test_wrong_asn_is_refused_with_bad_peer_as() -> Result<()> { async fn test_wrong_asn_is_refused_with_bad_peer_as() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer {
let _server = server_dialing(&listener, PEER_ASN).await?; server: _server,
listener,
} = server_dialing(PEER_ASN).await?;
let mut peer = accept(&listener).await?; let mut peer = TestPeer::accept(&listener).await?;
peer.send_open(65099, Ipv4Addr::new(10, 0, 0, 2), 90) peer.send_open(65099, PEER_ID, 90).await?;
.await?;
let _ = peer.recv_open().await?; let _ = peer.recv_open().await?;
let notif = tokio::time::timeout(TIMEOUT, async { let notif = peer.expect_notification().await?;
loop {
match peer.recv().await? {
Message::Notification(n) => return Ok::<_, eyre::Error>(n),
Message::KeepAlive => {}
other => eyre::bail!("unexpected {other:?}"),
}
}
})
.await??;
assert!( assert!(
matches!(notif.error, BgpError::OpenMsg(OpenMsgSubcode::BadPeerAs)), matches!(notif.error, BgpError::OpenMsg(OpenMsgSubcode::BadPeerAs)),
"{notif:?}" "{notif:?}"
); );
// The dialer retries rather than giving up. // The dialer retries rather than giving up.
let _again = accept(&listener).await?; let _again = TestPeer::accept(&listener).await?;
Ok(()) Ok(())
} }
@@ -200,32 +130,19 @@ async fn test_inbound_from_configured_address_uses_config() -> Result<()> {
// so the dialer never succeeds; then connect inbound from loopback. // so the dialer never succeeds; then connect inbound from loopback.
let server = TestServer::start_with(LOCAL_ASN, |c| { let server = TestServer::start_with(LOCAL_ASN, |c| {
c.connect_retry = Duration::from_secs(60); c.connect_retry = Duration::from_secs(60);
let at = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1);
c.peers = vec![PeerConfig { c.peers = vec![PeerConfig {
graceful_restart: Default::default(),
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "r1".into(),
endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1).into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import: ImportPolicy::permissive(), import: ImportPolicy::permissive(),
role: None, ..peer_config("r1", at, PEER_ASN)
require_role: false,
export: Default::default(),
}]; }];
}) })
.await?; .await?;
// Wrong ASN inbound → refused. // Wrong ASN inbound → refused.
let mut bad = TestPeer::connect(server.addr).await?; let mut bad = TestPeer::connect(server.addr).await?;
bad.send_open(65099, Ipv4Addr::new(10, 0, 0, 2), 90).await?; bad.send_open(65099, PEER_ID, 90).await?;
let _ = bad.recv_open().await?; let _ = bad.recv_open().await?;
let msg = tokio::time::timeout(TIMEOUT, bad.recv()).await??; let msg = bad.recv().await?;
assert!( assert!(
matches!(&msg, Message::Notification(n) if matches!(n.error, BgpError::OpenMsg(OpenMsgSubcode::BadPeerAs))), matches!(&msg, Message::Notification(n) if matches!(n.error, BgpError::OpenMsg(OpenMsgSubcode::BadPeerAs))),
"{msg:?}" "{msg:?}"
@@ -233,17 +150,8 @@ async fn test_inbound_from_configured_address_uses_config() -> Result<()> {
// Right ASN inbound → established, named. // Right ASN inbound → established, named.
let mut good = TestPeer::connect(server.addr).await?; let mut good = TestPeer::connect(server.addr).await?;
good.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90) good.handshake(PEER_ASN, PEER_ID, 90).await?;
.await?; let listed = server.wait_for_session().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("r1")); assert_eq!(listed.name.as_deref(), Some("r1"));
Ok(()) Ok(())
} }
@@ -253,64 +161,33 @@ async fn test_inbound_from_configured_address_uses_config() -> Result<()> {
/// reported as the reason for backing off rather than silently ignored. /// reported as the reason for backing off rather than silently ignored.
#[tokio::test] #[tokio::test]
async fn test_local_address_is_used_as_the_source() -> Result<()> { async fn test_local_address_is_used_as_the_source() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer {
let endpoint: SocketAddr = listener.local_addr()?; server: _server,
let _server = TestServer::start_with(LOCAL_ASN, |c| { listener,
} = TestServer::with_peer(LOCAL_ASN, PEER_ASN, |c, peer| {
c.connect_retry = Duration::from_millis(200); c.connect_retry = Duration::from_millis(200);
c.peers = vec![PeerConfig { peer.import = ImportPolicy::permissive();
graceful_restart: Default::default(), peer.local_address = Some(Ipv4Addr::LOCALHOST.into());
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: Some(Ipv4Addr::LOCALHOST.into()),
families: FamilySet::supported(),
add_path: None,
import: ImportPolicy::permissive(),
role: None,
require_role: false,
export: Default::default(),
}];
}) })
.await?; .await?;
let (_stream, from) = tokio::time::timeout(TIMEOUT, listener.accept()).await??; let (_stream, from) = tokio::time::timeout(TIMEOUT, listener.accept()).await??;
assert_eq!(from.ip(), std::net::IpAddr::from(Ipv4Addr::LOCALHOST)); assert_eq!(from.ip(), std::net::IpAddr::from(Ipv4Addr::LOCALHOST));
// 192.0.2.1 is documentation space; no host has it. // 192.0.2.1 is documentation space; no host has it.
let endpoint = listener.local_addr()?;
let server = TestServer::start_with(LOCAL_ASN, |c| { let server = TestServer::start_with(LOCAL_ASN, |c| {
c.connect_retry = Duration::from_millis(200); c.connect_retry = Duration::from_millis(200);
c.peers = vec![PeerConfig { c.peers = vec![PeerConfig {
graceful_restart: Default::default(),
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: Some(Ipv4Addr::new(192, 0, 2, 1).into()), local_address: Some(Ipv4Addr::new(192, 0, 2, 1).into()),
families: FamilySet::supported(),
add_path: None,
import: ImportPolicy::permissive(), import: ImportPolicy::permissive(),
role: None, ..peer_config("r1", endpoint, PEER_ASN)
require_role: false,
export: Default::default(),
}]; }];
}) })
.await?; .await?;
let reason = tokio::time::timeout(TIMEOUT, async { let reason = wait_for("the dialer is backing off", || {
loop { match server.registry.configured_peers().into_iter().next() {
if let Some((_, DialState::Backoff { last_error, .. })) = Some((_, DialState::Backoff { last_error, .. })) => Some(last_error),
server.registry.configured_peers().into_iter().next() _ => None,
{
return last_error;
}
tokio::time::sleep(Duration::from_millis(20)).await;
} }
}) })
.await?; .await?;
@@ -325,16 +202,14 @@ async fn test_local_address_is_used_as_the_source() -> Result<()> {
/// such a peer was dialled every few seconds for ever. /// such a peer was dialled every few seconds for ever.
#[tokio::test] #[tokio::test]
async fn test_a_peer_that_refuses_the_session_is_dialled_ever_more_slowly() -> Result<()> { async fn test_a_peer_that_refuses_the_session_is_dialled_ever_more_slowly() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer { server, listener } = server_dialing(PEER_ASN).await?;
let server = server_dialing(&listener, PEER_ASN).await?;
let mut waits = Vec::new(); let mut waits = Vec::new();
for _ in 0..3 { for _ in 0..3 {
let mut peer = accept(&listener).await?; let mut peer = TestPeer::accept(&listener).await?;
peer.send_open(65099, Ipv4Addr::new(10, 0, 0, 2), 90) peer.send_open(65099, PEER_ID, 90).await?;
.await?;
let _ = peer.recv_open().await?; let _ = peer.recv_open().await?;
let refusal = tokio::time::timeout(TIMEOUT, peer.recv()).await??; let refusal = peer.recv().await?;
assert!( assert!(
matches!(&refusal, Message::Notification(n) if n.error == BgpError::OpenMsg(OpenMsgSubcode::BadPeerAs)), matches!(&refusal, Message::Notification(n) if n.error == BgpError::OpenMsg(OpenMsgSubcode::BadPeerAs)),
"{refusal:?}" "{refusal:?}"
@@ -342,17 +217,17 @@ async fn test_a_peer_that_refuses_the_session_is_dialled_ever_more_slowly() -> R
drop(peer); drop(peer);
// How long the dialer says it will wait before trying again. // How long the dialer says it will wait before trying again.
let wait = tokio::time::timeout(TIMEOUT, async { let wait = wait_for("the dialer is backing off", || {
loop { match server.registry.configured_peers().into_iter().next() {
if let Some((_, DialState::Backoff { until, last_error })) = Some((_, DialState::Backoff { until, last_error })) => {
server.registry.configured_peers().into_iter().next()
{
assert!(last_error.contains("AS65099"), "{last_error}"); assert!(last_error.contains("AS65099"), "{last_error}");
return until Some(
.duration_since(std::time::SystemTime::now()) until
.unwrap_or_default(); .duration_since(std::time::SystemTime::now())
.unwrap_or_default(),
)
} }
tokio::time::sleep(Duration::from_millis(5)).await; _ => None,
} }
}) })
.await?; .await?;
@@ -371,8 +246,7 @@ async fn test_a_peer_that_refuses_the_session_is_dialled_ever_more_slowly() -> R
/// session ended. /// session ended.
#[tokio::test] #[tokio::test]
async fn test_a_passive_peer_is_not_dialled_and_comes_up_when_it_connects() -> Result<()> { async fn test_a_passive_peer_is_not_dialled_and_comes_up_when_it_connects() -> Result<()> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; let ServerWithPeer { server, listener } = server_with_peer(PEER_ASN, true).await?;
let server = server_with_peer(&listener, PEER_ASN, true).await?;
let state = || server.registry.configured_peers()[0].1.clone(); let state = || server.registry.configured_peers()[0].1.clone();
// Many retry intervals pass, and nothing connects. // Many retry intervals pass, and nothing connects.
@@ -385,29 +259,18 @@ async fn test_a_passive_peer_is_not_dialled_and_comes_up_when_it_connects() -> R
assert_eq!(state(), DialState::Passive { last_error: None }); assert_eq!(state(), DialState::Passive { last_error: None });
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90) peer.handshake(PEER_ASN, PEER_ID, 90).await?;
.await?; let listed = server.wait_for_session().await?;
let listed = tokio::time::timeout(TIMEOUT, async {
loop {
if let Some(session) = server.registry.list().first().cloned() {
return session;
}
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
assert_eq!(listed.name.as_deref(), Some("r1")); assert_eq!(listed.name.as_deref(), Some("r1"));
drop(peer); drop(peer);
tokio::time::timeout(TIMEOUT, async { wait_until("the peer is shown as waiting, with how it ended", || {
while !matches!( matches!(
state(), state(),
DialState::Passive { DialState::Passive {
last_error: Some(_) last_error: Some(_)
} }
) { )
tokio::time::sleep(Duration::from_millis(5)).await;
}
}) })
.await?; .await?;
assert!(server.registry.list().is_empty()); assert!(server.registry.list().is_empty());
+18 -59
View File
@@ -7,78 +7,39 @@ use std::net::Ipv4Addr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::default_policy;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::{ServerWithPeer, TestServer};
use bgp_integration_tests::{TIMEOUT, prefix};
use bgp_packet::errors::{BgpError, CeaseSubcode}; use bgp_packet::errors::{BgpError, CeaseSubcode};
use bgp_packet::message::Message; use bgp_packet::message::Message;
use bgp_packet::open::{BgpRole, Capability}; use bgp_packet::open::{BgpRole, Capability};
use bgp_primitives::constants::AfiSafi; use bgp_primitives::constants::AfiSafi;
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::LocalRoute; use bgp_rib::LocalRoute;
use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use bgp_server::policy::{ExportLimits, PeerRole, PrefixSet}; use bgp_server::policy::{ExportLimits, PeerRole, PrefixSet};
use eyre::{Result, bail}; use eyre::{Result, bail};
use tokio::net::TcpListener;
const LOCAL_ASN: u32 = 1299; const LOCAL_ASN: u32 = 1299;
const PEER_ASN: u32 = 3333; const PEER_ASN: u32 = 3333;
const TIMEOUT: Duration = Duration::from_secs(5); const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
/// What a test holds on to: the server, and the listener its dialling
/// goes to.
struct Setup {
server: TestServer,
_listener: TcpListener,
}
/// A server that originates `originated`, with one configured peer at the /// A server that originates `originated`, with one configured peer at the
/// address the test connects from, which is a peer of ours held to /// address the test connects from, which is a peer of ours held to
/// `limits`. /// `limits`.
async fn server_with(originated: &[&str], limits: ExportLimits) -> Result<Setup> { async fn server_with(originated: &[&str], limits: ExportLimits) -> Result<ServerWithPeer> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?;
let endpoint = listener.local_addr()?;
let originate = originated let originate = originated
.iter() .iter()
.map(|p| LocalRoute::new(prefix(p), Default::default())) .map(|p| LocalRoute::new(prefix(p), Default::default()))
.collect(); .collect();
let server = TestServer::start_with(LOCAL_ASN, |c| { TestServer::with_peer(LOCAL_ASN, PEER_ASN, |c, peer| {
c.accept_unconfigured_peers = false;
c.originate = originate; c.originate = originate;
c.peers = vec![PeerConfig { peer.export_limits = Some(Arc::new(limits));
graceful_restart: Default::default(), peer.role = Some(PeerRole::Peer);
groups: Vec::new(),
export_limits: Some(Arc::new(limits)),
passive: false,
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import: default_policy(),
role: Some(PeerRole::Peer),
require_role: false,
export: Default::default(),
}];
})
.await?;
Ok(Setup {
server,
_listener: listener,
}) })
.await
} }
/// A session with the server, as its peer. /// A session with the server, as its peer.
async fn established(server: &TestServer) -> Result<TestPeer> { async fn established(server: &TestServer) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?;
let capabilities = vec![ let capabilities = vec![
Capability::FourByteAsn { asn: PEER_ASN }, Capability::FourByteAsn { asn: PEER_ASN },
Capability::multiprotocol(AfiSafi::IPV4_UNICAST), Capability::multiprotocol(AfiSafi::IPV4_UNICAST),
@@ -87,9 +48,9 @@ async fn established(server: &TestServer) -> Result<TestPeer> {
role: BgpRole::Peer, role: BgpRole::Peer,
}, },
]; ];
peer.handshake_with(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90, capabilities) server
.await?; .established_with(PEER_ASN, PEER_ID, capabilities)
Ok(peer) .await
} }
/// What the peer has been told once it has been quiet for a while: the /// What the peer has been told once it has been quiet for a while: the
@@ -97,8 +58,7 @@ async fn established(server: &TestServer) -> Result<TestPeer> {
/// did. /// did.
async fn told(peer: &mut TestPeer) -> (BTreeSet<String>, Option<(BgpError, Vec<u8>)>) { async fn told(peer: &mut TestPeer) -> (BTreeSet<String>, Option<(BgpError, Vec<u8>)>) {
let mut routes = BTreeSet::new(); let mut routes = BTreeSet::new();
while let Ok(Ok(message)) = tokio::time::timeout(Duration::from_millis(500), peer.recv()).await while let Ok(message) = peer.recv_within(Duration::from_millis(500)).await {
{
match message { match message {
Message::Update(update) => { Message::Update(update) => {
for route in &update.withdrawn_routes { for route in &update.withdrawn_routes {
@@ -134,7 +94,8 @@ async fn test_a_route_outside_the_export_limits_is_not_announced() -> Result<()>
prefixes: allowing(&["193.0.0.0/21"]), prefixes: allowing(&["193.0.0.0/21"]),
max_prefixes: None, max_prefixes: None,
}; };
let Setup { server, .. } = server_with(&["193.0.0.0/21", "198.51.100.0/24"], limits).await?; let ServerWithPeer { server, .. } =
server_with(&["193.0.0.0/21", "198.51.100.0/24"], limits).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
let (routes, ended) = told(&mut peer).await; let (routes, ended) = told(&mut peer).await;
assert_eq!(routes, BTreeSet::from(["193.0.0.0/21".to_string()])); assert_eq!(routes, BTreeSet::from(["193.0.0.0/21".to_string()]));
@@ -152,7 +113,8 @@ async fn test_going_over_the_export_limit_ends_the_session_and_keeps_the_peer_do
prefixes: None, prefixes: None,
max_prefixes: Some(2), max_prefixes: Some(2),
}; };
let Setup { server, .. } = server_with(&["193.0.0.0/21", "193.0.8.0/21"], limits).await?; let ServerWithPeer { server, .. } =
server_with(&["193.0.0.0/21", "193.0.8.0/21"], limits).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
let (routes, ended) = told(&mut peer).await; let (routes, ended) = told(&mut peer).await;
assert_eq!(routes.len(), 2); assert_eq!(routes.len(), 2);
@@ -173,15 +135,12 @@ async fn test_going_over_the_export_limit_ends_the_session_and_keeps_the_peer_do
text.starts_with("export limit of 2 routes reached"), text.starts_with("export limit of 2 routes reached"),
"{text}" "{text}"
); );
assert!(peer.is_closed().await); assert!(peer.closed_within(TIMEOUT).await);
// The peer is kept down: a new connection is closed unanswered. // The peer is kept down: a new connection is closed unanswered.
let mut again = TestPeer::connect(server.addr).await?; let mut again = TestPeer::connect(server.addr).await?;
assert!( assert!(
matches!( again.closed_unanswered().await,
tokio::time::timeout(TIMEOUT, again.recv()).await,
Ok(Err(_))
),
"a held-down peer gets no session" "a held-down peer gets no session"
); );
Ok(()) Ok(())
+6 -34
View File
@@ -6,52 +6,24 @@
//! stops with an error, for whatever supervises it to restart it. //! stops with an error, for whatever supervises it to restart it.
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::TIMEOUT;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::TestServer;
use bgp_packet::errors::{BgpError, CeaseSubcode}; use bgp_packet::errors::{BgpError, CeaseSubcode};
use bgp_packet::message::Message;
use bgp_rib::RibStopped; use bgp_rib::RibStopped;
use bgp_server::error::ServerError; use bgp_server::error::ServerError;
use eyre::{Result, bail}; use eyre::Result;
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const TIMEOUT: Duration = Duration::from_secs(5);
async fn established(server: &TestServer) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?;
peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 1), 90)
.await?;
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
Ok(peer)
}
/// Reads until the server's NOTIFICATION, skipping KEEPALIVEs.
async fn farewell(peer: &mut TestPeer) -> Result<BgpError> {
tokio::time::timeout(TIMEOUT, async {
loop {
match peer.recv().await? {
Message::KeepAlive => {}
Message::Notification(n) => return Ok(n.error),
other => bail!("expected NOTIFICATION, got {other:?}"),
}
}
})
.await?
}
#[tokio::test] #[tokio::test]
async fn test_losing_the_local_rib_stops_the_server_and_tells_the_peers() -> Result<()> { async fn test_losing_the_local_rib_stops_the_server_and_tells_the_peers() -> Result<()> {
let mut server = TestServer::start(LOCAL_ASN).await?; let mut server = TestServer::start(LOCAL_ASN).await?;
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
let mut peer = established(&server).await?; let mut peer = server
.established(PEER_ASN, Ipv4Addr::new(10, 0, 0, 1))
.await?;
server.local_rib.stop_for_testing(); server.local_rib.stop_for_testing();
@@ -61,7 +33,7 @@ async fn test_losing_the_local_rib_stops_the_server_and_tells_the_peers() -> Res
"{outcome:?}" "{outcome:?}"
); );
assert_eq!( assert_eq!(
farewell(&mut peer).await?, peer.expect_notification().await?.error,
BgpError::Cease(CeaseSubcode::AdminShutdown) BgpError::Cease(CeaseSubcode::AdminShutdown)
); );
@@ -4,20 +4,17 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::announcement::{Announcement, announcement};
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::TestServer;
use bgp_integration_tests::{TIMEOUT, wait_until as until};
use bgp_packet::errors::{BgpError, CeaseSubcode}; use bgp_packet::errors::{BgpError, CeaseSubcode};
use bgp_packet::message::{ use bgp_packet::message::{Message, NotificationMessage, UpdateMessage};
AsPathAttribute, Message, NextHopPathAttribute, NotificationMessage, OriginPathAttribute,
PathAttribute, UpdateMessage,
};
use bgp_packet::open::{Capability, GracefulRestartEntry}; use bgp_packet::open::{Capability, GracefulRestartEntry};
use bgp_primitives::constants::{Afi, AfiSafi, Safi}; use bgp_primitives::constants::{Afi, AfiSafi, Safi};
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_server::restart::Leave; use bgp_server::restart::Leave;
use eyre::{Result, bail}; use eyre::{Result, bail};
const TIMEOUT: Duration = Duration::from_secs(5);
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2); const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2);
const PREFIXES: [&str; 3] = ["10.1.0.0/16", "10.2.0.0/16", "10.3.0.0/16"]; const PREFIXES: [&str; 3] = ["10.1.0.0/16", "10.2.0.0/16", "10.3.0.0/16"];
@@ -57,37 +54,13 @@ fn capabilities(more: impl IntoIterator<Item = Capability>) -> Vec<Capability> {
capabilities capabilities
} }
fn announcement(prefixes: &[&str]) -> Message { /// The peer's own routes.
Message::Update(UpdateMessage { fn own(prefixes: &[&str]) -> Announcement {
withdrawn_routes: vec![], announcement(prefixes, &[PEER_ASN])
path_attributes: vec![
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(vec![PEER_ASN]),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
],
announced_routes: prefixes
.iter()
.map(|p| IpPrefix::try_from(*p).unwrap().into())
.collect(),
})
} }
fn end_of_rib(family: AfiSafi) -> Message { fn end_of_rib(family: AfiSafi) -> UpdateMessage {
Message::Update(UpdateMessage::end_of_rib(family)) UpdateMessage::end_of_rib(family)
}
/// Waits until `done` holds, or fails with `what`.
async fn until(what: &str, mut done: impl FnMut() -> bool) -> Result<()> {
let waited = tokio::time::timeout(TIMEOUT, async {
while !done() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await;
if waited.is_err() {
bail!("timed out waiting until {what}");
}
Ok(())
} }
/// The prefixes in the server's table, and how many of its routes are /// The prefixes in the server's table, and how many of its routes are
@@ -109,7 +82,7 @@ async fn peer_with_routes(server: &TestServer, offer: Capability) -> Result<Test
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
peer.handshake_with(PEER_ASN, PEER_ID, 90, capabilities([offer])) peer.handshake_with(PEER_ASN, PEER_ID, 90, capabilities([offer]))
.await?; .await?;
peer.send(&announcement(&PREFIXES)).await?; peer.announce(own(&PREFIXES)).await?;
until("the routes are in", || server.rib.len() == 3).await?; until("the routes are in", || server.rib.len() == 3).await?;
Ok(peer) Ok(peer)
} }
@@ -155,12 +128,12 @@ async fn test_a_restarting_peer_keeps_its_routes_until_it_has_sent_them_again()
server.registry.list().len() == 1 && server.restarts.kept().is_empty() server.registry.list().len() == 1 && server.restarts.kept().is_empty()
}) })
.await?; .await?;
peer.send(&announcement(&PREFIXES[..2])).await?; peer.announce(own(&PREFIXES[..2])).await?;
until("two are fresh", || table(&server).1 == 1).await?; until("two are fresh", || table(&server).1 == 1).await?;
// The peer has them all, as far as its limit goes. // The peer has them all, as far as its limit goes.
assert_eq!(server.registry.list()[0].counters.routes(), 3); assert_eq!(server.registry.list()[0].counters.routes(), 3);
peer.send(&end_of_rib(AfiSafi::IPV4_UNICAST)).await?; peer.announce(end_of_rib(AfiSafi::IPV4_UNICAST)).await?;
until("the one not sent again is gone", || { until("the one not sent again is gone", || {
table(&server) == (vec!["10.1.0.0/16".into(), "10.2.0.0/16".into()], 0) table(&server) == (vec!["10.1.0.0/16".into(), "10.2.0.0/16".into()], 0)
}) })
@@ -213,7 +186,7 @@ async fn test_only_a_restart_keeps_the_routes() -> Result<()> {
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
peer.handshake_with(PEER_ASN, PEER_ID, 90, capabilities(offer)) peer.handshake_with(PEER_ASN, PEER_ID, 90, capabilities(offer))
.await?; .await?;
peer.send(&announcement(&PREFIXES)).await?; peer.announce(own(&PREFIXES)).await?;
until("the routes are in", || server.rib.len() == 3).await?; until("the routes are in", || server.rib.len() == 3).await?;
if let Some(goodbye) = &goodbye { if let Some(goodbye) = &goodbye {
peer.send(&Message::Notification(goodbye.clone())).await?; peer.send(&Message::Notification(goodbye.clone())).await?;
@@ -310,7 +283,7 @@ async fn test_we_offer_graceful_restart_and_say_when_our_routes_are_all_sent() -
// Nothing is originated: the markers are all there is. // Nothing is originated: the markers are all there is.
let mut markers = Vec::new(); let mut markers = Vec::new();
while markers.len() < 2 { while markers.len() < 2 {
let update = tokio::time::timeout(TIMEOUT, peer.recv_update()).await??; let update = peer.recv_update().await?;
match update.end_of_rib_family() { match update.end_of_rib_family() {
Some(family) => markers.push(family), Some(family) => markers.push(family),
None => bail!("expected End-of-RIB, got {update:?}"), None => bail!("expected End-of-RIB, got {update:?}"),
@@ -346,14 +319,14 @@ async fn test_leaving_tells_each_peer_as_graceful_restart_has_it() -> Result<()>
server.registry.leave(leave, Duration::from_secs(2)).await; server.registry.leave(leave, Duration::from_secs(2)).await;
let mut notification = None; let mut notification = None;
// Whatever came before (End-of-RIB markers) is passed over. // Whatever came before (End-of-RIB markers) is passed over.
while let Ok(Ok(message)) = tokio::time::timeout(TIMEOUT, peer.recv()).await { while let Ok(message) = peer.recv().await {
if let Message::Notification(n) = message { if let Message::Notification(n) = message {
notification = Some(n.error); notification = Some(n.error);
break; break;
} }
} }
assert_eq!(notification, told, "{leave:?} with {offer:?}"); assert_eq!(notification, told, "{leave:?} with {offer:?}");
assert!(peer.is_closed().await); assert!(peer.closed_within(TIMEOUT).await);
} }
Ok(()) Ok(())
} }
+11 -40
View File
@@ -3,46 +3,22 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::announcement::announcement;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::TestServer;
use bgp_packet::message::{ use bgp_integration_tests::{TIMEOUT, get_json, prefix};
AsPathAttribute, CommunitiesPathAttribute, LargeCommunity, Message, NextHopPathAttribute, use bgp_packet::message::LargeCommunity;
OriginPathAttribute, PathAttribute, UpdateMessage,
};
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
use eyre::Result; use eyre::Result;
use serde_json::{Value, json}; use serde_json::{Value, json};
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const TIMEOUT: Duration = Duration::from_secs(5);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
async fn established_peer(server: &TestServer) -> Result<TestPeer> { async fn established_peer(server: &TestServer) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?; server
peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 1), 90) .established(PEER_ASN, Ipv4Addr::new(10, 0, 0, 1))
.await?; .await
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
Ok(peer)
}
async fn get_json(client: &reqwest::Client, url: &str) -> Result<Value> {
Ok(client
.get(url)
.send()
.await?
.error_for_status()?
.json()
.await?)
} }
#[tokio::test] #[tokio::test]
@@ -155,16 +131,11 @@ async fn test_received_routes_summary() -> Result<()> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let mut peer = established_peer(&server).await?; let mut peer = established_peer(&server).await?;
peer.send(&Message::Update(UpdateMessage { peer.announce(
withdrawn_routes: vec![], announcement(&["203.0.113.0/24"], &[PEER_ASN, 65002])
path_attributes: vec![ .with_next_hop(Ipv4Addr::new(10, 0, 0, 1))
PathAttribute::Origin(OriginPathAttribute::IGP), .with_communities(&[(65001, 7)]),
AsPathAttribute::from_asns(vec![PEER_ASN, 65002]), )
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(10, 0, 0, 1))),
PathAttribute::Communities(CommunitiesPathAttribute(vec![(65001, 7)])),
],
announced_routes: vec![prefix("203.0.113.0/24").into()],
}))
.await?; .await?;
let received = tokio::time::timeout(TIMEOUT, async { let received = tokio::time::timeout(TIMEOUT, async {
+63 -122
View File
@@ -5,100 +5,51 @@ use std::collections::BTreeSet;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::default_policy; use bgp_integration_tests::announcement::announcement;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::{ServerWithPeer, TestServer};
use bgp_integration_tests::{default_policy, prefix, wait_for, wait_until};
use bgp_packet::errors::{BgpError, CeaseSubcode}; use bgp_packet::errors::{BgpError, CeaseSubcode};
use bgp_packet::message::{ use bgp_packet::message::{
AsPathAttribute, CommunitiesPathAttribute, LargeCommunitiesPathAttribute, LargeCommunity, CommunitiesPathAttribute, LargeCommunitiesPathAttribute, LargeCommunity, PathAttribute,
Message, NextHopPathAttribute, OriginPathAttribute, PathAttribute, UpdateMessage,
}; };
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use bgp_server::marks::{Marks, Patterns, Scrub}; use bgp_server::marks::{Marks, Patterns, Scrub};
use bgp_server::policy::{ImportPolicy, ImportRule, PrefixSet}; use bgp_server::policy::{ImportPolicy, ImportRule, PrefixSet};
use bgp_server::session::DialState; use bgp_server::session::DialState;
use eyre::Result; use eyre::Result;
use tokio::net::TcpListener;
/// AS numbers as they are on the Internet: the checks refuse the private /// AS numbers as they are on the Internet: the checks refuse the private
/// ones the other tests use. /// ones the other tests use.
const LOCAL_ASN: u32 = 1299; const LOCAL_ASN: u32 = 1299;
const PEER_ASN: u32 = 3333; const PEER_ASN: u32 = 3333;
const TIMEOUT: Duration = Duration::from_secs(5); const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
/// A server with one configured peer, at the address the test connects /// A server with one configured peer, at the address the test connects
/// from, whose routes are judged by `import`. Returns the listener that /// from, whose routes are judged by `import`. The listener that keeps the
/// keeps the server's own dialling occupied. /// server's own dialling occupied comes with it.
async fn server_with(import: ImportPolicy) -> Result<(TestServer, TcpListener)> { async fn server_with(import: ImportPolicy) -> Result<ServerWithPeer> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; TestServer::with_peer(LOCAL_ASN, PEER_ASN, |c, peer| {
let endpoint = listener.local_addr()?;
let server = TestServer::start_with(LOCAL_ASN, |c| {
c.accept_unconfigured_peers = false;
c.connect_retry = Duration::from_millis(50); c.connect_retry = Duration::from_millis(50);
c.peers = vec![PeerConfig { peer.import = import;
graceful_restart: Default::default(),
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import,
role: None,
require_role: false,
export: Default::default(),
}];
}) })
.await?; .await
Ok((server, listener))
} }
async fn established(server: &TestServer) -> Result<TestPeer> { async fn established(server: &TestServer) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?; server.established(PEER_ASN, PEER_ID).await
peer.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(5)).await;
}
})
.await?;
Ok(peer)
} }
/// Announces IPv4 `prefixes` with the AS path `path`. /// Announces IPv4 `prefixes` with the AS path `path`.
async fn announce(peer: &mut TestPeer, prefixes: &[&str], path: &[u32]) -> Result<()> { async fn announce(peer: &mut TestPeer, prefixes: &[&str], path: &[u32]) -> Result<()> {
peer.send(&Message::Update(UpdateMessage { peer.announce(announcement(prefixes, path)).await
withdrawn_routes: vec![],
path_attributes: vec![
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(path.to_vec()),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
],
announced_routes: prefixes.iter().map(|p| prefix(p).into()).collect(),
}))
.await
} }
/// Waits until the server has dealt with `count` announced routes of the /// Waits until the server has dealt with `count` announced routes of the
/// session's, taken or refused, and returns the prefixes in its table. /// session's, taken or refused, and returns the prefixes in its table.
async fn table_after(server: &TestServer, taken: u64, refused: u64) -> Result<BTreeSet<String>> { async fn table_after(server: &TestServer, taken: u64, refused: u64) -> Result<BTreeSet<String>> {
let counters = server.registry.list()[0].counters.clone(); let counters = server.registry.list()[0].counters.clone();
tokio::time::timeout(TIMEOUT, async { wait_until("the routes are dealt with", || {
while (counters.routes(), counters.rejected()) != (taken, refused) { (counters.routes(), counters.rejected()) == (taken, refused)
tokio::time::sleep(Duration::from_millis(5)).await;
}
}) })
.await .await
.map_err(|_| { .map_err(|_| {
@@ -126,7 +77,10 @@ fn set(prefixes: &[&str]) -> BTreeSet<String> {
/// through us. Everything a peer sent used to be taken. /// through us. Everything a peer sent used to be taken.
#[tokio::test] #[tokio::test]
async fn test_routes_that_no_peer_should_announce_are_refused() -> Result<()> { async fn test_routes_that_no_peer_should_announce_are_refused() -> Result<()> {
let (server, _listener) = server_with(default_policy()).await?; let ServerWithPeer {
server,
listener: _listener,
} = server_with(default_policy()).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
// One UPDATE with good and bad prefixes: the good ones are taken. // One UPDATE with good and bad prefixes: the good ones are taken.
@@ -164,7 +118,10 @@ async fn test_routes_that_no_peer_should_announce_are_refused() -> Result<()> {
/// and the new one is not acceptable. /// and the new one is not acceptable.
#[tokio::test] #[tokio::test]
async fn test_a_refused_route_replaces_the_one_that_was_taken() -> Result<()> { async fn test_a_refused_route_replaces_the_one_that_was_taken() -> Result<()> {
let (server, _listener) = server_with(default_policy()).await?; let ServerWithPeer {
server,
listener: _listener,
} = server_with(default_policy()).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce(&mut peer, &["193.0.0.0/21", "193.0.16.0/24"], &[PEER_ASN]).await?; announce(&mut peer, &["193.0.0.0/21", "193.0.16.0/24"], &[PEER_ASN]).await?;
@@ -181,7 +138,10 @@ async fn test_a_refused_route_replaces_the_one_that_was_taken() -> Result<()> {
async fn test_a_peer_is_held_to_its_prefix_list_and_origins() -> Result<()> { async fn test_a_peer_is_held_to_its_prefix_list_and_origins() -> Result<()> {
let mut prefixes = PrefixSet::default(); let mut prefixes = PrefixSet::default();
prefixes.allow(prefix("193.0.0.0/21"), 24); prefixes.allow(prefix("193.0.0.0/21"), 24);
let (server, _listener) = server_with(ImportPolicy { let ServerWithPeer {
server,
listener: _listener,
} = server_with(ImportPolicy {
prefixes: Some(prefixes), prefixes: Some(prefixes),
origin_asns: Some([PEER_ASN, 2121].into()), origin_asns: Some([PEER_ASN, 2121].into()),
..default_policy() ..default_policy()
@@ -211,7 +171,7 @@ async fn test_a_peer_is_held_to_its_prefix_list_and_origins() -> Result<()> {
/// again. /// again.
#[tokio::test] #[tokio::test]
async fn test_a_peer_over_its_limit_is_cut_off_and_kept_down() -> Result<()> { async fn test_a_peer_over_its_limit_is_cut_off_and_kept_down() -> Result<()> {
let (server, listener) = server_with(ImportPolicy { let ServerWithPeer { server, listener } = server_with(ImportPolicy {
max_prefixes: Some(3), max_prefixes: Some(3),
hold_down: Duration::from_millis(1500), hold_down: Duration::from_millis(1500),
..default_policy() ..default_policy()
@@ -219,7 +179,7 @@ async fn test_a_peer_over_its_limit_is_cut_off_and_kept_down() -> Result<()> {
.await?; .await?;
// The server dials the peer as well; that connection is left unanswered // The server dials the peer as well; that connection is left unanswered
// until the end. // until the end.
let dialled = tokio::time::timeout(TIMEOUT, listener.accept()).await??; let dialled = TestPeer::accept(&listener).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
// Refused routes do not count towards the limit, and replacing a // Refused routes do not count towards the limit, and replacing a
@@ -234,58 +194,43 @@ async fn test_a_peer_over_its_limit_is_cut_off_and_kept_down() -> Result<()> {
assert_eq!(table_after(&server, 3, 1).await?.len(), 3); assert_eq!(table_after(&server, 3, 1).await?.len(), 3);
announce(&mut peer, &["193.0.3.0/24"], &[PEER_ASN]).await?; announce(&mut peer, &["193.0.3.0/24"], &[PEER_ASN]).await?;
let notification = tokio::time::timeout(TIMEOUT, async { let notification = peer.expect_notification().await?;
loop {
if let Message::Notification(notification) = peer.recv().await? {
return Ok::<_, eyre::Report>(notification);
}
}
})
.await??;
assert_eq!( assert_eq!(
notification.error, notification.error,
BgpError::Cease(CeaseSubcode::MaxPrefixes) BgpError::Cease(CeaseSubcode::MaxPrefixes)
); );
drop(peer); drop(peer);
tokio::time::timeout(TIMEOUT, async { wait_until("the peer's routes are gone", || {
while !server.received_routes().is_empty() { server.received_routes().is_empty()
tokio::time::sleep(Duration::from_millis(5)).await;
}
}) })
.await?; .await?;
// Held down: a new connection gets nothing. // Held down: a new connection gets nothing.
let mut early = TestPeer::connect(server.addr).await?; let mut early = TestPeer::connect(server.addr).await?;
assert!( assert!(
matches!( early.closed_unanswered().await,
tokio::time::timeout(TIMEOUT, early.recv()).await,
Ok(Err(_))
),
"a peer that is held down is not answered" "a peer that is held down is not answered"
); );
// Nor is it dialled: once the connection the server had open to it is // Nor is it dialled: once the connection the server had open to it is
// gone, the dialer waits out the hold-down and says so. // gone, the dialer waits out the hold-down and says so.
drop(dialled); drop(dialled);
let reason = tokio::time::timeout(TIMEOUT, async { let reason = wait_for("the dialer says the peer is held down", || {
loop { match server.registry.configured_peers().into_iter().next() {
if let Some((_, DialState::Backoff { last_error, .. })) = Some((_, DialState::Backoff { last_error, .. }))
server.registry.configured_peers().into_iter().next() if last_error.contains("held down") =>
&& last_error.contains("held down")
{ {
return last_error; Some(last_error)
} }
tokio::time::sleep(Duration::from_millis(10)).await; _ => None,
} }
}) })
.await?; .await?;
assert!(reason.contains("limit of routes"), "{reason}"); assert!(reason.contains("limit of routes"), "{reason}");
// When the time is up the peer is dialled again, and taken back. // When the time is up the peer is dialled again, and taken back.
tokio::time::timeout(TIMEOUT, listener.accept()).await??; TestPeer::accept(&listener).await?;
let mut later = TestPeer::connect(server.addr).await?; let mut later = TestPeer::connect(server.addr).await?;
later later.handshake(PEER_ASN, PEER_ID, 90).await?;
.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90)
.await?;
Ok(()) Ok(())
} }
@@ -296,7 +241,10 @@ async fn test_a_peer_over_its_limit_is_cut_off_and_kept_down() -> Result<()> {
async fn test_a_peers_routes_are_marked_and_ranked_as_configured() -> Result<()> { async fn test_a_peers_routes_are_marked_and_ranked_as_configured() -> Result<()> {
use bgp_packet::message::{CommunitiesPathAttribute, LocalPrefPathAttribute}; use bgp_packet::message::{CommunitiesPathAttribute, LocalPrefPathAttribute};
use bgp_server::marks::Marks; use bgp_server::marks::Marks;
let (server, _listener) = server_with(ImportPolicy { let ServerWithPeer {
server,
listener: _listener,
} = server_with(ImportPolicy {
marks: Marks::parse( marks: Marks::parse(
&["1299:*".to_string()], &["1299:*".to_string()],
&["1299:1001".to_string()], &["1299:1001".to_string()],
@@ -308,16 +256,10 @@ async fn test_a_peers_routes_are_marked_and_ranked_as_configured() -> Result<()>
}) })
.await?; .await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
peer.send(&Message::Update(UpdateMessage { peer.announce(
withdrawn_routes: vec![], announcement(&["193.0.0.0/21", "193.0.8.0/22"], &[PEER_ASN])
path_attributes: vec![ .with_communities(&[(3333, 5), (1299, 2002)]),
PathAttribute::Origin(OriginPathAttribute::IGP), )
AsPathAttribute::from_asns(vec![PEER_ASN]),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
PathAttribute::Communities(CommunitiesPathAttribute(vec![(3333, 5), (1299, 2002)])),
],
announced_routes: vec![prefix("193.0.0.0/21").into(), prefix("193.0.8.0/22").into()],
}))
.await?; .await?;
table_after(&server, 2, 0).await?; table_after(&server, 2, 0).await?;
let routes = server.received_routes(); let routes = server.received_routes();
@@ -388,7 +330,10 @@ async fn test_our_communities_are_not_taken_from_other_networks() -> Result<()>
}), }),
..default_policy() ..default_policy()
}; };
let (server, _listener) = server_with(scrubbed).await?; let ServerWithPeer {
server,
listener: _listener,
} = server_with(scrubbed).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce_with(&mut peer, &["193.0.0.0/21"], ours_and_theirs()).await?; announce_with(&mut peer, &["193.0.0.0/21"], ours_and_theirs()).await?;
table_after(&server, 1, 0).await?; table_after(&server, 1, 0).await?;
@@ -402,7 +347,10 @@ async fn test_our_communities_are_not_taken_from_other_networks() -> Result<()>
] ]
); );
let (server, _listener) = server_with(default_policy()).await?; let ServerWithPeer {
server,
listener: _listener,
} = server_with(default_policy()).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce_with(&mut peer, &["193.0.0.0/21"], ours_and_theirs()).await?; announce_with(&mut peer, &["193.0.0.0/21"], ours_and_theirs()).await?;
table_after(&server, 1, 0).await?; table_after(&server, 1, 0).await?;
@@ -424,18 +372,8 @@ async fn announce_with(
prefixes: &[&str], prefixes: &[&str],
more: Vec<PathAttribute>, more: Vec<PathAttribute>,
) -> Result<()> { ) -> Result<()> {
let mut path_attributes = vec![ peer.announce(announcement(prefixes, &[PEER_ASN]).with_attributes(more))
PathAttribute::Origin(OriginPathAttribute::IGP), .await
AsPathAttribute::from_asns(vec![PEER_ASN]),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
];
path_attributes.extend(more);
peer.send(&Message::Update(UpdateMessage {
withdrawn_routes: vec![],
path_attributes,
announced_routes: prefixes.iter().map(|p| prefix(p).into()).collect(),
}))
.await
} }
/// Rules do to some routes what the rest of the policy does to all: by /// Rules do to some routes what the rest of the policy does to all: by
@@ -465,7 +403,10 @@ async fn test_rules_apply_to_the_routes_they_hold_for() -> Result<()> {
], ],
..default_policy() ..default_policy()
}; };
let (server, _listener) = server_with(policy).await?; let ServerWithPeer {
server,
listener: _listener,
} = server_with(policy).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce_with( announce_with(
&mut peer, &mut peer,
+2 -10
View File
@@ -12,8 +12,6 @@ use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
use eyre::Result; use eyre::Result;
const TIMEOUT: Duration = Duration::from_secs(5);
/// The OPEN a BIRD 2 router sends for an IPv6-only session from AS210036: /// The OPEN a BIRD 2 router sends for an IPv6-only session from AS210036:
/// one optional parameter carrying MP(IPv6 unicast), route refresh, /// one optional parameter carrying MP(IPv6 unicast), route refresh,
/// extended message, graceful restart (aware only), 4-octet AS, enhanced /// extended message, graceful restart (aware only), 4-octet AS, enhanced
@@ -77,19 +75,13 @@ async fn test_bird_open_and_end_of_rib_are_accepted() -> Result<()> {
peer.send_raw(BIRD_OPEN).await?; peer.send_raw(BIRD_OPEN).await?;
peer.recv_open().await?; peer.recv_open().await?;
match tokio::time::timeout(TIMEOUT, peer.recv()).await?? { match peer.recv().await? {
Message::KeepAlive => {} Message::KeepAlive => {}
other => eyre::bail!("server did not accept BIRD's OPEN: {other:?}"), other => eyre::bail!("server did not accept BIRD's OPEN: {other:?}"),
} }
peer.send(&Message::KeepAlive).await?; peer.send(&Message::KeepAlive).await?;
tokio::time::timeout(TIMEOUT, async { let session = server.wait_for_session().await?;
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
let session = server.registry.list().remove(0);
assert_eq!( assert_eq!(
session.peer.asn, 210036, session.peer.asn, 210036,
"4-octet ASN must come from the capability, not AS_TRANS" "4-octet ASN must come from the capability, not AS_TRANS"
+7 -24
View File
@@ -12,16 +12,14 @@ use std::process::Command;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use bgp_fib::FibConfig; use bgp_fib::FibConfig;
use bgp_integration_tests::announcement::{announcement, withdrawal};
use bgp_integration_tests::prefix;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::TestServer;
use bgp_packet::message::{
AsPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute, PathAttribute,
UpdateMessage,
};
use bgp_primitives::ip_prefix::IpPrefix;
use eyre::Result; use eyre::Result;
use serde_json::Value; use serde_json::Value;
/// The kernel is given more time than the loopback tests allow.
const TIMEOUT: Duration = Duration::from_secs(10); const TIMEOUT: Duration = Duration::from_secs(10);
const TABLE: u32 = 1100; const TABLE: u32 = 1100;
const INTERFACE: &str = "ferrix100"; const INTERFACE: &str = "ferrix100";
@@ -87,19 +85,8 @@ impl Drop for Interface {
} }
async fn announce(peer: &mut TestPeer, prefixes: &[&str], next_hop: Ipv4Addr) -> Result<()> { async fn announce(peer: &mut TestPeer, prefixes: &[&str], next_hop: Ipv4Addr) -> Result<()> {
peer.send(&Message::Update(UpdateMessage { peer.announce(announcement(prefixes, &[65001]).with_next_hop(next_hop))
withdrawn_routes: vec![], .await
path_attributes: vec![
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(vec![65001]),
PathAttribute::NextHop(NextHopPathAttribute(next_hop)),
],
announced_routes: prefixes
.iter()
.map(|p| IpPrefix::try_from(*p).unwrap().into())
.collect(),
}))
.await
} }
/// A peer's routes go from the session to the kernel, and out of it again /// A peer's routes go from the session to the kernel, and out of it again
@@ -174,12 +161,8 @@ async fn test_routes_from_a_session_reach_the_kernel() -> Result<()> {
); );
// One is withdrawn. // One is withdrawn.
peer.send(&Message::Update(UpdateMessage { peer.announce(withdrawal([prefix("198.18.0.0/15").into()]))
withdrawn_routes: vec![IpPrefix::try_from("198.18.0.0/15").unwrap().into()], .await?;
path_attributes: vec![],
announced_routes: vec![],
}))
.await?;
kernel_becomes(&["203.0.113.0/24 via 10.100.0.2 dev ferrix100 proto bgp metric 20"]); kernel_becomes(&["203.0.113.0/24 via 10.100.0.2 dev ferrix100 proto bgp metric 20"]);
// The session ends: packets must not go on following its routes. // The session ends: packets must not go on following its routes.
+33 -85
View File
@@ -4,16 +4,16 @@
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::announcement::announcement;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::{ReceivedRoute, TestServer};
use bgp_integration_tests::{peer_config, prefix, wait_for};
use bgp_packet::errors::{BgpError, OpenMsgSubcode}; use bgp_packet::errors::{BgpError, OpenMsgSubcode};
use bgp_packet::message::{ use bgp_packet::message::{
AsPathAttribute, Message, MpReachNlriPathAttribute, NextHopPathAttribute, NlriNextHop, AsPathAttribute, Message, MpReachNlriPathAttribute, NlriNextHop, PathAttribute, UpdateMessage,
OriginPathAttribute, PathAttribute, UpdateMessage,
}; };
use bgp_packet::open::Capability; use bgp_packet::open::Capability;
use bgp_primitives::constants::{Afi, AfiSafi, Safi}; use bgp_primitives::constants::{Afi, AfiSafi, Safi};
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
use bgp_server::config::PeerConfig; use bgp_server::config::PeerConfig;
use bgp_server::family::{FamilySet, SUPPORTED}; use bgp_server::family::{FamilySet, SUPPORTED};
@@ -23,11 +23,6 @@ use eyre::{Result, bail};
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1); const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1);
const TIMEOUT: Duration = Duration::from_secs(5);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
fn only(family: AfiSafi) -> FamilySet { fn only(family: AfiSafi) -> FamilySet {
[family].into_iter().collect() [family].into_iter().collect()
@@ -37,35 +32,26 @@ fn four_octet() -> Capability {
Capability::FourByteAsn { asn: PEER_ASN } Capability::FourByteAsn { asn: PEER_ASN }
} }
async fn wait_for_session(server: &TestServer) -> Result<bgp_server::session::SessionHandle> { /// Waits until the server has a route, and returns what it has.
Ok(tokio::time::timeout(TIMEOUT, async { async fn some_routes(server: &TestServer) -> Result<Vec<ReceivedRoute>> {
loop { wait_for("a route is in the table", || {
if let Some(session) = server.registry.list().into_iter().next() { let routes = server.received_routes();
return session; (!routes.is_empty()).then_some(routes)
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}) })
.await?) .await
} }
/// An UPDATE announcing one IPv4 route in the body and one IPv6 route in /// An UPDATE announcing one IPv4 route in the body and one IPv6 route in
/// MP_REACH_NLRI. /// MP_REACH_NLRI.
fn dual_stack_update() -> UpdateMessage { fn dual_stack_update() -> UpdateMessage {
UpdateMessage { let ipv6 = PathAttribute::MpReachNlri(MpReachNlriPathAttribute {
withdrawn_routes: vec![], family: AfiSafi::IPV6_UNICAST,
path_attributes: vec![ next_hop: NlriNextHop::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),
PathAttribute::Origin(OriginPathAttribute::IGP), prefixes: vec![prefix("2001:db8:1::/48").into()],
AsPathAttribute::from_asns(vec![PEER_ASN]), });
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))), announcement(&["192.0.2.0/24"], &[PEER_ASN])
PathAttribute::MpReachNlri(MpReachNlriPathAttribute { .with_attributes([ipv6])
family: AfiSafi::IPV6_UNICAST, .into()
next_hop: NlriNextHop::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),
prefixes: vec![prefix("2001:db8:1::/48").into()],
}),
],
announced_routes: vec![prefix("192.0.2.0/24").into()],
}
} }
/// The session carries the families both OPENs offered, and the server's /// The session carries the families both OPENs offered, and the server's
@@ -93,7 +79,7 @@ async fn test_families_are_the_ones_both_sides_offered() -> Result<()> {
"the server's OPEN offers {family}" "the server's OPEN offers {family}"
); );
} }
let session = wait_for_session(&server).await?; let session = server.wait_for_session().await?;
assert_eq!(session.families, only(AfiSafi::IPV4_UNICAST)); assert_eq!(session.families, only(AfiSafi::IPV4_UNICAST));
Ok(()) Ok(())
} }
@@ -114,20 +100,11 @@ async fn test_routes_of_a_family_not_negotiated_are_not_accepted() -> Result<()>
], ],
) )
.await?; .await?;
wait_for_session(&server).await?; server.wait_for_session().await?;
peer.send(&Message::Update(dual_stack_update())).await?; peer.announce(dual_stack_update()).await?;
let routes = tokio::time::timeout(TIMEOUT, async { let routes = some_routes(&server).await?;
loop {
let routes = server.received_routes();
if !routes.is_empty() {
return routes;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
// One UPDATE is applied as a whole, so the IPv6 route is not on its way. // One UPDATE is applied as a whole, so the IPv6 route is not on its way.
assert_eq!(routes.len(), 1); assert_eq!(routes.len(), 1);
assert_eq!(routes[0].prefix, prefix("192.0.2.0/24")); assert_eq!(routes[0].prefix, prefix("192.0.2.0/24"));
@@ -159,7 +136,7 @@ async fn test_routes_of_a_family_not_negotiated_are_not_sent() -> Result<()> {
], ],
) )
.await?; .await?;
let session = wait_for_session(&server).await?; let session = server.wait_for_session().await?;
assert_eq!(session.families, only(AfiSafi::IPV6_UNICAST)); assert_eq!(session.families, only(AfiSafi::IPV6_UNICAST));
// A second peer that takes IPv4 is sent the route. The advertiser // A second peer that takes IPv4 is sent the route. The advertiser
@@ -168,7 +145,7 @@ async fn test_routes_of_a_family_not_negotiated_are_not_sent() -> Result<()> {
let mut dual = TestPeer::connect(server.addr).await?; let mut dual = TestPeer::connect(server.addr).await?;
dual.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90) dual.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90)
.await?; .await?;
let update = tokio::time::timeout(TIMEOUT, dual.recv_update()).await??; let update = dual.recv_update().await?;
assert_eq!(update.announced_routes, vec![prefix("10.100.0.0/16")]); assert_eq!(update.announced_routes, vec![prefix("10.100.0.0/16")]);
let nothing = tokio::time::timeout(Duration::from_millis(200), v6_only.recv_update()).await; let nothing = tokio::time::timeout(Duration::from_millis(200), v6_only.recv_update()).await;
@@ -182,23 +159,11 @@ async fn test_routes_of_a_family_not_negotiated_are_not_sent() -> Result<()> {
async fn test_no_family_in_common_refuses_the_session() -> Result<()> { async fn test_no_family_in_common_refuses_the_session() -> Result<()> {
let server = TestServer::start_with(LOCAL_ASN, |c| { let server = TestServer::start_with(LOCAL_ASN, |c| {
// Inbound connections from this address inherit the peer's settings. // Inbound connections from this address inherit the peer's settings.
let at = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1);
c.peers = vec![PeerConfig { c.peers = vec![PeerConfig {
graceful_restart: Default::default(),
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "v6-only".into(),
endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1).into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: only(AfiSafi::IPV6_UNICAST), families: only(AfiSafi::IPV6_UNICAST),
add_path: None,
import: ImportPolicy::permissive(), import: ImportPolicy::permissive(),
role: None, ..peer_config("v6-only", at, PEER_ASN)
require_role: false,
export: Default::default(),
}]; }];
// Keep the dialer out of the way. // Keep the dialer out of the way.
c.connect_retry = Duration::from_secs(3600); c.connect_retry = Duration::from_secs(3600);
@@ -223,7 +188,7 @@ async fn test_no_family_in_common_refuses_the_session() -> Result<()> {
.collect(); .collect();
assert_eq!(offered, [&Capability::multiprotocol(AfiSafi::IPV6_UNICAST)]); assert_eq!(offered, [&Capability::multiprotocol(AfiSafi::IPV6_UNICAST)]);
match tokio::time::timeout(TIMEOUT, peer.recv()).await?? { match peer.recv().await? {
Message::Notification(n) => { Message::Notification(n) => {
assert_eq!( assert_eq!(
n.error, n.error,
@@ -256,7 +221,7 @@ async fn test_a_peer_offering_an_unsupported_family_is_still_accepted() -> Resul
) )
.await?; .await?;
let session = wait_for_session(&server).await?; let session = server.wait_for_session().await?;
assert_eq!(session.families, only(AfiSafi::IPV4_UNICAST)); assert_eq!(session.families, only(AfiSafi::IPV4_UNICAST));
assert!( assert!(
session.four_octet_asn, session.four_octet_asn,
@@ -281,13 +246,13 @@ async fn test_a_peer_without_capabilities_gets_two_octet_as_paths() -> Result<()
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
peer.handshake_with(PEER_ASN, PEER_ID, 90, vec![]).await?; peer.handshake_with(PEER_ASN, PEER_ID, 90, vec![]).await?;
let session = wait_for_session(&server).await?; let session = server.wait_for_session().await?;
assert!(!session.four_octet_asn); assert!(!session.four_octet_asn);
assert_eq!(session.families, only(AfiSafi::IPV4_UNICAST)); assert_eq!(session.families, only(AfiSafi::IPV4_UNICAST));
// The test peer now decodes AS_PATH two octets wide, as such a speaker // The test peer now decodes AS_PATH two octets wide, as such a speaker
// would; an UPDATE encoded four octets wide would not parse. // would; an UPDATE encoded four octets wide would not parse.
let update = tokio::time::timeout(TIMEOUT, peer.recv_update()).await??; let update = peer.recv_update().await?;
assert_eq!(update.announced_routes, vec![prefix("10.100.0.0/16")]); assert_eq!(update.announced_routes, vec![prefix("10.100.0.0/16")]);
assert!( assert!(
update update
@@ -302,26 +267,9 @@ async fn test_a_peer_without_capabilities_gets_two_octet_as_paths() -> Result<()
assert_eq!(as4_path, Some(vec![WIDE_ASN])); assert_eq!(as4_path, Some(vec![WIDE_ASN]));
// And its own two-octet paths are read correctly. // And its own two-octet paths are read correctly.
peer.send(&Message::Update(UpdateMessage { peer.announce(announcement(&["192.0.2.0/24"], &[PEER_ASN, 64512]))
withdrawn_routes: vec![], .await?;
path_attributes: vec![ let routes = some_routes(&server).await?;
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(vec![PEER_ASN, 64512]),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
],
announced_routes: vec![prefix("192.0.2.0/24").into()],
}))
.await?;
let routes = tokio::time::timeout(TIMEOUT, async {
loop {
let routes = server.received_routes();
if !routes.is_empty() {
return routes;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
assert!( assert!(
routes[0] routes[0]
.attributes .attributes
+40 -119
View File
@@ -3,74 +3,36 @@
//! marked as only to go to customers. //! marked as only to go to customers.
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::{Ipv4Addr, Ipv6Addr};
use std::time::Duration;
use bgp_integration_tests::default_policy; use bgp_integration_tests::announcement::announcement;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::{ServerWithPeer, TestServer};
use bgp_integration_tests::{prefix, wait_until};
use bgp_packet::errors::{BgpError, OpenMsgSubcode}; use bgp_packet::errors::{BgpError, OpenMsgSubcode};
use bgp_packet::message::{ use bgp_packet::message::{
AsPathAttribute, Message, MpReachNlriPathAttribute, NextHopPathAttribute, NlriNextHop, AsPathAttribute, Message, MpReachNlriPathAttribute, NlriNextHop, OriginPathAttribute,
OriginPathAttribute, PathAttribute, UpdateMessage, PathAttribute, UpdateMessage,
}; };
use bgp_packet::open::{BgpRole, Capability}; use bgp_packet::open::{BgpRole, Capability};
use bgp_primitives::constants::AfiSafi; use bgp_primitives::constants::AfiSafi;
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::LocalRoute; use bgp_rib::LocalRoute;
use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use bgp_server::policy::PeerRole; use bgp_server::policy::PeerRole;
use eyre::{Result, bail}; use eyre::{Result, bail};
use serde_json::Value; use serde_json::Value;
use tokio::net::TcpListener;
const LOCAL_ASN: u32 = 1299; const LOCAL_ASN: u32 = 1299;
const PEER_ASN: u32 = 3333; const PEER_ASN: u32 = 3333;
const TIMEOUT: Duration = Duration::from_secs(5); const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
/// What a test holds on to: the server, and the listener its dialling
/// goes to.
struct Setup {
server: TestServer,
_listener: TcpListener,
}
/// A server with one configured peer at the address the test connects /// A server with one configured peer at the address the test connects
/// from, which is `role` to it, and which originates 193.0.0.0/21. /// from, which is `role` to it, and which originates 193.0.0.0/21.
async fn server_with(role: Option<PeerRole>, require_role: bool) -> Result<Setup> { async fn server_with(role: Option<PeerRole>, require_role: bool) -> Result<ServerWithPeer> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?; TestServer::with_peer(LOCAL_ASN, PEER_ASN, |c, peer| {
let endpoint = listener.local_addr()?;
let server = TestServer::start_with(LOCAL_ASN, |c| {
c.accept_unconfigured_peers = false;
c.originate = vec![LocalRoute::new(prefix("193.0.0.0/21"), Default::default())]; c.originate = vec![LocalRoute::new(prefix("193.0.0.0/21"), Default::default())];
c.peers = vec![PeerConfig { peer.role = role;
graceful_restart: Default::default(), peer.require_role = require_role;
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import: default_policy(),
role,
require_role,
export: Default::default(),
}];
})
.await?;
Ok(Setup {
server,
_listener: listener,
}) })
.await
} }
/// The capabilities of an ordinary peer, and with `role` what it says it /// The capabilities of an ordinary peer, and with `role` what it says it
@@ -88,16 +50,9 @@ fn capabilities(role: Option<BgpRole>) -> Vec<Capability> {
/// A session with the server, on which the peer says it is `says`. /// A session with the server, on which the peer says it is `says`.
async fn established(server: &TestServer, says: Option<BgpRole>) -> Result<TestPeer> { async fn established(server: &TestServer, says: Option<BgpRole>) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?; server
peer.handshake_with(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90, capabilities(says)) .established_with(PEER_ASN, PEER_ID, capabilities(says))
.await?; .await
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
Ok(peer)
} }
/// Announces `prefixes` with the AS path `path` and, with `marked_by`, an /// Announces `prefixes` with the AS path `path` and, with `marked_by`, an
@@ -108,30 +63,17 @@ async fn announce(
path: &[u32], path: &[u32],
marked_by: Option<u32>, marked_by: Option<u32>,
) -> Result<()> { ) -> Result<()> {
let mut path_attributes = vec![ let mut announcement = announcement(prefixes, path);
PathAttribute::Origin(OriginPathAttribute::IGP), if let Some(asn) = marked_by {
AsPathAttribute::from_asns(path.to_vec()), announcement = announcement.with_only_to_customer(asn);
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))), }
]; peer.announce(announcement).await
path_attributes.extend(marked_by.map(PathAttribute::OnlyToCustomer));
peer.send(&Message::Update(UpdateMessage {
withdrawn_routes: vec![],
path_attributes,
announced_routes: prefixes.iter().map(|p| prefix(p).into()).collect(),
}))
.await
} }
/// Waits until the table holds `count` routes, and returns the mark each /// Waits until the table holds `count` routes, and returns the mark each
/// has, by prefix. /// has, by prefix.
async fn marks(server: &TestServer, count: usize) -> Result<Vec<(String, Option<u32>)>> { async fn marks(server: &TestServer, count: usize) -> Result<Vec<(String, Option<u32>)>> {
tokio::time::timeout(TIMEOUT, async { server.wait_for_routes(count).await.map_err(|_| {
while server.received_routes().len() != count {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await
.map_err(|_| {
eyre::eyre!( eyre::eyre!(
"expected {count} routes, the table has {:?}", "expected {count} routes, the table has {:?}",
server.received_routes() server.received_routes()
@@ -167,26 +109,16 @@ fn announced_role(open: &bgp_packet::open::OpenMessage) -> Option<BgpRole> {
/// shown as one whose peer confirmed its role. /// shown as one whose peer confirmed its role.
#[tokio::test] #[tokio::test]
async fn test_roles_that_fit_together_are_confirmed() -> Result<()> { async fn test_roles_that_fit_together_are_confirmed() -> Result<()> {
let Setup { server, .. } = server_with(Some(PeerRole::Customer), false).await?; let ServerWithPeer { server, .. } = server_with(Some(PeerRole::Customer), false).await?;
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
let open = peer let open = peer
.handshake_with( .handshake_with(PEER_ASN, PEER_ID, 90, capabilities(Some(BgpRole::Customer)))
PEER_ASN,
Ipv4Addr::new(10, 0, 0, 2),
90,
capabilities(Some(BgpRole::Customer)),
)
.await?; .await?;
// The peer is our customer: we are its provider. // The peer is our customer: we are its provider.
assert_eq!(announced_role(&open), Some(BgpRole::Provider)); assert_eq!(announced_role(&open), Some(BgpRole::Provider));
tokio::time::timeout(TIMEOUT, async { server.wait_for_session().await?;
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
let peers: Value = reqwest::get(format!("{base}/api/peers")) let peers: Value = reqwest::get(format!("{base}/api/peers"))
.await? .await?
.json() .json()
@@ -201,18 +133,13 @@ async fn test_roles_that_fit_together_are_confirmed() -> Result<()> {
/// configured as is refused, with the error RFC 9234 gives for it. /// configured as is refused, with the error RFC 9234 gives for it.
#[tokio::test] #[tokio::test]
async fn test_a_peer_that_says_it_is_something_else_is_refused() -> Result<()> { async fn test_a_peer_that_says_it_is_something_else_is_refused() -> Result<()> {
let Setup { server, .. } = server_with(Some(PeerRole::Customer), false).await?; let ServerWithPeer { server, .. } = server_with(Some(PeerRole::Customer), false).await?;
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
// It thinks it is our peer; we have it down as a customer. // It thinks it is our peer; we have it down as a customer.
peer.send_open_with( peer.send_open_with(PEER_ASN, PEER_ID, 90, capabilities(Some(BgpRole::Peer)))
PEER_ASN, .await?;
Ipv4Addr::new(10, 0, 0, 2),
90,
capabilities(Some(BgpRole::Peer)),
)
.await?;
peer.recv_open().await?; peer.recv_open().await?;
match tokio::time::timeout(TIMEOUT, peer.recv()).await?? { match peer.recv().await? {
Message::Notification(n) => { Message::Notification(n) => {
assert_eq!(n.error, BgpError::OpenMsg(OpenMsgSubcode::RoleMismatch)); assert_eq!(n.error, BgpError::OpenMsg(OpenMsgSubcode::RoleMismatch));
// Subcode 11 on the wire. // Subcode 11 on the wire.
@@ -228,7 +155,7 @@ async fn test_a_peer_that_says_it_is_something_else_is_refused() -> Result<()> {
/// our word for what it is, unless the configuration requires it to say. /// our word for what it is, unless the configuration requires it to say.
#[tokio::test] #[tokio::test]
async fn test_a_peer_that_does_not_say_is_refused_only_where_required() -> Result<()> { async fn test_a_peer_that_does_not_say_is_refused_only_where_required() -> Result<()> {
let Setup { server, .. } = server_with(Some(PeerRole::Customer), false).await?; let ServerWithPeer { server, .. } = server_with(Some(PeerRole::Customer), false).await?;
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
let _peer = established(&server, None).await?; let _peer = established(&server, None).await?;
let peers: Value = reqwest::get(format!("{base}/api/peers")) let peers: Value = reqwest::get(format!("{base}/api/peers"))
@@ -238,12 +165,12 @@ async fn test_a_peer_that_does_not_say_is_refused_only_where_required() -> Resul
assert_eq!(peers[0]["role"], "customer"); assert_eq!(peers[0]["role"], "customer");
assert_eq!(peers[0]["role_confirmed"], false); assert_eq!(peers[0]["role_confirmed"], false);
let Setup { server, .. } = server_with(Some(PeerRole::Customer), true).await?; let ServerWithPeer { server, .. } = server_with(Some(PeerRole::Customer), true).await?;
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
peer.send_open_with(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90, capabilities(None)) peer.send_open_with(PEER_ASN, PEER_ID, 90, capabilities(None))
.await?; .await?;
peer.recv_open().await?; peer.recv_open().await?;
match tokio::time::timeout(TIMEOUT, peer.recv()).await?? { match peer.recv().await? {
Message::Notification(n) => { Message::Notification(n) => {
assert_eq!(n.error, BgpError::OpenMsg(OpenMsgSubcode::RoleMismatch)); assert_eq!(n.error, BgpError::OpenMsg(OpenMsgSubcode::RoleMismatch));
} }
@@ -257,7 +184,7 @@ async fn test_a_peer_that_does_not_say_is_refused_only_where_required() -> Resul
/// whatever else the peer's policy takes. /// whatever else the peer's policy takes.
#[tokio::test] #[tokio::test]
async fn test_a_marked_route_from_a_customer_is_a_leak() -> Result<()> { async fn test_a_marked_route_from_a_customer_is_a_leak() -> Result<()> {
let Setup { server, .. } = server_with(Some(PeerRole::Customer), false).await?; let ServerWithPeer { server, .. } = server_with(Some(PeerRole::Customer), false).await?;
let mut peer = established(&server, Some(BgpRole::Customer)).await?; let mut peer = established(&server, Some(BgpRole::Customer)).await?;
// The customer's own route, and one its other provider gave it. // The customer's own route, and one its other provider gave it.
@@ -275,12 +202,7 @@ async fn test_a_marked_route_from_a_customer_is_a_leak() -> Result<()> {
marks(&server, 1).await?, marks(&server, 1).await?,
[("194.0.0.0/24".to_string(), None)] [("194.0.0.0/24".to_string(), None)]
); );
tokio::time::timeout(TIMEOUT, async { wait_until("a route is refused", || !server.refusals.peers().is_empty()).await?;
while server.refusals.peers().is_empty() {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
let refused = &server.refusals.peers()[0].1; let refused = &server.refusals.peers()[0].1;
assert_eq!(refused.total(), 1); assert_eq!(refused.total(), 1);
assert_eq!(refused.recent()[0].prefix, prefix("8.8.8.0/24")); assert_eq!(refused.recent()[0].prefix, prefix("8.8.8.0/24"));
@@ -296,7 +218,7 @@ async fn test_a_marked_route_from_a_customer_is_a_leak() -> Result<()> {
/// the route from above or from another peer. /// the route from above or from another peer.
#[tokio::test] #[tokio::test]
async fn test_a_route_marked_by_someone_else_is_a_leak_from_a_peer() -> Result<()> { async fn test_a_route_marked_by_someone_else_is_a_leak_from_a_peer() -> Result<()> {
let Setup { server, .. } = server_with(Some(PeerRole::Peer), false).await?; let ServerWithPeer { server, .. } = server_with(Some(PeerRole::Peer), false).await?;
let mut peer = established(&server, Some(BgpRole::Peer)).await?; let mut peer = established(&server, Some(BgpRole::Peer)).await?;
announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN], Some(PEER_ASN)).await?; announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN], Some(PEER_ASN)).await?;
@@ -326,14 +248,13 @@ async fn test_a_route_marked_by_someone_else_is_a_leak_from_a_peer() -> Result<(
#[tokio::test] #[tokio::test]
async fn test_routes_from_a_provider_are_marked_on_the_way_in() -> Result<()> { async fn test_routes_from_a_provider_are_marked_on_the_way_in() -> Result<()> {
for four_octet in [true, false] { for four_octet in [true, false] {
let Setup { server, .. } = server_with(Some(PeerRole::Provider), false).await?; let ServerWithPeer { server, .. } = server_with(Some(PeerRole::Provider), false).await?;
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = TestPeer::connect(server.addr).await?;
let mut offered = capabilities(Some(BgpRole::Provider)); let mut offered = capabilities(Some(BgpRole::Provider));
if !four_octet { if !four_octet {
offered.retain(|capability| !matches!(capability, Capability::FourByteAsn { .. })); offered.retain(|capability| !matches!(capability, Capability::FourByteAsn { .. }));
} }
peer.handshake_with(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90, offered) peer.handshake_with(PEER_ASN, PEER_ID, 90, offered).await?;
.await?;
announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN, 2121], None).await?; announce(&mut peer, &["194.0.0.0/24"], &[PEER_ASN, 2121], None).await?;
// One that someone further up has marked already keeps that mark. // One that someone further up has marked already keeps that mark.
@@ -387,9 +308,9 @@ async fn test_our_routes_are_marked_on_the_way_down_or_across() -> Result<()> {
(PeerRole::Provider, BgpRole::Provider, false), (PeerRole::Provider, BgpRole::Provider, false),
(PeerRole::RouteServer, BgpRole::RouteServer, false), (PeerRole::RouteServer, BgpRole::RouteServer, false),
] { ] {
let Setup { server, .. } = server_with(Some(peer_is), false).await?; let ServerWithPeer { server, .. } = server_with(Some(peer_is), false).await?;
let mut peer = established(&server, Some(says)).await?; let mut peer = established(&server, Some(says)).await?;
let update = tokio::time::timeout(TIMEOUT, peer.recv_update()).await??; let update = peer.recv_update().await?;
assert_eq!(update.announced_routes, [prefix("193.0.0.0/21")]); assert_eq!(update.announced_routes, [prefix("193.0.0.0/21")]);
let mark = update let mark = update
.path_attributes .path_attributes
@@ -401,9 +322,9 @@ async fn test_our_routes_are_marked_on_the_way_down_or_across() -> Result<()> {
assert_eq!(mark, marked.then_some(LOCAL_ASN), "to our {peer_is:?}"); assert_eq!(mark, marked.then_some(LOCAL_ASN), "to our {peer_is:?}");
} }
// Nothing is said to a peer whose role is not known. // Nothing is said to a peer whose role is not known.
let Setup { server, .. } = server_with(None, false).await?; let ServerWithPeer { server, .. } = server_with(None, false).await?;
let mut peer = established(&server, None).await?; let mut peer = established(&server, None).await?;
let update = tokio::time::timeout(TIMEOUT, peer.recv_update()).await??; let update = peer.recv_update().await?;
assert!( assert!(
!update !update
.path_attributes .path_attributes
+49 -165
View File
@@ -2,34 +2,23 @@
//! cache, a peer that announces routes, and the validation data changing //! cache, a peer that announces routes, and the validation data changing
//! under both. //! under both.
use std::collections::BTreeSet;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::default_policy; use bgp_integration_tests::announcement::{announcement, withdrawal};
use bgp_integration_tests::test_cache::TestCache; use bgp_integration_tests::test_cache::TestCache;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::{ServerWithPeer, TestServer};
use bgp_packet::message::{ use bgp_integration_tests::{default_policy, prefix, wait_until};
AsPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute, PathAttribute, use bgp_packet::message::{AsPathAttribute, PathAttribute};
UpdateMessage,
};
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rpki::{CacheState, RpkiConfig, Vrp}; use bgp_rpki::{CacheState, RpkiConfig, Vrp};
use bgp_server::config::PeerConfig;
use bgp_server::family::FamilySet;
use bgp_server::policy::ImportPolicy; use bgp_server::policy::ImportPolicy;
use eyre::Result; use eyre::Result;
use serde_json::Value; use serde_json::Value;
use tokio::net::TcpListener;
const LOCAL_ASN: u32 = 1299; const LOCAL_ASN: u32 = 1299;
const PEER_ASN: u32 = 3333; const PEER_ASN: u32 = 3333;
const TIMEOUT: Duration = Duration::from_secs(5); const PEER_ID: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
fn vrp(p: &str, max_length: u8, asn: u32) -> Vrp { fn vrp(p: &str, max_length: u8, asn: u32) -> Vrp {
Vrp { Vrp {
@@ -39,13 +28,6 @@ fn vrp(p: &str, max_length: u8, asn: u32) -> Vrp {
} }
} }
/// What a test holds on to: the server, and the listener its dialling
/// goes to.
struct Setup {
server: TestServer,
_listener: TcpListener,
}
/// A server that validates against `cache`, with one configured peer at /// A server that validates against `cache`, with one configured peer at
/// the address the test connects from. Returns once the server has the /// the address the test connects from. Returns once the server has the
/// cache's data. /// cache's data.
@@ -53,12 +35,9 @@ async fn server_with(
cache: &TestCache, cache: &TestCache,
expire: Option<Duration>, expire: Option<Duration>,
import: ImportPolicy, import: ImportPolicy,
) -> Result<Setup> { ) -> Result<ServerWithPeer> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?;
let endpoint = listener.local_addr()?;
let cache = cache.address(); let cache = cache.address();
let server = TestServer::start_with(LOCAL_ASN, |c| { let setup = TestServer::with_peer(LOCAL_ASN, PEER_ASN, |c, peer| {
c.accept_unconfigured_peers = false;
c.rpki = Some(RpkiConfig { c.rpki = Some(RpkiConfig {
caches: vec![cache], caches: vec![cache],
// Data never expires sooner than a refresh and a retry take. // Data never expires sooner than a refresh and a retry take.
@@ -66,50 +45,19 @@ async fn server_with(
retry: Some(Duration::from_millis(100)), retry: Some(Duration::from_millis(100)),
expire, expire,
}); });
c.peers = vec![PeerConfig { peer.import = import;
graceful_restart: Default::default(),
groups: Vec::new(),
export_limits: None,
passive: false,
reflector_client: false,
name: "r1".into(),
endpoint: endpoint.into(),
remote_asn: PEER_ASN,
hold_time_secs: 90,
local_address: None,
families: FamilySet::supported(),
add_path: None,
import,
role: None,
require_role: false,
export: Default::default(),
}];
}) })
.await?; .await?;
let rpki = server.rpki.clone().expect("validation is configured"); let rpki = setup.server.rpki.clone().expect("validation is configured");
tokio::time::timeout(TIMEOUT, async { wait_until("the server has the cache's data", || {
while rpki.status().state != CacheState::Synchronised { rpki.status().state == CacheState::Synchronised
tokio::time::sleep(Duration::from_millis(5)).await;
}
}) })
.await?; .await?;
Ok(Setup { Ok(setup)
server,
_listener: listener,
})
} }
async fn established(server: &TestServer) -> Result<TestPeer> { async fn established(server: &TestServer) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?; server.established(PEER_ASN, PEER_ID).await
peer.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(5)).await;
}
})
.await?;
Ok(peer)
} }
/// Announces `prefixes` as originated by `origin`, reached through the /// Announces `prefixes` as originated by `origin`, reached through the
@@ -120,53 +68,7 @@ async fn announce(peer: &mut TestPeer, prefixes: &[&str], origin: u32) -> Result
} else { } else {
vec![PEER_ASN, origin] vec![PEER_ASN, origin]
}; };
peer.send(&Message::Update(UpdateMessage { peer.announce(announcement(prefixes, &path)).await
withdrawn_routes: vec![],
path_attributes: vec![
PathAttribute::Origin(OriginPathAttribute::IGP),
AsPathAttribute::from_asns(path),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
],
announced_routes: prefixes.iter().map(|p| prefix(p).into()).collect(),
}))
.await
}
fn set(prefixes: &[&str]) -> BTreeSet<String> {
prefixes.iter().map(|p| p.to_string()).collect()
}
/// Waits until the table holds exactly `expected` and the peer has `held`
/// routes held aside.
async fn table_becomes(server: &TestServer, expected: &[&str], held: u64) -> Result<()> {
let expected = set(expected);
let table = || -> BTreeSet<String> {
server
.received_routes()
.iter()
.map(|route| route.prefix.to_string())
.collect()
};
let held_now = || {
server
.registry
.list()
.first()
.map_or(0, |session| session.counters.held())
};
tokio::time::timeout(TIMEOUT, async {
while table() != expected || held_now() != held {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await
.map_err(|_| {
eyre::eyre!(
"the table has {:?} with {} held; expected {expected:?} with {held} held",
table(),
held_now()
)
})
} }
/// A route whose origin is not the one the holder of the addresses /// A route whose origin is not the one the holder of the addresses
@@ -175,7 +77,7 @@ async fn table_becomes(server: &TestServer, expected: &[&str], held: u64) -> Res
#[tokio::test] #[tokio::test]
async fn test_an_invalid_route_is_refused_and_the_rest_are_taken() -> Result<()> { async fn test_an_invalid_route_is_refused_and_the_rest_are_taken() -> Result<()> {
let cache = TestCache::start(vec![vrp("193.0.0.0/21", 24, PEER_ASN)]).await; let cache = TestCache::start(vec![vrp("193.0.0.0/21", 24, PEER_ASN)]).await;
let Setup { server, .. } = server_with(&cache, None, default_policy()).await?; let ServerWithPeer { server, .. } = server_with(&cache, None, default_policy()).await?;
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
@@ -184,7 +86,9 @@ async fn test_an_invalid_route_is_refused_and_the_rest_are_taken() -> Result<()>
// AS, more specific than it may announce. // AS, more specific than it may announce.
announce(&mut peer, &["193.0.4.0/24"], 15169).await?; announce(&mut peer, &["193.0.4.0/24"], 15169).await?;
announce(&mut peer, &["193.0.6.0/25"], PEER_ASN).await?; announce(&mut peer, &["193.0.6.0/25"], PEER_ASN).await?;
table_becomes(&server, &["193.0.0.0/21", "194.0.0.0/24"], 1).await?; server
.table_becomes(&["193.0.0.0/21", "194.0.0.0/24"], 1)
.await?;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let get = |path: &str| { let get = |path: &str| {
@@ -230,18 +134,18 @@ async fn test_an_invalid_route_is_refused_and_the_rest_are_taken() -> Result<()>
async fn test_routes_follow_the_validation_data() -> Result<()> { async fn test_routes_follow_the_validation_data() -> Result<()> {
let ripe = vrp("193.0.0.0/21", 24, PEER_ASN); let ripe = vrp("193.0.0.0/21", 24, PEER_ASN);
let cache = TestCache::start(vec![ripe]).await; let cache = TestCache::start(vec![ripe]).await;
let Setup { server, .. } = server_with(&cache, None, default_policy()).await?; let ServerWithPeer { server, .. } = server_with(&cache, None, default_policy()).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
// Nothing is known about 8.8.8.0/24, so it is taken. 193.0.4.0/24 is // Nothing is known about 8.8.8.0/24, so it is taken. 193.0.4.0/24 is
// covered, and not for this origin. // covered, and not for this origin.
announce(&mut peer, &["8.8.8.0/24", "193.0.4.0/24"], 15169).await?; announce(&mut peer, &["8.8.8.0/24", "193.0.4.0/24"], 15169).await?;
table_becomes(&server, &["8.8.8.0/24"], 1).await?; server.table_becomes(&["8.8.8.0/24"], 1).await?;
// The holder of 8.8.8.0/24 authorises another AS: the route in the // The holder of 8.8.8.0/24 authorises another AS: the route in the
// table is now invalid, and goes. // table is now invalid, and goes.
cache.set(vec![ripe, vrp("8.8.8.0/24", 24, 13335)]); cache.set(vec![ripe, vrp("8.8.8.0/24", 24, 13335)]);
table_becomes(&server, &[], 2).await?; server.table_becomes(&[], 2).await?;
let refused = &server.refusals.peers()[0].1; let refused = &server.refusals.peers()[0].1;
assert_eq!(refused.total(), 2); assert_eq!(refused.total(), 2);
assert_eq!(refused.recent()[0].prefix, prefix("8.8.8.0/24")); assert_eq!(refused.recent()[0].prefix, prefix("8.8.8.0/24"));
@@ -254,7 +158,9 @@ async fn test_routes_follow_the_validation_data() -> Result<()> {
vrp("8.8.8.0/24", 24, 15169), vrp("8.8.8.0/24", 24, 15169),
vrp("193.0.4.0/24", 24, 15169), vrp("193.0.4.0/24", 24, 15169),
]); ]);
table_becomes(&server, &["8.8.8.0/24", "193.0.4.0/24"], 0).await?; server
.table_becomes(&["8.8.8.0/24", "193.0.4.0/24"], 0)
.await?;
for route in server.received_routes() { for route in server.received_routes() {
assert!( assert!(
route route
@@ -273,27 +179,23 @@ async fn test_routes_follow_the_validation_data() -> Result<()> {
async fn test_a_held_route_the_peer_withdrew_does_not_come_back() -> Result<()> { async fn test_a_held_route_the_peer_withdrew_does_not_come_back() -> Result<()> {
let ripe = vrp("193.0.0.0/21", 24, PEER_ASN); let ripe = vrp("193.0.0.0/21", 24, PEER_ASN);
let cache = TestCache::start(vec![ripe]).await; let cache = TestCache::start(vec![ripe]).await;
let Setup { server, .. } = server_with(&cache, None, default_policy()).await?; let ServerWithPeer { server, .. } = server_with(&cache, None, default_policy()).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce(&mut peer, &["193.0.4.0/24", "193.0.5.0/24"], 15169).await?; announce(&mut peer, &["193.0.4.0/24", "193.0.5.0/24"], 15169).await?;
table_becomes(&server, &[], 2).await?; server.table_becomes(&[], 2).await?;
// One is withdrawn, the other announced again with the right origin. // One is withdrawn, the other announced again with the right origin.
peer.send(&Message::Update(UpdateMessage { peer.announce(withdrawal([prefix("193.0.4.0/24").into()]))
withdrawn_routes: vec![prefix("193.0.4.0/24").into()], .await?;
path_attributes: vec![],
announced_routes: vec![],
}))
.await?;
announce(&mut peer, &["193.0.5.0/24"], PEER_ASN).await?; announce(&mut peer, &["193.0.5.0/24"], PEER_ASN).await?;
table_becomes(&server, &["193.0.5.0/24"], 0).await?; server.table_becomes(&["193.0.5.0/24"], 0).await?;
// Now the old origin would be valid for both. Neither old route is // Now the old origin would be valid for both. Neither old route is
// there to be brought back. // there to be brought back.
cache.set(vec![ripe, vrp("193.0.4.0/23", 24, 15169)]); cache.set(vec![ripe, vrp("193.0.4.0/23", 24, 15169)]);
tokio::time::sleep(Duration::from_millis(500)).await; tokio::time::sleep(Duration::from_millis(500)).await;
table_becomes(&server, &["193.0.5.0/24"], 0).await?; server.table_becomes(&["193.0.5.0/24"], 0).await?;
assert!( assert!(
server.received_routes()[0] server.received_routes()[0]
.attributes .attributes
@@ -312,12 +214,12 @@ async fn test_a_peer_can_be_exempt_from_validation() -> Result<()> {
accept_rpki_invalid: true, accept_rpki_invalid: true,
..default_policy() ..default_policy()
}; };
let Setup { server, .. } = server_with(&cache, None, import).await?; let ServerWithPeer { server, .. } = server_with(&cache, None, import).await?;
let (base, _http) = server.serve_http().await?; let (base, _http) = server.serve_http().await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce(&mut peer, &["193.0.4.0/24"], 15169).await?; announce(&mut peer, &["193.0.4.0/24"], 15169).await?;
table_becomes(&server, &["193.0.4.0/24"], 0).await?; server.table_becomes(&["193.0.4.0/24"], 0).await?;
let routes: Value = reqwest::get(format!("{base}/api/routes/received")) let routes: Value = reqwest::get(format!("{base}/api/routes/received"))
.await? .await?
.json() .json()
@@ -333,14 +235,14 @@ async fn test_a_peer_can_be_exempt_from_validation() -> Result<()> {
#[tokio::test] #[tokio::test]
async fn test_held_routes_are_taken_when_the_validation_data_expires() -> Result<()> { async fn test_held_routes_are_taken_when_the_validation_data_expires() -> Result<()> {
let cache = TestCache::start(vec![vrp("193.0.0.0/21", 24, PEER_ASN)]).await; let cache = TestCache::start(vec![vrp("193.0.0.0/21", 24, PEER_ASN)]).await;
let Setup { server, .. } = let ServerWithPeer { server, .. } =
server_with(&cache, Some(Duration::from_millis(1500)), default_policy()).await?; server_with(&cache, Some(Duration::from_millis(1500)), default_policy()).await?;
let mut peer = established(&server).await?; let mut peer = established(&server).await?;
announce(&mut peer, &["193.0.4.0/24"], 15169).await?; announce(&mut peer, &["193.0.4.0/24"], 15169).await?;
table_becomes(&server, &[], 1).await?; server.table_becomes(&[], 1).await?;
drop(cache); drop(cache);
table_becomes(&server, &["193.0.4.0/24"], 0).await?; server.table_becomes(&["193.0.4.0/24"], 0).await?;
assert!(server.rpki.as_ref().unwrap().current().vrps.is_empty()); assert!(server.rpki.as_ref().unwrap().current().vrps.is_empty());
Ok(()) Ok(())
} }
@@ -356,37 +258,21 @@ async fn test_an_unused_as4_path_does_not_make_a_route_valid() -> Result<()> {
use bgp_packet::message::AsPathSegment; use bgp_packet::message::AsPathSegment;
const HOLDER: u32 = 15169; const HOLDER: u32 = 15169;
let cache = TestCache::start(vec![vrp("193.0.0.0/21", 24, HOLDER)]).await; let cache = TestCache::start(vec![vrp("193.0.0.0/21", 24, HOLDER)]).await;
let Setup { server, .. } = server_with(&cache, None, default_policy()).await?; let ServerWithPeer { server, .. } = server_with(&cache, None, default_policy()).await?;
// No capabilities: AS numbers are two octets wide on this session. // No capabilities: AS numbers are two octets wide on this session.
let mut peer = TestPeer::connect(server.addr).await?; let mut peer = server.established_with(PEER_ASN, PEER_ID, vec![]).await?;
peer.handshake_with(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90, vec![])
.await?;
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().is_empty() {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
assert!(!server.registry.list()[0].four_octet_asn); assert!(!server.registry.list()[0].four_octet_asn);
peer.send(&Message::Update(UpdateMessage { let as4_path = PathAttribute::As4Path(AsPathAttribute {
withdrawn_routes: vec![], segments: vec![AsPathSegment {
path_attributes: vec![ ordered: true,
PathAttribute::Origin(OriginPathAttribute::IGP), path: vec![3356, HOLDER],
AsPathAttribute::from_asns(vec![PEER_ASN]), }],
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))), });
PathAttribute::As4Path(AsPathAttribute { peer.announce(announcement(&["193.0.4.0/24"], &[PEER_ASN]).with_attributes([as4_path]))
segments: vec![AsPathSegment { .await?;
ordered: true, server.table_becomes(&[], 1).await?;
path: vec![3356, HOLDER],
}],
}),
],
announced_routes: vec![prefix("193.0.4.0/24").into()],
}))
.await?;
table_becomes(&server, &[], 1).await?;
// The data changes, and every held route is looked at again. // The data changes, and every held route is looked at again.
cache.set(vec![ cache.set(vec![
@@ -394,14 +280,12 @@ async fn test_an_unused_as4_path_does_not_make_a_route_valid() -> Result<()> {
vrp("198.51.100.0/24", 24, 64500), vrp("198.51.100.0/24", 24, 64500),
]); ]);
let rpki = server.rpki.clone().unwrap(); let rpki = server.rpki.clone().unwrap();
tokio::time::timeout(TIMEOUT, async { wait_until("the server has the new data", || {
while rpki.current().vrps.len() != 2 { rpki.current().vrps.len() == 2
tokio::time::sleep(Duration::from_millis(5)).await;
}
}) })
.await?; .await?;
// Time enough for the look, and then some. // Time enough for the look, and then some.
tokio::time::sleep(Duration::from_millis(600)).await; tokio::time::sleep(Duration::from_millis(600)).await;
table_becomes(&server, &[], 1).await?; server.table_becomes(&[], 1).await?;
Ok(()) Ok(())
} }
+18 -53
View File
@@ -6,25 +6,21 @@ use std::net::Ipv4Addr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use bgp_integration_tests::announcement::{announcement, withdrawal};
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::TestServer; use bgp_integration_tests::test_server::TestServer;
use bgp_integration_tests::{TIMEOUT, prefix};
use bgp_packet::message::{ use bgp_packet::message::{
AsPathAttribute, CommunitiesPathAttribute, Message, NextHopPathAttribute, OriginPathAttribute, CommunitiesPathAttribute, Message, NextHopPathAttribute, PathAttribute, RouteRefreshMessage,
PathAttribute, RouteRefreshMessage, UpdateMessage, UpdateMessage,
}; };
use bgp_primitives::constants::AfiSafi; use bgp_primitives::constants::AfiSafi;
use bgp_primitives::ip_prefix::IpPrefix;
use bgp_rib::{LocalRoute, LocalRouteAttrs}; use bgp_rib::{LocalRoute, LocalRouteAttrs};
use bgp_server::marks::{Control, Marks}; use bgp_server::marks::{Control, Marks};
use bgp_server::policy::ExportPolicy; use bgp_server::policy::ExportPolicy;
use eyre::Result; use eyre::Result;
const LOCAL_ASN: u32 = 64601; const LOCAL_ASN: u32 = 64601;
const TIMEOUT: Duration = Duration::from_secs(5);
fn prefix(s: &str) -> IpPrefix {
IpPrefix::try_from(s).unwrap()
}
/// A server that passes on to every peer what it learned from the others. /// A server that passes on to every peer what it learned from the others.
async fn transit(customize: impl FnOnce(&mut bgp_server::config::Config)) -> Result<TestServer> { async fn transit(customize: impl FnOnce(&mut bgp_server::config::Config)) -> Result<TestServer> {
@@ -40,17 +36,9 @@ async fn transit(customize: impl FnOnce(&mut bgp_server::config::Config)) -> Res
/// The peer with AS `64700 + n`, its session up. /// The peer with AS `64700 + n`, its session up.
async fn peer(server: &TestServer, n: u8) -> Result<TestPeer> { async fn peer(server: &TestServer, n: u8) -> Result<TestPeer> {
let before = server.registry.list().len(); server
let mut peer = TestPeer::connect(server.addr).await?; .established(64700 + u32::from(n), Ipv4Addr::new(10, 0, 0, n))
peer.handshake(64700 + u32::from(n), Ipv4Addr::new(10, 0, 0, n), 90) .await
.await?;
tokio::time::timeout(TIMEOUT, async {
while server.registry.list().len() != before + 1 {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
Ok(peer)
} }
async fn announce( async fn announce(
@@ -59,31 +47,13 @@ async fn announce(
path: &[u32], path: &[u32],
communities: &[(u16, u16)], communities: &[(u16, u16)],
) -> Result<()> { ) -> Result<()> {
let mut path_attributes = vec![ peer.announce(announcement(prefixes, path).with_communities(communities))
PathAttribute::Origin(OriginPathAttribute::IGP), .await
AsPathAttribute::from_asns(path.to_vec()),
PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))),
];
if !communities.is_empty() {
path_attributes.push(PathAttribute::Communities(CommunitiesPathAttribute(
communities.to_vec(),
)));
}
peer.send(&Message::Update(UpdateMessage {
withdrawn_routes: vec![],
path_attributes,
announced_routes: prefixes.iter().map(|p| prefix(p).into()).collect(),
}))
.await
} }
async fn withdraw(peer: &mut TestPeer, prefixes: &[&str]) -> Result<()> { async fn withdraw(peer: &mut TestPeer, prefixes: &[&str]) -> Result<()> {
peer.send(&Message::Update(UpdateMessage { peer.announce(withdrawal(prefixes.iter().map(|p| prefix(p).into())))
withdrawn_routes: prefixes.iter().map(|p| prefix(p).into()).collect(), .await
path_attributes: vec![],
announced_routes: vec![],
}))
.await
} }
/// What a peer has been told so far, as the prefixes it would now have /// What a peer has been told so far, as the prefixes it would now have
@@ -180,7 +150,7 @@ async fn test_routes_from_one_peer_reach_the_others() -> Result<()> {
// The next hop is ourselves: the address of our end of the session. // The next hop is ourselves: the address of our end of the session.
announce(&mut one, &["10.4.0.0/16"], &[64701], &[]).await?; announce(&mut one, &["10.4.0.0/16"], &[64701], &[]).await?;
let update = tokio::time::timeout(TIMEOUT, two.recv_update()).await??; let update = two.recv_update().await?;
assert!( assert!(
update update
.path_attributes .path_attributes
@@ -220,7 +190,7 @@ async fn test_a_peer_is_kept_at_the_best_route() -> Result<()> {
// A better route for it comes: that is the one told now. // A better route for it comes: that is the one told now.
announce(&mut near, &["10.1.0.0/16"], &[64701, 65001], &[]).await?; announce(&mut near, &["10.1.0.0/16"], &[64701, 65001], &[]).await?;
let update = tokio::time::timeout(TIMEOUT, watcher.recv_update()).await??; let update = watcher.recv_update().await?;
told.take(&update); told.take(&update);
assert_eq!(told.routes["10.1.0.0/16"], [LOCAL_ASN, 64701, 65001]); assert_eq!(told.routes["10.1.0.0/16"], [LOCAL_ASN, 64701, 65001]);
assert!( assert!(
@@ -230,7 +200,7 @@ async fn test_a_peer_is_kept_at_the_best_route() -> Result<()> {
// And goes again: back to the other, with no gap. // And goes again: back to the other, with no gap.
withdraw(&mut near, &["10.1.0.0/16"]).await?; withdraw(&mut near, &["10.1.0.0/16"]).await?;
let update = tokio::time::timeout(TIMEOUT, watcher.recv_update()).await??; let update = watcher.recv_update().await?;
told.take(&update); told.take(&update);
assert_eq!(told.routes["10.1.0.0/16"], [LOCAL_ASN, 64702, 65000, 65001]); assert_eq!(told.routes["10.1.0.0/16"], [LOCAL_ASN, 64702, 65000, 65001]);
@@ -239,7 +209,7 @@ async fn test_a_peer_is_kept_at_the_best_route() -> Result<()> {
// told of. // told of.
let mut told_far = Told::default(); let mut told_far = Told::default();
told_becomes(&mut far, &mut told_far, &["10.1.0.0/16"]).await?; told_becomes(&mut far, &mut told_far, &["10.1.0.0/16"]).await?;
let gone = tokio::time::timeout(TIMEOUT, far.recv_update()).await??; let gone = far.recv_update().await?;
assert_eq!(gone.withdrawn_routes, vec![prefix("10.1.0.0/16")]); assert_eq!(gone.withdrawn_routes, vec![prefix("10.1.0.0/16")]);
told_nothing(&mut far).await; told_nothing(&mut far).await;
@@ -251,7 +221,7 @@ async fn test_a_peer_is_kept_at_the_best_route() -> Result<()> {
AfiSafi::IPV4_UNICAST, AfiSafi::IPV4_UNICAST,
))) )))
.await?; .await?;
let again = tokio::time::timeout(TIMEOUT, late.recv_update()).await??; let again = late.recv_update().await?;
assert_eq!(again.announced_routes, vec![prefix("10.1.0.0/16")]); assert_eq!(again.announced_routes, vec![prefix("10.1.0.0/16")]);
Ok(()) Ok(())
} }
@@ -349,7 +319,7 @@ async fn test_communities_say_who_is_told() -> Result<()> {
&[(0, 64702), (3356, 3)], &[(0, 64702), (3356, 3)],
) )
.await?; .await?;
let update = tokio::time::timeout(TIMEOUT, three.recv_update()).await??; let update = three.recv_update().await?;
assert!( assert!(
update update
.path_attributes .path_attributes
@@ -378,12 +348,7 @@ async fn test_nothing_learned_is_passed_on_unless_asked_for() -> Result<()> {
let mut told = Told::default(); let mut told = Told::default();
told_becomes(&mut two, &mut told, &["10.99.0.0/16"]).await?; told_becomes(&mut two, &mut told, &["10.99.0.0/16"]).await?;
announce(&mut one, &["10.1.0.0/16"], &[64701], &[]).await?; announce(&mut one, &["10.1.0.0/16"], &[64701], &[]).await?;
tokio::time::timeout(TIMEOUT, async { server.wait_for_routes(1).await?;
while server.rib.len() != 1 {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
told_nothing(&mut two).await; told_nothing(&mut two).await;
Ok(()) Ok(())
} }
+19 -40
View File
@@ -2,19 +2,18 @@
//! cost the routes in the UPDATE, not the session. //! cost the routes in the UPDATE, not the session.
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration;
use bgp_integration_tests::test_peer::TestPeer; use bgp_integration_tests::test_peer::TestPeer;
use bgp_integration_tests::test_server::{ReceivedRoute, TestServer}; use bgp_integration_tests::test_server::{ReceivedRoute, TestServer};
use bgp_integration_tests::{prefix, wait_for};
use bgp_packet::errors::{BgpError, UpdateMsgSubcode}; use bgp_packet::errors::{BgpError, UpdateMsgSubcode};
use bgp_packet::message::{LocalPrefPathAttribute, Message, PathAttribute}; use bgp_packet::message::{LocalPrefPathAttribute, PathAttribute};
use bgp_primitives::ip_prefix::IpPrefix; use bgp_primitives::ip_prefix::IpPrefix;
use bgp_server::metrics::UPDATE_ERRORS; use bgp_server::metrics::UPDATE_ERRORS;
use eyre::{Result, bail}; use eyre::Result;
const LOCAL_ASN: u32 = 65000; const LOCAL_ASN: u32 = 65000;
const PEER_ASN: u32 = 65001; const PEER_ASN: u32 = 65001;
const TIMEOUT: Duration = Duration::from_secs(5);
const WELL_KNOWN: u8 = 0x40; const WELL_KNOWN: u8 = 0x40;
const OPTIONAL_TRANSITIVE: u8 = 0xc0; const OPTIONAL_TRANSITIVE: u8 = 0xc0;
@@ -58,31 +57,24 @@ fn update(attributes: &[Vec<u8>], nlri: &[u8]) -> Vec<u8> {
const ROUTE_A: [u8; 3] = [16, 10, 1]; const ROUTE_A: [u8; 3] = [16, 10, 1];
const ROUTE_B: [u8; 3] = [16, 10, 2]; 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<TestPeer> { async fn established(server: &TestServer, asn: u32) -> Result<TestPeer> {
let mut peer = TestPeer::connect(server.addr).await?; server.established(asn, Ipv4Addr::new(10, 0, 0, 1)).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`. /// Waits until the server's Adj-RIB-In holds exactly `prefixes`.
async fn wait_for_routes(server: &TestServer, prefixes: &[&str]) -> Result<Vec<ReceivedRoute>> { async fn wait_for_routes(server: &TestServer, prefixes: &[&str]) -> Result<Vec<ReceivedRoute>> {
let mut expected: Vec<IpPrefix> = prefixes.iter().map(|p| prefix(p)).collect(); let mut expected: Vec<IpPrefix> = prefixes.iter().map(|p| prefix(p)).collect();
expected.sort_by_key(|p| p.to_string()); expected.sort_by_key(|p| p.to_string());
Ok(tokio::time::timeout(TIMEOUT, async { wait_for(&format!("the table holds {prefixes:?}"), || {
loop { let mut routes = server.received_routes();
let mut routes = server.received_routes(); routes.sort_by_key(|r| r.prefix.to_string());
routes.sort_by_key(|r| r.prefix.to_string()); routes
if routes.iter().map(|r| &r.prefix).eq(expected.iter()) { .iter()
return routes; .map(|r| &r.prefix)
} .eq(expected.iter())
tokio::time::sleep(Duration::from_millis(10)).await; .then_some(routes)
}
}) })
.await?) .await
} }
/// The situation RFC 7606 exists for: a route is in the table, and then an /// The situation RFC 7606 exists for: a route is in the table, and then an
@@ -205,24 +197,11 @@ async fn test_an_unreadable_prefix_still_resets_the_session() -> Result<()> {
)) ))
.await?; .await?;
loop { let notification = peer.expect_notification().await?;
match tokio::time::timeout(TIMEOUT, peer.recv()).await?? { assert_eq!(
Message::KeepAlive => {} notification.error,
Message::Notification(n) => { BgpError::UpdateMsg(UpdateMsgSubcode::InvalidNetworkField)
assert_eq!( );
n.error, server.wait_for_sessions(0).await?;
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(()) Ok(())
} }