loop counter to prevent infiinte number of look ups per-packet
[vpp.git] / src / vnet / dpo / lookup_dpo.c
index 96fedd2..ebdbc12 100644 (file)
 
 #include <vnet/ip/ip.h>
 #include <vnet/dpo/lookup_dpo.h>
-#include <vnet/dpo/load_balance.h>
-#include <vnet/mpls/mpls.h>
+#include <vnet/dpo/load_balance_map.h>
+#include <vnet/mpls/mpls_lookup.h>
 #include <vnet/fib/fib_table.h>
 #include <vnet/fib/ip4_fib.h>
 #include <vnet/fib/ip6_fib.h>
 #include <vnet/fib/mpls_fib.h>
+#include <vnet/mfib/mfib_table.h>
+#include <vnet/mfib/ip4_mfib.h>
+#include <vnet/mfib/ip6_mfib.h>
 
 static const char *const lookup_input_names[] = LOOKUP_INPUTS;
+static const char *const lookup_cast_names[] = LOOKUP_CASTS;
+
+/**
+ * If a packet encounters a lookup DPO more than the many times
+ * then we assume there is a loop in the forward graph and drop the packet
+ */
+#define MAX_LUKPS_PER_PACKET 4
 
 /**
  * @brief Enumeration of the lookup subtypes
@@ -31,6 +41,7 @@ typedef enum lookup_sub_type_t_
 {
     LOOKUP_SUB_TYPE_SRC,
     LOOKUP_SUB_TYPE_DST,
+    LOOKUP_SUB_TYPE_DST_MCAST,
     LOOKUP_SUB_TYPE_DST_TABLE_FROM_INTERFACE,
 } lookup_sub_type_t;
 #define LOOKUP_SUB_TYPE_NUM (LOOKUP_SUB_TYPE_DST_TABLE_FROM_INTERFACE+1)
@@ -67,6 +78,7 @@ lookup_dpo_get_index (lookup_dpo_t *lkd)
 static void
 lookup_dpo_add_or_lock_i (fib_node_index_t fib_index,
                           dpo_proto_t proto,
+                          lookup_cast_t cast,
                           lookup_input_t input,
                           lookup_table_t table_config,
                           dpo_id_t *dpo)
@@ -79,6 +91,7 @@ lookup_dpo_add_or_lock_i (fib_node_index_t fib_index,
     lkd->lkd_proto = proto;
     lkd->lkd_input = input;
     lkd->lkd_table = table_config;
+    lkd->lkd_cast  = cast;
 
     /*
      * use the input type to select the lookup sub-type
@@ -100,6 +113,10 @@ lookup_dpo_add_or_lock_i (fib_node_index_t fib_index,
             type = lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST];
             break;
         }
+        if (LOOKUP_MULTICAST == cast)
+        {
+            type = lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST_MCAST];
+        }
     }
 
     if (0 == type)
@@ -115,20 +132,33 @@ lookup_dpo_add_or_lock_i (fib_node_index_t fib_index,
 void
 lookup_dpo_add_or_lock_w_fib_index (fib_node_index_t fib_index,
                                     dpo_proto_t proto,
+                                    lookup_cast_t cast,
                                     lookup_input_t input,
                                     lookup_table_t table_config,
                                     dpo_id_t *dpo)
 {
     if (LOOKUP_TABLE_FROM_CONFIG == table_config)
     {
-       fib_table_lock(fib_index, dpo_proto_to_fib(proto));
+        if (LOOKUP_UNICAST == cast)
+        {
+            fib_table_lock(fib_index,
+                           dpo_proto_to_fib(proto),
+                           FIB_SOURCE_RR);
+        }
+        else
+        {
+            mfib_table_lock(fib_index,
+                            dpo_proto_to_fib(proto),
+                            MFIB_SOURCE_RR);
+        }
     }
-    lookup_dpo_add_or_lock_i(fib_index, proto, input, table_config, dpo);
+    lookup_dpo_add_or_lock_i(fib_index, proto, cast, input, table_config, dpo);
 }
 
 void
 lookup_dpo_add_or_lock_w_table_id (u32 table_id,
                                    dpo_proto_t proto,
+                                   lookup_cast_t cast,
                                    lookup_input_t input,
                                    lookup_table_t table_config,
                                    dpo_id_t *dpo)
@@ -137,13 +167,24 @@ lookup_dpo_add_or_lock_w_table_id (u32 table_id,
 
     if (LOOKUP_TABLE_FROM_CONFIG == table_config)
     {
-       fib_index =
-           fib_table_find_or_create_and_lock(dpo_proto_to_fib(proto),
-                                             table_id);
+        if (LOOKUP_UNICAST == cast)
+        {
+            fib_index =
+                fib_table_find_or_create_and_lock(dpo_proto_to_fib(proto),
+                                                  table_id,
+                                                  FIB_SOURCE_RR);
+        }
+        else
+        {
+            fib_index =
+                mfib_table_find_or_create_and_lock(dpo_proto_to_fib(proto),
+                                                   table_id,
+                                                   MFIB_SOURCE_RR);
+        }
     }
 
     ASSERT(FIB_NODE_INDEX_INVALID != fib_index);
-    lookup_dpo_add_or_lock_i(fib_index, proto, input, table_config, dpo);    
+    lookup_dpo_add_or_lock_i(fib_index, proto, cast, input, table_config, dpo);
 }
 
 u8*
@@ -156,16 +197,29 @@ format_lookup_dpo (u8 *s, va_list *args)
 
     if (LOOKUP_TABLE_FROM_INPUT_INTERFACE == lkd->lkd_table)
     {
-        s = format(s, "%s lookup in interface's %U table",
+        s = format(s, "%s,%s lookup in interface's %U table",
                    lookup_input_names[lkd->lkd_input],
+                   lookup_cast_names[lkd->lkd_cast],
                    format_dpo_proto, lkd->lkd_proto);
     }
     else
     {
-       s = format(s, "%s lookup in %U",
-                  lookup_input_names[lkd->lkd_input],
-                  format_fib_table_name, lkd->lkd_fib_index,
-                  dpo_proto_to_fib(lkd->lkd_proto));
+        if (LOOKUP_UNICAST == lkd->lkd_cast)
+        {
+            s = format(s, "%s,%s lookup in %U",
+                       lookup_input_names[lkd->lkd_input],
+                       lookup_cast_names[lkd->lkd_cast],
+                       format_fib_table_name, lkd->lkd_fib_index,
+                       dpo_proto_to_fib(lkd->lkd_proto));
+        }
+        else
+        {
+            s = format(s, "%s,%s lookup in %U",
+                       lookup_input_names[lkd->lkd_input],
+                       lookup_cast_names[lkd->lkd_cast],
+                       format_mfib_table_name, lkd->lkd_fib_index,
+                       dpo_proto_to_fib(lkd->lkd_proto));
+        }
     }
     return (s);
 }
@@ -193,8 +247,18 @@ lookup_dpo_unlock (dpo_id_t *dpo)
     {
         if (LOOKUP_TABLE_FROM_CONFIG == lkd->lkd_table)
         {
-           fib_table_unlock(lkd->lkd_fib_index,
-                            dpo_proto_to_fib(lkd->lkd_proto));
+            if (LOOKUP_UNICAST == lkd->lkd_cast)
+            {
+                fib_table_unlock(lkd->lkd_fib_index,
+                                 dpo_proto_to_fib(lkd->lkd_proto),
+                                 FIB_SOURCE_RR);
+            }
+            else
+            {
+                mfib_table_unlock(lkd->lkd_fib_index,
+                                  dpo_proto_to_fib(lkd->lkd_proto),
+                                  MFIB_SOURCE_RR);
+            }
         }
         pool_put(lookup_dpo_pool, lkd);
     }
@@ -205,19 +269,15 @@ ip4_src_fib_lookup_one (u32 src_fib_index0,
                         const ip4_address_t * addr0,
                         u32 * src_adj_index0)
 {
-    ip4_fib_mtrie_leaf_t leaf0, leaf1;
+    ip4_fib_mtrie_leaf_t leaf0;
     ip4_fib_mtrie_t * mtrie0;
 
     mtrie0 = &ip4_fib_get (src_fib_index0)->mtrie;
 
-    leaf0 = leaf1 = IP4_FIB_MTRIE_LEAF_ROOT;
-    leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 0);
-    leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 1);
+    leaf0 = ip4_fib_mtrie_lookup_step_one (mtrie0, addr0);
     leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 2);
     leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 3);
 
-    /* Handle default route. */
-    leaf0 = (leaf0 == IP4_FIB_MTRIE_LEAF_EMPTY ? mtrie0->default_leaf : leaf0);
     src_adj_index0[0] = ip4_fib_mtrie_leaf_get_adj_index (leaf0);
 }
 
