From 3b82d3e39331c5c6cade1014444091ba31052551 Mon Sep 17 00:00:00 2001 From: Elias Rudberg Date: Tue, 8 Dec 2020 14:21:19 +0100 Subject: nat: avoid hairpinning infinite loop problem Fix in nat44 hairpinning code to check if anything was actually changed in the snat_hairpinning() routine, and return 0 if nothing changed. This helps avoid an infinite loop repeating the three nodes nat44-hairpinning-->ip4-lookup-->ip4-local in case there was no change. Also add a corresponding test case. This is essentially a cherry-pick of change 30284 but the automatic cherry-picking did not work because of some filename changes. Type: fix Signed-off-by: Elias Rudberg Change-Id: I21a59ae7423f40abeff9fc0411330da58b3011f0 --- src/plugins/nat/nat44_hairpinning.c | 12 +++++ src/plugins/nat/test/test_nat.py | 88 +++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/src/plugins/nat/nat44_hairpinning.c b/src/plugins/nat/nat44_hairpinning.c index 9eadcf30832..2859046ae05 100644 --- a/src/plugins/nat/nat44_hairpinning.c +++ b/src/plugins/nat/nat44_hairpinning.c @@ -108,6 +108,7 @@ snat_hairpinning (vlib_main_t * vm, vlib_node_runtime_t * node, ip4_address_t sm0_addr; u16 sm0_port; u32 sm0_fib_index; + u32 old_sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_TX]; /* Check if destination is static mappings */ if (!snat_static_mapping_match (sm, ip0->dst_address, udp0->dst_port, sm->outside_fib_index, proto0, @@ -159,6 +160,17 @@ snat_hairpinning (vlib_main_t * vm, vlib_node_runtime_t * node, vnet_buffer (b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index; } + /* Check if anything has changed and if not, then return 0. This + helps avoid infinite loop, repeating the three nodes + nat44-hairpinning-->ip4-lookup-->ip4-local, in case nothing has + changed. */ + old_dst_addr0 = ip0->dst_address.as_u32; + old_dst_port0 = tcp0->dst; + if (new_dst_addr0 == old_dst_addr0 + && new_dst_port0 == old_dst_port0 + && vnet_buffer (b0)->sw_if_index[VLIB_TX] == old_sw_if_index) + return 0; + /* Destination is behind the same NAT, use internal address and port */ if (new_dst_addr0) { diff --git a/src/plugins/nat/test/test_nat.py b/src/plugins/nat/test/test_nat.py index a305b7aa9f4..94718281193 100644 --- a/src/plugins/nat/test/test_nat.py +++ b/src/plugins/nat/test/test_nat.py @@ -2610,6 +2610,94 @@ class TestNAT44(MethodHolder): self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise + def test_hairpinning_avoid_inf_loop(self): + """ NAT44 hairpinning - 1:1 NAPT avoid infinite loop """ + + host = self.pg0.remote_hosts[0] + server = self.pg0.remote_hosts[1] + host_in_port = 1234 + host_out_port = 0 + server_in_port = 5678 + server_out_port = 8765 + + self.nat44_add_address(self.nat_addr) + flags = self.config_flags.NAT_IS_INSIDE + self.vapi.nat44_interface_add_del_feature( + sw_if_index=self.pg0.sw_if_index, + flags=flags, is_add=1) + self.vapi.nat44_interface_add_del_feature( + sw_if_index=self.pg1.sw_if_index, + is_add=1) + + # add static mapping for server + self.nat44_add_static_mapping(server.ip4, self.nat_addr, + server_in_port, server_out_port, + proto=IP_PROTOS.tcp) + + # add another static mapping that maps pg0.local_ip4 address to itself + self.nat44_add_static_mapping(self.pg0.local_ip4, self.pg0.local_ip4) + + # send packet from host to VPP (the packet should get dropped) + p = (Ether(src=host.mac, dst=self.pg0.local_mac) / + IP(src=host.ip4, dst=self.pg0.local_ip4) / + TCP(sport=host_in_port, dport=server_out_port)) + self.pg0.add_stream(p) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + # Here VPP used to crash due to an infinite loop + + cnt = self.statistics.get_counter('/nat44/hairpinning')[0] + # send packet from host to server + p = (Ether(src=host.mac, dst=self.pg0.local_mac) / + IP(src=host.ip4, dst=self.nat_addr) / + TCP(sport=host_in_port, dport=server_out_port)) + self.pg0.add_stream(p) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + capture = self.pg0.get_capture(1) + p = capture[0] + try: + ip = p[IP] + tcp = p[TCP] + self.assertEqual(ip.src, self.nat_addr) + self.assertEqual(ip.dst, server.ip4) + self.assertNotEqual(tcp.sport, host_in_port) + self.assertEqual(tcp.dport, server_in_port) + self.assert_packet_checksums_valid(p) + host_out_port = tcp.sport + except: + self.logger.error(ppp("Unexpected or invalid packet:", p)) + raise + + after = self.statistics.get_counter('/nat44/hairpinning')[0] + if_idx = self.pg0.sw_if_index + self.assertEqual(after[if_idx] - cnt[if_idx], 1) + + # send reply from server to host + p = (Ether(src=server.mac, dst=self.pg0.local_mac) / + IP(src=server.ip4, dst=self.nat_addr) / + TCP(sport=server_in_port, dport=host_out_port)) + self.pg0.add_stream(p) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + capture = self.pg0.get_capture(1) + p = capture[0] + try: + ip = p[IP] + tcp = p[TCP] + self.assertEqual(ip.src, self.nat_addr) + self.assertEqual(ip.dst, host.ip4) + self.assertEqual(tcp.sport, server_out_port) + self.assertEqual(tcp.dport, host_in_port) + self.assert_packet_checksums_valid(p) + except: + self.logger.error(ppp("Unexpected or invalid packet:", p)) + raise + + after = self.statistics.get_counter('/nat44/hairpinning')[0] + if_idx = self.pg0.sw_if_index + self.assertEqual(after[if_idx] - cnt[if_idx], 2) + def test_interface_addr(self): """ Acquire NAT44 addresses from interface """ self.vapi.nat44_add_del_interface_addr( -- cgit 1.2.3-korg