NAT64: Fallback to 3-tuple key for non TCP/UDP sessions (VPP-884)

Change-Id: I4cafc8291725feb499355092bd429433e649b5b2
Signed-off-by: Matus Fabian <matfabia@cisco.com>
diff --git a/src/plugins/snat/nat64.c b/src/plugins/snat/nat64.c
index 47809b0..bd915b5 100644
--- a/src/plugins/snat/nat64.c
+++ b/src/plugins/snat/nat64.c
@@ -125,7 +125,9 @@
       clib_bitmap_free (a->busy_##n##_port_bitmap);
       foreach_snat_protocol
 #undef _
-	vec_del1 (nm->addr_pool, i);
+	/* Delete sessions using address */
+	nat64_db_free_out_addr (&nm->db, &a->addr);
+      vec_del1 (nm->addr_pool, i);
     }
 
   /* Add/del external address to FIB */
@@ -362,7 +364,7 @@
   addr.as_u64[1] = in_addr->as_u64[1];
   bibe =
     nat64_db_bib_entry_find (&nm->db, &addr, clib_host_to_net_u16 (in_port),
-			     p, fib_index, 1);
+			     proto, fib_index, 1);
 
   if (is_add)
     {
@@ -389,8 +391,11 @@
 	      foreach_snat_protocol
 #undef _
 	    default:
-	      clib_warning ("unknown protocol");
-	      return VNET_API_ERROR_INVALID_VALUE_2;
+	      memset (&addr, 0, sizeof (addr));
+	      addr.ip4.as_u32 = out_addr->as_u32;
+	      if (nat64_db_bib_entry_find
+		  (&nm->db, &addr, 0, proto, fib_index, 0))
+		return VNET_API_ERROR_INVALID_VALUE;
 	    }
 	  break;
 	}
@@ -398,7 +403,7 @@
 	nat64_db_bib_entry_create (&nm->db, in_addr, out_addr,
 				   clib_host_to_net_u16 (in_port),
 				   clib_host_to_net_u16 (out_port), fib_index,
-				   p, 1);
+				   proto, 1);
       if (!bibe)
 	return VNET_API_ERROR_UNSPECIFIED;
     }
@@ -511,7 +516,7 @@
   nat64_main_t *nm = &nat64_main;
   u32 now = (u32) vlib_time_now (vm);
 
-  switch (ste->proto)
+  switch (ip_proto_to_snat_proto (ste->proto))
     {
     case SNAT_PROTOCOL_ICMP:
       ste->expire = now + nm->icmp_timeout;
@@ -539,6 +544,7 @@
       ste->expire = now + nm->udp_timeout;
       return;
     default:
+      ste->expire = now + nm->udp_timeout;
       return;
     }
 }
@@ -808,9 +814,6 @@
       clib_warning ("invalid prefix length");
       break;
     }
-
-  clib_warning ("%U %U plen %u", format_ip6_address, ip6, format_ip4_address,
-		ip4, plen);
 }
 
 /**
diff --git a/src/plugins/snat/nat64_cli.c b/src/plugins/snat/nat64_cli.c
index 32f671d..d48cb72 100644
--- a/src/plugins/snat/nat64_cli.c
+++ b/src/plugins/snat/nat64_cli.c
@@ -303,7 +303,7 @@
   ip4_address_t out_addr;
   u16 in_port = 0;
   u16 out_port = 0;
-  u32 vrf_id = 0;
+  u32 vrf_id = 0, protocol;
   snat_protocol_t proto = 0;
   u8 p = 0;
   int rv;
@@ -327,6 +327,11 @@
 	;
       else if (unformat (line_input, "%U", unformat_snat_protocol, &proto))
 	;
+      else
+	if (unformat
+	    (line_input, "%U %U %u", unformat_ip6_address, &in_addr,
+	     unformat_ip4_address, &out_addr, &protocol))
+	p = (u8) protocol;
       else if (unformat (line_input, "del"))
 	is_add = 0;
       else
@@ -337,19 +342,24 @@
 	}
     }
 
-  if (!in_port)
+  if (!p)
     {
-      error = clib_error_return (0, "inside port and address  must be set");
-      goto done;
-    }
+      if (!in_port)
+	{
+	  error =
+	    clib_error_return (0, "inside port and address  must be set");
+	  goto done;
+	}
 
-  if (!out_port)
-    {
-      error = clib_error_return (0, "outside port and address  must be set");
-      goto done;
-    }
+      if (!out_port)
+	{
+	  error =
+	    clib_error_return (0, "outside port and address  must be set");
+	  goto done;
+	}
 
-  p = snat_proto_to_ip_proto (proto);
+      p = snat_proto_to_ip_proto (proto);
+    }
 
   rv =
     nat64_add_del_static_bib_entry (&in_addr, &out_addr, in_port, out_port, p,
@@ -391,12 +401,27 @@
   if (!fib)
     return -1;
 
-  vlib_cli_output (vm, " %U %u %U %u %U vrf %u %s %u sessions",
-		   format_ip6_address, &bibe->in_addr,
-		   clib_net_to_host_u16 (bibe->in_port), format_ip4_address,
-		   &bibe->out_addr, clib_net_to_host_u16 (bibe->out_port),
-		   format_snat_protocol, bibe->proto, fib->ft_table_id,
-		   bibe->is_static ? "static" : "dynamic", bibe->ses_num);
+  switch (bibe->proto)
+    {
+    case IP_PROTOCOL_ICMP:
+    case IP_PROTOCOL_TCP:
+    case IP_PROTOCOL_UDP:
+      vlib_cli_output (vm, " %U %u %U %u protocol %U vrf %u %s %u sessions",
+		       format_ip6_address, &bibe->in_addr,
+		       clib_net_to_host_u16 (bibe->in_port),
+		       format_ip4_address, &bibe->out_addr,
+		       clib_net_to_host_u16 (bibe->out_port),
+		       format_snat_protocol,
+		       ip_proto_to_snat_proto (bibe->proto), fib->ft_table_id,
+		       bibe->is_static ? "static" : "dynamic", bibe->ses_num);
+      break;
+    default:
+      vlib_cli_output (vm, " %U %U protocol %u vrf %u %s %u sessions",
+		       format_ip6_address, &bibe->in_addr,
+		       format_ip4_address, &bibe->out_addr,
+		       bibe->proto, fib->ft_table_id,
+		       bibe->is_static ? "static" : "dynamic", bibe->ses_num);
+    }
   return 0;
 }
 
@@ -407,7 +432,8 @@
   nat64_main_t *nm = &nat64_main;
   unformat_input_t _line_input, *line_input = &_line_input;
   clib_error_t *error = 0;
-  snat_protocol_t proto = 0;
+  u32 proto = ~0;
+  u8 p = 0;
 
   if (nm->is_disabled)
     return clib_error_return (0,
@@ -417,6 +443,8 @@
     return 0;
 
   if (unformat (line_input, "%U", unformat_snat_protocol, &proto))
+    p = snat_proto_to_ip_proto (proto);
+  else if (unformat (line_input, "unknown"))
     ;
   else
     {
@@ -426,7 +454,7 @@
     }
 
   vlib_cli_output (vm, "NAT64 %U BIB:", format_snat_protocol, proto);
-  nat64_db_bib_walk (&nm->db, proto, nat64_cli_bib_walk, vm);
+  nat64_db_bib_walk (&nm->db, p, nat64_cli_bib_walk, vm);
 
 done:
   unformat_free (line_input);
@@ -563,26 +591,36 @@
 
   u32 vrf_id = fib->ft_table_id;
 
-  if (ste->proto == SNAT_PROTOCOL_ICMP)
-    vlib_cli_output (vm, " %U %U %u %U %U %u %U vrf %u",
+  if (ste->proto == IP_PROTOCOL_ICMP)
+    vlib_cli_output (vm, " %U %U %u %U %U %u protocol %U vrf %u",
 		     format_ip6_address, &bibe->in_addr,
 		     format_ip6_address, &ste->in_r_addr,
 		     clib_net_to_host_u16 (bibe->in_port),
 		     format_ip4_address, &bibe->out_addr,
 		     format_ip4_address, &ste->out_r_addr,
 		     clib_net_to_host_u16 (bibe->out_port),
-		     format_snat_protocol, bibe->proto, vrf_id);
+		     format_snat_protocol,
+		     ip_proto_to_snat_proto (bibe->proto), vrf_id);
+  else if (ste->proto == IP_PROTOCOL_TCP || ste->proto == IP_PROTOCOL_UDP)
+    vlib_cli_output (vm, " %U %u %U %u %U %u %U %u protcol %U vrf %u",
+		     format_ip6_address, &bibe->in_addr,
+		     clib_net_to_host_u16 (bibe->in_port),
+		     format_ip6_address, &ste->in_r_addr,
+		     clib_net_to_host_u16 (ste->r_port),
+		     format_ip4_address, &bibe->out_addr,
+		     clib_net_to_host_u16 (bibe->out_port),
+		     format_ip4_address, &ste->out_r_addr,
+		     clib_net_to_host_u16 (ste->r_port),
+		     format_snat_protocol,
+		     ip_proto_to_snat_proto (bibe->proto), vrf_id);
   else
-    vlib_cli_output (vm, " %U %u %U %u %U %u %U %u %U vrf %u",
+    vlib_cli_output (vm, " %U %U %U %U protocol %u vrf %u",
 		     format_ip6_address, &bibe->in_addr,
-		     clib_net_to_host_u16 (bibe->in_port),
 		     format_ip6_address, &ste->in_r_addr,
-		     clib_net_to_host_u16 (ste->r_port),
 		     format_ip4_address, &bibe->out_addr,
-		     clib_net_to_host_u16 (bibe->out_port),
 		     format_ip4_address, &ste->out_r_addr,
-		     clib_net_to_host_u16 (ste->r_port),
-		     format_snat_protocol, bibe->proto, vrf_id);
+		     bibe->proto, vrf_id);
+
   return 0;
 }
 
@@ -593,7 +631,8 @@
   nat64_main_t *nm = &nat64_main;
   unformat_input_t _line_input, *line_input = &_line_input;
   clib_error_t *error = 0;
-  snat_protocol_t proto = 0;
+  u32 proto = ~0;
+  u8 p = 0;
 
   if (nm->is_disabled)
     return clib_error_return (0,
@@ -603,6 +642,8 @@
     return 0;
 
   if (unformat (line_input, "%U", unformat_snat_protocol, &proto))
+    p = snat_proto_to_ip_proto (proto);
+  else if (unformat (line_input, "unknown"))
     ;
   else
     {
@@ -613,7 +654,7 @@
 
   vlib_cli_output (vm, "NAT64 %U session table:", format_snat_protocol,
 		   proto);
-  nat64_db_st_walk (&nm->db, proto, nat64_cli_st_walk, vm);
+  nat64_db_st_walk (&nm->db, p, nat64_cli_st_walk, vm);
 
 done:
   unformat_free (line_input);
@@ -819,7 +860,7 @@
 ?*/
 VLIB_CLI_COMMAND (show_nat64_bib_command, static) = {
   .path = "show nat64 bib",
-  .short_help = "show nat64 bib tcp|udp|icmp",
+  .short_help = "show nat64 bib tcp|udp|icmp|unknown",
   .function = nat64_show_bib_command_fn,
 };
 
