NAT64: ICMP error support 53/7053/2
authorMatus Fabian <matfabia@cisco.com>
Thu, 8 Jun 2017 12:24:28 +0000 (05:24 -0700)
committerOle Trøan <otroan@employees.org>
Fri, 9 Jun 2017 07:30:14 +0000 (07:30 +0000)
Added ICMP error messages translation.
Added check for multi thread (not supported yet, so init failed).
Added API definition for custom NAT64 refix.

Change-Id: Ice2f04631af63e594aecc09087a1cf59f3b676fb
Signed-off-by: Matus Fabian <matfabia@cisco.com>
src/plugins/snat/nat64.c
src/plugins/snat/nat64_in2out.c
src/plugins/snat/nat64_out2in.c
src/plugins/snat/snat.api
src/plugins/snat/snat.c
src/vnet/ip/ip4_to_ip6.h
test/test_snat.py

index 9b6b3c8..ef74547 100644 (file)
@@ -45,9 +45,19 @@ nat64_init (vlib_main_t * vm)
 {
   nat64_main_t *nm = &nat64_main;
   clib_error_t *error = 0;
+  vlib_thread_main_t *tm = vlib_get_thread_main ();
+
+  if (tm->n_vlib_mains > 1)
+    {
+      error = clib_error_return (0, "multi thread not supported");
+      goto error;
+    }
 
   if (nat64_db_init (&nm->db))
-    error = clib_error_return (0, "NAT64 DB init failed");
+    {
+      error = clib_error_return (0, "NAT64 DB init failed");
+      goto error;
+    }
 
   /* set session timeouts to default values */
   nm->udp_timeout = SNAT_UDP_TIMEOUT;
@@ -56,6 +66,7 @@ nat64_init (vlib_main_t * vm)
   nm->tcp_est_timeout = SNAT_TCP_ESTABLISHED_TIMEOUT;
   nm->tcp_incoming_syn_timeout = SNAT_TCP_INCOMING_SYN;
 
+error:
   return error;
 }
 
index d9d94a9..eba6932 100644 (file)
@@ -221,9 +221,11 @@ nat64_in2out_icmp_set_cb (ip6_header_t * ip6, ip4_header_t * ip4, void *arg)
     }
   else
     {
-      //TODO: ICMP error
-      clib_warning ("not ICMP echo request/reply, %u", icmp->type);
-      return -1;
+      if (!vec_len (nm->addr_pool))
+       return -1;
+
+      ip4->src_address.as_u32 = nm->addr_pool[0].addr.as_u32;
+      ip4->dst_address.as_u32 = daddr.ip4.as_u32;
     }
 
   return 0;
@@ -231,10 +233,71 @@ nat64_in2out_icmp_set_cb (ip6_header_t * ip6, ip4_header_t * ip4, void *arg)
 
 static int
 nat64_in2out_inner_icmp_set_cb (ip6_header_t * ip6, ip4_header_t * ip4,
-                               void *ctx)
+                               void *arg)
 {
-  //TODO:
-  return -1;
+  nat64_main_t *nm = &nat64_main;
+  nat64_in2out_set_ctx_t *ctx = arg;
+  nat64_db_st_entry_t *ste;
+  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);
+
+  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];
+
+  if (proto == SNAT_PROTOCOL_ICMP)
+    {
+      icmp46_header_t *icmp = ip6_next_header (ip6);
+      u16 in_id = ((u16 *) (icmp))[2];
+
+      if (!
+         (icmp->type == ICMP4_echo_request
+          || icmp->type == ICMP4_echo_reply))
+       return -1;
+
+      ste =
+       nat64_db_st_entry_find (&nm->db, &daddr, &saddr, in_id, 0, proto,
+                               fib_index, 1);
+      if (!ste)
+       return -1;
+
+      bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index);
+      if (!bibe)
+       return -1;
+
+      ip4->dst_address.as_u32 = bibe->out_addr.as_u32;
+      ((u16 *) (icmp))[2] = bibe->out_port;
+      ip4->src_address.as_u32 = saddr.ip4.as_u32;
+    }
+  else
+    {
+      udp_header_t *udp = ip6_next_header (ip6);
+      u16 sport = udp->src_port;
+      u16 dport = udp->dst_port;
+
+      ste =
+       nat64_db_st_entry_find (&nm->db, &daddr, &saddr, dport, sport, proto,
+                               fib_index, 1);
+      if (!ste)
+       return -1;
+
+      bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index);
+      if (!bibe)
+       return -1;
+
+      ip4->dst_address.as_u32 = bibe->out_addr.as_u32;
+      udp->dst_port = bibe->out_port;
+      ip4->src_address.as_u32 = saddr.ip4.as_u32;
+    }
+
+  return 0;
 }
 
 static uword
