Control communities could say which peers a route goes to, by AS. Now
they can also name sessions by group, and ask for prepends:
L:11:P .. L:13:P our AS once to three times more towards AS P
(P 0: towards every peer in another AS)
L:20:G / L:21:G not to / only to the sessions of group G
L:31:G .. L:33:G our AS once to three times more towards group G
A group is a number given to sessions in the configuration (`groups` of
a peer): those at one exchange, or those over one link. The same network
at two exchanges is one AS but two sessions, which is what steering
inbound traffic between exchanges needs. Of several prepends that apply
the most counts, on top of the session's own as_path_prepend. Functions
2 to 9, as the documentation already promised, and 100 and up stay
informational; 10 to 99 are kept for actions to come.
They are acted on where a route leaves our AS, and taken off there; our
informational communities are not. To a peer in our own AS a route goes
with them, unchanged, so that a mark put on at one router is acted on at
another. Before, they were acted on and taken off on internal sessions
too, so a route marked "to nobody" did not even reach our own routers.
And they are ours to give: the communities of our own AS (L:n, L:n:m,
and 0:n where control communities are on) are taken off every route
from a peer in another AS as it comes in, before import marks are put
on. A peer at an exchange could otherwise decide where its routes go
from us, and set our informational marks. A customer that is to steer
its own routes is given import.accept_control_communities. An UPDATE
without any of ours is still taken as it came, without being written
out again.
This changes what a customer that set control communities gets: it now
needs accept_control_communities. The transit example and the interop
lab say so; the lab also has FRR, as a customer, ask for a prepend
towards BIRD's group, and BIRD, as a provider, have its "not to" taken
off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
3.9 KiB
Rust
117 lines
3.9 KiB
Rust
//! Who may connect: only the addresses of configured peers, and each of
|
|
//! them only so many times at once.
|
|
|
|
use std::net::{Ipv4Addr, SocketAddr};
|
|
use std::time::Duration;
|
|
|
|
use bgp_integration_tests::test_peer::TestPeer;
|
|
use bgp_integration_tests::test_server::TestServer;
|
|
use bgp_server::config::PeerConfig;
|
|
use bgp_server::family::FamilySet;
|
|
use bgp_server::policy::ImportPolicy;
|
|
use eyre::Result;
|
|
use tokio::net::TcpListener;
|
|
|
|
const LOCAL_ASN: u32 = 65000;
|
|
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(),
|
|
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(),
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
/// no session, no routes taken from it. Any host used to be able to open a
|
|
/// session as any AS it named.
|
|
#[tokio::test]
|
|
async fn test_a_host_that_is_not_a_configured_peer_is_refused() -> Result<()> {
|
|
// The only peer configured is somewhere else (documentation space).
|
|
let server = TestServer::start_with(LOCAL_ASN, |c| {
|
|
c.accept_unconfigured_peers = false;
|
|
c.peers = vec![peer_at("192.0.2.9:179".parse().unwrap())];
|
|
})
|
|
.await?;
|
|
|
|
let mut stranger = TestPeer::connect(server.addr).await?;
|
|
assert!(closed_unanswered(&mut stranger).await);
|
|
assert!(server.registry.list().is_empty());
|
|
Ok(())
|
|
}
|
|
|
|
/// The address of a configured peer is let in, and held to what is
|
|
/// configured for that peer.
|
|
#[tokio::test]
|
|
async fn test_a_configured_peer_is_let_in() -> Result<()> {
|
|
// Somewhere for the server's own dialling to go; never answered.
|
|
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?;
|
|
let endpoint = listener.local_addr()?;
|
|
let server = TestServer::start_with(LOCAL_ASN, |c| {
|
|
c.accept_unconfigured_peers = false;
|
|
c.peers = vec![peer_at(endpoint)];
|
|
})
|
|
.await?;
|
|
|
|
let mut peer = TestPeer::connect(server.addr).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(10)).await;
|
|
}
|
|
})
|
|
.await?;
|
|
assert_eq!(server.registry.list()[0].name.as_deref(), Some("r1"));
|
|
Ok(())
|
|
}
|
|
|
|
/// One address may hold only a few connections at a time, so that one host
|
|
/// cannot use up the server's sockets with connections that never get as
|
|
/// far as a session.
|
|
#[tokio::test]
|
|
async fn test_one_address_may_open_only_a_few_connections() -> Result<()> {
|
|
let server = TestServer::start(LOCAL_ASN).await?;
|
|
|
|
// Four that say nothing: each is sent the server's OPEN and waited on.
|
|
let mut held = Vec::new();
|
|
for _ in 0..4 {
|
|
let mut peer = TestPeer::connect(server.addr).await?;
|
|
tokio::time::timeout(TIMEOUT, peer.recv_open()).await??;
|
|
held.push(peer);
|
|
}
|
|
let mut one_too_many = TestPeer::connect(server.addr).await?;
|
|
assert!(closed_unanswered(&mut one_too_many).await);
|
|
|
|
// When one of them goes, there is room again.
|
|
held.pop();
|
|
tokio::time::timeout(TIMEOUT, async {
|
|
loop {
|
|
let mut peer = TestPeer::connect(server.addr).await?;
|
|
if peer.recv_open().await.is_ok() {
|
|
return Ok::<_, eyre::Report>(());
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
|
}
|
|
})
|
|
.await??;
|
|
Ok(())
|
|
}
|