@@ -883,7 +924,7 @@
 ?*/
 VLIB_CLI_COMMAND (show_nat64_st_command, static) = {
   .path = "show nat64 session table",
-  .short_help = "show nat64 session table tcp|udp|icmp",
+  .short_help = "show nat64 session table tcp|udp|icmp|unknown",
   .function = nat64_show_st_command_fn,
 };
 
diff --git a/src/plugins/snat/nat64_db.c b/src/plugins/snat/nat64_db.c
index d15761d..b6e199c 100644
--- a/src/plugins/snat/nat64_db.c
+++ b/src/plugins/snat/nat64_db.c
@@ -44,7 +44,7 @@
 nat64_db_bib_entry_t *
 nat64_db_bib_entry_create (nat64_db_t * db, ip6_address_t * in_addr,
 			   ip4_address_t * out_addr, u16 in_port,
-			   u16 out_port, u32 fib_index, snat_protocol_t proto,
+			   u16 out_port, u32 fib_index, u8 proto,
 			   u8 is_static)
 {
   nat64_db_bib_entry_t *bibe;
@@ -52,7 +52,7 @@
   clib_bihash_kv_24_8_t kv;
 
   /* create pool entry */
-  switch (proto)
+  switch (ip_proto_to_snat_proto (proto))
     {
 /* *INDENT-OFF* */
 #define _(N, i, n, s) \
@@ -64,8 +64,9 @@
 #undef _
 /* *INDENT-ON* */
     default:
-      clib_warning ("unknown protocol %u", proto);
-      return 0;
+      pool_get (db->bib._unk_proto_bib, bibe);
+      kv.value = bibe - db->bib._unk_proto_bib;
+      break;
     }
   memset (bibe, 0, sizeof (*bibe));
   bibe->in_addr.as_u64[0] = in_addr->as_u64[0];
@@ -110,7 +111,7 @@
   u32 *ste_to_be_free = 0, *ste_index, bibe_index;
   nat64_db_st_entry_t *st, *ste;
 
-  switch (bibe->proto)
+  switch (ip_proto_to_snat_proto (bibe->proto))
     {
 /* *INDENT-OFF* */
 #define _(N, i, n, s) \
@@ -122,8 +123,9 @@
 #undef _
 /* *INDENT-ON* */
     default:
-      clib_warning ("unknown protocol %u", bibe->proto);
-      return;
+      bib = db->bib._unk_proto_bib;
+      st = db->st._unk_proto_st;
+      break;
     }
 
   bibe_index = bibe - bib;
@@ -169,14 +171,14 @@
 
 nat64_db_bib_entry_t *
 nat64_db_bib_entry_find (nat64_db_t * db, ip46_address_t * addr, u16 port,
-			 snat_protocol_t proto, u32 fib_index, u8 is_ip6)
+			 u8 proto, u32 fib_index, u8 is_ip6)
 {
   nat64_db_bib_entry_t *bibe = 0;
   nat64_db_bib_entry_key_t bibe_key;
   clib_bihash_kv_24_8_t kv, value;
   nat64_db_bib_entry_t *bib;
 
-  switch (proto)
+  switch (ip_proto_to_snat_proto (proto))
     {
 /* *INDENT-OFF* */
 #define _(N, i, n, s) \
@@ -187,8 +189,8 @@
 #undef _
 /* *INDENT-ON* */
     default:
-      clib_warning ("unknown protocol %u", proto);
-      return 0;
+      bib = db->bib._unk_proto_bib;
+      break;
     }
 
   bibe_key.addr.as_u64[0] = addr->as_u64[0];
@@ -210,12 +212,12 @@
 }
 
 void
