diff options
-rw-r--r-- | bgpd/bgp_connection.c | 57 | ||||
-rw-r--r-- | bgpd/bgp_connection.h | 3 | ||||
-rw-r--r-- | bgpd/bgp_fsm.c | 25 | ||||
-rw-r--r-- | bgpd/bgp_fsm.x | 2623 | ||||
-rw-r--r-- | bgpd/bgp_notification.c | 42 | ||||
-rw-r--r-- | bgpd/bgp_notification.h | 5 | ||||
-rw-r--r-- | bgpd/bgp_open_state.c | 24 | ||||
-rw-r--r-- | bgpd/bgp_open_state.h | 3 | ||||
-rw-r--r-- | lib/qtimers.c | 40 | ||||
-rw-r--r-- | lib/qtimers.h | 3 | ||||
-rw-r--r-- | lib/sockunion.c | 57 | ||||
-rw-r--r-- | lib/sockunion.h | 7 |
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(¬ification) ; + 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(¬ification) ; + bgp_notify_unset(¬ification) ; 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(¬ification) ; +} ; + +/*------------------------------------------------------------------------------ + * Raise a general exception -- not I/O related. + * + * Note that I/O problems are signalled by bgp_fsm_io_error(). + * + * NB: may NOT be used within the FSM. + */ +extern void +bgp_fsm_raise_exception(bgp_connection connection, bgp_session_event_t except, + bgp_notify notification) +{ + bgp_fsm_throw_exception(connection, except, notification, 0, + bgp_fsm_BGP_Stop) ; +} ; + +/*------------------------------------------------------------------------------ + * Raise a discard exception for sibling. + * + * A connection will discard any sibling if: + * + * * the session is being disabled (by the Peering Engine) + * + * * an invalid event is bringing down the session + * + * * it has reached Established state, and is snuffing out the sibling. + * + * + * + * NB: requires the session LOCKED + */ +static void +bgp_fsm_discard_sibling(bgp_connection sibling, bgp_notify notification) +{ + bgp_fsm_throw_exception(sibling, bgp_session_eDiscard, + notification, 0, bgp_fsm_BGP_Stop) ; +} ; + +/*------------------------------------------------------------------------------ + * Raise a NOTIFICATION received exception + */ +extern void +bgp_fsm_notification_exception(bgp_connection connection, + bgp_notify notification) +{ + bgp_fsm_throw_exception(connection, bgp_session_eNOM_recv, notification, 0, + bgp_fsm_Receive_NOTIFICATION_message) ; +} ; + +/*------------------------------------------------------------------------------ + * Raise a "fatal I/O error" exception on the given connection. + * + * Error to be reported as "TCP_fatal_error". + */ +extern void +bgp_fsm_io_fatal_error(bgp_connection connection, int err) +{ + plog_err (connection->log, "%s [Error] bgp IO error: %s", + connection->host, safe_strerror(err)) ; + + bgp_fsm_throw_exception(connection, bgp_session_eTCP_error, NULL, err, + bgp_fsm_TCP_fatal_error) ; +} ; + +/*------------------------------------------------------------------------------ + * Raise an "I/O error" exception on the given connection. + * + * This is used by read/write operations -- so not until the TCP connection + * is up (which implies OpenSent state or later). + * + * It is assumed that the read/write code deals with EAGAIN/EWOULDBLOCK/EINTR + * itself -- so only real errors are reported here. + * + * A read operation that returns zero is reported here as err == 0. + * + * The following are reported as "TCP_connection_closed": + * + * 0, ECONNRESET, ENETDOWN, ENETUNREACH, EPIPE or ETIMEDOUT + * + * All other errors are reported as "TCP_fatal_error". + */ +extern void +bgp_fsm_io_error(bgp_connection connection, int err) +{ + if ( (err == 0) + || (err == ECONNRESET) + || (err == ENETDOWN) + || (err == ENETUNREACH) + || (err == EPIPE) + || (err == ETIMEDOUT) ) + { + if (BGP_DEBUG(events, EVENTS)) + { + if (err == 0) + plog_debug(connection->log, + "%s [Event] BGP connection closed fd %d", + connection->host, qps_file_fd(&connection->qf)) ; + else + plog_debug(connection->log, + "%s [Event] BGP connection closed fd %d (%s)", + connection->host, qps_file_fd(&connection->qf), + safe_strerror(err)) ; + } ; + + bgp_fsm_throw_exception(connection, bgp_session_eTCP_dropped, NULL, err, + bgp_fsm_TCP_connection_closed) ; + } + else + bgp_fsm_io_fatal_error(connection, err) ; +} ; + +/*------------------------------------------------------------------------------ + * Signal completion of a connect() or an accept() for the given connection. + * + * This is used by the connect() and accept() qpselect actions. It is also + * used if a connect() attempt fails immediately. + * + * If err == 0, then all is well: copy the local and remote sockunions + * and generate TCP_connection_open event + * + * If err is one of: + * + * ECONNREFUSED, ECONNRESET, EHOSTUNREACH or ETIMEDOUT + * + * generate TCP_connection_open_failed event. (accept() does not return any of + * these errors.) + * + * Other errors are reported as TCP_fatal_error. + */ +extern void +bgp_fsm_connect_completed(bgp_connection connection, int err, + union sockunion* su_local, + union sockunion* su_remote) +{ + if (err == 0) + { + bgp_fsm_event(connection, bgp_fsm_TCP_connection_open) ; + + connection->su_local = sockunion_dup(su_local) ; + connection->su_remote = sockunion_dup(su_remote) ; + } + else if ( (err == ECONNREFUSED) + || (err == ECONNRESET) + || (err == EHOSTUNREACH) + || (err == ETIMEDOUT) ) + bgp_fsm_throw_exception(connection, bgp_session_eTCP_failed, NULL, err, + bgp_fsm_TCP_connection_open_failed) ; + else + bgp_fsm_io_fatal_error(connection, err) ; +} ; + +/*------------------------------------------------------------------------------ + * Post the given exception. + * + * Forget the notification if not OpenSent/OpenConfirm/Established. Cannot + * send notification in any other state -- nor receive one. + * + * NB: takes responsibility for the notification structure. + */ +static void +bgp_fsm_post_exception(bgp_connection connection, bgp_session_event_t except, + bgp_notify notification, int err) +{ + connection->except = except ; + + if ( (connection->state != bgp_fsm_OpenSent) + && (connection->state != bgp_fsm_OpenConfirm) + && (connection->state != bgp_fsm_Established) ) + bgp_notify_free(¬ification) ; + + bgp_notify_set(&connection->notification, notification) ; + + connection->err = err ; +} ; + +/*------------------------------------------------------------------------------ + * Post the given exception and raise the given event. + * + * NB: takes responsibility for the notification structure. + */ +static void +bgp_fsm_throw_exception(bgp_connection connection, bgp_session_event_t except, + bgp_notify notification, int err, bgp_fsm_event_t event) +{ + bgp_fsm_post_exception(connection, except,notification, err) ; + bgp_fsm_event(connection, event) ; +} ; + +/*------------------------------------------------------------------------------ + * Post and immediately catch a non-I/O exception. + * + * For use WITHIN the FSM. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_state_t +bgp_fsm_post_catch(bgp_connection connection, bgp_session_event_t except, + bgp_notify notification, bgp_fsm_state_t next_state) +{ + bgp_fsm_post_exception(connection, except, notification, 0) ; + return bgp_fsm_catch(connection, next_state) ; +} ; + +/*============================================================================== + * For debug... + */ +#define BGP_FSM_DEBUG(connection, message) \ + if (BGP_DEBUG (fsm, FSM)) \ + plog_debug (connection->log, "%s [FSM] " message, connection->host) + +/*============================================================================== + * The FSM table + */ + +#define bgp_fsm_action(FUNCNAME) \ + bgp_fsm_state_t FUNCNAME(bgp_connection connection, \ + bgp_fsm_state_t next_state, bgp_fsm_event_t event) + +typedef bgp_fsm_action(bgp_fsm_action_func) ; + +struct bgp_fsm +{ + bgp_fsm_action_func* action ; + bgp_fsm_state_t next_state ; +} ; + +static bgp_fsm_action(bgp_fsm_null) ; +static bgp_fsm_action(bgp_fsm_enter) ; +static bgp_fsm_action(bgp_fsm_stop) ; +static bgp_fsm_action(bgp_fsm_invalid) ; +static bgp_fsm_action(bgp_fsm_start) ; +static bgp_fsm_action(bgp_fsm_send_open) ; +static bgp_fsm_action(bgp_fsm_failed) ; +static bgp_fsm_action(bgp_fsm_fatal) ; +static bgp_fsm_action(bgp_fsm_retry) ; +static bgp_fsm_action(bgp_fsm_closed) ; +static bgp_fsm_action(bgp_fsm_expire) ; +static bgp_fsm_action(bgp_fsm_recv_open) ; +static bgp_fsm_action(bgp_fsm_error) ; +static bgp_fsm_action(bgp_fsm_recv_nom) ; +static bgp_fsm_action(bgp_fsm_sent_nom) ; +static bgp_fsm_action(bgp_fsm_send_kal) ; +static bgp_fsm_action(bgp_fsm_establish) ; +static bgp_fsm_action(bgp_fsm_recv_kal) ; +static bgp_fsm_action(bgp_fsm_update) ; +static bgp_fsm_action(bgp_fsm_exit) ; + +/*------------------------------------------------------------------------------ + * Finite State Machine events + * + * 0. null_event + * + * Do nothing. As quietly as possible. + * + * Never generated, so should not be seen ! + * + * 1. BGP_Start + * + * a. in Initial state (-> Idle) + * + * raised immediately after creating the connection. + * + * b. in Idle state + * + * raised on expiry of IdleHoldTime. + * + * primary connection: proceed to Connect + * + * secondary connection: proceed to Accept + * + * Cannot happen at any other time. + * + * 2. BGP_Stop + * + * a. in all states: + * + * -- from Routeing Engine -- at any time. + * + * -- internally in the event of collision resolution. + * + * -- internally, in the event of some protocol error -- once + * connection is up and packets are being transferred. + * + * See above for further discussion. + * + * 3. TCP_connection_open + * + * a. primary connection: in Connect state (-> OpenSent) + * + * raised when a connect() connection succeeds + * + * b. secondary connection: in Active state (-> OpenSent) + * + * raised when an accept() connection is accepted. + * + * Cannot happen at any other time. + * + * 4. TCP_connection_closed + * + * Raised by "EOF" on read or by EPIPE and some other errors. + * + * a. in OpenSent and OpenConfirm states + * + * This may be because the the other end has detected a collision. + * It may be because the other end is being vexatious. + * + * Fall back to Idle. + * + * b. and Established state + * + * Stop the session. + * + * NB: any errors generated when the OPEN message is sent (on exit from + * Connect or Active states) are not delivered until has entered + * OpenSent state. + * + * Cannot happen at any other time. + * + * 5. TCP_connection_open_failed ("soft" error) + * + * a. in Connect or Active states + * + * Close the connection. For Active state, disable accept. + * + * Stay in Connect/Active (until ConnectRetryTimer expires). + * + * Cannot happen at any other time. + * + * 6. TCP_fatal_error ("hard" error) + * + * a. in Connect or Active states + * + * Close the connection. For Active state, disable accept. + * + * Stay in Connect/Active (until ConnectRetryTimer expires). + * + * b. in OpenSent/OpenConfirm states + * + * Close the connection. + * + * Fall back to Idle. + * + * c. in Established state + * + * Close the connection and the session. + * + * Go to Stopping state. + * + * d. in Stopping state. + * + * Close the connection. + * + * Cannot happen at any other time (ie Idle). + * + * 7. ConnectRetry_timer_expired + * + * a. in either Connect or Active states ONLY. + * + * Time to give up current connection attempt(s), and start trying + * to connect all over again. + * + * Cannot happen at any other time. + * + * 8. Hold_Timer_expired + * + * a. in OpenSent state + * + * Time to give up waiting for an OPEN (or NOTIFICATION) from the + * other end. For this wait the RFC recommends a "large" value for + * the hold time -- and suggests 4 minutes. + * + * Or, if the connection was stopped by the collision resolution + * process, time to close the connection. + * + * Close the connection. Fall back to Idle. + * + * b. in OpenConfirm state + * + * Time to give up waiting for a KEEPALIVE to confirm the connection. + * For this wait the hold time used is that negotiated in the OPEN + * messages that have been exchanged. + * + * Or, if the connection was stopped by the collision resolution + * process, time to close the connection. + * + * Close the connection. Fall back to Idle. + * + * c. in Established state + * + * The session has failed. Stop. + * + * In this state the hold time used is that negotiated in the OPEN + * messages that have been exchanged. + * + * d. in Stopping state + * + * Time to give up trying to send NOTIFICATION and terminate the + * connection. + * + * Cannot happen at any other time. + * + * 9. KeepAlive_timer_expired + * + * a. in OpenConfirm and Established states + * + * Time to send a KEEPALIVE message. + * + * In these states the keepalive time used is that which follows + * from the hold time negotiated in the OPEN messages that have been + * exchanged. + * + * Cannot happen at any other time. + * + * 10. Receive_OPEN_message + * + * Generated by read action. + * + * a. in OpenSent state -- the expected response + * + * Proceed (via collision resolution) to OpenConfirm or Stopping. + * + * b. in OpenConfirm state -- FSM error + * + * Send NOTIFICATION. Fall back to Idle. + * + * c. in Established state -- FSM error + * + * Send NOTIFICATION. Terminate session. + * + * Cannot happen at any other time (connection not up or read closed). + * + * 11. Receive_KEEPALIVE_message + * + * Generated by read action. + * + * a. in OpenSent state -- FSM error + * + * Send NOTIFICATION. Fall back to Idle. + * + * b. in OpenConfirm state -- the expected response + * + * c. in Established state -- expected + * + * Cannot happen at any other time (connection not up or read closed). + * + * 12. Receive_UPDATE_message + * + * Generated by read action. + * + * a. in OpenSent and OpenConfirm states -- FSM error + * + * Send NOTIFICATION. Fall back to Idle. + * + * b. in Established state -- expected + * + * Cannot happen at any other time (connection not up or read closed). + * + * 13. Receive_NOTIFICATION_message + * + * Generated by read action. + * + * a. in OpenSent, OpenConfirm and Established states -- give up + * on the session. + * + * a. in OpenSent, OpenConfirm and Established states -- give up + * on the session. + * + * Cannot happen at any other time (connection not up or read closed). + * + * 14. Sent_NOTIFICATION_message + * + * Generated by write action when completed sending the message. + * + * a. in Stopping state -- the desired outcome + * + * Terminate the connection. + * + * Cannot happen at any other time. + */ + +/*------------------------------------------------------------------------------ + * Finite State Machine Table + */ + +static const struct bgp_fsm +bgp_fsm[bgp_fsm_last_state + 1][bgp_fsm_last_event + 1] = +{ + { + /* bgp_fsm_Initial: initialised in this state............................... + * + * Expect only a BGP_Start event, which arms the IdleHoldTimer and advances + * to the Idle state. + * + * Could (just) get a bgp_fsm_Stop if other connection stops immediately ! + * + * A connection should be in this state for a brief period between being + * initialised and set going. + * + * All other events (other than null) are invalid (should not happen). + */ + {bgp_fsm_null, bgp_fsm_Initial}, /* null event */ + {bgp_fsm_enter, bgp_fsm_Idle}, /* BGP_Start */ + {bgp_fsm_stop, bgp_fsm_Initial}, /* BGP_Stop */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_closed */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_fatal_error */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Hold_Timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ + }, + { + /* bgp_fsm_Idle: waiting for IdleHoldTimer.................................. + * + * When a session is enabled both its connections start in this state. + * (Noting that an accept() only session starts only the secondary + * connection and a connect() only session starts only the primary.) + * + * While in this state is waiting for the IdleHoldTimer to expire. This + * timer becomes longer if the peer misbehaves. + * + * If a connection stops at OpenState or OpenConfirm, may loop back through + * Idle, with an increased IdleHoldTimer. + * + * In Idle state the connection is dormant. (While the secondary is Idle, + * no connections will be accepted.) + * + * If the peer keeps making or accepting TCP connections, and then dropping + * them, then the IdleHoldTimer will grow to slow down the rate of vexatious + * connections. + * + * When a connection falls back to Idle it will have been closed. + * + * The expected events are: + * + * * BGP_Start -- generated by IdleHoldTimer expired + * + * For primary connection: + * + * Causes a connect() to be attempted. + * + * * Connect state -- if connect() OK, or failed "soft" + * + * * Stopping state -- if connect() failed "hard" + * + * Bring connection and session to a dead stop. + * + * For secondary connection: + * + * Enables session->accept, and goes to "Active" state. + * + * Note that bgp_fsm_start() decides on the appropriate next state. + * + * * BGP_Stop -- for whatever reason + * + * All other events (other than null) are invalid (should not happen). + */ + {bgp_fsm_null, bgp_fsm_Idle}, /* null event */ + {bgp_fsm_start, bgp_fsm_Connect}, /* BGP_Start */ + {bgp_fsm_stop, bgp_fsm_Idle}, /* BGP_Stop */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_closed */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_fatal_error */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Hold_Timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ + }, + { + /* bgp_fsm_Connect: waiting for connect (and listen)........................ + * + * Only the primary connection can be in this state. + * + * While in this state is waiting for connection to succeed or fail, or for + * the ConnectRetryTimer to expire. + * + * The expected events are: + * + * * TCP_connection_open + * + * Send BGP OPEN message, arm the HoldTimer ("large" value) and advance + * to OpenSent. + * + * * TCP_connection_open_fail ("soft" error) + * + * Shut down the connection. Stay in Connect state. + * + * The ConnectRetryTimer is left running. + * + * * TCP_fatal_error ("hard" error) + * + * Bring connection and session to a dead stop. + * + * * ConnectRetry_timer_expired + * + * Shut down the connection. Retry opening a connection. Stay in + * Connect state. Refresh the ConnectRetryTimer. + * + * * BGP_Stop -- for whatever reason + * + * All other events (other than null) are invalid (should not happen). + */ + {bgp_fsm_null, bgp_fsm_Connect}, /* null event */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ + {bgp_fsm_stop, bgp_fsm_Connect}, /* BGP_Stop */ + {bgp_fsm_send_open, bgp_fsm_OpenSent}, /* TCP_connection_open */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_closed */ + {bgp_fsm_failed, bgp_fsm_Connect}, /* TCP_connection_open_failed */ + {bgp_fsm_fatal, bgp_fsm_Connect}, /* TCP_fatal_error */ + {bgp_fsm_retry, bgp_fsm_Connect}, /* ConnectRetry_timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Hold_Timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ + }, + { + /* bgp_fsm_Active: waiting for listen (only)................................ + * + * Only the secondary connection can be in this state. + * + * While in this state is waiting for an incoming connection to succeed or + * for the ConnectRetryTimer to expire. + * + * The expected events are: + * + * * TCP_connection_open + * + * Send BGP OPEN message, arm the HoldTimer ("large" value) and advance + * to OpenSent. + * + * * TCP_connection_open_fail ("soft" error) + * + * Shut down the connection. Stay in Connect state. + * + * The ConnectRetryTimer is left running. + * + * * TCP_fatal_error + * + * Bring connection and session to a dead stop. + * + * * ConnectRetry_timer_expired + * + * Stay in Active state. Refresh the ConnectRetryTimer. + * + * * BGP_Stop -- for whatever reason + * + * All other events (other than null) are invalid (should not happen). + */ + {bgp_fsm_null, bgp_fsm_Active}, /* null event */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ + {bgp_fsm_stop, bgp_fsm_Active}, /* BGP_Stop */ + {bgp_fsm_send_open, bgp_fsm_OpenSent}, /* TCP_connection_open */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_closed */ + {bgp_fsm_failed, bgp_fsm_Active}, /* TCP_connection_open_failed */ + {bgp_fsm_fatal, bgp_fsm_Active}, /* TCP_fatal_error */ + {bgp_fsm_retry, bgp_fsm_Active}, /* ConnectRetry_timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Hold_Timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ + }, + { + /* bgp_fsm_OpenSent: waiting for Open from the other end.................... + * + * Both primary and secondary connections can be in this state. + * + * While in this state is waiting for a BGP OPEN to arrive or for the + * HoldTimer ("large" value) to expire. + * + * The expected events are: + * + * * Receive_OPEN_message + * + * This means has received a satisfactory BGP OPEN from the other end, + * so the session is very nearly up. + * + * If there is another connection, and it is in OpenConfirm state, + * then must now choose between the two -- terminating one or the + * other with a "Connection Collision Resolution" NOTIFICATION message. + * + * If proceeding, send a BGP KEEPALIVE message (effectively ACK), arm + * HoldTimer and KeepliveTimer (as per negotiated values) and advance + * to OpenConfirm state. + * + * * Receive_UPDATE_message + * + * FSM error -- bring connection to a dead stop. + * + * * Receive_KEEPALIVE_message + * + * FSM error -- bring connection to a dead stop. + * + * * Receive_NOTIFICATION_message + * + * Bring connection to a dead stop. + * + * * TCP_connection_closed + * + * Close connection, + * + * * TCP_fatal_error + * + * Bring connection and session to a dead stop. + * + * * Hold_Timer_expired + * + * If primary, promote the secondary. If no secondary... + * + * * BGP_Stop -- for whatever reason + * + * All other events (other than null) are invalid (should not happen). + */ + {bgp_fsm_null, bgp_fsm_OpenSent}, /* null event */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ + {bgp_fsm_stop, bgp_fsm_Idle}, /* BGP_Stop */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ + {bgp_fsm_closed, bgp_fsm_Idle}, /* TCP_connection_closed */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ + {bgp_fsm_fatal, bgp_fsm_Idle}, /* TCP_fatal_error */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ + {bgp_fsm_expire, bgp_fsm_Idle}, /* Hold_Timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ + {bgp_fsm_recv_open, bgp_fsm_OpenConfirm}, /* Receive_OPEN_message */ + {bgp_fsm_error, bgp_fsm_OpenSent}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_error, bgp_fsm_OpenSent}, /* Receive_UPDATE_message */ + {bgp_fsm_recv_nom, bgp_fsm_Idle}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_sent_nom, bgp_fsm_OpenSent}, /* Sent NOTIFICATION message */ + }, + { + /* bgp_fsm_OpenConfirm: Opens sent and received, waiting for KeepAlive...... + * + * Only one of the two connections can reach this state. + * + * While in this state is waiting for a BGP KEEPALIVE to arrive or for the + * HoldTimer to expire, or for the KeepaliveTimer to prompt sending of + * another KEEPALIVE message. + * + * The expected events are: + * + * * Receive_KEEPALIVE_message + * + * This means that the other end is acknowledging the OPEN, and the + * session is now Established. + * + * If there is another connection, now is the time to kill it off. + * + * This connection becomes the primary and only connection. + * + * Arm HoldTimer and KeepliveTimer (as per negotiated values) and + * advance to Established state. + * + * Pass a session established message to the Routeing Engine, complete + * with the bgp_open_state for the successful connection. + * + * * Receive_OPEN_message + * + * FSM error -- bring connection to a dead stop. + * + * If primary, promote the secondary. If no secondary... + * + * * Receive_UPDATE_message + * + * FSM error -- bring connection to a dead stop. + * + * If primary, promote the secondary. If no secondary... + * + * * Receive_NOTIFICATION_message + * + * Bring connection to a dead stop. + * + * If primary, promote the secondary. If no secondary... + * + * * TCP_connection_closed + * + * If primary, promote the secondary. If no secondary... + * + * * TCP_fatal_error + * + * Bring connection and session to a dead stop. + * + * * KeepAlive_Timer_expired + * + * Send KEEPALIVE message and recharge KeepaliveTimer. + * + * * Hold_Timer_expired + * + * If primary, promote the secondary. If no secondary... + * + * * BGP_Stop -- for whatever reason + * + * All other events (other than null) are invalid (should not happen). + */ + {bgp_fsm_null, bgp_fsm_OpenConfirm}, /* null event */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ + {bgp_fsm_stop, bgp_fsm_Idle}, /* BGP_Stop */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ + {bgp_fsm_closed, bgp_fsm_Idle}, /* TCP_connection_closed */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ + {bgp_fsm_fatal, bgp_fsm_Idle}, /* TCP_fatal_error */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ + {bgp_fsm_expire, bgp_fsm_Idle}, /* Hold_Timer_expired */ + {bgp_fsm_send_kal, bgp_fsm_OpenConfirm}, /* KeepAlive_timer_expired */ + {bgp_fsm_error, bgp_fsm_OpenConfirm}, /* Receive_OPEN_message */ + {bgp_fsm_establish, bgp_fsm_Established}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_error, bgp_fsm_OpenConfirm}, /* Receive_UPDATE_message */ + {bgp_fsm_recv_nom, bgp_fsm_Idle}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_sent_nom, bgp_fsm_OpenConfirm}, /* Sent NOTIFICATION message */ + }, + { + /* bgp_fsm_Established: session is up and running........................... + * + * Only the primary connection exists in this state. + * + * While in this state is waiting for a BGP UPDATE (or KEEPALIVE) messages + * to arrive or for the HoldTimer to expire, or for the KeepaliveTimer to + * prompt sending of another KEEPALIVE message. + * + * The expected events are: + * + * * Receive_OPEN_message + * + * FSM error -- bring connection and session to a dead stop. + * + * * Receive_UPDATE_message + * + * Restart the HoldTimer. + * + * * Receive_KEEPALIVE_message + * + * Restart the HoldTimer. + * + * * Receive_NOTIFICATION_message + * + * Bring connection and session to a dead stop. + * + * * TCP_connection_closed + * + * Bring connection and session to a dead stop. + * + * * TCP_fatal_error + * + * Bring connection and session to a dead stop. + * + * * KeepAlive_Timer_expired + * + * Send KEEPALIVE message and recharge KeepaliveTimer. + * + * * Hold_Timer_expired + * + * If primary, promote the secondary. If no secondary... + * + * * BGP_Stop -- for whatever reason + * + * All other events (other than null) are invalid (should not happen). + */ + {bgp_fsm_null, bgp_fsm_Established}, /* null event */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ + {bgp_fsm_stop, bgp_fsm_Stopping}, /* BGP_Stop */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ + {bgp_fsm_closed, bgp_fsm_Stopping}, /* TCP_connection_closed */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ + {bgp_fsm_fatal, bgp_fsm_Stopping}, /* TCP_fatal_error */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ + {bgp_fsm_expire, bgp_fsm_Stopping}, /* Hold_Timer_expired */ + {bgp_fsm_send_kal, bgp_fsm_Established}, /* KeepAlive_timer_expired */ + {bgp_fsm_error, bgp_fsm_Stopping}, /* Receive_OPEN_message */ + {bgp_fsm_recv_kal, bgp_fsm_Established}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_update, bgp_fsm_Established}, /* Receive_UPDATE_message */ + {bgp_fsm_recv_nom, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ + }, + { + /* bgp_fsm_Stopping: waiting (briefly) to send Notification................. + * + * Before a connection is sent to Stopping state the reasons for stopping + * are set. (See bgp_fsm_set_stopping.) + * + * There are three ways to arrive in Stopping state: + * + * a) administrative Stop -- that is, the Routeing Engine is stopping the + * session. + * + * Both connections must be stopped. + * + * b) the sibling has reached Established state and is snuffing out + * its rival. + * + * Only the current connection must be stopped. + * + * c) the session was Established, but is now stopping. + * + * There is only one connection, and that must be stopped. + * + * Before the transition to Stopping state, + * + * The complication is the possible need to send a NOTIFICATION message + * before closing the connection. + * + * Once a connection has reached Established state, the TCP write buffers + * may be full, so it may not be possible immediately to send the + * NOTIFICATION. Note that stopping from Established state is always + * stop-dead. + * + * In other states there should be plenty of room in the TCP write buffers. + * + * On entry to Stopping: + * + * 1) if this is stop-dead -- unlink self from session. + * + * NB: this clears the pointer from session to connection. + * + * .... + * + * 2) if there is a NOTIFICATION message (notification_pending): + * + * * close the connection for reading and purge read buffers + * * purge the write buffering and any pending writes + * * stop all timers + * * send the NOTIFICATION + * + * if the NOTIFICATION immediately clears the buffers (or fails), + * clear the notification_pending flag. + * + * 3) if the notification_pending flag is still set: + * + * * for stop-idle set a short time-out (5 seconds) + * * for stop-dead set a longer time-out (30 seconds) + * + * stays in Stopping state, waiting for NOTIFICATION to be sent, or + * to fail, or for the timeout. + * + * (Should not really need the time-out for stop-idle, but seems + * neater than crash closing the connection.) + * + * While in Stopping state, any further event will clear the + * notification-pending flag. + * + * When the notification-pending flag is not set: + * + * * close the connection + * * purge all buffering + * * stop all timers + * + * * for stop-idle: proceed to Idle state + + + + + + * In this state the connection is no longer associated with a session. + * + * This state exists only to allow the TCP output buffer to drain + * sufficiently to allow the tail end of one BGP message to be sent, + * followed by a NOTIFICATION message. + * + * When entering this state, if there is no NOTIFICATION to send, then + * will terminate the session. + * + * While in this state is waiting for the NOTIFICATION message to have been + * sent, or for the HoldTimer to expire (does not wait indefinitely). + * + * The expected events are: + * + * * Sent NOTIFICATION message + * * Hold_Timer_expired + * * TCP_fatal_error + * * TCP_connection_closed + * + * Clear NOTIFICATION pending, so connection will then be terminated. + * + * All other events (other than null) are invalid (should not happen). + */ + {bgp_fsm_null, bgp_fsm_Stopping}, /* null event */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Start */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* BGP_Stop */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open */ + {bgp_fsm_exit, bgp_fsm_Stopping}, /* TCP_connection_closed */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* TCP_connection_open_failed */ + {bgp_fsm_exit, bgp_fsm_Stopping}, /* TCP_fatal_error */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* ConnectRetry_timer_expired */ + {bgp_fsm_exit, bgp_fsm_Stopping}, /* Hold_Timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* KeepAlive_timer_expired */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_OPEN_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_UPDATE_message */ + {bgp_fsm_invalid, bgp_fsm_Stopping}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_sent_nom, bgp_fsm_Stopping}, /* Sent NOTIFICATION message */ + }, +} ; + +static const char *bgp_event_str[] = +{ + "NULL", + "BGP_Start", + "BGP_Stop", + "TCP_connection_open", + "TCP_connection_closed", + "TCP_connection_open_failed", + "TCP_fatal_error", + "ConnectRetry_timer_expired", + "Hold_Timer_expired", + "KeepAlive_timer_expired", + "Receive_OPEN_message", + "Receive_KEEPALIVE_message", + "Receive_UPDATE_message", + "Receive_NOTIFICATION_message", + "Sent_NOTIFICATION_message", +} ; + +/*============================================================================== + * Signal FSM event. + */ + +static void +bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) ; + +/*------------------------------------------------------------------------------ + * Signal event to FSM for the given connection. + * + * + * + */ +extern void +bgp_fsm_event(bgp_connection connection, bgp_fsm_event_t event) +{ + bgp_session session ; + bgp_fsm_state_t next_state ; + const struct bgp_fsm* fsm ; + + dassert( (event >= bgp_fsm_null_event) + && (event <= bgp_fsm_last_event)) ; + dassert( (connection->state >= bgp_fsm_first_state) + && (connection->state <= bgp_fsm_last_state) ) ; + + /* Watch out for recursing through the FSM for this connection. */ + ++connection->fsm_active ; + + if (connection->fsm_active == 2) + { + connection->post = event ; + return ; + } ; + + /* Lock the session for the convenience of the event handlers. + * + * NB: if the current state is Stopping, then connection is no longer + * attached to session -- so connection->session is NULL -- BEWARE ! + * + * The session lock does nothing if no session is attached. + */ + session = connection->session ; + + if (session != NULL) + BGP_SESSION_LOCK(session) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + + do + { + assert(connection->fsm_active == 1) ; + + fsm = &bgp_fsm[connection->state][event] ; + next_state = fsm->next_state ; + + /* Call function. */ + next_state = fsm->action(connection, next_state, event) ; + + dassert( (next_state >= bgp_fsm_first_state) + && (next_state <= bgp_fsm_last_state) ) ; + + /* If state is changed. */ + if (next_state != connection->state) + { + bgp_fsm_state_t prev_state = connection->state ; + + /* Complete the state change */ + bgp_fsm_state_change(connection, next_state) ; + + /* Log state change as required. */ + if (BGP_DEBUG(fsm, FSM)) + plog_debug(connection->log, + "%s [FSM] %s (%s->%s)", + connection->host, + bgp_event_str[event], + LOOKUP (bgp_status_msg, prev_state), + LOOKUP (bgp_status_msg, next_state)) ; + + if (BGP_DEBUG(normal, NORMAL)) + zlog_debug ("%s on %s went from %s to %s", + connection->host, + bgp_event_str[event], + LOOKUP (bgp_status_msg, prev_state), + LOOKUP (bgp_status_msg, next_state)); + } ; + + /* Pick up post event -- if any */ + event = connection->post ; + connection->post = bgp_fsm_null_event ; + + } while (--connection->fsm_active != 0) ; + + /* If required, post session event. */ + + if ((connection->except != bgp_session_null_event) && (session != NULL)) + { + /* Some exceptions are not reported to the Routeing Engine + * + * In particular: eDiscard and eCollision -- so the only time the + * connection->state will be Stopping is when the session is being + * stopped. (eDiscard and eCollision go quietly to Stopping !) + */ + if (connection->except <= bgp_session_max_event) + bgp_session_event(session, connection->except, + connection->notification, + connection->err, + connection->ordinal, + (connection->state == bgp_fsm_Stopping)) ; + + /* Tidy up -- notification already cleared */ + connection->except = bgp_session_null_event ; + connection->err = 0 ; + bgp_notify_free(&connection->notification) ; /* if any */ + } + + if (session != NULL) + BGP_SESSION_UNLOCK(session) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ +} ; + +/*============================================================================== + * The BGP FSM Action Functions + */ + +static void +bgp_hold_timer_set(bgp_connection connection, unsigned secs) ; + +static void +bgp_hold_timer_recharge(bgp_connection connection) ; + +static bgp_fsm_state_t +bgp_fsm_send_notification(bgp_connection connection, + bgp_fsm_state_t next_state) ; + +/*------------------------------------------------------------------------------ + * Null action -- do nothing at all. + */ +static bgp_fsm_action(bgp_fsm_null) +{ + return next_state ; +} ; + +/*------------------------------------------------------------------------------ + * Entry point to FSM. + * + * This is the first thing to happen to the FSM, and takes it from Initial + * state to Idle, with IdleHoldTimer running. + * + * NB: the IdleHoldTimer is always a finite time. So the start up event for + * the primary connection *cannot* fail. + * + * NB: the session is locked. + */ +static bgp_fsm_action(bgp_fsm_enter) +{ + if (connection->ordinal == bgp_connection_secondary) + bgp_prepare_to_accept(connection) ; + + return next_state ; +} ; + +/*------------------------------------------------------------------------------ + * Stop BGP Connection -- general exception event. + * + * An exception should have been raised, treat as invalid if not. + * + * If is eDisabled, set next_state == Stopping. + * If is eDiscard, set next_state == Stopping. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_stop) +{ + if (connection->except == bgp_session_null_event) + return bgp_fsm_invalid(connection, bgp_fsm_Stopping, event) ; + + if ( (connection->except == bgp_session_eDisabled) + || (connection->except == bgp_session_eDiscard) ) + next_state = bgp_fsm_Stopping ; + + return bgp_fsm_catch(connection, next_state) ; +} ; + +/*------------------------------------------------------------------------------ + * Invalid event -- cannot occur in current state. + * + * Brings down the session, sending an FSM error NOTIFICATION. + * + * Forces transition to Stopping state for this connection and any sibling. + * + * If already in Stopping state, force exit. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_invalid) +{ + if (BGP_DEBUG(fsm, FSM)) \ + plog_debug(connection->log, "%s [FSM] invalid event %d in state %d", + connection->host, event, connection->state) ; + + if (connection->state != bgp_fsm_Stopping) + return bgp_fsm_post_catch(connection, bgp_session_eInvalid, + bgp_notify_new(BGP_NOMC_FSM, BGP_NOMS_UNSPECIFIC, 0), + bgp_fsm_Stopping) ; + else + return bgp_fsm_exit(connection, bgp_fsm_Stopping, event) ; +} ; + +/*------------------------------------------------------------------------------ + * Start up BGP Connection + * + * This is used: + * + * * to change from Idle to Connect or Active -- when the IdleHoldTimer + * expires. + * + * * to loop back to Connect or Active -- when the ConnectRetryTimer expires. + * + * The state entered depends on whether this is the primary or secondary + * connection. + * + * If this is the primary connection, then kicks a connect() into life, + * before the state change. Note that if that fails, then post an event to + * be processed as soon as completes the state transition. + * + * If this is the secondary connection, enables the session for accept(). + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_start) +{ + if (connection->ordinal == bgp_connection_primary) + { + next_state = bgp_fsm_Connect ; + bgp_open_connect(connection) ; + } + else + { + next_state = bgp_fsm_Active ; + bgp_connection_enable_accept(connection) ; + } ; + + return next_state ; +} ; + +/*------------------------------------------------------------------------------ + * TCP connection open has come up -- connect() or accept() + * + * Send BGP Open Message to peer. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_send_open) +{ + char buf_l[SU_ADDRSTRLEN] ; + char buf_r[SU_ADDRSTRLEN] ; + const char* how ; + + if (BGP_DEBUG (normal, NORMAL)) + { + if (connection->ordinal == bgp_connection_primary) + how = "connect" ; + else + how = "accept" ; + + zlog_debug("%s open %s(), local address %s", + sockunion2str(connection->su_remote, buf_r, SU_ADDRSTRLEN), + how, + sockunion2str(connection->su_local, buf_l, SU_ADDRSTRLEN)) ; + } ; + + bgp_connection_read_enable(connection) ; + + bgp_msg_send_open(connection, connection->session->open_send) ; + + return next_state ; +} ; + +/*------------------------------------------------------------------------------ + * TCP connection has failed to come up -- Connect/Active states. + * + * This is in response to TCP_connection_open_failed, which has posted the + * exception -- so now need to deal with it. + * + * Close the connection -- if secondary connection, disable accept. + * + * Will stay in Connect/Active states. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_failed) +{ + return bgp_fsm_catch(connection, next_state) ; +} ; + +/*------------------------------------------------------------------------------ + * Fatal I/O error -- any state (other than Idle and Stopping). + * + * Close the connection (if any) -- if secondary connection, disable accept. + * + * This is in response to TCP_fatal_error, which has posted the + * exception -- so now need to deal with it. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_fatal) +{ + return bgp_fsm_catch(connection, next_state) ; +} ; + +/*------------------------------------------------------------------------------ + * ConnectRetryTimer expired -- Connect/Active states. + * + * If the connection failed, the connection will have been closed. For the + * secondary connection accept() will have been disabled. + * + * For primary connection: + * + * * close the attempt to connect() (if still ative) + * * start the connect() attempt again + * + * For secondary connection: + * + * * re-enable accept (if has been cleared) and wait for same + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_retry) +{ + if (connection->ordinal == bgp_connection_primary) + bgp_close_connect(connection) ; + + bgp_fsm_post_exception(connection, bgp_session_eRetry, NULL, 0) ; + + return bgp_fsm_start(connection, next_state, event) ; +} ; + +/*------------------------------------------------------------------------------ + * TCP connection has closed -- OpenSent/OpenConfirm/Established states + * + * This is in response to TCP_connection_closed, which has posted the + * exception -- so now need to deal with it. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_closed) +{ + return bgp_fsm_catch(connection, next_state) ; +} ; + +/*------------------------------------------------------------------------------ + * Hold timer expire -- OpenSent/OpenConfirm/Stopping + * + * This means either: have finished sending NOTIFICATION (end of "courtesy" + * wait time) + * + * or: can wait no longer for something from the other end. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_expire) +{ + /* The process of sending a NOTIFICATION comes to an end here. */ + if (connection->notification_pending) + { + bgp_connection_close(connection) ; + + return next_state ; + } ; + + /* Otherwise: post and immediately catch exception. */ + return bgp_fsm_post_catch(connection, bgp_session_eExpired, + bgp_notify_new(BGP_NOMC_HOLD_EXP, BGP_NOMS_UNSPECIFIC, 0), + next_state) ; +} ; + +/*------------------------------------------------------------------------------ + * Received an acceptable OPEN Message + * + * The next state is expected to be OpenConfirm. + * + * However: this is where we do Collision Resolution. + * + * If the sibling connection has reached OpenConfirm before this one, then now + * this one either closes its sibling, or itself. + * + * As soon as a connection reaches Established, it immediately kills off any + * sibling -- so the farthest two connections can get is to OpenSent. + * + * The connection that is closed should send a Cease/Collision Resolution + * NOTIFICATION. The other end should do likewise. + * + * The connection that is closed will fall back to Idle -- so that if the + * connection that wins the race to OpenConfirm fails there, then both will be + * back in Idle state. + * + * If makes it past Collision Resolution, respond with a KEEPALIVE (to "ack" + * the OPEN message). + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_recv_open) +{ + bgp_session session = connection->session ; + bgp_connection sibling = bgp_connection_get_sibling(connection) ; + + assert(session != NULL) ; + + /* If there is a sibling, and it is in OpenConfirm state, then now must do + * collision resolution. + */ + if ((sibling != NULL) && (sibling->state == bgp_fsm_OpenConfirm)) + { + bgp_connection loser ; + + /* NB: bgp_id in open_state is in *host* order */ + loser = (session->open_send->bgp_id < sibling->open_recv->bgp_id) + ? connection + : sibling ; + + /* Set reason for stopping */ + bgp_fsm_post_exception(loser, bgp_session_eCollision, + bgp_notify_new(BGP_NOMC_CEASE, BGP_NOMS_C_COLLISION, 0), 0) ; + + /* If self is the loser, treat this as a BGP_Stop event ! */ + /* Otherwise, issue BGP_Stop event for sibling. */ + if (loser == connection) + return bgp_fsm_catch(connection, next_state) ; + else + bgp_fsm_event(sibling, bgp_fsm_BGP_Stop) ; + } ; + + /* All is well: send a KEEPALIVE message to acknowledge the OPEN */ + bgp_msg_send_keepalive(connection) ; + + /* Transition to OpenConfirm state */ + return next_state ; +} + +/*------------------------------------------------------------------------------ + * FSM error -- received wrong type of message ! + * + * For example, an OPEN message while in Established state. + * + * For use in: OpenSent, OpenConfirm and Established states. + * + * Sends NOTIFICATION. + * + * Next state will be same as current, except for Established, when will be + * Stopping. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_error) +{ + return bgp_fsm_post_catch(connection, bgp_session_eFSM_error, + bgp_notify_new(BGP_NOMC_FSM, BGP_NOMS_UNSPECIFIC, 0), + next_state) ; +} ; + +/*------------------------------------------------------------------------------ + * Receive NOTIFICATION from far end -- OpenSent/OpenConfirm/Established + * + * This is in response to Receive_NOTIFICATION_message, which has posted the + * exception -- so now need to deal with it. + * + * Next state will be Idle, except for Established, when will be Stopping. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_recv_nom) +{ + return bgp_fsm_catch(connection, next_state) ; +} ; + +/*------------------------------------------------------------------------------ + * Pending NOTIFICATION has cleared write buffers + * -- OpenSent/OpenConfirm/Stopping + * + * Set the "courtesy" HoldTimer. Expect to stay in current state. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_sent_nom) +{ + bgp_hold_timer_set(connection, 5) ; + return next_state ; +} ; + +/*------------------------------------------------------------------------------ + * Seed Keepalive to peer. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_send_kal) +{ + bgp_msg_send_keepalive(connection) ; + return next_state ; +} ; + +/*------------------------------------------------------------------------------ + * Session Established ! + * + * If there is another connection, that is now snuffed out and this connection + * becomes the primary. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_establish) +{ + bgp_session session = connection->session ; + bgp_connection sibling = bgp_connection_get_sibling(connection) ; + + assert(session != NULL) ; + + /* The first thing to do is to snuff out any sibling */ + if (sibling != NULL) + bgp_fsm_discard_sibling(sibling, + bgp_notify_new(BGP_NOMC_CEASE, BGP_NOMS_C_COLLISION, 0)) ; + + /* Establish self as primary and copy state up to session */ + bgp_connection_make_primary(connection) ; + + /* Change the session state and post event */ + assert(session->state == bgp_session_sEnabled) ; + + session->state = bgp_session_sEstablished ; + bgp_fsm_post_exception(connection, bgp_session_eEstablished, NULL, 0) ; + + /* TODO: now would be a good time to withdraw the password from listener ? */ + + return next_state ; +} ; + +/*------------------------------------------------------------------------------ + * Keepalive packet is received -- OpenConfirm/Established + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_recv_kal) +{ + /* peer count update */ +//peer->keepalive_in++; + + bgp_hold_timer_recharge(connection) ; + return next_state ; +} ; + +/*------------------------------------------------------------------------------ + * Update packet is received. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_update) +{ + bgp_hold_timer_recharge(connection) ; + return next_state ; +} + +/*------------------------------------------------------------------------------ + * Connection exit + * + * Ring down the curtain. Connection structure will be freed by the BGP Engine. + * + * NB: requires the session LOCKED + */ +static bgp_fsm_action(bgp_fsm_exit) +{ + assert(connection->state == bgp_fsm_Stopping) ; + + bgp_connection_exit(connection) ; + + return bgp_fsm_Stopping ; +} ; + +/*============================================================================== + * Catching FSM Exceptions. + * + * Throwing/Posting Exceptions sets: + * + * connection->except ) + * connection->err ) which define the exception + * connection->notification ) + * + * An event has been raised, and the FSM has a (default next_state). + * + * 1a) notification & not eNOM_recv + * + * Start sending the NOTIFICATION message. + * + * NB: won't be a notification unless OpenSent/OpenConfirm/Established. + * + * For OpenSent/OpenConfirm, override the next_state to stay where it is + * until NOTIFICATION process completes. + * + * Sending NOTIFICATION closes the connection for reading. + * + * 1b) otherwise: close the connection. + * + * 2) if next state is Stopping, and not eDiscard + * + * This means we bring down the session, so discard any sibling. + * + * The sibling will send any notification, and proceed immediately to + * Stopping. + * + * (The sibling will be eDiscard -- so no deadly embrace here.) + * + * The state machine takes care of the rest: + * + * * complete entry to new state (for Stopping will cut connection loose). + * + * * send message to Routeing Engine + * + * NB: requires the session LOCKED + */ +static bgp_fsm_state_t +bgp_fsm_catch(bgp_connection connection, bgp_fsm_state_t next_state) +{ + /* If there is a NOTIFICATION to send, now is the time to do that. + * Otherwise, close the connection. + */ + if ( (connection->notification != NULL) + && (connection->except != bgp_session_eNOM_recv) ) + { + next_state = bgp_fsm_send_notification(connection, next_state) ; + } + else + bgp_connection_close(connection) ; + + /* If stopping and not eDiscard, do in any sibling */ + if ( (next_state == bgp_fsm_Stopping) + && (connection->except != bgp_session_eDiscard) ) + { + bgp_connection sibling ; + + sibling = bgp_connection_get_sibling(connection) ; /* ... if any */ + + if (sibling != NULL) + bgp_fsm_discard_sibling(sibling, + bgp_notify_dup(connection->notification)) ; + } ; + + /* Return the (possibly adjusted) next_state */ + return next_state ; +} ; + +/*------------------------------------------------------------------------------ + * Dispatch notification message + * + * Part closing the connection guarantees that can get the notification + * message into the buffers. + * + * Process will generate the following events: + * + * -- I/O failure of any sort + * -- Sent_NOTIFICATION_message + * -- HoldTimer expired + * + * When get Sent_NOTIFICATION_message, will set final "courtesy" timer, so + * unless I/O fails, final end of process is HoldTimer expired (with + * + */ +static bgp_fsm_state_t +bgp_fsm_send_notification(bgp_connection connection, bgp_fsm_state_t next_state) +{ + int ret ; + + /* If the next_state is not Stopping, then the sending of the notification + * holds the FSM in the current state. Will move forward when the + * HoldTimer expires -- either because lost patience in getting the + * notification away, or at the end of the "courtesy" time. + */ + if (next_state != bgp_fsm_Stopping) + next_state = connection->state ; + + /* Close for reading and flush write buffers. */ + bgp_connection_part_close(connection) ; + + /* Write the message + * + * If the write fails it raises a suitable event, which will now be + * sitting waiting to be processed on the way out of the FSM. + */ + ret = bgp_msg_write_notification(connection, connection->notification) ; + + connection->notification_pending = (ret >= 0) ; + /* is pending if not failed */ + if (ret > 0) + /* notification reached the TCP buffers instantly + * + * Send ourselves the good news ! + */ + bgp_fsm_event(connection, bgp_fsm_Sent_NOTIFICATION_message) ; + + else if (ret == 0) + /* notification is sitting in the write buffer + * + * Set notification_pending so that write action will raise the required + * event in due course. + * + * Set the HoldTimer to something suitable. Don't really expect this + * to happen in anything except Established state -- but copes. (Is + * ready to wait 20 seconds in Stopping state and 5 otherwise.) + */ + bgp_hold_timer_set(connection, (next_state == bgp_fsm_Stopping) ? 20 : 5) ; + + /* Return suitable state. */ + return next_state ; +} ; + +/*============================================================================== + * The BGP connections timers handling. + * + * The FSM has four timers: + * + * * IdleHoldTimer -- uses connection.hold_timer with jitter + * + * This runs while in Idle state, and is a period in which no connections + * are started, and none will be accepted. + * + * The purpose of this timer is to slow down re-making connections with + * peers who are flapping or otherwise proving a nuisance. + * + * This is a one shot timer, which generates a bgp_fsm_BGP_Start event. + * + * * ConnectRetryTimer -- uses connection.hold_timer with jitter + * + * This runs while in Connect or Active state, and is the period for which + * the connection is prepared to wait between attempts to connect. + * + * When trying to make a connect connection: + * + * The FSM will be in Connect state. + * + * If listen connections are enabled, will be listening. + * + * If nothing happens before the ConnectRetryTimer expires, then + * the connection attempt will be abandoned, and another started. + * + * If the connection attempt fails, moves to Active state -- with the + * timer still running. + * + * If nothing further happens before the ConnectRetryTimer expires, + * another connect will be started and the FSM returns to Connect state. + * + * When only listening is enabled: + * + * The FSM will be in Active state (!). + * + * If nothing happens before the ConnectRetryTimer expires, then the + * FSM will loop round back into Active state. + * + * This timer is recharged each time it goes off, and generates a + * bgp_fsm_ConnectRetry_timer_expired event. + * + * * HoldTimer -- uses connection.hold_timer *without* jitter + * + * This timer is used in OpenSent state, and limits the time will wait for + * an Open to appear from the other end. RFC4271 calls for this to be a + * "large value" -- suggesting 240 seconds. + * + * This timer is also used in OpenConfirm and Established states, and limits + * the time the connection will be held if hear nothing from the other end. + * In these states the timer is set to the negotiated HoldTime. If this is + * zero, then the HoldTime is infinite. + * + * This is a one shot timer, which generates a bgp_fsm_Hold_Timer_expired + * event. + * + * * KeepaliveTimer -- uses connection.keepalive_timer with jitter. + * + * This timer is used in OpenConfirm and Established states only. + * + * The default KeepalineTimer is 1/3 the HoldTimer, and is set from the + * negotiated HoldTime. If that is zero, then the KeepaliveTime is also + * infinite, and no KEEPALIVE messages will be sent (other than the "ack" + * of the OPEN message). + * + * This timer is recharged each time it goes off, and generates a + * bgp_fsm_KeepAlive_timer_expired event. + */ + +/* Forward reference */ +static void +bgp_timer_set(bgp_connection connection, qtimer timer, unsigned secs, + int jitter, qtimer_action* action) ; + +/* Forward reference the action functions */ +static qtimer_action bgp_idle_hold_timer_action ; +static qtimer_action bgp_connect_retry_timer_action ; +static qtimer_action bgp_hold_timer_action ; +static qtimer_action bgp_keepalive_timer_action ; + +/*============================================================================== + * Completion of State Change + * + * This performs fixed changes associated with the entry to each state from + * *another* state. + * + * connection->state == current (soon to be old) state + * + * Set and unset all the connection timers as required by the new state of + * the connection -- which may depend on the current state. + * + * NB: requires the session LOCKED + */ +static void +bgp_fsm_state_change(bgp_connection connection, bgp_fsm_state_t new_state) +{ + bgp_connection sibling ; + unsigned interval ; + bgp_session session = connection->session ; + + switch (new_state) + { + /* Base state of connection's finite state machine -- when a session has + * been enabled. Falls back to Idle in the event of various errors. + * + * In Idle state: + * + * either: the IdleHoldTimer is running, at the end of which the + * BGP Engine will try to connect. + * + * or: the connection is comatose, in which case will stay that way + * until sibling connection also falls back to Idle (from + * OpenSent/OpenConfirm. + * + * When entering Idle from anything other than Initial state, and not + * falling into a coma, extend the IdleHoldTimer. + * + * In Idle state refuses connections. + */ + case bgp_fsm_Idle: + interval = session->idle_hold_timer_interval ; + sibling = bgp_connection_get_sibling(connection) ; + + if (connection->state == bgp_fsm_Initial) + interval = (interval > 0) ? interval : 1 ; /* may not be zero */ + else + { + if ( (sibling != NULL) + && ( (sibling->state == bgp_fsm_OpenSent) + || (sibling->state == bgp_fsm_OpenConfirm) ) ) + { + interval = 0 ; /* unset the HoldTimer */ + connection->comatose = 1 ; /* so now comatose */ + } + else + { + /* increase the IdleHoldTimer interval */ + interval *= 2 ; + + if (interval < 4) /* enforce this minimum */ + interval = 4 ; + else if (interval > 120) + interval = 120 ; + + session->idle_hold_timer_interval = interval ; + + /* if sibling is comatose, set time for it to come round */ + + if ((sibling != NULL) && (sibling->comatose)) + { + connection->comatose = 0 ; /* no longer comatose */ + bgp_timer_set(sibling, &sibling->hold_timer, interval, 1, + bgp_idle_hold_timer_action) ; + } ; + } ; + } ; + + bgp_timer_set(connection, &connection->hold_timer, interval, 1, + bgp_idle_hold_timer_action) ; + + qtimer_unset(&connection->keepalive_timer) ; + + break; + + /* In Connect state the BGP Engine is attempting to make a connection + * with the peer and may be listening for a connection. + * + * In Active state the BGP Engine is only listening (!). + * + * In both cases, waits for the connect_hold_timer_interval. + * + * The ConnectRetryTimer automatically recharges, because will loop back + * round into the same state. + */ + case bgp_fsm_Connect: + case bgp_fsm_Active: + bgp_timer_set(connection, &connection->hold_timer, + session->connect_retry_timer_interval, 1, + bgp_connect_retry_timer_action) ; + qtimer_unset(&connection->keepalive_timer) ; + break; + + /* In OpenSent state is waiting for an OPEN from the other end, before + * proceeding to OpenConfirm state. + * + * Prepared to wait for quite a long time for this. + * + * Note that session->accept is left as it is. If have reached OpenSent + * on: + * + * * a connect() connection, then session->accept will be true and will + * still accept in-bound connections. + * + * * an accept() connection, then session->accept will be false. + */ + case bgp_fsm_OpenSent: + bgp_hold_timer_set(connection, session->open_hold_timer_interval) ; + qtimer_unset(&connection->keepalive_timer) ; + break; + + /* In OpenConfirm state is waiting for an "ack" before proceeding to + * Established. Session->accept is left as it is. If have reached + * OpenConfirm on: + * + * * a connect() connection, then session->accept may still be true and + * will still accept in-bound connections. (Collision detection may + * have discarded an accept() connection already.) + * + * * an accept() connection, then session->accept will be false. + * + * There is only one way into Established, and that is from OpenConfirm. + * OpenConfirm starts the KeepaliveTimer. It would be wrong to reset the + * timer on entry to Established. + * + * In both cases have just received a message, so can restart the HoldTimer. + * + * Both use the negotiated Hold Time and Keepalive Time. May send further + * KEEPALIVE messages in OpenConfirm. + * + * If the negotiated Hold Time value is zero, then the Keepalive Time + * value will also be zero, and this will unset both timers. + */ + case bgp_fsm_OpenConfirm: + bgp_timer_set(connection, &connection->keepalive_timer, + connection->keepalive_timer_interval, 1, + bgp_keepalive_timer_action) ; + case bgp_fsm_Established: + bgp_hold_timer_set(connection, connection->hold_timer_interval) ; + break; + + /* The connection is coming to an dead stop. + * + * If not sending a NOTIFICATION then stop HoldTimer now. + * + * Unlink connection from session. + */ + case bgp_fsm_Stopping: + if (!connection->notification_pending) + qtimer_unset(&connection->hold_timer) ; + + qtimer_unset(&connection->keepalive_timer) ; + + session->connections[connection->ordinal] = NULL ; + connection->session = NULL ; + connection->p_mutex = NULL ; + + break ; + + default: + zabort("Unknown bgp_fsm_state") ; + } ; + + /* Finally: set the new state */ + connection->state = new_state ; +} ; + +/*============================================================================== + * Timer set and Timer Action Functions + */ + +/*------------------------------------------------------------------------------ + * Start or reset given qtimer with given interval, in seconds. + * + * If the interval is zero, unset the timer. + */ +static void +bgp_timer_set(bgp_connection connection, qtimer timer, unsigned secs, + int jitter, qtimer_action* action) +{ + if (secs == 0) + qtimer_unset(timer) ; + else + { + secs *= 40 ; /* a bit of resolution for jitter */ + if (jitter) + secs -= ((rand() % ((int)secs + 1)) / 4) ; + qtimer_set_interval(timer, QTIME(secs) / 40, action) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Set HoldTimer with given time (without jitter) so will generate a + * Hold_Timer_expired event. + * + * Setting 0 will unset the HoldTimer. + */ +static void +bgp_hold_timer_set(bgp_connection connection, unsigned secs) +{ + bgp_timer_set(connection, &connection->hold_timer, secs, 0, + bgp_hold_timer_action) ; +} ; + +/*------------------------------------------------------------------------------ + * Recharge the HoldTimer + */ + +static void +bgp_hold_timer_recharge(bgp_connection connection) +{ + bgp_hold_timer_set(connection, connection->hold_timer_interval) ; +} ; + +/*------------------------------------------------------------------------------ + * BGP start timer action => bgp_fsm_BGP_Start event + * + * The timer is automatically unset, which is fine. + */ +static void +bgp_idle_hold_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) +{ + bgp_connection connection = timer_info ; + + BGP_FSM_DEBUG(connection, "Timer (start timer expire)") ; + + bgp_fsm_event(connection, bgp_fsm_BGP_Start) ; +} ; + +/*------------------------------------------------------------------------------ + * BGP connect retry timer => bgp_fsm_ConnectRetry_timer_expired event + * + * The timer is recharged here, applying a new "jitter", but that may be + * overridden by the bgp_event() handling. + */ +static void +bgp_connect_retry_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) +{ + bgp_connection connection = timer_info ; + + BGP_FSM_DEBUG(connection, "Timer (connect timer expire)") ; + + bgp_timer_set(connection, &connection->hold_timer, + connection->session->connect_retry_timer_interval, 1, NULL) ; + + bgp_fsm_event(connection, bgp_fsm_ConnectRetry_timer_expired) ; +} ; + +/*------------------------------------------------------------------------------ + * BGP hold timer => bgp_fsm_Hold_Timer_expired event + * + * The timer is automatically unset, which is fine. + */ +static void +bgp_hold_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) +{ + bgp_connection connection = timer_info ; + + BGP_FSM_DEBUG(connection, "Timer (holdtime timer expire)") ; + + bgp_fsm_event(connection, bgp_fsm_Hold_Timer_expired) ; +} ; + +/*------------------------------------------------------------------------------ + * BGP keepalive fire => bgp_fsm_KeepAlive_timer_expired + * + * The timer is recharged here, applying a new "jitter", but that may be + * overridden by the bgp_event() handling. + */ +static void +bgp_keepalive_timer_action(qtimer qtr, void* timer_info, qtime_mono_t when) +{ + bgp_connection connection = timer_info ; + + BGP_FSM_DEBUG(connection, "Timer (keepalive timer expire)") ; + + bgp_timer_set(connection, &connection->keepalive_timer, + connection->session->keepalive_timer_interval, 1, NULL) ; + + bgp_fsm_event(connection, bgp_fsm_KeepAlive_timer_expired) ; +} ; + +/*============================================================================*/ +/* BGP Peer Down Cause */ +/* TODO: this is also defined in bgp_peer.c */ +#if 0 +const char *peer_down_str[] = +{ + "", + "Router ID changed", + "Remote AS changed", + "Local AS change", + "Cluster ID changed", + "Confederation identifier changed", + "Confederation peer changed", + "RR client config change", + "RS client config change", + "Update source change", + "Address family activated", + "Admin. shutdown", + "User reset", + "BGP Notification received", + "BGP Notification send", + "Peer closed the session", + "Neighbor deleted", + "Peer-group add member", + "Peer-group delete member", + "Capability changed", + "Passive config change", + "Multihop config change", + "NSF peer closed the session" +}; +#endif diff --git a/bgpd/bgp_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, |