BFD: modify session parameters

Change-Id: I666e5c0cc71a3693640960c93cdd1907f84fbe23
Signed-off-by: Klement Sekera <ksekera@cisco.com>
diff --git a/src/vnet/bfd/bfd.api b/src/vnet/bfd/bfd.api
index 17ca35b..f307ed2 100644
--- a/src/vnet/bfd/bfd.api
+++ b/src/vnet/bfd/bfd.api
@@ -107,6 +107,40 @@
   i32 retval;
 };
 
+/** \brief Modify UDP BFD session on interface
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param sw_if_index - sw index of the interface
+    @param desired_min_tx - desired min transmit interval (microseconds)
+    @param required_min_rx - required min receive interval (microseconds)
+    @param local_addr - local address
+    @param peer_addr - peer address
+    @param is_ipv6 - local_addr, peer_addr are IPv6 if non-zero, otherwise IPv4
+    @param detect_mult - detect multiplier (# of packets missed before connection goes down)
+*/
+define bfd_udp_mod
+{
+  u32 client_index;
+  u32 context;
+  u32 sw_if_index;
+  u32 desired_min_tx;
+  u32 required_min_rx;
+  u8 local_addr[16];
+  u8 peer_addr[16];
+  u8 is_ipv6;
+  u8 detect_mult;
+};
+
+/** \brief Modify UDP BFD session response
+    @param context - sender context, to match reply w/ request
+    @param retval - return code for the request
+*/
+define bfd_udp_mod_reply
+{
+  u32 context;
+  i32 retval;
+};
+
 /** \brief Delete UDP BFD session on interface
     @param client_index - opaque cookie to identify the sender
     @param context - sender context, to match reply w/ request
@@ -155,6 +189,9 @@
     @param is_authenticated - non-zero if authentication in-use, zero otherwise
     @param bfd_key_id - ID of key currently in-use if auth is on
     @param conf_key_id - configured key ID for this session
+    @param required_min_rx - required min receive interval (microseconds)
+    @param desired_min_tx - desired min transmit interval (microseconds)
+    @param detect_mult - detect multiplier (# of packets missed before connection goes down)
 */
 define bfd_udp_session_details
 {
@@ -167,6 +204,9 @@
   u8 is_authenticated;
   u8 bfd_key_id;
   u32 conf_key_id;
+  u32 required_min_rx;
+  u32 desired_min_tx;
+  u8 detect_mult;
 };
 
 /** \brief Set flags of BFD UDP session
diff --git a/src/vnet/bfd/bfd_api.c b/src/vnet/bfd/bfd_api.c
index cfc3a38..af70f0e 100644
--- a/src/vnet/bfd/bfd_api.c
+++ b/src/vnet/bfd/bfd_api.c
@@ -45,6 +45,7 @@
 
 #define foreach_vpe_api_msg                                \
   _ (BFD_UDP_ADD, bfd_udp_add)                             \
+  _ (BFD_UDP_MOD, bfd_udp_mod)                             \
   _ (BFD_UDP_DEL, bfd_udp_del)                             \
   _ (BFD_UDP_SESSION_DUMP, bfd_udp_session_dump)           \
   _ (BFD_UDP_SESSION_SET_FLAGS, bfd_udp_session_set_flags) \
@@ -98,6 +99,25 @@
 }
 
 static void
+vl_api_bfd_udp_mod_t_handler (vl_api_bfd_udp_mod_t * mp)
+{
+  vl_api_bfd_udp_mod_reply_t *rmp;
+  int rv;
+
+  VALIDATE_SW_IF_INDEX (mp);
+
+  BFD_UDP_API_PARAM_COMMON_CODE;
+
+  rv = bfd_udp_mod_session (BFD_UDP_API_PARAM_FROM_MP (mp),
+			    clib_net_to_host_u32 (mp->desired_min_tx),
+			    clib_net_to_host_u32 (mp->required_min_rx),
+			    mp->detect_mult);
+
+  BAD_SW_IF_INDEX_LABEL;
+  REPLY_MACRO (VL_API_BFD_UDP_MOD_REPLY);
+}
+
+static void
 vl_api_bfd_udp_del_t_handler (vl_api_bfd_udp_del_t * mp)
 {
   vl_api_bfd_udp_del_reply_t *rmp;
@@ -146,6 +166,10 @@
 		   sizeof (key->peer_addr.ip4.data));
     }
 
+  mp->required_min_rx =
+    clib_host_to_net_u32 (bs->config_required_min_rx_usec);
+  mp->desired_min_tx = clib_host_to_net_u32 (bs->config_desired_min_tx_usec);
+  mp->detect_mult = bs->local_detect_mult;
   vl_msg_api_send_shmem (q, (u8 *) & mp);
 }
 
diff --git a/src/vnet/bfd/bfd_api.h b/src/vnet/bfd/bfd_api.h
index 128a3dc..f4486a7 100644
--- a/src/vnet/bfd/bfd_api.h
+++ b/src/vnet/bfd/bfd_api.h
@@ -26,9 +26,17 @@
 
 vnet_api_error_t
 bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr,
-		     const ip46_address_t * peer_addr, u32 desired_min_tx_us,
-		     u32 required_min_rx_us, u8 detect_mult,
-		     u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id);
+		     const ip46_address_t * peer_addr,
+		     u32 desired_min_tx_usec, u32 required_min_rx_usec,
+		     u8 detect_mult, u8 is_authenticated, u32 conf_key_id,
+		     u8 bfd_key_id);
+
+vnet_api_error_t bfd_udp_mod_session (u32 sw_if_index,
+				      const ip46_address_t * local_addr,
+				      const ip46_address_t * peer_addr,
+				      u32 desired_min_tx_usec,
+				      u32 required_min_rx_usec,
+				      u8 detect_mult);
 
 vnet_api_error_t bfd_udp_del_session (u32 sw_if_index,
 				      const ip46_address_t * local_addr,
diff --git a/src/vnet/bfd/bfd_debug.h b/src/vnet/bfd/bfd_debug.h
index 707ebab..a06e934 100644
--- a/src/vnet/bfd/bfd_debug.h
+++ b/src/vnet/bfd/bfd_debug.h
@@ -63,6 +63,13 @@
     }                                                                    \
   while (0);
 
+#define BFD_CLK_FMT "%luus/%lu clocks/%.2fs"
+#define BFD_CLK_PRN(clocks)                                                \
+  (u64) ((((f64)clocks) / vlib_get_main ()->clib_time.clocks_per_second) * \
+         USEC_PER_SECOND),                                                 \
+      (clocks),                                                            \
+      (((f64)clocks) / vlib_get_main ()->clib_time.clocks_per_second)
+
 #else
 #define BFD_DBG(...)
 #define BFD_ERR(...)
diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c
index 8f2fae2..798d063 100644
--- a/src/vnet/bfd/bfd_main.c
+++ b/src/vnet/bfd/bfd_main.c
@@ -30,11 +30,16 @@
 #endif
 
 static u64
-bfd_us_to_clocks (bfd_main_t * bm, u64 us)
+bfd_usec_to_clocks (const bfd_main_t * bm, u64 us)
 {
   return bm->cpu_cps * ((f64) us / USEC_PER_SECOND);
 }
 
+// static u64 bfd_clocks_to_usec (const bfd_main_t *bm, u64 clocks)
+//{
+//  return (clocks / bm->cpu_cps) * USEC_PER_SECOND;
+//}
+
 static vlib_node_registration_t bfd_process_node;
 
 /* set to 0 here, real values filled at startup */
