summaryrefslogtreecommitdiffstats
path: root/bgpd/bgp_fsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'bgpd/bgp_fsm.c')
-rw-r--r--bgpd/bgp_fsm.c278
1 files changed, 162 insertions, 116 deletions
diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c
index 68bb0282..46bd3569 100644
--- a/bgpd/bgp_fsm.c
+++ b/bgpd/bgp_fsm.c
@@ -55,13 +55,19 @@
*
* In some cases the action routine may override the the default new state.
*
+ * In general the FSM manages connections, but there is some interaction with
+ * the session. In particular, exceptions are expressed as session_eXXX
+ * values -- which are passed to the Peering Engine as session events. The
+ * handling of FSM events is depends mostly on the FSM state, but any
+ * exception influences that too.
+ *
* When a new state is entered, bgp_fsm_state_change() is called to complete
* the transition (in particular to set/unset timers).
*
* The fsm action functions are called with the session locked.
*
*------------------------------------------------------------------------------
- * FSM "events"
+ * FSM "events" and Session "exceptions".
*
* These are raised when:
*
@@ -73,7 +79,12 @@
*
* * timers go off
*
- * and the mechanism is to call bgp_fsm_event().
+ * FSM events are raised by calling bgp_fsm_event(). A number of events are
+ * associated with session exceptions -- the exception is posted in the
+ * connection and then a suitable FSM event is raised.
+ *
+ * The most general FSM event is fsm_eBGP_Stop -- which MUST have a posted
+ * exception to tell it why the stop has been been raised.
*
* However, nothing external calls bgp_fsm_event() directly -- functions
* defined here will raise the appropriate event.
@@ -92,15 +103,15 @@
*
* However, the FSM does some I/O operations -- notably write() and connect().
* These may complete immediately, and may need to trigger a new event. To
- * handle this, the connection can set a "post" event, to be processed at the
- * tail end of the current event processing.
+ * handle this, the connection can set a "follow on" event, to be processed at
+ * immediately after the current event and any state change that makes.
*
* Also, some things within the FSM are most consistently dealt with by
* raising follow on events.
*
- * Note that there is only one level of "post" event. The FSM only ever issues
- * one I/O operation per event or more than one follow on event, and never both
- * at the same time. (It's a RULE.)
+ * Note that there is only one level of "follow on" event. The FSM never
+ * issues more than one I/O operation per event or more than one follow on
+ * event, and never both at the same time. (It's a RULE.)
*
*------------------------------------------------------------------------------
* Primary and Secondary Connections
@@ -122,8 +133,8 @@
* Up to sEstablished state, the primary connection will be the out-bound
* connect() connection (if allowed) and the secondary will be the in-bound
* accept() connection (if allowed). In sEstablished state, the primary
- * connection is the one that won the race -- any other connection is snuffed
- * out.
+ * connection is the one that won the race -- any other connection has been
+ * snuffed out.
*
* As per the RFC, collision detection/resolution is performed when an OPEN
* message is received -- that is, as the connection attempts to advance to
@@ -134,7 +145,7 @@
* See below for a discussion of the fall back to sIdle -- the losing connection
* will remain comatose until the winner either reaches sEstablished (when the
* loser is snuffed out) or the winner falls back to sIdle (when the
- * IdleHoldTimer for the loser is set, and it will be awoken in due course).
+ * IdleHoldTimer for the loser is set, and it will wake up in due course).
*
* NB: the RFC talks of matching source/destination and destination/source
* addresses of connections in order to detect collisions. This code
@@ -195,13 +206,13 @@
* * if a sibling exists and is in sOpenSent or sOpenConfirm:
*
* - do not change the IdleHoldTimer interval.
- * - do not set the IdleHoldTimer (with jitter).
+ * - unset the IdleHoldTimer.
* - set self "comatose".
*
* * otherwise:
*
* - increase the IdleHoldTimer interval.
- * - set the IdleHoldTimer.
+ * - set the IdleHoldTimer (with jitter).
*
* and if a sibling exists and is comatose:
*
@@ -253,10 +264,10 @@
*
* The basic mechanism is:
*
- * * exceptions may the "thrown" -- which records a given exception in the
+ * * exceptions may the "thrown" -- which posts a given exception in the
* connection then kicks the FSM with a given fsm_eXxxxx event.
*
- * Information recorded is:
+ * Information posted is:
*
* sesssion_eXxxxx -- what the exception is
* notification -- any NOTIFICATION message
@@ -273,8 +284,13 @@
* See the various exception functions below for what exceptions are posted and
* what fsm_eXxxx events are generated.
*
- * For internally generated exceptions, the FSM throws exceptions with an
- * fsm_eBGP_Stop event.
+ * The following fsm events require an exception:
+ *
+ * bgp_fsm_eBGP_Stop -- bgp_fsm_exception()
+ * bgp_fsm_eTCP_connection_closed -- bgp_fsm_io_error()
+ * bgp_fsm_eTCP_connection_open_failed -- bgp_fsm_connect_completed()
+ * bgp_fsm_eTCP_fatal_error -- bgp_fsm_io_fatal_error()
+ * bgp_fsm_eReceive_NOTIFICATION_message -- bgp_fsm_notification_exception()
*
*------------------------------------------------------------------------------
* FSM errors
@@ -308,8 +324,7 @@
* This ensures there is room in the write buffer at the very least.
*
* For sOpenSent and sOpenConfirm states there should be zero chance of
- * there being anything to purge, and probably no write buffer in any
- * case.
+ * there being anything to purge.
*
* -- purge any pending write messages for the connection (for sEstablished).
*
@@ -358,6 +373,26 @@ static void
bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event) ;
/*==============================================================================
+ * Recharge the HoldTimer
+ *
+ * Defined here for the convenience of bgp_fsm_pre_update(), which is called
+ * once for every incoming update.
+ *
+ * NB: no jitter.
+ *
+ * NB: do nothing if connection->hold_timer_interval == 0
+ *
+ * NB: if connection->hold_timer_interval != 0, timer MUST be set
+ */
+static inline void
+bgp_hold_timer_recharge(bgp_connection connection)
+{
+ if (connection->hold_timer_interval != 0)
+ qtimer_set_interval(&connection->hold_timer,
+ QTIME(connection->hold_timer_interval), NULL) ;
+} ;
+
+/*==============================================================================
* Enable the given session -- which must be newly initialised.
*
* This is the first step in the FSM, and the connection advances to Idle.
@@ -448,8 +483,6 @@ bgp_fsm_keepalive_received(bgp_connection connection)
* Deals, via the FSM, with unexpected "update" events -- for example an
* UPDATE (or ROUTE-REFRESH) before reaching sEstablished !
*/
-static void bgp_hold_timer_recharge(bgp_connection connection) ;
-
extern int
bgp_fsm_pre_update(bgp_connection connection)
{
@@ -492,8 +525,7 @@ bgp_fsm_disable_session(bgp_session session, bgp_notify notification)
connection = session->connections[bgp_connection_secondary] ;
if (connection != NULL)
- bgp_fsm_throw(connection, bgp_session_eDisabled, notification, 0,
- bgp_fsm_eBGP_Stop) ;
+ bgp_fsm_exception(connection, bgp_session_eDisabled, notification) ;
else
{
/* Acknowledge the disable -- session is stopped. */
@@ -511,7 +543,7 @@ bgp_fsm_disable_session(bgp_session session, bgp_notify notification)
* NB: can throw an exception for other connections while in the FSM.
*
* Can throw an exception for the current connection while in the FSM, the
- * fsm_active/post mechanism looks after this.
+ * fsm_active/follow_on mechanism looks after this.
*/
extern void
bgp_fsm_exception(bgp_connection connection, bgp_session_event_t except,
@@ -773,7 +805,7 @@ static bgp_fsm_action(bgp_fsm_exit) ;
*
* Start a new accept() attempt.
*
- * 2. BGP_Stop
+ * 2. BGP_Stop -- process the posted exception (invalid if none !)
*
* a. in all states:
*
@@ -798,7 +830,7 @@ static bgp_fsm_action(bgp_fsm_exit) ;
*
* Cannot happen at any other time.
*
- * 4. TCP_connection_closed
+ * 4. TCP_connection_closed -- process the posted exception (MUST be there)
*
* Raised by "EOF" on read or by EPIPE and some other errors.
*
@@ -820,6 +852,7 @@ static bgp_fsm_action(bgp_fsm_exit) ;
* Cannot happen at any other time.
*
* 5. TCP_connection_open_failed ("soft" error)
+ * -- process the posted exception (MUST be there)
*
* a. in sConnect or sActive states
*
@@ -830,6 +863,7 @@ static bgp_fsm_action(bgp_fsm_exit) ;
* Cannot happen at any other time.
*
* 6. TCP_fatal_error ("hard" error)
+ * -- process the posted exception (MUST be there)
*
* a. in sConnect or sActive states
*
@@ -959,6 +993,7 @@ static bgp_fsm_action(bgp_fsm_exit) ;
* Cannot happen at any other time (connection not up or read closed).
*
* 13. Receive_NOTIFICATION_message
+ * -- process the posted exception (MUST be there)
*
* Generated by read action.
*
@@ -1379,7 +1414,7 @@ bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event)
if (connection->fsm_active == 2)
{
- connection->post = event ;
+ connection->follow_on = event ;
return ;
} ;
@@ -1431,9 +1466,9 @@ bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event)
LOOKUP (bgp_status_msg, next_state));
} ;
- /* Pick up post event -- if any */
- event = connection->post ;
- connection->post = bgp_fsm_null_event ;
+ /* Pick up follow_on event -- if any */
+ event = connection->follow_on ;
+ connection->follow_on = bgp_fsm_null_event ;
} while (--connection->fsm_active != 0) ;
@@ -1679,7 +1714,7 @@ static bgp_fsm_action(bgp_fsm_fatal)
*/
static bgp_fsm_action(bgp_fsm_retry)
{
- bgp_connection_close(connection, 0) ; /* FSM does timers */
+ bgp_connection_close(connection) ; /* FSM does timers */
bgp_fsm_throw(connection, bgp_session_eRetry, NULL, 0,
bgp_fsm_eBGP_Start) ;
@@ -1714,7 +1749,7 @@ static bgp_fsm_action(bgp_fsm_expire)
/* The process of sending a NOTIFICATION comes to an end here. */
if (connection->notification_pending)
{
- bgp_connection_close(connection, 0) ; /* FSM deals with timers */
+ bgp_connection_close(connection) ; /* FSM deals with timers */
return next_state ;
} ;
@@ -1885,7 +1920,6 @@ static bgp_fsm_action(bgp_fsm_establish)
bgp_connection_make_primary(connection) ;
/* Signal exciting session event */
- session->made = 1 ;
bgp_session_event(session, bgp_session_eEstablished, NULL, 0, 0, 0) ;
/* TODO: now would be a good time to withdraw the password from listener ? */
@@ -1937,18 +1971,19 @@ static bgp_fsm_action(bgp_fsm_exit)
* Uses the posted information and the expected next_state to deal with some
* exception. Proceeds:
*
+ * 0) stop any timers -- so if held in sOpenSent/sOpenConfirm by notification
+ * process, won't (eg) have extraneous keepalive going off.
+ *
* 1a) if have notification & not eNOM_recv & is in a suitable state
*
- * (suitable state is sOpenSent/sOpenConfirm/sEstablished.
+ * Suitable states are sOpenSent/sOpenConfirm/sEstablished.
*
- * Start sending the NOTIFICATION message.
+ * Send NOTIFICATION -- see notes above on the process.
*
* For sOpenSent/sOpenConfirm, override the next_state to stay where it is
* until NOTIFICATION process completes.
*
- * For sEstablished, the next state will be sStopping.
- *
- * Sending NOTIFICATION closes the connection for reading.
+ * For sEstablished, the next_state will be sStopping.
*
* 1b) otherwise: close the connection file.
*
@@ -1964,37 +1999,54 @@ static bgp_fsm_action(bgp_fsm_exit)
*
* (The sibling will be session_eDiscard -- so no deadly embrace here.)
*
+ * So: proceeds to the given next_state unless has started notification process,
+ * and next_state was not sStopping.
+ *
+ * Issues follow-on events:
+ *
+ * * bgp_fsm_eSent_NOTIFICATION_message if notification clears to TCP buffers
+ * immediately.
+ *
+ * * bgp_fsm_eTCP_fatal_error (or other such) if fails trying to send
+ * notification.
+ *
+ * * bgp_fsm_eBGP_Stop if next_state is sStopping and no notification to
+ * send.
+ *
* The state machine takes care of the rest:
*
- * * complete entry to new state (for sStopping will cut connection loose).
+ * * complete entry to new state
*
* * send message to Routeing Engine
*
+ * * cutting the connection loose if ends up sStopping.
+ *
* NB: requires the session LOCKED -- connection-wise
*/
static bgp_fsm_state_t
bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state)
{
- int send_notification ;
+ bgp_notify send_notification ;
+
+ assert(connection->except != bgp_session_null_event) ;
+
+ /* It's bad news, so stop doing whatever was doing. */
+ qtimer_unset(&connection->hold_timer) ;
+ qtimer_unset(&connection->keepalive_timer) ;
- /* Have a notification to send iff have not just received one, and are in a
+ /* Have a notification to send iff not just received one, and is in a
* suitable state to send one at all.
*/
if (connection->except == bgp_session_eNOM_recv)
- send_notification = 0 ;
+ send_notification = NULL ;
else
{
- if ( (connection->state == bgp_fsm_sOpenSent)
- || (connection->state == bgp_fsm_sOpenConfirm)
- || (connection->state == bgp_fsm_sEstablished) )
- {
- send_notification = (connection->notification != NULL) ;
- }
- else
- {
- bgp_notify_unset(&connection->notification) ;
- send_notification = 0 ;
- } ;
+ if ( (connection->state != bgp_fsm_sOpenSent)
+ && (connection->state != bgp_fsm_sOpenConfirm)
+ && (connection->state != bgp_fsm_sEstablished) )
+ bgp_notify_unset(&connection->notification) ;
+
+ send_notification = connection->notification ;
} ;
/* If there is a NOTIFICATION to send, now is the time to do that.
@@ -2003,7 +2055,7 @@ bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state)
* The state transition stuff looks after timers. In particular an error
* in Connect/Active states leaves the ConnectRetryTimer running.
*/
- if (send_notification)
+ if (send_notification != NULL)
{
int ret ;
@@ -2021,7 +2073,7 @@ bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state)
* If the write fails it raises a suitable event, which will now be
* sitting waiting to be processed on the way out of the FSM.
*/
- ret = bgp_msg_write_notification(connection, connection->notification) ;
+ ret = bgp_msg_write_notification(connection, send_notification) ;
connection->notification_pending = (ret >= 0) ;
/* is pending if not failed */
@@ -2047,7 +2099,7 @@ bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state)
}
else
{
- bgp_connection_close(connection, 0) ; /* FSM deals with timers */
+ bgp_connection_close(connection) ; /* FSM deals with timers */
if (next_state == bgp_fsm_sStopping) /* can exit if sStopping */
bgp_fsm_event(connection, bgp_fsm_eBGP_Stop) ;
@@ -2062,11 +2114,10 @@ bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state)
sibling = bgp_connection_get_sibling(connection) ; /* ... if any */
if (sibling != NULL)
- bgp_fsm_discard_sibling(sibling,
- bgp_notify_dup(connection->notification)) ;
+ bgp_fsm_discard_sibling(sibling, bgp_notify_dup(send_notification)) ;
} ;
- /* Return the (possibly adjusted) next_state */
+ /* Return the (possibly adjusted) next_state */
return next_state ;
} ;
@@ -2132,11 +2183,6 @@ bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state)
* bgp_fsm_eKeepAlive_timer_expired event.
*/
-/* Forward reference */
-static void
-bgp_timer_set(bgp_connection connection, qtimer timer, unsigned secs,
- int jitter, qtimer_action* action) ;
-
/* Forward reference the action functions */
static qtimer_action bgp_idle_hold_timer_action ;
static qtimer_action bgp_connect_retry_timer_action ;
@@ -2144,6 +2190,48 @@ static qtimer_action bgp_hold_timer_action ;
static qtimer_action bgp_keepalive_timer_action ;
/*==============================================================================
+ * Timer set functions -- general and HoldTimer specific.
+ */
+enum
+{
+ no_jitter = 0,
+ with_jitter = 1,
+} ;
+
+/*------------------------------------------------------------------------------
+ * Start or reset given qtimer with given interval, in seconds.
+ *
+ * If the interval is zero, unset the timer.
+ */
+static void
+bgp_timer_set(bgp_connection connection, qtimer timer, unsigned secs,
+ int jitter, qtimer_action* action)
+{
+ if (secs == 0)
+ qtimer_unset(timer) ;
+ else
+ {
+ secs *= 40 ; /* a bit of resolution for jitter */
+ if (jitter != no_jitter)
+ secs -= ((rand() % ((int)secs + 1)) / 4) ;
+ qtimer_set_interval(timer, QTIME(secs) / 40, action) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set HoldTimer with given time (without jitter) so will generate a
+ * Hold_Timer_expired event.
+ *
+ * Setting 0 will unset the HoldTimer.
+ */
+static void
+bgp_hold_timer_set(bgp_connection connection, unsigned secs)
+{
+ bgp_timer_set(connection, &connection->hold_timer, secs, no_jitter,
+ bgp_hold_timer_action) ;
+} ;
+
+/*==============================================================================
* Completion of State Change
*
* This performs fixed changes associated with the entry to each state from
@@ -2214,14 +2302,14 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state)
if ((sibling != NULL) && (sibling->comatose))
{
connection->comatose = 0 ; /* no longer comatose */
- bgp_timer_set(sibling, &sibling->hold_timer, interval, 1,
- bgp_idle_hold_timer_action) ;
+ bgp_timer_set(sibling, &sibling->hold_timer, interval,
+ with_jitter, bgp_idle_hold_timer_action) ;
} ;
} ;
} ;
- bgp_timer_set(connection, &connection->hold_timer, interval, 1,
- bgp_idle_hold_timer_action) ;
+ bgp_timer_set(connection, &connection->hold_timer, interval,
+ with_jitter, bgp_idle_hold_timer_action) ;
qtimer_unset(&connection->keepalive_timer) ;
@@ -2237,7 +2325,7 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state)
case bgp_fsm_sConnect:
case bgp_fsm_sActive:
bgp_timer_set(connection, &connection->hold_timer,
- session->connect_retry_timer_interval, 1,
+ session->connect_retry_timer_interval, with_jitter,
bgp_connect_retry_timer_action) ;
qtimer_unset(&connection->keepalive_timer) ;
break;
@@ -2269,8 +2357,8 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state)
*/
case bgp_fsm_sOpenConfirm:
bgp_timer_set(connection, &connection->keepalive_timer,
- connection->keepalive_timer_interval, 1,
- bgp_keepalive_timer_action) ;
+ connection->keepalive_timer_interval,
+ with_jitter, bgp_keepalive_timer_action) ;
case bgp_fsm_sEstablished:
bgp_hold_timer_set(connection, connection->hold_timer_interval) ;
break;
@@ -2294,51 +2382,8 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state)
} ;
/*==============================================================================
- * Timer set and Timer Action Functions
- */
-
-/*------------------------------------------------------------------------------
- * Start or reset given qtimer with given interval, in seconds.
- *
- * If the interval is zero, unset the timer.
- */
-static void
-bgp_timer_set(bgp_connection connection, qtimer timer, unsigned secs,
- int jitter, qtimer_action* action)
-{
- if (secs == 0)
- qtimer_unset(timer) ;
- else
- {
- secs *= 40 ; /* a bit of resolution for jitter */
- if (jitter)
- secs -= ((rand() % ((int)secs + 1)) / 4) ;
- qtimer_set_interval(timer, QTIME(secs) / 40, action) ;
- } ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Set HoldTimer with given time (without jitter) so will generate a
- * Hold_Timer_expired event.
- *
- * Setting 0 will unset the HoldTimer.
+ * Timer Action Functions
*/
-static void
-bgp_hold_timer_set(bgp_connection connection, unsigned secs)
-{
- bgp_timer_set(connection, &connection->hold_timer, secs, 0,
- bgp_hold_timer_action) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Recharge the HoldTimer
- */
-
-static void
-bgp_hold_timer_recharge(bgp_connection connection)
-{
- bgp_hold_timer_set(connection, connection->hold_timer_interval) ;
-} ;
/*------------------------------------------------------------------------------
* BGP start timer action => bgp_fsm_eBGP_Start event
@@ -2369,7 +2414,7 @@ bgp_connect_retry_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when)
BGP_FSM_DEBUG(connection, "Timer (connect timer expire)") ;
bgp_timer_set(connection, &connection->hold_timer,
- connection->session->connect_retry_timer_interval, 1, NULL) ;
+ connection->session->connect_retry_timer_interval, with_jitter, NULL) ;
bgp_fsm_event(connection, bgp_fsm_eConnectRetry_timer_expired) ;
} ;
@@ -2403,7 +2448,8 @@ bgp_keepalive_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when)
BGP_FSM_DEBUG(connection, "Timer (keepalive timer expire)") ;
bgp_timer_set(connection, &connection->keepalive_timer,
- connection->session->keepalive_timer_interval, 1, NULL) ;
+ connection->session->keepalive_timer_interval,
+ with_jitter, NULL) ;
bgp_fsm_event(connection, bgp_fsm_eKeepAlive_timer_expired) ;
} ;