aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorElias Rudberg <elias.rudberg@bahnhof.net>2020-12-08 14:21:19 +0100
committerAndrew Yourtchenko <ayourtch@gmail.com>2020-12-10 09:30:46 +0000
commit3b82d3e39331c5c6cade1014444091ba31052551 (patch)
tree756b83f3914ce9ab56116f2f97dfbc2018f8116d /src
parenta23dd5dbb5cde4cb12b3d7b0bed8156e2ff7e6b5 (diff)
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 <elias.rudberg@bahnhof.net> Change-Id: I21a59ae7423f40abeff9fc0411330da58b3011f0
Diffstat (limited to 'src')
-rw-r--r--src/plugins/nat/nat44_hairpinning.c12
-rw-r--r--src/plugins/nat/test/test_nat.py88
2 files changed, 100 insertions, 0 deletions
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(