@@ -83,9 +88,11 @@
   bs->remote_state = BFD_STATE_down;
   bs->local_demand = 0;
   bs->remote_discr = 0;
-  bs->desired_min_tx_us = BFD_DEFAULT_DESIRED_MIN_TX_US;
-  bs->desired_min_tx_clocks = bfd_us_to_clocks (bm, bs->desired_min_tx_us);
-  bs->remote_min_rx_us = 1;
+  bs->config_desired_min_tx_usec = BFD_DEFAULT_DESIRED_MIN_TX_US;
+  bs->config_desired_min_tx_clocks = bm->default_desired_min_tx_clocks;
+  bs->effective_desired_min_tx_clocks = bm->default_desired_min_tx_clocks;
+  bs->remote_min_rx_usec = 1;
+  bs->remote_min_rx_clocks = bfd_usec_to_clocks (bm, bs->remote_min_rx_usec);
   bs->remote_demand = 0;
   bs->auth.remote_seq_number = 0;
   bs->auth.remote_seq_number_known = 0;
@@ -123,7 +130,8 @@
   if (!bs->local_demand)
     {
       bs->transmit_interval_clocks =
-	clib_max (bs->desired_min_tx_clocks, bs->remote_min_rx_clocks);
+	clib_max (bs->effective_desired_min_tx_clocks,
+		  bs->remote_min_rx_clocks);
     }
   else
     {
@@ -193,18 +201,18 @@
 {
   if (!bs->local_demand)
     {
+      /* asynchronous mode */
       bs->detection_time_clocks =
 	bs->remote_detect_mult *
-	bfd_us_to_clocks (bm, clib_max (bs->required_min_rx_us,
-					bs->remote_desired_min_tx_us));
+	clib_max (bs->effective_required_min_rx_clocks,
+		  bs->remote_desired_min_tx_clocks);
     }
   else
     {
+      /* demand mode */
       bs->detection_time_clocks =
-	bs->local_detect_mult *
-	bfd_us_to_clocks (bm,
-			  clib_max (bs->desired_min_tx_us,
-				    bs->remote_min_rx_us));
+	bs->local_detect_mult * clib_max (bs->config_desired_min_tx_clocks,
+					  bs->remote_min_rx_clocks);
     }
   BFD_DBG ("Recalculated detection time %lu clocks/%.2fs",
 	   bs->detection_time_clocks,
@@ -259,14 +267,14 @@
 }
 
 static void
-bfd_set_desired_min_tx (bfd_main_t * bm, bfd_session_t * bs, u64 now,
-			u32 desired_min_tx_us, int handling_wakeup)
+bfd_set_effective_desired_min_tx (bfd_main_t * bm,
+				  bfd_session_t * bs, u64 now,
+				  u64 desired_min_tx_clocks,
+				  int handling_wakeup)
 {
-  bs->desired_min_tx_us = desired_min_tx_us;
-  bs->desired_min_tx_clocks = bfd_us_to_clocks (bm, bs->desired_min_tx_us);
-  BFD_DBG ("Set desired min tx to %uus/%lu clocks/%.2fs",
-	   bs->desired_min_tx_us, bs->desired_min_tx_clocks,
-	   bs->desired_min_tx_clocks / bm->cpu_cps);
+  bs->effective_desired_min_tx_clocks = desired_min_tx_clocks;
+  BFD_DBG ("Set effective desired min tx to " BFD_CLK_FMT,
+	   BFD_CLK_PRN (bs->effective_desired_min_tx_clocks));
   bfd_recalc_detection_time (bm, bs);
   bfd_recalc_tx_interval (bm, bs);
   bfd_calc_next_tx (bm, bs, now);
@@ -274,15 +282,29 @@
 }
 
 static void
+bfd_set_effective_required_min_rx (bfd_main_t * bm,
+				   bfd_session_t * bs, u64 now,
+				   u64 required_min_rx_clocks,
+				   int handling_wakeup)
+{
+  bs->effective_required_min_rx_clocks = required_min_rx_clocks;
+  BFD_DBG ("Set effective required min rx to " BFD_CLK_FMT,
+	   BFD_CLK_PRN (bs->effective_required_min_rx_clocks));
+  bfd_recalc_detection_time (bm, bs);
+  bfd_set_timer (bm, bs, now, handling_wakeup);
+}
+
+static void
 bfd_set_remote_required_min_rx (bfd_main_t * bm, bfd_session_t * bs,
 				u64 now,
-				u32 remote_required_min_rx_us,
+				u32 remote_required_min_rx_usec,
 				int handling_wakeup)
 {
-  bs->remote_min_rx_us = remote_required_min_rx_us;
-  bs->remote_min_rx_clocks = bfd_us_to_clocks (bm, bs->remote_min_rx_us);
-  BFD_DBG ("Set remote min rx to %uus/%lu clocks/%.2fs", bs->remote_min_rx_us,
-	   bs->remote_min_rx_clocks, bs->remote_min_rx_clocks / bm->cpu_cps);
+  bs->remote_min_rx_usec = remote_required_min_rx_usec;
+  bs->remote_min_rx_clocks =
+    bfd_usec_to_clocks (bm, remote_required_min_rx_usec);
+  BFD_DBG ("Set remote min rx to " BFD_CLK_FMT,
+	   BFD_CLK_PRN (bs->remote_min_rx_clocks));
   bfd_recalc_detection_time (bm, bs);
   bfd_recalc_tx_interval (bm, bs);
   bfd_calc_next_tx (bm, bs, now);
@@ -316,32 +338,6 @@
   return 0;
 }
 
-const char *
-bfd_diag_code_string (bfd_diag_code_e diag)
-{
-#define F(n, t, s)             \
-  case BFD_DIAG_CODE_NAME (t): \
-    return s;
-  switch (diag)
-    {
-    foreach_bfd_diag_code (F)}
-  return "UNKNOWN";
-#undef F
-}
-
-const char *
-bfd_state_string (bfd_state_e state)
-{
-#define F(n, t, s)         \
-  case BFD_STATE_NAME (t): \
-    return s;
-  switch (state)
-    {
-    foreach_bfd_state (F)}
-  return "UNKNOWN";
-#undef F
-}
-
 void
 bfd_session_set_flags (bfd_session_t * bs, u8 admin_up_down)
 {
@@ -404,31 +400,63 @@
   switch (bs->local_state)
     {
     case BFD_STATE_admin_down:
-      bfd_set_desired_min_tx (bm, bs, now,
-			      clib_max (bs->config_desired_min_tx_us,
-					BFD_DEFAULT_DESIRED_MIN_TX_US),
-			      handling_wakeup);
+      bfd_set_effective_required_min_rx (bm, bs, now,
+					 bs->config_required_min_rx_clocks,
+					 handling_wakeup);
+      bfd_set_effective_desired_min_tx (bm, bs, now,
+					clib_max
+					(bs->config_desired_min_tx_clocks,
+					 bm->default_desired_min_tx_clocks),
+					handling_wakeup);
       break;
     case BFD_STATE_down:
-      bfd_set_desired_min_tx (bm, bs, now,
-			      clib_max (bs->config_desired_min_tx_us,
-					BFD_DEFAULT_DESIRED_MIN_TX_US),
-			      handling_wakeup);
+      bfd_set_effective_required_min_rx (bm, bs, now,
+					 bs->config_required_min_rx_clocks,
+					 handling_wakeup);
+      bfd_set_effective_desired_min_tx (bm, bs, now,
+					clib_max
+					(bs->config_desired_min_tx_clocks,
+					 bm->default_desired_min_tx_clocks),
+					handling_wakeup);
       break;
     case BFD_STATE_init:
-      bfd_set_desired_min_tx (bm, bs, now,
-			      clib_max (bs->config_desired_min_tx_us,
-					BFD_DEFAULT_DESIRED_MIN_TX_US),
-			      handling_wakeup);
+      bfd_set_effective_desired_min_tx (bm, bs, now,
+					clib_max
+					(bs->config_desired_min_tx_clocks,
+					 bm->default_desired_min_tx_clocks),
+					handling_wakeup);
       break;
     case BFD_STATE_up:
-      bfd_set_desired_min_tx (bm, bs, now, bs->config_desired_min_tx_us,
-			      handling_wakeup);
+      if (POLL_NOT_NEEDED == bs->poll_state)
+	{
+	  bfd_set_effective_required_min_rx (bm, bs, now,
+					     bs->config_required_min_rx_clocks,
+					     handling_wakeup);
+	}
+      bfd_set_effective_desired_min_tx (bm, bs, now,
+					bs->config_desired_min_tx_clocks,
+					handling_wakeup);
       break;
     }
 }
 
 static void
+bfd_on_config_change (vlib_main_t * vm, vlib_node_runtime_t * rt,
+		      bfd_main_t * bm, bfd_session_t * bs, u64 now)
+{
+  if (bs->remote_demand)
+    {
+      /* TODO - initiate poll sequence here */
+    }
+  else
+    {
+      /* asynchronous - poll is part of periodic - nothing to do here */
+    }
+  bfd_recalc_detection_time (bm, bs);
+  bfd_set_timer (bm, bs, now, 0);
+}
+
+static void
 bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b,
 			 bfd_session_t * bs)
 {
@@ -557,11 +585,10 @@
   pkt->head.length = clib_host_to_net_u32 (bfd_length);
   pkt->my_disc = bs->local_discr;
   pkt->your_disc = bs->remote_discr;
-  pkt->des_min_tx = clib_host_to_net_u32 (bs->desired_min_tx_us);
-  pkt->req_min_rx = clib_host_to_net_u32 (bs->required_min_rx_us);
-  pkt->req_min_echo_rx = clib_host_to_net_u32 (bs->required_min_echo_rx_us);
+  pkt->des_min_tx = clib_host_to_net_u32 (bs->config_desired_min_tx_usec);
+  pkt->req_min_rx = clib_host_to_net_u32 (bs->config_required_min_rx_usec);
+  pkt->req_min_echo_rx = clib_host_to_net_u32 (1);
   b->current_length = bfd_length;
-  bfd_add_auth_section (b, bs);
 }
 
 static void
@@ -569,7 +596,7 @@
 		   bfd_main_t * bm, bfd_session_t * bs, u64 now,
 		   int handling_wakeup)
 {
-  if (!bs->remote_min_rx_us)
+  if (!bs->remote_min_rx_usec)
     {
       BFD_DBG
 	("bfd.RemoteMinRxInterval is zero, not sending periodic control "
@@ -593,6 +620,14 @@
 	  return;
 	}
       bfd_init_control_frame (b, bs);
+      if (POLL_NOT_NEEDED != bs->poll_state)
+	{
+	  /* here we are either beginning a new poll sequence or retrying .. */
+	  bfd_pkt_set_poll (vlib_buffer_get_current (b));
+	  bs->poll_state = POLL_IN_PROGRESS;
+	  BFD_DBG ("Setting poll bit in packet, bs_idx=%u", bs->bs_idx);
+	}
+      bfd_add_auth_section (b, bs);
       bfd_add_transport_layer (vm, b, bs);
       bs->last_tx_clocks = now;
       bfd_calc_next_tx (bm, bs, now);
@@ -613,8 +648,14 @@
   BFD_DBG ("Send final control frame for bs_idx=%lu", bs->bs_idx);
   bfd_init_control_frame (b, bs);
   bfd_pkt_set_final (vlib_buffer_get_current (b));
+  bfd_add_auth_section (b, bs);
   bfd_add_transport_layer (vm, b, bs);
   bs->last_tx_clocks = clib_cpu_time_now ();
+  /*
+   * RFC allows to include changes in final frame, so if there were any
+   * pending, we already did that, thus we can clear any pending poll needs
+   */
+  bs->poll_state = POLL_NOT_NEEDED;
 }
 
 static void
@@ -629,6 +670,14 @@
       BFD_DBG ("Rx timeout, session goes down");
       bfd_set_diag (bs, BFD_DIAG_CODE_det_time_exp);
       bfd_set_state (bm, bs, BFD_STATE_down, handling_wakeup);
+      /*
+       * If the remote system does not receive any
+       * BFD Control packets for a Detection Time, it SHOULD reset
+       * bfd.RemoteMinRxInterval to its initial value of 1 (per section 6.8.1,
+       * since it is no longer required to maintain previous session state)
+       * and then can transmit at its own rate.
+       */
+      bfd_set_remote_required_min_rx (bm, bs, now, 1, handling_wakeup);
     }
 }
 