-nat64_db_bib_walk (nat64_db_t * db, snat_protocol_t proto,
+nat64_db_bib_walk (nat64_db_t * db, u8 proto,
 		   nat64_db_bib_walk_fn_t fn, void *ctx)
 {
   nat64_db_bib_entry_t *bib, *bibe;
 
-  switch (proto)
+  switch (ip_proto_to_snat_proto (proto))
     {
 /* *INDENT-OFF* */
 #define _(N, i, n, s) \
@@ -226,8 +228,8 @@
 #undef _
 /* *INDENT-ON* */
     default:
-      clib_warning ("unknown protocol");
-      return;
+      bib = db->bib._unk_proto_bib;
+      break;
     }
 
   /* *INDENT-OFF* */
@@ -240,12 +242,11 @@
 }
 
 nat64_db_bib_entry_t *
-nat64_db_bib_entry_by_index (nat64_db_t * db, snat_protocol_t proto,
-			     u32 bibe_index)
+nat64_db_bib_entry_by_index (nat64_db_t * db, u8 proto, u32 bibe_index)
 {
   nat64_db_bib_entry_t *bib;
 
-  switch (proto)
+  switch (ip_proto_to_snat_proto (proto))
     {
 /* *INDENT-OFF* */
 #define _(N, i, n, s) \
@@ -256,20 +257,20 @@
 #undef _
 /* *INDENT-ON* */
     default:
-      clib_warning ("unknown protocol %u", proto);
-      return 0;
+      bib = db->bib._unk_proto_bib;
+      break;
     }
 
   return pool_elt_at_index (bib, bibe_index);
 }
 
 void
-nat64_db_st_walk (nat64_db_t * db, snat_protocol_t proto,
+nat64_db_st_walk (nat64_db_t * db, u8 proto,
 		  nat64_db_st_walk_fn_t fn, void *ctx)
 {
   nat64_db_st_entry_t *st, *ste;
 
-  switch (proto)
+  switch (ip_proto_to_snat_proto (proto))
     {
 /* *INDENT-OFF* */
 #define _(N, i, n, s) \
@@ -280,8 +281,8 @@
 #undef _
 /* *INDENT-ON* */
     default:
-      clib_warning ("unknown protocol");
-      return;
+      st = db->st._unk_proto_st;
+      break;
     }
 
   /* *INDENT-OFF* */
@@ -304,7 +305,7 @@
   clib_bihash_kv_48_8_t kv;
 
   /* create pool entry */
-  switch (bibe->proto)
+  switch (ip_proto_to_snat_proto (bibe->proto))
     {
 /* *INDENT-OFF* */
 #define _(N, i, n, s) \
@@ -317,8 +318,10 @@
 #undef _
 /* *INDENT-ON* */
     default:
-      clib_warning ("unknown protocol %u", bibe->proto);
-      return 0;
+      pool_get (db->st._unk_proto_st, ste);
+      kv.value = ste - db->st._unk_proto_st;
+      bib = db->bib._unk_proto_bib;
+      break;
     }
   memset (ste, 0, sizeof (*ste));
   ste->in_r_addr.as_u64[0] = in_r_addr->as_u64[0];
@@ -374,7 +377,7 @@
   nat64_db_st_entry_key_t ste_key;
   clib_bihash_kv_48_8_t kv;
 
-  switch (ste->proto)
+  switch (ip_proto_to_snat_proto (ste->proto))
     {
 /* *INDENT-OFF* */
 #define _(N, i, n, s) \
@@ -386,8 +389,9 @@
 #undef _
 /* *INDENT-ON* */
     default:
-      clib_warning ("unknown protocol %u", ste->proto);
-      return;
+      st = db->st._unk_proto_st;
+      bib = db->bib._unk_proto_bib;
+      break;
     }
 
   bibe = pool_elt_at_index (bib, ste->bibe_index);
@@ -438,14 +442,14 @@
 nat64_db_st_entry_t *
 nat64_db_st_entry_find (nat64_db_t * db, ip46_address_t * l_addr,
 			ip46_address_t * r_addr, u16 l_port, u16 r_port,
-			snat_protocol_t proto, u32 fib_index, u8 is_ip6)
+			u8 proto, u32 fib_index, u8 is_ip6)
 {
   nat64_db_st_entry_t *ste = 0;
   nat64_db_st_entry_t *st;
   nat64_db_st_entry_key_t ste_key;
   clib_bihash_kv_48_8_t kv, value;
 
-  switch (proto)
+  switch (ip_proto_to_snat_proto (proto))
     {
 /* *INDENT-OFF* */
 #define _(N, i, n, s) \
@@ -456,8 +460,8 @@
 #undef _
 /* *INDENT-ON* */
     default:
-      clib_warning ("unknown protocol %u", proto);
-      return ste;
+      st = db->st._unk_proto_st;
+      break;
     }
 
   memset (&ste_key, 0, sizeof (ste_key));
@@ -504,6 +508,47 @@
   ste_to_be_free = 0;
   foreach_snat_protocol
 #undef _
+  st = db->st._unk_proto_st;
+  pool_foreach (ste, st, ({
+    if (ste->expire < now)
+      vec_add1 (ste_to_be_free, ste - st);
+  }));
+  vec_foreach (ste_index, ste_to_be_free)
+    nat64_db_st_entry_free (db, pool_elt_at_index(st, ste_index[0]));
+  vec_free (ste_to_be_free);
+/* *INDENT-ON* */
+}
+
+void
+nat64_db_free_out_addr (nat64_db_t * db, ip4_address_t * out_addr)
+{
+  u32 *ste_to_be_free = 0, *ste_index;
+  nat64_db_st_entry_t *st, *ste;
+  nat64_db_bib_entry_t *bibe;
+
+/* *INDENT-OFF* */
+#define _(N, i, n, s) \
+  st = db->st._##n##_st; \
+  pool_foreach (ste, st, ({ \
+    bibe = pool_elt_at_index (db->bib._##n##_bib, ste->bibe_index); \
+    if (bibe->out_addr.as_u32 == out_addr->as_u32) \
+      vec_add1 (ste_to_be_free, ste - st); \
+  })); \
+  vec_foreach (ste_index, ste_to_be_free) \
+    nat64_db_st_entry_free (db, pool_elt_at_index(st, ste_index[0])); \
+  vec_free (ste_to_be_free); \
+  ste_to_be_free = 0;
+  foreach_snat_protocol
+#undef _
+  st = db->st._unk_proto_st;
+  pool_foreach (ste, st, ({
+    bibe = pool_elt_at_index (db->bib._unk_proto_bib, ste->bibe_index);
+    if (bibe->out_addr.as_u32 == out_addr->as_u32)
+      vec_add1 (ste_to_be_free, ste - st);
+  }));
+  vec_foreach (ste_index, ste_to_be_free)
+    nat64_db_st_entry_free (db, pool_elt_at_index(st, ste_index[0]));
+  vec_free (ste_to_be_free);
 /* *INDENT-ON* */
 }
 
diff --git a/src/plugins/snat/nat64_db.h b/src/plugins/snat/nat64_db.h
index caea3bf..4511fb2 100644
--- a/src/plugins/snat/nat64_db.h
+++ b/src/plugins/snat/nat64_db.h
@@ -63,6 +63,7 @@
   foreach_snat_protocol
 #undef _
 /* *INDENT-ON* */
+  nat64_db_bib_entry_t *_unk_proto_bib;
 
   /* BIB lookup */
   clib_bihash_24_8_t in2out;
@@ -109,6 +110,7 @@
   foreach_snat_protocol
 #undef _
 /* *INDENT-ON* */
+  nat64_db_st_entry_t *_unk_proto_st;
 
   /* session lookup */
   clib_bihash_48_8_t in2out;
@@ -149,8 +151,7 @@
 						 ip4_address_t * out_addr,
 						 u16 in_port, u16 out_port,
 						 u32 fib_index,
-						 snat_protocol_t proto,
-						 u8 is_static);
+						 u8 proto, u8 is_static);
 
 /**
  * @brief Free NAT64 BIB entry.
@@ -170,11 +171,11 @@
  * @brief Walk NAT64 BIB.
  *
  * @param db NAT64 DB.
- * @param proto BIB protocol (TCP/UDP/ICMP).
+ * @param proto L4 protocol.
  * @param fn The function to invoke on each entry visited.
  * @param ctx A context passed in the visit function.
  */
-void nat64_db_bib_walk (nat64_db_t * db, snat_protocol_t proto,
+void nat64_db_bib_walk (nat64_db_t * db, u8 proto,
 			nat64_db_bib_walk_fn_t fn, void *ctx);
 
 /**
@@ -192,7 +193,7 @@
 nat64_db_bib_entry_t *nat64_db_bib_entry_find (nat64_db_t * db,
 					       ip46_address_t * addr,
 					       u16 port,
-					       snat_protocol_t proto,
+					       u8 proto,
 					       u32 fib_index, u8 is_ip6);
 
 /**
@@ -205,8 +206,7 @@
  * @return BIB entry if found.
  */
 nat64_db_bib_entry_t *nat64_db_bib_entry_by_index (nat64_db_t * db,
-						   snat_protocol_t proto,
-						   u32 bibe_index);
+						   u8 proto, u32 bibe_index);
 /**
  * @brief Create new NAT64 session table entry.
  *
@@ -250,7 +250,7 @@
 					     ip46_address_t * l_addr,
 					     ip46_address_t * r_addr,
 					     u16 l_port, u16 r_port,
-					     snat_protocol_t proto,
+					     u8 proto,
 					     u32 fib_index, u8 is_ip6);
 
 /**
@@ -263,11 +263,11 @@
  * @brief Walk NAT64 session table.
  *
  * @param db NAT64 DB.
- * @param proto Session table protocol (TCP/UDP/ICMP).
+ * @param proto L4 protocol.
  * @param fn The function to invoke on each entry visited.
  * @param ctx A context passed in the visit function.
  */
-void nat64_db_st_walk (nat64_db_t * db, snat_protocol_t proto,
+void nat64_db_st_walk (nat64_db_t * db, u8 proto,
 		       nat64_db_st_walk_fn_t fn, void *ctx);
 
 /**
@@ -278,6 +278,14 @@
  */
 void nad64_db_st_free_expired (nat64_db_t * db, u32 now);
 
+/**
+ * @brief Free sessions using specific outside address.
+ *
+ * @param db NAT64 DB.
+ * @param out_addr Outside address to match.
+ */
+void nat64_db_free_out_addr (nat64_db_t * db, ip4_address_t * out_addr);
+
 #endif /* __included_nat64_db_h__ */
 
 /*
diff --git a/src/plugins/snat/nat64_in2out.c b/src/plugins/snat/nat64_in2out.c
index 126b076..8c67fec 100644
--- a/src/plugins/snat/nat64_in2out.c
+++ b/src/plugins/snat/nat64_in2out.c
@@ -25,6 +25,7 @@
 {
   u32 sw_if_index;
   u32 next_index;
+  u8 is_slow_path;
 } nat64_in2out_trace_t;
 
 static u8 *
@@ -33,15 +34,19 @@
   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
   nat64_in2out_trace_t *t = va_arg (*args, nat64_in2out_trace_t *);
+  char *tag;
+
+  tag = t->is_slow_path ? "NAT64-in2out-slowpath" : "NAT64-in2out";
 
   s =
-    format (s, "NAT64-in2out: sw_if_index %d, next index %d", t->sw_if_index,
+    format (s, "%s: sw_if_index %d, next index %d", tag, t->sw_if_index,
 	    t->next_index);
 
   return s;
 }
 
 vlib_node_registration_t nat64_in2out_node;
+vlib_node_registration_t nat64_in2out_slowpath_node;
 
 #define foreach_nat64_in2out_error                 \
 _(UNSUPPORTED_PROTOCOL, "unsupported protocol")    \
@@ -68,6 +73,7 @@
   NAT64_IN2OUT_NEXT_IP4_LOOKUP,
   NAT64_IN2OUT_NEXT_IP6_LOOKUP,
   NAT64_IN2OUT_NEXT_DROP,
+  NAT64_IN2OUT_NEXT_SLOWPATH,
   NAT64_IN2OUT_N_NEXT,
 } nat64_in2out_next_t;
 
@@ -113,7 +119,7 @@
   ip46_address_t saddr, daddr;
   u32 sw_if_index, fib_index;
   udp_header_t *udp = ip6_next_header (ip6);
-  snat_protocol_t proto = ip_proto_to_snat_proto (ip6->protocol);
+  u8 proto = ip6->protocol;
   u16 sport = udp->src_port;
   u16 dport = udp->dst_port;
 
@@ -146,7 +152,8 @@
 	  u16 out_port;
 	  ip4_address_t out_addr;
 	  if (nat64_alloc_out_addr_and_port
-	      (fib_index, proto, &out_addr, &out_port))
+	      (fib_index, ip_proto_to_snat_proto (proto), &out_addr,
+	       &out_port))
 	    return -1;
 
 	  bibe =
@@ -172,7 +179,7 @@
 
   ip4->dst_address.as_u32 = ste->out_r_addr.as_u32;
 
-  if (proto == SNAT_PROTOCOL_TCP)
+  if (proto == IP_PROTOCOL_TCP)
     {
       u16 *checksum;
       ip_csum_t csum;
@@ -212,12 +219,12 @@
       u16 in_id = ((u16 *) (icmp))[2];
       ste =
 	nat64_db_st_entry_find (&nm->db, &saddr, &daddr, in_id, 0,
-				SNAT_PROTOCOL_ICMP, fib_index, 1);
+				IP_PROTOCOL_ICMP, fib_index, 1);
 
       if (ste)
 	{
 	  bibe =
-	    nat64_db_bib_entry_by_index (&nm->db, SNAT_PROTOCOL_ICMP,
+	    nat64_db_bib_entry_by_index (&nm->db, IP_PROTOCOL_ICMP,
 					 ste->bibe_index);
 	  if (!bibe)
 	    return -1;
@@ -226,7 +233,7 @@
 	{
 	  bibe =
 	    nat64_db_bib_entry_find (&nm->db, &saddr, in_id,
-				     SNAT_PROTOCOL_ICMP, fib_index, 1);
+				     IP_PROTOCOL_ICMP, fib_index, 1);
 
 	  if (!bibe)
 	    {
@@ -240,7 +247,7 @@
 		nat64_db_bib_entry_create (&nm->db, &ip6->src_address,
 					   &out_addr, in_id,
 					   clib_host_to_net_u16 (out_id),
-					   fib_index, SNAT_PROTOCOL_ICMP, 0);
+					   fib_index, IP_PROTOCOL_ICMP, 0);
 	      if (!bibe)
 		return -1;
 	    }
@@ -282,7 +289,7 @@
   nat64_db_bib_entry_t *bibe;
   ip46_address_t saddr, daddr;
   u32 sw_if_index, fib_index;
-  snat_protocol_t proto = ip_proto_to_snat_proto (ip6->protocol);
+  u8 proto = ip6->protocol;
 
   sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX];
   fib_index =
@@ -293,10 +300,11 @@
   daddr.as_u64[0] = ip6->dst_address.as_u64[0];
   daddr.as_u64[1] = ip6->dst_address.as_u64[1];
 
-  if (proto == SNAT_PROTOCOL_ICMP)
+  if (proto == IP_PROTOCOL_ICMP6)
     {
       icmp46_header_t *icmp = ip6_next_header (ip6);
       u16 in_id = ((u16 *) (icmp))[2];
+      proto = IP_PROTOCOL_ICMP;
 
       if (!
 	  (icmp->type == ICMP4_echo_request
@@ -341,7 +349,7 @@
       udp->dst_port = bibe->out_port;
       ip4->src_address.as_u32 = ste->out_r_addr.as_u32;
 
-      if (proto == SNAT_PROTOCOL_TCP)
+      if (proto == IP_PROTOCOL_TCP)
 	checksum = &tcp->checksum;
       else
 	checksum = &udp->checksum;
@@ -353,6 +361,153 @@
   return 0;
 }
 
+typedef struct unk_proto_st_walk_ctx_t_
+{
+  ip6_address_t src_addr;
+  ip6_address_t dst_addr;
+  ip4_address_t out_addr;
+  u32 fib_index;
+  u8 proto;
+} unk_proto_st_walk_ctx_t;
+
+static int
+unk_proto_st_walk (nat64_db_st_entry_t * ste, void *arg)
+{
+  nat64_main_t *nm = &nat64_main;
+  unk_proto_st_walk_ctx_t *ctx = arg;
+  nat64_db_bib_entry_t *bibe;
+  ip46_address_t saddr, daddr;
+
+  if (ip46_address_is_equal (&ste->in_r_addr, &ctx->dst_addr))
+    {
+      bibe =
+	nat64_db_bib_entry_by_index (&nm->db, ste->proto, ste->bibe_index);
+      if (!bibe)
+	return -1;
+
+      if (ip46_address_is_equal (&bibe->in_addr, &ctx->src_addr)
+	  && bibe->fib_index == ctx->fib_index)
+	{
+	  memset (&saddr, 0, sizeof (saddr));
+	  saddr.ip4.as_u32 = bibe->out_addr.as_u32;
+	  memset (&daddr, 0, sizeof (daddr));
+	  nat64_extract_ip4 (&ctx->dst_addr, &daddr.ip4, ctx->fib_index);
+
+	  if (nat64_db_st_entry_find
+	      (&nm->db, &daddr, &saddr, 0, 0, ctx->proto, ctx->fib_index, 0))
+	    return -1;
+
+	  ctx->out_addr.as_u32 = bibe->out_addr.as_u32;
+	  return 1;
+	}
+    }
+
+  return 0;
+}
+
+static int
+nat64_in2out_unk_proto_set_cb (ip6_header_t * ip6, ip4_header_t * ip4,
+			       void *arg)
+{
+  nat64_main_t *nm = &nat64_main;
+  nat64_in2out_set_ctx_t *ctx = arg;
+  nat64_db_bib_entry_t *bibe;
+  nat64_db_st_entry_t *ste;
+  ip46_address_t saddr, daddr, addr;
+  u32 sw_if_index, fib_index;
+  u8 proto = ip6->protocol;
+  int i;
+
+  sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX];
+  fib_index =
+    fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index);
+
+  saddr.as_u64[0] = ip6->src_address.as_u64[0];
+  saddr.as_u64[1] = ip6->src_address.as_u64[1];
+  daddr.as_u64[0] = ip6->dst_address.as_u64[0];
+  daddr.as_u64[1] = ip6->dst_address.as_u64[1];
+
+  ste =
+    nat64_db_st_entry_find (&nm->db, &saddr, &daddr, 0, 0, proto, fib_index,
+			    1);
+
+  if (ste)
+    {
+      bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index);
+      if (!bibe)
+	return -1;
+    }
+  else
+    {
+      bibe =
+	nat64_db_bib_entry_find (&nm->db, &saddr, 0, proto, fib_index, 1);
+
+      if (!bibe)
+	{
+	  /* Choose same out address as for TCP/UDP session to same dst */
+	  unk_proto_st_walk_ctx_t ctx = {
+	    .src_addr.as_u64[0] = ip6->src_address.as_u64[0],
+	    .src_addr.as_u64[1] = ip6->src_address.as_u64[1],
+	    .dst_addr.as_u64[0] = ip6->dst_address.as_u64[0],
+	    .dst_addr.as_u64[1] = ip6->dst_address.as_u64[1],
+	    .out_addr.as_u32 = 0,
+	    .fib_index = fib_index,
+	    .proto = proto,
+	  };
+
+	  nat64_db_st_walk (&nm->db, IP_PROTOCOL_TCP, unk_proto_st_walk,
+			    &ctx);
+
+	  if (!ctx.out_addr.as_u32)
+	    nat64_db_st_walk (&nm->db, IP_PROTOCOL_UDP, unk_proto_st_walk,
+			      &ctx);
+
+	  /* Verify if out address is not already in use for protocol */
+	  memset (&addr, 0, sizeof (addr));
+	  addr.ip4.as_u32 = ctx.out_addr.as_u32;
+	  if (nat64_db_bib_entry_find (&nm->db, &addr, 0, proto, 0, 0))
+	    ctx.out_addr.as_u32 = 0;
+
+	  if (!ctx.out_addr.as_u32)
+	    {
+	      for (i = 0; i < vec_len (nm->addr_pool); i++)
+		{
+		  addr.ip4.as_u32 = nm->addr_pool[i].addr.as_u32;
+		  if (!nat64_db_bib_entry_find
+		      (&nm->db, &addr, 0, proto, 0, 0))
+		    break;
+		}
+	    }
+
+	  if (!ctx.out_addr.as_u32)
+	    return -1;
+
+	  bibe =
+	    nat64_db_bib_entry_create (&nm->db, &ip6->src_address,
+				       &ctx.out_addr, 0, 0, fib_index, proto,
+				       0);
+	  if (!bibe)
+	    return -1;
+	}
+
+      nat64_extract_ip4 (&ip6->dst_address, &daddr.ip4, fib_index);
+      ste =
+	nat64_db_st_entry_create (&nm->db, bibe, &ip6->dst_address,
+				  &daddr.ip4, 0);
+      if (!ste)
+	return -1;
+    }
+
+  nat64_session_reset_timeout (ste, ctx->vm);
+
+  ip4->src_address.as_u32 = bibe->out_addr.as_u32;
+  ip4->dst_address.as_u32 = ste->out_r_addr.as_u32;
+
+  return 0;
+}
+
+
+
 static int
 nat64_in2out_tcp_udp_hairpinning (vlib_main_t * vm, vlib_buffer_t * b,
 				  ip6_header_t * ip6)
