]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
move more of the state machine into proto_bfd
authorAlan T. DeKok <aland@freeradius.org>
Thu, 2 Mar 2023 21:27:11 +0000 (16:27 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 2 Mar 2023 21:27:39 +0000 (16:27 -0500)
the state machine doesn't send or receive packets, but that will
gradually get fixed.

src/listen/bfd/proto_bfd.c
src/listen/bfd/proto_bfd.h
src/listen/bfd/proto_bfd_udp.c
src/listen/bfd/proto_bfd_udp.mk
src/listen/bfd/session.c [new file with mode: 0644]
src/listen/bfd/session.h [new file with mode: 0644]

index 06932e844ec440574db1bba3049a15c6d35af37f..4965431a7d8f3be4758af1c4b6a1ce91a12bd4bb 100644 (file)
@@ -42,10 +42,10 @@ static CONF_PARSER const proto_bfd_config[] = {
 };
 
 static const CONF_PARSER peer_config[] = {
-       { FR_CONF_OFFSET("min_transmit_interval", FR_TYPE_TIME_DELTA, proto_bfd_peer_t, min_transmit_interval ) },
-       { FR_CONF_OFFSET("min_receive_interval", FR_TYPE_TIME_DELTA, proto_bfd_peer_t, min_receive_interval ) },
-       { FR_CONF_OFFSET("max_timeouts", FR_TYPE_UINT32, proto_bfd_peer_t, max_timeouts ) },
-
+       { FR_CONF_OFFSET("min_transmit_interval", FR_TYPE_TIME_DELTA, proto_bfd_peer_t, desired_min_tx_interval ) },
+       { FR_CONF_OFFSET("min_receive_interval", FR_TYPE_TIME_DELTA, proto_bfd_peer_t, required_min_rx_interval ) },
+       { FR_CONF_OFFSET("max_timeouts", FR_TYPE_UINT32, proto_bfd_peer_t, detect_multi ) },
+       { FR_CONF_OFFSET("demand", FR_TYPE_BOOL, proto_bfd_peer_t, demand_mode ) },
 
        CONF_PARSER_TERMINATOR
 };
@@ -433,14 +433,14 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx)
 
                        peer = (proto_bfd_peer_t *) c;
 
-                       FR_TIME_DELTA_BOUND_CHECK("peer.min_transmit_interval", peer->min_transmit_interval, >=, fr_time_delta_from_usec(30));
-                       FR_TIME_DELTA_BOUND_CHECK("peer.min_transmit_interval", peer->min_transmit_interval, <=, fr_time_delta_from_sec(2));
+                       FR_TIME_DELTA_BOUND_CHECK("peer.min_transmit_interval", peer->desired_min_tx_interval, >=, fr_time_delta_from_usec(30));
+                       FR_TIME_DELTA_BOUND_CHECK("peer.min_transmit_interval", peer->desired_min_tx_interval, <=, fr_time_delta_from_sec(2));
 
-                       FR_TIME_DELTA_BOUND_CHECK("peer.min_recieve_interval", peer->min_transmit_interval, >=, fr_time_delta_from_usec(30));
-                       FR_TIME_DELTA_BOUND_CHECK("peer.min_received_interval", peer->min_transmit_interval, <=, fr_time_delta_from_sec(2));
+                       FR_TIME_DELTA_BOUND_CHECK("peer.min_recieve_interval", peer->required_min_rx_interval, >=, fr_time_delta_from_usec(30));
+                       FR_TIME_DELTA_BOUND_CHECK("peer.min_received_interval", peer->required_min_rx_interval, <=, fr_time_delta_from_sec(2));
 
