Networking · Network Operations
Reading a BGP Session Failure From Its State and Its Last Error
How the session states narrow a failure to transport, capability negotiation, or policy, and why a session that reaches Established can still exchange no routes.
A BGP session that is down reports one of six states, and the state alone localises the failure more sharply than any log message. The states correspond to stages of a fixed sequence: establish a transport connection, exchange opening messages that negotiate capabilities, exchange an initial set of routes, then settle into a steady exchange of updates. A failure at each stage has a different set of plausible causes, and reading the state as a stage rather than as a status is what turns a vague “session down” into a specific hypothesis.
The six states as a narrowing sequence
Idle is the state in which the router is neither listening nor attempting a connection. It is not a failure state in itself; a session sits there while it waits for a retry timer after a previous attempt failed, and the reason for the previous failure is what the router records. A session that stays in Idle with no retry scheduled is usually administratively disabled, or its configuration is incomplete enough that the router refuses to start it.
Connect means the router is attempting a transport connection to its peer, or waiting for one. Active means the router is listening for an incoming connection from the peer. The names are the source of a persistent misreading: Active sounds like progress and is in fact the state of a router that has failed to establish a connection and is waiting to try again. A session oscillating between Connect and Active is failing at the transport layer, before any BGP message has been exchanged.
OpenSent means a transport connection exists and the router has sent its opening message and is waiting for the peer’s. OpenConfirm means both opening messages have been received and accepted and the router is waiting for the keepalive that completes the negotiation. Established means the session is exchanging updates.
The narrowing is immediate. A failure in Connect or Active is a transport problem: the peer is unreachable, the port is filtered, or the peer is not listening on the address the router is connecting to. A failure in OpenSent or OpenConfirm is a message-level problem: the two ends disagree about something in the opening exchange. A failure after Established is either a policy problem or a hold-timer expiry, and the two are distinguished by whether the session resets or merely stops carrying routes.
The transport stage: two addresses and one port
The transport connection is a TCP connection to port 179. Its failure has four distinct causes that the state does not separate, and separating them requires looking at the addressing.
The first is that the router is connecting to an address that is not the peer’s. A BGP configuration names a neighbour address, and that address must be one the peer will accept a connection on. Where the peer is reachable by several addresses, a configuration that uses one and a peer that only listens on another produce a Connect/Active oscillation with no packets reaching anything that would answer.
The second is filtering. A rule that permits the session’s own traffic but not the reverse direction, or that permits TCP to the peer’s address but not from it, produces a connection that is established in one direction only. A filter that permits the session but drops the peer’s keepalives produces an Established session that dies after the hold timer, which is a different signature and appears at the next stage.
The third is that the peer is not configured for this neighbour at all, so it answers the connection attempt with a reset. From the initiating side this is indistinguishable from a filter that rejects the connection.
The fourth is a routing problem in the ordinary sense: the peer’s address is not reachable, so the connection attempt fails before any filter is consulted.
These are separated by observing the connection rather than the session. A capture at either end shows whether the opening SYN arrives and what answers it; a transport-level check to the peer’s address shows whether the address is reachable at all. The capture artifacts that make this reading reliable, particularly on a host with segmentation offload, are covered in packet capture placement.
# does anything answer on the BGP port, and from which source address
nc -v -w 3 192.0.2.1 179
ss -tn state established '( dport = :179 or sport = :179 )'
A connection that completes confirms the transport and leaves the session failure in the opening exchange. A connection that hangs or is refused places the failure before any BGP message, and the next check is the routing and filtering between the two addresses rather than anything in the protocol configuration.
The opening stage: what the two ends must agree on
Once a transport connection exists, the two ends exchange opening messages. Each carries the peer’s autonomous system number, a hold time, an identifier, and a set of optional parameters declaring capabilities. The exchange fails in a small number of well-defined ways, and the failure is reported in a notification message carrying a code and a subcode.
The most common is a mismatch in the expected peer’s autonomous system. A router configured to expect a specific number and receiving an opening message from a different one rejects the session. This happens whenever the peer’s number changed, or whenever the neighbour is configured with a wrong number, or when the connection is answered by a different device than intended, which is a real possibility when a peer address is reachable through more than one path.
The second is a hold-time disagreement. Each side proposes a hold time and the smaller of the two governs, so a difference is not itself a failure. A hold time of zero means no keepalives are exchanged, and a router that requires a nonzero hold time may reject that. The values that matter are the negotiated minimum and the derived keepalive interval, which is conventionally one third of the hold time.
The third is a capability mismatch. The opening messages declare multiprotocol support, graceful restart, route refresh, and other extensions. A peer that requires an extension the other does not declare may refuse the session outright or may accept it and behave differently. The distinction between refusing and accepting-but-degrading is the source of a class of problems where a session is up and a feature silently does nothing.
The reported code for each of these is in the notification message, and the router retains the last one it received. That retained notification is the single most informative artifact for a session that fails at this stage, and it should be read before any hypothesis about policy.
The established stage: policy and timers
A session that reaches Established has passed every check the protocol can make about identity and capability. What remains are two mechanisms with very different signatures.
The first is the hold timer. Each side sends keepalives at the negotiated interval and considers the session dead if nothing arrives within the hold time. Expiry produces a reset with a notification indicating that the hold time expired, and the session returns to Idle before re-establishing. A session that establishes and dies repeatedly at an interval close to the hold time is experiencing loss on the path rather than a configuration fault, and the loss is often specific to the size or timing of keepalive packets.
The second is policy, and it does not reset the session. A session can be Established, healthy, exchanging keepalives, and carrying no routes in either direction because an import policy rejected everything, or an export policy suppressed everything. The state is correct and the routing table is empty, which is why “the session is up” is not evidence that the session is working.
The default behaviour makes this sharper. The specification that requires explicit policy for external sessions exists because the older default of accepting and advertising everything produced accidental transit. A router that follows it will reject all routes from an external peer until a policy admits them, so an established session with no routes is the expected state of a freshly configured external neighbour with no policy attached.
Reading the evidence in order
The efficient sequence reads the state, then the last notification, then the received and advertised route counts. The router’s own neighbour detail carries all of it; the point is to read the fields in that order rather than to reach for the first alarming one.
The neighbour detail carries the state, how long it has held, the negotiated hold time and keepalive interval, the number of prefixes received and sent, and the last notification sent and received. The uptime figure is what exposes flapping that a single reading of the state hides: a session that has been up for a few seconds while the router has been running for days is flapping.
The prefix counts are the discriminator for policy problems. A session in Established with a nonzero received count and a zero advertised count has an export problem; both zero with an established session has either an import problem on both sides or a peer that has nothing to send.
The last notification’s code narrows the stage. A code indicating a hold-timer expiry points at the path; a code indicating an unacceptable peer number points at configuration; a code indicating a capability problem points at the extensions in use. The subcodes exist precisely to make this distinction without ambiguity, and reading the code is faster than any amount of inference from symptoms.
What a stable session does not prove
An established session that carries a stable set of prefixes can still be the wrong session. A neighbour configured toward the wrong peer’s address, answered by a device that happens to run BGP and happens to accept the session, produces a healthy session exchanging whatever that device advertises. Nothing in the session state reports that the peer is not the intended one; only the peer’s identity as declared in its opening message, and the prefixes it sends, reveal it.
The same limitation applies to the reverse direction. A session that is up and carrying routes says nothing about whether the paths those routes describe are usable. The next-hop of a received route must be resolvable, and the resolution is a separate mechanism with its own failure modes, including the neighbour cache behaviour covered in neighbour cache staleness. A route that is present, valid by the protocol’s own rules, and unusable because its next hop does not resolve is a common cause of a session that looks perfect and a forwarding table that is missing entries.