@@ -364,7 +519,7 @@
   u32 sw_if_index, fib_index;
   udp_header_t *udp = ip6_next_header (ip6);
   tcp_header_t *tcp = ip6_next_header (ip6);
-  snat_protocol_t proto = ip_proto_to_snat_proto (ip6->protocol);
+  u8 proto = ip6->protocol;
   u16 sport = udp->src_port;
   u16 dport = udp->dst_port;
   u16 *checksum;
@@ -379,7 +534,7 @@
   daddr.as_u64[0] = ip6->dst_address.as_u64[0];
   daddr.as_u64[1] = ip6->dst_address.as_u64[1];
 
-  if (proto == SNAT_PROTOCOL_UDP)
+  if (proto == IP_PROTOCOL_UDP)
     checksum = &udp->checksum;
   else
     checksum = &tcp->checksum;
@@ -411,7 +566,8 @@
 	  u16 out_port;
 	  ip4_address_t out_addr;
 	  if (nat64_alloc_out_addr_and_port
-	      (fib_index, proto, &out_addr, &out_port))
+	      (fib_index, ip_proto_to_snat_proto (proto), &out_addr,
+	       &out_port))
 	    return -1;
 
 	  bibe =
@@ -488,7 +644,7 @@
   ip6_header_t *inner_ip6;
   ip46_address_t saddr, daddr;
   u32 sw_if_index, fib_index;