index 3eed997..c11bbb5 100644 (file)
@@ -208,9 +208,12 @@ nat64_out2in_icmp_set_cb (ip4_header_t * ip4, ip6_header_t * ip6, void *arg)
     }
   else
     {
-      //TODO: ICMP error
-      clib_warning ("not ICMP echo request/reply, %u", icmp->type);
-      return -1;
+      ip6_header_t *inner_ip6 = (ip6_header_t *) u8_ptr_add (icmp, 8);
+
+      ip6->src_address.as_u64[0] = ip6_saddr.as_u64[0];
+      ip6->src_address.as_u64[1] = ip6_saddr.as_u64[1];
+      ip6->dst_address.as_u64[0] = inner_ip6->src_address.as_u64[0];
+      ip6->dst_address.as_u64[1] = inner_ip6->src_address.as_u64[1];
     }
 
   return 0;
@@ -218,10 +221,79 @@ nat64_out2in_icmp_set_cb (ip4_header_t * ip4, ip6_header_t * ip6, void *arg)
 
 static int
 nat64_out2in_inner_icmp_set_cb (ip4_header_t * ip4, ip6_header_t * ip6,
-                               void *ctx)
+                               void *arg)
 {
-  //TODO:
-  return -1;
+  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_daddr;
+  u32 sw_if_index, fib_index;
+  snat_protocol_t proto = ip_proto_to_snat_proto (ip4->protocol);
+
+  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);
+
+  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;
+
+  memcpy (&ip6_daddr, well_known_prefix, sizeof (ip6_daddr));
+  ip6_daddr.as_u32[3] = ip4->dst_address.as_u32;
+
+  if (proto == SNAT_PROTOCOL_ICMP)
+    {
+      icmp46_header_t *icmp = ip4_next_header (ip4);
+      u16 out_id = ((u16 *) (icmp))[2];
+
+      if (!
+         (icmp->type == ICMP6_echo_request
+          || icmp->type == ICMP6_echo_reply))
+       return -1;
+
+      ste =
+       nat64_db_st_entry_find (&nm->db, &saddr, &daddr, out_id, 0, proto,
+                               fib_index, 0);
+      if (!ste)
+       return -1;
+
+      bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index);
+      if (!bibe)
+       return -1;
+
+      ip6->dst_address.as_u64[0] = ip6_daddr.as_u64[0];
+      ip6->dst_address.as_u64[1] = ip6_daddr.as_u64[1];
+      ip6->src_address.as_u64[0] = bibe->in_addr.as_u64[0];
+      ip6->src_address.as_u64[1] = bibe->in_addr.as_u64[1];
+      ((u16 *) (icmp))[2] = bibe->in_port;
+    }
+  else
+    {
+      udp_header_t *udp = ip4_next_header (ip4);
+      u16 dport = udp->dst_port;
+      u16 sport = udp->src_port;
+
+      ste =
+       nat64_db_st_entry_find (&nm->db, &saddr, &daddr, sport, dport, proto,
+                               fib_index, 0);
+      if (!ste)
+       return -1;
+
+      bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index);
+      if (!bibe)
+       return -1;
+
+      ip6->dst_address.as_u64[0] = ip6_daddr.as_u64[0];
+      ip6->dst_address.as_u64[1] = ip6_daddr.as_u64[1];
+      ip6->src_address.as_u64[0] = bibe->in_addr.as_u64[0];
+      ip6->src_address.as_u64[1] = bibe->in_addr.as_u64[1];
+      udp->src_port = bibe->in_port;
+    }
+
+  return 0;
 }
 
 static uword
