diff options
Diffstat (limited to 'bgpd/bgp_fsm.c')
-rw-r--r-- | bgpd/bgp_fsm.c | 318 |
1 files changed, 174 insertions, 144 deletions
diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index cfa6561c..030c7926 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -43,6 +43,12 @@ /*============================================================================== * The BGP Finite State Machine * + * The working of BGP is described in the RFC (4271) in terms of a finite + * state machine. + * + * This code follows the older Quagga code, which appears to be based on the + * earlier RFC 1771. + * * The state machine is represented as a table, indexed by [state, event], * giving an action to be performed to deal with the event and the state that * will advance to (or stay at). @@ -63,10 +69,15 @@ * * * some I/O operations complete * + * * some I/O operations fail + * * * timers go off * * and the mechanism is to call bgp_fsm_event(). * + * However, very little calls bgp_fsm_event() directly -- mostly functions + * defined here will raise the appropriate event. + * * Note that the event is dealt with *immediately* -- there is no queueing of * events. This avoids the problem of the state of the connection being * "out of date" until its event queue is emptied, and the problem of the state @@ -95,14 +106,15 @@ * If two connections are made, only one will reach OpenConfirm state and * hence Established. * - * The session->accept flag is set iff the secondary connection is prepared to - * accept a connection. The flag is cleared as soon as a connection is - * accepted (or if something goes wrong while waiting for or making an accept() - * connection). - * * When a session is enabled, the allowed connections are initialised and * a BGP_Start event issued for each one. * + * The listeners (in bgp_network) will only accept connections from addresses + * known to be peer addresses, and then only when the accept field in the + * peer_index entry for the peer is set (see bgp_peer_index). This code looks + * after setting and clearing that pointer -- which (when set) points to the + * secondary connection of the session. + * * Up to Established 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 Established state, the primary @@ -111,13 +123,13 @@ * * As per the RFC, collision detection/resolution is performed when an OPEN * message is received -- that is, as the connection attempts to advance to - * OpenConfirm state. At that point, if the sibling is in OpenConfirm state, - * then one of the two connections is closed (and will go Idle once the + * sOpenConfirm state. At that point, if the sibling is in sOpenConfirm state, + * then one of the two connections is closed (and will go sIdle once the * NOTIFICATION has been sent). * - * See below for a discussion of the fall back to Idle -- the losing connection - * will remain comatose until the winner either reaches Established (when the - * loser is snuffed out) or the winner falls back to Idle (when the + * 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). * * NB: the RFC talks of matching source/destination and destination/source @@ -157,28 +169,28 @@ * * The FSM proceeds in three basic phases: * - * 1) attempting to establish a TCP connection: Idle/Active/Connect + * 1) attempting to establish a TCP connection: sIdle/sActive/sConnect * * In this phase there is no connection for the other end to close ! * - * Idle is a "stutter step" which becomes longer each time the FSM falls - * back to Idle, which it does if the process fails in OpenSent or - * OpenConfirm. + * sIdle is a "stutter step" which becomes longer each time the FSM falls + * back to sIdle, which it does if the process fails in sOpenSent or + * sOpenConfirm. * - * Cannot fail in Idle ! + * Cannot fail in sIdle ! * - * In Active/Connect any failure causes the FSM to stop trying to connect, - * then it does nothing further until the end of the ConnectRetryTimer + * In sActive/sConnect any failure causes the FSM to stop trying to + * connect. It does nothing further until the end of the ConnectRetryTimer * interval -- at which point it will try again, re-charging the timer. * (That is usually 120 seconds (less jitter) -- so in the worst case, it * will try to do something impossible every 90-120 seconds.) * - * A connection may fall back to Idle from OpenSent/OpenConfirm (see - * below). While one connection is OpenSent or OpenConfirm don't really + * A connection may fall back to sIdle from sOpenSent/sOpenConfirm (see + * below). While one connection is sOpenSent or sOpenConfirm don't really * want to start another TCP connection in competition. So, on entry - * to Idle: + * to sIdle: * - * * if sibling exists and is in OpenSent or OpenConfirm: + * * if sibling exists and is in sOpenSent or sOpenConfirm: * * - do not change the IdleHoldTimer interval. * - do not set the IdleHoldTimer (with jitter). @@ -194,8 +206,8 @@ * - set *its* IdleHoldTimer (with jitter). * - clear *its* comatose flag. * - * The effect is that if both connections make it to OpenSent, then only - * when *both* fall back to Idle will the FSM try to make any new TCP + * The effect is that if both connections make it to sOpenSent, then only + * when *both* fall back to sIdle will the FSM try to make any new TCP * connections. * * The IdleHoldTimer increases up to 120 seconds. In the worst case, the @@ -203,18 +215,18 @@ * drops them. In which case, the IdleHoldTimer grows, and the disruption * reduces to once every 90-120 seconds ! * - * 2) attempting to establish a BGP session: OpenSent/OpenConfirm + * 2) attempting to establish a BGP session: sOpenSent/sOpenConfirm * * If something goes wrong, or the other end closes the connection (with - * or without notification) the FSM will loop back to Idle state. Also, + * or without notification) the FSM will loop back to sIdle state. Also, * when collision resolution closes one connection it too loops back to - * Idle (see above). + * sIdle (see above). * - * Both connections may reach OpenSent. Only one at once can reach - * OpenConfirm -- collision resolution sees to that. + * Both connections may reach sOpenSent. Only one at once can reach + * sOpenConfirm -- collision resolution sees to that. * * Note that while a NOTIFICATION is being sent the connection stays - * in OpenSent/OpenConfirm state. + * in sOpenSent/sOpenConfirm state. * * 3) BGP session established * @@ -292,7 +304,7 @@ *------------------------------------------------------------------------------ * Sending NOTIFICATION message * - * In OpenSent, OpenConfirm and Established states may send a NOTIFICATION + * In sOpenSent, sOpenConfirm and sEstablished states may send a NOTIFICATION * message. * * The procedure for sending a NOTIFICATION is: @@ -308,29 +320,29 @@ * * This ensures there is room in the write buffer at the very least. * - * For OpenSent and OpenConfirm states there should be zero chance of + * For sOpenSent and sOpenConfirm states there should be zero chance of * there being anything to purge, and probably no write buffer in any * case. * - * -- purge any pending write messages for the connection (for Established). + * -- purge any pending write messages for the connection (for sEstablished). * * -- set notification_pending = 1 (write pending) * * -- write the NOTIFICATION message. * - * For Established, the message will at the very least be written to the - * write buffer. For OpenSent and OpenConfirm expect it to go directly + * For sEstablished, the message will at the very least be written to the + * write buffer. For sOpenSent and sOpenConfirm expect it to go directly * to the TCP buffer. * * -- set HoldTimer to a waiting to clear buffer time -- say 20 secs. * - * Don't expect to need to wait at all in OpenSent/OpenConfirm states. + * Don't expect to need to wait at all in sOpenSent/sOpenConfirm states. * * -- when the NOTIFICATION message clears the write buffer, that will * generate a Sent_NOTIFICATION_message event. * - * After sending the NOTIFICATION, OpenSent & OpenConfirm stay in their - * respective states. Established goes to Stopping State. + * After sending the NOTIFICATION, sOpenSent & sOpenConfirm stay in their + * respective states. sEstablished goes to sStopping State. * * When the Sent_NOTIFICATION_message event occurs, set the HoldTimer to * a "courtesy" time of 5 seconds. Remain in the current state. @@ -377,6 +389,9 @@ * are responsible for the decision to start and to stop trying to connect. */ +static void +bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event) ; + /*============================================================================== * Enable the given session -- which must be newly initialised. * @@ -425,7 +440,7 @@ bgp_fsm_enable_session(bgp_session session) } ; /*============================================================================= - * Raising exceptions. + * Signalling events and throwing exceptions. * * Before generating a BGP_Stop event the cause of the stop MUST be set for * the connection. @@ -459,6 +474,34 @@ static bgp_fsm_state_t bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state) ; /*------------------------------------------------------------------------------ + * Signal that valid OPEN message has been received and processed into the + * connection->open_recv. + */ +extern void +bgp_fsm_open_received(bgp_connection connection) +{ + bgp_fsm_event(connection, bgp_fsm_eReceive_OPEN_message) ; +} ; + +/*------------------------------------------------------------------------------ + * Signal that valid KEEPALIVE message has been received. + */ +extern void +bgp_fsm_keepalive_received(bgp_connection connection) +{ + bgp_fsm_event(connection, bgp_fsm_eReceive_KEEPALIVE_message); +} ; + +/*------------------------------------------------------------------------------ + * Signal that notification message has cleared buffers into TCP. + */ +extern void +bgp_fsm_notification_sent(bgp_connection connection) +{ + bgp_fsm_event(connection, bgp_fsm_eSent_NOTIFICATION_message) ; +} ; + +/*------------------------------------------------------------------------------ * Ultimate exception -- disable the session * * Does nothing if neither connection exists (which implies the session has @@ -490,17 +533,17 @@ bgp_fsm_disable_session(bgp_session session, bgp_notify notification) } ; /*------------------------------------------------------------------------------ - * Raise a general exception -- not I/O related. + * Throw a general exception -- not I/O related. * * Note that I/O problems are signalled by bgp_fsm_io_error(). * - * NB: can raise an exception for other connections while in the FSM. + * NB: can throw an exception for other connections while in the FSM. * - * can raise an exception for the current connection while in the FSM, the + * can throw an exception for the current connection while in the FSM, the * fsm_active/post mechanism looks after this. */ extern void -bgp_fsm_raise_exception(bgp_connection connection, bgp_session_event_t except, +bgp_fsm_general_exception(bgp_connection connection, bgp_session_event_t except, bgp_notify notification) { bgp_fsm_throw_exception(connection, except, notification, 0, @@ -645,9 +688,6 @@ bgp_fsm_connect_completed(bgp_connection connection, int err, /*------------------------------------------------------------------------------ * Post the given exception. * - * Forget the notification if not OpenSent/OpenConfirm/Established. Cannot - * send notification in any other state -- nor receive one. - * * NB: takes responsibility for the notification structure. */ static void @@ -655,14 +695,7 @@ bgp_fsm_post_exception(bgp_connection connection, bgp_session_event_t except, bgp_notify notification, int err) { connection->except = except ; - - if ( (connection->state != bgp_fsm_sOpenSent) - && (connection->state != bgp_fsm_sOpenConfirm) - && (connection->state != bgp_fsm_sEstablished) ) - bgp_notify_unset(¬ification) ; - bgp_notify_set(&connection->notification, notification) ; - connection->err = err ; } ; @@ -1510,7 +1543,7 @@ bgp_fsm[bgp_fsm_last_state + 1][bgp_fsm_last_event + 1] = */ {bgp_fsm_null, bgp_fsm_sStopping}, /* null event */ {bgp_fsm_invalid, bgp_fsm_sStopping}, /* BGP_Start */ - {bgp_fsm_invalid, bgp_fsm_sStopping}, /* BGP_Stop */ + {bgp_fsm_exit, bgp_fsm_sStopping}, /* BGP_Stop */ {bgp_fsm_invalid, bgp_fsm_sStopping}, /* TCP_connection_open */ {bgp_fsm_exit, bgp_fsm_sStopping}, /* TCP_connection_closed */ {bgp_fsm_invalid, bgp_fsm_sStopping}, /* TCP_connection_open_failed */ @@ -1558,7 +1591,7 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) ; * * */ -extern void +static void bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event) { bgp_fsm_state_t next_state ; @@ -1675,10 +1708,6 @@ bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event) static void bgp_hold_timer_set(bgp_connection connection, unsigned secs) ; -static bgp_fsm_state_t -bgp_fsm_send_notification(bgp_connection connection, - bgp_fsm_state_t next_state) ; - /*------------------------------------------------------------------------------ * Null action -- do nothing at all. */ @@ -1967,10 +1996,10 @@ static bgp_fsm_action(bgp_fsm_recv_open) */ if (connection->open_recv->bgp_id != sibling->open_recv->bgp_id) { - bgp_fsm_raise_exception(sibling, bgp_session_eOpen_reject, + bgp_fsm_general_exception(sibling, bgp_session_eOpen_reject, bgp_msg_noms_o_bad_id(NULL, sibling->open_recv->bgp_id)) ; - bgp_fsm_raise_exception(connection, bgp_session_eOpen_reject, + bgp_fsm_general_exception(connection, bgp_session_eOpen_reject, bgp_msg_noms_o_bad_id(NULL, connection->open_recv->bgp_id)) ; return connection->state ; @@ -1983,7 +2012,7 @@ static bgp_fsm_action(bgp_fsm_recv_open) : sibling ; /* Throw exception */ - bgp_fsm_raise_exception(loser, bgp_session_eCollision, + bgp_fsm_general_exception(loser, bgp_session_eCollision, bgp_notify_new(BGP_NOMC_CEASE, BGP_NOMS_C_COLLISION, 0)) ; /* If self is the loser, exit now to process the eBGP_Stop */ @@ -2140,31 +2169,36 @@ static bgp_fsm_action(bgp_fsm_exit) * * An event has been raised, and the FSM has a (default next_state). * - * 1a) notification & not eNOM_recv + * 1a) notification & not eNOM_recv & suitable state * - * Start sending the NOTIFICATION message. + * (suitable state is sOpenSent/sOpenConfirm/sEstablished. * - * NB: won't be a notification unless OpenSent/OpenConfirm/Established. + * Start sending the NOTIFICATION message. * - * For OpenSent/OpenConfirm, override the next_state to stay where it is + * 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. * * 1b) otherwise: close the connection file. * - * 2) if next state is Stopping, and not eDiscard + * If the next_state is sStopping, there is nothing else to do, so + * post an eBGP_Stop event, so that the connection exits. + * + * 2) if next state is sStopping, and not eDiscard * * This means we bring down the session, so discard any sibling. * * The sibling will send any notification, and proceed immediately to - * Stopping. + * sStopping. * * (The sibling will be eDiscard -- so no deadly embrace here.) * * The state machine takes care of the rest: * - * * complete entry to new state (for Stopping will cut connection loose). + * * complete entry to new state (for sStopping will cut connection loose). * * * send message to Routeing Engine * @@ -2173,20 +2207,85 @@ static bgp_fsm_action(bgp_fsm_exit) static bgp_fsm_state_t bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state) { + int send_notification ; + + /* Have a notification to send iff have not just received one, and are in a + * suitable state to send one at all. + */ + if (connection->except == bgp_session_eNOM_recv) + send_notification = 0 ; + 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 there is a NOTIFICATION to send, now is the time to do that. * Otherwise, close the connection but leave the timers. * * The state transition stuff looks after timers. In particular an error * in Connect/Active states leaves the ConnectRetryTimer running. */ - if ( (connection->notification != NULL) - && (connection->except != bgp_session_eNOM_recv) ) - next_state = bgp_fsm_send_notification(connection, next_state) ; + if (send_notification) + { + int ret ; + + /* If not changing to stopping, we hold in the current state until + * the NOTIFICATION process is complete. + */ + if (next_state != bgp_fsm_sStopping) + next_state = connection->state ; + + /* Close for reading and flush write buffers. */ + bgp_connection_part_close(connection) ; + + /* Write the message + * + * 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) ; + + connection->notification_pending = (ret >= 0) ; + /* is pending if not failed */ + if (ret > 0) + /* notification reached the TCP buffers instantly + * + * Send ourselves the good news ! + */ + bgp_fsm_notification_sent(connection) ; + + else if (ret == 0) + /* notification is sitting in the write buffer + * + * notification_pending is set, so write action will raise the required + * event in due course. + * + * Set the HoldTimer to something suitable. Don't really expect this + * to happen in anything except sEstablished state -- but copes. (Is + * ready to wait 20 seconds in sStopping state and 5 otherwise.) + */ + bgp_hold_timer_set(connection, + (next_state == bgp_fsm_sStopping) ? 20 : 5) ; + } else - bgp_connection_close(connection, 0) ; /* FSM deals with timers */ + { + bgp_connection_close(connection, 0) ; /* FSM deals with timers */ + if (next_state == bgp_fsm_sStopping) /* can exit if sStopping */ + bgp_fsm_event(connection, bgp_fsm_eBGP_Stop) ; + } ; - /* If stopping and not eDiscard, do in any sibling */ + /* If sStopping and not eDiscard, do in any sibling */ if ( (next_state == bgp_fsm_sStopping) && (connection->except != bgp_session_eDiscard) ) { @@ -2203,73 +2302,6 @@ bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state) return next_state ; } ; -/*------------------------------------------------------------------------------ - * Dispatch notification message - * - * Part closing the connection guarantees that can get the notification - * message into the buffers. - * - * Process will generate the following events: - * - * -- I/O failure of any sort - * -- Sent_NOTIFICATION_message - * -- HoldTimer expired - * - * When get Sent_NOTIFICATION_message, will set final "courtesy" timer, so - * unless I/O fails, final end of process is HoldTimer expired (with - * - * NB: requires the session to be locked -- connection-wise. - * - * NB: leaves the notification sitting in the connection. - */ -static bgp_fsm_state_t -bgp_fsm_send_notification(bgp_connection connection, bgp_fsm_state_t next_state) -{ - int ret ; - - /* If the next_state is not Stopping, then the sending of the notification - * holds the FSM in the current state. Will move forward when the - * HoldTimer expires -- either because lost patience in getting the - * notification away, or at the end of the "courtesy" time. - */ - if (next_state != bgp_fsm_sStopping) - next_state = connection->state ; - - /* Close for reading and flush write buffers. */ - bgp_connection_part_close(connection) ; - - /* Write the message - * - * 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) ; - - connection->notification_pending = (ret >= 0) ; - /* is pending if not failed */ - if (ret > 0) - /* notification reached the TCP buffers instantly - * - * Send ourselves the good news ! - */ - bgp_fsm_event(connection, bgp_fsm_eSent_NOTIFICATION_message) ; - - else if (ret == 0) - /* notification is sitting in the write buffer - * - * Set notification_pending so that write action will raise the required - * event in due course. - * - * Set the HoldTimer to something suitable. Don't really expect this - * to happen in anything except Established state -- but copes. (Is - * ready to wait 20 seconds in Stopping state and 5 otherwise.) - */ - bgp_hold_timer_set(connection, (next_state == bgp_fsm_sStopping) ? 20 : 5) ; - - /* Return suitable state. */ - return next_state ; -} ; - /*============================================================================== * The BGP connections timers handling. * @@ -2505,14 +2537,12 @@ bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) /* The connection is coming to an dead stop. * - * If not sending a NOTIFICATION then stop HoldTimer now. + * Leave the HoldTimer running -- may be waiting for NOTIFICATION to clear, + * or for the "courtesy" time to expire. * * Unlink connection from session. */ case bgp_fsm_sStopping: - if (!connection->notification_pending) - qtimer_unset(&connection->hold_timer) ; - qtimer_unset(&connection->keepalive_timer) ; break ; |