-  snat_protocol_t proto;
+  u8 proto;
   udp_header_t *udp;
   tcp_header_t *tcp;
   u16 *checksum, sport, dport;
@@ -499,9 +655,9 @@
 
   inner_ip6 = (ip6_header_t *) u8_ptr_add (icmp, 8);
 
-  proto = ip_proto_to_snat_proto (inner_ip6->protocol);
+  proto = inner_ip6->protocol;
 
-  if (proto == SNAT_PROTOCOL_ICMP)
+  if (proto == IP_PROTOCOL_ICMP6)
     return -1;
 
   sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
@@ -519,7 +675,7 @@
   sport = udp->src_port;
   dport = udp->dst_port;
 
-  if (proto == SNAT_PROTOCOL_UDP)
+  if (proto == IP_PROTOCOL_UDP)
     checksum = &udp->checksum;
   else
     checksum = &tcp->checksum;
@@ -593,13 +749,144 @@
   return 0;
 }
 
-static uword
-nat64_in2out_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node,
-		      vlib_frame_t * frame)
+static int
+nat64_in2out_unk_proto_hairpinning (vlib_main_t * vm, vlib_buffer_t * b,
+				    ip6_header_t * ip6)
+{
+  nat64_main_t *nm = &nat64_main;
+  nat64_db_bib_entry_t *bibe;
+  nat64_db_st_entry_t *ste;
+  ip46_address_t saddr, daddr, addr;
+  u32 sw_if_index, fib_index;
+  u8 proto = ip6->protocol;
+  int i;
+
+  sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
+  fib_index =
+    fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index);
+
+  saddr.as_u64[0] = ip6->src_address.as_u64[0];
+  saddr.as_u64[1] = ip6->src_address.as_u64[1];
+  daddr.as_u64[0] = ip6->dst_address.as_u64[0];
+  daddr.as_u64[1] = ip6->dst_address.as_u64[1];
+
+  ste =
+    nat64_db_st_entry_find (&nm->db, &saddr, &daddr, 0, 0, proto, fib_index,
+			    1);
+
+  if (ste)
+    {
+      bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index);
+      if (!bibe)
+	return -1;
+    }
+  else
+    {
+      bibe =
+	nat64_db_bib_entry_find (&nm->db, &saddr, 0, proto, fib_index, 1);
+
+      if (!bibe)
+	{
+	  /* Choose same out address as for TCP/UDP session to same dst */
+	  unk_proto_st_walk_ctx_t ctx = {
+	    .src_addr.as_u64[0] = ip6->src_address.as_u64[0],
+	    .src_addr.as_u64[1] = ip6->src_address.as_u64[1],
+	    .dst_addr.as_u64[0] = ip6->dst_address.as_u64[0],
+	    .dst_addr.as_u64[1] = ip6->dst_address.as_u64[1],
+	    .out_addr.as_u32 = 0,
+	    .fib_index = fib_index,
+	    .proto = proto,
+	  };
+
+	  nat64_db_st_walk (&nm->db, IP_PROTOCOL_TCP, unk_proto_st_walk,
+			    &ctx);
+
+	  if (!ctx.out_addr.as_u32)
+	    nat64_db_st_walk (&nm->db, IP_PROTOCOL_UDP, unk_proto_st_walk,
+			      &ctx);
+
+	  /* Verify if out address is not already in use for protocol */
+	  memset (&addr, 0, sizeof (addr));
+	  addr.ip4.as_u32 = ctx.out_addr.as_u32;
+	  if (nat64_db_bib_entry_find (&nm->db, &addr, 0, proto, 0, 0))
+	    ctx.out_addr.as_u32 = 0;
+
+	  if (!ctx.out_addr.as_u32)
+	    {
+	      for (i = 0; i < vec_len (nm->addr_pool); i++)
+		{
+		  addr.ip4.as_u32 = nm->addr_pool[i].addr.as_u32;
+		  if (!nat64_db_bib_entry_find
+		      (&nm->db, &addr, 0, proto, 0, 0))
+		    break;
+		}
+	    }
+
+	  if (!ctx.out_addr.as_u32)
+	    return -1;
+
+	  bibe =
+	    nat64_db_bib_entry_create (&nm->db, &ip6->src_address,
+				       &ctx.out_addr, 0, 0, fib_index, proto,
+				       0);
+	  if (!bibe)
+	    return -1;
+	}
+
+      nat64_extract_ip4 (&ip6->dst_address, &daddr.ip4, fib_index);
+      ste =
+	nat64_db_st_entry_create (&nm->db, bibe, &ip6->dst_address,
+				  &daddr.ip4, 0);
+      if (!ste)
+	return -1;
+    }
+
+  nat64_session_reset_timeout (ste, vm);
+
+  nat64_compose_ip6 (&ip6->src_address, &bibe->out_addr, fib_index);
+
+  memset (&saddr, 0, sizeof (saddr));
+  memset (&daddr, 0, sizeof (daddr));
+  saddr.ip4.as_u32 = bibe->out_addr.as_u32;
+  daddr.ip4.as_u32 = ste->out_r_addr.as_u32;
+
+  ste = nat64_db_st_entry_find (&nm->db, &daddr, &saddr, 0, 0, proto, 0, 0);
+
+  if (ste)
+    {
+      bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index);
+      if (!bibe)
+	return -1;
+    }
+  else
+    {
+      bibe = nat64_db_bib_entry_find (&nm->db, &daddr, 0, proto, 0, 0);
+
+      if (!bibe)
+	return -1;
+
+      ste =
+	nat64_db_st_entry_create (&nm->db, bibe, &ip6->src_address,
+				  &saddr.ip4, 0);
+    }
+
+  ip6->dst_address.as_u64[0] = bibe->in_addr.as_u64[0];
+  ip6->dst_address.as_u64[1] = bibe->in_addr.as_u64[1];
+
+  return 0;
+}
+
+static inline uword
+nat64_in2out_node_fn_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
+			     vlib_frame_t * frame, u8 is_slow_path)
 {
   u32 n_left_from, *from, *to_next;
   nat64_in2out_next_t next_index;
   u32 pkts_processed = 0;
+  u32 stats_node_index;
+
+  stats_node_index =
+    is_slow_path ? nat64_in2out_slowpath_node.index : nat64_in2out_node.index;
 
   from = vlib_frame_vector_args (frame);
   n_left_from = frame->n_vectors;
@@ -649,7 +936,7 @@
 	    }
 
 	  proto0 = ip_proto_to_snat_proto (l4_protocol0);
-	  if (PREDICT_FALSE ((proto0 == ~0) || (frag_offset0 != 0)))
+	  if (frag_offset0 != 0)
 	    {
 	      next0 = NAT64_IN2OUT_NEXT_DROP;
 	      b0->error =
@@ -657,6 +944,41 @@
 	      goto trace0;
 	    }
 