@@ -722,6 +771,19 @@
 		       (u32) * event_data);
 	    }
 	  break;
+	case BFD_EVENT_CONFIG_CHANGED:
+	  if (!pool_is_free_index (bm->sessions, *event_data))
+	    {
+	      bfd_session_t *bs =
+		pool_elt_at_index (bm->sessions, *event_data);
+	      bfd_on_config_change (vm, rt, bm, bs, now);
+	    }
+	  else
+	    {
+	      BFD_DBG ("Ignoring event for non-existent session index %u",
+		       (u32) * event_data);
+	    }
+	  break;
 	default:
 	  clib_warning ("BUG: event type 0x%wx", event_type);
 	  break;
@@ -810,6 +872,8 @@
   memset (&bm->wheel, 0, sizeof (bm->wheel));
   bm->cpu_cps = vm->clib_time.clocks_per_second;
   BFD_DBG ("cps is %.2f", bm->cpu_cps);
+  bm->default_desired_min_tx_clocks =
+    bfd_usec_to_clocks (bm, BFD_DEFAULT_DESIRED_MIN_TX_US);
   const u64 now = clib_cpu_time_now ();
   timing_wheel_init (&bm->wheel, now, bm->cpu_cps);
   bm->wheel_inaccuracy = 2 << bm->wheel.log2_clocks_per_bin;
