From 732036d677b84aa8eaea45f8059783e827622b77 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 8 Jun 2017 05:24:28 -0700 Subject: NAT64: ICMP error support 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 --- src/plugins/snat/nat64.c | 13 +++++- src/plugins/snat/nat64_in2out.c | 75 +++++++++++++++++++++++++++++++--- src/plugins/snat/nat64_out2in.c | 84 +++++++++++++++++++++++++++++++++++--- src/plugins/snat/snat.api | 39 ++++++++++++++++++ src/plugins/snat/snat.c | 6 ++- src/vnet/ip/ip4_to_ip6.h | 13 +++--- test/test_snat.py | 90 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 297 insertions(+), 23 deletions(-) diff --git a/src/plugins/snat/nat64.c b/src/plugins/snat/nat64.c index 9b6b3c8a..ef745473 100644 --- a/src/plugins/snat/nat64.c +++ b/src/plugins/snat/nat64.c @@ -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; } diff --git a/src/plugins/snat/nat64_in2out.c b/src/plugins/snat/nat64_in2out.c index d9d94a97..eba69326 100644 --- a/src/plugins/snat/nat64_in2out.c +++ b/src/plugins/snat/nat64_in2out.c @@ -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 diff --git a/src/plugins/snat/nat64_out2in.c b/src/plugins/snat/nat64_out2in.c index 3eed9974..c11bbb5a 100644 --- a/src/plugins/snat/nat64_out2in.c +++ b/src/plugins/snat/nat64_out2in.c @@ -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 diff --git a/src/plugins/snat/snat.api b/src/plugins/snat/snat.api index e6289af2..62c96058 100644 --- a/src/plugins/snat/snat.api +++ b/src/plugins/snat/snat.api @@ -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; +}; diff --git a/src/plugins/snat/snat.c b/src/plugins/snat/snat.c index e95abf27..351f8dc7 100644 --- a/src/plugins/snat/snat.c +++ b/src/plugins/snat/snat.c @@ -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; } diff --git a/src/vnet/ip/ip4_to_ip6.h b/src/vnet/ip/ip4_to_ip6.h index 96b8bf1e..965d27c3 100644 --- a/src/vnet/ip/ip4_to_ip6.h +++ b/src/vnet/ip/ip4_to_ip6.h @@ -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) { diff --git a/test/test_snat.py b/test/test_snat.py index 64fa3057..c6344a9e 100644 --- a/test/test_snat.py +++ b/test/test_snat.py @@ -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. -- cgit 1.2.3-korg