diff options
-rw-r--r-- | bgpd/bgp_fsm.x | 2623 | ||||
-rw-r--r-- | bgpd/bgp_session.c | 2 |
2 files changed, 1 insertions, 2624 deletions
diff --git a/bgpd/bgp_fsm.x b/bgpd/bgp_fsm.x deleted file mode 100644 index a00f40cc..00000000 --- a/bgpd/bgp_fsm.x +++ /dev/null @@ -1,2623 +0,0 @@ -/* BGP-4 Finite State Machine - * From RFC1771 [A Border Gateway Protocol 4 (BGP-4)] - * Copyright (C) 1996, 97, 98 Kunihiro Ishiguro - * - * Recast for pthreaded bgpd: Copyright (C) Chris Hall (GMCH), Highwayman - * - * This file is part of GNU Zebra. - * - * GNU Zebra is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * GNU Zebra is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GNU Zebra; see the file COPYING. If not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#include <zebra.h> -//#include "bgpd/bgp.h" - -#include "log.h" - -#include "bgpd/bgp_session.h" -#include "bgpd/bgp_connection.h" -#include "bgpd/bgp_notification.h" -#include "bgpd/bgp_fsm.h" -#include "bgpd/bgp_msg_write.h" - -#include "lib/qtimers.h" -#include "lib/sockunion.h" - -#include "bgpd/bgp_debug.h" -#include "bgpd/bgp_network.h" -#include "bgpd/bgp_dump.h" - -/*============================================================================== - * The BGP Finite State Machine - * - * 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). - * - * In some cases the action routine may override the the default new state. - * - * 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" - * - * These are raised when: - * - * * the BGP Engine receives instructions from the Routeing Engine - * - * * some I/O operations complete - * - * * timers go off - * - * and the mechanism is to call bgp_fsm_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 - * changing between the raising of an event and dealing with same. - * - * The downside is that need to deal with the possibility of events being - * raised while the FSM is in some indeterminate state while processing an - * event for the current connection. This requires a little care. - * - * Timers, messages, and qpselect actions all occur outside the FSM. So there - * is no problem there. - * - * 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. - * - * Note that there is only one level of "post" event. The FSM only ever issues - * one I/O operation per event. (It's a RULE.) - * - *------------------------------------------------------------------------------ - * Primary and Secondary Connections - * - * To support BGP's "symmetrical" open strategy, this code allows for two - * connections to be made for a session -- one connect() and one accept(). - * 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. - * - * 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 - * connection is the one that won the race -- any other connection is 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 - * 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 - * 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 - * 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 - * addresses of connections in order to detect collisions. This code - * uses only the far end address to detect collisions. It does so - * implicitly because the in-bound connection is matched with the out- - * bound one using the peer's known IP address -- effectively its name. - * - * [It is not deemed relevant if the local addresses for the in- and out- - * bound connections are different.] - * - * The RFC further says "the local system MUST examine all of its - * connections that are in OpenConfirm state" ... "If, among these - * connections, there is a connection to a remote BGP speaker whose BGP - * identifier equals the one in the OPEN message, and this connection - * collides with [it]" ... then must resolve the collision. - * - * This code almost does this, but: - * - * * there can only be one connection that collides (i.e. only one other - * which has the same remote end address), and that is the sibling - * connection. - * - * So there's not a lot of "examining" to be done. - * - * * the RFC seems to accept that there could be two distinct connections - * with the same remote end address, but *different* BGP Identifiers. - * - * As far as Quagga is concerned, that is impossible. The remote end - * IP address is the name of the peering session, and there cannot - * be two peering sessions with the same name. It follows that Quagga - * requires that the "My AS" and the "BGP Identifier" entries in the - * OPEN messages from a given remote end IP address MUST MATCH ! - * - *------------------------------------------------------------------------------ - * Exception Handling. - * - * The FSM proceeds in three basic phases: - * - * 1) attempting to establish a TCP connection: Idle/Active/Connect - * - * 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. - * - * Cannot fail in Idle ! - * - * In Active/Connect any failure causes the FSM to stop trying to connect, - * then 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 - * want to start another TCP connection in competition. So, on entry - * to Idle: - * - * * if sibling exists and is in OpenSent or OpenConfirm: - * - * - do not change the IdleHoldTimer interval. - * - do not set the IdleHoldTimer (with jitter). - * - set self "comatose". - * - * * otherwise: - * - * - increase the IdleHoldTimer interval. - * - set the IdleHoldTimer. - * - * and if sibling exists and is comatose: - * - * - 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 - * connections. - * - * The IdleHoldTimer increases up to 120 seconds. In the worst case, the - * far end repeatedly makes outgoing connection attempts, and immediately - * 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 - * - * If something goes wrong, or the other end closes the connection (with - * or without notification) the FSM will loop back to Idle state. Also, - * when collision resolution closes one connection it too loops back to - * Idle (see above). - * - * Both connections may reach OpenSent. Only one at once can reach - * OpenConfirm -- collision resolution sees to that. - * - * Note that while a NOTIFICATION is being sent the connection stays - * in OpenSent/OpenConfirm state. - * - * 3) BGP session established - * - * If something goes wrong, or the other end closes the connection - * (with or without notification) will stop the session. - * - * When things do go wrong, one of the following events is generated: - * - * a. BGP_Stop -- general exception - * - * The function bgp_fsm_exception() sets the reason for the exception and - * raises an BGP_Stop event. - * - * Within the FSM, bgp_fsm_set_exception() sets the reason for the - * exception and ..... - * connections. These may be used, for example, to signal that an UPDATE - * message is invalid. - * - * See below for further discussion of BGP_Stop. - * - * (The FSM itself uses bgp_fsm_set_stopping() before moving to - * Stopping state.) - * - * b. TCP_connection_closed ("soft" error) - * - * A read or write operation finds that the connection has been closed. - * - * This is raised when a read operation returns 0 bytes. - * - * Is also raised when read or write see the errors: - * - * ECONNRESET, ENETDOWN, ENETUNREACH, EPIPE or ETIMEDOUT - * - * Other errors are reported as TCP_fatal_error. - * - * The function bgp_fsm_io_error() is used by read and write operations to - * signal an error -- it decides which event to generate. (Error == 0 is - * used to signal a read operation that has returned 0.) - * - * c. TCP_connection_open_failed ("soft" error) - * - * A connect() operation has failed: - * - * ECONNREFUSED, ECONNRESET, EHOSTUNREACH or ETIMEDOUT - * - * Other errors are reported as TCP_fatal_error. - * - * The function bgp_fsm_connect_completed() decides what event to generate. - * (It will generate TCP_connection_open if there is no error.) - * - * All errors that accept() may raise are fatal. - * - * d. TCP_fatal_error ("hard" error) - * - * Raised by unexpected errors in connect/accept/read/write - * - * The function bgp_fsm_io_fatal_error() will generate a TCP_fatal_error. - * - * Things may also go wrong withing the FSM. - * - * The procedure for dealing with an exception - * - *------------------------------------------------------------------------------ - * FSM errors - * - * Invalid events: if the FSM receives an event that cannot be raised in the - * current state, it will terminate the session, sending an FSM Error - * NOTIFICATION (if a TCP connection is up). See bgp_fsm_invalid(). - * - * If the FSM receives a message type that is not expected in the current, - * state, it will close the connection (if OpenSent or OpenConfirm) or stop - * the session (if Established), also sending an FSM Error NOTIFICATION. - * See bgp_fsm_error(). - * - *------------------------------------------------------------------------------ - * Sending NOTIFICATION message - * - * In OpenSent, OpenConfirm and Established states may send a NOTIFICATION - * message. - * - * The procedure for sending a NOTIFICATION is: - * - * -- close the connection for reading and clear read buffers. - * - * This ensures that no further read I/O can occur and no related events. - * - * Note that anything sent from the other end is acknowledged, but - * quietly discarded. - * - * -- purge the write buffer of all output except any partly sent message. - * - * This ensures there is room in the write buffer at the very least. - * - * For OpenSent and OpenConfirm 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). - * - * -- 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 - * 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. - * - * -- 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. - * - * When the Sent_NOTIFICATION_message event occurs, set the HoldTimer to - * a "courtesy" time of 5 seconds. Remain in the current state. - * - * During the "courtesy" time the socket will continue to acknowledge, but - * discard input. In the case of Collision Resolution this gives a little time - * for the other end to send its NOTIFICATION message. In all cases, it gives - * a little time before the connection is slammed shut. - * - * When the HoldTimer expires close the connection completely (whether or not - * the NOTIFICATION has cleared the write buffer). - * - *------------------------------------------------------------------------------ - * Communication with the Routeing Engine - * - * The FSM sends the following messages to the Routeing Engine: - * - * * bgp_session_event messages - * - * These keep the Routeing Engine up to date with the progress and state of - * the FSM. - * - * In particular, these event messages tell the Routeing Engine when the - * session enters and leaves sEstablished -- which is what really matters - * to it ! - * - * * bgp_session_update - * - * Each time an update message arrives from the peer, it is forwarded. - * - * TODO: flow control for incoming updates ?? - * - * Three things bring the FSM to a dead stop, and stop the session: - * - * 1) administrative Stop -- ie the Routeing Engine disabling the session. - * - * 2) invalid events -- which are assumed to be bugs, really. - * - * 3) anything that stops the session while in Established state. - * - * This means that the FSM will plough on trying to establish connections with - * configured peers, even in circumstances when the likelihood of success - * appears slim to vanishing. However, the Routeing Engine and the operator - * are responsible for the decision to start and to stop trying to connect. - */ - -/*============================================================================== - * Enable the given session -- which must be newly initialised. - * - * This is the first step in the FSM, and the connection advances to Idle. - * - * Returns in something of a hurry if not enabled for connect() or for accept(). - * - * NB: requires the session LOCKED - */ -extern void -bgp_fsm_enable_session(bgp_session session) -{ - bgp_connection connection ; - - /* Proceed instantly to a dead stop if neither connect nor listen ! */ - if (!(session->connect || session->listen)) - { - bgp_session_event(session, bgp_session_eInvalid, NULL, 0, 0, 1) ; - return ; - } ; - - /* Primary connection -- if connect allowed - * - * NB: the start event for the primary connection is guaranteed to succeed, - * and nothing further will happen until the initial IdleHoldTimer - * expires -- always has a small, non-zero time. - * - * This ensures that the secondary connection can be started before - * there's any change of the session being torn down !! - */ - if (session->connect) - { - connection = bgp_connection_init_new(NULL, session, - bgp_connection_primary) ; - bgp_fsm_event(connection, bgp_fsm_BGP_Start) ; - } ; - - /* Secondary connection -- if listen allowed - */ - if (session->listen) - { - connection = bgp_connection_init_new(NULL, session, - bgp_connection_secondary) ; - bgp_fsm_event(connection, bgp_fsm_BGP_Start) ; - } ; -} ; - - /*============================================================================= - * Raising exceptions. - * - * Before generating a BGP_Stop event the cause of the stop MUST be set for - * the connection. - * - * In Established state any BGP_Stop closes the connection and stops the - * session -- sending a NOTIFICATION. - * - * In other states, the cause affects the outcome: - * - * * bgp_stopped_admin -- send NOTIFICATION to all connections - * go to Stopping state and stop the session. - * (once any NOTIFICATION has cleared, terminates - * the each connection.) - * - * * bgp_stopped_collision -- send NOTIFICATION - * close connection & fall back to Idle - * (can only happen in OpenSent/OpenConfirm) - * - * * otherwise -- if TCP connection up: - * send NOTIFICATION - * close connection & fall back to Idle - * otherwise - * do nothing (stay in Idle/Connect/Active) - */ - -static void -bgp_fsm_throw_exception(bgp_connection connection, bgp_session_event_t except, - bgp_notify notification, int err, bgp_fsm_event_t event) ; - -static bgp_fsm_state_t -bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state) ; - -/*------------------------------------------------------------------------------ - * Ultimate exception -- disable the session - * - * Does nothing if neither connection exists (which implies the session has - * already been disabled, or never got off the ground). - * - * NB: takes responsibility for the notification structure. - * - * NB: requires the session LOCKED - */ -extern void -bgp_fsm_disable_session(bgp_session session, bgp_notify notification) -{ - bgp_connection connection ; - - connection = session->connections[bgp_connection_primary] ; - if (connection == NULL) - connection = session->connections[bgp_connection_secondary] ; - - if (connection != NULL) - bgp_fsm_throw_exception(connection, bgp_session_eDisabled, notification, 0, - bgp_fsm_BGP_Stop) ; - else - bgp_notify_free(¬ification) ; -} ; - -/*------------------------------------------------------------------------------ - * Raise a general exception -- not I/O related. - * - * Note that I/O problems are signalled by bgp_fsm_io_error(). - * - * NB: may NOT be used within the FSM. - */ -extern void -bgp_fsm_raise_exception(bgp_connection connection, bgp_session_event_t except, - bgp_notify notification) -{ - bgp_fsm_throw_exception(connection, except, notification, 0, - bgp_fsm_BGP_Stop) ; -} ; - -/*------------------------------------------------------------------------------ - * Raise a discard exception for sibling. - * - * A connection will discard any sibling if: - * - * * the session is being disabled (by the Peering Engine) - * - * * an invalid event is bringing down the session - * - * * it has reached Established state, and is snuffing out the sibling. - * - * - * - * NB: requires the session LOCKED - */ -static void -bgp_fsm_discard_sibling(bgp_connection sibling, bgp_notify notification) -{ - bgp_fsm_throw_exception(sibling, bgp_session_eDiscard, - notification, 0, bgp_fsm_BGP_Stop) ; -} ; - -/*------------------------------------------------------------------------------ - * Raise a NOTIFICATION received exception - */ -extern void -bgp_fsm_notification_exception(bgp_connection connection, - bgp_notify notification) -{ - bgp_fsm_throw_exception(connection, bgp_session_eNOM_recv, notification, 0, - bgp_fsm_Receive_NOTIFICATION_message) ; -} ; - -/*------------------------------------------------------------------------------ - * Raise a "fatal I/O error" exception on the given connection. - * - * Error to be reported as "TCP_fatal_error". - */ -extern void -bgp_fsm_io_fatal_error(bgp_connection connection, int err) -{ - plog_err (connection->log, "%s [Error] bgp IO error: %s", - connection->host, safe_strerror(err)) ; - - bgp_fsm_throw_exception(connection, bgp_session_eTCP_error, NULL, err, - bgp_fsm_TCP_fatal_error) ; -} ; - -/*------------------------------------------------------------------------------ - * Raise an "I/O error" exception on the given connection. - * - * This is used by read/write operations -- so not until the TCP connection - * is up (which implies OpenSent state or later). - * - * It is assumed that the read/write code deals with EAGAIN/EWOULDBLOCK/EINTR - * itself -- so only real errors are reported here. - * - * A read operation that returns zero is reported here as err == 0. - * - * The following are reported as "TCP_connection_closed": - * - * 0, ECONNRESET, ENETDOWN, ENETUNREACH, EPIPE or ETIMEDOUT - * - * All other errors are reported as "TCP_fatal_error". - */ -extern void -bgp_fsm_io_error(bgp_connection connection, int err) -{ - if ( (err == 0) - || (err == ECONNRESET) - || (err == ENETDOWN) - || (err == ENETUNREACH) - || (err == EPIPE) - || (err == ETIMEDOUT) ) - { - if (BGP_DEBUG(events, EVENTS)) - { - if (err == 0) - plog_debug(connection->log, - "%s [Event] BGP connection closed fd %d", - connection->host, qps_file_fd(&connection->qf)) ; - else - plog_debug(connection->log, - "%s [Event] BGP connection closed fd %d (%s)", - connection->host, qps_file_fd(&connection->qf), - safe_strerror(err)) ; - } ; - - bgp_fsm_throw_exception(connection, bgp_session_eTCP_dropped, NULL, err, - bgp_fsm_TCP_connection_closed) ; - } - else - bgp_fsm_io_fatal_error(connection, err) ; -} ; - -/*------------------------------------------------------------------------------ - * Signal completion of a connect() or an accept() for the given connection. - * - * This is used by the connect() and accept() qpselect actions. It is also - * used if a connect() attempt fails immediately. - * - * If err == 0, then all is well: copy the local and remote sockunions - * and generate TCP_connection_open event - * - * If err is one of: - * - * ECONNREFUSED, ECONNRESET, EHOSTUNREACH or ETIMEDOUT - * - * generate TCP_connection_open_failed event. (accept() does not return any of - * these errors.) - * - * Other errors are reported as TCP_fatal_error. - */ -extern void -bgp_fsm_connect_completed(bgp_connection connection, int err, - union sockunion* su_local, - union sockunion* su_remote) -{ - if (err == 0) - { - bgp_fsm_event(connection, bgp_fsm_TCP_connection_open) ; - - connection->su_local = sockunion_dup(su_local) ; - connection->su_remote = sockunion_dup(su_remote) ; - } - else if ( (err == ECONNREFUSED) - || (err == ECONNRESET) - || (err == EHOSTUNREACH) - || (err == ETIMEDOUT) ) - bgp_fsm_throw_exception(connection, bgp_session_eTCP_failed, NULL, err, - bgp_fsm_TCP_connection_open_failed) ; - else - bgp_fsm_io_fatal_error(connection, 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 -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_OpenSent) - && (connection->state != bgp_fsm_OpenConfirm) - && (connection->state != bgp_fsm_Established) ) - bgp_notify_free(¬ification) ; - - bgp_notify_set(&connection->notification, notification) ; - - connection->err = err ; -} ; - -/*------------------------------------------------------------------------------ - * Post the given exception and raise the given event. - * - * NB: takes responsibility for the notification structure. - */ -static void -bgp_fsm_throw_exception(bgp_connection connection, bgp_session_event_t except, - bgp_notify notification, int err, bgp_fsm_event_t event) -{ - bgp_fsm_post_exception(connection, except,notification, err) ; - bgp_fsm_event(connection, event) ; -} ; - -/*------------------------------------------------------------------------------ - * Post and immediately catch a non-I/O exception. - * - * For use WITHIN the FSM. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_state_t -bgp_fsm_post_catch(bgp_connection connection, bgp_session_event_t except, - bgp_notify notification, bgp_fsm_state_t next_state) -{ - bgp_fsm_post_exception(connection, except, notification, 0) ; - return bgp_fsm_catch(connection, next_state) ; -} ; - -/*============================================================================== - * For debug... - */ -#define BGP_FSM_DEBUG(connection, message) \ - if (BGP_DEBUG (fsm, FSM)) \ - plog_debug (connection->log, "%s [FSM] " message, connection->host) - -/*============================================================================== - * The FSM table - */ - -#define bgp_fsm_action(FUNCNAME) \ - bgp_fsm_state_t FUNCNAME(bgp_connection connection, \ - bgp_fsm_state_t next_state, bgp_fsm_event_t event) - -typedef bgp_fsm_action(bgp_fsm_action_func) ; - -struct bgp_fsm -{ - bgp_fsm_action_func* action ; - bgp_fsm_state_t next_state ; -} ; - -static bgp_fsm_action(bgp_fsm_null) ; -static bgp_fsm_action(bgp_fsm_enter) ; -static bgp_fsm_action(bgp_fsm_stop) ; -static bgp_fsm_action(bgp_fsm_invalid) ; -static bgp_fsm_action(bgp_fsm_start) ; -static bgp_fsm_action(bgp_fsm_send_open) ; -static bgp_fsm_action(bgp_fsm_failed) ; -static bgp_fsm_action(bgp_fsm_fatal) ; -static bgp_fsm_action(bgp_fsm_retry) ; -static bgp_fsm_action(bgp_fsm_closed) ; -static bgp_fsm_action(bgp_fsm_expire) ; -static bgp_fsm_action(bgp_fsm_recv_open) ; -static bgp_fsm_action(bgp_fsm_error) ; -static bgp_fsm_action(bgp_fsm_recv_nom) ; -static bgp_fsm_action(bgp_fsm_sent_nom) ; -static bgp_fsm_action(bgp_fsm_send_kal) ; -static bgp_fsm_action(bgp_fsm_establish) ; -static bgp_fsm_action(bgp_fsm_recv_kal) ; -static bgp_fsm_action(bgp_fsm_update) ; -static bgp_fsm_action(bgp_fsm_exit) ; - -/*------------------------------------------------------------------------------ - * Finite State Machine events - * - * 0. null_event - * - * Do nothing. As quietly as possible. - * - * Never generated, so should not be seen ! - * - * 1. BGP_Start - * - * a. in Initial state (-> Idle) - * - * raised immediately after creating the connection. - * - * b. in Idle state - * - * raised on expiry of IdleHoldTime. - * - * primary connection: proceed to Connect - * - * secondary connection: proceed to Accept - * - * Cannot happen at any other time. - * - * 2. BGP_Stop - * - * a. in all states: - * - * -- from Routeing Engine -- at any time. - * - * -- internally in the event of collision resolution. - * - * -- internally, in the event of some protocol error -- once - * connection is up and packets are being transferred. - * - * See above for further discussion. - * - * 3. TCP_connection_open - * - * a. primary connection: in Connect state (-> OpenSent) - * - * raised when a connect() connection succeeds - * - * b. secondary connection: in Active state (-> OpenSent) - * - * raised when an accept() connection is accepted. - * - * Cannot happen at any other time. - * - * 4. TCP_connection_closed - * - * Raised by "EOF" on read or by EPIPE and some other errors. - * - * a. in OpenSent and OpenConfirm states - * - * This may be because the the other end has detected a collision. - * It may be because the other end is being vexatious. - * - * Fall back to Idle. - * - * b. and Established state - * - * Stop the session. - * - * NB: any errors generated when the OPEN message is sent (on exit from - * Connect or Active states) are not delivered until has entered - * OpenSent state. - * - * Cannot happen at any other time. - * - * 5. TCP_connection_open_failed ("soft" error) - * - * a. in Connect or Active states - * - * Close the connection. For Active state, disable accept. - * - * Stay in Connect/Active (until ConnectRetryTimer expires). - * - * Cannot happen at any other time. - * - * 6. TCP_fatal_error ("hard" error) - * - * a. in Connect or Active states - * - * Close the connection. For Active state, disable accept. - * - * Stay in Connect/Active (until ConnectRetryTimer expires). - * - * b. in OpenSent/OpenConfirm states - * - * Close the connection. - * - * Fall back to Idle. - * - * c. in Established state - * - * Close the connection and the session. - * - * Go to Stopping state. - * - * d. in Stopping state. - * - * Close the connection. - * - * Cannot happen at any other time (ie Idle). - * - * 7. ConnectRetry_timer_expired - * - * a. in either Connect or Active states ONLY. - * - * Time to give up current connection attempt(s), and start trying - * to connect all over again. - * - * Cannot happen at any other time. - * - * 8. Hold_Timer_expired - * - * a. in OpenSent state - * - * Time to give up waiting for an OPEN (or NOTIFICATION) from the - * other end. For this wait the RFC recommends a "large" value for - * the hold time -- and suggests 4 minutes. - * - * Or, if the connection was stopped by the collision resolution - * process, time to close the connection. - * - * Close the connection. Fall back to Idle. - * - * b. in OpenConfirm state - * - * Time to give up waiting for a KEEPALIVE to confirm the connection. - * For this wait the hold time used is that negotiated in the OPEN - * messages that have been exchanged. - * - * Or, if the connection was stopped by the collision resolution - * process, time to close the connection. - * - * Close the connection. Fall back to Idle. - * - * c. in Established state - * - * The session has failed. Stop. - * - * In this state the hold time used is that negotiated in the OPEN - * messages that have been exchanged. - * - * d. in Stopping state - * - * Time to give up trying to send NOTIFICATION and terminate the - * connection. - * - * Cannot happen at any other time. - * - * 9. KeepAlive_timer_expired - * - * a. in OpenConfirm and Established states - * - * Time to send a KEEPALIVE message. - * - * In these states the keepalive time used is that which follows - * from the hold time negotiated in the OPEN messages that have been - * exchanged. - * - * Cannot happen at any other time. - * - * 10. Receive_OPEN_message - * - * Generated by read action. - * - * a. in OpenSent state -- the expected response - * - * Proceed (via collision resolution) to OpenConfirm or Stopping. - * - * b. in OpenConfirm state -- FSM error - * - * Send NOTIFICATION. Fall back to Idle. - * - * c. in Established state -- FSM error - * - * Send NOTIFICATION. Terminate session. - * - * Cannot happen at any other time (connection not up or read closed). - * - * 11. Receive_KEEPALIVE_message - * - * Generated by read action. - * - * a. in OpenSent state -- FSM error - * - * Send NOTIFICATION. Fall back to Idle. - * - * b. in OpenConfirm state -- the expected response - * - * c. in Established state -- expected - * - * Cannot happen at any other time (connection not up or read closed). - * - * 12. Receive_UPDATE_message - * - * Generated by read action. - * - * a. in OpenSent and OpenConfirm states -- FSM error - * - * Send NOTIFICATION. Fall back to Idle. - * - * b. in Established state -- expected - * - * Cannot happen at any other time (connection not up or read closed). - * - * 13. Receive_NOTIFICATION_message - * - * Generated by read action. - * - * a. in OpenSent, OpenConfirm and Established states -- give up - * on the session. - * - * a. in OpenSent, OpenConfirm and Established states -- give up - * on the session. - * - * Cannot happen at any other time (connection not up or read closed). - * - * 14. Sent_NOTIFICATION_message - * - * Generated by write action when completed sending the message. - * - * a. in Stopping state -- the desired outcome - * - * Terminate the connection. - * - * Cannot happen at any other time. - */ - -/*------------------------------------------------------------------------------ - * Finite State Machine Table - */ - -static const struct bgp_fsm -bgp_fsm[bgp_fsm_last_state + 1][bgp_fsm_last_event + 1] = -{ - { - /* bgp_fsm_Initial: initialised in this state............................... - * - * Expect only a BGP_Start event, which arms the IdleHoldTimer and advances - * to the Idle state. - * - * Could (just) get a bgp_fsm_Stop if other connection stops immediately ! - * - * A connection should be in this state for a brief period between being - * initialised and set going. - * - * All other events (other than null) are invalid (should not happen). - */ - {bgp_fsm_null, bgp_fsm_Initial}, /* null event */ - {bgp_fsm_enter, bgp_fsm_Idle}, /* BGP_Start */ - {bgp_fsm_stop, bgp_fsm_Initial}, /* BGP_Stop */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_closed */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_fatal_error */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Hold_Timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ - }, - { - /* bgp_fsm_Idle: waiting for IdleHoldTimer.................................. - * - * When a session is enabled both its connections start in this state. - * (Noting that an accept() only session starts only the secondary - * connection and a connect() only session starts only the primary.) - * - * While in this state is waiting for the IdleHoldTimer to expire. This - * timer becomes longer if the peer misbehaves. - * - * If a connection stops at OpenState or OpenConfirm, may loop back through - * Idle, with an increased IdleHoldTimer. - * - * In Idle state the connection is dormant. (While the secondary is Idle, - * no connections will be accepted.) - * - * If the peer keeps making or accepting TCP connections, and then dropping - * them, then the IdleHoldTimer will grow to slow down the rate of vexatious - * connections. - * - * When a connection falls back to Idle it will have been closed. - * - * The expected events are: - * - * * BGP_Start -- generated by IdleHoldTimer expired - * - * For primary connection: - * - * Causes a connect() to be attempted. - * - * * Connect state -- if connect() OK, or failed "soft" - * - * * Stopping state -- if connect() failed "hard" - * - * Bring connection and session to a dead stop. - * - * For secondary connection: - * - * Enables session->accept, and goes to "Active" state. - * - * Note that bgp_fsm_start() decides on the appropriate next state. - * - * * BGP_Stop -- for whatever reason - * - * All other events (other than null) are invalid (should not happen). - */ - {bgp_fsm_null, bgp_fsm_Idle}, /* null event */ - {bgp_fsm_start, bgp_fsm_Connect}, /* BGP_Start */ - {bgp_fsm_stop, bgp_fsm_Idle}, /* BGP_Stop */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_closed */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_fatal_error */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Hold_Timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ - }, - { - /* bgp_fsm_Connect: waiting for connect (and listen)........................ - * - * Only the primary connection can be in this state. - * - * While in this state is waiting for connection to succeed or fail, or for - * the ConnectRetryTimer to expire. - * - * The expected events are: - * - * * TCP_connection_open - * - * Send BGP OPEN message, arm the HoldTimer ("large" value) and advance - * to OpenSent. - * - * * TCP_connection_open_fail ("soft" error) - * - * Shut down the connection. Stay in Connect state. - * - * The ConnectRetryTimer is left running. - * - * * TCP_fatal_error ("hard" error) - * - * Bring connection and session to a dead stop. - * - * * ConnectRetry_timer_expired - * - * Shut down the connection. Retry opening a connection. Stay in - * Connect state. Refresh the ConnectRetryTimer. - * - * * BGP_Stop -- for whatever reason - * - * All other events (other than null) are invalid (should not happen). - */ - {bgp_fsm_null, bgp_fsm_Connect}, /* null event */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ - {bgp_fsm_stop, bgp_fsm_Connect}, /* BGP_Stop */ - {bgp_fsm_send_open, bgp_fsm_OpenSent}, /* TCP_connection_open */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_closed */ - {bgp_fsm_failed, bgp_fsm_Connect}, /* TCP_connection_open_failed */ - {bgp_fsm_fatal, bgp_fsm_Connect}, /* TCP_fatal_error */ - {bgp_fsm_retry, bgp_fsm_Connect}, /* ConnectRetry_timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Hold_Timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ - }, - { - /* bgp_fsm_Active: waiting for listen (only)................................ - * - * Only the secondary connection can be in this state. - * - * While in this state is waiting for an incoming connection to succeed or - * for the ConnectRetryTimer to expire. - * - * The expected events are: - * - * * TCP_connection_open - * - * Send BGP OPEN message, arm the HoldTimer ("large" value) and advance - * to OpenSent. - * - * * TCP_connection_open_fail ("soft" error) - * - * Shut down the connection. Stay in Connect state. - * - * The ConnectRetryTimer is left running. - * - * * TCP_fatal_error - * - * Bring connection and session to a dead stop. - * - * * ConnectRetry_timer_expired - * - * Stay in Active state. Refresh the ConnectRetryTimer. - * - * * BGP_Stop -- for whatever reason - * - * All other events (other than null) are invalid (should not happen). - */ - {bgp_fsm_null, bgp_fsm_Active}, /* null event */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ - {bgp_fsm_stop, bgp_fsm_Active}, /* BGP_Stop */ - {bgp_fsm_send_open, bgp_fsm_OpenSent}, /* TCP_connection_open */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_closed */ - {bgp_fsm_failed, bgp_fsm_Active}, /* TCP_connection_open_failed */ - {bgp_fsm_fatal, bgp_fsm_Active}, /* TCP_fatal_error */ - {bgp_fsm_retry, bgp_fsm_Active}, /* ConnectRetry_timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Hold_Timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ - }, - { - /* bgp_fsm_OpenSent: waiting for Open from the other end.................... - * - * Both primary and secondary connections can be in this state. - * - * While in this state is waiting for a BGP OPEN to arrive or for the - * HoldTimer ("large" value) to expire. - * - * The expected events are: - * - * * Receive_OPEN_message - * - * This means has received a satisfactory BGP OPEN from the other end, - * so the session is very nearly up. - * - * If there is another connection, and it is in OpenConfirm state, - * then must now choose between the two -- terminating one or the - * other with a "Connection Collision Resolution" NOTIFICATION message. - * - * If proceeding, send a BGP KEEPALIVE message (effectively ACK), arm - * HoldTimer and KeepliveTimer (as per negotiated values) and advance - * to OpenConfirm state. - * - * * Receive_UPDATE_message - * - * FSM error -- bring connection to a dead stop. - * - * * Receive_KEEPALIVE_message - * - * FSM error -- bring connection to a dead stop. - * - * * Receive_NOTIFICATION_message - * - * Bring connection to a dead stop. - * - * * TCP_connection_closed - * - * Close connection, - * - * * TCP_fatal_error - * - * Bring connection and session to a dead stop. - * - * * Hold_Timer_expired - * - * If primary, promote the secondary. If no secondary... - * - * * BGP_Stop -- for whatever reason - * - * All other events (other than null) are invalid (should not happen). - */ - {bgp_fsm_null, bgp_fsm_OpenSent}, /* null event */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ - {bgp_fsm_stop, bgp_fsm_Idle}, /* BGP_Stop */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ - {bgp_fsm_closed, bgp_fsm_Idle}, /* TCP_connection_closed */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ - {bgp_fsm_fatal, bgp_fsm_Idle}, /* TCP_fatal_error */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ - {bgp_fsm_expire, bgp_fsm_Idle}, /* Hold_Timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ - {bgp_fsm_recv_open, bgp_fsm_OpenConfirm}, /* Receive_OPEN_message */ - {bgp_fsm_error, bgp_fsm_OpenSent}, /* Receive_KEEPALIVE_message */ - {bgp_fsm_error, bgp_fsm_OpenSent}, /* Receive_UPDATE_message */ - {bgp_fsm_recv_nom, bgp_fsm_Idle}, /* Receive_NOTIFICATION_message */ - {bgp_fsm_sent_nom, bgp_fsm_OpenSent}, /* Sent NOTIFICATION message */ - }, - { - /* bgp_fsm_OpenConfirm: Opens sent and received, waiting for KeepAlive...... - * - * Only one of the two connections can reach this state. - * - * While in this state is waiting for a BGP KEEPALIVE to arrive or for the - * HoldTimer to expire, or for the KeepaliveTimer to prompt sending of - * another KEEPALIVE message. - * - * The expected events are: - * - * * Receive_KEEPALIVE_message - * - * This means that the other end is acknowledging the OPEN, and the - * session is now Established. - * - * If there is another connection, now is the time to kill it off. - * - * This connection becomes the primary and only connection. - * - * Arm HoldTimer and KeepliveTimer (as per negotiated values) and - * advance to Established state. - * - * Pass a session established message to the Routeing Engine, complete - * with the bgp_open_state for the successful connection. - * - * * Receive_OPEN_message - * - * FSM error -- bring connection to a dead stop. - * - * If primary, promote the secondary. If no secondary... - * - * * Receive_UPDATE_message - * - * FSM error -- bring connection to a dead stop. - * - * If primary, promote the secondary. If no secondary... - * - * * Receive_NOTIFICATION_message - * - * Bring connection to a dead stop. - * - * If primary, promote the secondary. If no secondary... - * - * * TCP_connection_closed - * - * If primary, promote the secondary. If no secondary... - * - * * TCP_fatal_error - * - * Bring connection and session to a dead stop. - * - * * KeepAlive_Timer_expired - * - * Send KEEPALIVE message and recharge KeepaliveTimer. - * - * * Hold_Timer_expired - * - * If primary, promote the secondary. If no secondary... - * - * * BGP_Stop -- for whatever reason - * - * All other events (other than null) are invalid (should not happen). - */ - {bgp_fsm_null, bgp_fsm_OpenConfirm}, /* null event */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ - {bgp_fsm_stop, bgp_fsm_Idle}, /* BGP_Stop */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ - {bgp_fsm_closed, bgp_fsm_Idle}, /* TCP_connection_closed */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ - {bgp_fsm_fatal, bgp_fsm_Idle}, /* TCP_fatal_error */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ - {bgp_fsm_expire, bgp_fsm_Idle}, /* Hold_Timer_expired */ - {bgp_fsm_send_kal, bgp_fsm_OpenConfirm}, /* KeepAlive_timer_expired */ - {bgp_fsm_error, bgp_fsm_OpenConfirm}, /* Receive_OPEN_message */ - {bgp_fsm_establish, bgp_fsm_Established}, /* Receive_KEEPALIVE_message */ - {bgp_fsm_error, bgp_fsm_OpenConfirm}, /* Receive_UPDATE_message */ - {bgp_fsm_recv_nom, bgp_fsm_Idle}, /* Receive_NOTIFICATION_message */ - {bgp_fsm_sent_nom, bgp_fsm_OpenConfirm}, /* Sent NOTIFICATION message */ - }, - { - /* bgp_fsm_Established: session is up and running........................... - * - * Only the primary connection exists in this state. - * - * While in this state is waiting for a BGP UPDATE (or KEEPALIVE) messages - * to arrive or for the HoldTimer to expire, or for the KeepaliveTimer to - * prompt sending of another KEEPALIVE message. - * - * The expected events are: - * - * * Receive_OPEN_message - * - * FSM error -- bring connection and session to a dead stop. - * - * * Receive_UPDATE_message - * - * Restart the HoldTimer. - * - * * Receive_KEEPALIVE_message - * - * Restart the HoldTimer. - * - * * Receive_NOTIFICATION_message - * - * Bring connection and session to a dead stop. - * - * * TCP_connection_closed - * - * Bring connection and session to a dead stop. - * - * * TCP_fatal_error - * - * Bring connection and session to a dead stop. - * - * * KeepAlive_Timer_expired - * - * Send KEEPALIVE message and recharge KeepaliveTimer. - * - * * Hold_Timer_expired - * - * If primary, promote the secondary. If no secondary... - * - * * BGP_Stop -- for whatever reason - * - * All other events (other than null) are invalid (should not happen). - */ - {bgp_fsm_null, bgp_fsm_Established}, /* null event */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ - {bgp_fsm_stop, bgp_fsm_Stopping}, /* BGP_Stop */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ - {bgp_fsm_closed, bgp_fsm_Stopping}, /* TCP_connection_closed */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ - {bgp_fsm_fatal, bgp_fsm_Stopping}, /* TCP_fatal_error */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ - {bgp_fsm_expire, bgp_fsm_Stopping}, /* Hold_Timer_expired */ - {bgp_fsm_send_kal, bgp_fsm_Established}, /* KeepAlive_timer_expired */ - {bgp_fsm_error, bgp_fsm_Stopping}, /* Receive_OPEN_message */ - {bgp_fsm_recv_kal, bgp_fsm_Established}, /* Receive_KEEPALIVE_message */ - {bgp_fsm_update, bgp_fsm_Established}, /* Receive_UPDATE_message */ - {bgp_fsm_recv_nom, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ - }, - { - /* bgp_fsm_Stopping: waiting (briefly) to send Notification................. - * - * Before a connection is sent to Stopping state the reasons for stopping - * are set. (See bgp_fsm_set_stopping.) - * - * There are three ways to arrive in Stopping state: - * - * a) administrative Stop -- that is, the Routeing Engine is stopping the - * session. - * - * Both connections must be stopped. - * - * b) the sibling has reached Established state and is snuffing out - * its rival. - * - * Only the current connection must be stopped. - * - * c) the session was Established, but is now stopping. - * - * There is only one connection, and that must be stopped. - * - * Before the transition to Stopping state, - * - * The complication is the possible need to send a NOTIFICATION message - * before closing the connection. - * - * Once a connection has reached Established state, the TCP write buffers - * may be full, so it may not be possible immediately to send the - * NOTIFICATION. Note that stopping from Established state is always - * stop-dead. - * - * In other states there should be plenty of room in the TCP write buffers. - * - * On entry to Stopping: - * - * 1) if this is stop-dead -- unlink self from session. - * - * NB: this clears the pointer from session to connection. - * - * .... - * - * 2) if there is a NOTIFICATION message (notification_pending): - * - * * close the connection for reading and purge read buffers - * * purge the write buffering and any pending writes - * * stop all timers - * * send the NOTIFICATION - * - * if the NOTIFICATION immediately clears the buffers (or fails), - * clear the notification_pending flag. - * - * 3) if the notification_pending flag is still set: - * - * * for stop-idle set a short time-out (5 seconds) - * * for stop-dead set a longer time-out (30 seconds) - * - * stays in Stopping state, waiting for NOTIFICATION to be sent, or - * to fail, or for the timeout. - * - * (Should not really need the time-out for stop-idle, but seems - * neater than crash closing the connection.) - * - * While in Stopping state, any further event will clear the - * notification-pending flag. - * - * When the notification-pending flag is not set: - * - * * close the connection - * * purge all buffering - * * stop all timers - * - * * for stop-idle: proceed to Idle state - - - - - - * In this state the connection is no longer associated with a session. - * - * This state exists only to allow the TCP output buffer to drain - * sufficiently to allow the tail end of one BGP message to be sent, - * followed by a NOTIFICATION message. - * - * When entering this state, if there is no NOTIFICATION to send, then - * will terminate the session. - * - * While in this state is waiting for the NOTIFICATION message to have been - * sent, or for the HoldTimer to expire (does not wait indefinitely). - * - * The expected events are: - * - * * Sent NOTIFICATION message - * * Hold_Timer_expired - * * TCP_fatal_error - * * TCP_connection_closed - * - * Clear NOTIFICATION pending, so connection will then be terminated. - * - * All other events (other than null) are invalid (should not happen). - */ - {bgp_fsm_null, bgp_fsm_Stopping}, /* null event */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Stop */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ - {bgp_fsm_exit, bgp_fsm_Stopping}, /* TCP_connection_closed */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ - {bgp_fsm_exit, bgp_fsm_Stopping}, /* TCP_fatal_error */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ - {bgp_fsm_exit, bgp_fsm_Stopping}, /* Hold_Timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ - {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ - {bgp_fsm_sent_nom, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ - }, -} ; - -static const char *bgp_event_str[] = -{ - "NULL", - "BGP_Start", - "BGP_Stop", - "TCP_connection_open", - "TCP_connection_closed", - "TCP_connection_open_failed", - "TCP_fatal_error", - "ConnectRetry_timer_expired", - "Hold_Timer_expired", - "KeepAlive_timer_expired", - "Receive_OPEN_message", - "Receive_KEEPALIVE_message", - "Receive_UPDATE_message", - "Receive_NOTIFICATION_message", - "Sent_NOTIFICATION_message", -} ; - -/*============================================================================== - * Signal FSM event. - */ - -static void -bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) ; - -/*------------------------------------------------------------------------------ - * Signal event to FSM for the given connection. - * - * - * - */ -extern void -bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event) -{ - bgp_session session ; - bgp_fsm_state_t next_state ; - const struct bgp_fsm* fsm ; - - dassert( (event >= bgp_fsm_null_event) - && (event <= bgp_fsm_last_event)) ; - dassert( (connection->state >= bgp_fsm_first_state) - && (connection->state <= bgp_fsm_last_state) ) ; - - /* Watch out for recursing through the FSM for this connection. */ - ++connection->fsm_active ; - - if (connection->fsm_active == 2) - { - connection->post = event ; - return ; - } ; - - /* Lock the session for the convenience of the event handlers. - * - * NB: if the current state is Stopping, then connection is no longer - * attached to session -- so connection->session is NULL -- BEWARE ! - * - * The session lock does nothing if no session is attached. - */ - session = connection->session ; - - if (session != NULL) - BGP_SESSION_LOCK(session) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ - - do - { - assert(connection->fsm_active == 1) ; - - fsm = &bgp_fsm[connection->state][event] ; - next_state = fsm->next_state ; - - /* Call function. */ - next_state = fsm->action(connection, next_state, event) ; - - dassert( (next_state >= bgp_fsm_first_state) - && (next_state <= bgp_fsm_last_state) ) ; - - /* If state is changed. */ - if (next_state != connection->state) - { - bgp_fsm_state_t prev_state = connection->state ; - - /* Complete the state change */ - bgp_fsm_state_change(connection, next_state) ; - - /* Log state change as required. */ - if (BGP_DEBUG(fsm, FSM)) - plog_debug(connection->log, - "%s [FSM] %s (%s->%s)", - connection->host, - bgp_event_str[event], - LOOKUP (bgp_status_msg, prev_state), - LOOKUP (bgp_status_msg, next_state)) ; - - if (BGP_DEBUG(normal, NORMAL)) - zlog_debug ("%s on %s went from %s to %s", - connection->host, - bgp_event_str[event], - LOOKUP (bgp_status_msg, prev_state), - LOOKUP (bgp_status_msg, next_state)); - } ; - - /* Pick up post event -- if any */ - event = connection->post ; - connection->post = bgp_fsm_null_event ; - - } while (--connection->fsm_active != 0) ; - - /* If required, post session event. */ - - if ((connection->except != bgp_session_null_event) && (session != NULL)) - { - /* Some exceptions are not reported to the Routeing Engine - * - * In particular: eDiscard and eCollision -- so the only time the - * connection->state will be Stopping is when the session is being - * stopped. (eDiscard and eCollision go quietly to Stopping !) - */ - if (connection->except <= bgp_session_max_event) - bgp_session_event(session, connection->except, - connection->notification, - connection->err, - connection->ordinal, - (connection->state == bgp_fsm_Stopping)) ; - - /* Tidy up -- notification already cleared */ - connection->except = bgp_session_null_event ; - connection->err = 0 ; - bgp_notify_free(&connection->notification) ; /* if any */ - } - - if (session != NULL) - BGP_SESSION_UNLOCK(session) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ -} ; - -/*============================================================================== - * The BGP FSM Action Functions - */ - -static void -bgp_hold_timer_set(bgp_connection connection, unsigned secs) ; - -static void -bgp_hold_timer_recharge(bgp_connection connection) ; - -static bgp_fsm_state_t -bgp_fsm_send_notification(bgp_connection connection, - bgp_fsm_state_t next_state) ; - -/*------------------------------------------------------------------------------ - * Null action -- do nothing at all. - */ -static bgp_fsm_action(bgp_fsm_null) -{ - return next_state ; -} ; - -/*------------------------------------------------------------------------------ - * Entry point to FSM. - * - * This is the first thing to happen to the FSM, and takes it from Initial - * state to Idle, with IdleHoldTimer running. - * - * NB: the IdleHoldTimer is always a finite time. So the start up event for - * the primary connection *cannot* fail. - * - * NB: the session is locked. - */ -static bgp_fsm_action(bgp_fsm_enter) -{ - if (connection->ordinal == bgp_connection_secondary) - bgp_prepare_to_accept(connection) ; - - return next_state ; -} ; - -/*------------------------------------------------------------------------------ - * Stop BGP Connection -- general exception event. - * - * An exception should have been raised, treat as invalid if not. - * - * If is eDisabled, set next_state == Stopping. - * If is eDiscard, set next_state == Stopping. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_stop) -{ - if (connection->except == bgp_session_null_event) - return bgp_fsm_invalid(connection, bgp_fsm_Stopping, event) ; - - if ( (connection->except == bgp_session_eDisabled) - || (connection->except == bgp_session_eDiscard) ) - next_state = bgp_fsm_Stopping ; - - return bgp_fsm_catch(connection, next_state) ; -} ; - -/*------------------------------------------------------------------------------ - * Invalid event -- cannot occur in current state. - * - * Brings down the session, sending an FSM error NOTIFICATION. - * - * Forces transition to Stopping state for this connection and any sibling. - * - * If already in Stopping state, force exit. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_invalid) -{ - if (BGP_DEBUG(fsm, FSM)) \ - plog_debug(connection->log, "%s [FSM] invalid event %d in state %d", - connection->host, event, connection->state) ; - - if (connection->state != bgp_fsm_Stopping) - return bgp_fsm_post_catch(connection, bgp_session_eInvalid, - bgp_notify_new(BGP_NOMC_FSM, BGP_NOMS_UNSPECIFIC, 0), - bgp_fsm_Stopping) ; - else - return bgp_fsm_exit(connection, bgp_fsm_Stopping, event) ; -} ; - -/*------------------------------------------------------------------------------ - * Start up BGP Connection - * - * This is used: - * - * * to change from Idle to Connect or Active -- when the IdleHoldTimer - * expires. - * - * * to loop back to Connect or Active -- when the ConnectRetryTimer expires. - * - * The state entered depends on whether this is the primary or secondary - * connection. - * - * If this is the primary connection, then kicks a connect() into life, - * before the state change. Note that if that fails, then post an event to - * be processed as soon as completes the state transition. - * - * If this is the secondary connection, enables the session for accept(). - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_start) -{ - if (connection->ordinal == bgp_connection_primary) - { - next_state = bgp_fsm_Connect ; - bgp_open_connect(connection) ; - } - else - { - next_state = bgp_fsm_Active ; - bgp_connection_enable_accept(connection) ; - } ; - - return next_state ; -} ; - -/*------------------------------------------------------------------------------ - * TCP connection open has come up -- connect() or accept() - * - * Send BGP Open Message to peer. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_send_open) -{ - char buf_l[SU_ADDRSTRLEN] ; - char buf_r[SU_ADDRSTRLEN] ; - const char* how ; - - if (BGP_DEBUG (normal, NORMAL)) - { - if (connection->ordinal == bgp_connection_primary) - how = "connect" ; - else - how = "accept" ; - - zlog_debug("%s open %s(), local address %s", - sockunion2str(connection->su_remote, buf_r, SU_ADDRSTRLEN), - how, - sockunion2str(connection->su_local, buf_l, SU_ADDRSTRLEN)) ; - } ; - - bgp_connection_read_enable(connection) ; - - bgp_msg_send_open(connection, connection->session->open_send) ; - - return next_state ; -} ; - -/*------------------------------------------------------------------------------ - * TCP connection has failed to come up -- Connect/Active states. - * - * This is in response to TCP_connection_open_failed, which has posted the - * exception -- so now need to deal with it. - * - * Close the connection -- if secondary connection, disable accept. - * - * Will stay in Connect/Active states. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_failed) -{ - return bgp_fsm_catch(connection, next_state) ; -} ; - -/*------------------------------------------------------------------------------ - * Fatal I/O error -- any state (other than Idle and Stopping). - * - * Close the connection (if any) -- if secondary connection, disable accept. - * - * This is in response to TCP_fatal_error, which has posted the - * exception -- so now need to deal with it. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_fatal) -{ - return bgp_fsm_catch(connection, next_state) ; -} ; - -/*------------------------------------------------------------------------------ - * ConnectRetryTimer expired -- Connect/Active states. - * - * If the connection failed, the connection will have been closed. For the - * secondary connection accept() will have been disabled. - * - * For primary connection: - * - * * close the attempt to connect() (if still ative) - * * start the connect() attempt again - * - * For secondary connection: - * - * * re-enable accept (if has been cleared) and wait for same - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_retry) -{ - if (connection->ordinal == bgp_connection_primary) - bgp_close_connect(connection) ; - - bgp_fsm_post_exception(connection, bgp_session_eRetry, NULL, 0) ; - - return bgp_fsm_start(connection, next_state, event) ; -} ; - -/*------------------------------------------------------------------------------ - * TCP connection has closed -- OpenSent/OpenConfirm/Established states - * - * This is in response to TCP_connection_closed, which has posted the - * exception -- so now need to deal with it. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_closed) -{ - return bgp_fsm_catch(connection, next_state) ; -} ; - -/*------------------------------------------------------------------------------ - * Hold timer expire -- OpenSent/OpenConfirm/Stopping - * - * This means either: have finished sending NOTIFICATION (end of "courtesy" - * wait time) - * - * or: can wait no longer for something from the other end. - * - * NB: requires the session LOCKED - */ -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) ; - - return next_state ; - } ; - - /* Otherwise: post and immediately catch exception. */ - return bgp_fsm_post_catch(connection, bgp_session_eExpired, - bgp_notify_new(BGP_NOMC_HOLD_EXP, BGP_NOMS_UNSPECIFIC, 0), - next_state) ; -} ; - -/*------------------------------------------------------------------------------ - * Received an acceptable OPEN Message - * - * The next state is expected to be OpenConfirm. - * - * However: this is where we do Collision Resolution. - * - * If the sibling connection has reached OpenConfirm before this one, then now - * this one either closes its sibling, or itself. - * - * As soon as a connection reaches Established, it immediately kills off any - * sibling -- so the farthest two connections can get is to OpenSent. - * - * The connection that is closed should send a Cease/Collision Resolution - * NOTIFICATION. The other end should do likewise. - * - * The connection that is closed will fall back to Idle -- so that if the - * connection that wins the race to OpenConfirm fails there, then both will be - * back in Idle state. - * - * If makes it past Collision Resolution, respond with a KEEPALIVE (to "ack" - * the OPEN message). - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_recv_open) -{ - bgp_session session = connection->session ; - bgp_connection sibling = bgp_connection_get_sibling(connection) ; - - assert(session != NULL) ; - - /* If there is a sibling, and it is in OpenConfirm state, then now must do - * collision resolution. - */ - if ((sibling != NULL) && (sibling->state == bgp_fsm_OpenConfirm)) - { - bgp_connection loser ; - - /* NB: bgp_id in open_state is in *host* order */ - loser = (session->open_send->bgp_id < sibling->open_recv->bgp_id) - ? connection - : sibling ; - - /* Set reason for stopping */ - bgp_fsm_post_exception(loser, bgp_session_eCollision, - bgp_notify_new(BGP_NOMC_CEASE, BGP_NOMS_C_COLLISION, 0), 0) ; - - /* If self is the loser, treat this as a BGP_Stop event ! */ - /* Otherwise, issue BGP_Stop event for sibling. */ - if (loser == connection) - return bgp_fsm_catch(connection, next_state) ; - else - bgp_fsm_event(sibling, bgp_fsm_BGP_Stop) ; - } ; - - /* All is well: send a KEEPALIVE message to acknowledge the OPEN */ - bgp_msg_send_keepalive(connection) ; - - /* Transition to OpenConfirm state */ - return next_state ; -} - -/*------------------------------------------------------------------------------ - * FSM error -- received wrong type of message ! - * - * For example, an OPEN message while in Established state. - * - * For use in: OpenSent, OpenConfirm and Established states. - * - * Sends NOTIFICATION. - * - * Next state will be same as current, except for Established, when will be - * Stopping. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_error) -{ - return bgp_fsm_post_catch(connection, bgp_session_eFSM_error, - bgp_notify_new(BGP_NOMC_FSM, BGP_NOMS_UNSPECIFIC, 0), - next_state) ; -} ; - -/*------------------------------------------------------------------------------ - * Receive NOTIFICATION from far end -- OpenSent/OpenConfirm/Established - * - * This is in response to Receive_NOTIFICATION_message, which has posted the - * exception -- so now need to deal with it. - * - * Next state will be Idle, except for Established, when will be Stopping. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_recv_nom) -{ - return bgp_fsm_catch(connection, next_state) ; -} ; - -/*------------------------------------------------------------------------------ - * Pending NOTIFICATION has cleared write buffers - * -- OpenSent/OpenConfirm/Stopping - * - * Set the "courtesy" HoldTimer. Expect to stay in current state. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_sent_nom) -{ - bgp_hold_timer_set(connection, 5) ; - return next_state ; -} ; - -/*------------------------------------------------------------------------------ - * Seed Keepalive to peer. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_send_kal) -{ - bgp_msg_send_keepalive(connection) ; - return next_state ; -} ; - -/*------------------------------------------------------------------------------ - * Session Established ! - * - * If there is another connection, that is now snuffed out and this connection - * becomes the primary. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_establish) -{ - bgp_session session = connection->session ; - bgp_connection sibling = bgp_connection_get_sibling(connection) ; - - assert(session != NULL) ; - - /* The first thing to do is to snuff out any sibling */ - if (sibling != NULL) - bgp_fsm_discard_sibling(sibling, - bgp_notify_new(BGP_NOMC_CEASE, BGP_NOMS_C_COLLISION, 0)) ; - - /* Establish self as primary and copy state up to session */ - bgp_connection_make_primary(connection) ; - - /* Change the session state and post event */ - assert(session->state == bgp_session_sEnabled) ; - - session->state = bgp_session_sEstablished ; - bgp_fsm_post_exception(connection, bgp_session_eEstablished, NULL, 0) ; - - /* TODO: now would be a good time to withdraw the password from listener ? */ - - return next_state ; -} ; - -/*------------------------------------------------------------------------------ - * Keepalive packet is received -- OpenConfirm/Established - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_recv_kal) -{ - /* peer count update */ -//peer->keepalive_in++; - - bgp_hold_timer_recharge(connection) ; - return next_state ; -} ; - -/*------------------------------------------------------------------------------ - * Update packet is received. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_update) -{ - bgp_hold_timer_recharge(connection) ; - return next_state ; -} - -/*------------------------------------------------------------------------------ - * Connection exit - * - * Ring down the curtain. Connection structure will be freed by the BGP Engine. - * - * NB: requires the session LOCKED - */ -static bgp_fsm_action(bgp_fsm_exit) -{ - assert(connection->state == bgp_fsm_Stopping) ; - - bgp_connection_exit(connection) ; - - return bgp_fsm_Stopping ; -} ; - -/*============================================================================== - * Catching FSM Exceptions. - * - * Throwing/Posting Exceptions sets: - * - * connection->except ) - * connection->err ) which define the exception - * connection->notification ) - * - * An event has been raised, and the FSM has a (default next_state). - * - * 1a) notification & not eNOM_recv - * - * Start sending the NOTIFICATION message. - * - * NB: won't be a notification unless OpenSent/OpenConfirm/Established. - * - * For OpenSent/OpenConfirm, override the next_state to stay where it is - * until NOTIFICATION process completes. - * - * Sending NOTIFICATION closes the connection for reading. - * - * 1b) otherwise: close the connection. - * - * 2) if next state is Stopping, 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. - * - * (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). - * - * * send message to Routeing Engine - * - * NB: requires the session LOCKED - */ -static bgp_fsm_state_t -bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state) -{ - /* If there is a NOTIFICATION to send, now is the time to do that. - * Otherwise, close the connection. - */ - if ( (connection->notification != NULL) - && (connection->except != bgp_session_eNOM_recv) ) - { - next_state = bgp_fsm_send_notification(connection, next_state) ; - } - else - bgp_connection_close(connection) ; - - /* If stopping and not eDiscard, do in any sibling */ - if ( (next_state == bgp_fsm_Stopping) - && (connection->except != bgp_session_eDiscard) ) - { - bgp_connection sibling ; - - sibling = bgp_connection_get_sibling(connection) ; /* ... if any */ - - if (sibling != NULL) - bgp_fsm_discard_sibling(sibling, - bgp_notify_dup(connection->notification)) ; - } ; - - /* Return the (possibly adjusted) 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 - * - */ -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_Stopping) - 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_Sent_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_Stopping) ? 20 : 5) ; - - /* Return suitable state. */ - return next_state ; -} ; - -/*============================================================================== - * The BGP connections timers handling. - * - * The FSM has four timers: - * - * * IdleHoldTimer -- uses connection.hold_timer with jitter - * - * This runs while in Idle state, and is a period in which no connections - * are started, and none will be accepted. - * - * The purpose of this timer is to slow down re-making connections with - * peers who are flapping or otherwise proving a nuisance. - * - * This is a one shot timer, which generates a bgp_fsm_BGP_Start event. - * - * * ConnectRetryTimer -- uses connection.hold_timer with jitter - * - * This runs while in Connect or Active state, and is the period for which - * the connection is prepared to wait between attempts to connect. - * - * When trying to make a connect connection: - * - * The FSM will be in Connect state. - * - * If listen connections are enabled, will be listening. - * - * If nothing happens before the ConnectRetryTimer expires, then - * the connection attempt will be abandoned, and another started. - * - * If the connection attempt fails, moves to Active state -- with the - * timer still running. - * - * If nothing further happens before the ConnectRetryTimer expires, - * another connect will be started and the FSM returns to Connect state. - * - * When only listening is enabled: - * - * The FSM will be in Active state (!). - * - * If nothing happens before the ConnectRetryTimer expires, then the - * FSM will loop round back into Active state. - * - * This timer is recharged each time it goes off, and generates a - * bgp_fsm_ConnectRetry_timer_expired event. - * - * * HoldTimer -- uses connection.hold_timer *without* jitter - * - * This timer is used in OpenSent state, and limits the time will wait for - * an Open to appear from the other end. RFC4271 calls for this to be a - * "large value" -- suggesting 240 seconds. - * - * This timer is also used in OpenConfirm and Established states, and limits - * the time the connection will be held if hear nothing from the other end. - * In these states the timer is set to the negotiated HoldTime. If this is - * zero, then the HoldTime is infinite. - * - * This is a one shot timer, which generates a bgp_fsm_Hold_Timer_expired - * event. - * - * * KeepaliveTimer -- uses connection.keepalive_timer with jitter. - * - * This timer is used in OpenConfirm and Established states only. - * - * The default KeepalineTimer is 1/3 the HoldTimer, and is set from the - * negotiated HoldTime. If that is zero, then the KeepaliveTime is also - * infinite, and no KEEPALIVE messages will be sent (other than the "ack" - * of the OPEN message). - * - * This timer is recharged each time it goes off, and generates a - * bgp_fsm_KeepAlive_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 ; -static qtimer_action bgp_hold_timer_action ; -static qtimer_action bgp_keepalive_timer_action ; - -/*============================================================================== - * Completion of State Change - * - * This performs fixed changes associated with the entry to each state from - * *another* state. - * - * connection->state == current (soon to be old) state - * - * Set and unset all the connection timers as required by the new state of - * the connection -- which may depend on the current state. - * - * NB: requires the session LOCKED - */ -static void -bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) -{ - bgp_connection sibling ; - unsigned interval ; - bgp_session session = connection->session ; - - switch (new_state) - { - /* Base state of connection's finite state machine -- when a session has - * been enabled. Falls back to Idle in the event of various errors. - * - * In Idle state: - * - * either: the IdleHoldTimer is running, at the end of which the - * BGP Engine will try to connect. - * - * or: the connection is comatose, in which case will stay that way - * until sibling connection also falls back to Idle (from - * OpenSent/OpenConfirm. - * - * When entering Idle from anything other than Initial state, and not - * falling into a coma, extend the IdleHoldTimer. - * - * In Idle state refuses connections. - */ - case bgp_fsm_Idle: - interval = session->idle_hold_timer_interval ; - sibling = bgp_connection_get_sibling(connection) ; - - if (connection->state == bgp_fsm_Initial) - interval = (interval > 0) ? interval : 1 ; /* may not be zero */ - else - { - if ( (sibling != NULL) - && ( (sibling->state == bgp_fsm_OpenSent) - || (sibling->state == bgp_fsm_OpenConfirm) ) ) - { - interval = 0 ; /* unset the HoldTimer */ - connection->comatose = 1 ; /* so now comatose */ - } - else - { - /* increase the IdleHoldTimer interval */ - interval *= 2 ; - - if (interval < 4) /* enforce this minimum */ - interval = 4 ; - else if (interval > 120) - interval = 120 ; - - session->idle_hold_timer_interval = interval ; - - /* if sibling is comatose, set time for it to come round */ - - 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(connection, &connection->hold_timer, interval, 1, - bgp_idle_hold_timer_action) ; - - qtimer_unset(&connection->keepalive_timer) ; - - break; - - /* In Connect state the BGP Engine is attempting to make a connection - * with the peer and may be listening for a connection. - * - * In Active state the BGP Engine is only listening (!). - * - * In both cases, waits for the connect_hold_timer_interval. - * - * The ConnectRetryTimer automatically recharges, because will loop back - * round into the same state. - */ - case bgp_fsm_Connect: - case bgp_fsm_Active: - bgp_timer_set(connection, &connection->hold_timer, - session->connect_retry_timer_interval, 1, - bgp_connect_retry_timer_action) ; - qtimer_unset(&connection->keepalive_timer) ; - break; - - /* In OpenSent state is waiting for an OPEN from the other end, before - * proceeding to OpenConfirm state. - * - * Prepared to wait for quite a long time for this. - * - * Note that session->accept is left as it is. If have reached OpenSent - * on: - * - * * a connect() connection, then session->accept will be true and will - * still accept in-bound connections. - * - * * an accept() connection, then session->accept will be false. - */ - case bgp_fsm_OpenSent: - bgp_hold_timer_set(connection, session->open_hold_timer_interval) ; - qtimer_unset(&connection->keepalive_timer) ; - break; - - /* In OpenConfirm state is waiting for an "ack" before proceeding to - * Established. Session->accept is left as it is. If have reached - * OpenConfirm on: - * - * * a connect() connection, then session->accept may still be true and - * will still accept in-bound connections. (Collision detection may - * have discarded an accept() connection already.) - * - * * an accept() connection, then session->accept will be false. - * - * There is only one way into Established, and that is from OpenConfirm. - * OpenConfirm starts the KeepaliveTimer. It would be wrong to reset the - * timer on entry to Established. - * - * In both cases have just received a message, so can restart the HoldTimer. - * - * Both use the negotiated Hold Time and Keepalive Time. May send further - * KEEPALIVE messages in OpenConfirm. - * - * If the negotiated Hold Time value is zero, then the Keepalive Time - * value will also be zero, and this will unset both timers. - */ - case bgp_fsm_OpenConfirm: - bgp_timer_set(connection, &connection->keepalive_timer, - connection->keepalive_timer_interval, 1, - bgp_keepalive_timer_action) ; - case bgp_fsm_Established: - bgp_hold_timer_set(connection, connection->hold_timer_interval) ; - break; - - /* The connection is coming to an dead stop. - * - * If not sending a NOTIFICATION then stop HoldTimer now. - * - * Unlink connection from session. - */ - case bgp_fsm_Stopping: - if (!connection->notification_pending) - qtimer_unset(&connection->hold_timer) ; - - qtimer_unset(&connection->keepalive_timer) ; - - session->connections[connection->ordinal] = NULL ; - connection->session = NULL ; - connection->p_mutex = NULL ; - - break ; - - default: - zabort("Unknown bgp_fsm_state") ; - } ; - - /* Finally: set the new state */ - connection->state = 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. - */ -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_BGP_Start event - * - * The timer is automatically unset, which is fine. - */ -static void -bgp_idle_hold_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) -{ - bgp_connection connection = timer_info ; - - BGP_FSM_DEBUG(connection, "Timer (start timer expire)") ; - - bgp_fsm_event(connection, bgp_fsm_BGP_Start) ; -} ; - -/*------------------------------------------------------------------------------ - * BGP connect retry timer => bgp_fsm_ConnectRetry_timer_expired event - * - * The timer is recharged here, applying a new "jitter", but that may be - * overridden by the bgp_event() handling. - */ -static void -bgp_connect_retry_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) -{ - bgp_connection connection = timer_info ; - - BGP_FSM_DEBUG(connection, "Timer (connect timer expire)") ; - - bgp_timer_set(connection, &connection->hold_timer, - connection->session->connect_retry_timer_interval, 1, NULL) ; - - bgp_fsm_event(connection, bgp_fsm_ConnectRetry_timer_expired) ; -} ; - -/*------------------------------------------------------------------------------ - * BGP hold timer => bgp_fsm_Hold_Timer_expired event - * - * The timer is automatically unset, which is fine. - */ -static void -bgp_hold_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) -{ - bgp_connection connection = timer_info ; - - BGP_FSM_DEBUG(connection, "Timer (holdtime timer expire)") ; - - bgp_fsm_event(connection, bgp_fsm_Hold_Timer_expired) ; -} ; - -/*------------------------------------------------------------------------------ - * BGP keepalive fire => bgp_fsm_KeepAlive_timer_expired - * - * The timer is recharged here, applying a new "jitter", but that may be - * overridden by the bgp_event() handling. - */ -static void -bgp_keepalive_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) -{ - bgp_connection connection = timer_info ; - - BGP_FSM_DEBUG(connection, "Timer (keepalive timer expire)") ; - - bgp_timer_set(connection, &connection->keepalive_timer, - connection->session->keepalive_timer_interval, 1, NULL) ; - - bgp_fsm_event(connection, bgp_fsm_KeepAlive_timer_expired) ; -} ; - -/*============================================================================*/ -/* BGP Peer Down Cause */ -/* TODO: this is also defined in bgp_peer.c */ -#if 0 -const char *peer_down_str[] = -{ - "", - "Router ID changed", - "Remote AS changed", - "Local AS change", - "Cluster ID changed", - "Confederation identifier changed", - "Confederation peer changed", - "RR client config change", - "RS client config change", - "Update source change", - "Address family activated", - "Admin. shutdown", - "User reset", - "BGP Notification received", - "BGP Notification send", - "Peer closed the session", - "Neighbor deleted", - "Peer-group add member", - "Peer-group delete member", - "Capability changed", - "Passive config change", - "Multihop config change", - "NSF peer closed the session" -}; -#endif diff --git a/bgpd/bgp_session.c b/bgpd/bgp_session.c index 5ee07d65..83fc419a 100644 --- a/bgpd/bgp_session.c +++ b/bgpd/bgp_session.c @@ -246,7 +246,7 @@ bgp_session_enable(bgp_peer peer) session->open_send = bgp_peer_open_state_init_new(session->open_send, peer); session->open_recv = bgp_open_state_free(session->open_recv); - session->connect = (peer->flags & PEER_FLAG_PASSIVE) != 0 ; + session->connect = (peer->flags & PEER_FLAG_PASSIVE) == 0 ; session->listen = 1 ; session->ttl = peer->ttl ; |