@@ -1283,7 +1347,8 @@
 	  while (0);
 	}
     }
-  bs->remote_desired_min_tx_us = clib_net_to_host_u32 (pkt->des_min_tx);
+  bs->remote_desired_min_tx_clocks =
+    bfd_usec_to_clocks (bm, clib_net_to_host_u32 (pkt->des_min_tx));
   bs->remote_detect_mult = pkt->head.detect_mult;
   bfd_set_remote_required_min_rx (bm, bs, now,
 				  clib_net_to_host_u32 (pkt->req_min_rx), 0);
@@ -1297,6 +1362,18 @@
    */
   /* FIXME 6.8.2 */
   /* FIXME 6.8.4 */
+  if (bs->poll_state == POLL_IN_PROGRESS && bfd_pkt_get_final (pkt))
+    {
+      bs->poll_state = POLL_NOT_NEEDED;
+      BFD_DBG ("Poll sequence terminated, bs_idx=%u", bs->bs_idx);
+      if (BFD_STATE_up == bs->local_state)
+	{
+	  bfd_set_effective_required_min_rx (bm, bs, now,
+					     bs->config_required_min_rx_clocks,
+					     0);
+	  bfd_recalc_detection_time (bm, bs);
+	}
+    }
   if (BFD_STATE_admin_down == bs->local_state)
     return;
   if (BFD_STATE_admin_down == bs->remote_state)
