NAT44: active-passive HA (VPP-1571)
[vpp.git] / src / plugins / nat / nat.c
index 8115a57..3856add 100755 (executable)
@@ -29,6 +29,8 @@
 #include <nat/nat_reass.h>
 #include <nat/nat_inlines.h>
 #include <nat/nat_affinity.h>
+#include <nat/nat_syslog.h>
+#include <nat/nat_ha.h>
 #include <vnet/fib/fib_table.h>
 #include <vnet/fib/ip4_fib.h>
 
@@ -174,7 +176,8 @@ VLIB_PLUGIN_REGISTER () = {
 /* *INDENT-ON* */
 
 void
-nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
+nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index,
+                      u8 is_ha)
 {
   snat_session_key_t key;
   clib_bihash_kv_8_8_t kv;
@@ -236,6 +239,14 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
       ed_kv.key[1] = ed_key.as_u64[1];
       if (clib_bihash_add_del_16_8 (&tsm->in2out_ed, &ed_kv, 0))
        nat_log_warn ("in2out_ed key del failed");
+
+      if (!is_ha)
+       nat_syslog_nat44_sdel (s->user_index, s->in2out.fib_index,
+                              &s->in2out.addr, s->in2out.port,
+                              &s->ext_host_nat_addr, s->ext_host_nat_port,
+                              &s->out2in.addr, s->out2in.port,
+                              &s->ext_host_addr, s->ext_host_port,
+                              s->in2out.protocol, is_twice_nat_session (s));
     }
   else
     {
@@ -245,17 +256,32 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index)
       kv.key = s->out2in.as_u64;
       if (clib_bihash_add_del_8_8 (&tsm->out2in, &kv, 0))
        nat_log_warn ("out2in key del failed");
+
+      if (!is_ha)
+       nat_syslog_nat44_apmdel (s->user_index, s->in2out.fib_index,
+                                &s->in2out.addr, s->in2out.port,
+                                &s->out2in.addr, s->out2in.port,
+                                s->in2out.protocol);
     }
 
   if (snat_is_unk_proto_session (s))
     return;
 
-  /* log NAT event */
-  snat_ipfix_logging_nat44_ses_delete (s->in2out.addr.as_u32,
-                                      s->out2in.addr.as_u32,
-                                      s->in2out.protocol,
-                                      s->in2out.port,
-                                      s->out2in.port, s->in2out.fib_index);
+  if (!is_ha)
+    {
+      /* log NAT event */
+      snat_ipfix_logging_nat44_ses_delete (thread_index,
+                                          s->in2out.addr.as_u32,
+                                          s->out2in.addr.as_u32,
+                                          s->in2out.protocol,
+                                          s->in2out.port,
+                                          s->out2in.port,
+                                          s->in2out.fib_index);
+
+      nat_ha_sdel (&s->out2in.addr, s->out2in.port, &s->ext_host_addr,
+                  s->ext_host_port, s->out2in.protocol, s->out2in.fib_index,
+                  thread_index);
+    }
 
   /* Twice NAT address and port for external host */
   if (is_twice_nat_session (s))