-                       FR_INTEGER_BOUND_CHECK("peer.max_timeouts", peer->max_timeouts, >=, 1);
-                       FR_INTEGER_BOUND_CHECK("peer.max_timeouts", peer->max_timeouts, <=, 10);
+                       FR_INTEGER_BOUND_CHECK("peer.max_timeouts", peer->detect_multi, >=, 1);
+                       FR_INTEGER_BOUND_CHECK("peer.max_timeouts", peer->detect_multi, <=, 10);
 
                        if (((c->ipaddr.af == AF_INET) && (c->ipaddr.prefix != 32)) ||
                            ((c->ipaddr.af == AF_INET6) && (c->ipaddr.prefix != 128))) {
index 0de1304270f2801969fea2abce3b181c03687803..6baa7909e0081359de1f129e8f237c5a827580a3 100644 (file)
 /*
  * $Id$
  *
- * @file proto_radius.h
+ * @file proto_bfd.h
  * @brief Structures for the RADIUS protocol
  *
- * @copyright 2018 Alan DeKok (aland@freeradius.org)
+ * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
  */
 #include <freeradius-devel/io/master.h>
 #include <freeradius-devel/bfd/bfd.h>
@@ -41,7 +41,76 @@ typedef struct {
 typedef struct {
        fr_client_t                     client;                 //!< might as well re-use this, others need it
 
-       fr_time_delta_t                 min_transmit_interval;
-       fr_time_delta_t                 min_receive_interval;
-       uint32_t                        max_timeouts;
+       /*
+        *      Peers are defined globally to a virtual server.  Each
+        *      peer can only have one session associated with it.
+        */
+       void                            *inst;                  //!< proto_bfd_udp instance using this session
+       fr_listen_t                     *listen;                //!< associated listener
+
+       int                             sockfd;                 //!< cached for laziness
+       fr_event_list_t                 *el;
+
+       struct sockaddr_storage remote_sockaddr;                //!< cached for laziness
+       socklen_t       salen;
+
+       /*
+        *      Internal state management
+        */
+       fr_event_timer_t const  *ev_timeout;                    //!< when we time out for not receiving a packet
+       fr_event_timer_t const  *ev_packet;                     //!< for when we next send a packet
+       fr_time_t       last_recv;                              //!< last received packet
+       fr_time_t       next_recv;                              //!< when we next expect to receive a packet
+       fr_time_t       last_sent;                              //!< the last time we sent a packet
+
+       bfd_session_state_t session_state;                      //!< our view of the session state
+       bfd_session_state_t remote_session_state;               //!< their view of the session state
+
+       /*
+        *      BFD state machine, and fields we use to manage it.
+        *
+        *      The public names in the configuration files are what makes sense.
+        *
+        *      The names here are the names from the protocol, so that we can be sure the state machine is
+        *      implemented correctly.
+        */
+       uint32_t        local_disc;                             //!< our session ID, which is unique to this session
+       uint32_t        remote_disc;                            //!< their session ID
+
+       bfd_diag_t      local_diag;                             //!< diagnostics for errors
+
+       uint32_t        detect_multi;
+
+       fr_time_delta_t desired_min_tx_interval;                //!< intervals between transmits
+       fr_time_delta_t required_min_rx_interval;               //!< intervals between receives
+
+       fr_time_delta_t remote_min_rx_interval;                 //!< their min_rx_interval
+       fr_time_delta_t remote_min_echo_rx_interval;            //!< their min_echo_rx_interval
+
+       fr_time_delta_t my_min_echo_rx_interval;                //!< what we send for echo_rx_interval
+
+       fr_time_delta_t next_min_tx_interval;                   //!< how to update this when we're polling
+
+
+       bool            demand_mode;                            //!< demand is "once session is up, stop sending packets"
+       bool            remote_demand_mode;                     //!< their demand mode
+
+       bool            doing_poll;
+
+       /*
+        *      Authentication configuration and states.
+        */
+       bool            auth_seq_known;                         //!< do we know the authentication sequence number?
+
+       bfd_auth_type_t auth_type;                              //!< what kind of authentication is used
+
+       uint32_t        recv_auth_seq;                          //!< their auth_seq number
+       uint32_t        xmit_auth_seq;                          //!< our auth_seq number
+
+       size_t          secret_len;                             //!< doesn't change while we're running
+
+       fr_time_delta_t detection_time;                         //!< used to set ev_timeout
+       int             detection_timeouts;                     //!< too many timeouts means !auth_seq_known
+
+       bool            passive;                                //!< active or passive role from RFC 5880 - unused
 } proto_bfd_peer_t;
index dc815fe1bed6fda078b7f62e52f2a25079c99fd1..95d66d59e44e81bae973d0eb221b970a73ee3a00 100644 (file)
@@ -30,7 +30,7 @@
 #include <freeradius-devel/io/listen.h>
 #include <freeradius-devel/io/schedule.h>
 
-#include "proto_bfd.h"
+#include "session.h"
 
 extern fr_app_io_t proto_bfd_udp;
 
@@ -321,11 +321,13 @@ static char const *mod_name(fr_listen_t *li)
 
 static int mod_bootstrap(module_inst_ctx_t const *mctx)
 {
-       proto_bfd_udp_t *inst = talloc_get_type_abort(mctx->inst->data, proto_bfd_udp_t);
+       proto_bfd_udp_t         *inst = talloc_get_type_abort(mctx->inst->data, proto_bfd_udp_t);
        CONF_SECTION            *conf = mctx->inst->conf;
        size_t                  num;
        CONF_ITEM               *ci;
        CONF_SECTION            *server_cs;
+       fr_rb_iter_inorder_t    iter;
+       proto_bfd_peer_t        *peer;
 
        inst->cs = conf;
 
@@ -400,6 +402,22 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx)
                return -1;
        }
 
+       /*
+        *      Walk over the list of peers, associating them with this listener.
+        */
+       for (peer = fr_rb_iter_init_inorder(&iter, inst->peers);
+            peer != NULL;
+            peer = fr_rb_iter_next_inorder(&iter)) {
+               if (peer->client.ipaddr.af != inst->ipaddr.af) continue;
+
+               if (peer->inst) continue;
+
+               peer->inst = inst;
+               if (bfd_session_init(peer) < 0) {
+                       return -1;
+               }
+       }
+
        return 0;
 }
 
index a2363202c813391fcf63b2bc21200015dc47f35a..c6de8e314c44edfdbe2b2148f48d5ff229ab4015 100644 (file)
@@ -4,6 +4,6 @@ ifneq "$(TARGETNAME)" ""
 TARGET         := $(TARGETNAME)$(L)
 endif
 
-SOURCES                := $(TARGETNAME).c
+SOURCES                := $(TARGETNAME).c session.c
 
 TGT_PREREQS    := libfreeradius-bfd$(L)