@@ -1333,6 +1410,20 @@
     }
 }
 
+static const char *
+bfd_poll_state_string (bfd_poll_state_e state)
+{
+  switch (state)
+    {
+#define F(x)     \
+  case POLL_##x: \
+    return "POLL_" #x;
+      foreach_bfd_poll_state (F)
+#undef F
+    }
+  return "UNKNOWN";
+}
+
 u8 *
 format_bfd_session (u8 * s, va_list * args)
 {
@@ -1353,17 +1444,18 @@
 	      "remote-seq-num=%u, "
 	      "is-delayed=%s, "
 	      "curr-key=%U, "
-	      "next-key=%U}",
+	      "next-key=%U},"
+	      "poll-state: %s",
 	      bs->bs_idx, bfd_state_string (bs->local_state),
 	      bfd_state_string (bs->remote_state), bs->local_discr,
 	      bs->remote_discr, bfd_diag_code_string (bs->local_diag),
-	      bs->desired_min_tx_us, bs->required_min_rx_us,
-	      bs->required_min_echo_rx_us, bs->remote_min_rx_us,
-	      (bs->local_demand ? "yes" : "no"),
+	      bs->config_desired_min_tx_usec, bs->config_required_min_rx_usec,
+	      1, bs->remote_min_rx_usec, (bs->local_demand ? "yes" : "no"),
 	      (bs->remote_demand ? "yes" : "no"), bs->local_detect_mult,
 	      bs->auth.local_seq_number, bs->auth.remote_seq_number,
 	      (bs->auth.is_delayed ? "yes" : "no"), format_bfd_auth_key,
-	      bs->auth.curr_key, format_bfd_auth_key, bs->auth.next_key);
+	      bs->auth.curr_key, format_bfd_auth_key, bs->auth.next_key,
+	      bfd_poll_state_string (bs->poll_state));
   return s;
 }
 