@@ -309,6 +335,9 @@ nat_user_get_or_create (snat_main_t * sm, ip4_address_t * addr, u32 fib_index,
       /* add user */
       if (clib_bihash_add_del_8_8 (&tsm->user_hash, &kv, 1))
        nat_log_warn ("user_hash keay add failed");
+
+      vlib_set_simple_counter (&sm->total_users, thread_index, 0,
+                              pool_elts (tsm->users));
     }
   else
     {
@@ -320,7 +349,7 @@ nat_user_get_or_create (snat_main_t * sm, ip4_address_t * addr, u32 fib_index,
 
 snat_session_t *
 nat_session_alloc_or_recycle (snat_main_t * sm, snat_user_t * u,
-                             u32 thread_index)
+                             u32 thread_index, f64 now)
 {
   snat_session_t *s;
   snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index];
@@ -351,7 +380,7 @@ nat_session_alloc_or_recycle (snat_main_t * sm, snat_user_t * u,
 
       /* Get the session */
       s = pool_elt_at_index (tsm->sessions, session_index);
-      nat_free_session_data (sm, s, thread_index);
+      nat_free_session_data (sm, s, thread_index, 0);
       if (snat_is_session_static (s))
        u->nstaticsessions--;
       else
@@ -382,8 +411,14 @@ nat_session_alloc_or_recycle (snat_main_t * sm, snat_user_t * u,
       clib_dlist_addtail (tsm->list_pool,
                          s->per_user_list_head_index,
                          per_user_translation_list_elt - tsm->list_pool);
+
+      s->user_index = u - tsm->users;
+      vlib_set_simple_counter (&sm->total_sessions, thread_index, 0,
+                              pool_elts (tsm->sessions));
     }
 
+  s->ha_last_refreshed = now;
+
   return s;
 }
 
@@ -397,64 +432,72 @@ nat_ed_session_alloc (snat_main_t * sm, snat_user_t * u, u32 thread_index,
   u32 oldest_index;
   u64 sess_timeout_time;
 
-  if ((u->nsessions + u->nstaticsessions) >= sm->max_translations_per_user)
+  if (PREDICT_FALSE (!(u->nsessions) && !(u->nstaticsessions)))
+    goto alloc_new;
+
+  oldest_index =
+    clib_dlist_remove_head (tsm->list_pool,
+                           u->sessions_per_user_list_head_index);
+  oldest_elt = pool_elt_at_index (tsm->list_pool, oldest_index);
+  s = pool_elt_at_index (tsm->sessions, oldest_elt->value);
+  sess_timeout_time = s->last_heard + (f64) nat44_session_get_timeout (sm, s);
+  if (now >= sess_timeout_time)
     {
-      oldest_index =
-       clib_dlist_remove_head (tsm->list_pool,
-                               u->sessions_per_user_list_head_index);
-      oldest_elt = pool_elt_at_index (tsm->list_pool, oldest_index);
-      s = pool_elt_at_index (tsm->sessions, oldest_elt->value);
-      sess_timeout_time =
-       s->last_heard + (f64) nat44_session_get_timeout (sm, s);
-      if (now >= sess_timeout_time)
-       {
-         clib_dlist_addtail (tsm->list_pool,
-                             u->sessions_per_user_list_head_index,
-                             oldest_index);
-         nat_free_session_data (sm, s, thread_index);
-         if (snat_is_session_static (s))
-           u->nstaticsessions--;
-         else
-           u->nsessions--;
-         s->flags = 0;
-         s->total_bytes = 0;
-         s->total_pkts = 0;
-         s->state = 0;
-         s->ext_host_addr.as_u32 = 0;
-         s->ext_host_port = 0;
-         s->ext_host_nat_addr.as_u32 = 0;
-         s->ext_host_nat_port = 0;
-       }
+      clib_dlist_addtail (tsm->list_pool,
+                         u->sessions_per_user_list_head_index, oldest_index);
+      nat_free_session_data (sm, s, thread_index, 0);
+      if (snat_is_session_static (s))
+       u->nstaticsessions--;
       else
+       u->nsessions--;
+      s->flags = 0;
+      s->total_bytes = 0;
+      s->total_pkts = 0;
+      s->state = 0;
+      s->ext_host_addr.as_u32 = 0;
+      s->ext_host_port = 0;
+      s->ext_host_nat_addr.as_u32 = 0;
+      s->ext_host_nat_port = 0;
+    }
+  else
+    {
+      clib_dlist_addhead (tsm->list_pool,
+                         u->sessions_per_user_list_head_index, oldest_index);
+      if ((u->nsessions + u->nstaticsessions) >=
+         sm->max_translations_per_user)
        {
-         clib_dlist_addhead (tsm->list_pool,
-                             u->sessions_per_user_list_head_index,
-                             oldest_index);
          nat_log_warn ("max translations per user %U", format_ip4_address,
                        &u->addr);
          snat_ipfix_logging_max_entries_per_user
-           (sm->max_translations_per_user, u->addr.as_u32);
+           (thread_index, sm->max_translations_per_user, u->addr.as_u32);
          return 0;
        }
-    }
-  else
-    {
-      pool_get (tsm->sessions, s);
-      clib_memset (s, 0, sizeof (*s));
+      else
+       {
+       alloc_new:
+         pool_get (tsm->sessions, s);
+         clib_memset (s, 0, sizeof (*s));
 
-      /* Create list elts */
-      pool_get (tsm->list_pool, per_user_translation_list_elt);
-      clib_dlist_init (tsm->list_pool,
-                      per_user_translation_list_elt - tsm->list_pool);
+         /* Create list elts */
+         pool_get (tsm->list_pool, per_user_translation_list_elt);
+         clib_dlist_init (tsm->list_pool,
+                          per_user_translation_list_elt - tsm->list_pool);
 
-      per_user_translation_list_elt->value = s - tsm->sessions;
-      s->per_user_index = per_user_translation_list_elt - tsm->list_pool;
-      s->per_user_list_head_index = u->sessions_per_user_list_head_index;
+         per_user_translation_list_elt->value = s - tsm->sessions;
+         s->per_user_index = per_user_translation_list_elt - tsm->list_pool;
+         s->per_user_list_head_index = u->sessions_per_user_list_head_index;
 
-      clib_dlist_addtail (tsm->list_pool,
-                         s->per_user_list_head_index,
-                         per_user_translation_list_elt - tsm->list_pool);
+         clib_dlist_addtail (tsm->list_pool,
+                             s->per_user_list_head_index,
+                             per_user_translation_list_elt - tsm->list_pool);
+       }
+
+      vlib_set_simple_counter (&sm->total_sessions, thread_index, 0,
+                              pool_elts (tsm->sessions));
     }
+
+  s->ha_last_refreshed = now;
+
   return s;
 }
 
@@ -558,6 +601,10 @@ is_snat_address_used_in_static_mapping (snat_main_t * sm, ip4_address_t addr)
   /* *INDENT-OFF* */
   pool_foreach (m, sm->static_mappings,
   ({
+      if (is_addr_only_static_mapping (m) ||
+          is_out2in_only_static_mapping (m) ||
+          is_identity_static_mapping (m))
+        continue;
       if (m->external_addr.as_u32 == addr.as_u32)
         return 1;
   }));
@@ -643,7 +690,7 @@ snat_add_static_mapping (ip4_address_t l_addr, ip4_address_t e_addr,
   snat_session_t *s;
   snat_static_map_resolve_t *rp, *rp_match = 0;
   nat44_lb_addr_port_t *local;
-  u8 find = 0;
+  u32 find = ~0;
 
   if (!sm->endpoint_dependent)
     {
@@ -737,13 +784,13 @@ snat_add_static_mapping (ip4_address_t l_addr, ip4_address_t e_addr,
          if (is_identity_static_mapping (m))
            {
               /* *INDENT-OFF* */
-              vec_foreach (local, m->locals)
-                {
-                  if (local->vrf_id == vrf_id)
-                    return VNET_API_ERROR_VALUE_EXIST;
-                }
+              pool_foreach (local, m->locals,
+              ({
+                if (local->vrf_id == vrf_id)
+                  return VNET_API_ERROR_VALUE_EXIST;
+              }));
               /* *INDENT-ON* */
-             vec_add2 (m->locals, local, 1);
+             pool_get (m->locals, local);
              local->vrf_id = vrf_id;
              local->fib_index =
                fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4, vrf_id,
@@ -857,7 +904,7 @@ snat_add_static_mapping (ip4_address_t l_addr, ip4_address_t e_addr,
       if (identity_nat)
        {
          m->flags |= NAT_STATIC_MAPPING_FLAG_IDENTITY_NAT;
-         vec_add2 (m->locals, local, 1);
+         pool_get (m->locals, local);
          local->vrf_id = vrf_id;
          local->fib_index = fib_index;
        }
@@ -932,7 +979,7 @@ snat_add_static_mapping (ip4_address_t l_addr, ip4_address_t e_addr,
                        continue;
 
                      nat_free_session_data (sm, s,
-                                            tsm - sm->per_thread_data);
+                                            tsm - sm->per_thread_data, 0);
                      nat44_delete_session (sm, s, tsm - sm->per_thread_data);
 
                      if (!addr_only && !sm->endpoint_dependent)
@@ -954,19 +1001,22 @@ snat_add_static_mapping (ip4_address_t l_addr, ip4_address_t e_addr,
 
       if (identity_nat)
        {
-         for (i = 0; i < vec_len (m->locals); i++)
-           {
-             if (m->locals[i].vrf_id == vrf_id)
-               {
-                 find = 1;
-                 break;
-               }
-           }
-         if (!find)
+         if (vrf_id == ~0)
+           vrf_id = sm->inside_vrf_id;
+
+          /* *INDENT-OFF* */
+          pool_foreach (local, m->locals,
+          ({
+           if (local->vrf_id == vrf_id)
+              find = local - m->locals;
+         }));
+          /* *INDENT-ON* */
+         if (find == ~0)
            return VNET_API_ERROR_NO_SUCH_ENTRY;
 
-         fib_index = m->locals[i].fib_index;
-         vec_del1 (m->locals, i);
+         local = pool_elt_at_index (m->locals, find);
+         fib_index = local->fib_index;
+         pool_put (m->locals, local);
        }
       else
        fib_index = m->fib_index;
@@ -1053,7 +1103,7 @@ snat_add_static_mapping (ip4_address_t l_addr, ip4_address_t e_addr,
                        continue;
 
                      nat_free_session_data (sm, s,
-                                            tsm - sm->per_thread_data);
+                                            tsm - sm->per_thread_data, 0);
                      nat44_delete_session (sm, s, tsm - sm->per_thread_data);
 
                      if (!addr_only && !sm->endpoint_dependent)
@@ -1064,7 +1114,7 @@ snat_add_static_mapping (ip4_address_t l_addr, ip4_address_t e_addr,
        }
 
       fib_table_unlock (fib_index, FIB_PROTOCOL_IP4, FIB_SOURCE_PLUGIN_LOW);
-      if (vec_len (m->locals))
+      if (pool_elts (m->locals))
        return 0;
 
       m_key.addr = m->external_addr;
@@ -1233,7 +1283,8 @@ nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
            }
          locals[i].prefix = (i == 0) ? locals[i].probability :
            (locals[i - 1].prefix + locals[i].probability);
-         vec_add1 (m->locals, locals[i]);
+         pool_get (m->locals, local);
+         *local = locals[i];
          if (sm->num_workers > 1)
            {
              ip4_header_t ip = {
@@ -1306,8 +1357,8 @@ nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
        }
 
       /* *INDENT-OFF* */
-      vec_foreach (local, m->locals)
-        {
+      pool_foreach (local, m->locals,
+      ({
           fib_table_unlock (local->fib_index, FIB_PROTOCOL_IP4,
                             FIB_SOURCE_PLUGIN_LOW);
           m_key.addr = local->addr;
@@ -1336,7 +1387,7 @@ nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
 
           /* Delete sessions */
           u_key.addr = local->addr;
-          u_key.fib_index = m->fib_index;
+          u_key.fib_index = local->fib_index;
           kv.key = u_key.as_u64;
           if (!clib_bihash_search_8_8 (&tsm->user_hash, &kv, &value))
             {
@@ -1361,16 +1412,16 @@ nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
                           (clib_net_to_host_u16 (s->in2out.port) != local->port))
                         continue;
 
-                      nat_free_session_data (sm, s, tsm - sm->per_thread_data);
+                      nat_free_session_data (sm, s, tsm - sm->per_thread_data, 0);
                       nat44_delete_session (sm, s, tsm - sm->per_thread_data);
                     }
                 }
             }
-        }
+      }));
       /* *INDENT-ON* */
       if (m->affinity)
        nat_affinity_flush_service (m->affinity_per_service_list_head_index);
-      vec_free (m->locals);
+      pool_free (m->locals);
       vec_free (m->tag);
       vec_free (m->workers);
 
@@ -1380,6 +1431,189 @@ nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port,
   return 0;
 }
 
+int
+nat44_lb_static_mapping_add_del_local (ip4_address_t e_addr, u16 e_port,
+                                      ip4_address_t l_addr, u16 l_port,
+                                      snat_protocol_t proto, u32 vrf_id,
+                                      u8 probability, u8 is_add)
+{
+  snat_main_t *sm = &snat_main;
+  snat_static_mapping_t *m = 0;
+  snat_session_key_t m_key;
+  clib_bihash_kv_8_8_t kv, value;
+  nat44_lb_addr_port_t *local, *prev_local, *match_local = 0;
+  snat_main_per_thread_data_t *tsm;
+  snat_user_key_t u_key;
+  snat_user_t *u;
+  snat_session_t *s;
+  dlist_elt_t *head, *elt;
+  u32 elt_index, head_index, ses_index, *locals = 0;
+  uword *bitmap = 0;
+  int i;
+
+  if (!sm->endpoint_dependent)
+    return VNET_API_ERROR_FEATURE_DISABLED;
+
+  m_key.addr = e_addr;
+  m_key.port = e_port;
+  m_key.protocol = proto;
+  m_key.fib_index = 0;
+  kv.key = m_key.as_u64;
+  if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value))
+    m = pool_elt_at_index (sm->static_mappings, value.value);
+
+  if (!m)
+    return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+  if (!is_lb_static_mapping (m))
+    return VNET_API_ERROR_INVALID_VALUE;
+
+  /* *INDENT-OFF* */
+  pool_foreach (local, m->locals,
+  ({
+    if ((local->addr.as_u32 == l_addr.as_u32) && (local->port == l_port) &&
+        (local->vrf_id == vrf_id))
+      {
+        match_local = local;
+        break;
+      }
+  }));
+  /* *INDENT-ON* */
+
+  if (is_add)
+    {
+      if (match_local)
+       return VNET_API_ERROR_VALUE_EXIST;
+
+      pool_get (m->locals, local);
+      clib_memset (local, 0, sizeof (*local));
+      local->addr.as_u32 = l_addr.as_u32;
+      local->port = l_port;
+      local->probability = probability;
+      local->vrf_id = vrf_id;
+      local->fib_index =
+       fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4, vrf_id,
+                                          FIB_SOURCE_PLUGIN_LOW);
+
+      if (!is_out2in_only_static_mapping (m))
+       {
+         m_key.addr = l_addr;
+         m_key.port = l_port;
+         m_key.fib_index = local->fib_index;
+         kv.key = m_key.as_u64;
+         kv.value = m - sm->static_mappings;
+         if (clib_bihash_add_del_8_8 (&sm->static_mapping_by_local, &kv, 1))
+           nat_log_err ("static_mapping_by_local key add failed");
+       }
+    }
+  else
+    {
+      if (!match_local)
+       return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+      if (pool_elts (m->locals) < 3)
+       return VNET_API_ERROR_UNSPECIFIED;
+
+      fib_table_unlock (match_local->fib_index, FIB_PROTOCOL_IP4,
+                       FIB_SOURCE_PLUGIN_LOW);
+
+      if (!is_out2in_only_static_mapping (m))
+       {
+         m_key.addr = l_addr;
+         m_key.port = l_port;
+         m_key.fib_index = match_local->fib_index;
+         kv.key = m_key.as_u64;
+         if (clib_bihash_add_del_8_8 (&sm->static_mapping_by_local, &kv, 0))
+           nat_log_err ("static_mapping_by_local key del failed");
+       }
+
+      if (sm->num_workers > 1)
+       {
+         ip4_header_t ip = {
+           .src_address = local->addr,
+         };
+         tsm = vec_elt_at_index (sm->per_thread_data,
+                                 sm->worker_in2out_cb (&ip, m->fib_index));
+       }
+      else
+       tsm = vec_elt_at_index (sm->per_thread_data, sm->num_workers);
+
+      /* Delete sessions */
+      u_key.addr = match_local->addr;
+      u_key.fib_index = match_local->fib_index;
+      kv.key = u_key.as_u64;
+      if (!clib_bihash_search_8_8 (&tsm->user_hash, &kv, &value))
+       {
+         u = pool_elt_at_index (tsm->users, value.value);
+         if (u->nstaticsessions)
+           {
+             head_index = u->sessions_per_user_list_head_index;
+             head = pool_elt_at_index (tsm->list_pool, head_index);
+             elt_index = head->next;
+             elt = pool_elt_at_index (tsm->list_pool, elt_index);
+             ses_index = elt->value;
+             while (ses_index != ~0)
+               {
+                 s = pool_elt_at_index (tsm->sessions, ses_index);
+                 elt = pool_elt_at_index (tsm->list_pool, elt->next);
+                 ses_index = elt->value;
+
+                 if (!(is_lb_session (s)))
+                   continue;
+
+                 if ((s->in2out.addr.as_u32 != match_local->addr.as_u32) ||
+                     (clib_net_to_host_u16 (s->in2out.port) !=
+                      match_local->port))
+                   continue;
+
+                 nat_free_session_data (sm, s, tsm - sm->per_thread_data, 0);
+                 nat44_delete_session (sm, s, tsm - sm->per_thread_data);
+               }
+           }
+       }
+
+      pool_put (m->locals, match_local);
+    }
+
+  vec_free (m->workers);
+
+  /* *INDENT-OFF* */
+  pool_foreach (local, m->locals,
+  ({
+    vec_add1 (locals, local - m->locals);
+    if (sm->num_workers > 1)
+      {
+        ip4_header_t ip;
+        ip.src_address.as_u32 = local->addr.as_u32,
+        bitmap = clib_bitmap_set (bitmap,
+                                  sm->worker_in2out_cb (&ip, local->fib_index),
+                                  1);
+      }
+  }));
+  /* *INDENT-ON* */
+
+  ASSERT (vec_len (locals) > 1);
+
+  local = pool_elt_at_index (m->locals, locals[0]);
+  local->prefix = local->probability;
+  for (i = 1; i < vec_len (locals); i++)
+    {
+      local = pool_elt_at_index (m->locals, locals[i]);
+      prev_local = pool_elt_at_index (m->locals, locals[i - 1]);
+      local->prefix = local->probability + prev_local->prefix;
+    }
+
+  /* Assign workers */
+  if (sm->num_workers > 1)
+    {
+      /* *INDENT-OFF* */
+      clib_bitmap_foreach (i, bitmap, ({ vec_add1(m->workers, i); }));
+      /* *INDENT-ON* */
+    }
+
+  return 0;
+}
+
 int
 snat_del_address (snat_main_t * sm, ip4_address_t addr, u8 delete_sm,
                  u8 twice_nat)
