summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_connection.c57
-rw-r--r--bgpd/bgp_connection.h3
-rw-r--r--bgpd/bgp_fsm.c25
-rw-r--r--bgpd/bgp_fsm.x2623
-rw-r--r--bgpd/bgp_notification.c42
-rw-r--r--bgpd/bgp_notification.h5
-rw-r--r--bgpd/bgp_open_state.c24
-rw-r--r--bgpd/bgp_open_state.h3
-rw-r--r--lib/qtimers.c40
-rw-r--r--lib/qtimers.h3
-rw-r--r--lib/sockunion.c57
-rw-r--r--lib/sockunion.h7
12 files changed, 2830 insertions, 59 deletions
diff --git a/bgpd/bgp_connection.c b/bgpd/bgp_connection.c
index dcfd57e7..43a4fbc0 100644
--- a/bgpd/bgp_connection.c
+++ b/bgpd/bgp_connection.c
@@ -293,12 +293,10 @@ bgp_connection_free(bgp_connection connection)
bgp_connection_close(connection) ;
/* Free any components which still exist */
- bgp_notify_free(&connection->notification) ;
- bgp_open_state_free(connection->open_recv) ;
- if (connection->su_local != NULL)
- sockunion_free(connection->su_local) ;
- if (connection->su_remote != NULL)
- sockunion_free(connection->su_remote) ;
+ bgp_notify_unset(&connection->notification) ;
+ bgp_open_state_unset(&connection->open_recv) ;
+ sockunion_unset(&connection->su_local) ;
+ sockunion_unset(&connection->su_remote) ;
if (connection->host != NULL)
XFREE(MTYPE_BGP_PEER_HOST, connection->host) ;
stream_free(connection->ibuf) ;
@@ -469,7 +467,7 @@ bgp_connection_queue_process(void)
*
* Does not touch:
*
- * * state of the connection
+ * * state of the connection (including post event)
*
* NB: requires the session to be LOCKED.
*/
@@ -486,15 +484,13 @@ bgp_connection_open(bgp_connection connection, int fd)
qps_add_file(bgp_nexus->selection, &connection->qf, fd, connection) ;
/* Clear sundry state is clear */
- connection->post = bgp_fsm_null_event ; /* no post event event */
-
connection->except = bgp_session_null_event ;
- bgp_notify_free(&connection->notification) ;
+ bgp_notify_unset(&connection->notification) ;
connection->err = 0 ; /* so far, so good */
/* These accept NULL arguments */
connection->open_recv = bgp_open_state_free(connection->open_recv) ;
- bgp_notify_free(&connection->notification) ;
+ bgp_notify_unset(&connection->notification) ;
/* Copy the original hold_timer_interval and keepalive_timer_interval
* Assume these have sensible initial values.
@@ -559,29 +555,15 @@ bgp_connection_disable_accept(bgp_connection connection)
extern void
bgp_connection_close(bgp_connection connection)
{
- int fd ;
-
- /* close the qfile and any associate file descriptor */
- qps_remove_file(&connection->qf) ;
- fd = qps_file_unset_fd(&connection->qf) ;
- if (fd != fd_undef)
- shutdown(fd, SHUT_RDWR) ;
+ bgp_connection_close_file(connection) ;
/* If this is the secondary connection, do not accept any more. */
if (connection->ordinal == bgp_connection_secondary)
bgp_connection_disable_accept(connection) ;
/* forget any addresses */
- if (connection->su_local != NULL)
- {
- sockunion_clear(connection->su_local) ;
- connection->su_local = NULL;
- }
- if (connection->su_remote != NULL)
- {
- sockunion_clear(connection->su_remote) ;
- connection->su_remote = NULL;
- }
+ sockunion_unset(&connection->su_local) ;
+ sockunion_unset(&connection->su_remote) ;
/* Unset all the timers */
qtimer_unset(&connection->hold_timer) ;
@@ -604,6 +586,25 @@ bgp_connection_close(bgp_connection connection)
} ;
/*------------------------------------------------------------------------------
+ * Close connection's file, if any.
+ *
+ * * if there is an fd, close it
+ * * if qfile is active, remove it
+ */
+extern void
+bgp_connection_close_file(bgp_connection connection)
+{
+ int fd ;
+
+ qps_remove_file(&connection->qf) ;
+
+ fd = qps_file_unset_fd(&connection->qf) ;
+ if (fd != fd_undef)
+ shutdown(fd, SHUT_RDWR) ;
+
+} ;
+
+/*------------------------------------------------------------------------------
* Close connection for reading and purge the write buffers.
*
* This is done when the connection is about to be fully closed, but need to
diff --git a/bgpd/bgp_connection.h b/bgpd/bgp_connection.h
index 988cfef7..7a349974 100644
--- a/bgpd/bgp_connection.h
+++ b/bgpd/bgp_connection.h
@@ -195,6 +195,9 @@ extern void
bgp_connection_close(bgp_connection connection) ;
extern void
+bgp_connection_close_file(bgp_connection connection) ;
+
+extern void
bgp_connection_part_close(bgp_connection connection) ;
extern void
diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c
index 97c60b1c..2d06cb2a 100644
--- a/bgpd/bgp_fsm.c
+++ b/bgpd/bgp_fsm.c
@@ -481,7 +481,7 @@ bgp_fsm_disable_session(bgp_session session, bgp_notify notification)
bgp_fsm_throw_exception(connection, bgp_session_eDisabled, notification, 0,
bgp_fsm_BGP_Stop) ;
else
- bgp_notify_free(&notification) ;
+ bgp_notify_free(notification) ;
} ;
/*------------------------------------------------------------------------------
@@ -621,8 +621,8 @@ bgp_fsm_connect_completed(bgp_connection connection, int err,
{
bgp_fsm_event(connection, bgp_fsm_TCP_connection_open) ;
- connection->su_local = sockunion_dup(su_local) ;
- connection->su_remote = sockunion_dup(su_remote) ;
+ sockunion_set_dup(&connection->su_local, su_local) ;
+ sockunion_set_dup(&connection->su_remote, su_remote) ;
}
else if ( (err == ECONNREFUSED)
|| (err == ECONNRESET)
@@ -651,7 +651,7 @@ bgp_fsm_post_exception(bgp_connection connection, bgp_session_event_t except,
if ( (connection->state != bgp_fsm_OpenSent)
&& (connection->state != bgp_fsm_OpenConfirm)
&& (connection->state != bgp_fsm_Established) )
- bgp_notify_free(&notification) ;
+ bgp_notify_unset(&notification) ;
bgp_notify_set(&connection->notification, notification) ;
@@ -1623,7 +1623,7 @@ bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event)
/* Tidy up -- notification already cleared */
connection->except = bgp_session_null_event ;
connection->err = 0 ;
- bgp_notify_free(&connection->notification) ; /* if any */
+ bgp_notify_unset(&connection->notification) ; /* if any */
}
if (session != NULL)
@@ -1823,21 +1823,30 @@ static bgp_fsm_action(bgp_fsm_fatal)
/*------------------------------------------------------------------------------
* 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 existing connection (may already be closed if failed)
+ * * close the attempt to connect() (if still active)
* * start the connect() attempt again
*
* For secondary connection:
*
- * * close the existing connection (easy, 'cos never opened !)
* * re-enable accept (if has been cleared) and wait for same
*
+ * If no accept() has been attempted, then accept will still be enabled,
+ * and re-enabling it will make no difference.
+ *
+ * NB: the connection remains in the current state, and the retry timer will
+ * still be running, because it automatically recharges.
+ *
* NB: requires the session LOCKED
*/
static bgp_fsm_action(bgp_fsm_retry)
{
- bgp_connection_close(connection) ;
+ if (connection->ordinal == bgp_connection_primary)
+ bgp_connection_close_file(connection) ;
bgp_fsm_post_exception(connection, bgp_session_eRetry, NULL, 0) ;
diff --git a/bgpd/bgp_fsm.x b/bgpd/bgp_fsm.x
new file mode 100644
index 00000000..a00f40cc
--- /dev/null
+++ b/bgpd/bgp_fsm.x
@@ -0,0 +1,2623 @@
+/* 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(&notification) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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(&notification) ;
+
+ 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_notification.c b/bgpd/bgp_notification.c
index bd52d516..392289d0 100644
--- a/bgpd/bgp_notification.c
+++ b/bgpd/bgp_notification.c
@@ -73,6 +73,18 @@ bgp_notify_new(bgp_nom_code_t code, bgp_nom_subcode_t subcode,
} ;
/*------------------------------------------------------------------------------
+ * Free notification structure
+ *
+ * Does nothing if there is no structure.
+ */
+extern void
+bgp_notify_free(bgp_notify notification)
+{
+ if (notification != NULL)
+ XFREE(MTYPE_BGP_NOTIFY, notification) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Duplicate existing notification (if any)
*/
extern bgp_notify
@@ -94,7 +106,19 @@ bgp_notify_dup(bgp_notify notification)
} ;
/*------------------------------------------------------------------------------
- * Set notification (if any)
+ * Unset pointer to notification and free any existing notification structure.
+ *
+ * Does nothing if there is no structure.
+ */
+extern void
+bgp_notify_unset(bgp_notify* p_notification)
+{
+ if (*p_notification != NULL)
+ XFREE(MTYPE_BGP_NOTIFY, *p_notification) ; /* sets *p_notification NULL */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set pointer to notification
*
* Frees any existing notification at the destination.
*
@@ -109,7 +133,7 @@ bgp_notify_set(bgp_notify* p_dst, bgp_notify src)
} ;
/*------------------------------------------------------------------------------
- * Set notification (if any) to a *copy* of the source.
+ * Set pointer to notification to a *copy* of the source.
*
* Frees any existing notification at the destination.
*/
@@ -120,7 +144,7 @@ bgp_notify_set_dup(bgp_notify* p_dst, bgp_notify src)
} ;
/*------------------------------------------------------------------------------
- * Set notification (if any) and set source pointer NULL
+ * Set pointer to notification and unset source pointer
*
* Frees any existing notification at the destination.
*
@@ -134,18 +158,6 @@ bgp_notify_set_mov(bgp_notify* p_dst, bgp_notify* p_src)
*p_src = NULL ;
} ;
-/*------------------------------------------------------------------------------
- * Free notification structure
- *
- * Does nothing if there is no structure.
- */
-extern void
-bgp_notify_free(bgp_notify* p_notification)
-{
- if (*p_notification != NULL)
- XFREE(MTYPE_BGP_NOTIFY, *p_notification) ; /* sets *p_notification NULL */
-} ;
-
/*==============================================================================
* Append data to given notification
*
diff --git a/bgpd/bgp_notification.h b/bgpd/bgp_notification.h
index b9670e8a..4d8d4737 100644
--- a/bgpd/bgp_notification.h
+++ b/bgpd/bgp_notification.h
@@ -121,12 +121,15 @@ extern bgp_notify
bgp_notify_new(bgp_nom_code_t code, bgp_nom_subcode_t subcode,
bgp_size_t size) ;
extern void
-bgp_notify_free(bgp_notify* p_notification) ;
+bgp_notify_free(bgp_notify notification) ;
extern bgp_notify
bgp_notify_dup(bgp_notify notification) ;
extern void
+bgp_notify_unset(bgp_notify* p_notification) ;
+
+extern void
bgp_notify_set(bgp_notify* p_dst, bgp_notify src) ;
extern void
diff --git a/bgpd/bgp_open_state.c b/bgpd/bgp_open_state.c
index 9c743d5b..80cd3f33 100644
--- a/bgpd/bgp_open_state.c
+++ b/bgpd/bgp_open_state.c
@@ -39,10 +39,11 @@
*
*/
-/* Initialise new bgp_open_state structure -- allocate if required.
+/*------------------------------------------------------------------------------
+ * Initialise new bgp_open_state structure -- allocate if required.
*
*/
-bgp_open_state
+extern bgp_open_state
bgp_open_state_init_new(bgp_open_state state)
{
if (state == NULL)
@@ -55,7 +56,12 @@ bgp_open_state_init_new(bgp_open_state state)
return state ;
}
-bgp_open_state
+/*------------------------------------------------------------------------------
+ * Free bgp_open_state structure (if any)
+ *
+ * Returns NULL.
+ */
+extern bgp_open_state
bgp_open_state_free(bgp_open_state state)
{
bgp_cap_unknown unknown ;
@@ -71,6 +77,18 @@ bgp_open_state_free(bgp_open_state state)
return NULL ;
}
+/*------------------------------------------------------------------------------
+ * Unset pointer to open_state structure and free structure (if any).
+ */
+extern void
+bgp_open_state_unset(bgp_open_state* p_state)
+{
+ bgp_cap_unknown unknown ;
+
+ bgp_open_state_free(*p_state) ;
+ *p_state = NULL ;
+} ;
+
/*==============================================================================
* Construct new bgp_open_state for the given peer -- allocate if required.
*
diff --git a/bgpd/bgp_open_state.h b/bgpd/bgp_open_state.h
index 2019941e..e9529208 100644
--- a/bgpd/bgp_open_state.h
+++ b/bgpd/bgp_open_state.h
@@ -104,6 +104,9 @@ extern bgp_open_state
bgp_open_state_free(bgp_open_state state) ;
extern void
+bgp_open_state_unset(bgp_open_state* state) ;
+
+extern void
bgp_open_state_unknown_add(bgp_open_state state, uint8_t code,
void* value, bgp_size_t length) ;
extern int
diff --git a/lib/qtimers.c b/lib/qtimers.c
index 43335d55..a5d0e27b 100644
--- a/lib/qtimers.c
+++ b/lib/qtimers.c
@@ -154,6 +154,8 @@ qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto)
{
qtimer qtr ;
+ qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */
+
qtr = heap_top_item(&qtp->timers) ;
if ((qtr != NULL) && (qtr->time <= upto))
{
@@ -321,6 +323,7 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action)
qtp = qtr->pile ;
dassert(qtp != NULL) ;
assert(qtp == our_pile);
+ qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */
qtr->time = when ;
@@ -328,7 +331,9 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action)
heap_update_item(&qtp->timers, qtr) ; /* update in heap */
else
heap_push_item(&qtp->timers, qtr) ; /* add to heap */
+
assert(qtp == qtr->pile);
+ qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */
qtr->state = qtr_state_active ; /* overrides any unset pending */
@@ -349,11 +354,46 @@ qtimer_unset(qtimer qtr)
{
qtimer_pile qtp = qtr->pile ;
dassert(qtp != NULL) ;
+
assert(qtp == our_pile);
+ qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */
heap_delete_item(&qtp->timers, qtr) ;
+
assert(qtp == qtr->pile);
+ qtimer_pile_verify(qtp) ; /* TODO: remove after debuggery */
qtr->state = qtr_state_inactive ; /* overrides any unset pending */
} ;
} ;
+
+/*==============================================================================
+ * Verification code for debug purposes.
+ */
+extern void
+qtimer_pile_verify(qtimer_pile qtp)
+{
+ heap th = &qtp->timers ;
+ vector v ;
+ vector_index i ;
+ vector_index e ;
+ qtimer qtr ;
+
+ /* Eclipse flags offsetof(struct qtimer, backlink) as a syntax error :-( */
+ typedef struct qtimer qtimer_t ;
+
+ assert(th->cmp == (heap_cmp*)qtimer_cmp) ;
+ assert(th->state == Heap_Has_Backlink) ;
+ assert(th->backlink_offset == offsetof(qtimer_t, backlink)) ;
+
+ v = &th->v ;
+ e = vector_end(v) ;
+ for (i = 0 ; i < e ; ++i)
+ {
+ qtr = vector_get_item(v, i) ;
+
+ assert(qtr->pile == qtp) ;
+ assert(qtr->backlink == i) ;
+ assert(qtr->action != NULL) ;
+ } ;
+} ;
diff --git a/lib/qtimers.h b/lib/qtimers.h
index d4305dc0..3d509acb 100644
--- a/lib/qtimers.h
+++ b/lib/qtimers.h
@@ -132,6 +132,9 @@ qtimer_add_interval(qtimer qtr, qtimer_action* action) ;
Inline qtime_t
qtimer_get_interval(qtimer qtr) ;
+void
+qtimer_pile_verify(qtimer_pile qtp) ;
+
/*==============================================================================
* Inline functions
*/
diff --git a/lib/sockunion.c b/lib/sockunion.c
index d1fdb189..dbfccfb8 100644
--- a/lib/sockunion.c
+++ b/lib/sockunion.c
@@ -732,12 +732,63 @@ sockunion_free (union sockunion *su)
}
/*==============================================================================
- * Clear a given sockunion -- ie zeroise it
+ * Sockunion reference utilities
+ */
+
+/*------------------------------------------------------------------------------
+ * Unset pointer to sockunion -- free any sockunion referenced
+ *
+ * Does nothing if there is no sockunion
+ */
+extern void
+sockunion_unset(sockunion* p_su)
+{
+ if (*p_su != NULL)
+ XFREE(MTYPE_BGP_NOTIFY, *p_su) ; /* sets *p_su NULL */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set pointer to sockunion (if any)
+ *
+ * Frees any existing sockunion at the destination.
+ *
+ * NB: copies the source pointer -- so must be clear about responsibility
+ * for the sockunion.
+ */
+extern void
+sockunion_set(sockunion* p_dst, sockunion su)
+{
+ sockunion_unset(p_dst) ;
+ *p_dst = su ;
+}
+
+/*------------------------------------------------------------------------------
+ * Set pointer to a *copy* of the given sockunion
+ *
+ * Frees any existing sockunion at the destination.
+ *
+ * NB: copies the source pointer -- so must be clear about responsibility
+ * for the sockunion structure.
+ */
+extern void
+sockunion_set_dup(sockunion* p_dst, sockunion su)
+{
+ sockunion_set(p_dst, sockunion_dup(su)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set pointer to sockunion (if any) and unset source pointer.
+ *
+ * Frees any existing sockunion at the destination.
+ *
+ * NB: responsibility for the sockunion passes to the destination.
*/
extern void
-sockunion_clear(union sockunion* su)
+sockunion_set_mov(sockunion* p_dst, sockunion* p_src)
{
- memset(su, 0, sizeof(union sockunion)) ;
+ sockunion_unset(p_dst) ;
+ *p_dst = *p_src ;
+ *p_src = NULL ;
} ;
/*==============================================================================
diff --git a/lib/sockunion.h b/lib/sockunion.h
index 8fcaded4..c00c02e4 100644
--- a/lib/sockunion.h
+++ b/lib/sockunion.h
@@ -41,6 +41,7 @@ union sockunion {
#define su_port su_si.si_port
#endif /* 0 */
+typedef union sockunion* sockunion ;
union sockunion
{
struct sockaddr sa;
@@ -118,7 +119,11 @@ extern int sockunion_getsockname (int, union sockunion*);
extern int sockunion_getpeername (int, union sockunion*);
extern union sockunion *sockunion_dup (union sockunion *);
extern void sockunion_free (union sockunion *);
-extern void sockunion_clear(union sockunion*);
+
+extern void sockunion_unset(sockunion* p_su) ;
+extern void sockunion_set(sockunion* p_dst, sockunion su) ;
+extern void sockunion_set_dup(sockunion* p_dst, sockunion su) ;
+extern void sockunion_set_mov(sockunion* p_dst, sockunion* p_src) ;
#ifndef HAVE_INET_NTOP
extern const char * inet_ntop (int family, const void *addrptr,