@@ -1462,6 +1554,62 @@
 #endif
 }
 
+vnet_api_error_t
+bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs,
+			u32 desired_min_tx_usec,
+			u32 required_min_rx_usec, u8 detect_mult)
+{
+  if (bs->local_detect_mult != detect_mult ||
+      bs->config_desired_min_tx_usec != desired_min_tx_usec ||
+      bs->config_required_min_rx_usec != required_min_rx_usec)
+    {
+      BFD_DBG ("Changing session params: %U", format_bfd_session, bs);
+      switch (bs->poll_state)
+	{
+	case POLL_NOT_NEEDED:
+	  if (BFD_STATE_up == bs->local_state ||
+	      BFD_STATE_init == bs->local_state)
+	    {
+	      /* poll sequence is not needed for detect multiplier change */
+	      if (bs->config_desired_min_tx_usec != desired_min_tx_usec ||
+		  bs->config_required_min_rx_usec != required_min_rx_usec)
+		{
+		  bs->poll_state = POLL_NEEDED;
+		  BFD_DBG ("Set poll state=%s, bs_idx=%u",
+			   bfd_poll_state_string (bs->poll_state),
+			   bs->bs_idx);
+		}
+	    }
+	  break;
+	case POLL_NEEDED:
+	  /* nothing to do */
+	  break;
+	case POLL_IN_PROGRESS:
+	  /* can't change params now ... */
+	  BFD_ERR ("Poll in progress, cannot change params for session with "
+		   "bs_idx=%u", bs->bs_idx);
+	  return VNET_API_ERROR_BFD_EAGAIN;
+	}
+
+      bs->local_detect_mult = detect_mult;
+      bs->config_desired_min_tx_usec = desired_min_tx_usec;
+      bs->config_desired_min_tx_clocks =
+	bfd_usec_to_clocks (bm, desired_min_tx_usec);
+      bs->config_required_min_rx_usec = required_min_rx_usec;
+      bs->config_required_min_rx_clocks =
+	bfd_usec_to_clocks (bm, required_min_rx_usec);
+      BFD_DBG ("Changed session params: %U", format_bfd_session, bs);
+
+      vlib_process_signal_event (bm->vlib_main, bm->bfd_process_node_index,
+				 BFD_EVENT_CONFIG_CHANGED, bs->bs_idx);
+    }
+  else
+    {
+      BFD_DBG ("Ignore parameter change - no change, bs_idx=%u", bs->bs_idx);
+    }
+  return 0;
+}
+
 bfd_main_t bfd_main;
 
 /*
diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h
index b66b79e..361ff0b 100644
--- a/src/vnet/bfd/bfd_main.h
+++ b/src/vnet/bfd/bfd_main.h
@@ -64,6 +64,18 @@
   bfd_auth_type_e auth_type;
 } bfd_auth_key_t;
 
+#define foreach_bfd_poll_state(F)\
+  F(NOT_NEEDED)\
+F(NEEDED)\
+F(IN_PROGRESS)
+
+typedef enum
+{
+#define F(x) POLL_##x,
+  foreach_bfd_poll_state (F)
+#undef F
+} bfd_poll_state_e;
+
 typedef struct
 {
   /* index in bfd_main.sessions pool */
@@ -85,28 +97,34 @@
   u32 remote_discr;
 
   /* configured desired min tx interval (microseconds) */