diff --git a/src/listen/bfd/session.c b/src/listen/bfd/session.c
new file mode 100644 (file)
index 0000000..27ed565
--- /dev/null
@@ -0,0 +1,1280 @@
+/*
+ *   This program 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 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program 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 this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ */
+
+/**
+ * $Id$
+ * @file src/listen/bfd/session.c
+ * @brief BFD Session handling
+ *
+ * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
+ */
+
+#include <freeradius-devel/util/debug.h>
+
+#include <freeradius-devel/util/time.h>
+#include <freeradius-devel/util/event.h>
+#include <freeradius-devel/util/base16.h>
+#include <freeradius-devel/util/md5.h>
+#include <freeradius-devel/util/rand.h>
+#include <freeradius-devel/util/sha1.h>
+#include <freeradius-devel/util/socket.h>
+#include <freeradius-devel/util/time.h>
+
+#include "session.h"
+
+
+#define BFD_MAX_SECRET_LENGTH 20
+
+#if 0
+typedef struct {
+       uint8_t         auth_type;
+       uint8_t         auth_len;
+       uint8_t         key_id;
+} __attribute__ ((packed)) bfd_auth_basic_t;
+
+
+typedef struct {
+       uint8_t         auth_type;
+       uint8_t         auth_len;
+       uint8_t         key_id;
+       uint8_t         password[16];
+} __attribute__ ((packed)) bfd_auth_simple_t;
+
+typedef struct {
+       uint8_t         auth_type;
+       uint8_t         auth_len;
+       uint8_t         key_id;
+       uint8_t         reserved;
+       uint32_t        sequence_no;
+       uint8_t         digest[MD5_DIGEST_LENGTH];
+} __attribute__ ((packed)) bfd_auth_md5_t;
+
+typedef struct {
+       uint8_t         auth_type;
+       uint8_t         auth_len;
+       uint8_t         key_id;
+       uint8_t         reserved;
+       uint32_t        sequence_no;
+       uint8_t         digest[SHA1_DIGEST_LENGTH];
+} __attribute__ ((packed)) bfd_auth_sha1_t;
+
+typedef union bfd_auth_t {
+       bfd_auth_basic_t        basic;
+       bfd_auth_simple_t       password;
+       bfd_auth_md5_t          md5;
+       bfd_auth_sha1_t         sha1;
+} __attribute__ ((packed)) bfd_auth_t;
+
+
+static int bfd_verify_sequence(proto_bfd_peer_t *session, uint32_t sequence_no,
+                              int keyed)
+{
+       uint32_t start, stop;
+
+       start = session->recv_auth_seq;
+       if (keyed) {
+               start++;
+       }
+       stop = start + 3 * session->detect_multi;
+
+       if (start < stop) {
+               if ((sequence_no < start) ||
+                   (sequence_no > stop)) {
+                       return 0;
+               }
+
+       } else {        /* start is ~2^32, stop is ~10 */
+               if ((sequence_no > start) &&
+                   (sequence_no < stop)) {
+                       return 0;
+               }
+       }
+
+       return 1;
+}
+
+static void bfd_calc_md5(proto_bfd_peer_t *session, bfd_packet_t *bfd)
+{
+       bfd_auth_md5_t *md5 = &bfd->auth.md5;
+
+       fr_assert(session->secret_len <= sizeof(md5->digest));
+       fr_assert(md5->auth_len == sizeof(*md5));
+
+       memset(md5->digest, 0, sizeof(md5->digest));
+       memcpy(md5->digest, session->secret, session->secret_len);
+
+       fr_md5_calc(md5->digest,(const uint8_t *) bfd, bfd->length);
+}
+
+static void bfd_auth_md5(proto_bfd_peer_t *session, bfd_packet_t *bfd)
+{
+       bfd_auth_md5_t *md5 = &bfd->auth.md5;
+
+       md5->auth_type = session->auth_type;
+       md5->auth_len = sizeof(*md5);
+       bfd->length += md5->auth_len;
+
+       md5->key_id = 0;
+       md5->sequence_no = session->xmit_auth_seq++;
+
+       bfd_calc_md5(session, bfd);
+}
+
+static int bfd_verify_md5(proto_bfd_peer_t *session, bfd_packet_t *bfd)
+{
+       int rcode;
+       bfd_auth_md5_t *md5 = &bfd->auth.md5;
+       uint8_t digest[sizeof(md5->digest)];
+
+       if (md5->auth_len != sizeof(*md5)) return 0;
+
+       if (md5->key_id != 0) return 0;
+
+       memcpy(digest, md5->digest, sizeof(digest));
+
+       bfd_calc_md5(session, bfd);
+       rcode = fr_digest_cmp(digest, md5->digest, sizeof(digest));
+
+       memcpy(md5->digest, digest, sizeof(md5->digest)); /* pedantic */
+
+       if (rcode != 0) {
+               DEBUG("BFD %s MD5 Digest failed: **** RE-ENTER THE SECRET ON BOTH ENDS ****", session->client.shortname);
+               return 0;
+       }
+
+       /*
+        *      Do this AFTER the authentication instead of before!
+        */
+       if (!session->auth_seq_known) {
+               session->auth_seq_known = 1;
+
+       } else if (!bfd_verify_sequence(session, md5->sequence_no,
+                                       (md5->auth_type == BFD_AUTH_MET_KEYED_MD5))) {
+               DEBUG("MD5 sequence out of window");
+               return 0;
+       }
+
+       session->recv_auth_seq = md5->sequence_no;
+
+       return 1;
+}
+
+static void bfd_calc_sha1(proto_bfd_peer_t *session, bfd_packet_t *bfd)
+{
+       fr_sha1_ctx ctx;
+       bfd_auth_sha1_t *sha1 = &bfd->auth.sha1;
+
+       fr_assert(session->secret_len <= sizeof(sha1->digest));
+       fr_assert(sha1->auth_len == sizeof(*sha1));
+
+       memset(sha1->digest, 0, sizeof(sha1->digest));
+       memcpy(sha1->digest, session->secret, session->secret_len);
+
+       fr_sha1_init(&ctx);
+       fr_sha1_update(&ctx, (const uint8_t *) bfd, bfd->length);
+       fr_sha1_final(sha1->digest, &ctx);
+}
+
+static void bfd_auth_sha1(proto_bfd_peer_t *session, bfd_packet_t *bfd)
+{
+       bfd_auth_sha1_t *sha1 = &bfd->auth.sha1;
+
+       sha1->auth_type = session->auth_type;
+       sha1->auth_len = sizeof(*sha1);
+       bfd->length += sha1->auth_len;
+
+       sha1->key_id = 0;
+       sha1->sequence_no = session->xmit_auth_seq++;
+
+       bfd_calc_sha1(session, bfd);
+}
+
+static int bfd_verify_sha1(proto_bfd_peer_t *session, bfd_packet_t *bfd)
+{
+       int rcode;
+       bfd_auth_sha1_t *sha1 = &bfd->auth.sha1;
+       uint8_t digest[sizeof(sha1->digest)];
+
+       if (sha1->auth_len != sizeof(*sha1)) return 0;
+
+       if (sha1->key_id != 0) return 0;
+
+       memcpy(digest, sha1->digest, sizeof(digest));
+
+       bfd_calc_sha1(session, bfd);
+       rcode = fr_digest_cmp(digest, sha1->digest, sizeof(digest));
+
+       memcpy(sha1->digest, digest, sizeof(sha1->digest)); /* pedantic */
+
+       if (rcode != 0) {
+               DEBUG("BFD %d SHA1 Digest failed: **** RE-ENTER THE SECRET ON BOTH ENDS ****", session->number);
+               return 0;
+       }
+
+       /*
+        *      Do this AFTER the authentication instead of before!
+        */
+       if (!session->auth_seq_known) {
+               session->auth_seq_known = 1;
+
+       } else if (!bfd_verify_sequence(session, sha1->sequence_no,
+                                       (sha1->auth_type == BFD_AUTH_MET_KEYED_SHA1))) {
+               DEBUG("SHA1 sequence out of window");
+               return 0;
+       }
+
+       session->recv_auth_seq = sha1->sequence_no;
+
+       return 1;
+}
+
+
+static int bfd_authenticate(proto_bfd_peer_t *session, bfd_packet_t *bfd)
+{
+       switch (bfd->auth.basic.auth_type) {
+       case BFD_AUTH_RESERVED:
+               return 0;
+
+       case BFD_AUTH_SIMPLE:
+               break;
+
+       case BFD_AUTH_KEYED_MD5:
+       case BFD_AUTH_MET_KEYED_MD5:
+               return bfd_verify_md5(session, bfd);
+
+       case BFD_AUTH_KEYED_SHA1:
+       case BFD_AUTH_MET_KEYED_SHA1:
+               return bfd_verify_sha1(session, bfd);
+       }
+
+       return 0;
+}
+
+
+
+static void bfd_sign(proto_bfd_peer_t *session, bfd_packet_t *bfd)
+{
+       if (bfd->auth_present) {
+               switch (session->auth_type) {
+               case BFD_AUTH_RESERVED:
+                       break;
+
+               case BFD_AUTH_SIMPLE:
+                       break;
+
+               case BFD_AUTH_KEYED_MD5:
+               case BFD_AUTH_MET_KEYED_MD5:
+                       bfd_auth_md5(session, bfd);
+                       break;
+
+               case BFD_AUTH_KEYED_SHA1:
+               case BFD_AUTH_MET_KEYED_SHA1:
+                       bfd_auth_sha1(session, bfd);
+                       break;
+               }
+       }
+}
+
+
+/*
+ *     Send an immediate response to a poll request.
+ *
+ *     Note that this doesn't affect our "last_sent" timer.
+ *     That's set only when we intend to send a packet.
+ */
+static void bfd_poll_response(proto_bfd_peer_t *session)
+{
+       bfd_packet_t bfd;
+
+       bfd_control_packet_init(session, &bfd);
+       bfd.poll = 0;           /* Section 6.5 */
+       bfd.final = 1;
+
+       /*
+        *      TO DO: rate limit poll responses.
+        */
+
+       bfd_sign(session, &bfd);
+
+       if (sendto(session->socket.fd, &bfd, bfd.length, 0,
+                  (struct sockaddr *) &session->remote_sockaddr,
+                  session->salen) < 0) {
+               ERROR("Failed sending poll response: %s", fr_syserror(errno));
+       }
+}
+
+
+static int bfd_process(proto_bfd_peer_t *session, bfd_packet_t *bfd)
+{
+       if (bfd->auth_present &&
+           (session->auth_type == BFD_AUTH_RESERVED)) {
+               DEBUG("BFD %d packet asked to authenticate an unauthenticated session.", session->number);
+               return 0;
+       }
+
+       if (!bfd->auth_present &&
+           (session->auth_type != BFD_AUTH_RESERVED)) {
+               DEBUG("BFD %d packet failed to authenticate an authenticated session.", session->number);
+               return 0;
+       }
+
+       if (bfd->auth_present && !bfd_authenticate(session, bfd)) {
+               return 0;
+       }
+
+       DEBUG("BFD %d processing packet", session->number);
+       session->remote_disc = bfd->my_disc;
+       session->remote_session_state = bfd->state;
+       session->remote_demand_mode = bfd->demand;
+
+       /*
+        *      The other end is reducing the RX interval.  Do that
+        *      now.
+        */
+       if ((bfd->required_min_rx_interval < session->remote_min_rx_interval) &&
+           !session->demand_mode) {
+               bfd_stop_control(session);
+               bfd_start_control(session);
+       }
+       session->remote_min_rx_interval = bfd->required_min_rx_interval;
+
+       session->remote_min_echo_rx_interval = bfd->min_echo_rx_interval;
+       if (bfd->min_echo_rx_interval == 0) {
+#if 0
+               /*
+                *      Echo packets are BFD packets with
+                *      application-layer data echoed back to the
+                *      sender.  We don't do that.
+                */
+               bfd_stop_echo(session);
+#endif
+       }
+
+       if (session->doing_poll && bfd->final) {
+               bfd_stop_poll(session);
+       }
+
+       /*
+        *      Update transmit intervals as in 6.8.7
+        */
+
+       /*
+        *      Update detection times as in 6.8.4
+        */
+       if (!session->demand_mode) {
+               if (session->required_min_rx_interval >= bfd->desired_min_tx_interval) {
+                       session->detection_time = session->required_min_rx_interval;
+               } else {
+                       session->detection_time = bfd->desired_min_tx_interval;
+               }
+       } else {
+               if (session->desired_min_tx_interval >= session->remote_min_rx_interval) {
+                       session->detection_time = session->desired_min_tx_interval;
+               } else {
+                       session->detection_time = session->remote_min_rx_interval;
+               }
+       }
+       session->detection_time *= session->detect_multi;
+
+       if (session->session_state == BFD_STATE_ADMIN_DOWN) {
+               DEBUG("Discarding BFD packet (admin down)");
+               return 0;
+       }
+
+       if (bfd->state == BFD_STATE_ADMIN_DOWN) {
+               if (bfd->state != BFD_STATE_DOWN) {
+                       session->local_diag = BFD_NEIGHBOR_DOWN;
+               }
+
+               DEBUG("BFD %d State %s -> DOWN (admin down)",
+                     session->number, bfd_state[session->session_state]);
+               session->session_state = BFD_STATE_DOWN;
+               bfd_trigger(session);
+
+               bfd_set_desired_min_tx_interval(session, USEC);
+
+       } else {
+               switch (session->session_state) {
+               case BFD_STATE_DOWN:
+                       switch (bfd->state) {
+                       case BFD_STATE_DOWN:
+                               DEBUG("BFD %d State DOWN -> INIT (neighbor down)",
+                                     session->number);
+                               session->session_state = BFD_STATE_INIT;
+                               bfd_trigger(session);
+
+                               bfd_set_desired_min_tx_interval(session, USEC);
+                               break;
+
+                       case BFD_STATE_INIT:
+                               DEBUG("BFD %d State DOWN -> UP (neighbor INIT)",
+                                     session->number);
+                               session->session_state = BFD_STATE_UP;
+                               bfd_trigger(session);
+                               break;
+
+                       default: /* don't change anything */
+                               break;
+                       }
+                       break;
+
+               case BFD_STATE_INIT:
+                       switch (bfd->state) {
+                       case BFD_STATE_INIT:
+                       case BFD_STATE_UP:
+                               DEBUG("BFD %d State INIT -> UP",
+                                     session->number);
+                               session->session_state = BFD_STATE_UP;
+                               bfd_trigger(session);
+                               break;
+
+                       default: /* don't change anything */
+                               break;
+                       }
+                       break;
+
+               case BFD_STATE_UP:
+                       switch (bfd->state) {
+                       case BFD_STATE_DOWN:
+                               session->local_diag = BFD_NEIGHBOR_DOWN;
+
+                               DEBUG("BFD %d State UP -> DOWN (neighbor down)", session->number);
+                               session->session_state = BFD_STATE_DOWN;
+                               bfd_trigger(session);
+
+                               bfd_set_desired_min_tx_interval(session, USEC);
+                               break;
+
+                       default:
+                               break;
+                       }
+                       break;
+
+               default:
+                       DEBUG("Internal sanity check failed");
+                       return 0;
+               }
+       }
+
+       /*
+        *      Check if demand mode should be active (Section 6.6)
+        */
+       if (session->remote_demand_mode &&
+           (session->session_state == BFD_STATE_UP) &&
+           (session->remote_session_state == BFD_STATE_UP)) {
+               DEBUG("BFD %d demand mode UP / UP, stopping packets", session->number);
+               bfd_stop_control(session);
+       }
+
+       if (bfd->poll) {
+               bfd_poll_response(session);
+       }
+
+       /*
+        *      We've received the packet for the purpose of Section
+        *      6.8.4.
+        */
+       session->last_recv = fr_time();
+
+       /*
+        *      We've received a packet, but missed the previous one.
+        *      Warn about it.
+        */
+       if ((session->detect_multi >= 2) && (fr_time_gt(session->last_recv, session->next_recv))) {
+               fr_radius_packet_t packet;
+               request_t request;
+
+               bfd_request(session, &request, &packet);
+
+               trigger_exec(unlang_interpret_get_thread_default(), NULL, "server.bfd.warn", false, NULL);
+       }
+
+
+       if ((!session->remote_demand_mode) ||
+           (session->session_state != BFD_STATE_UP) ||
+           (session->remote_session_state != BFD_STATE_UP)) {
+               bfd_start_control(session);
+       }
+
+       if (session->server_cs) {
+               request_t *request;
+               fr_radius_packet_t *packet, *reply;
+
+               request = request_alloc_internal(session, NULL);
+               packet = fr_radius_packet_alloc(request, 0);
+               reply = fr_radius_packet_alloc(request, 0);
+
+               bfd_request(session, request, packet);
+
+               memset(reply, 0, sizeof(*reply));
+
+               request->reply = reply;
+               fr_socket_addr_swap(&request->reply->socket, &session->socket);
+
+               /*
+                *      FIXME: add my state, remote state as VPs?
+                */
+               if (fr_debug_lvl) {
+                       request->log.dst = talloc_zero(request, log_dst_t);
+                       request->log.dst->func = vlog_request;
+                       request->log.dst->uctx = &default_log;
+
+                       request->log.lvl = fr_debug_lvl;
+               }
+               request->component = NULL;
+               request->module = NULL;
+
+               DEBUG2("server %s {", cf_section_name2(unlang_call_current(request)));
+               if (unlang_interpret_push_section(request, session->unlang, RLM_MODULE_NOTFOUND, UNLANG_TOP_FRAME) < 0) {
+                       talloc_free(request);
+                       return 0;
+               }
+               unlang_interpret_synchronous(unlang_interpret_event_list(request), request);
+               DEBUG("}");
+
+               /*
+                *      FIXME: grab attributes from the reply
+                *      and cache them for use in the next request.
+                */
+               talloc_free(request);
+       }
+
+       return 1;
+}
+
+
+/*
+ *     Requirements of 6.8.3
+ *
+ *     Changes to:
+ *
+ *             session->desired_min_tx_interval
+ *             session->required_min_rx_interval
+ *
+ *     mean we start polling.
+ */
+
+/*
+ *     Check if an incoming request is "ok"
+ *
+ *     It takes packets, not requests.  It sees if the packet looks
+ *     OK.  If so, it does a number of sanity checks on it.
+ */
+static int bfd_socket_recv(rad_listen_t *listener)
+{
+       ssize_t         rcode;
+       bfd_socket_t    *sock = listener->data;
+       proto_bfd_peer_t        *session;
+       proto_bfd_peer_t        my_session;
+       struct sockaddr_storage src;
+       socklen_t       sizeof_src = sizeof(src);
+       bfd_packet_t    bfd;
+
+       rcode = recvfrom(listener->fd, &bfd, sizeof(bfd), 0,
+                        (struct sockaddr *)&src, &sizeof_src);
+       if (rcode < 0) {
+               ERROR("Failed receiving packet: %s", fr_syserror(errno));
+               return 0;
+       }
+
+       if (rcode < 24) {
+               DEBUG("BFD packet is too short (%d < 24)", (int) rcode);
+               return 0;
+       }
+
+       if (bfd.version != 1) {
+               DEBUG("BFD packet has wrong version (%d != 1)", bfd.version);
+               return 0;
+       }
+
+       if (bfd.length < 24) {
+               DEBUG("BFD packet has wrong length (%d < 24)", bfd.length);
+               return 0;
+       }
+
+       if (bfd.length > sizeof(bfd)) {
+               DEBUG("BFD packet has wrong length (%d > %zd)", bfd.length, sizeof(bfd));
+               return 0;
+       }
+
+       if (bfd.auth_present) {
+               if (bfd.length < 26) {
+                       DEBUG("BFD packet has wrong length (%d < 26)",
+                             bfd.length);
+                       return 0;
+               }
+
+               if (bfd.length < 24 + bfd.auth.basic.auth_len) {
+                       DEBUG("BFD packet is too short (%d < %d)",
+                             bfd.length, 24 + bfd.auth.basic.auth_len);
+                       return 0;
+
+               }
+
+               if (bfd.length != 24 + bfd.auth.basic.auth_len) {
+                       DEBUG("WARNING: What is the extra data?");
+               }
+
+       }
+
+       if (bfd.detect_multi == 0) {
+               DEBUG("BFD packet has detect_multi == 0");
+               return 0;
+       }
+
+       if (bfd.multipoint != 0) {
+               DEBUG("BFD packet has multi != 0");
+               return 0;
+       }
+
+       if (bfd.my_disc == 0) {
+               DEBUG("BFD packet has my_disc == 0");
+               return 0;
+       }
+
+       if ((bfd.your_disc == 0) &&
+           !((bfd.state == BFD_STATE_DOWN) ||
+             (bfd.state == BFD_STATE_ADMIN_DOWN))) {
+               DEBUG("BFD packet has invalid your-disc / state");
+               return 0;
+       }
+
+       /*
+        *      We SHOULD use "your_disc", but what the heck.
+        */
+       fr_ipaddr_from_sockaddr(&my_session.socket.inet.dst_ipaddr,
+                               &my_session.socket.inet.dst_port, &src, sizeof_src);
+
+       session = fr_rb_find(sock->session_tree, &my_session);
+       if (!session) {
+               DEBUG("BFD unknown peer");
+               return 0;
+       }
+
+       if (!event_list) {
+               uint8_t *p = (uint8_t *) &bfd;
+               size_t total = bfd.length;
+
+               /*
+                *      A child has had a problem.  Do some cleanups.
+                */
+               if (session->blocked) bfd_pthread_free(session);
+
+               /*
+                *      No event list, try to create a new one.
+                */
+               if (!session->el && !bfd_pthread_create(session)) {
+                       DEBUG("BFD %d - error trying to create child thread",
+                             session->number);
+                       return 0;
+               }
+
+               do {
+                       rcode = write(session->pipefd[1], p, total);
+                       if ((rcode < 0) && (errno == EINTR)) continue;
+
+                       if (rcode < 0) {
+                               session->blocked = true;
+                               return 0;
+                       }
+
+                       total -= rcode;
+                       p += rcode;
+               } while (total > 0);
+               return 0;
+       }
+
+       return bfd_process(session, &bfd);
+}
+
+static int bfd_parse_ip_port(CONF_SECTION *cs, fr_ipaddr_t *ipaddr, uint16_t *port)
+{
+       int rcode;
+
+       /*
+        *      Try IPv4 first
+        */
+       memset(ipaddr, 0, sizeof(*ipaddr));
+       ipaddr->addr.v4.s_addr = htonl(INADDR_NONE);
+       rcode = cf_pair_parse(NULL, cs, "ipaddr", FR_ITEM_POINTER(FR_TYPE_IPV4_ADDR, ipaddr), NULL, T_INVALID);
+       if (rcode < 0) return -1;
+
+       if (rcode == 0) { /* successfully parsed IPv4 */
+               ipaddr->af = AF_INET;
+
+       } else {        /* maybe IPv6? */
+               rcode = cf_pair_parse(NULL, cs, "ipv6addr", FR_ITEM_POINTER(FR_TYPE_IPV6_ADDR, ipaddr), NULL, T_INVALID);
+               if (rcode < 0) return -1;
+
+               if (rcode == 1) {
+                       cf_log_err(cf_section_to_item(cs),
+                                  "No address specified in section");
+                       return -1;
+               }
+               ipaddr->af = AF_INET6;
+       }
+
+       rcode = cf_pair_parse(NULL, cs, "port", FR_ITEM_POINTER(FR_TYPE_UINT16, port), "0", T_INVALID);
+       if (rcode < 0) return -1;
+
+       return 0;
+}
+
+/*
+ *     @fixme: move some of this to parse
+ */
+static int bfd_init_sessions(CONF_SECTION *cs, bfd_socket_t *sock, int sockfd)
+{
+       CONF_ITEM *ci;
+       CONF_SECTION *peer;
+       uint16_t port;
+       fr_ipaddr_t ipaddr;
+
+       for (ci=cf_item_next(cs, NULL);
+            ci != NULL;
+            ci=cf_item_next(cs, ci)) {
+               proto_bfd_peer_t *session, my_session;
+
+              if (!cf_item_is_section(ci)) continue;
+
+              peer = cf_item_to_section(ci);
+
+              if (strcmp(cf_section_name1(peer), "peer") != 0) continue;
+
+              if (bfd_parse_ip_port(peer, &ipaddr, &port) < 0) {
+                      return -1;
+              }
+
+              my_session.socket.inet.dst_ipaddr = ipaddr;
+              my_session.socket.inet.dst_port = port;
+              if (fr_rb_find(sock->session_tree, &my_session) != NULL) {
+                      cf_log_err(ci, "Peers must have unique IP addresses");
+                      return -1;
+              }
+
+              session = bfd_new_session(sock, sockfd, peer, &ipaddr, port);
+              if (!session) return -1;
+       }
+
+       return 0;
+}
+
+
+/*
+ *     None of these functions are used.
+ */
+static int bfd_socket_send(UNUSED rad_listen_t *listener, UNUSED request_t *request)
+{
+       fr_assert(0 == 1);
+       return 0;
+}
+
+
+static int bfd_socket_encode(UNUSED rad_listen_t *listener, UNUSED request_t *request)
+{
+       fr_assert(0 == 1);
+       return 0;
+}
+
+
+static int bfd_socket_decode(UNUSED rad_listen_t *listener, UNUSED request_t *request)
+{
+       fr_assert(0 == 1);
+       return 0;
+}
+
+static fr_table_num_sorted_t const auth_types[] = {
+       { L("keyed-md5"),               BFD_AUTH_KEYED_MD5      },
+       { L("keyed-sha1"),              BFD_AUTH_KEYED_SHA1     },
+       { L("met-keyed-md5"),   BFD_AUTH_MET_KEYED_MD5  },
+       { L("met-keyed-sha1"),  BFD_AUTH_MET_KEYED_SHA1 },
+       { L("none"),            BFD_AUTH_RESERVED       },
+       { L("simple"),          BFD_AUTH_SIMPLE         }
+};
+static size_t auth_types_len = NUM_ELEMENTS(auth_types);
+
+static int bfd_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
+{
+       bfd_socket_t *sock = this->data;
+       char const *auth_type_str = NULL;
+       uint16_t listen_port;
+       fr_ipaddr_t ipaddr;
+
+       fr_assert(sock != NULL);
+
+       if (bfd_parse_ip_port(cs, &ipaddr, &listen_port) < 0) {
+               return -1;
+       }
+
+       sock->my_ipaddr = ipaddr;
+       sock->my_port = listen_port;
+
+       if (cf_pair_parse(sock, cs, "interface", FR_ITEM_POINTER(FR_TYPE_STRING, &sock->interface), NULL, T_INVALID) < 0) return -1;
+
+       if (cf_pair_parse(sock, cs, "min_receive_interval", FR_ITEM_POINTER(FR_TYPE_UINT32,
+                         &sock->min_rx_interval), "1000", T_BARE_WORD) < 0) return -1;
+       if (cf_pair_parse(sock, cs, "max_timeouts", FR_ITEM_POINTER(FR_TYPE_UINT32,
+                         &sock->max_timeouts), "3", T_BARE_WORD) < 0) return -1;
+       if (cf_pair_parse(sock, cs, "demand", FR_ITEM_POINTER(FR_TYPE_BOOL, &sock->demand),
+                         "no", T_DOUBLE_QUOTED_STRING) < 0) return -1;
+       if (cf_pair_parse(NULL, cs, "auth_type", FR_ITEM_POINTER(FR_TYPE_STRING, &auth_type_str),
+                         NULL, T_INVALID) < 0) return -1;
+
+       if (!this->server) {
+               char const *server;
+               if (cf_pair_parse(sock, cs, "server", FR_ITEM_POINTER(FR_TYPE_STRING, &server),
+                                 NULL, T_INVALID) < 0) return -1;
+               sock->server_cs = virtual_server_find(server);
+       } else {
+               sock->server_cs = this->server_cs;
+       }
+
+       if (sock->min_tx_interval < 100) sock->min_tx_interval = 100;
+       if (sock->min_tx_interval > 10000) sock->min_tx_interval = 10000;
+
+       if (sock->min_rx_interval < 100) sock->min_rx_interval = 100;
+       if (sock->min_rx_interval > 10000) sock->min_rx_interval = 10000;
+
+       if (sock->max_timeouts == 0) sock->max_timeouts = 1;
+       if (sock->max_timeouts > 10) sock->max_timeouts = 10;
+
+       sock->auth_type = fr_table_value_by_str(auth_types, auth_type_str, BFD_AUTH_INVALID);
+       if (sock->auth_type == BFD_AUTH_INVALID) {
+               ERROR("Unknown auth_type '%s'", auth_type_str);
+               return -1;
+       }
+
+       if (sock->auth_type == BFD_AUTH_SIMPLE) {
+               ERROR("'simple' authentication is insecure and is not supported");
+               return -1;
+       }
+
+       if (sock->auth_type != BFD_AUTH_RESERVED) {
+               sock->secret_len = bfd_parse_secret(cs, sock->secret);
+
+               if (sock->secret_len == 0) {
+                       ERROR("Cannot have empty secret");
+                       return -1;
+               }
+
+               if (((sock->auth_type == BFD_AUTH_KEYED_MD5) ||
+                    (sock->auth_type == BFD_AUTH_MET_KEYED_MD5)) &&
+                   (sock->secret_len > 16)) {
+                       ERROR("Secret must be no more than 16 bytes when using MD5");
+                       return -1;
+               }
+       }
+
+       sock->session_tree = fr_rb_inline_talloc_alloc(sock, proto_bfd_peer_t, node, bfd_session_cmp, bfd_session_free);
+       if (!sock->session_tree) {
+               ERROR("Failed creating session tree!");
+               return -1;
+       }
+
+       /*
+        *      Find the sibling "bfd" section of the "listen" section.
+        */
+       sock->unlang = cf_section_find(cf_item_to_section(cf_parent(cs)), "bfd", NULL);
+
+       return 0;
+}
+
+       /*
+        *      Bootstrap the initial set of connections.
+        */
+       if (bfd_init_sessions(cs, sock, this->fd) < 0) {
+               return -1;
+       }
+
+       return 0;
+}
+#endif
+
+//static int bfd_start_packets(proto_bfd_peer_t *session);
+static int bfd_start_control(proto_bfd_peer_t *session);
+static int bfd_stop_control(proto_bfd_peer_t *session);
+//static int bfd_process(proto_bfd_peer_t *session, bfd_packet_t *bfd);
+static void bfd_set_timeout(proto_bfd_peer_t *session, fr_time_t when);
+
+static int bfd_start_packets(proto_bfd_peer_t *session);
+
+static const char *bfd_state[] = {
+       "admin-down",
+       "down",
+       "init",
+       "up"
+};
+
+
+static void bfd_control_packet_init(proto_bfd_peer_t *session,
+                                  bfd_packet_t *bfd)
+{
+       memset(bfd, 0, sizeof(*bfd));
+
+       bfd->version = 1;
+       bfd->diag = session->local_diag;
+       bfd->state = session->session_state;
+       bfd->poll = 0;  /* fixed by poll response */
+       bfd->final = 0; /* fixed by poll response */
+       bfd->control_plane_independent = 0;
+
+       bfd->auth_present = (session->auth_type != BFD_AUTH_RESERVED);
+
+       /*
+        *      If we're UP / UP, signal that we've entered demand
+        *      mode, and stop sending packets.
+        */
+       if (session->demand_mode &&
+           (session->session_state == BFD_STATE_UP) &&
+           (session->remote_session_state == BFD_STATE_UP)) {
+               bfd->demand = true;
+
+               DEBUG("BFD %s demand mode UP / UP, sending ACK and done.",
+                     session->client.shortname);
+               bfd_stop_control(session);
+       } else {
+               bfd->demand = false;
+       }
+
+       bfd->multipoint = 0;
+       bfd->detect_multi = session->detect_multi;
+       bfd->length = 24;       /* auth types add to this later */
+
+       bfd->my_disc = session->local_disc;
+       bfd->your_disc = session->remote_disc;
+
+       bfd->desired_min_tx_interval = fr_time_delta_to_usec(session->desired_min_tx_interval);
+       bfd->required_min_rx_interval = fr_time_delta_to_usec(session->required_min_rx_interval);
+
+       bfd->min_echo_rx_interval = fr_time_delta_to_usec(session->my_min_echo_rx_interval);
+}
+
+
+/*
+ *     Send one BFD packet.
+ */
+static void bfd_send_packet(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *ctx)
+{
+       proto_bfd_peer_t *session = ctx;
+       bfd_packet_t bfd;
+
+       bfd_control_packet_init(session, &bfd);
+
+       bfd.poll = session->doing_poll;
+
+       if (!bfd.demand) {
+               bfd_start_packets(session);
+       }
+
+//     bfd_sign(session, &bfd);
+
+       DEBUG("BFD %s sending packet state %s",
+             session->client.shortname, bfd_state[session->session_state]);
+
+#if 0
+       if (sendto(session->socket.fd, &bfd, bfd.length, 0,
+                  (struct sockaddr *) &session->remote_sockaddr,
+                  session->salen) < 0) {
+               ERROR("Failed sending packet: %s", fr_syserror(errno));
+       }
+#endif
+}
+
+/*
+ *     Start sending packets.
+ */
+static int bfd_start_packets(proto_bfd_peer_t *session)
+{
+       uint64_t        interval, base;
+       uint64_t        jitter;
+
+       /*
+        *      Reset the timers.
+        */
+       fr_event_timer_delete(&session->ev_packet);
+
+       session->last_sent = fr_time();
+       
+       if (fr_time_delta_cmp(session->desired_min_tx_interval, session->remote_min_rx_interval) >= 0) {
+               interval = fr_time_delta_unwrap(session->desired_min_tx_interval);
+       } else {
+               interval = fr_time_delta_unwrap(session->remote_min_rx_interval);
+       }
+       base = (interval * 3) / 4;
+       jitter = fr_rand();     /* 32-bit number */
+
+       if (session->detect_multi == 1) {
+               jitter *= 644245094;    /* 15% of 2^32 */
+
+       } else {
+               jitter *= (1 << 30); /* 25% of 2^32 */
+       }
+
+       jitter >>= 32;
+       jitter *= interval;
+       jitter >>= 32;
+       interval = base;
+       interval += jitter;
+
+       if (fr_event_timer_in(session, session->el, &session->ev_packet,
+                             fr_time_delta_wrap(interval),
+                             bfd_send_packet, session) < 0) {
+               fr_assert("Failed to insert event" == NULL);
+       }
+
+       return 0;
+}
+
+
+/*
+ *     Start polling for the peer.
+ */
+static int bfd_start_poll(proto_bfd_peer_t *session)
+{
+       if (session->doing_poll) return 0;
+
+       /*
+        *      Already sending packets.  Reset the timers and set the
+        *      poll bit.
+        */
+       if (!session->remote_demand_mode) {
+               bfd_stop_control(session);
+       }
+
+       session->doing_poll = true;
+
+       /*
+        *      Send POLL packets, even if we're not sending CONTROL
+        *      packets.
+        */
+       return bfd_start_packets(session);
+}
+
+/*
+ *     Stop polling for packets.
+ */
+static int bfd_stop_poll(proto_bfd_peer_t *session)
+{
+       if (!session->doing_poll) return 0;
+
+       /*
+        *      We tried to increase the min_tx during a polling
+        *      sequence.  That isn't kosher, so we instead waited
+        *      until now.
+        */
+       if (fr_time_delta_unwrap(session->next_min_tx_interval) > 0) {
+               session->desired_min_tx_interval = session->next_min_tx_interval;
+               session->next_min_tx_interval = fr_time_delta_wrap(0);
+       }
+
+       /*
+        *      Already sending packets.  Clear the poll bit and
+        *      re-set the timers.
+        */
+       if (!session->remote_demand_mode) {
+               fr_assert(session->ev_timeout != NULL);
+               fr_assert(session->ev_packet != NULL);
+               session->doing_poll = false;
+
+               bfd_stop_control(session);
+               bfd_start_control(session);
+               return 1;
+       }
+
+       session->doing_poll = false;
+
+       return bfd_stop_control(session);
+}
+
+/*
+ *     Timer functions
+ */
+static void bfd_set_desired_min_tx_interval(proto_bfd_peer_t *session, fr_time_delta_t value)
+{
+       /*
+        *      Increasing the value: don't change it if we're already
+        *      polling.
+        */
+       if (session->doing_poll &&
+           (session->session_state == BFD_STATE_UP) &&
+           (fr_time_delta_cmp(value, session->desired_min_tx_interval) > 0)) {
+               session->next_min_tx_interval = value;
+               return;
+       }
+
+       /*
+        *      Don't poll more than once per second.
+        */
+       if (session->session_state != BFD_STATE_UP) {
+               if (fr_time_delta_cmp(value, fr_time_delta_from_sec(1)) < 0) value = fr_time_delta_from_sec(1);
+       }
+
+       session->desired_min_tx_interval = value;
+       bfd_stop_control(session);
+       session->doing_poll = 0;
+
+       bfd_start_poll(session);
+}
+
+
+/*
+ *     We failed to see a packet.
+ */
+static void bfd_detection_timeout(UNUSED fr_event_list_t *el, fr_time_t now, void *ctx)
+{
+       proto_bfd_peer_t *session = ctx;
+
+       DEBUG("BFD %s Timeout state %s ****** ", session->client.shortname,
+             bfd_state[session->session_state]);
+
+       if (!session->demand_mode) {
+               switch (session->session_state) {
+               case BFD_STATE_INIT:
+               case BFD_STATE_UP:
+                       goto start_poll;
+
+               default:
+                       break;
+               }
+
+       } else if (!session->doing_poll) {
+       start_poll:
+               DEBUG("BFD %s State <timeout> -> DOWN (control expired)", session->client.shortname);
+               session->session_state = BFD_STATE_DOWN;
+               session->local_diag =  BFD_CTRL_EXPIRED;
+//             bfd_trigger(session);
+
+               bfd_set_desired_min_tx_interval(session, fr_time_delta_from_sec(1));
+       }
+
+       session->remote_disc = 0;
+
+       if (session->detection_timeouts >= 2) {
+               session->auth_seq_known = 0;
+       }
+
+       session->detection_timeouts++;
+
+       bfd_set_timeout(session, now);
+}
+
+/*
+ *     Set the timeout for when we've lost enough packets to be
+ *     worried.
+ */
+static void bfd_set_timeout(proto_bfd_peer_t *session, fr_time_t when)
+{
+       fr_time_t timeout;
+       uint64_t delay;
+
+       fr_event_timer_delete(&session->ev_timeout);
+
+       timeout = fr_time_add(when, session->detection_time);
+
+       /*
+        *      When we SHOULD have received the next packet.
+        */
+       if (session->detect_multi >= 2) {
+               delay = fr_time_delta_unwrap(session->detection_time) / session->detect_multi;
+       } else {
+               delay = fr_time_delta_unwrap(session->detection_time);
+       }
+       delay += delay / 2;
+
+       session->next_recv = fr_time_add(when, fr_time_delta_from_usec(delay));
+       
+       if (fr_event_timer_at(session, session->el, &session->ev_timeout,
+                             timeout, bfd_detection_timeout, session) < 0) {
+               fr_assert("Failed to insert event" == NULL);
+       }
+}
+
+
+/*
+ *     Start / stop control packets.
+ */
+static int bfd_stop_control(proto_bfd_peer_t *session)
+{
+       fr_event_timer_delete(&session->ev_timeout);
+       fr_event_timer_delete(&session->ev_packet);
+       return 1;
+}
+
+static int bfd_start_control(proto_bfd_peer_t *session)
+{
+       /*
+        *      @todo - change our discriminator?
+        */
+
+       /*
+        *      We don't expect to see remote packets, so don't do anything.
+        */
+       if (fr_time_delta_unwrap(session->remote_min_rx_interval) == 0) return 0;
+
+       if ((session->remote_disc == 0) && session->passive) return 0;
+
+       /*
+        *      We were asked to go "up" when we were alread "up" 
+        */
+       if (session->remote_demand_mode &&
+           (session->session_state == BFD_STATE_UP) &&
+           (session->remote_session_state == BFD_STATE_UP) &&
+           !session->doing_poll) {
+               DEBUG("BFD %s warning: asked to start UP / UP ?",
+                     session->client.shortname);
+               fr_assert(0 == 1);
+               bfd_stop_control(session);
+               return 0;
+       }
+
+       bfd_set_timeout(session, session->last_recv);
+
+       if (session->ev_packet) return 0;
+
+       /*
+        *      Start sending packets.
+        */
+       return bfd_start_packets(session);
+}
+
+
+int bfd_session_init(proto_bfd_peer_t *session)
+{
+       session->session_state = BFD_STATE_DOWN;
+       session->local_disc = fr_rand();
+       session->remote_disc = 0;
+       session->local_diag = BFD_DIAG_NONE;
+       session->remote_min_rx_interval = fr_time_delta_wrap(0);
+       session->remote_demand_mode = false;
+       session->recv_auth_seq = 0;
+       session->xmit_auth_seq = fr_rand();
+       session->auth_seq_known = 0;
+
+       /*
+        *      Initialize the detection time.
+        */
+       if (!session->demand_mode) {
+               session->detection_time = session->required_min_rx_interval;
+       } else {
+               session->detection_time = session->desired_min_tx_interval;
+       }
+       session->detection_time = fr_time_delta_wrap(session->detect_multi * fr_time_delta_unwrap(session->detection_time));
+
+       bfd_stop_poll(session); /* compilation hack for now */
+
+       return 0;
+}
diff --git a/src/listen/bfd/session.h b/src/listen/bfd/session.h
new file mode 100644 (file)
index 0000000..e783254
--- /dev/null
@@ -0,0 +1,28 @@
+#pragma once
+/*
+ *  This program 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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program 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 this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/*
+ * $Id$
+ *
+ * @file src/listen/bfd/session.h
+ * @brief BFD Session handling
+ *
+ * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
+ */
+#include "proto_bfd.h"
+
+int    bfd_session_init(proto_bfd_peer_t *session);