@@ -235,13 +295,8 @@ ip4_src_fib_lookup_two (u32 src_fib_index0,
     mtrie0 = &ip4_fib_get (src_fib_index0)->mtrie;
     mtrie1 = &ip4_fib_get (src_fib_index1)->mtrie;
 
-    leaf0 = leaf1 = IP4_FIB_MTRIE_LEAF_ROOT;
-
-    leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 0);
-    leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1, addr1, 0);
-
-    leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 1);
-    leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1, addr1, 1);
+    leaf0 = ip4_fib_mtrie_lookup_step_one (mtrie0, addr0);
+    leaf1 = ip4_fib_mtrie_lookup_step_one (mtrie1, addr1);
 
     leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 2);
     leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1, addr1, 2);
@@ -249,9 +304,6 @@ ip4_src_fib_lookup_two (u32 src_fib_index0,
     leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0, addr0, 3);
     leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1, addr1, 3);
 
-    /* Handle default route. */
-    leaf0 = (leaf0 == IP4_FIB_MTRIE_LEAF_EMPTY ? mtrie0->default_leaf : leaf0);
-    leaf1 = (leaf1 == IP4_FIB_MTRIE_LEAF_EMPTY ? mtrie1->default_leaf : leaf1);
     src_adj_index0[0] = ip4_fib_mtrie_leaf_get_adj_index (leaf0);
     src_adj_index1[0] = ip4_fib_mtrie_leaf_get_adj_index (leaf1);
 }