-  u32 config_desired_min_tx_us;
+  u32 config_desired_min_tx_usec;
 
-  /* desired min tx interval (microseconds) */
-  u32 desired_min_tx_us;
+  /* configured desired min tx interval (clocks) */
+  u64 config_desired_min_tx_clocks;
 
-  /* desired min tx interval (clocks) */
-  u64 desired_min_tx_clocks;
+  /* effective desired min tx interval (clocks) */
+  u64 effective_desired_min_tx_clocks;
 
-  /* required min rx interval (microseconds) */
-  u32 required_min_rx_us;
+  /* configured required min rx interval (microseconds) */
+  u32 config_required_min_rx_usec;
 
-  /* required min echo rx interval (microseconds) */
-  u32 required_min_echo_rx_us;
+  /* configured required min rx interval (clocks) */
+  u64 config_required_min_rx_clocks;
+
+  /* effective required min rx interval (clocks) */
+  u64 effective_required_min_rx_clocks;
 
   /* remote min rx interval (microseconds) */
-  u32 remote_min_rx_us;
+  u64 remote_min_rx_usec;
 
   /* remote min rx interval (clocks) */
   u64 remote_min_rx_clocks;
 
-  /* remote desired min tx interval (microseconds) */
-  u32 remote_desired_min_tx_us;
+  /* remote desired min tx interval (clocks) */
+  u64 remote_desired_min_tx_clocks;
+
+  /* configured detect multiplier */
+  u8 local_detect_mult;
 
   /* 1 if in demand mode, 0 otherwise */
   u8 local_demand;
@@ -114,9 +132,6 @@
   /* 1 if remote system sets demand mode, 0 otherwise */
   u8 remote_demand;
 
-  /* local detect multiplier */
-  u8 local_detect_mult;
-
   /* remote detect multiplier */
   u8 remote_detect_mult;
 
@@ -138,6 +153,9 @@
   /* detection time */
   u64 detection_time_clocks;
 
+  /* state info regarding poll sequence */
+  bfd_poll_state_e poll_state;
+
   /* authentication information */
   struct
   {
@@ -175,6 +193,7 @@
   /* transport type for this session */
   bfd_transport_t transport;
 
+  /* union of transport-specific data */
   union
   {
     bfd_udp_session_t udp;
@@ -205,6 +224,9 @@
   /* cpu clocks per second */
   f64 cpu_cps;
 
+  /* default desired min tx in clocks */
+  u64 default_desired_min_tx_clocks;
+
   /* for generating random numbers */
   u32 random_seed;
 
@@ -243,6 +265,7 @@
 {
   BFD_EVENT_RESCHEDULE = 1,
   BFD_EVENT_NEW_SESSION,
+  BFD_EVENT_CONFIG_CHANGED,
 } bfd_process_event_e;
 
 u8 *bfd_input_format_trace (u8 * s, va_list * args);
@@ -265,6 +288,10 @@
 vnet_api_error_t bfd_auth_activate (bfd_session_t * bs, u32 conf_key_id,
 				    u8 bfd_key_id, u8 is_delayed);
 vnet_api_error_t bfd_auth_deactivate (bfd_session_t * bs, u8 is_delayed);
+vnet_api_error_t
+bfd_session_set_params (bfd_main_t * bm, bfd_session_t * bs,
+			u32 desired_min_tx_usec,
+			u32 required_min_rx_usec, u8 detect_mult);
 
 #define USEC_PER_MS 1000LL
 #define USEC_PER_SECOND (1000 * USEC_PER_MS)
diff --git a/src/vnet/bfd/bfd_protocol.c b/src/vnet/bfd/bfd_protocol.c
index 180fc6d..92b226b 100644
--- a/src/vnet/bfd/bfd_protocol.c
+++ b/src/vnet/bfd/bfd_protocol.c
@@ -150,6 +150,32 @@
   return "UNKNOWN";
 }
 
+const char *
+bfd_diag_code_string (bfd_diag_code_e diag)
+{
+#define F(n, t, s)             \
+  case BFD_DIAG_CODE_NAME (t): \
+    return s;
+  switch (diag)
+    {
+    foreach_bfd_diag_code (F)}
+  return "UNKNOWN";
+#undef F
+}
+
+const char *
+bfd_state_string (bfd_state_e state)
+{
+#define F(n, t, s)         \
+  case BFD_STATE_NAME (t): \
+    return s;
+  switch (state)
+    {
+    foreach_bfd_state (F)}
+  return "UNKNOWN";
+#undef F
+}
+
 /*
  * fd.io coding-style-patch-verification: ON
  *
diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c
index 443f425..e21b887 100644
--- a/src/vnet/bfd/bfd_udp.c
+++ b/src/vnet/bfd/bfd_udp.c
@@ -141,8 +141,8 @@
 
 static vnet_api_error_t
 bfd_udp_add_session_internal (bfd_udp_main_t * bum, u32 sw_if_index,
-			      u32 desired_min_tx_us, u32 required_min_rx_us,
-			      u8 detect_mult,
+			      u32 desired_min_tx_usec,
+			      u32 required_min_rx_usec, u8 detect_mult,
 			      const ip46_address_t * local_addr,
 			      const ip46_address_t * peer_addr,
 			      bfd_session_t ** bs_out)
@@ -189,12 +189,9 @@
 	       "returns %d", format_ip46_address, &key->peer_addr,
 	       IP46_TYPE_ANY, key->sw_if_index, bus->adj_index);
     }
-  bs->config_desired_min_tx_us = desired_min_tx_us;
-  bs->required_min_rx_us = required_min_rx_us;
-  bs->required_min_echo_rx_us = required_min_rx_us;	/* FIXME */
-  bs->local_detect_mult = detect_mult;
   *bs_out = bs;
-  return 0;
+  return bfd_session_set_params (bum->bfd_main, bs, desired_min_tx_usec,
+				 required_min_rx_usec, detect_mult);
 }
 
 static vnet_api_error_t
