Peer at link-local addresses: fe80::1%eth0
A peer's endpoint may be a link-local IPv6 address with the interface it is on. Such a peer is the address and the interface together, wherever peers are told apart: admission, collisions, hold-downs, the refusal log, the status page. The interface is looked up by name each time it is needed, so one that appears later or is made anew is found. Our routes go out with our link-local address in both places of MP_REACH_NLRI's next hop, as FRR sends its own. A peer's link-local next hop is taken alone, twice, or after "::" (BIRD), and resolved in the kernel on the interface of the session rather than by looking fe80::/64 up, which every interface has. The lab has a new phase, linklocal: sessions with FRR (by interface, with radvd beside the daemon) and BIRD over a LAN with link-local addresses only, both families, pings across, the interface down and up. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
92e013ca01
commit
fd2248feb5
38 files changed
+975
-151
No files matched your search
+50
-17
@@ -11,6 +11,7 @@ use bgp_primitives::ip_prefix::IpPrefix;
|
||||
use bgp_rib::{LocalRoute, LocalRouteAttrs};
|
||||
use bgp_rpki::RpkiConfig;
|
||||
|
||||
use crate::address::{Endpoint, PeerAddress};
|
||||
use crate::error::{ConfigError, ImportProblem, OriginateProblem, PeerProblem};
|
||||
use crate::family::FamilySet;
|
||||
use crate::policy::{ImportPolicy, PeerRole, PrefixSet};
|
||||
@@ -54,7 +55,8 @@ pub struct Config {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PeerConfig {
|
||||
pub name: String,
|
||||
pub endpoint: SocketAddr,
|
||||
/// Where the peer is. A link-local address has its interface with it.
|
||||
pub endpoint: Endpoint,
|
||||
pub remote_asn: u32,
|
||||
pub hold_time_secs: u16,
|
||||
/// Source address for connections we open to this peer; `None` lets the
|
||||
@@ -76,8 +78,8 @@ pub struct PeerConfig {
|
||||
|
||||
impl Config {
|
||||
/// The configured peer at `address`, if there is one.
|
||||
pub fn peer_at(&self, address: IpAddr) -> Option<&PeerConfig> {
|
||||
self.peers.iter().find(|peer| peer.endpoint.ip() == address)
|
||||
pub fn peer_at(&self, address: PeerAddress) -> Option<&PeerConfig> {
|
||||
self.peers.iter().find(|peer| peer.endpoint.is_at(address))
|
||||
}
|
||||
|
||||
pub fn from_proto(proto: BgpConfig) -> Result<Self, ConfigError> {
|
||||
@@ -124,10 +126,10 @@ impl Config {
|
||||
if peers.iter().any(|earlier| earlier.name == peer.name) {
|
||||
return Err(rejected(PeerProblem::DuplicateName));
|
||||
}
|
||||
if peers
|
||||
.iter()
|
||||
.any(|earlier| earlier.endpoint.ip() == peer.endpoint.ip())
|
||||
{
|
||||
if peers.iter().any(|earlier| {
|
||||
earlier.endpoint.ip == peer.endpoint.ip
|
||||
&& earlier.endpoint.interface == peer.endpoint.interface
|
||||
}) {
|
||||
return Err(rejected(PeerProblem::DuplicateAddress));
|
||||
}
|
||||
peers.push(peer);
|
||||
@@ -197,8 +199,9 @@ impl Config {
|
||||
|
||||
fn parse_peer(peer: Peer) -> Result<PeerConfig, PeerProblem> {
|
||||
let ep = peer.endpoint.as_ref().ok_or(PeerProblem::MissingEndpoint)?;
|
||||
let endpoint =
|
||||
parse_endpoint(&ep.address, ep.port).map_err(|e| PeerProblem::Endpoint(Box::new(e)))?;
|
||||
let endpoint = parse_port(ep.port)
|
||||
.and_then(|port| Ok(Endpoint::parse(&ep.address, port)?))
|
||||
.map_err(|e| PeerProblem::Endpoint(Box::new(e)))?;
|
||||
|
||||
if peer.remote_asn == 0 {
|
||||
return Err(PeerProblem::RemoteAsn);
|
||||
@@ -216,7 +219,7 @@ fn parse_peer(peer: Peer) -> Result<PeerConfig, PeerProblem> {
|
||||
} else {
|
||||
let ip = IpAddr::from_str(&peer.local_address)
|
||||
.map_err(|_| PeerProblem::LocalAddress(peer.local_address.clone()))?;
|
||||
if ip.is_ipv4() != endpoint.is_ipv4() {
|
||||
if ip.is_ipv4() != endpoint.ip.is_ipv4() {
|
||||
return Err(PeerProblem::AddressFamilyMismatch);
|
||||
}
|
||||
Some(ip)
|
||||
@@ -266,7 +269,7 @@ fn parse_peer(peer: Peer) -> Result<PeerConfig, PeerProblem> {
|
||||
Ok(PeerConfig {
|
||||
// A peer without a name goes by its address.
|
||||
name: if peer.name.is_empty() {
|
||||
endpoint.ip().to_string()
|
||||
endpoint.to_string()
|
||||
} else {
|
||||
peer.name
|
||||
},
|
||||
@@ -374,11 +377,15 @@ fn parse_import(import: &proto::ImportPolicy) -> Result<ImportPolicy, ImportProb
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_port(port: u32) -> Result<u16, ConfigError> {
|
||||
match port {
|
||||
0 => Ok(DEFAULT_BGP_PORT),
|
||||
port => u16::try_from(port).map_err(|_| ConfigError::Port(port)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_endpoint(address: &str, port: u32) -> Result<SocketAddr, ConfigError> {
|
||||
let port = match port {
|
||||
0 => DEFAULT_BGP_PORT,
|
||||
port => u16::try_from(port).map_err(|_| ConfigError::Port(port))?,
|
||||
};
|
||||
let port = parse_port(port)?;
|
||||
let ip = IpAddr::from_str(address).map_err(|_| ConfigError::Address(address.to_string()))?;
|
||||
Ok(SocketAddr::new(ip, port))
|
||||
}
|
||||
@@ -624,6 +631,28 @@ mod tests {
|
||||
PeerProblem::DuplicateAddress
|
||||
);
|
||||
|
||||
// A link-local address is one peer on each interface it is on.
|
||||
assert_eq!(
|
||||
problem(peer("r1", "fe80::1%eth0") + &peer("r2", "fe80::1%eth0")),
|
||||
PeerProblem::DuplicateAddress
|
||||
);
|
||||
let config = parse(&format!(
|
||||
"{BASE}{}{}",
|
||||
peer("", "fe80::1%eth0"),
|
||||
peer("", "fe80::1%eth1")
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
config.peers.iter().map(|p| &p.name).collect::<Vec<_>>(),
|
||||
["fe80::1%eth0", "fe80::1%eth1"]
|
||||
);
|
||||
// And nothing without one.
|
||||
assert_eq!(
|
||||
problem(peer("r1", "fe80::1")).to_string(),
|
||||
"endpoint is invalid: fe80::1 is a link-local address, which needs the interface it \
|
||||
is on: fe80::1%eth0, for example"
|
||||
);
|
||||
|
||||
// A peer without a name goes by its address, so two of them differ.
|
||||
let config = parse(&format!(
|
||||
"{BASE}{}{}",
|
||||
@@ -635,11 +664,15 @@ mod tests {
|
||||
assert_eq!(config.peers[1].name, "192.0.2.3");
|
||||
assert_eq!(
|
||||
config
|
||||
.peer_at("192.0.2.3".parse().unwrap())
|
||||
.peer_at("192.0.2.3".parse::<IpAddr>().unwrap().into())
|
||||
.map(|p| &*p.name),
|
||||
Some("192.0.2.3")
|
||||
);
|
||||
assert!(config.peer_at("192.0.2.4".parse().unwrap()).is_none());
|
||||
assert!(
|
||||
config
|
||||
.peer_at("192.0.2.4".parse::<IpAddr>().unwrap().into())
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
/// A peer would refuse our OPEN.
|
||||
|
||||
Reference in new issue
Block a user