@@ -1442,7 +1676,7 @@ snat_del_address (snat_main_t * sm, ip4_address_t addr, u8 delete_sm,
           pool_foreach (ses, tsm->sessions, ({
             if (ses->out2in.addr.as_u32 == addr.as_u32)
               {
-                nat_free_session_data (sm, ses, tsm - sm->per_thread_data);
+                nat_free_session_data (sm, ses, tsm - sm->per_thread_data, 0);
                 vec_add1 (ses_to_be_removed, ses - tsm->sessions);
               }
           }));
@@ -1551,7 +1785,7 @@ snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
             {
               if (is_del)
                 {
-                  outside_fib->refcount--;
+                 outside_fib->refcount--;
                   if (!outside_fib->refcount)
                     vec_del1 (sm->outside_fibs, outside_fib - sm->outside_fibs);
                 }
@@ -1751,6 +1985,10 @@ snat_interface_add_del_output_feature (u32 sw_if_index,
   snat_interface_t *i;
   snat_address_t *ap;
   snat_static_mapping_t *m;
+  nat_outside_fib_t *outside_fib;
+  u32 fib_index = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
+                                                      sw_if_index);
+
 
   if (sm->deterministic ||
       (sm->static_mapping_only && !(sm->static_mapping_connection_tracking)))
@@ -1764,6 +2002,34 @@ snat_interface_add_del_output_feature (u32 sw_if_index,
   }));
   /* *INDENT-ON* */
 
