Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | """NAT44 ED output-feature tests""" |
| 3 | |
| 4 | import random |
| 5 | import unittest |
Dave Wallace | cf9356d | 2024-07-23 01:28:19 -0400 | [diff] [blame] | 6 | import struct |
| 7 | import socket |
Dave Wallace | 8800f73 | 2023-08-31 00:47:44 -0400 | [diff] [blame] | 8 | from scapy.layers.inet import Ether, IP, TCP |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 9 | from scapy.packet import Raw |
| 10 | from scapy.data import IP_PROTOS |
Dave Wallace | 8800f73 | 2023-08-31 00:47:44 -0400 | [diff] [blame] | 11 | from framework import VppTestCase |
| 12 | from asfframework import VppTestRunner |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 13 | from vpp_papi import VppEnum |
Dmitry Valter | 34fa0ce | 2024-03-11 10:38:46 +0000 | [diff] [blame] | 14 | from config import config |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 15 | |
| 16 | |
| 17 | def get_nat44_ed_in2out_worker_index(ip, vpp_worker_count): |
| 18 | if 0 == vpp_worker_count: |
| 19 | return 0 |
| 20 | numeric = socket.inet_aton(ip) |
| 21 | numeric = struct.unpack("!L", numeric)[0] |
| 22 | numeric = socket.htonl(numeric) |
| 23 | h = numeric + (numeric >> 8) + (numeric >> 16) + (numeric >> 24) |
| 24 | return 1 + h % vpp_worker_count |
| 25 | |
| 26 | |
Dmitry Valter | 34fa0ce | 2024-03-11 10:38:46 +0000 | [diff] [blame] | 27 | @unittest.skipIf("nat" in config.excluded_plugins, "Exclude NAT plugin tests") |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 28 | class TestNAT44EDOutput(VppTestCase): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 29 | """NAT44 ED output feature Test Case""" |
| 30 | |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 31 | max_sessions = 1024 |
| 32 | |
| 33 | @classmethod |
| 34 | def setUpClass(cls): |
| 35 | super().setUpClass() |
| 36 | cls.create_pg_interfaces(range(2)) |
| 37 | cls.interfaces = list(cls.pg_interfaces) |
| 38 | |
| 39 | @classmethod |
| 40 | def tearDownClass(cls): |
| 41 | super().tearDownClass() |
| 42 | |
| 43 | def setUp(self): |
| 44 | super().setUp() |
| 45 | for i in self.interfaces: |
| 46 | i.admin_up() |
| 47 | i.config_ip4() |
| 48 | i.resolve_arp() |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 49 | self.vapi.nat44_ed_plugin_enable_disable(sessions=self.max_sessions, enable=1) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 50 | |
| 51 | def tearDown(self): |
| 52 | if not self.vpp_dead: |
| 53 | self.logger.debug(self.vapi.cli("show nat44 sessions")) |
| 54 | super().tearDown() |
| 55 | if not self.vpp_dead: |
| 56 | for i in self.pg_interfaces: |
| 57 | i.unconfig_ip4() |
| 58 | i.admin_down() |
| 59 | self.vapi.nat44_ed_plugin_enable_disable(enable=0) |
| 60 | |
| 61 | def test_static_dynamic(self): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 62 | """Create static mapping which matches existing dynamic mapping""" |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 63 | |
Filip Varga | b681082 | 2022-02-15 11:56:07 -0800 | [diff] [blame] | 64 | config = self.vapi.nat44_show_running_config() |
| 65 | old_timeouts = config.timeouts |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 66 | new_transitory = 2 |
| 67 | self.vapi.nat_set_timeouts( |
| 68 | udp=old_timeouts.udp, |
| 69 | tcp_established=old_timeouts.tcp_established, |
| 70 | icmp=old_timeouts.icmp, |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 71 | tcp_transitory=new_transitory, |
| 72 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 73 | |
| 74 | local_host = self.pg0.remote_ip4 |
| 75 | remote_host = self.pg1.remote_ip4 |
| 76 | nat_intf = self.pg1 |
| 77 | outside_addr = nat_intf.local_ip4 |
| 78 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 79 | self.vapi.nat44_add_del_address_range( |
| 80 | first_ip_address=outside_addr, |
| 81 | last_ip_address=outside_addr, |
| 82 | vrf_id=0xFFFFFFFF, |
| 83 | is_add=1, |
| 84 | flags=0, |
| 85 | ) |
| 86 | self.vapi.nat44_interface_add_del_feature( |
| 87 | sw_if_index=self.pg0.sw_if_index, is_add=1 |
| 88 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 89 | self.vapi.nat44_interface_add_del_feature( |
| 90 | sw_if_index=self.pg0.sw_if_index, |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 91 | flags=VppEnum.vl_api_nat_config_flags_t.NAT_IS_INSIDE, |
| 92 | is_add=1, |
| 93 | ) |
Filip Varga | b681082 | 2022-02-15 11:56:07 -0800 | [diff] [blame] | 94 | self.vapi.nat44_ed_add_del_output_interface( |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 95 | sw_if_index=self.pg1.sw_if_index, is_add=1 |
| 96 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 97 | |
| 98 | thread_index = get_nat44_ed_in2out_worker_index( |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 99 | local_host, self.vpp_worker_count |
| 100 | ) |
| 101 | port_per_thread = int((0xFFFF - 1024) / max(1, self.vpp_worker_count)) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 102 | local_sport = 1024 + random.randint(1, port_per_thread) |
| 103 | if self.vpp_worker_count > 0: |
| 104 | local_sport += port_per_thread * (thread_index - 1) |
| 105 | |
| 106 | remote_dport = 10000 |
| 107 | |
| 108 | pg0 = self.pg0 |
| 109 | pg1 = self.pg1 |
| 110 | |
| 111 | # first setup a dynamic TCP session |
| 112 | |
| 113 | # SYN packet in->out |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 114 | p = ( |
| 115 | Ether(src=pg0.remote_mac, dst=pg0.local_mac) |
| 116 | / IP(src=local_host, dst=remote_host) |
| 117 | / TCP(sport=local_sport, dport=remote_dport, flags="S") |
| 118 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 119 | p = self.send_and_expect(pg0, [p], pg1)[0] |
| 120 | |
| 121 | self.assertEqual(p[IP].src, outside_addr) |
| 122 | self.assertEqual(p[TCP].sport, local_sport) |
| 123 | outside_port = p[TCP].sport |
| 124 | |
| 125 | # SYN+ACK packet out->in |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 126 | p = ( |
| 127 | Ether(src=pg1.remote_mac, dst=pg1.local_mac) |
| 128 | / IP(src=remote_host, dst=outside_addr) |
| 129 | / TCP(sport=remote_dport, dport=outside_port, flags="SA") |
| 130 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 131 | self.send_and_expect(pg1, [p], pg0) |
| 132 | |
| 133 | # ACK packet in->out |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 134 | p = ( |
| 135 | Ether(src=pg0.remote_mac, dst=pg0.local_mac) |
| 136 | / IP(src=local_host, dst=remote_host) |
| 137 | / TCP(sport=local_sport, dport=remote_dport, flags="A") |
| 138 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 139 | self.send_and_expect(pg0, [p], pg1) |
| 140 | |
| 141 | # now we have a session up, create a conflicting static mapping |
| 142 | self.vapi.nat44_add_del_static_mapping( |
| 143 | is_add=1, |
| 144 | local_ip_address=local_host, |
| 145 | external_ip_address=outside_addr, |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 146 | external_sw_if_index=0xFFFFFFFF, |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 147 | local_port=local_sport, |
| 148 | external_port=outside_port, |
| 149 | protocol=IP_PROTOS.tcp, |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 150 | flags=VppEnum.vl_api_nat_config_flags_t.NAT_IS_OUT2IN_ONLY, |
| 151 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 152 | |
| 153 | sessions = self.vapi.nat44_user_session_dump(local_host, 0) |
| 154 | self.assertEqual(1, len(sessions)) |
| 155 | |
| 156 | # now send some more data over existing session - it should pass |
| 157 | |
| 158 | # in->out |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 159 | p = ( |
| 160 | Ether(src=pg0.remote_mac, dst=pg0.local_mac) |
| 161 | / IP(src=local_host, dst=remote_host) |
| 162 | / TCP(sport=local_sport, dport=remote_dport) |
| 163 | / Raw("zippity zap") |
| 164 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 165 | self.send_and_expect(pg0, [p], pg1) |
| 166 | |
| 167 | # out->in |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 168 | p = ( |
| 169 | Ether(src=pg1.remote_mac, dst=pg1.local_mac) |
| 170 | / IP(src=remote_host, dst=outside_addr) |
| 171 | / TCP(sport=remote_dport, dport=outside_port) |
| 172 | / Raw("flippity flop") |
| 173 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 174 | self.send_and_expect(pg1, [p], pg0) |
| 175 | |
| 176 | # now close the session |
| 177 | |
| 178 | # FIN packet in -> out |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 179 | p = ( |
| 180 | Ether(src=pg0.remote_mac, dst=pg0.local_mac) |
| 181 | / IP(src=local_host, dst=remote_host) |
| 182 | / TCP(sport=local_sport, dport=remote_dport, flags="FA", seq=100, ack=300) |
| 183 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 184 | self.send_and_expect(pg0, [p], pg1) |
| 185 | |
| 186 | # FIN+ACK packet out -> in |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 187 | p = ( |
| 188 | Ether(src=pg1.remote_mac, dst=pg1.local_mac) |
| 189 | / IP(src=remote_host, dst=outside_addr) |
| 190 | / TCP(sport=remote_dport, dport=outside_port, flags="FA", seq=300, ack=101) |
| 191 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 192 | self.send_and_expect(pg1, [p], pg0) |
| 193 | |
| 194 | # ACK packet in -> out |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 195 | p = ( |
| 196 | Ether(src=pg0.remote_mac, dst=pg0.local_mac) |
| 197 | / IP(src=local_host, dst=remote_host) |
| 198 | / TCP(sport=local_sport, dport=remote_dport, flags="A", seq=101, ack=301) |
| 199 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 200 | self.send_and_expect(pg0, [p], pg1) |
| 201 | |
| 202 | # session now in transitory timeout |
| 203 | # try SYN packet in->out - should be dropped |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 204 | p = ( |
| 205 | Ether(src=pg0.remote_mac, dst=pg0.local_mac) |
| 206 | / IP(src=local_host, dst=remote_host) |
| 207 | / TCP(sport=local_sport, dport=remote_dport, flags="S") |
| 208 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 209 | pg0.add_stream(p) |
| 210 | self.pg_enable_capture() |
| 211 | self.pg_start() |
| 212 | |
| 213 | self.sleep(new_transitory, "wait for transitory timeout") |
| 214 | pg0.assert_nothing_captured(0) |
| 215 | |
| 216 | # session should still exist |
| 217 | sessions = self.vapi.nat44_user_session_dump(pg0.remote_ip4, 0) |
| 218 | self.assertEqual(1, len(sessions)) |
| 219 | |
| 220 | # send FIN+ACK packet in->out - will cause session to be wiped |
| 221 | # but won't create a new session |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 222 | p = ( |
Steven Luong | e4238aa | 2024-04-19 09:49:20 -0700 | [diff] [blame] | 223 | Ether(src=pg1.remote_mac, dst=pg1.local_mac) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 224 | / IP(src=local_host, dst=remote_host) |
| 225 | / TCP(sport=local_sport, dport=remote_dport, flags="FA", seq=300, ack=101) |
| 226 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 227 | pg1.add_stream(p) |
| 228 | self.pg_enable_capture() |
| 229 | self.pg_start() |
| 230 | pg0.assert_nothing_captured(0) |
| 231 | |
| 232 | sessions = self.vapi.nat44_user_session_dump(pg0.remote_ip4, 0) |
| 233 | self.assertEqual(0, len(sessions)) |
| 234 | |
| 235 | # create a new session and make sure the outside port is remapped |
| 236 | # SYN packet in->out |
| 237 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 238 | p = ( |
| 239 | Ether(src=pg0.remote_mac, dst=pg0.local_mac) |
| 240 | / IP(src=local_host, dst=remote_host) |
| 241 | / TCP(sport=local_sport, dport=remote_dport, flags="S") |
| 242 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 243 | p = self.send_and_expect(pg0, [p], pg1)[0] |
| 244 | |
| 245 | self.assertEqual(p[IP].src, outside_addr) |
| 246 | self.assertNotEqual(p[TCP].sport, local_sport) |
| 247 | |
| 248 | # make sure static mapping works and creates a new session |
| 249 | # SYN packet out->in |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 250 | p = ( |
| 251 | Ether(src=pg1.remote_mac, dst=pg1.local_mac) |
| 252 | / IP(src=remote_host, dst=outside_addr) |
| 253 | / TCP(sport=remote_dport, dport=outside_port, flags="S") |
| 254 | ) |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 255 | self.send_and_expect(pg1, [p], pg0) |
| 256 | |
| 257 | sessions = self.vapi.nat44_user_session_dump(pg0.remote_ip4, 0) |
| 258 | self.assertEqual(2, len(sessions)) |
| 259 | |
| 260 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 261 | if __name__ == "__main__": |
Klement Sekera | ff334db | 2021-05-26 13:02:35 +0200 | [diff] [blame] | 262 | unittest.main(testRunner=VppTestRunner) |