Check roles in OPEN and prevent route leaks with Only to Customer (RFC 9234)
A peer's configured role now does three more things: - Our OPEN carries the BGP Role capability with what we are to the peer. A peer whose own says it is something that does not pair with that, or that sends two different roles, is refused with OPEN Message Error / Role Mismatch. A peer that says nothing is accepted unless require_role is set. A role on a peer in our own AS is a configuration error. - Routes from a provider, a peer or a route server are marked Only to Customer on the way in if they are not already, and our routes are marked on the way out to a customer, a peer or a route server client. - A marked route from a customer or route server client, or from a peer with someone else's mark, is a leak and is refused. No policy setting turns that off. The packet crate gains the Only to Customer attribute (malformed: treat-as-withdraw), and the Role Mismatch subcode is corrected from 8 to 11. The status page shows whether the peer confirmed its role. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
8086aa994f
commit
f9ef781d51
30 files changed
+1128
-25
No files matched your search
@@ -66,6 +66,9 @@ pub struct PeerConfig {
|
||||
pub import: ImportPolicy,
|
||||
/// What the peer is to us, if that was said.
|
||||
pub role: Option<PeerRole>,
|
||||
/// Refuse the peer if it does not say what it is itself (RFC 9234
|
||||
/// §4.2, "strict mode").
|
||||
pub require_role: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -109,6 +112,10 @@ impl Config {
|
||||
problem,
|
||||
};
|
||||
let peer = parse_peer(peer).map_err(rejected)?;
|
||||
// RFC 9234 is about what networks are to each other.
|
||||
if peer.role.is_some() && peer.remote_asn == proto.local_asn {
|
||||
return Err(rejected(PeerProblem::RoleInOwnAs));
|
||||
}
|
||||
// A peer is told from the others by its name in what the
|
||||
// dialer reports, and by its address when it connects to us.
|
||||
if peers.iter().any(|earlier| earlier.name == peer.name) {
|
||||
@@ -236,6 +243,9 @@ fn parse_peer(peer: Peer) -> Result<PeerConfig, PeerProblem> {
|
||||
Ok(proto::Role::ROUTE_SERVER_CLIENT) => Some(PeerRole::RouteServerClient),
|
||||
Err(_) => return Err(PeerProblem::Role),
|
||||
};
|
||||
if peer.require_role && role.is_none() {
|
||||
return Err(PeerProblem::RequireRoleWithoutRole);
|
||||
}
|
||||
|
||||
Ok(PeerConfig {
|
||||
// A peer without a name goes by its address.
|
||||
@@ -252,6 +262,7 @@ fn parse_peer(peer: Peer) -> Result<PeerConfig, PeerProblem> {
|
||||
add_path,
|
||||
import,
|
||||
role,
|
||||
require_role: peer.require_role,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -764,6 +775,26 @@ mod tests {
|
||||
role_of("role: ROUTE_SERVER").unwrap(),
|
||||
Some(PeerRole::RouteServer)
|
||||
);
|
||||
// Roles are between networks, and a peer can be required to say
|
||||
// its own only where we know what to expect.
|
||||
let problem = |body: &str| match role_of(body).err().unwrap() {
|
||||
ConfigError::Peer { problem, .. } => problem,
|
||||
other => panic!("{other:?}"),
|
||||
};
|
||||
assert_eq!(
|
||||
problem("role: PEER remote_asn: 65000"),
|
||||
PeerProblem::RoleInOwnAs
|
||||
);
|
||||
assert_eq!(
|
||||
problem("require_role: true"),
|
||||
PeerProblem::RequireRoleWithoutRole
|
||||
);
|
||||
let config = parse(&format!(
|
||||
"{BASE}peers {{ endpoint {{ address: \"192.0.2.2\" }} remote_asn: 1 \
|
||||
role: PEER require_role: true }}"
|
||||
))
|
||||
.unwrap();
|
||||
assert!(config.peers[0].require_role);
|
||||
assert!(!import_of("").unwrap().accept_aspa_invalid);
|
||||
assert!(
|
||||
import_of("import { accept_aspa_invalid: true }")
|
||||
|
||||
Reference in new issue
Block a user