+	  if (is_slow_path)
+	    {
+	      if (PREDICT_TRUE (proto0 == ~0))
+		{
+		  if (is_hairpinning (&ip60->dst_address))
+		    {
+		      next0 = NAT64_IN2OUT_NEXT_IP6_LOOKUP;
+		      if (nat64_in2out_unk_proto_hairpinning (vm, b0, ip60))
+			{
+			  next0 = NAT64_IN2OUT_NEXT_DROP;
+			  b0->error =
+			    node->errors[NAT64_IN2OUT_ERROR_NO_TRANSLATION];
+			}
+		      goto trace0;
+		    }
+
+		  if (ip6_to_ip4 (b0, nat64_in2out_unk_proto_set_cb, &ctx0))
+		    {
+		      next0 = NAT64_IN2OUT_NEXT_DROP;
+		      b0->error =
+			node->errors[NAT64_IN2OUT_ERROR_NO_TRANSLATION];
+		      goto trace0;
+		    }
+		}
+	      goto trace0;
+	    }
+	  else
+	    {
+	      if (PREDICT_FALSE (proto0 == ~0))
+		{
+		  next0 = NAT64_IN2OUT_NEXT_SLOWPATH;
+		  goto trace0;
+		}
+	    }
+
 	  if (proto0 == SNAT_PROTOCOL_ICMP)
 	    {
 	      if (is_hairpinning (&ip60->dst_address))
@@ -680,7 +1002,7 @@
 		  goto trace0;
 		}
 	    }
-	  else
+	  else if (proto0 == SNAT_PROTOCOL_TCP || proto0 == SNAT_PROTOCOL_UDP)
 	    {
 	      if (is_hairpinning (&ip60->dst_address))
 		{
@@ -711,6 +1033,7 @@
 		vlib_add_trace (vm, node, b0, sizeof (*t));
 	      t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX];
 	      t->next_index = next0;
+	      t->is_slow_path = is_slow_path;
 	    }
 
 	  pkts_processed += next0 != NAT64_IN2OUT_NEXT_DROP;
@@ -721,32 +1044,71 @@
 	}
       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
     }
-  vlib_node_increment_counter (vm, nat64_in2out_node.index,
+  vlib_node_increment_counter (vm, stats_node_index,
 			       NAT64_IN2OUT_ERROR_IN2OUT_PACKETS,
 			       pkts_processed);
   return frame->n_vectors;
 }
 
+static uword
+nat64_in2out_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node,
+		      vlib_frame_t * frame)
+{
+  return nat64_in2out_node_fn_inline (vm, node, frame, 0);
+}
+
 /* *INDENT-OFF* */
 VLIB_REGISTER_NODE (nat64_in2out_node) = {
-  .function = nat64_in2out_node_fn,.name = "nat64-in2out",
+  .function = nat64_in2out_node_fn,
+  .name = "nat64-in2out",
   .vector_size = sizeof (u32),
   .format_trace = format_nat64_in2out_trace,
   .type = VLIB_NODE_TYPE_INTERNAL,
   .n_errors = ARRAY_LEN (nat64_in2out_error_strings),
   .error_strings = nat64_in2out_error_strings,
-  .n_next_nodes = 2,
+  .n_next_nodes = NAT64_IN2OUT_N_NEXT,
   /* edit / add dispositions here */
   .next_nodes = {
     [NAT64_IN2OUT_NEXT_DROP] = "error-drop",
     [NAT64_IN2OUT_NEXT_IP4_LOOKUP] = "ip4-lookup",
     [NAT64_IN2OUT_NEXT_IP6_LOOKUP] = "ip6-lookup",
+    [NAT64_IN2OUT_NEXT_SLOWPATH] = "nat64-in2out-slowpath",
   },
 };
 /* *INDENT-ON* */
 
 VLIB_NODE_FUNCTION_MULTIARCH (nat64_in2out_node, nat64_in2out_node_fn);
 