+  if (!is_inside)
+    {
+      /* *INDENT-OFF* */
+      vec_foreach (outside_fib, sm->outside_fibs)
+        {
+          if (outside_fib->fib_index == fib_index)
+            {
+              if (is_del)
+                {
+                 outside_fib->refcount--;
+                  if (!outside_fib->refcount)
+                    vec_del1 (sm->outside_fibs, outside_fib - sm->outside_fibs);
+                }
+              else
+                outside_fib->refcount++;
+              goto feature_set;
+            }
+        }
+      /* *INDENT-ON* */
+      if (!is_del)
+       {
+         vec_add2 (sm->outside_fibs, outside_fib, 1);
+         outside_fib->refcount = 1;
+         outside_fib->fib_index = fib_index;
+       }
+    }
+
+feature_set:
   if (is_inside)
     {
       if (sm->endpoint_dependent)
@@ -1894,6 +2160,65 @@ snat_set_workers (uword * bitmap)
   return 0;
 }
 
+static void
+snat_update_outside_fib (u32 sw_if_index, u32 new_fib_index,
+                        u32 old_fib_index)
+{
+  snat_main_t *sm = &snat_main;
+  nat_outside_fib_t *outside_fib;
+  snat_interface_t *i;
+  u8 is_add = 1;
+
+  if (new_fib_index == old_fib_index)
+    return;
+
+  if (!vec_len (sm->outside_fibs))
+    return;
+
+  pool_foreach (i, sm->interfaces, (
+                                    {
+                                    if (i->sw_if_index == sw_if_index)
+                                    {
+                                    if (!(nat_interface_is_outside (i)))
+                                    return;}
+                                    }
+               ));
+  vec_foreach (outside_fib, sm->outside_fibs)
+  {
+    if (outside_fib->fib_index == old_fib_index)
+      {
+       outside_fib->refcount--;
+       if (!outside_fib->refcount)
+         vec_del1 (sm->outside_fibs, outside_fib - sm->outside_fibs);
+       break;
+      }
+  }
+
+  vec_foreach (outside_fib, sm->outside_fibs)
+  {
+    if (outside_fib->fib_index == new_fib_index)
+      {
+       outside_fib->refcount++;
+       is_add = 0;
+       break;
+      }
+  }
+
+  if (is_add)
+    {
+      vec_add2 (sm->outside_fibs, outside_fib, 1);
+      outside_fib->refcount = 1;
+      outside_fib->fib_index = new_fib_index;
+    }
+}
+
+static void
+snat_ip4_table_bind (ip4_main_t * im,
+                    uword opaque,
+                    u32 sw_if_index, u32 new_fib_index, u32 old_fib_index)
+{
+  snat_update_outside_fib (sw_if_index, new_fib_index, old_fib_index);
+}
 
 static void
 snat_ip4_add_del_interface_address_cb (ip4_main_t * im,
@@ -1931,7 +2256,7 @@ snat_init (vlib_main_t * vm)
   uword *bitmap = 0;
   u32 i;
   ip4_add_del_interface_address_callback_t cb4;
-  vlib_node_t *error_drop_node;
+  vlib_node_t *node;
 
   sm->vlib_main = vm;
   sm->vnet_main = vnet_get_main ();
@@ -1953,10 +2278,63 @@ snat_init (vlib_main_t * vm)
   sm->addr_and_port_alloc_alg = NAT_ADDR_AND_PORT_ALLOC_ALG_DEFAULT;
   sm->forwarding_enabled = 0;
   sm->log_class = vlib_log_register_class ("nat", 0);
-  error_drop_node = vlib_get_node_by_name (vm, (u8 *) "error-drop");
-  sm->error_node_index = error_drop_node->index;
   sm->mss_clamping = 0;
 
+  node = vlib_get_node_by_name (vm, (u8 *) "error-drop");
+  sm->error_node_index = node->index;
+
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-in2out");
+  sm->in2out_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-in2out-output");
+  sm->in2out_output_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-in2out-fast");
+  sm->in2out_fast_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-in2out-slowpath");
+  sm->in2out_slowpath_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-in2out-output-slowpath");
+  sm->in2out_slowpath_output_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-in2out-reass");
+  sm->in2out_reass_node_index = node->index;
+
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-ed-in2out");
+  sm->ed_in2out_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-ed-in2out-slowpath");
+  sm->ed_in2out_slowpath_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-ed-in2out-reass");
+  sm->ed_in2out_reass_node_index = node->index;
+
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-out2in");
+  sm->out2in_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-out2in-fast");
+  sm->out2in_fast_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-out2in-reass");
+  sm->out2in_reass_node_index = node->index;
+
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-ed-out2in");
+  sm->ed_out2in_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-ed-out2in-slowpath");
+  sm->ed_out2in_slowpath_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-ed-out2in-reass");
+  sm->ed_out2in_reass_node_index = node->index;
+
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-det-in2out");
+  sm->det_in2out_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-det-out2in");
+  sm->det_out2in_node_index = node->index;
+
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-hairpinning");
+  sm->hairpinning_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-hairpin-dst");
+  sm->hairpin_dst_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-hairpin-src");
+  sm->hairpin_src_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-ed-hairpinning");
+  sm->ed_hairpinning_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-ed-hairpin-dst");
+  sm->ed_hairpin_dst_node_index = node->index;
+  node = vlib_get_node_by_name (vm, (u8 *) "nat44-ed-hairpin-src");
+  sm->ed_hairpin_src_node_index = node->index;
+
   p = hash_get_mem (tm->thread_registrations_by_name, "workers");
   if (p)
     {
@@ -2000,6 +2378,16 @@ snat_init (vlib_main_t * vm)
 
   nat_dpo_module_init ();
 
+  /* Init counters */
+  sm->total_users.name = "total-users";
+  sm->total_users.stat_segment_name = "/nat44/total-users";
+  vlib_validate_simple_counter (&sm->total_users, 0);
+  vlib_zero_simple_counter (&sm->total_users, 0);
+  sm->total_sessions.name = "total-sessions";
+  sm->total_sessions.stat_segment_name = "/nat44/total-sessions";
+  vlib_validate_simple_counter (&sm->total_sessions, 0);
+  vlib_zero_simple_counter (&sm->total_sessions, 0);
+
   /* Init IPFIX logging */
   snat_ipfix_logging_init (vm);
 
@@ -2010,7 +2398,12 @@ snat_init (vlib_main_t * vm)
 
   dslite_init (vm);
 
-  nat66_init ();
+  nat66_init (vm);
+
+  ip4_table_bind_callback_t cbt4 = {
+    .function = snat_ip4_table_bind,
+  };
+  vec_add1 (ip4_main.table_bind_callbacks, cbt4);
 
   /* Init virtual fragmenentation reassembly */
   return nat_reass_init (vm);
@@ -2056,6 +2449,42 @@ snat_free_outside_address_and_port (snat_address_t * addresses,
     }
 }
 
+static int
+nat_set_outside_address_and_port (snat_address_t * addresses,
+                                 u32 thread_index, snat_session_key_t * k)
+{
+  snat_address_t *a = 0;
+  u32 address_index;
+  u16 port_host_byte_order = clib_net_to_host_u16 (k->port);
+
+  for (address_index = 0; address_index < vec_len (addresses);
+       address_index++)
+    {
+      if (addresses[address_index].addr.as_u32 != k->addr.as_u32)
+       continue;
+
+      a = addresses + address_index;
+      switch (k->protocol)
+       {
+#define _(N, j, n, s) \
+        case SNAT_PROTOCOL_##N: \
+          if (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, port_host_byte_order)) \
+            return VNET_API_ERROR_INSTANCE_IN_USE; \
+          clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, port_host_byte_order, 1); \
+          a->busy_##n##_ports_per_thread[thread_index]++; \
+          a->busy_##n##_ports++; \
+          return 0;
+         foreach_snat_protocol
+#undef _
+       default:
+         nat_log_info ("unknown protocol");
+         return 1;
+       }
+    }
+
+  return VNET_API_ERROR_NO_SUCH_ENTRY;
+}
+
 int
 snat_static_mapping_match (snat_main_t * sm,
                           snat_session_key_t match,
@@ -2070,8 +2499,9 @@ snat_static_mapping_match (snat_main_t * sm,
   snat_static_mapping_t *m;
   snat_session_key_t m_key;
   clib_bihash_8_8_t *mapping_hash = &sm->static_mapping_by_local;
-  u32 rand, lo = 0, hi, mid;
+  u32 rand, lo = 0, hi, mid, *tmp = 0, i;
   u8 backend_index;
+  nat44_lb_addr_port_t *local;
 
   m_key.fib_index = match.fib_index;
   if (by_external)
@@ -2111,42 +2541,52 @@ snat_static_mapping_match (snat_main_t * sm,
                                              &backend_index))
                goto get_local;
 
-             mapping->addr = m->locals[backend_index].addr;
-             mapping->port =
-               clib_host_to_net_u16 (m->locals[backend_index].port);
-             mapping->fib_index = m->locals[backend_index].fib_index;
+             local = pool_elt_at_index (m->locals, backend_index);
+             mapping->addr = local->addr;
+             mapping->port = clib_host_to_net_u16 (local->port);
+             mapping->fib_index = local->fib_index;
              goto end;
            }
        get_local:
-         hi = vec_len (m->locals) - 1;
-         rand = 1 + (random_u32 (&sm->random_seed) % m->locals[hi].prefix);
+          /* *INDENT-OFF* */
+          pool_foreach_index (i, m->locals,
+          ({
+            vec_add1 (tmp, i);
+          }));
+          /* *INDENT-ON* */
+         hi = vec_len (tmp) - 1;
+         local = pool_elt_at_index (m->locals, tmp[hi]);
+         rand = 1 + (random_u32 (&sm->random_seed) % local->prefix);
          while (lo < hi)
            {
              mid = ((hi - lo) >> 1) + lo;
-             (rand > m->locals[mid].prefix) ? (lo = mid + 1) : (hi = mid);
+             local = pool_elt_at_index (m->locals, tmp[mid]);
+             (rand > local->prefix) ? (lo = mid + 1) : (hi = mid);
            }
-         if (!(m->locals[lo].prefix >= rand))
+         local = pool_elt_at_index (m->locals, tmp[lo]);
+         if (!(local->prefix >= rand))
            return 1;
          if (PREDICT_FALSE (sm->num_workers > 1))
            {
              ip4_header_t ip = {
-               .src_address = m->locals[lo].addr,
+               .src_address = local->addr,
              };
              if (sm->worker_in2out_cb (&ip, m->fib_index) !=
                  vlib_get_thread_index ())
                goto get_local;
            }