@@ -278,7 +330,7 @@ lookup_dpo_ip4_inline (vlib_main_t * vm,
                        int table_from_interface)
 {
     u32 n_left_from, next_index, * from, * to_next;
-    u32 cpu_index = os_get_cpu_number();
+    u32 thread_index = vlib_get_thread_index();
     vlib_combined_counter_main_t * cm = &load_balance_main.lbm_to_counters;
 
     from = vlib_frame_vector_args (from_frame);
@@ -419,12 +471,29 @@ lookup_dpo_ip4_inline (vlib_main_t * vm,
            vnet_buffer(b1)->ip.adj_index[VLIB_TX] = dpo1->dpoi_index;
 
            vlib_increment_combined_counter
-               (cm, cpu_index, lbi0, 1,
+               (cm, thread_index, lbi0, 1,
                 vlib_buffer_length_in_chain (vm, b0));
            vlib_increment_combined_counter
-               (cm, cpu_index, lbi1, 1,
+               (cm, thread_index, lbi1, 1,
                 vlib_buffer_length_in_chain (vm, b1));
 
+            if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) {
+                vnet_buffer2(b0)->loop_counter = 0;
+                b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID;
+            }
+            if (!(b1->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) {
+                vnet_buffer2(b1)->loop_counter = 0;
+                b1->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID;
+            }
+
+            vnet_buffer2(b0)->loop_counter++;
+            vnet_buffer2(b1)->loop_counter++;
+
+            if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET))
+                next0 = IP_LOOKUP_NEXT_DROP;
+            if (PREDICT_FALSE(vnet_buffer2(b1)->loop_counter > MAX_LUKPS_PER_PACKET))
+                next1 = IP_LOOKUP_NEXT_DROP;
+
            if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
            {
                lookup_trace_t *tr = vlib_add_trace (vm, node,
@@ -523,9 +592,19 @@ lookup_dpo_ip4_inline (vlib_main_t * vm,
            vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index;
 
            vlib_increment_combined_counter
-               (cm, cpu_index, lbi0, 1,
+               (cm, thread_index, lbi0, 1,
                 vlib_buffer_length_in_chain (vm, b0));
 
+            if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) {
+                vnet_buffer2(b0)->loop_counter = 0;
+                b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID;
+            }
+
+            vnet_buffer2(b0)->loop_counter++;
+
+            if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET))
+                next0 = IP_LOOKUP_NEXT_DROP;
+
            if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
            {
                lookup_trace_t *tr = vlib_add_trace (vm, node,
@@ -549,7 +628,7 @@ format_lookup_trace (u8 * s, va_list * args)
     CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
     CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
     lookup_trace_t * t = va_arg (*args, lookup_trace_t *);
-    uword indent = format_get_indent (s);
+    u32 indent = format_get_indent (s);
     s = format (s, "%U fib-index:%d addr:%U load-balance:%d",
                 format_white_space, indent,
                 t->fib_index,
@@ -558,7 +637,7 @@ format_lookup_trace (u8 * s, va_list * args)
     return s;
 }
 
-always_inline uword
+static uword
 lookup_ip4_dst (vlib_main_t * vm,
                 vlib_node_runtime_t * node,
                 vlib_frame_t * from_frame)
@@ -575,7 +654,7 @@ VLIB_REGISTER_NODE (lookup_ip4_dst_node) = {
 };
 VLIB_NODE_FUNCTION_MULTIARCH (lookup_ip4_dst_node, lookup_ip4_dst)
 
-always_inline uword
+static uword
 lookup_ip4_dst_itf (vlib_main_t * vm,
                     vlib_node_runtime_t * node,
                     vlib_frame_t * from_frame)
@@ -592,7 +671,7 @@ VLIB_REGISTER_NODE (lookup_ip4_dst_itf_node) = {
 };
 VLIB_NODE_FUNCTION_MULTIARCH (lookup_ip4_dst_itf_node, lookup_ip4_dst_itf)
 
-always_inline uword
+static uword
 lookup_ip4_src (vlib_main_t * vm,
                 vlib_node_runtime_t * node,
                 vlib_frame_t * from_frame)
@@ -618,7 +697,7 @@ lookup_dpo_ip6_inline (vlib_main_t * vm,
 {
     vlib_combined_counter_main_t * cm = &load_balance_main.lbm_to_counters;
     u32 n_left_from, next_index, * from, * to_next;
-    u32 cpu_index = os_get_cpu_number();
+    u32 thread_index = vlib_get_thread_index();
 
     from = vlib_frame_vector_args (from_frame);
     n_left_from = from_frame->n_vectors;
@@ -734,6 +813,23 @@ lookup_dpo_ip6_inline (vlib_main_t * vm,
            hash_c0 = vnet_buffer (b0)->ip.flow_hash = 0;
            hash_c1 = vnet_buffer (b1)->ip.flow_hash = 0;
 
+            if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) {
+                vnet_buffer2(b0)->loop_counter = 0;
+                b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID;
+            }
+            if (!(b1->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) {
+                vnet_buffer2(b1)->loop_counter = 0;
+                b1->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID;
+            }
+
+            vnet_buffer2(b0)->loop_counter++;
+            vnet_buffer2(b1)->loop_counter++;
+
+            if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET))
+                next0 = IP_LOOKUP_NEXT_DROP;
+            if (PREDICT_FALSE(vnet_buffer2(b1)->loop_counter > MAX_LUKPS_PER_PACKET))
+                next1 = IP_LOOKUP_NEXT_DROP;
+
            if (PREDICT_FALSE (lb0->lb_n_buckets > 1))
            {
                flow_hash_config0 = lb0->lb_hash_config;
@@ -761,10 +857,10 @@ lookup_dpo_ip6_inline (vlib_main_t * vm,
            vnet_buffer(b1)->ip.adj_index[VLIB_TX] = dpo1->dpoi_index;
 
            vlib_increment_combined_counter
-               (cm, cpu_index, lbi0, 1,
+               (cm, thread_index, lbi0, 1,
                 vlib_buffer_length_in_chain (vm, b0));
            vlib_increment_combined_counter
-               (cm, cpu_index, lbi1, 1,
+               (cm, thread_index, lbi1, 1,
                 vlib_buffer_length_in_chain (vm, b1));
 
            if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
@@ -864,8 +960,18 @@ lookup_dpo_ip6_inline (vlib_main_t * vm,
            next0 = dpo0->dpoi_next_node;
            vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index;
 
+            if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) {
+                vnet_buffer2(b0)->loop_counter = 0;
+                b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID;
+            }
+
+            vnet_buffer2(b0)->loop_counter++;
+
+            if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET))
+                next0 = IP_LOOKUP_NEXT_DROP;
+
            vlib_increment_combined_counter
-               (cm, cpu_index, lbi0, 1,
+               (cm, thread_index, lbi0, 1,
                 vlib_buffer_length_in_chain (vm, b0));
 
            if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
@@ -884,7 +990,7 @@ lookup_dpo_ip6_inline (vlib_main_t * vm,
     return from_frame->n_vectors;
 }
 
-always_inline uword
+static uword
 lookup_ip6_dst (vlib_main_t * vm,
                 vlib_node_runtime_t * node,
                 vlib_frame_t * from_frame)
@@ -901,7 +1007,7 @@ VLIB_REGISTER_NODE (lookup_ip6_dst_node) = {
 };
 VLIB_NODE_FUNCTION_MULTIARCH (lookup_ip6_dst_node, lookup_ip6_dst)
 
-always_inline uword
+static uword
 lookup_ip6_dst_itf (vlib_main_t * vm,
                    vlib_node_runtime_t * node,
                    vlib_frame_t * from_frame)
@@ -918,7 +1024,7 @@ VLIB_REGISTER_NODE (lookup_ip6_dst_itf_node) = {
 };
 VLIB_NODE_FUNCTION_MULTIARCH (lookup_ip6_dst_itf_node, lookup_ip6_dst_itf)
 
-always_inline uword
+static uword
 lookup_ip6_src (vlib_main_t * vm,
                 vlib_node_runtime_t * node,
                 vlib_frame_t * from_frame)
@@ -942,7 +1048,7 @@ lookup_dpo_mpls_inline (vlib_main_t * vm,
                        int table_from_interface)
 {
     u32 n_left_from, next_index, * from, * to_next;
-    u32 cpu_index = os_get_cpu_number();
+    u32 thread_index = vlib_get_thread_index();
     vlib_combined_counter_main_t * cm = &load_balance_main.lbm_to_counters;
 
     from = vlib_frame_vector_args (from_frame);
@@ -961,7 +1067,7 @@ lookup_dpo_mpls_inline (vlib_main_t * vm,
 
         while (n_left_from > 0 && n_left_to_next > 0)
         {
-            u32 bi0, lkdi0, lbi0, fib_index0,  next0;
+            u32 bi0, lkdi0, lbi0, fib_index0, next0, hash0;
             const mpls_unicast_header_t * hdr0;
             const load_balance_t *lb0;
             const lookup_dpo_t * lkd0;
@@ -1005,9 +1111,54 @@ lookup_dpo_mpls_inline (vlib_main_t * vm,
             next0 = dpo0->dpoi_next_node;
             vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index;
 
-            vlib_increment_combined_counter
-                (cm, cpu_index, lbi0, 1,
-                 vlib_buffer_length_in_chain (vm, b0));
+
+            if (MPLS_IS_REPLICATE & lbi0)
+            {
+                next0 = mpls_lookup_to_replicate_edge;
+                vnet_buffer (b0)->ip.adj_index[VLIB_TX] =
+                    (lbi0 & ~MPLS_IS_REPLICATE);
+            }
+            else
+            {
+                lb0 = load_balance_get(lbi0);
+                ASSERT (lb0->lb_n_buckets > 0);
+                ASSERT (is_pow2 (lb0->lb_n_buckets));
+
+                if (PREDICT_FALSE(lb0->lb_n_buckets > 1))
+                {
+                    hash0 = vnet_buffer (b0)->ip.flow_hash =
+                        mpls_compute_flow_hash(hdr0, lb0->lb_hash_config);
+                    dpo0 = load_balance_get_fwd_bucket
+                        (lb0,
+                         (hash0 & (lb0->lb_n_buckets_minus_1)));
+                }
+                else
+                {
+                    dpo0 = load_balance_get_bucket_i (lb0, 0);
+                }
+                next0 = dpo0->dpoi_next_node;
+
+                vnet_buffer (b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index;
+
+                vlib_increment_combined_counter
+                    (cm, thread_index, lbi0, 1,
+                     vlib_buffer_length_in_chain (vm, b0));
+            }
+
+            vnet_buffer (b0)->mpls.ttl = ((char*)hdr0)[3];
+            vnet_buffer (b0)->mpls.exp = (((char*)hdr0)[2] & 0xe) >> 1;
+            vnet_buffer (b0)->mpls.first = 1;
+            vlib_buffer_advance(b0, sizeof(*hdr0));
+
+            if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) {
+                vnet_buffer2(b0)->loop_counter = 0;
+                b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID;
+            }
+
+            vnet_buffer2(b0)->loop_counter++;
+
+            if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET))
+                next0 = MPLS_LOOKUP_NEXT_DROP;
 
            if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) 
             {
@@ -1032,7 +1183,7 @@ format_lookup_mpls_trace (u8 * s, va_list * args)
     CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
     CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
     lookup_trace_t * t = va_arg (*args, lookup_trace_t *);
-    uword indent = format_get_indent (s);
+    u32 indent = format_get_indent (s);
     mpls_unicast_header_t hdr;
 
     hdr.label_exp_s_ttl = clib_net_to_host_u32(t->hdr.label_exp_s_ttl);
@@ -1045,7 +1196,7 @@ format_lookup_mpls_trace (u8 * s, va_list * args)
     return s;
 }
 
-always_inline uword
+static uword
 lookup_mpls_dst (vlib_main_t * vm,
                 vlib_node_runtime_t * node,
                 vlib_frame_t * from_frame)
@@ -1063,7 +1214,7 @@ VLIB_REGISTER_NODE (lookup_mpls_dst_node) = {
 };
 VLIB_NODE_FUNCTION_MULTIARCH (lookup_mpls_dst_node, lookup_mpls_dst)
 
-always_inline uword
+static uword
 lookup_mpls_dst_itf (vlib_main_t * vm,
                     vlib_node_runtime_t * node,
                     vlib_frame_t * from_frame)
@@ -1081,6 +1232,158 @@ VLIB_REGISTER_NODE (lookup_mpls_dst_itf_node) = {
 };
 VLIB_NODE_FUNCTION_MULTIARCH (lookup_mpls_dst_itf_node, lookup_mpls_dst_itf)
 
+typedef enum lookup_ip_dst_mcast_next_t_ {
+    LOOKUP_IP_DST_MCAST_NEXT_DROP,
+    LOOKUP_IP_DST_MCAST_NEXT_RPF,
+    LOOKUP_IP_DST_MCAST_N_NEXT,
+} mfib_forward_lookup_next_t;
+
+always_inline uword
+lookup_dpo_ip_dst_mcast_inline (vlib_main_t * vm,
+                                vlib_node_runtime_t * node,
+                                vlib_frame_t * from_frame,
+                                int is_v4)
+{
+    u32 n_left_from, next_index, * from, * to_next;
+
+    from = vlib_frame_vector_args (from_frame);
+    n_left_from = from_frame->n_vectors;
+
+    next_index = LOOKUP_IP_DST_MCAST_NEXT_RPF;
+
+    while (n_left_from > 0)
+    {
+        u32 n_left_to_next;
+
+        vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);
+
+        /* while (n_left_from >= 4 && n_left_to_next >= 2) */
+        /*   } */
+
+        while (n_left_from > 0 && n_left_to_next > 0)
+        {
+            u32 bi0, lkdi0, fib_index0,  next0;
+            const lookup_dpo_t * lkd0;
+            fib_node_index_t mfei0;
+            vlib_buffer_t * b0;
+
+            bi0 = from[0];
+            to_next[0] = bi0;
+            from += 1;
+            to_next += 1;
+            n_left_from -= 1;
+            n_left_to_next -= 1;
+
+            b0 = vlib_get_buffer (vm, bi0);
+
+            /* dst lookup was done by mpls lookup */
+            lkdi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
+            lkd0 = lookup_dpo_get(lkdi0);
+            fib_index0 = lkd0->lkd_fib_index;
+            next0 = LOOKUP_IP_DST_MCAST_NEXT_RPF;
+
+            if (is_v4)
+            {
+                ip4_header_t * ip0;
+
+                ip0 = vlib_buffer_get_current (b0);
+                mfei0 = ip4_mfib_table_lookup(ip4_mfib_get(fib_index0),
+                                              &ip0->src_address,
+                                              &ip0->dst_address,
+                                              64);
+                if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
+                {
+                    lookup_trace_t *tr = vlib_add_trace (vm, node,
+                                                         b0, sizeof (*tr));
+                    tr->fib_index = fib_index0;
+                    tr->lbi = mfei0;
+                    tr->addr.ip4 = ip0->dst_address;
+                }
+            }
+            else
+            {
+                ip6_header_t * ip0;
+
+                ip0 = vlib_buffer_get_current (b0);
+                mfei0 = ip6_mfib_table_lookup2(ip6_mfib_get(fib_index0),
+                                               &ip0->src_address,
+                                               &ip0->dst_address);
+                if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
+                {
+                    lookup_trace_t *tr = vlib_add_trace (vm, node,
+                                                         b0, sizeof (*tr));
+                    tr->fib_index = fib_index0;
+                    tr->lbi = mfei0;
+                    tr->addr.ip6 = ip0->dst_address;
+                }
+            }
+
+            vnet_buffer (b0)->ip.adj_index[VLIB_TX] = mfei0;
+
+            if (!(b0->flags & VNET_BUFFER_F_LOOP_COUNTER_VALID)) {
+                vnet_buffer2(b0)->loop_counter = 0;
+                b0->flags |= VNET_BUFFER_F_LOOP_COUNTER_VALID;
+            }
+
+            vnet_buffer2(b0)->loop_counter++;
+
+            if (PREDICT_FALSE(vnet_buffer2(b0)->loop_counter > MAX_LUKPS_PER_PACKET))
+                next0 = LOOKUP_IP_DST_MCAST_NEXT_DROP;
+
+            vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next,
+                                            n_left_to_next, bi0, next0);
+        }
+        vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+    return from_frame->n_vectors;
+}
+
+static uword
+lookup_ip4_dst_mcast (vlib_main_t * vm,
+                      vlib_node_runtime_t * node,
+                      vlib_frame_t * from_frame)
+{
+    return (lookup_dpo_ip_dst_mcast_inline(vm, node, from_frame, 1));
+}
+
+VLIB_REGISTER_NODE (lookup_ip4_dst_mcast_node) = {
+    .function = lookup_ip4_dst_mcast,
+    .name = "lookup-ip4-dst-mcast",
+    .vector_size = sizeof (u32),
+
+    .format_trace = format_lookup_trace,
+    .n_next_nodes = LOOKUP_IP_DST_MCAST_N_NEXT,
+    .next_nodes = {
+        [LOOKUP_IP_DST_MCAST_NEXT_DROP] = "ip4-drop",
+        [LOOKUP_IP_DST_MCAST_NEXT_RPF] = "ip4-mfib-forward-rpf",
+    },
+};
+VLIB_NODE_FUNCTION_MULTIARCH (lookup_ip4_dst_mcast_node,
+                              lookup_ip4_dst_mcast)
+
+static uword
+lookup_ip6_dst_mcast (vlib_main_t * vm,
+                      vlib_node_runtime_t * node,
+                      vlib_frame_t * from_frame)
+{
+    return (lookup_dpo_ip_dst_mcast_inline(vm, node, from_frame, 0));
+}
+
+VLIB_REGISTER_NODE (lookup_ip6_dst_mcast_node) = {
+    .function = lookup_ip6_dst_mcast,
+    .name = "lookup-ip6-dst-mcast",
+    .vector_size = sizeof (u32),
+
+    .format_trace = format_lookup_trace,
+    .n_next_nodes = LOOKUP_IP_DST_MCAST_N_NEXT,
+    .next_nodes = {
+        [LOOKUP_IP_DST_MCAST_NEXT_DROP] = "ip6-drop",
+        [LOOKUP_IP_DST_MCAST_NEXT_RPF] = "ip6-mfib-forward-rpf",
+    },
+};
+VLIB_NODE_FUNCTION_MULTIARCH (lookup_ip6_dst_mcast_node,
+                              lookup_ip6_dst_mcast)
+
 static void
 lookup_dpo_mem_show (void)
 {
@@ -1141,6 +1444,22 @@ const static char* const * const lookup_dst_nodes[DPO_PROTO_NUM] =
     [DPO_PROTO_MPLS] = lookup_dst_mpls_nodes,
 };
 
+const static char* const lookup_dst_mcast_ip4_nodes[] =
+{
+    "lookup-ip4-dst-mcast",
+    NULL,
+};
+const static char* const lookup_dst_mcast_ip6_nodes[] =
+{
+    "lookup-ip6-dst-mcast",
+    NULL,
+};
+const static char* const * const lookup_dst_mcast_nodes[DPO_PROTO_NUM] =
+{
+    [DPO_PROTO_IP4]  = lookup_dst_mcast_ip4_nodes,
+    [DPO_PROTO_IP6]  = lookup_dst_mcast_ip6_nodes,
+};
+
 const static char* const lookup_dst_from_interface_ip4_nodes[] =
 {
     "lookup-ip4-dst-itf",
@@ -1180,6 +1499,8 @@ lookup_dpo_module_init (void)
         dpo_register_new_type(&lkd_vft, lookup_src_nodes);
     lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST] =
         dpo_register_new_type(&lkd_vft, lookup_dst_nodes);
+    lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST_MCAST] =
+        dpo_register_new_type(&lkd_vft, lookup_dst_mcast_nodes);
     lookup_dpo_sub_types[LOOKUP_SUB_TYPE_DST_TABLE_FROM_INTERFACE] =
         dpo_register_new_type(&lkd_vft, lookup_dst_from_interface_nodes);
 }