+static uword
+nat64_in2out_slowpath_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node,
+			       vlib_frame_t * frame)
+{
+  return nat64_in2out_node_fn_inline (vm, node, frame, 1);
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (nat64_in2out_slowpath_node) = {
+  .function = nat64_in2out_slowpath_node_fn,
+  .name = "nat64-in2out-slowpath",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat64_in2out_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN (nat64_in2out_error_strings),
+  .error_strings = nat64_in2out_error_strings,
+  .n_next_nodes = NAT64_IN2OUT_N_NEXT,
+  /* edit / add dispositions here */
+  .next_nodes = {
+    [NAT64_IN2OUT_NEXT_DROP] = "error-drop",
+    [NAT64_IN2OUT_NEXT_IP4_LOOKUP] = "ip4-lookup",
+    [NAT64_IN2OUT_NEXT_IP6_LOOKUP] = "ip6-lookup",
+    [NAT64_IN2OUT_NEXT_SLOWPATH] = "nat64-in2out-slowpath",
+  },
+};
+/* *INDENT-ON* */
+
+VLIB_NODE_FUNCTION_MULTIARCH (nat64_in2out_slowpath_node,
+			      nat64_in2out_slowpath_node_fn);
+
 /*
  * fd.io coding-style-patch-verification: ON
  *
diff --git a/src/plugins/snat/nat64_out2in.c b/src/plugins/snat/nat64_out2in.c
index 755aa63..cd5b253 100644
--- a/src/plugins/snat/nat64_out2in.c
+++ b/src/plugins/snat/nat64_out2in.c
@@ -88,7 +88,7 @@
   ip6_address_t ip6_saddr;
   udp_header_t *udp = ip4_next_header (ip4);
   tcp_header_t *tcp = ip4_next_header (ip4);
-  snat_protocol_t proto = ip_proto_to_snat_proto (ip4->protocol);
+  u8 proto = ip4->protocol;
   u16 dport = udp->dst_port;
   u16 sport = udp->src_port;
   u32 sw_if_index, fib_index;
@@ -135,7 +135,7 @@
   ip6->dst_address.as_u64[1] = bibe->in_addr.as_u64[1];
   udp->dst_port = bibe->in_port;
 
-  if (proto == SNAT_PROTOCOL_UDP)
+  if (proto == IP_PROTOCOL_UDP)
     checksum = &udp->checksum;
   else
     checksum = &tcp->checksum;
@@ -173,12 +173,12 @@
       u16 out_id = ((u16 *) (icmp))[2];
       ste =
 	nat64_db_st_entry_find (&nm->db, &daddr, &saddr, out_id, 0,
-				SNAT_PROTOCOL_ICMP, fib_index, 0);
+				IP_PROTOCOL_ICMP, fib_index, 0);
 
       if (ste)
 	{
 	  bibe =
-	    nat64_db_bib_entry_by_index (&nm->db, SNAT_PROTOCOL_ICMP,
+	    nat64_db_bib_entry_by_index (&nm->db, IP_PROTOCOL_ICMP,
 					 ste->bibe_index);
 	  if (!bibe)
 	    return -1;
@@ -187,7 +187,7 @@
 	{
 	  bibe =
 	    nat64_db_bib_entry_find (&nm->db, &daddr, out_id,
-				     SNAT_PROTOCOL_ICMP, fib_index, 0);
+				     IP_PROTOCOL_ICMP, fib_index, 0);
 	  if (!bibe)
 	    return -1;
 
@@ -231,7 +231,7 @@
   nat64_db_st_entry_t *ste;
   ip46_address_t saddr, daddr;
   u32 sw_if_index, fib_index;
-  snat_protocol_t proto = ip_proto_to_snat_proto (ip4->protocol);
+  u8 proto = ip4->protocol;
 
   sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX];
   fib_index =
@@ -242,10 +242,11 @@
   memset (&daddr, 0, sizeof (daddr));
   daddr.ip4.as_u32 = ip4->dst_address.as_u32;
 
-  if (proto == SNAT_PROTOCOL_ICMP)
+  if (proto == IP_PROTOCOL_ICMP6)
     {
       icmp46_header_t *icmp = ip4_next_header (ip4);
       u16 out_id = ((u16 *) (icmp))[2];
+      proto = IP_PROTOCOL_ICMP;
 
       if (!
 	  (icmp->type == ICMP6_echo_request
@@ -294,7 +295,7 @@
       ip6->src_address.as_u64[1] = bibe->in_addr.as_u64[1];
       udp->src_port = bibe->in_port;
 
-      if (proto == SNAT_PROTOCOL_UDP)
+      if (proto == IP_PROTOCOL_UDP)
 	checksum = &udp->checksum;
       else
 	checksum = &tcp->checksum;
@@ -311,6 +312,62 @@
   return 0;
 }
 
+static int
+nat64_out2in_unk_proto_set_cb (ip4_header_t * ip4, ip6_header_t * ip6,
+			       void *arg)
+{
+  nat64_main_t *nm = &nat64_main;
+  nat64_out2in_set_ctx_t *ctx = arg;
+  nat64_db_bib_entry_t *bibe;
+  nat64_db_st_entry_t *ste;
+  ip46_address_t saddr, daddr;
+  ip6_address_t ip6_saddr;
+  u32 sw_if_index, fib_index;
+  u8 proto = ip4->protocol;
+
+  sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX];
+  fib_index = ip4_fib_table_get_index_for_sw_if_index (sw_if_index);
+
+  memset (&saddr, 0, sizeof (saddr));
+  saddr.ip4.as_u32 = ip4->src_address.as_u32;
+  memset (&daddr, 0, sizeof (daddr));
+  daddr.ip4.as_u32 = ip4->dst_address.as_u32;
+
+  ste =
+    nat64_db_st_entry_find (&nm->db, &daddr, &saddr, 0, 0, proto, fib_index,
+			    0);
+  if (ste)
+    {
+      bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index);
+      if (!bibe)
+	return -1;
+    }
+  else
+    {
+      bibe =
+	nat64_db_bib_entry_find (&nm->db, &daddr, 0, proto, fib_index, 0);
+
+      if (!bibe)
+	return -1;
+
+      nat64_compose_ip6 (&ip6_saddr, &ip4->src_address, bibe->fib_index);
+      ste =
+	nat64_db_st_entry_create (&nm->db, bibe, &ip6_saddr, &saddr.ip4, 0);
+    }
+
+  nat64_session_reset_timeout (ste, ctx->vm);
+
+  ip6->src_address.as_u64[0] = ste->in_r_addr.as_u64[0];
+  ip6->src_address.as_u64[1] = ste->in_r_addr.as_u64[1];
+
+  ip6->dst_address.as_u64[0] = bibe->in_addr.as_u64[0];
+  ip6->dst_address.as_u64[1] = bibe->in_addr.as_u64[1];
+
+  vnet_buffer (ctx->b)->sw_if_index[VLIB_TX] = bibe->fib_index;
+
+  return 0;
+}
+
 static uword
 nat64_out2in_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node,
 		      vlib_frame_t * frame)
@@ -354,13 +411,6 @@
 	  next0 = NAT64_OUT2IN_NEXT_LOOKUP;
 
 	  proto0 = ip_proto_to_snat_proto (ip40->protocol);
-	  if (PREDICT_FALSE (proto0 == ~0))
-	    {
-	      next0 = NAT64_OUT2IN_NEXT_DROP;
-	      b0->error =
-		node->errors[NAT64_OUT2IN_ERROR_UNSUPPORTED_PROTOCOL];
-	      goto trace0;
-	    }
 
 	  if (proto0 == SNAT_PROTOCOL_ICMP)
 	    {
@@ -373,7 +423,7 @@
 		  goto trace0;
 		}
 	    }
-	  else
+	  else if (proto0 == SNAT_PROTOCOL_TCP || proto0 == SNAT_PROTOCOL_UDP)
 	    {
 	      if (ip4_to_ip6_tcp_udp (b0, nat64_out2in_tcp_udp_set_cb, &ctx0))
 		{
@@ -382,6 +432,15 @@
 		  goto trace0;
 		}
 	    }
+	  else
+	    {
+	      if (ip4_to_ip6 (b0, nat64_out2in_unk_proto_set_cb, &ctx0))
+		{
+		  next0 = NAT64_OUT2IN_NEXT_DROP;
+		  b0->error = node->errors[NAT64_OUT2IN_ERROR_NO_TRANSLATION];
+		  goto trace0;
+		}
+	    }
 
 	trace0:
 	  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
diff --git a/src/plugins/snat/snat.c b/src/plugins/snat/snat.c
index f196b5c..315cec8 100644
--- a/src/plugins/snat/snat.c
+++ b/src/plugins/snat/snat.c
@@ -1398,6 +1398,7 @@
 #undef _
     default:
       s = format (s, "unknown");
+      return s;
     }
   s = format (s, "%s", t);
   return s;
diff --git a/src/plugins/snat/snat_api.c b/src/plugins/snat/snat_api.c
index ee623d2..227074f 100644
--- a/src/plugins/snat/snat_api.c
+++ b/src/plugins/snat/snat_api.c
@@ -1580,7 +1580,7 @@
   rmp->i_port = bibe->in_port;
   rmp->o_port = bibe->out_port;
   rmp->vrf_id = ntohl (fib->ft_table_id);
-  rmp->proto = snat_proto_to_ip_proto (bibe->proto);
+  rmp->proto = bibe->proto;
   rmp->is_static = bibe->is_static;
   rmp->ses_num = ntohl (bibe->ses_num);
 
@@ -1594,7 +1594,6 @@
 {
   unix_shared_memory_queue_t *q;
   nat64_main_t *nm = &nat64_main;
-  snat_protocol_t proto;
 
   if (nm->is_disabled)
     return;
@@ -1608,9 +1607,7 @@
     .context = mp->context,
   };
 
-  proto = ip_proto_to_snat_proto (mp->proto);
-
-  nat64_db_bib_walk (&nm->db, proto, nat64_api_bib_walk, &ctx);
+  nat64_db_bib_walk (&nm->db, mp->proto, nat64_api_bib_walk, &ctx);
 }
 
 static void *
@@ -1729,7 +1726,7 @@
   clib_memcpy (rmp->or_addr, &(ste->out_r_addr), 4);
   rmp->il_port = ste->r_port;
   rmp->vrf_id = ntohl (fib->ft_table_id);
-  rmp->proto = snat_proto_to_ip_proto (ste->proto);
+  rmp->proto = ste->proto;
 
   vl_msg_api_send_shmem (ctx->q, (u8 *) & rmp);
 
@@ -1741,7 +1738,6 @@
 {
   unix_shared_memory_queue_t *q;
   nat64_main_t *nm = &nat64_main;
-  snat_protocol_t proto;
 
   if (nm->is_disabled)
     return;
@@ -1755,9 +1751,7 @@
     .context = mp->context,
   };
 
-  proto = ip_proto_to_snat_proto (mp->proto);
-
-  nat64_db_st_walk (&nm->db, proto, nat64_api_st_walk, &ctx);
+  nat64_db_st_walk (&nm->db, mp->proto, nat64_api_st_walk, &ctx);
 }
 
 static void *
diff --git a/src/vnet/ip/ip4_to_ip6.h b/src/vnet/ip/ip4_to_ip6.h
index cdd3707..6ffc562 100644
--- a/src/vnet/ip/ip4_to_ip6.h
+++ b/src/vnet/ip/ip4_to_ip6.h
@@ -586,6 +586,68 @@
   return 0;
 }
 
+/**
+ * @brief Translate IPv4 packet to IPv6 (IP header only).
+ *
+ * @param p   Buffer to translate.
+ * @param fn  The function to translate header.
+ * @param ctx A context passed in the header translate function.
+ *
+ * @returns 0 on success, non-zero value otherwise.
+ */
+always_inline int
+ip4_to_ip6 (vlib_buffer_t * p, ip4_to_ip6_set_fn_t fn, void *ctx)
+{
+  ip4_header_t *ip4;
+  ip6_header_t *ip6;
+  ip6_frag_hdr_t *frag;
+  u32 frag_id;
+  int rv;
+
+  ip4 = vlib_buffer_get_current (p);
+
+  // Deal with fragmented packets
+  if (PREDICT_FALSE (ip4->flags_and_fragment_offset &
+		     clib_host_to_net_u16 (IP4_HEADER_FLAG_MORE_FRAGMENTS)))
+    {
+      ip6 =
+	(ip6_header_t *) u8_ptr_add (ip4,
+				     sizeof (*ip4) - sizeof (*ip6) -
+				     sizeof (*frag));
+      frag =
+	(ip6_frag_hdr_t *) u8_ptr_add (ip4, sizeof (*ip4) - sizeof (*frag));
+      frag_id = frag_id_4to6 (ip4->fragment_id);
+      vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6) - sizeof (*frag));
+    }
+  else
+    {
+      ip6 = (ip6_header_t *) (((u8 *) ip4) + sizeof (*ip4) - sizeof (*ip6));
+      vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6));
+      frag = NULL;
+    }
+
+  ip6->ip_version_traffic_class_and_flow_label =
+    clib_host_to_net_u32 ((6 << 28) + (ip4->tos << 20));
+  ip6->payload_length = u16_net_add (ip4->length, -sizeof (*ip4));
+  ip6->hop_limit = ip4->ttl;
+  ip6->protocol = ip4->protocol;
+
+  if (PREDICT_FALSE (frag != NULL))
+    {
+      frag->next_hdr = ip6->protocol;
+      frag->identification = frag_id;
+      frag->rsv = 0;
+      frag->fragment_offset_and_more = ip6_frag_hdr_offset_and_more (0, 1);
+      ip6->protocol = IP_PROTOCOL_IPV6_FRAGMENTATION;
+      ip6->payload_length = u16_net_add (ip6->payload_length, sizeof (*frag));
+    }
+
+  if ((rv = fn (ip4, ip6, ctx)) != 0)
+    return rv;
+
+  return 0;
+}
+
 #endif /* __included_ip4_to_ip6_h__ */
 
 /*
diff --git a/src/vnet/ip/ip6_to_ip4.h b/src/vnet/ip/ip6_to_ip4.h
index 7a0d534..c14b46c 100644
--- a/src/vnet/ip/ip6_to_ip4.h
+++ b/src/vnet/ip/ip6_to_ip4.h
@@ -562,6 +562,67 @@
   return 0;
 }
 
+/**
+ * @brief Translate IPv6 packet to IPv4 (IP header only).
+ *
+ * @param p   Buffer to translate.
+ * @param fn  The function to translate header.
+ * @param ctx A context passed in the header translate function.
+ *
+ * @returns 0 on success, non-zero value otherwise.
+ */
+always_inline int
+ip6_to_ip4 (vlib_buffer_t * p, ip6_to_ip4_set_fn_t fn, void *ctx)
+{
+  ip6_header_t *ip6;
+  ip4_header_t *ip4;
+  u16 fragment_id;
+  u16 flags;
+  u16 frag_offset;
+  u8 l4_protocol;
+  u16 l4_offset;
+  int rv;
+
+  ip6 = vlib_buffer_get_current (p);
+
+  if (ip6_parse
+      (ip6, p->current_length, &l4_protocol, &l4_offset, &frag_offset))
+    return -1;
+
+  ip4 = (ip4_header_t *) u8_ptr_add (ip6, l4_offset - sizeof (*ip4));
+
+  vlib_buffer_advance (p, l4_offset - sizeof (*ip4));
+
+  if (PREDICT_FALSE (frag_offset))
+    {
+      //Only the first fragment
+      ip6_frag_hdr_t *hdr = (ip6_frag_hdr_t *) u8_ptr_add (ip6, frag_offset);
+      fragment_id = frag_id_6to4 (hdr->identification);
+      flags = clib_host_to_net_u16 (IP4_HEADER_FLAG_MORE_FRAGMENTS);
+    }
+  else
+    {
+      fragment_id = 0;
+      flags = 0;
+    }
+
+  if ((rv = fn (ip6, ip4, ctx)) != 0)
+    return rv;
+
+  ip4->ip_version_and_header_length =
+    IP4_VERSION_AND_HEADER_LENGTH_NO_OPTIONS;
+  ip4->tos = ip6_translate_tos (ip6);
+  ip4->length = u16_net_add (ip6->payload_length,
+			     sizeof (*ip4) + sizeof (*ip6) - l4_offset);
+  ip4->fragment_id = fragment_id;
+  ip4->flags_and_fragment_offset = flags;
+  ip4->ttl = ip6->hop_limit;
+  ip4->protocol = l4_protocol;
+  ip4->checksum = ip4_header_checksum (ip4);
+
+  return 0;
+}
+
 #endif /* __included_ip6_to_ip4_h__ */
 
 /*
diff --git a/test/test_snat.py b/test/test_snat.py
index 9f5377e..8fd05fa 100644
--- a/test/test_snat.py
+++ b/test/test_snat.py
@@ -3497,7 +3497,7 @@
                                   vrf1_pref64_len)
         self.verify_capture_in_ip6(capture, dst_ip, self.pg2.remote_ip6)
 
-    def _test_unknown_proto(self):
+    def test_unknown_proto(self):
         """ NAT64 translate packet with unknown protocol """
 
         self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
@@ -3516,7 +3516,7 @@
         p = self.pg1.get_capture(1)
 
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-             IPv6(src=self.pg0.remote_ip6, dst=remote_ip6) /
+             IPv6(src=self.pg0.remote_ip6, dst=remote_ip6, nh=47) /
              GRE() /
              IP(src=self.pg2.local_ip4, dst=self.pg2.remote_ip4) /
              TCP(sport=1234, dport=1234))
@@ -3547,13 +3547,13 @@
         packet = p[0]
         try:
             self.assertEqual(packet[IPv6].src, remote_ip6)
-            self.assertEqual(packet[IPv6].dst, self.pgi0.remote_ip6)
-            self.assertTrue(packet.haslayer(GRE))
+            self.assertEqual(packet[IPv6].dst, self.pg0.remote_ip6)
+            self.assertEqual(packet[IPv6].nh, 47)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
 
-    def _test_hairpinning_unknown_proto(self):
+    def test_hairpinning_unknown_proto(self):
         """ NAT64 translate packet with unknown protocol - hairpinning """
 
         client = self.pg0.remote_hosts[0]
@@ -3561,23 +3561,40 @@
         server_tcp_in_port = 22
         server_tcp_out_port = 4022
         client_tcp_in_port = 1234
-        client_udp_in_port = 1235
-        nat_addr_ip6 = self.compose_ip6(self.nat_addr, '64:ff9b::', 96)
+        client_tcp_out_port = 1235
+        server_nat_ip = "10.0.0.100"
+        client_nat_ip = "10.0.0.110"
+        server_nat_ip_n = socket.inet_pton(socket.AF_INET, server_nat_ip)
+        client_nat_ip_n = socket.inet_pton(socket.AF_INET, client_nat_ip)
+        server_nat_ip6 = self.compose_ip6(server_nat_ip, '64:ff9b::', 96)
+        client_nat_ip6 = self.compose_ip6(client_nat_ip, '64:ff9b::', 96)
 
-        self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
-                                                self.nat_addr_n)
+        self.vapi.nat64_add_del_pool_addr_range(server_nat_ip_n,
+                                                client_nat_ip_n)
         self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
         self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
 
         self.vapi.nat64_add_del_static_bib(server.ip6n,
-                                           self.nat_addr_n,
+                                           server_nat_ip_n,
                                            server_tcp_in_port,
                                            server_tcp_out_port,
                                            IP_PROTOS.tcp)
 
+        self.vapi.nat64_add_del_static_bib(server.ip6n,
+                                           server_nat_ip_n,
+                                           0,
+                                           0,
+                                           IP_PROTOS.gre)
+
+        self.vapi.nat64_add_del_static_bib(client.ip6n,
+                                           client_nat_ip_n,
+                                           client_tcp_in_port,
+                                           client_tcp_out_port,
+                                           IP_PROTOS.tcp)
+
         # client to server
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-             IPv6(src=client.ip6, dst=nat_addr_ip6) /
+             IPv6(src=client.ip6, dst=server_nat_ip6) /
              TCP(sport=client_tcp_in_port, dport=server_tcp_out_port))
         self.pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
@@ -3585,7 +3602,7 @@
         p = self.pg0.get_capture(1)
 
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-             IPv6(src=client.ip6, dst=nat_addr_ip6) /
+             IPv6(src=client.ip6, dst=server_nat_ip6, nh=IP_PROTOS.gre) /
              GRE() /
              IP(src=self.pg2.local_ip4, dst=self.pg2.remote_ip4) /
              TCP(sport=1234, dport=1234))
@@ -3595,16 +3612,16 @@
         p = self.pg0.get_capture(1)
         packet = p[0]
         try:
-            self.assertEqual(packet[IPv6].src, nat_addr_ip6)
+            self.assertEqual(packet[IPv6].src, client_nat_ip6)
             self.assertEqual(packet[IPv6].dst, server.ip6)
-            self.assertTrue(packet.haslayer(GRE))
+            self.assertEqual(packet[IPv6].nh, IP_PROTOS.gre)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
 
         # server to client
         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
-             IPv6(src=server.ip6, dst=nat_addr_ip6) /
+             IPv6(src=server.ip6, dst=client_nat_ip6, nh=IP_PROTOS.gre) /
              GRE() /
              IP(src=self.pg2.remote_ip4, dst=self.pg2.local_ip4) /
              TCP(sport=1234, dport=1234))
@@ -3614,9 +3631,9 @@
         p = self.pg0.get_capture(1)
         packet = p[0]
         try:
-            self.assertEqual(packet[IPv6].src, nat_addr_ip6)
+            self.assertEqual(packet[IPv6].src, server_nat_ip6)
             self.assertEqual(packet[IPv6].dst, client.ip6)
-            self.assertTrue(packet.haslayer(GRE))
+            self.assertEqual(packet[IPv6].nh, IP_PROTOS.gre)
         except:
             self.logger.error(ppp("Unexpected or invalid packet:", packet))
             raise
@@ -3702,9 +3719,11 @@
             self.logger.info(self.vapi.cli("show nat64 bib tcp"))
             self.logger.info(self.vapi.cli("show nat64 bib udp"))
             self.logger.info(self.vapi.cli("show nat64 bib icmp"))
+            self.logger.info(self.vapi.cli("show nat64 bib unknown"))
             self.logger.info(self.vapi.cli("show nat64 session table tcp"))
             self.logger.info(self.vapi.cli("show nat64 session table udp"))
             self.logger.info(self.vapi.cli("show nat64 session table icmp"))
+            self.logger.info(self.vapi.cli("show nat64 session table unknown"))
             self.clear_nat64()
 
 if __name__ == '__main__':