-         mapping->addr = m->locals[lo].addr;
-         mapping->port = clib_host_to_net_u16 (m->locals[lo].port);
-         mapping->fib_index = m->locals[lo].fib_index;
+         mapping->addr = local->addr;
+         mapping->port = clib_host_to_net_u16 (local->port);
+         mapping->fib_index = local->fib_index;
          if (m->affinity)
            {
              if (nat_affinity_create_and_lock (ext_host_addr[0], match.addr,
                                                match.protocol, match.port,
-                                               lo, m->affinity,
+                                               tmp[lo], m->affinity,
                                                m->affinity_per_service_list_head_index))
                nat_log_info ("create affinity record failed");
            }
+         vec_free (tmp);
        }
       else
        {
@@ -2287,7 +2727,7 @@ nat_alloc_addr_and_port_default (snat_address_t * addresses,
     }
 
   /* Totally out of translations to use... */
-  snat_ipfix_logging_addresses_exhausted (0);
+  snat_ipfix_logging_addresses_exhausted (thread_index, 0);
   return 1;
 }
 
@@ -2337,7 +2777,7 @@ nat_alloc_addr_and_port_mape (snat_address_t * addresses,
 
 exhausted:
   /* Totally out of translations to use... */
-  snat_ipfix_logging_addresses_exhausted (0);
+  snat_ipfix_logging_addresses_exhausted (thread_index, 0);
   return 1;
 }
 
@@ -2385,7 +2825,7 @@ nat_alloc_addr_and_port_range (snat_address_t * addresses,
 
 exhausted:
   /* Totally out of translations to use... */
-  snat_ipfix_logging_addresses_exhausted (0);
+  snat_ipfix_logging_addresses_exhausted (thread_index, 0);
   return 1;
 }
 
@@ -2527,20 +2967,47 @@ snat_get_worker_out2in_cb (ip4_header_t * ip0, u32 rx_fib_index0)
       if (PREDICT_FALSE (nat_reass_is_drop_frag (0)))
        return vlib_get_thread_index ();
 
-      if (PREDICT_TRUE (!ip4_is_first_fragment (ip0)))
-       {
-         nat_reass_ip4_t *reass;
+      nat_reass_ip4_t *reass;
+      reass = nat_ip4_reass_find (ip0->src_address, ip0->dst_address,
+                                 ip0->fragment_id, ip0->protocol);
 
-         reass = nat_ip4_reass_find (ip0->src_address, ip0->dst_address,
-                                     ip0->fragment_id, ip0->protocol);
+      if (reass && (reass->thread_index != (u32) ~ 0))
+       return reass->thread_index;
 
-         if (reass && (reass->thread_index != (u32) ~ 0))
-           return reass->thread_index;
-         else
-           return vlib_get_thread_index ();
+      if (ip4_is_first_fragment (ip0))
+       {
+         reass =
+           nat_ip4_reass_create (ip0->src_address, ip0->dst_address,
+                                 ip0->fragment_id, ip0->protocol);
+         if (!reass)
+           goto no_reass;
+
+         if (PREDICT_FALSE (pool_elts (sm->static_mappings)))
+           {
+             m_key.addr = ip0->dst_address;
+             m_key.port = clib_net_to_host_u16 (port);
+             m_key.protocol = proto;
+             m_key.fib_index = rx_fib_index0;
+             kv.key = m_key.as_u64;
+             if (!clib_bihash_search_8_8
+                 (&sm->static_mapping_by_external, &kv, &value))
+               {
+                 m = pool_elt_at_index (sm->static_mappings, value.value);
+                 reass->thread_index = m->workers[0];
+                 return reass->thread_index;
+               }
+           }
+         reass->thread_index = sm->first_worker_index;
+         reass->thread_index +=
+           sm->workers[(clib_net_to_host_u16 (port) - 1024) /
+                       sm->port_per_thread];
+         return reass->thread_index;
        }
+      else
+       return vlib_get_thread_index ();
     }
 
+no_reass:
   /* unknown protocol */
   if (PREDICT_FALSE (proto == ~0))
     {
@@ -2692,6 +3159,334 @@ nat44_ed_get_worker_out2in_cb (ip4_header_t * ip, u32 rx_fib_index)
   return next_worker_index;
 }
 