index e6289af..62c9605 100644 (file)
@@ -812,3 +812,42 @@ define nat64_st_details {
   u32 vrf_id;
   u8 proto;
 };
+
+/** \brief Add/del NAT64 prefix
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param prefix - NAT64 prefix
+    @param prefix - NAT64 prefix length
+    @param vrf_id - VRF id of tenant
+    @param is_add - 1 if add, 0 if delete
+*/
+autoreply define nat64_add_del_prefix {
+  u32 client_index;
+  u32 context;
+  u8 prefix[16];
+  u8 prefix_len;
+  u32 vrf_id;
+  u8 is_add;
+};
+
+/** \brief Dump NAT64 prefix
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+*/
+define nat64_prefix_dump {
+  u32 client_index;
+  u32 context;
+};
+
+/** \brief Dump NAT64 prefix details response
+    @param context - sender context, to match reply w/ request
+    @param prefix - NAT64 prefix
+    @param prefix - NAT64 prefix length
+    @param vrf_id - VRF id of tenant
+*/
+define nat64_prefix_details {
+  u32 context;
+  u8 prefix[16];
+  u8 prefix_len;
+  u32 vrf_id;
+};
index e95abf2..351f8dc 100644 (file)
@@ -725,7 +725,7 @@ snat_ip4_add_del_interface_address_cb (ip4_main_t * im,
 static clib_error_t * snat_init (vlib_main_t * vm)
 {
   snat_main_t * sm = &snat_main;
-  clib_error_t * error = 0;
+  clib_error_t * error = 0, * error_nat64 = 0;
   ip4_main_t * im = &ip4_main;
   ip_lookup_main_t * lm = &im->lookup_main;
   uword *p;
@@ -782,7 +782,9 @@ static clib_error_t * snat_init (vlib_main_t * vm)
   /* Init IPFIX logging */
   snat_ipfix_logging_init(vm);
 
-  error = nat64_init(vm);
+  error_nat64 = nat64_init(vm);
+  if (error_nat64)
+    clib_warning("NAT64 init failed: %U", format_clib_error, error_nat64);
 
   return error;
 }
index 96b8bf1..965d27c 100644 (file)
@@ -310,14 +310,11 @@ icmp_to_icmp6 (vlib_buffer_t * p, ip4_to_ip6_set_fn_t fn, void *ctx,
       else if (PREDICT_TRUE (inner_ip4->protocol == IP_PROTOCOL_UDP))
        {
          inner_L4_checksum = &((udp_header_t *) (inner_ip4 + 1))->checksum;
-         if (!*inner_L4_checksum)
-           {
-             return -1;
-           }
-         *inner_L4_checksum =
-           ip_csum_fold (ip_csum_sub_even
-                         (*inner_L4_checksum,
-                          *((u64 *) (&inner_ip4->src_address))));
+         if (*inner_L4_checksum)
+           *inner_L4_checksum =
+             ip_csum_fold (ip_csum_sub_even
+                           (*inner_L4_checksum,
+                            *((u64 *) (&inner_ip4->src_address))));
        }
       else if (inner_ip4->protocol == IP_PROTOCOL_ICMP)
        {
index 64fa305..c6344a9 100644 (file)
@@ -8,6 +8,7 @@ from framework import VppTestCase, VppTestRunner, running_extended_tests
 from scapy.layers.inet import IP, TCP, UDP, ICMP
 from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
 from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply
+from scapy.layers.inet6 import ICMPv6DestUnreach, IPerror6
 from scapy.layers.l2 import Ether, ARP
 from scapy.data import IP_PROTOS
 from scapy.packet import bind_layers
@@ -2635,6 +2636,95 @@ class TestNAT64(MethodHolder):
         ses_num_after_timeout = self.nat64_get_ses_num()
         self.assertNotEqual(ses_num_before_timeout, ses_num_after_timeout)
 
+    def test_icmp_error(self):
+        """ NAT64 ICMP Error message translation """
+        self.tcp_port_in = 6303
+        self.udp_port_in = 6304
+        self.icmp_id_in = 6305
+
+        ses_num_start = self.nat64_get_ses_num()
+
+        self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+                                                self.nat_addr_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)
+
+        # send some packets to create sessions
+        pkts = self.create_stream_in_ip6(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture_ip4 = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture_ip4, packet_num=3,
+                                nat_ip=self.nat_addr,
+                                dst_ip=self.pg1.remote_ip4)
+
+        pkts = self.create_stream_out(self.pg1, dst_ip=self.nat_addr)
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture_ip6 = self.pg0.get_capture(len(pkts))
+        ip = IPv6(src=''.join(['64:ff9b::', self.pg1.remote_ip4]))
+        self.verify_capture_in_ip6(capture_ip6, ip[IPv6].src,
+                                   self.pg0.remote_ip6)
+
+        # in2out
+        pkts = [Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+                IPv6(src=self.pg0.remote_ip6, dst=ip[IPv6].src) /
+                ICMPv6DestUnreach(code=1) /
+                packet[IPv6] for packet in capture_ip6]
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        for packet in capture:
+            try:
+                self.assertEqual(packet[IP].src, self.nat_addr)
+                self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
+                self.assertEqual(packet[ICMP].type, 3)
+                self.assertEqual(packet[ICMP].code, 13)
+                inner = packet[IPerror]
+                self.assertEqual(inner.src, self.pg1.remote_ip4)
+                self.assertEqual(inner.dst, self.nat_addr)
+                if inner.haslayer(TCPerror):
+                    self.assertEqual(inner[TCPerror].dport, self.tcp_port_out)
+                elif inner.haslayer(UDPerror):
+                    self.assertEqual(inner[UDPerror].dport, self.udp_port_out)
+                else:
+                    self.assertEqual(inner[ICMPerror].id, self.icmp_id_out)
+            except:
+                self.logger.error(ppp("Unexpected or invalid packet:", packet))
+                raise
+
+        # out2in
+        pkts = [Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+                IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+                ICMP(type=3, code=13) /
+                packet[IP] for packet in capture_ip4]
+        self.pg1.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(len(pkts))
+        for packet in capture:
+            try:
+                self.assertEqual(packet[IPv6].src, ip.src)
+                self.assertEqual(packet[IPv6].dst, self.pg0.remote_ip6)
+                icmp = packet[ICMPv6DestUnreach]
+                self.assertEqual(icmp.code, 1)
+                inner = icmp[IPerror6]
+                self.assertEqual(inner.src, self.pg0.remote_ip6)
+                self.assertEqual(inner.dst, ip.src)
+                if inner.haslayer(TCPerror):
+                    self.assertEqual(inner[TCPerror].sport, self.tcp_port_in)
+                elif inner.haslayer(UDPerror):
+                    self.assertEqual(inner[UDPerror].sport, self.udp_port_in)
+                else:
+                    self.assertEqual(inner[ICMPv6EchoRequest].id,
+                                     self.icmp_id_in)
+            except:
+                self.logger.error(ppp("Unexpected or invalid packet:", packet))
+                raise
+
     def nat64_get_ses_num(self):
         """
         Return number of active NAT64 sessions.