MPLS Mcast
[vpp.git] / src / vnet / fib / fib_test.c
index 92141dd..e4a8a70 100644 (file)
@@ -24,6 +24,9 @@
 #include <vnet/dpo/drop_dpo.h>
 #include <vnet/dpo/receive_dpo.h>
 #include <vnet/dpo/ip_null_dpo.h>
+#include <vnet/bfd/bfd_main.h>
+#include <vnet/dpo/interface_dpo.h>
+#include <vnet/dpo/replicate_dpo.h>
 
 #include <vnet/mpls/mpls.h>
 
 #include <vnet/fib/fib_node_list.h>
 #include <vnet/fib/fib_urpf_list.h>
 
+/*
+ * Add debugs for passing tests
+ */
+static int fib_test_do_debug;
+
 #define FIB_TEST_I(_cond, _comment, _args...)                  \
 ({                                                             \
     int _evald = (_cond);                                      \
@@ -40,6 +48,9 @@
        fformat(stderr, "FAIL:%d: " _comment "\n",              \
                __LINE__, ##_args);                             \
     } else {                                                   \
+       if (fib_test_do_debug)                                  \
+            fformat(stderr, "PASS:%d: " _comment "\n",          \
+                    __LINE__, ##_args);                                \
     }                                                          \
     _evald;                                                    \
 })
@@ -262,6 +273,7 @@ typedef enum fib_test_lb_bucket_type_t_ {
     FT_LB_O_LB,
     FT_LB_SPECIAL,
     FT_LB_ADJ,
+    FT_LB_INTF,
 } fib_test_lb_bucket_type_t;
 
 typedef struct fib_test_lb_bucket_t_ {
@@ -306,6 +318,31 @@ typedef struct fib_test_lb_bucket_t_ {
     };
 } fib_test_lb_bucket_t;
 
+typedef enum fib_test_rep_bucket_type_t_ {
+    FT_REP_LABEL_O_ADJ,
+    FT_REP_DISP_MFIB_LOOKUP,
+    FT_REP_INTF,
+} fib_test_rep_bucket_type_t;
+
+typedef struct fib_test_rep_bucket_t_ {
+    fib_test_rep_bucket_type_t type;
+
+    union
+    {
+       struct
+       {
+           mpls_eos_bit_t eos;
+           mpls_label_t label;
+           u8 ttl;
+           adj_index_t adj;
+       } label_o_adj;
+       struct
+       {
+           adj_index_t adj;
+       } adj;
+   };
+} fib_test_rep_bucket_t;
+
 #define FIB_TEST_LB(_cond, _comment, _args...)                 \
 {                                                              \
     if (!FIB_TEST_I(_cond, _comment, ##_args)) {               \
@@ -313,7 +350,83 @@ typedef struct fib_test_lb_bucket_t_ {
     }                                                          \
 }
 
-static int
+int
+fib_test_validate_rep_v (const replicate_t *rep,
+                         u16 n_buckets,
+                         va_list ap)
+{
+    const fib_test_rep_bucket_t *exp;
+    const dpo_id_t *dpo;
+    int bucket;
+
+    FIB_TEST_LB((n_buckets == rep->rep_n_buckets),
+                "n_buckets = %d", rep->rep_n_buckets);
+
+    for (bucket = 0; bucket < n_buckets; bucket++)
+    {
+       exp = va_arg(ap, fib_test_rep_bucket_t*);
+
+        dpo = replicate_get_bucket_i(rep, bucket);
+
+       switch (exp->type)
+       {
+       case FT_REP_LABEL_O_ADJ:
+           {
+               const mpls_label_dpo_t *mld;
+                mpls_label_t hdr;
+               FIB_TEST_LB((DPO_MPLS_LABEL == dpo->dpoi_type),
+                            "bucket %d stacks on %U",
+                            bucket,
+                            format_dpo_type, dpo->dpoi_type);
+           
+               mld = mpls_label_dpo_get(dpo->dpoi_index);
+                hdr = clib_net_to_host_u32(mld->mld_hdr[0].label_exp_s_ttl);
+
+               FIB_TEST_LB((vnet_mpls_uc_get_label(hdr) ==
+                            exp->label_o_adj.label),
+                           "bucket %d stacks on label %d",
+                           bucket,
+                           exp->label_o_adj.label);
+
+               FIB_TEST_LB((vnet_mpls_uc_get_s(hdr) ==
+                            exp->label_o_adj.eos),
+                           "bucket %d stacks on label %d %U",
+                           bucket,
+                           exp->label_o_adj.label,
+                           format_mpls_eos_bit, exp->label_o_adj.eos);
+
+               FIB_TEST_LB((DPO_ADJACENCY_INCOMPLETE == mld->mld_dpo.dpoi_type),
+                           "bucket %d label stacks on %U",
+                           bucket,
+                           format_dpo_type, mld->mld_dpo.dpoi_type);
+
+               FIB_TEST_LB((exp->label_o_adj.adj == mld->mld_dpo.dpoi_index),
+                           "bucket %d label stacks on adj %d",
+                           bucket,
+                           exp->label_o_adj.adj);
+           }
+           break;
+       case FT_REP_INTF:
+            FIB_TEST_LB((DPO_INTERFACE == dpo->dpoi_type),
+                        "bucket %d stacks on %U",
+                        bucket,
+                        format_dpo_type, dpo->dpoi_type);
+
+            FIB_TEST_LB((exp->adj.adj == dpo->dpoi_index),
+                        "bucket %d stacks on adj %d",
+                        bucket,
+                        exp->adj.adj);
+           break;
+        case FT_REP_DISP_MFIB_LOOKUP:
+//            ASSERT(0);
+            break;
+        }
+    }
+
+    return (!0);
+}
+
+int
 fib_test_validate_lb_v (const load_balance_t *lb,
                        u16 n_buckets,
                        va_list ap)
@@ -475,6 +588,16 @@ fib_test_validate_lb_v (const load_balance_t *lb,
                        bucket,
                        exp->adj.adj);
            break;
+       case FT_LB_INTF:
+           FIB_TEST_I((DPO_INTERFACE == dpo->dpoi_type),
+                      "bucket %d stacks on %U",
+                      bucket,
+                      format_dpo_type, dpo->dpoi_type);
+           FIB_TEST_LB((exp->adj.adj == dpo->dpoi_index),
+                       "bucket %d stacks on adj %d",
+                       bucket,
+                       exp->adj.adj);
+           break;
        case FT_LB_O_LB:
            FIB_TEST_I((DPO_LOAD_BALANCE == dpo->dpoi_type),
                        "bucket %d stacks on %U",
@@ -500,14 +623,13 @@ fib_test_validate_lb_v (const load_balance_t *lb,
     return (!0);
 }
 
-static int
+int
 fib_test_validate_entry (fib_node_index_t fei,
                         fib_forward_chain_type_t fct,
                         u16 n_buckets,
                         ...)
 {
     dpo_id_t dpo = DPO_INVALID;
-    const load_balance_t *lb;
     fib_prefix_t pfx;
     index_t fw_lbi;
     u32 fib_index;
@@ -520,47 +642,59 @@ fib_test_validate_entry (fib_node_index_t fei,
     fib_index = fib_entry_get_fib_index(fei);
     fib_entry_contribute_forwarding(fei, fct, &dpo);
 
-    FIB_TEST_LB((DPO_LOAD_BALANCE == dpo.dpoi_type),
-               "Entry links to %U",
-               format_dpo_type, dpo.dpoi_type);
-    lb = load_balance_get(dpo.dpoi_index);
-
-    res = fib_test_validate_lb_v(lb, n_buckets, ap);
+    if (DPO_REPLICATE == dpo.dpoi_type)
+    {
+        const replicate_t *rep;
 
-    /*
-     * ensure that the LB contributed by the entry is the
-     * same as the LB in the forwarding tables
-     */
-    if (fct == fib_entry_get_default_chain_type(fib_entry_get(fei)))
+        rep = replicate_get(dpo.dpoi_index);
+        res = fib_test_validate_rep_v(rep, n_buckets, ap);
+    }
+    else
     {
-        switch (pfx.fp_proto)
-        {
-        case FIB_PROTOCOL_IP4:
-            fw_lbi = ip4_fib_forwarding_lookup(fib_index, &pfx.fp_addr.ip4);
-            break;
-        case FIB_PROTOCOL_IP6:
-            fw_lbi = ip6_fib_table_fwding_lookup(&ip6_main, fib_index, &pfx.fp_addr.ip6);
-            break;
-        case FIB_PROTOCOL_MPLS:
-            {
-                mpls_unicast_header_t hdr = {
-                    .label_exp_s_ttl = 0,
-                };
+        const load_balance_t *lb;
+
+        FIB_TEST_LB((DPO_LOAD_BALANCE == dpo.dpoi_type),
+                    "Entry links to %U",
+                    format_dpo_type, dpo.dpoi_type);
 
-                vnet_mpls_uc_set_label(&hdr.label_exp_s_ttl, pfx.fp_label);
-                vnet_mpls_uc_set_s(&hdr.label_exp_s_ttl, pfx.fp_eos);
-                hdr.label_exp_s_ttl = clib_host_to_net_u32(hdr.label_exp_s_ttl);
+        lb = load_balance_get(dpo.dpoi_index);
+        res = fib_test_validate_lb_v(lb, n_buckets, ap);
 
-                fw_lbi = mpls_fib_table_forwarding_lookup(fib_index, &hdr);
+        /*
+         * ensure that the LB contributed by the entry is the
+         * same as the LB in the forwarding tables
+         */
+        if (fct == fib_entry_get_default_chain_type(fib_entry_get(fei)))
+        {
+            switch (pfx.fp_proto)
+            {
+            case FIB_PROTOCOL_IP4:
+                fw_lbi = ip4_fib_forwarding_lookup(fib_index, &pfx.fp_addr.ip4);
+                break;
+            case FIB_PROTOCOL_IP6:
+                fw_lbi = ip6_fib_table_fwding_lookup(&ip6_main, fib_index, &pfx.fp_addr.ip6);
                 break;
+            case FIB_PROTOCOL_MPLS:
+                {
+                    mpls_unicast_header_t hdr = {
+                        .label_exp_s_ttl = 0,
+                    };
+
+                    vnet_mpls_uc_set_label(&hdr.label_exp_s_ttl, pfx.fp_label);
+                    vnet_mpls_uc_set_s(&hdr.label_exp_s_ttl, pfx.fp_eos);
+                    hdr.label_exp_s_ttl = clib_host_to_net_u32(hdr.label_exp_s_ttl);
+
+                    fw_lbi = mpls_fib_table_forwarding_lookup(fib_index, &hdr);
+                    break;
+                }
+            default:
+                fw_lbi = 0;
             }
-        default:
-            fw_lbi = 0;
+            FIB_TEST_LB((fw_lbi == dpo.dpoi_index),
+                        "Contributed LB = FW LB: %U\n %U",
+                        format_load_balance, fw_lbi, 0,
+                        format_load_balance, dpo.dpoi_index, 0);
         }
-        FIB_TEST_LB((fw_lbi == dpo.dpoi_index),
-                    "Contributed LB = FW LB: %U\n %U",
-                    format_load_balance, fw_lbi, 0,
-                    format_load_balance, dpo.dpoi_index, 0);
     }
 
     dpo_reset(&dpo);
@@ -1280,6 +1414,7 @@ fib_test_v4 (void)
 
     lookup_dpo_add_or_lock_w_fib_index(fib_index,
                                        DPO_PROTO_IP4,
+                                       LOOKUP_UNICAST,
                                        LOOKUP_INPUT_DST_ADDR,
                                        LOOKUP_TABLE_FROM_CONFIG,
                                        &ex_dpo);
@@ -2596,7 +2731,6 @@ fib_test_v4 (void)
                             NULL,
                             FIB_ROUTE_PATH_FLAG_NONE);
 
-
     fei = fib_table_lookup(fib_index, &pfx_5_5_5_6_s_32);
     dpo1 = fib_entry_contribute_ip_forwarding(fei);
 
@@ -6735,6 +6869,509 @@ fib_test_walk (void)
     return (0);
 }
 
+/*
+ * declaration of the otherwise static callback functions
+ */
+void fib_bfd_notify (bfd_listen_event_e event,
+                     const bfd_session_t *session);
+void adj_bfd_notify (bfd_listen_event_e event,
+                     const bfd_session_t *session);
+
+/**
+ * Test BFD session interaction with FIB
+ */
+static int
+fib_test_bfd (void)
+{
+    fib_node_index_t fei;
+    test_main_t *tm;
+    int n_feis;
+
+    /* via 10.10.10.1 */
+    ip46_address_t nh_10_10_10_1 = {
+        .ip4.as_u32 = clib_host_to_net_u32(0x0a0a0a01),
+    };
+    /* via 10.10.10.2 */
+    ip46_address_t nh_10_10_10_2 = {
+        .ip4.as_u32 = clib_host_to_net_u32(0x0a0a0a02),
+    };
+    /* via 10.10.10.10 */
+    ip46_address_t nh_10_10_10_10 = {
+        .ip4.as_u32 = clib_host_to_net_u32(0x0a0a0a0a),
+    };
+    n_feis = fib_entry_pool_size();
+
+    tm = &test_main;
+
+    /*
+     * add interface routes. we'll assume this works. it's tested elsewhere
+     */
+    fib_prefix_t pfx_10_10_10_10_s_24 = {
+        .fp_len = 24,
+        .fp_proto = FIB_PROTOCOL_IP4,
+        .fp_addr = nh_10_10_10_10,
+    };
+
+    fib_table_entry_update_one_path(0, &pfx_10_10_10_10_s_24,
+                                    FIB_SOURCE_INTERFACE,
+                                    (FIB_ENTRY_FLAG_CONNECTED |
+                                     FIB_ENTRY_FLAG_ATTACHED),
+                                    FIB_PROTOCOL_IP4,
+                                    NULL,
+                                    tm->hw[0]->sw_if_index,
+                                    ~0, // invalid fib index
+                                    1, // weight
+                                    NULL,
+                                    FIB_ROUTE_PATH_FLAG_NONE);
+
+    fib_prefix_t pfx_10_10_10_10_s_32 = {
+        .fp_len = 32,
+        .fp_proto = FIB_PROTOCOL_IP4,
+        .fp_addr = nh_10_10_10_10,
+    };
+    fib_table_entry_update_one_path(0, &pfx_10_10_10_10_s_32,
+                                    FIB_SOURCE_INTERFACE,
+                                    (FIB_ENTRY_FLAG_CONNECTED |
+                                     FIB_ENTRY_FLAG_LOCAL),
+                                    FIB_PROTOCOL_IP4,
+                                    NULL,
+                                    tm->hw[0]->sw_if_index,
+                                    ~0, // invalid fib index
+                                    1, // weight
+                                    NULL,
+                                    FIB_ROUTE_PATH_FLAG_NONE);
+
+    /*
+     * A BFD session via a neighbour we do not yet know
+     */
+    bfd_session_t bfd_10_10_10_1 = {
+        .udp = {
+            .key = {
+                .fib_index = 0,
+                .peer_addr = nh_10_10_10_1,
+            },
+        },
+        .hop_type = BFD_HOP_TYPE_MULTI,
+        .local_state = BFD_STATE_init,
+    };
+
+    fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1);
+
+    /*
+     * A new entry will be created that forwards via the adj
+     */
+    adj_index_t ai_10_10_10_1 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4,
+                                                    VNET_LINK_IP4,
+                                                    &nh_10_10_10_1,
+                                                    tm->hw[0]->sw_if_index);
+    fib_prefix_t pfx_10_10_10_1_s_32 = {
+        .fp_addr = nh_10_10_10_1,
+        .fp_len = 32,
+        .fp_proto = FIB_PROTOCOL_IP4,
+    };
+    fib_test_lb_bucket_t adj_o_10_10_10_1 = {
+        .type = FT_LB_ADJ,
+        .adj = {
+            .adj = ai_10_10_10_1,
+        },
+    };
+
+    fei = fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32);
+    FIB_TEST(fib_test_validate_entry(fei,
+                                     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+                                     1,
+                                     &adj_o_10_10_10_1),
+             "BFD sourced %U via %U",
+             format_fib_prefix, &pfx_10_10_10_1_s_32,
+             format_ip_adjacency, ai_10_10_10_1, FORMAT_IP_ADJACENCY_NONE);
+
+    /*
+     * Delete the BFD session. Expect the fib_entry to be removed
+     */
+    fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1);
+
+    fei = fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32);
+    FIB_TEST(FIB_NODE_INDEX_INVALID == fei,
+             "BFD sourced %U removed",
+             format_fib_prefix, &pfx_10_10_10_1_s_32);
+
+    /*
+     * Add the BFD source back
+     */
+    fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1);
+
+    /*
+     * source the entry via the ADJ fib
+     */
+    fei = fib_table_entry_update_one_path(0,
+                                          &pfx_10_10_10_1_s_32,
+                                          FIB_SOURCE_ADJ,
+                                          FIB_ENTRY_FLAG_ATTACHED,
+                                          FIB_PROTOCOL_IP4,
+                                          &nh_10_10_10_1,
+                                          tm->hw[0]->sw_if_index,
+                                          ~0, // invalid fib index
+                                          1,
+                                          NULL,
+                                          FIB_ROUTE_PATH_FLAG_NONE);
+
+    /*
+     * Delete the BFD session. Expect the fib_entry to remain
+     */
+    fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1);
+
+    fei = fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32);
+    FIB_TEST(fib_test_validate_entry(fei,
+                                     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+                                     1,
+                                     &adj_o_10_10_10_1),
+             "BFD sourced %U remains via %U",
+             format_fib_prefix, &pfx_10_10_10_1_s_32,
+             format_ip_adjacency, ai_10_10_10_1, FORMAT_IP_ADJACENCY_NONE);
+
+    /*
+     * Add the BFD source back
+     */
+    fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1);
+
+    /*
+     * Create another ADJ FIB
+     */
+    fib_prefix_t pfx_10_10_10_2_s_32 = {
+        .fp_addr = nh_10_10_10_2,
+        .fp_len = 32,
+        .fp_proto = FIB_PROTOCOL_IP4,
+    };
+    fib_table_entry_update_one_path(0,
+                                    &pfx_10_10_10_2_s_32,
+                                    FIB_SOURCE_ADJ,
+                                    FIB_ENTRY_FLAG_ATTACHED,
+                                    FIB_PROTOCOL_IP4,
+                                    &nh_10_10_10_2,
+                                    tm->hw[0]->sw_if_index,
+                                    ~0, // invalid fib index
+                                    1,
+                                    NULL,
+                                    FIB_ROUTE_PATH_FLAG_NONE);
+    /*
+     * A BFD session for the new ADJ FIB
+     */
+    bfd_session_t bfd_10_10_10_2 = {
+        .udp = {
+            .key = {
+                .fib_index = 0,
+                .peer_addr = nh_10_10_10_2,
+            },
+        },
+        .hop_type = BFD_HOP_TYPE_MULTI,
+        .local_state = BFD_STATE_init,
+    };
+
+    fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_2);
+
+    /*
+     * remove the adj-fib source whilst the session is present
+     * then add it back
+     */
+    fib_table_entry_delete(0, &pfx_10_10_10_2_s_32, FIB_SOURCE_ADJ);
+    fib_table_entry_update_one_path(0,
+                                    &pfx_10_10_10_2_s_32,
+                                    FIB_SOURCE_ADJ,
+                                    FIB_ENTRY_FLAG_ATTACHED,
+                                    FIB_PROTOCOL_IP4,
+                                    &nh_10_10_10_2,
+                                    tm->hw[0]->sw_if_index,
+                                    ~0, // invalid fib index
+                                    1,
+                                    NULL,
+                                    FIB_ROUTE_PATH_FLAG_NONE);
+
+    /*
+     * Before adding a recursive via the BFD tracked ADJ-FIBs,
+     * bring one of the sessions UP, leave the other down
+     */
+    bfd_10_10_10_1.local_state = BFD_STATE_up;
+    fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1);
+    bfd_10_10_10_2.local_state = BFD_STATE_down;
+    fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_2);
+
+    /*
+     * A recursive prefix via both of the ADJ FIBs
+     */
+    fib_prefix_t pfx_200_0_0_0_s_24 = {
+        .fp_proto = FIB_PROTOCOL_IP4,
+        .fp_len = 32,
+        .fp_addr = {
+            .ip4.as_u32 = clib_host_to_net_u32(0xc8000000),
+        },
+    };
+    const dpo_id_t *dpo_10_10_10_1, *dpo_10_10_10_2;
+
+    dpo_10_10_10_1 =
+        fib_entry_contribute_ip_forwarding(
+            fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32));
+    dpo_10_10_10_2 =
+        fib_entry_contribute_ip_forwarding(
+            fib_table_lookup_exact_match(0, &pfx_10_10_10_2_s_32));
+
+    fib_test_lb_bucket_t lb_o_10_10_10_1 = {
+        .type = FT_LB_O_LB,
+        .lb = {
+            .lb = dpo_10_10_10_1->dpoi_index,
+        },
+    };
+    fib_test_lb_bucket_t lb_o_10_10_10_2 = {
+        .type = FT_LB_O_LB,
+        .lb = {
+            .lb = dpo_10_10_10_2->dpoi_index,
+        },
+    };
+
+    /*
+     * A prefix via the adj-fib that is BFD down => DROP
+     */
+    fei = fib_table_entry_path_add(0,
+                                   &pfx_200_0_0_0_s_24,
+                                   FIB_SOURCE_API,
+                                   FIB_ENTRY_FLAG_NONE,
+                                   FIB_PROTOCOL_IP4,
+                                   &nh_10_10_10_2,
+                                   ~0, // recursive
+                                   0, // default fib index
+                                   1,
+                                   NULL,
+                                   FIB_ROUTE_PATH_FLAG_NONE);
+    FIB_TEST(load_balance_is_drop(fib_entry_contribute_ip_forwarding(fei)),
+             "%U resolves via drop",
+             format_fib_prefix, &pfx_200_0_0_0_s_24);
+
+    /*
+     * add a path via the UP BFD adj-fib.
+     *  we expect that the DOWN BFD ADJ FIB is not used.
+     */
+    fei = fib_table_entry_path_add(0,
+                                   &pfx_200_0_0_0_s_24,
+                                   FIB_SOURCE_API,
+                                   FIB_ENTRY_FLAG_NONE,
+                                   FIB_PROTOCOL_IP4,
+                                   &nh_10_10_10_1,
+                                   ~0, // recursive
+                                   0, // default fib index
+                                   1,
+                                   NULL,
+                                   FIB_ROUTE_PATH_FLAG_NONE);
+
+    FIB_TEST(fib_test_validate_entry(fei,
+                                     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+                                     1,
+                                     &lb_o_10_10_10_1),
+             "Recursive %U only UP BFD adj-fibs",
+             format_fib_prefix, &pfx_200_0_0_0_s_24);
+
+    /*
+     * Send a BFD state change to UP - both sessions are now up
+     *  the recursive prefix should LB over both
+     */
+    bfd_10_10_10_2.local_state = BFD_STATE_up;
+    fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_2);
+
+
+    FIB_TEST(fib_test_validate_entry(fei,
+                                     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+                                     2,
+                                     &lb_o_10_10_10_1,
+                                     &lb_o_10_10_10_2),
+             "Recursive %U via both UP BFD adj-fibs",
+             format_fib_prefix, &pfx_200_0_0_0_s_24);
+
+    /*
+     * Send a BFD state change to DOWN
+     *  the recursive prefix should exclude the down
+     */
+    bfd_10_10_10_2.local_state = BFD_STATE_down;
+    fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_2);
+
+
+    FIB_TEST(fib_test_validate_entry(fei,
+                                     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+                                     1,
+                                     &lb_o_10_10_10_1),
+             "Recursive %U via only UP",
+             format_fib_prefix, &pfx_200_0_0_0_s_24);
+
+    /*
+     * Delete the BFD session while it is in the DOWN state.
+     *  FIB should consider the entry's state as back up
+     */
+    fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_2);
+
+    FIB_TEST(fib_test_validate_entry(fei,
+                                     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+                                     2,
+                                     &lb_o_10_10_10_1,
+                                     &lb_o_10_10_10_2),
+             "Recursive %U via both UP BFD adj-fibs post down session delete",
+             format_fib_prefix, &pfx_200_0_0_0_s_24);
+
+    /*
+     * Delete the BFD other session while it is in the UP state.
+     */
+    fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1);
+
+    FIB_TEST(fib_test_validate_entry(fei,
+                                     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+                                     2,
+                                     &lb_o_10_10_10_1,
+                                     &lb_o_10_10_10_2),
+             "Recursive %U via both UP BFD adj-fibs post up session delete",
+             format_fib_prefix, &pfx_200_0_0_0_s_24);
+
+    /*
+     * cleaup
+     */
+    fib_table_entry_delete(0, &pfx_200_0_0_0_s_24, FIB_SOURCE_API);
+    fib_table_entry_delete(0, &pfx_10_10_10_1_s_32, FIB_SOURCE_ADJ);
+    fib_table_entry_delete(0, &pfx_10_10_10_2_s_32, FIB_SOURCE_ADJ);
+
+    fib_table_entry_delete(0, &pfx_10_10_10_10_s_32, FIB_SOURCE_INTERFACE);
+    fib_table_entry_delete(0, &pfx_10_10_10_10_s_24, FIB_SOURCE_INTERFACE);
+
+    adj_unlock(ai_10_10_10_1);
+     /*
+     * test no-one left behind
+     */
+    FIB_TEST((n_feis == fib_entry_pool_size()), "Entries gone");
+    FIB_TEST(0 == adj_nbr_db_size(), "All adjacencies removed");
+
+    /*
+     * Single-hop BFD tests
+     */
+    bfd_10_10_10_1.hop_type = BFD_HOP_TYPE_SINGLE;
+    bfd_10_10_10_1.udp.key.sw_if_index = tm->hw[0]->sw_if_index;
+
+    adj_bfd_notify(BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1);
+
+    ai_10_10_10_1 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4,
+                                        VNET_LINK_IP4,
+                                        &nh_10_10_10_1,
+                                        tm->hw[0]->sw_if_index);
+    /*
+     * whilst the BFD session is not signalled, the adj is up
+     */
+    FIB_TEST(adj_is_up(ai_10_10_10_1), "Adj state up on uninit session");
+
+    /*
+     * bring the BFD session up
+     */
+    bfd_10_10_10_1.local_state = BFD_STATE_up;
+    adj_bfd_notify(BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1);
+    FIB_TEST(adj_is_up(ai_10_10_10_1), "Adj state up on UP session");
+
+    /*
+     * bring the BFD session down
+     */
+    bfd_10_10_10_1.local_state = BFD_STATE_down;
+    adj_bfd_notify(BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1);
+    FIB_TEST(!adj_is_up(ai_10_10_10_1), "Adj state down on DOWN session");
+
+
+    /*
+     * add an attached next hop FIB entry via the down adj
+     */
+    fib_prefix_t pfx_5_5_5_5_s_32 = {
+        .fp_addr = {
+            .ip4 = {
+                .as_u32 = clib_host_to_net_u32(0x05050505),
+            },
+        },
+        .fp_len = 32,
+        .fp_proto = FIB_PROTOCOL_IP4,
+    };
+
+    fei = fib_table_entry_path_add(0,
+                                   &pfx_5_5_5_5_s_32,
+                                   FIB_SOURCE_CLI,
+                                   FIB_ENTRY_FLAG_NONE,
+                                   FIB_PROTOCOL_IP4,
+                                   &nh_10_10_10_1,
+                                   tm->hw[0]->sw_if_index,
+                                   ~0, // invalid fib index
+                                   1,
+                                   NULL,
+                                   FIB_ROUTE_PATH_FLAG_NONE);
+    FIB_TEST(load_balance_is_drop(fib_entry_contribute_ip_forwarding(fei)),
+             "%U resolves via drop",
+             format_fib_prefix, &pfx_5_5_5_5_s_32);
+
+    /*
+     * Add a path via an ADJ that is up
+     */
+    adj_index_t ai_10_10_10_2 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4,
+                                                    VNET_LINK_IP4,
+                                                    &nh_10_10_10_2,
+                                                    tm->hw[0]->sw_if_index);
+
+    fib_test_lb_bucket_t adj_o_10_10_10_2 = {
+        .type = FT_LB_ADJ,
+        .adj = {
+            .adj = ai_10_10_10_2,
+        },
+    };
+    adj_o_10_10_10_1.adj.adj = ai_10_10_10_1;
+
+    fei = fib_table_entry_path_add(0,
+                                   &pfx_5_5_5_5_s_32,
+                                   FIB_SOURCE_CLI,
+                                   FIB_ENTRY_FLAG_NONE,
+                                   FIB_PROTOCOL_IP4,
+                                   &nh_10_10_10_2,
+                                   tm->hw[0]->sw_if_index,
+                                   ~0, // invalid fib index
+                                   1,
+                                   NULL,
+                                   FIB_ROUTE_PATH_FLAG_NONE);
+
+    FIB_TEST(fib_test_validate_entry(fei,
+                                     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+                                     1,
+                                     &adj_o_10_10_10_2),
+             "BFD sourced %U via %U",
+             format_fib_prefix, &pfx_5_5_5_5_s_32,
+             format_ip_adjacency, ai_10_10_10_2, FORMAT_IP_ADJACENCY_NONE);
+
+    /*
+     * Bring up the down session - should now LB
+     */
+    bfd_10_10_10_1.local_state = BFD_STATE_up;
+    adj_bfd_notify(BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1);
+    FIB_TEST(fib_test_validate_entry(fei,
+                                     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+                                     2,
+                                     &adj_o_10_10_10_1,
+                                     &adj_o_10_10_10_2),
+             "BFD sourced %U via noth adjs",
+             format_fib_prefix, &pfx_5_5_5_5_s_32);
+
+    /*
+     * remove the BFD session state from the adj
+     */
+    adj_bfd_notify(BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1);
+
+    /*
+     * clean-up
+     */
+    fib_table_entry_delete(0, &pfx_5_5_5_5_s_32, FIB_SOURCE_CLI);
+    adj_unlock(ai_10_10_10_1);
+    adj_unlock(ai_10_10_10_2);
+
+    /*
+     * test no-one left behind
+     */
+    FIB_TEST((n_feis == fib_entry_pool_size()), "Entries gone");
+    FIB_TEST(0 == adj_nbr_db_size(), "All adjacencies removed");
+    return (0);
+}
+
 static int
 lfib_test (void)
 {
@@ -6981,6 +7618,7 @@ lfib_test (void)
     fib_route_path_t *rpaths = NULL, rpath = {
        .frp_proto = FIB_PROTOCOL_MPLS,
        .frp_local_label = 1200,
+        .frp_eos = MPLS_NON_EOS,
        .frp_sw_if_index = ~0, // recurive
        .frp_fib_index = 0, // Default MPLS fib
        .frp_weight = 1,
@@ -7095,6 +7733,146 @@ lfib_test (void)
 
     dpo_reset(&ip_1200);
 
+    /*
+     * An rx-interface route.
+     *  like the tail of an mcast LSP
+     */
+    dpo_id_t idpo = DPO_INVALID;
+
+    interface_dpo_add_or_lock(DPO_PROTO_IP4,
+                              tm->hw[0]->sw_if_index,
+                              &idpo);
+
+    fib_prefix_t pfx_2500 = {
+       .fp_len = 21,
+       .fp_proto = FIB_PROTOCOL_MPLS,
+       .fp_label = 2500,
+       .fp_eos = MPLS_EOS,
+       .fp_payload_proto = DPO_PROTO_IP4,
+    };
+    fib_test_lb_bucket_t rx_intf_0 = {
+        .type = FT_LB_INTF,
+        .adj = {
+            .adj = idpo.dpoi_index,
+        },
+    };
+
+    lfe = fib_table_entry_update_one_path(fib_index,
+                                         &pfx_2500,
+                                         FIB_SOURCE_API,
+                                         FIB_ENTRY_FLAG_NONE,
+                                         FIB_PROTOCOL_IP4,
+                                         NULL,
+                                         tm->hw[0]->sw_if_index,
+                                         ~0, // invalid fib index
+                                         0,
+                                         NULL,
+                                         FIB_ROUTE_PATH_INTF_RX);
+    FIB_TEST(fib_test_validate_entry(lfe,
+                                    FIB_FORW_CHAIN_TYPE_MPLS_EOS,
+                                    1,
+                                    &rx_intf_0),
+            "2500 rx-interface 0");
+    fib_table_entry_delete(fib_index, &pfx_2500, FIB_SOURCE_API);
+
+    /*
+     * An MPLS mulicast entry
+     */
+    fib_prefix_t pfx_3500 = {
+       .fp_len = 21,
+       .fp_proto = FIB_PROTOCOL_MPLS,
+       .fp_label = 3500,
+       .fp_eos = MPLS_EOS,
+       .fp_payload_proto = DPO_PROTO_IP4,
+    };
+    fib_test_rep_bucket_t mc_0 = {
+        .type = FT_REP_LABEL_O_ADJ,
+       .label_o_adj = {
+           .adj = ai_mpls_10_10_10_1,
+           .label = 3300,
+           .eos = MPLS_EOS,
+       },
+    };
+    fib_test_rep_bucket_t mc_intf_0 = {
+        .type = FT_REP_INTF,
+        .adj = {
+            .adj = idpo.dpoi_index,
+        },
+    };
+    mpls_label_t *l3300 = NULL;
+    vec_add1(l3300, 3300);
+
+    lfe = fib_table_entry_update_one_path(lfib_index,
+                                         &pfx_3500,
+                                         FIB_SOURCE_API,
+                                         FIB_ENTRY_FLAG_MULTICAST,
+                                         FIB_PROTOCOL_IP4,
+                                         &nh_10_10_10_1,
+                                         tm->hw[0]->sw_if_index,
+                                         ~0, // invalid fib index
+                                         1,
+                                         l3300,
+                                         FIB_ROUTE_PATH_FLAG_NONE);
+    FIB_TEST(fib_test_validate_entry(lfe,
+                                    FIB_FORW_CHAIN_TYPE_MPLS_EOS,
+                                    1,
+                                    &mc_0),
+            "3500 via replicate over 10.10.10.1");
+
+    /*
+     * MPLS Bud-node. Add a replication via an interface-receieve path
+     */
+    lfe = fib_table_entry_path_add(lfib_index,
+                                  &pfx_3500,
+                                  FIB_SOURCE_API,
+                                  FIB_ENTRY_FLAG_MULTICAST,
+                                  FIB_PROTOCOL_IP4,
+                                   NULL,
+                                   tm->hw[0]->sw_if_index,
+                                   ~0, // invalid fib index
+                                   0,
+                                   NULL,
+                                   FIB_ROUTE_PATH_INTF_RX);
+    FIB_TEST(fib_test_validate_entry(lfe,
+                                     FIB_FORW_CHAIN_TYPE_MPLS_EOS,
+                                     2,
+                                     &mc_0,
+                                     &mc_intf_0),
+            "3500 via replicate over 10.10.10.1 and interface-rx");
+
+    /*
+     * Add a replication via an interface-free for-us path
+     */
+    fib_test_rep_bucket_t mc_disp = {
+        .type = FT_REP_DISP_MFIB_LOOKUP,
+        .adj = {
+            .adj = idpo.dpoi_index,
+        },
+    };
+    lfe = fib_table_entry_path_add(lfib_index,
+                                  &pfx_3500,
+                                  FIB_SOURCE_API,
+                                  FIB_ENTRY_FLAG_MULTICAST,
+                                  FIB_PROTOCOL_IP4,
+                                   NULL,
+                                   5, // rpf-id
+                                   0, // default table
+                                   0,
+                                   NULL,
+                                   FIB_ROUTE_PATH_RPF_ID);
+    FIB_TEST(fib_test_validate_entry(lfe,
+                                     FIB_FORW_CHAIN_TYPE_MPLS_EOS,
+                                     3,
+                                     &mc_0,
+                                     &mc_disp,
+                                     &mc_intf_0),
+            "3500 via replicate over 10.10.10.1 and interface-rx");
+
+
+    
+    fib_table_entry_delete(fib_index, &pfx_3500, FIB_SOURCE_API);
+    dpo_reset(&idpo);
+
     /*
      * cleanup
      */
@@ -7105,6 +7883,9 @@ lfib_test (void)
     FIB_TEST(lb_count == pool_elts(load_balance_pool),
             "Load-balance resources freed %d of %d",
              lb_count, pool_elts(load_balance_pool));
+    FIB_TEST(0 == pool_elts(interface_dpo_pool),
+            "interface_dpo resources freed %d of %d",
+             0, pool_elts(interface_dpo_pool));
 
     return (0);
 }
@@ -7119,6 +7900,11 @@ fib_test (vlib_main_t * vm,
     res = 0;
     fib_test_mk_intf(4);
 
+    if (unformat (input, "debug"))
+    {
+        fib_test_do_debug = 1;
+    }
+
     if (unformat (input, "ip"))
     {
        res += fib_test_v4();
@@ -7140,6 +7926,10 @@ fib_test (vlib_main_t * vm,
     {
        res += fib_test_walk();
     }
+    else if (unformat (input, "bfd"))
+    {
+       res += fib_test_bfd();
+    }
     else
     {
         /*
@@ -7151,6 +7941,7 @@ fib_test (vlib_main_t * vm,
        res += fib_test_v4();
        res += fib_test_v6();
        res += fib_test_ae();
+       res += fib_test_bfd();
        res += fib_test_label();
        res += lfib_test();
     }