@@ -298,8 +295,8 @@
 }
 
 static vnet_api_error_t
-bfd_api_verify_common (u32 sw_if_index, u32 desired_min_tx_us,
-		       u32 required_min_rx_us, u8 detect_mult,
+bfd_api_verify_common (u32 sw_if_index, u32 desired_min_tx_usec,
+		       u32 required_min_rx_usec, u8 detect_mult,
 		       const ip46_address_t * local_addr,
 		       const ip46_address_t * peer_addr)
 {
@@ -314,9 +311,9 @@
       clib_warning ("detect_mult < 1");
       return VNET_API_ERROR_INVALID_ARGUMENT;
     }
-  if (desired_min_tx_us < 1)
+  if (desired_min_tx_usec < 1)
     {
-      clib_warning ("desired_min_tx_us < 1");
+      clib_warning ("desired_min_tx_usec < 1");
       return VNET_API_ERROR_INVALID_ARGUMENT;
     }
   return 0;
@@ -334,22 +331,23 @@
 
 vnet_api_error_t
 bfd_udp_add_session (u32 sw_if_index, const ip46_address_t * local_addr,
-		     const ip46_address_t * peer_addr, u32 desired_min_tx_us,
-		     u32 required_min_rx_us, u8 detect_mult,
-		     u8 is_authenticated, u32 conf_key_id, u8 bfd_key_id)
+		     const ip46_address_t * peer_addr,
+		     u32 desired_min_tx_usec, u32 required_min_rx_usec,
+		     u8 detect_mult, u8 is_authenticated, u32 conf_key_id,
+		     u8 bfd_key_id)
 {
-  vnet_api_error_t rv = bfd_api_verify_common (sw_if_index, desired_min_tx_us,
-					       required_min_rx_us,
-					       detect_mult,
-					       local_addr, peer_addr);
+  vnet_api_error_t rv =
+    bfd_api_verify_common (sw_if_index, desired_min_tx_usec,
+			   required_min_rx_usec, detect_mult,
+			   local_addr, peer_addr);
   bfd_session_t *bs = NULL;
   if (!rv)
     {
       rv =
 	bfd_udp_add_session_internal (&bfd_udp_main, sw_if_index,
-				      desired_min_tx_us, required_min_rx_us,
-				      detect_mult, local_addr, peer_addr,
-				      &bs);
+				      desired_min_tx_usec,
+				      required_min_rx_usec, detect_mult,
+				      local_addr, peer_addr, &bs);
     }
   if (!rv && is_authenticated)
     {
@@ -374,6 +372,27 @@
 }
 
 vnet_api_error_t
+bfd_udp_mod_session (u32 sw_if_index,
+		     const ip46_address_t * local_addr,
+		     const ip46_address_t * peer_addr,
+		     u32 desired_min_tx_usec,
+		     u32 required_min_rx_usec, u8 detect_mult)
+{
+  bfd_session_t *bs = NULL;
+  vnet_api_error_t rv =
+    bfd_udp_find_session_by_api_input (sw_if_index, local_addr, peer_addr,
+				       &bs);
+  if (rv)
+    {
+      return rv;
+    }
+
+  return bfd_session_set_params (bfd_udp_main.bfd_main, bs,
+				 desired_min_tx_usec, required_min_rx_usec,
+				 detect_mult);
+}
+
+vnet_api_error_t
 bfd_udp_del_session (u32 sw_if_index,
 		     const ip46_address_t * local_addr,
 		     const ip46_address_t * peer_addr)