Passive peers: configured, and left to connect

A session could only be configured for a peer we would then dial, every
few seconds to two minutes, for as long as it was not up. A new peer
whose side was not ready yet filled the log with failed attempts, and a
peer that opens its sessions itself was dialled for nothing.

A peer with `passive: true` is not dialled. A connection from its address
is taken as for any configured peer, and the session comes up with all
that is configured for it: role, import policy, export limits,
hold-downs. Until then the status page and /api/peers show it as
`Passive`, waiting for the peer to connect; after a session, with how
that session ended, which nothing else would say since nobody dials it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Opus 5.5 committed 2026-10-06 00:05:54 +02:00
1 parent 910dd258fb
commit c9751883c4
22 files changed
+178 -8

No files matched your search

@@ -43,6 +43,7 @@ async fn server_offering(
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "route-server-client".into(), name: "route-server-client".into(),
endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1).into(), endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1).into(),
@@ -33,6 +33,7 @@ fn peer_at(endpoint: SocketAddr) -> PeerConfig {
require_role: false, require_role: false,
export: Default::default(), export: Default::default(),
export_limits: None, export_limits: None,
passive: false,
} }
} }
+1
View File
@@ -60,6 +60,7 @@ async fn server_with(
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
@@ -39,6 +39,7 @@ async fn server_dialing(listener: &TcpListener) -> Result<TestServer> {
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
@@ -170,6 +170,7 @@ async fn test_refused_routes_can_be_seen_and_counted() -> Result<()> {
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "refused-demo".into(), name: "refused-demo".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
+69
View File
@@ -23,13 +23,25 @@ const TIMEOUT: Duration = Duration::from_secs(5);
/// Starts a server configured to dial `listener`'s address as peer "r1". /// Starts a server configured to dial `listener`'s address as peer "r1".
async fn server_dialing(listener: &TcpListener, remote_asn: u32) -> Result<TestServer> { async fn server_dialing(listener: &TcpListener, remote_asn: u32) -> Result<TestServer> {
server_with_peer(listener, remote_asn, false).await
}
/// Starts a server with peer "r1" at `listener`'s address, which it dials
/// unless the peer is `passive`.
async fn server_with_peer(
listener: &TcpListener,
remote_asn: u32,
passive: bool,
) -> Result<TestServer> {
let endpoint: SocketAddr = listener.local_addr()?; let endpoint: SocketAddr = listener.local_addr()?;
TestServer::start_with(LOCAL_ASN, |c| { 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;
c.peers = vec![PeerConfig { c.peers = vec![PeerConfig {
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
@@ -192,6 +204,7 @@ async fn test_inbound_from_configured_address_uses_config() -> Result<()> {
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1).into(), endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1).into(),
@@ -248,6 +261,7 @@ async fn test_local_address_is_used_as_the_source() -> Result<()> {
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
@@ -273,6 +287,7 @@ async fn test_local_address_is_used_as_the_source() -> Result<()> {
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
@@ -350,3 +365,57 @@ async fn test_a_peer_that_refuses_the_session_is_dialled_ever_more_slowly() -> R
); );
Ok(()) Ok(())
} }
/// A passive peer is not dialled: its session comes up when it connects,
/// and until then, and after, it is shown as waiting, with how its last
/// session ended.
#[tokio::test]
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 server = server_with_peer(&listener, PEER_ASN, true).await?;
let state = || server.registry.configured_peers()[0].1.clone();
// Many retry intervals pass, and nothing connects.
assert!(
tokio::time::timeout(Duration::from_secs(1), listener.accept())
.await
.is_err(),
"a passive peer was dialled"
);
assert_eq!(state(), DialState::Passive { last_error: None });
let mut peer = TestPeer::connect(server.addr).await?;
peer.handshake(PEER_ASN, Ipv4Addr::new(10, 0, 0, 2), 90)
.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"));
drop(peer);
tokio::time::timeout(TIMEOUT, async {
while !matches!(
state(),
DialState::Passive {
last_error: Some(_)
}
) {
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await?;
assert!(server.registry.list().is_empty());
assert!(
tokio::time::timeout(Duration::from_millis(600), listener.accept())
.await
.is_err(),
"a passive peer was dialled after its session ended"
);
Ok(())
}
@@ -54,6 +54,7 @@ async fn server_with(originated: &[&str], limits: ExportLimits) -> Result<Setup>
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: Some(Arc::new(limits)), export_limits: Some(Arc::new(limits)),
passive: false,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
@@ -45,6 +45,7 @@ async fn server_with(import: ImportPolicy) -> Result<(TestServer, TcpListener)>
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
@@ -186,6 +186,7 @@ async fn test_no_family_in_common_refuses_the_session() -> Result<()> {
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "v6-only".into(), name: "v6-only".into(),
endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1).into(), endpoint: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 1).into(),
+1
View File
@@ -51,6 +51,7 @@ async fn server_with(role: Option<PeerRole>, require_role: bool) -> Result<Setup
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
+1
View File
@@ -70,6 +70,7 @@ async fn server_with(
graceful_restart: Default::default(), graceful_restart: Default::default(),
groups: Vec::new(), groups: Vec::new(),
export_limits: None, export_limits: None,
passive: false,
reflector_client: false, reflector_client: false,
name: "r1".into(), name: "r1".into(),
endpoint: endpoint.into(), endpoint: endpoint.into(),
+7
View File
@@ -359,6 +359,13 @@ message Peer {
// one whose role is PROVIDER, PEER or ROUTE_SERVER, or that is in // one whose role is PROVIDER, PEER or ROUTE_SERVER, or that is in
// another AS and has no role. // another AS and has no role.
ExportLimits export_limits = 15; ExportLimits export_limits = 15;
// Do not open connections to the peer: wait for it to connect. The
// session is configured in full, and comes up when the peer connects
// from `endpoint`'s address; the port of `endpoint` is not used. For a
// new peer whose side is not ready yet, or one that is to open the
// connections itself.
bool passive = 16;
} }
// What is announced to a peer. // What is announced to a peer.
+16
View File
@@ -108,6 +108,8 @@ pub struct PeerConfig {
/// Pass on to this peer, in our own AS, the routes learned from our /// Pass on to this peer, in our own AS, the routes learned from our
/// other routers, and its routes to them (RFC 4456). /// other routers, and its routes to them (RFC 4456).
pub reflector_client: bool, pub reflector_client: bool,
/// Do not dial the peer: wait for it to connect.
pub passive: bool,
} }
impl Config { impl Config {
@@ -456,6 +458,7 @@ fn parse_peer(
}, },
groups: peer.groups, groups: peer.groups,
reflector_client: peer.route_reflector_client, reflector_client: peer.route_reflector_client,
passive: peer.passive,
}) })
} }
@@ -1400,4 +1403,17 @@ mod tests {
} }
)); ));
} }
/// A peer is dialled unless it is passive.
#[test]
fn a_peer_can_be_left_to_connect() {
let config = parse(&format!(
"{BASE}peers {{ name: \"a\" endpoint {{ address: \"192.0.2.2\" }} remote_asn: 1 }}
peers {{ name: \"b\" endpoint {{ address: \"192.0.2.3\" }} remote_asn: 2
passive: true }}"
))
.unwrap();
let passive: Vec<bool> = config.peers.iter().map(|peer| peer.passive).collect();
assert_eq!(passive, [false, true]);
}
} }
+4
View File
@@ -124,6 +124,8 @@ pub struct PeerSettings {
pub reflector_client: bool, pub reflector_client: bool,
/// Graceful restart with the peer. /// Graceful restart with the peer.
pub graceful_restart: GracefulRestartConfig, pub graceful_restart: GracefulRestartConfig,
/// Whether the peer is left to connect to us, and not dialled.
pub passive: bool,
} }
impl PeerSettings { impl PeerSettings {
@@ -150,6 +152,7 @@ impl PeerSettings {
groups: Arc::default(), groups: Arc::default(),
reflector_client: false, reflector_client: false,
graceful_restart, graceful_restart,
passive: false,
} }
} }
} }
@@ -170,6 +173,7 @@ impl From<&PeerConfig> for PeerSettings {
groups: peer.groups.as_slice().into(), groups: peer.groups.as_slice().into(),
reflector_client: peer.reflector_client, reflector_client: peer.reflector_client,
graceful_restart: peer.graceful_restart, graceful_restart: peer.graceful_restart,
passive: peer.passive,
} }
} }
} }
+2
View File
@@ -32,6 +32,8 @@ pub fn spawn_dialers(
) -> Vec<JoinHandle<()>> { ) -> Vec<JoinHandle<()>> {
peers peers
.iter() .iter()
// A passive peer is left to connect to us.
.filter(|peer| !peer.passive)
.map(|peer| tokio::spawn(dial_loop(peer.clone(), Arc::clone(server), connect_retry))) .map(|peer| tokio::spawn(dial_loop(peer.clone(), Arc::clone(server), connect_retry)))
.collect() .collect()
} }
+20 -2
View File
@@ -32,7 +32,7 @@ use crate::reach;
use crate::refusals::RefusalLog; use crate::refusals::RefusalLog;
use crate::restart::{Leave, Restarts}; use crate::restart::{Leave, Restarts};
use crate::rpki::spawn_revalidator; use crate::rpki::spawn_revalidator;
use crate::session::{ConfiguredPeer, SessionRegistry}; use crate::session::{ConfiguredPeer, DialState, SessionRegistry};
use crate::sync::IgnorePoison; use crate::sync::IgnorePoison;
use crate::throttle::Throttle; use crate::throttle::Throttle;
@@ -125,6 +125,7 @@ impl BgpServer {
endpoint: p.endpoint.clone(), endpoint: p.endpoint.clone(),
remote_asn: p.remote_asn, remote_asn: p.remote_asn,
role: p.role, role: p.role,
passive: p.passive,
}) })
.collect(); .collect();
// The client starts now: with luck the data is there before the // The client starts now: with luck the data is there before the
@@ -304,6 +305,9 @@ impl BgpServer {
reach::spawn_revalidator(interfaces, Arc::clone(&self.rib), self.registry.clone()), reach::spawn_revalidator(interfaces, Arc::clone(&self.rib), self.registry.clone()),
)); ));
} }
for peer in self.config.peers.iter().filter(|peer| peer.passive) {
info!(peer = %peer.name, endpoint = %peer.endpoint, "Waiting for the peer to connect (passive)");
}
for dialer in spawn_dialers(&self.config.peers, &context, self.config.connect_retry) { for dialer in spawn_dialers(&self.config.peers, &context, self.config.connect_retry) {
tasks.push(named("dialer", dialer)); tasks.push(named("dialer", dialer));
} }
@@ -476,10 +480,24 @@ async fn serve(
peer: PeerSettings, peer: PeerSettings,
_slot: ConnectionSlot, _slot: ConnectionSlot,
) { ) {
// Nobody dials a passive peer, so its state is said here.
let passive = peer.name.clone().filter(|_| peer.passive);
let registry = context.registry.clone();
let end = run_session(stream, peer_addr, Direction::Inbound, context, peer).await; let end = run_session(stream, peer_addr, Direction::Inbound, context, peer).await;
if let Err(e) = end.result { if let Err(e) = &end.result {
warn!(peer_addr = %socket_text(peer_addr), err = %e, "BGP session terminated with error"); warn!(peer_addr = %socket_text(peer_addr), err = %e, "BGP session terminated with error");
} }
if let Some(name) = passive {
registry.set_dial_state(
&name,
DialState::Passive {
last_error: Some(match end.result {
Ok(()) => "session closed at our request".to_string(),
Err(e) => e.to_string(),
}),
},
);
}
} }
/// Why an incoming connection was closed without a session. /// Why an incoming connection was closed without a session.
+16 -1
View File
@@ -54,6 +54,12 @@ pub enum DialState {
/// How the last attempt or session ended. /// How the last attempt or session ended.
last_error: String, last_error: String,
}, },
/// The peer is passive: not dialled, waiting for it to connect.
Passive {
/// How its last session, or the last connection from it, ended,
/// if one did.
last_error: Option<String>,
},
} }
/// A peer from the configuration file, whether or not it is currently up. /// A peer from the configuration file, whether or not it is currently up.
@@ -64,6 +70,8 @@ pub struct ConfiguredPeer {
pub remote_asn: u32, pub remote_asn: u32,
/// What the peer is to us, if that was said. /// What the peer is to us, if that was said.
pub role: Option<PeerRole>, pub role: Option<PeerRole>,
/// Whether it is left to the peer to connect.
pub passive: bool,
} }
/// Counts kept by a session as it goes, for whoever wants to look. /// Counts kept by a session as it goes, for whoever wants to look.
@@ -321,7 +329,14 @@ impl SessionRegistry {
let (events, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY); let (events, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
let dial_states = configured let dial_states = configured
.iter() .iter()
.map(|p| (p.name.clone(), DialState::Idle)) .map(|p| {
let state = if p.passive {
DialState::Passive { last_error: None }
} else {
DialState::Idle
};
(p.name.clone(), state)
})
.collect(); .collect();
for peer in &configured { for peer in &configured {
PEER_UP.with_label_values(&[&peer.name]).set(0); PEER_UP.with_label_values(&[&peer.name]).set(0);
+5 -2
View File
@@ -63,7 +63,8 @@ pub struct PeerView {
pub name: Option<String>, pub name: Option<String>,
pub peer_addr: String, pub peer_addr: String,
pub peer_asn: u32, pub peer_asn: u32,
/// `Established`, `Connecting`, `Backoff` or `Idle`. /// `Established`, `Connecting`, `Backoff`, `Idle`, or `Passive` for a
/// peer that is not dialled and has not connected.
pub state: String, pub state: String,
/// `inbound` or `outbound`: which side opened the live TCP connection. /// `inbound` or `outbound`: which side opened the live TCP connection.
pub direction: Option<String>, pub direction: Option<String>,
@@ -95,7 +96,8 @@ pub struct PeerView {
/// Whether the peer's OPEN said the same (RFC 9234). `false` for a /// Whether the peer's OPEN said the same (RFC 9234). `false` for a
/// peer that does not say; one that says otherwise gets no session. /// peer that does not say; one that says otherwise gets no session.
pub role_confirmed: Option<bool>, pub role_confirmed: Option<bool>,
/// Why the last connection attempt or session ended, while in Backoff. /// Why the last connection attempt or session ended, while in Backoff;
/// for a passive peer, why its last session ended, if one did.
pub last_error: Option<String>, pub last_error: Option<String>,
pub retry_at_unix: Option<u64>, pub retry_at_unix: Option<u64>,
/// Graceful restart (RFC 4724), if both sides offered it. /// Graceful restart (RFC 4724), if both sides offered it.
@@ -172,6 +174,7 @@ impl PeerView {
DialState::Backoff { until, last_error } => { DialState::Backoff { until, last_error } => {
("Backoff", Some(last_error.clone()), Some(unix_secs(*until))) ("Backoff", Some(last_error.clone()), Some(unix_secs(*until)))
} }
DialState::Passive { last_error } => ("Passive", last_error.clone(), None),
}; };
Self { Self {
name: Some(peer.name.clone()), name: Some(peer.name.clone()),
+4 -2
View File
@@ -242,7 +242,7 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
tick(); tick();
} }
const STATE_CLASS = { Established: 'ok', Connecting: 'warn', Backoff: 'bad', Idle: 'dim' }; const STATE_CLASS = { Established: 'ok', Connecting: 'warn', Backoff: 'bad', Idle: 'dim', Passive: 'dim' };
// Where several routes for a prefix are exchanged (ADD-PATH), and which way. // Where several routes for a prefix are exchanged (ADD-PATH), and which way.
function addPath(p) { function addPath(p) {
const rx = p.add_path_receive || [], tx = p.add_path_send || []; const rx = p.add_path_receive || [], tx = p.add_path_send || [];
@@ -260,7 +260,9 @@ from <select id="lookup-peer"><option value="">any peer</option></select>
const up = p.state === 'Established'; const up = p.state === 'Established';
const restarting = !up && p.stale_until_unix const restarting = !up && p.stale_until_unix
? `<span class="warn" title="Graceful restart: the peer's routes are kept, and used, until it is back and has sent them again">restarting: ${p.stale_routes} routes kept for ${until(p.stale_until_unix)}</span>` : ''; ? `<span class="warn" title="Graceful restart: the peer's routes are kept, and used, until it is back and has sent them again">restarting: ${p.stale_routes} routes kept for ${until(p.stale_until_unix)}</span>` : '';
const detail = [restarting, p.state === 'Backoff' ? `${esc(p.last_error || '')}; retry in ${until(p.retry_at_unix)}` : ''] const detail = [restarting,
p.state === 'Backoff' ? `${esc(p.last_error || '')}; retry in ${until(p.retry_at_unix)}` : '',
p.state === 'Passive' ? `waiting for the peer to connect${p.last_error ? `; last session: ${esc(p.last_error)}` : ''}` : '']
.filter(Boolean).join('; '); .filter(Boolean).join('; ');
const gr = p.graceful_restart; const gr = p.graceful_restart;
const graceful = gr ? ` &middot; <span title="Graceful restart (RFC 4724): the peer says a restart takes ${gr.restart_time_secs}s${gr.notification ? ', and NOTIFICATIONs other than Hard Reset count as restarts (RFC 8538)' : ''}">graceful restart</span>` : ''; const graceful = gr ? ` &middot; <span title="Graceful restart (RFC 4724): the peer says a restart takes ${gr.restart_time_secs}s${gr.notification ? ', and NOTIFICATIONs other than Hard Reset count as restarts (RFC 8538)' : ''}">graceful restart</span>` : '';
+23
View File
@@ -57,6 +57,29 @@ Ferrix rejects a neighbor whose OPEN message contains an AS number other
than `remote_asn`, with the NOTIFICATION message OPEN Message Error / Bad than `remote_asn`, with the NOTIFICATION message OPEN Message Error / Bad
Peer AS. Peer AS.
### Passive Neighbors
A passive neighbor is a neighbor to which Ferrix does not initiate
connections. Ferrix accepts connections from the neighbor's address and
establishes the session with the configured settings when the neighbor
connects. Use a passive neighbor to configure a session before the
neighbor's side is ready, or when the neighbor initiates the connections:
```textproto
peers {
name: "new-peer"
endpoint { address: "192.0.2.10" }
remote_asn: 64510
role: PEER
passive: true
}
```
Until the neighbor connects, the status page and `/api/peers` show the
state `Passive`. After a session ends, they show the reason in the
neighbor's last error. Ferrix does not log connection attempts to a
passive neighbor, because it makes none.
## Inbound Connection Filtering ## Inbound Connection Filtering
Ferrix accepts inbound connections only from the addresses of configured Ferrix accepts inbound connections only from the addresses of configured
@@ -75,6 +75,7 @@ Statements for a BGP neighbor.
| `import` <span class="type">[ImportPolicy](#importpolicy)</span> | Default checks | Specifies the import policy for routes received from the neighbor. | | `import` <span class="type">[ImportPolicy](#importpolicy)</span> | Default checks | Specifies the import policy for routes received from the neighbor. |
| `role` <span class="type">[Role](#role)</span> | None | Specifies the BGP role of the neighbor (RFC 9234). Not permitted for IBGP neighbors. Required for every EBGP neighbor if any `export` statement enables `learned_routes`. | | `role` <span class="type">[Role](#role)</span> | None | Specifies the BGP role of the neighbor (RFC 9234). Not permitted for IBGP neighbors. Required for every EBGP neighbor if any `export` statement enables `learned_routes`. |
| `require_role` <span class="type">bool</span> | `false` | Rejects the session if the neighbor does not advertise a BGP role. Requires `role`. | | `require_role` <span class="type">bool</span> | `false` | Rejects the session if the neighbor does not advertise a BGP role. Requires `role`. |
| `passive` <span class="type">bool</span> | `false` | Prevents Ferrix from initiating connections to the neighbor. Ferrix accepts connections from the address of `endpoint`; the port of `endpoint` is not used. See [Passive Neighbors](@/docs/guides/peers.md#passive-neighbors). |
| `export` <span class="type">[ExportPolicy](#exportpolicy)</span> | Top-level `export` | Specifies the export policy for the neighbor. Replaces the top-level `export` statement. | | `export` <span class="type">[ExportPolicy](#exportpolicy)</span> | Top-level `export` | Specifies the export policy for the neighbor. Replaces the top-level `export` statement. |
| `graceful_restart` <span class="type">[GracefulRestart](#gracefulrestart)</span> | Top-level `graceful_restart` | Specifies graceful restart for the neighbor. Each statement that is set replaces the corresponding top-level statement. | | `graceful_restart` <span class="type">[GracefulRestart](#gracefulrestart)</span> | Top-level `graceful_restart` | Specifies graceful restart for the neighbor. Each statement that is set replaces the corresponding top-level statement. |
| `route_reflector_client` <span class="type">bool</span> | `false` | Makes the IBGP neighbor a route reflector client: Ferrix advertises to it the routes learned from other IBGP neighbors, and advertises its routes to them. Permitted only for IBGP neighbors. See [Route Reflection](@/docs/guides/passing-routes-on.md#route-reflection). | | `route_reflector_client` <span class="type">bool</span> | `false` | Makes the IBGP neighbor a route reflector client: Ferrix advertises to it the routes learned from other IBGP neighbors, and advertises its routes to them. Permitted only for IBGP neighbors. See [Route Reflection](@/docs/guides/passing-routes-on.md#route-reflection). |
+1 -1
View File
@@ -72,7 +72,7 @@ neighbor:
| Field | Description | | Field | Description |
|---|---| |---|---|
| `state` | Session state: `Established`, `Connecting`, `Backoff`, or `Idle`. | | `state` | Session state: `Established`, `Connecting`, `Backoff`, `Idle`, or `Passive` (a passive neighbor that has no session). |
| `direction` | `inbound` if the neighbor initiated the connection, `outbound` otherwise. | | `direction` | `inbound` if the neighbor initiated the connection, `outbound` otherwise. |
| `routes` | Number of the neighbor's routes in the routing table. | | `routes` | Number of the neighbor's routes in the routing table. |
| `rejected_routes` | Number of the neighbor's routes rejected by its import policy. | | `rejected_routes` | Number of the neighbor's routes rejected by its import policy. |