+void
+nat_ha_sadd_cb (ip4_address_t * in_addr, u16 in_port,
+               ip4_address_t * out_addr, u16 out_port,
+               ip4_address_t * eh_addr, u16 eh_port,
+               ip4_address_t * ehn_addr, u16 ehn_port, u8 proto,
+               u32 fib_index, u16 flags, u32 thread_index)
+{
+  snat_main_t *sm = &snat_main;
+  snat_session_key_t key;
+  snat_user_t *u;
+  snat_session_t *s;
+  clib_bihash_kv_8_8_t kv;
+  f64 now = vlib_time_now (sm->vlib_main);
+  nat_outside_fib_t *outside_fib;
+  fib_node_index_t fei = FIB_NODE_INDEX_INVALID;
+  snat_main_per_thread_data_t *tsm;
+  fib_prefix_t pfx = {
+    .fp_proto = FIB_PROTOCOL_IP4,
+    .fp_len = 32,
+    .fp_addr = {
+               .ip4.as_u32 = eh_addr->as_u32,
+               },
+  };
+
+  tsm = vec_elt_at_index (sm->per_thread_data, thread_index);
+
+  key.addr.as_u32 = out_addr->as_u32;
+  key.port = out_port;
+  key.protocol = proto;
+
+  if (!(flags & SNAT_SESSION_FLAG_STATIC_MAPPING))
+    {
+      if (nat_set_outside_address_and_port
+         (sm->addresses, thread_index, &key))
+       return;
+    }
+
+  u = nat_user_get_or_create (sm, in_addr, fib_index, thread_index);
+  if (!u)
+    return;
+
+  s = nat_session_alloc_or_recycle (sm, u, thread_index, now);
+  if (!s)
+    return;
+
+  s->last_heard = now;
+  s->flags = flags;
+  s->ext_host_addr.as_u32 = eh_addr->as_u32;
+  s->ext_host_port = eh_port;
+  user_session_increment (sm, u, snat_is_session_static (s));
+  switch (vec_len (sm->outside_fibs))
+    {
+    case 0:
+      key.fib_index = sm->outside_fib_index;
+      break;
+    case 1:
+      key.fib_index = sm->outside_fibs[0].fib_index;
+      break;
+    default:
+      /* *INDENT-OFF* */
+      vec_foreach (outside_fib, sm->outside_fibs)
+        {
+          fei = fib_table_lookup (outside_fib->fib_index, &pfx);
+          if (FIB_NODE_INDEX_INVALID != fei)
+            {
+              if (fib_entry_get_resolving_interface (fei) != ~0)
+                {
+                  key.fib_index = outside_fib->fib_index;
+                  break;
+                }
+            }
+        }
+      /* *INDENT-ON* */
+      break;
+    }
+  s->out2in = key;
+  kv.key = key.as_u64;
+  kv.value = s - tsm->sessions;
+  if (clib_bihash_add_del_8_8 (&tsm->out2in, &kv, 1))
+    nat_log_warn ("out2in key add failed");
+
+  key.addr.as_u32 = in_addr->as_u32;
+  key.port = in_port;
+  key.fib_index = fib_index;
+  s->in2out = key;
+  kv.key = key.as_u64;
+  if (clib_bihash_add_del_8_8 (&tsm->in2out, &kv, 1))
+    nat_log_warn ("in2out key add failed");
+}
+
+void
+nat_ha_sdel_cb (ip4_address_t * out_addr, u16 out_port,
+               ip4_address_t * eh_addr, u16 eh_port, u8 proto, u32 fib_index,
+               u32 ti)
+{
+  snat_main_t *sm = &snat_main;
+  snat_session_key_t key;
+  clib_bihash_kv_8_8_t kv, value;
+  u32 thread_index;
+  snat_session_t *s;
+  snat_main_per_thread_data_t *tsm;
+
+  if (sm->num_workers > 1)
+    thread_index =
+      sm->first_worker_index +
+      (sm->workers[(clib_net_to_host_u16 (out_port) -
+                   1024) / sm->port_per_thread]);
+  else
+    thread_index = sm->num_workers;
+  tsm = vec_elt_at_index (sm->per_thread_data, thread_index);
+
+  key.addr.as_u32 = out_addr->as_u32;
+  key.port = out_port;
+  key.protocol = proto;
+  key.fib_index = fib_index;
+  kv.key = key.as_u64;
+  if (clib_bihash_search_8_8 (&tsm->out2in, &kv, &value))
+    return;
+
+  s = pool_elt_at_index (tsm->sessions, value.value);
+  nat_free_session_data (sm, s, thread_index, 1);
+  nat44_delete_session (sm, s, thread_index);
+}
+
+void
+nat_ha_sref_cb (ip4_address_t * out_addr, u16 out_port,
+               ip4_address_t * eh_addr, u16 eh_port, u8 proto, u32 fib_index,
+               u32 total_pkts, u64 total_bytes, u32 thread_index)
+{
+  snat_main_t *sm = &snat_main;
+  snat_session_key_t key;
+  clib_bihash_kv_8_8_t kv, value;
+  snat_session_t *s;
+  snat_main_per_thread_data_t *tsm;
+
+  tsm = vec_elt_at_index (sm->per_thread_data, thread_index);
+
+  key.addr.as_u32 = out_addr->as_u32;
+  key.port = out_port;
+  key.protocol = proto;
+  key.fib_index = fib_index;
+  kv.key = key.as_u64;
+  if (clib_bihash_search_8_8 (&tsm->out2in, &kv, &value))
+    return;
+
+  s = pool_elt_at_index (tsm->sessions, value.value);
+  s->total_pkts = total_pkts;
+  s->total_bytes = total_bytes;
+}
+
+void
+nat_ha_sadd_ed_cb (ip4_address_t * in_addr, u16 in_port,
+                  ip4_address_t * out_addr, u16 out_port,
+                  ip4_address_t * eh_addr, u16 eh_port,
+                  ip4_address_t * ehn_addr, u16 ehn_port, u8 proto,
+                  u32 fib_index, u16 flags, u32 thread_index)
+{
+  snat_main_t *sm = &snat_main;
+  snat_session_key_t key;
+  snat_user_t *u;
+  snat_session_t *s;
+  clib_bihash_kv_16_8_t kv;
+  f64 now = vlib_time_now (sm->vlib_main);
+  nat_outside_fib_t *outside_fib;
+  fib_node_index_t fei = FIB_NODE_INDEX_INVALID;
+  snat_main_per_thread_data_t *tsm;
+  fib_prefix_t pfx = {
+    .fp_proto = FIB_PROTOCOL_IP4,
+    .fp_len = 32,
+    .fp_addr = {
+               .ip4.as_u32 = eh_addr->as_u32,
+               },
+  };
+
+  tsm = vec_elt_at_index (sm->per_thread_data, thread_index);
+
+  key.addr.as_u32 = out_addr->as_u32;
+  key.port = out_port;
+  key.protocol = proto;
+
+  if (!(flags & SNAT_SESSION_FLAG_STATIC_MAPPING))
+    {
+      if (nat_set_outside_address_and_port
+         (sm->addresses, thread_index, &key))
+       return;
+    }
+
+  key.addr.as_u32 = ehn_addr->as_u32;
+  key.port = ehn_port;
+  if (flags & SNAT_SESSION_FLAG_TWICE_NAT)
+    {
+      if (nat_set_outside_address_and_port
+         (sm->twice_nat_addresses, thread_index, &key))
+       return;
+    }
+
+  u = nat_user_get_or_create (sm, in_addr, fib_index, thread_index);
+  if (!u)
+    return;
+
+  s = nat_ed_session_alloc (sm, u, thread_index, now);
+  if (!s)
+    return;
+
+  s->last_heard = now;
+  s->flags = flags;
+  s->ext_host_nat_addr.as_u32 = s->ext_host_addr.as_u32 = eh_addr->as_u32;
+  s->ext_host_nat_port = s->ext_host_port = eh_port;
+  if (is_twice_nat_session (s))
+    {
+      s->ext_host_nat_addr.as_u32 = ehn_addr->as_u32;
+      s->ext_host_nat_port = ehn_port;
+    }
+  user_session_increment (sm, u, snat_is_session_static (s));
+  switch (vec_len (sm->outside_fibs))
+    {
+    case 0:
+      key.fib_index = sm->outside_fib_index;
+      break;
+    case 1:
+      key.fib_index = sm->outside_fibs[0].fib_index;
+      break;
+    default:
+      /* *INDENT-OFF* */
+      vec_foreach (outside_fib, sm->outside_fibs)
+        {
+          fei = fib_table_lookup (outside_fib->fib_index, &pfx);
+          if (FIB_NODE_INDEX_INVALID != fei)
+            {
+              if (fib_entry_get_resolving_interface (fei) != ~0)
+                {
+                  key.fib_index = outside_fib->fib_index;
+                  break;
+                }
+            }
+        }
+      /* *INDENT-ON* */
+      break;
+    }
+  key.addr.as_u32 = out_addr->as_u32;
+  key.port = out_port;
+  s->out2in = key;
+  kv.value = s - tsm->sessions;
+
+  key.addr.as_u32 = in_addr->as_u32;
+  key.port = in_port;
+  key.fib_index = fib_index;
+  s->in2out = key;
+
+  make_ed_kv (&kv, in_addr, &s->ext_host_nat_addr,
+             snat_proto_to_ip_proto (proto), fib_index, in_port,
+             s->ext_host_nat_port);
+  if (clib_bihash_add_del_16_8 (&tsm->in2out_ed, &kv, 1))
+    nat_log_warn ("in2out key add failed");
+
+  make_ed_kv (&kv, out_addr, eh_addr, snat_proto_to_ip_proto (proto),
+             s->out2in.fib_index, out_port, eh_port);
+  if (clib_bihash_add_del_16_8 (&tsm->out2in_ed, &kv, 1))
+    nat_log_warn ("out2in key add failed");
+}
+
+void
+nat_ha_sdel_ed_cb (ip4_address_t * out_addr, u16 out_port,
+                  ip4_address_t * eh_addr, u16 eh_port, u8 proto,
+                  u32 fib_index, u32 ti)
+{
+  snat_main_t *sm = &snat_main;
+  nat_ed_ses_key_t key;
+  clib_bihash_kv_16_8_t kv, value;
+  u32 thread_index;
+  snat_session_t *s;
+  snat_main_per_thread_data_t *tsm;
+
+  if (sm->num_workers > 1)
+    thread_index =
+      sm->first_worker_index +
+      (sm->workers[(clib_net_to_host_u16 (out_port) -
+                   1024) / sm->port_per_thread]);
+  else
+    thread_index = sm->num_workers;
+  tsm = vec_elt_at_index (sm->per_thread_data, thread_index);
+
+  key.l_addr.as_u32 = out_addr->as_u32;
+  key.l_port = out_port;
+  key.r_addr.as_u32 = eh_addr->as_u32;
+  key.r_port = eh_port;
+  key.proto = proto;
+  key.fib_index = fib_index;
+  kv.key[0] = key.as_u64[0];
+  kv.key[1] = key.as_u64[1];
+  if (clib_bihash_search_16_8 (&tsm->out2in_ed, &kv, &value))
+    return;
+
+  s = pool_elt_at_index (tsm->sessions, value.value);
+  nat_free_session_data (sm, s, thread_index, 1);
+  nat44_delete_session (sm, s, thread_index);
+}
+
+void
+nat_ha_sref_ed_cb (ip4_address_t * out_addr, u16 out_port,
+                  ip4_address_t * eh_addr, u16 eh_port, u8 proto,
+                  u32 fib_index, u32 total_pkts, u64 total_bytes,
+                  u32 thread_index)
+{
+  snat_main_t *sm = &snat_main;
+  nat_ed_ses_key_t key;
+  clib_bihash_kv_16_8_t kv, value;
+  snat_session_t *s;
+  snat_main_per_thread_data_t *tsm;
+
+  tsm = vec_elt_at_index (sm->per_thread_data, thread_index);
+
+  key.l_addr.as_u32 = out_addr->as_u32;
+  key.l_port = out_port;
+  key.r_addr.as_u32 = eh_addr->as_u32;
+  key.r_port = eh_port;
+  key.proto = proto;
+  key.fib_index = fib_index;
+  kv.key[0] = key.as_u64[0];
+  kv.key[1] = key.as_u64[1];
+  if (clib_bihash_search_16_8 (&tsm->out2in_ed, &kv, &value))
+    return;
+
+  s = pool_elt_at_index (tsm->sessions, value.value);
+  s->total_pkts = total_pkts;
+  s->total_bytes = total_bytes;
+}
+
 static clib_error_t *
 snat_config (vlib_main_t * vm, unformat_input_t * input)
 {
@@ -2829,6 +3624,8 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
          sm->icmp_match_in2out_cb = icmp_match_in2out_ed;
          sm->icmp_match_out2in_cb = icmp_match_out2in_ed;
          nat_affinity_init (vm);
+         nat_ha_init (vm, nat_ha_sadd_ed_cb, nat_ha_sdel_ed_cb,
+                      nat_ha_sref_ed_cb);
        }
       else
        {
@@ -2839,6 +3636,7 @@ snat_config (vlib_main_t * vm, unformat_input_t * input)
          sm->out2in_node_index = snat_out2in_node.index;
          sm->icmp_match_in2out_cb = icmp_match_in2out_slow;
          sm->icmp_match_out2in_cb = icmp_match_out2in_slow;
+         nat_ha_init (vm, nat_ha_sadd_cb, nat_ha_sdel_cb, nat_ha_sref_cb);
        }
       if (!static_mapping_only ||
          (static_mapping_only && static_mapping_connection_tracking))
@@ -3159,7 +3957,7 @@ nat44_del_session (snat_main_t * sm, ip4_address_t * addr, u16 port,
        return VNET_API_ERROR_UNSPECIFIED;
 
       s = pool_elt_at_index (tsm->sessions, value.value);
-      nat_free_session_data (sm, s, tsm - sm->per_thread_data);
+      nat_free_session_data (sm, s, tsm - sm->per_thread_data, 0);
       nat44_delete_session (sm, s, tsm - sm->per_thread_data);
       return 0;
     }
@@ -3206,7 +4004,7 @@ nat44_del_ed_session (snat_main_t * sm, ip4_address_t * addr, u16 port,
   if (pool_is_free_index (tsm->sessions, value.value))
     return VNET_API_ERROR_UNSPECIFIED;
   s = pool_elt_at_index (tsm->sessions, value.value);
-  nat_free_session_data (sm, s, tsm - sm->per_thread_data);
+  nat_free_session_data (sm, s, tsm - sm->per_thread_data, 0);
   nat44_delete_session (sm, s, tsm - sm->per_thread_data);
   return 0;
 }