4 from random import shuffle, randrange
6 from framework import VppTestCase
7 from asfframework import VppTestRunner
9 from scapy.packet import Raw
10 from scapy.layers.l2 import Ether, GRE
11 from scapy.layers.inet import IP, UDP, ICMP, icmptypes
12 from scapy.layers.inet6 import (
24 from util import ppp, fragment_rfc791, fragment_rfc8200
25 from vpp_gre_interface import VppGreInterface
26 from vpp_ip_route import VppIpRoute, VppRoutePath
27 from vpp_papi import VppEnum
29 # 35 is enough to have >257 400-byte fragments
30 test_packet_count = 35
33 class TestIPv4Reassembly(VppTestCase):
40 cls.create_pg_interfaces([0, 1])
44 # setup all interfaces
45 for i in cls.pg_interfaces:
51 cls.packet_sizes = [64, 512, 1518, 9018]
52 cls.padding = " abcdefghijklmn"
53 cls.create_stream(cls.packet_sizes)
54 cls.create_fragments()
57 def tearDownClass(cls):
58 super().tearDownClass()
61 """Test setup - force timeout on existing reassemblies"""
63 self.vapi.ip_reassembly_enable_disable(
64 sw_if_index=self.src_if.sw_if_index, enable_ip4=True
66 self.vapi.ip_reassembly_set(
68 max_reassemblies=1000,
69 max_reassembly_length=1000,
70 expire_walk_interval_ms=10,
72 self.virtual_sleep(0.25)
73 self.vapi.ip_reassembly_set(
75 max_reassemblies=1000,
76 max_reassembly_length=1000,
77 expire_walk_interval_ms=10000,
81 self.vapi.ip_reassembly_enable_disable(
82 sw_if_index=self.src_if.sw_if_index, enable_ip4=False
86 def show_commands_at_teardown(self):
87 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
88 self.logger.debug(self.vapi.ppcli("show buffers"))
91 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
92 """Create input packet stream
94 :param list packet_sizes: Required packet sizes.
96 for i in range(0, packet_count):
97 info = cls.create_packet_info(cls.src_if, cls.src_if)
98 payload = cls.info_to_payload(info)
100 Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac)
102 id=info.index, src=cls.src_if.remote_ip4, dst=cls.dst_if.remote_ip4
104 / UDP(sport=1234, dport=5678)
107 size = packet_sizes[(i // 2) % len(packet_sizes)]
108 cls.extend_packet(p, size, cls.padding)
112 def create_fragments(cls):
113 infos = cls._packet_infos
115 for index, info in infos.items():
117 # cls.logger.debug(ppp("Packet:",
118 # p.__class__(scapy.compat.raw(p))))
119 fragments_400 = fragment_rfc791(p, 400)
120 fragments_300 = fragment_rfc791(p, 300)
121 fragments_200 = [x for f in fragments_400 for x in fragment_rfc791(f, 200)]
122 cls.pkt_infos.append((index, fragments_400, fragments_300, fragments_200))
123 cls.fragments_400 = [x for (_, frags, _, _) in cls.pkt_infos for x in frags]
124 cls.fragments_300 = [x for (_, _, frags, _) in cls.pkt_infos for x in frags]
125 cls.fragments_200 = [x for (_, _, _, frags) in cls.pkt_infos for x in frags]
127 "Fragmented %s packets into %s 400-byte fragments, "
128 "%s 300-byte fragments and %s 200-byte fragments"
131 len(cls.fragments_400),
132 len(cls.fragments_300),
133 len(cls.fragments_200),
137 def verify_capture(self, capture, dropped_packet_indexes=[]):
138 """Verify captured packet stream.
140 :param list capture: Captured packet stream.
144 for packet in capture:
146 self.logger.debug(ppp("Got packet:", packet))
149 payload_info = self.payload_to_info(packet[Raw])
150 packet_index = payload_info.index
152 packet_index not in dropped_packet_indexes,
153 ppp("Packet received, but should be dropped:", packet),
155 if packet_index in seen:
156 raise Exception(ppp("Duplicate packet received", packet))
157 seen.add(packet_index)
158 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
159 info = self._packet_infos[packet_index]
160 self.assertTrue(info is not None)
161 self.assertEqual(packet_index, info.index)
162 saved_packet = info.data
163 self.assertEqual(ip.src, saved_packet[IP].src)
164 self.assertEqual(ip.dst, saved_packet[IP].dst)
165 self.assertEqual(udp.payload, saved_packet[UDP].payload)
167 self.logger.error(ppp("Unexpected or invalid packet:", packet))
169 for index in self._packet_infos:
171 index in seen or index in dropped_packet_indexes,
172 "Packet with packet_index %d not received" % index,
175 def test_reassembly(self):
176 """basic reassembly"""
178 self.pg_enable_capture()
179 self.src_if.add_stream(self.fragments_200)
182 packets = self.dst_if.get_capture(len(self.pkt_infos))
183 self.verify_capture(packets)
184 self.src_if.assert_nothing_captured()
186 # run it all again to verify correctness
187 self.pg_enable_capture()
188 self.src_if.add_stream(self.fragments_200)
191 packets = self.dst_if.get_capture(len(self.pkt_infos))
192 self.verify_capture(packets)
193 self.src_if.assert_nothing_captured()
195 def test_verify_clear_trace_mid_reassembly(self):
196 """verify clear trace works mid-reassembly"""
198 self.pg_enable_capture()
199 self.src_if.add_stream(self.fragments_200[0:-1])
202 self.logger.debug(self.vapi.cli("show trace"))
203 self.vapi.cli("clear trace")
205 self.src_if.add_stream(self.fragments_200[-1])
207 packets = self.dst_if.get_capture(len(self.pkt_infos))
208 self.verify_capture(packets)
210 def test_reversed(self):
211 """reverse order reassembly"""
213 fragments = list(self.fragments_200)
216 self.pg_enable_capture()
217 self.src_if.add_stream(fragments)
220 packets = self.dst_if.get_capture(len(self.packet_infos))
221 self.verify_capture(packets)
222 self.src_if.assert_nothing_captured()
224 # run it all again to verify correctness
225 self.pg_enable_capture()
226 self.src_if.add_stream(fragments)
229 packets = self.dst_if.get_capture(len(self.packet_infos))
230 self.verify_capture(packets)
231 self.src_if.assert_nothing_captured()
233 def test_long_fragment_chain(self):
234 """long fragment chain"""
236 error_cnt_str = "/err/ip4-full-reassembly-feature/reass_fragment_chain_too_long"
238 error_cnt = self.statistics.get_err_counter(error_cnt_str)
240 self.vapi.ip_reassembly_set(
242 max_reassemblies=1000,
243 max_reassembly_length=3,
244 expire_walk_interval_ms=50,
248 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
249 / IP(id=1000, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
250 / UDP(sport=1234, dport=5678)
254 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
255 / IP(id=1001, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
256 / UDP(sport=1234, dport=5678)
259 frags = fragment_rfc791(p1, 200) + fragment_rfc791(p2, 500)
261 self.pg_enable_capture()
262 self.src_if.add_stream(frags)
265 self.dst_if.get_capture(1)
266 self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
269 """fragment length + ip header size > 65535"""
270 self.vapi.cli("clear errors")
271 raw = b"""E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n\
272 \x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-message.\
273 Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Offset; Test-case: 5737"""
274 malformed_packet = Ether(
275 dst=self.src_if.local_mac, src=self.src_if.remote_mac
278 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
279 / IP(id=1000, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
280 / UDP(sport=1234, dport=5678)
283 valid_fragments = fragment_rfc791(p, 400)
285 counter = "/err/ip4-full-reassembly-feature/reass_malformed_packet"
286 error_counter = self.statistics.get_err_counter(counter)
287 self.pg_enable_capture()
288 self.src_if.add_stream([malformed_packet] + valid_fragments)
291 self.dst_if.get_capture(1)
292 self.logger.debug(self.vapi.ppcli("show error"))
293 self.assertEqual(self.statistics.get_err_counter(counter), error_counter + 1)
295 def test_44924(self):
296 """compress tiny fragments"""
299 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
305 src=self.src_if.remote_ip4,
306 dst=self.dst_if.remote_ip4,
308 / ICMP(type="echo-request", code=0, id=0x1FE6, seq=0x2407)
309 / Raw(load="Test-group: IPv4")
312 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
318 src=self.src_if.remote_ip4,
319 dst=self.dst_if.remote_ip4,
321 / ICMP(type="echo-request", code=0, id=0x1FE6, seq=0x2407)
322 / Raw(load=".IPv4.Fragmentation.vali")
325 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
330 src=self.src_if.remote_ip4,
331 dst=self.dst_if.remote_ip4,
333 / ICMP(type="echo-request", code=0, id=0x1FE6, seq=0x2407)
334 / Raw(load="d; Test-case: 44924")
338 self.pg_enable_capture()
339 self.src_if.add_stream(packets)
342 self.dst_if.get_capture(1)
344 def test_frag_1(self):
345 """fragment of size 1"""
346 self.vapi.cli("clear errors")
347 malformed_packets = [
349 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
356 src=self.src_if.remote_ip4,
357 dst=self.dst_if.remote_ip4,
359 / ICMP(type="echo-request")
362 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
368 src=self.src_if.remote_ip4,
369 dst=self.dst_if.remote_ip4,
376 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
377 / IP(id=1000, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
378 / UDP(sport=1234, dport=5678)
381 valid_fragments = fragment_rfc791(p, 400)
383 self.pg_enable_capture()
384 self.src_if.add_stream(malformed_packets + valid_fragments)
387 self.dst_if.get_capture(1)
389 self.assert_packet_counter_equal("ip4-full-reassembly-feature", 1)
390 # TODO remove above, uncomment below once clearing of counters
392 # self.assert_packet_counter_equal(
393 # "/err/ip4-full-reassembly-feature/reass_malformed_packet", 1)
395 def test_random(self):
396 """random order reassembly"""
398 fragments = list(self.fragments_200)
401 self.pg_enable_capture()
402 self.src_if.add_stream(fragments)
405 packets = self.dst_if.get_capture(len(self.packet_infos))
406 self.verify_capture(packets)
407 self.src_if.assert_nothing_captured()
409 # run it all again to verify correctness
410 self.pg_enable_capture()
411 self.src_if.add_stream(fragments)
414 packets = self.dst_if.get_capture(len(self.packet_infos))
415 self.verify_capture(packets)
416 self.src_if.assert_nothing_captured()
418 def test_duplicates(self):
419 """duplicate fragments"""
423 for (_, frags, _, _) in self.pkt_infos
425 for _ in range(0, min(2, len(frags)))
428 self.pg_enable_capture()
429 self.src_if.add_stream(fragments)
432 packets = self.dst_if.get_capture(len(self.pkt_infos))
433 self.verify_capture(packets)
434 self.src_if.assert_nothing_captured()
436 def test_overlap1(self):
437 """overlapping fragments case #1"""
440 for _, _, frags_300, frags_200 in self.pkt_infos:
441 if len(frags_300) == 1:
442 fragments.extend(frags_300)
444 for i, j in zip(frags_200, frags_300):
448 self.pg_enable_capture()
449 self.src_if.add_stream(fragments)
452 packets = self.dst_if.get_capture(len(self.pkt_infos))
453 self.verify_capture(packets)
454 self.src_if.assert_nothing_captured()
456 # run it all to verify correctness
457 self.pg_enable_capture()
458 self.src_if.add_stream(fragments)
461 packets = self.dst_if.get_capture(len(self.pkt_infos))
462 self.verify_capture(packets)
463 self.src_if.assert_nothing_captured()
465 def test_overlap2(self):
466 """overlapping fragments case #2"""
469 for _, _, frags_300, frags_200 in self.pkt_infos:
470 if len(frags_300) == 1:
471 fragments.extend(frags_300)
473 # care must be taken here so that there are no fragments
474 # received by vpp after reassembly is finished, otherwise
475 # new reassemblies will be started and packet generator will
476 # freak out when it detects unfreed buffers
477 zipped = zip(frags_300, frags_200)
483 self.pg_enable_capture()
484 self.src_if.add_stream(fragments)
487 packets = self.dst_if.get_capture(len(self.pkt_infos))
488 self.verify_capture(packets)
489 self.src_if.assert_nothing_captured()
491 # run it all to verify correctness
492 self.pg_enable_capture()
493 self.src_if.add_stream(fragments)
496 packets = self.dst_if.get_capture(len(self.pkt_infos))
497 self.verify_capture(packets)
498 self.src_if.assert_nothing_captured()
500 def test_timeout_inline(self):
501 """timeout (inline)"""
503 dropped_packet_indexes = set(
504 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
507 self.vapi.ip_reassembly_set(
509 max_reassemblies=1000,
510 max_reassembly_length=3,
511 expire_walk_interval_ms=10000,
514 self.pg_enable_capture()
515 self.src_if.add_stream(self.fragments_400)
518 packets = self.dst_if.get_capture(
519 len(self.pkt_infos) - len(dropped_packet_indexes)
521 self.verify_capture(packets, dropped_packet_indexes)
522 self.src_if.assert_nothing_captured()
524 def test_timeout_cleanup(self):
525 """timeout (cleanup)"""
527 # whole packets + fragmented packets sans last fragment
530 for (_, frags_400, _, _) in self.pkt_infos
531 for x in frags_400[: -1 if len(frags_400) > 1 else None]
534 # last fragments for fragmented packets
537 for (_, frags_400, _, _) in self.pkt_infos
538 if len(frags_400) > 1
541 dropped_packet_indexes = set(
542 index for (index, frags_400, _, _) in self.pkt_infos if len(frags_400) > 1
545 self.vapi.ip_reassembly_set(
547 max_reassemblies=1000,
548 max_reassembly_length=1000,
549 expire_walk_interval_ms=50,
552 self.pg_enable_capture()
553 self.src_if.add_stream(fragments)
556 self.virtual_sleep(0.25, "wait before sending rest of fragments")
558 self.src_if.add_stream(fragments2)
561 packets = self.dst_if.get_capture(
562 len(self.pkt_infos) - len(dropped_packet_indexes)
564 self.verify_capture(packets, dropped_packet_indexes)
565 self.src_if.assert_nothing_captured()
567 def test_disabled(self):
568 """reassembly disabled"""
570 dropped_packet_indexes = set(
571 index for (index, frags_400, _, _) in self.pkt_infos if len(frags_400) > 1
574 self.vapi.ip_reassembly_set(
577 max_reassembly_length=3,
578 expire_walk_interval_ms=10000,
581 self.pg_enable_capture()
582 self.src_if.add_stream(self.fragments_400)
585 packets = self.dst_if.get_capture(
586 len(self.pkt_infos) - len(dropped_packet_indexes)
588 self.verify_capture(packets, dropped_packet_indexes)
589 self.src_if.assert_nothing_captured()
591 def test_local_enable_disable(self):
592 """local reassembly enabled/disable"""
593 self.vapi.ip_reassembly_enable_disable(
594 sw_if_index=self.src_if.sw_if_index, enable_ip4=False
596 self.vapi.ip_local_reass_enable_disable(enable_ip4=True)
598 Ether(src=self.src_if.remote_mac, dst=self.src_if.local_mac)
599 / IP(src=self.src_if.remote_ip4, dst=self.src_if.local_ip4)
600 / ICMP(id=1234, type="echo-request")
603 frags = fragment_rfc791(p, 400)
604 r = self.send_and_expect(self.src_if, frags, self.src_if, n_rx=1)[0]
605 self.assertEqual(1234, r[ICMP].id)
606 self.assertEqual(icmptypes[r[ICMP].type], "echo-reply")
607 self.vapi.ip_local_reass_enable_disable()
609 self.send_and_assert_no_replies(self.src_if, frags)
610 self.vapi.ip_local_reass_enable_disable(enable_ip4=True)
613 class TestIPv4SVReassembly(VppTestCase):
614 """IPv4 Shallow Virtual Reassembly"""
620 cls.create_pg_interfaces([0, 1])
624 # setup all interfaces
625 for i in cls.pg_interfaces:
631 """Test setup - force timeout on existing reassemblies"""
633 self.vapi.ip_reassembly_enable_disable(
634 sw_if_index=self.src_if.sw_if_index,
636 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
638 self.vapi.ip_reassembly_set(
640 max_reassemblies=1000,
641 max_reassembly_length=1000,
642 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
643 expire_walk_interval_ms=10,
645 self.virtual_sleep(0.25)
646 self.vapi.ip_reassembly_set(
648 max_reassemblies=1000,
649 max_reassembly_length=1000,
650 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
651 expire_walk_interval_ms=10000,
656 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
657 self.logger.debug(self.vapi.ppcli("show buffers"))
659 def test_basic(self):
660 """basic reassembly"""
664 while len(payload) < payload_len:
665 payload += "%u " % counter
669 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
670 / IP(id=1, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
671 / UDP(sport=1234, dport=5678)
674 fragments = fragment_rfc791(p, payload_len / 4)
676 # send fragment #2 - should be cached inside reassembly
677 self.pg_enable_capture()
678 self.src_if.add_stream(fragments[1])
680 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
681 self.logger.debug(self.vapi.ppcli("show buffers"))
682 self.logger.debug(self.vapi.ppcli("show trace"))
683 self.dst_if.assert_nothing_captured()
685 # send fragment #1 - reassembly is finished now and both fragments
687 self.pg_enable_capture()
688 self.src_if.add_stream(fragments[0])
690 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
691 self.logger.debug(self.vapi.ppcli("show buffers"))
692 self.logger.debug(self.vapi.ppcli("show trace"))
693 c = self.dst_if.get_capture(2)
694 for sent, recvd in zip([fragments[1], fragments[0]], c):
695 self.assertEqual(sent[IP].src, recvd[IP].src)
696 self.assertEqual(sent[IP].dst, recvd[IP].dst)
697 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
699 # send rest of fragments - should be immediately forwarded
700 self.pg_enable_capture()
701 self.src_if.add_stream(fragments[2:])
703 c = self.dst_if.get_capture(len(fragments[2:]))
704 for sent, recvd in zip(fragments[2:], c):
705 self.assertEqual(sent[IP].src, recvd[IP].src)
706 self.assertEqual(sent[IP].dst, recvd[IP].dst)
707 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
709 def test_verify_clear_trace_mid_reassembly(self):
710 """verify clear trace works mid-reassembly"""
714 while len(payload) < payload_len:
715 payload += "%u " % counter
719 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
720 / IP(id=1, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
721 / UDP(sport=1234, dport=5678)
724 fragments = fragment_rfc791(p, payload_len / 4)
726 self.pg_enable_capture()
727 self.src_if.add_stream(fragments[1])
730 self.logger.debug(self.vapi.cli("show trace"))
731 self.vapi.cli("clear trace")
733 self.pg_enable_capture()
734 self.src_if.add_stream(fragments[0])
736 self.dst_if.get_capture(2)
738 self.logger.debug(self.vapi.cli("show trace"))
739 self.vapi.cli("clear trace")
741 self.pg_enable_capture()
742 self.src_if.add_stream(fragments[2:])
744 self.dst_if.get_capture(len(fragments[2:]))
746 def test_timeout(self):
747 """reassembly timeout"""
751 while len(payload) < payload_len:
752 payload += "%u " % counter
756 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
757 / IP(id=1, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
758 / UDP(sport=1234, dport=5678)
761 fragments = fragment_rfc791(p, payload_len / 4)
763 self.vapi.ip_reassembly_set(
765 max_reassemblies=1000,
766 max_reassembly_length=1000,
767 expire_walk_interval_ms=50,
768 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
771 # send fragments #2 and #1 - should be forwarded
772 self.pg_enable_capture()
773 self.src_if.add_stream(fragments[0:2])
775 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
776 self.logger.debug(self.vapi.ppcli("show buffers"))
777 self.logger.debug(self.vapi.ppcli("show trace"))
778 c = self.dst_if.get_capture(2)
779 for sent, recvd in zip([fragments[1], fragments[0]], c):
780 self.assertEqual(sent[IP].src, recvd[IP].src)
781 self.assertEqual(sent[IP].dst, recvd[IP].dst)
782 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
785 self.virtual_sleep(0.25, "wait before sending rest of fragments")
787 # send rest of fragments - shouldn't be forwarded
788 self.pg_enable_capture()
789 self.src_if.add_stream(fragments[2:])
791 self.dst_if.assert_nothing_captured()
794 """reassembly reuses LRU element"""
796 self.vapi.ip_reassembly_set(
799 max_reassembly_length=1000,
800 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
801 expire_walk_interval_ms=10000,
807 while len(payload) < payload_len:
808 payload += "%u " % counter
815 for i in range(packet_count)
817 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
818 / IP(id=i, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
819 / UDP(sport=1234, dport=5678)
822 for f in fragment_rfc791(p, payload_len / 4)
825 self.pg_enable_capture()
826 self.src_if.add_stream(fragments)
828 c = self.dst_if.get_capture(len(fragments))
829 for sent, recvd in zip(fragments, c):
830 self.assertEqual(sent[IP].src, recvd[IP].src)
831 self.assertEqual(sent[IP].dst, recvd[IP].dst)
832 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
834 def send_mixed_and_verify_capture(self, traffic):
837 for c in range(t["count"]):
840 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
844 src=self.src_if.remote_ip4,
845 dst=self.dst_if.remote_ip4,
847 / UDP(sport=1234, dport=5678)
851 self.counter = self.counter + 1
853 self.pg_enable_capture()
854 self.src_if.add_stream(stream)
856 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
857 self.logger.debug(self.vapi.ppcli("show buffers"))
858 self.logger.debug(self.vapi.ppcli("show trace"))
859 self.dst_if.get_capture(len(stream))
861 def test_mixed(self):
862 """mixed traffic correctly passes through SVR"""
865 self.send_mixed_and_verify_capture([{"count": 1, "flags": ""}])
866 self.send_mixed_and_verify_capture([{"count": 2, "flags": ""}])
867 self.send_mixed_and_verify_capture([{"count": 3, "flags": ""}])
868 self.send_mixed_and_verify_capture([{"count": 8, "flags": ""}])
869 self.send_mixed_and_verify_capture([{"count": 257, "flags": ""}])
871 self.send_mixed_and_verify_capture([{"count": 1, "flags": "MF"}])
872 self.send_mixed_and_verify_capture([{"count": 2, "flags": "MF"}])
873 self.send_mixed_and_verify_capture([{"count": 3, "flags": "MF"}])
874 self.send_mixed_and_verify_capture([{"count": 8, "flags": "MF"}])
875 self.send_mixed_and_verify_capture([{"count": 257, "flags": "MF"}])
877 self.send_mixed_and_verify_capture(
878 [{"count": 1, "flags": ""}, {"count": 1, "flags": "MF"}]
880 self.send_mixed_and_verify_capture(
881 [{"count": 2, "flags": ""}, {"count": 2, "flags": "MF"}]
883 self.send_mixed_and_verify_capture(
884 [{"count": 3, "flags": ""}, {"count": 3, "flags": "MF"}]
886 self.send_mixed_and_verify_capture(
887 [{"count": 8, "flags": ""}, {"count": 8, "flags": "MF"}]
889 self.send_mixed_and_verify_capture(
890 [{"count": 129, "flags": ""}, {"count": 129, "flags": "MF"}]
893 self.send_mixed_and_verify_capture(
895 {"count": 1, "flags": ""},
896 {"count": 1, "flags": "MF"},
897 {"count": 1, "flags": ""},
898 {"count": 1, "flags": "MF"},
901 self.send_mixed_and_verify_capture(
903 {"count": 2, "flags": ""},
904 {"count": 2, "flags": "MF"},
905 {"count": 2, "flags": ""},
906 {"count": 2, "flags": "MF"},
909 self.send_mixed_and_verify_capture(
911 {"count": 3, "flags": ""},
912 {"count": 3, "flags": "MF"},
913 {"count": 3, "flags": ""},
914 {"count": 3, "flags": "MF"},
917 self.send_mixed_and_verify_capture(
919 {"count": 8, "flags": ""},
920 {"count": 8, "flags": "MF"},
921 {"count": 8, "flags": ""},
922 {"count": 8, "flags": "MF"},
925 self.send_mixed_and_verify_capture(
927 {"count": 65, "flags": ""},
928 {"count": 65, "flags": "MF"},
929 {"count": 65, "flags": ""},
930 {"count": 65, "flags": "MF"},
935 class TestIPv4MWReassembly(VppTestCase):
936 """IPv4 Reassembly (multiple workers)"""
944 cls.create_pg_interfaces(range(cls.vpp_worker_count + 1))
946 cls.send_ifs = cls.pg_interfaces[:-1]
947 cls.dst_if = cls.pg_interfaces[-1]
949 # setup all interfaces
950 for i in cls.pg_interfaces:
955 # packets sizes reduced here because we are generating packets without
956 # Ethernet headers, which are added later (diff fragments go via
957 # different interfaces)
964 cls.padding = " abcdefghijklmn"
965 cls.create_stream(cls.packet_sizes)
966 cls.create_fragments()
969 def tearDownClass(cls):
970 super().tearDownClass()
973 """Test setup - force timeout on existing reassemblies"""
975 for intf in self.send_ifs:
976 self.vapi.ip_reassembly_enable_disable(
977 sw_if_index=intf.sw_if_index, enable_ip4=True
979 self.vapi.ip_reassembly_set(
981 max_reassemblies=1000,
982 max_reassembly_length=1000,
983 expire_walk_interval_ms=10,
985 self.virtual_sleep(0.25)
986 self.vapi.ip_reassembly_set(
988 max_reassemblies=1000,
989 max_reassembly_length=1000,
990 expire_walk_interval_ms=10000,
994 for intf in self.send_ifs:
995 self.vapi.ip_reassembly_enable_disable(
996 sw_if_index=intf.sw_if_index, enable_ip4=False
1000 def show_commands_at_teardown(self):
1001 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1002 self.logger.debug(self.vapi.ppcli("show buffers"))
1005 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1006 """Create input packet stream
1008 :param list packet_sizes: Required packet sizes.
1010 for i in range(0, packet_count):
1011 info = cls.create_packet_info(cls.src_if, cls.src_if)
1012 payload = cls.info_to_payload(info)
1014 IP(id=info.index, src=cls.src_if.remote_ip4, dst=cls.dst_if.remote_ip4)
1015 / UDP(sport=1234, dport=5678)
1018 size = packet_sizes[(i // 2) % len(packet_sizes)]
1019 cls.extend_packet(p, size, cls.padding)
1023 def create_fragments(cls):
1024 infos = cls._packet_infos
1026 for index, info in infos.items():
1028 # cls.logger.debug(ppp("Packet:",
1029 # p.__class__(scapy.compat.raw(p))))
1030 fragments_400 = fragment_rfc791(p, 400)
1031 cls.pkt_infos.append((index, fragments_400))
1032 cls.fragments_400 = [x for (_, frags) in cls.pkt_infos for x in frags]
1034 "Fragmented %s packets into %s 400-byte fragments, "
1035 % (len(infos), len(cls.fragments_400))
1038 def verify_capture(self, capture, dropped_packet_indexes=[]):
1039 """Verify captured packet stream.
1041 :param list capture: Captured packet stream.
1045 for packet in capture:
1047 self.logger.debug(ppp("Got packet:", packet))
1050 payload_info = self.payload_to_info(packet[Raw])
1051 packet_index = payload_info.index
1053 packet_index not in dropped_packet_indexes,
1054 ppp("Packet received, but should be dropped:", packet),
1056 if packet_index in seen:
1057 raise Exception(ppp("Duplicate packet received", packet))
1058 seen.add(packet_index)
1059 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1060 info = self._packet_infos[packet_index]
1061 self.assertTrue(info is not None)
1062 self.assertEqual(packet_index, info.index)
1063 saved_packet = info.data
1064 self.assertEqual(ip.src, saved_packet[IP].src)
1065 self.assertEqual(ip.dst, saved_packet[IP].dst)
1066 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1068 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1070 for index in self._packet_infos:
1072 index in seen or index in dropped_packet_indexes,
1073 "Packet with packet_index %d not received" % index,
1076 def send_packets(self, packets):
1077 for counter in range(self.vpp_worker_count):
1078 if 0 == len(packets[counter]):
1080 send_if = self.send_ifs[counter]
1083 Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1084 for x in packets[counter]
1090 def test_worker_conflict(self):
1091 """1st and FO=0 fragments on different workers"""
1093 # in first wave we send fragments which don't start at offset 0
1094 # then we send fragments with offset 0 on a different thread
1095 # then the rest of packets on a random thread
1096 first_packets = [[] for n in range(self.vpp_worker_count)]
1097 second_packets = [[] for n in range(self.vpp_worker_count)]
1098 rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1099 for _, p in self.pkt_infos:
1100 wi = randrange(self.vpp_worker_count)
1101 second_packets[wi].append(p[0])
1106 wi2 = randrange(self.vpp_worker_count)
1107 first_packets[wi2].append(p[1])
1108 wi3 = randrange(self.vpp_worker_count)
1109 rest_of_packets[wi3].extend(p[2:])
1111 self.pg_enable_capture()
1112 self.send_packets(first_packets)
1113 self.send_packets(second_packets)
1114 self.send_packets(rest_of_packets)
1116 packets = self.dst_if.get_capture(len(self.pkt_infos))
1117 self.verify_capture(packets)
1118 for send_if in self.send_ifs:
1119 send_if.assert_nothing_captured()
1121 self.logger.debug(self.vapi.ppcli("show trace"))
1122 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1123 self.logger.debug(self.vapi.ppcli("show buffers"))
1124 self.vapi.cli("clear trace")
1126 self.pg_enable_capture()
1127 self.send_packets(first_packets)
1128 self.send_packets(second_packets)
1129 self.send_packets(rest_of_packets)
1131 packets = self.dst_if.get_capture(len(self.pkt_infos))
1132 self.verify_capture(packets)
1133 for send_if in self.send_ifs:
1134 send_if.assert_nothing_captured()
1137 class TestIPv6Reassembly(VppTestCase):
1138 """IPv6 Reassembly"""
1141 def setUpClass(cls):
1142 super().setUpClass()
1144 cls.create_pg_interfaces([0, 1])
1145 cls.src_if = cls.pg0
1146 cls.dst_if = cls.pg1
1148 # setup all interfaces
1149 for i in cls.pg_interfaces:
1155 cls.packet_sizes = [64, 512, 1518, 9018]
1156 cls.padding = " abcdefghijklmn"
1157 cls.create_stream(cls.packet_sizes)
1158 cls.create_fragments()
1161 def tearDownClass(cls):
1162 super().tearDownClass()
1165 """Test setup - force timeout on existing reassemblies"""
1167 self.vapi.ip_reassembly_enable_disable(
1168 sw_if_index=self.src_if.sw_if_index, enable_ip6=True
1170 self.vapi.ip_reassembly_set(
1172 max_reassemblies=1000,
1173 max_reassembly_length=1000,
1174 expire_walk_interval_ms=10,
1177 self.virtual_sleep(0.25)
1178 self.vapi.ip_reassembly_set(
1180 max_reassemblies=1000,
1181 max_reassembly_length=1000,
1182 expire_walk_interval_ms=10000,
1185 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1186 self.logger.debug(self.vapi.ppcli("show buffers"))
1189 self.vapi.ip_reassembly_enable_disable(
1190 sw_if_index=self.src_if.sw_if_index, enable_ip6=False
1194 def show_commands_at_teardown(self):
1195 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1196 self.logger.debug(self.vapi.ppcli("show buffers"))
1199 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1200 """Create input packet stream for defined interface.
1202 :param list packet_sizes: Required packet sizes.
1204 for i in range(0, packet_count):
1205 info = cls.create_packet_info(cls.src_if, cls.src_if)
1206 payload = cls.info_to_payload(info)
1208 Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac)
1209 / IPv6(src=cls.src_if.remote_ip6, dst=cls.dst_if.remote_ip6)
1210 / UDP(sport=1234, dport=5678)
1213 size = packet_sizes[(i // 2) % len(packet_sizes)]
1214 cls.extend_packet(p, size, cls.padding)
1218 def create_fragments(cls):
1219 infos = cls._packet_infos
1221 for index, info in infos.items():
1223 # cls.logger.debug(ppp("Packet:",
1224 # p.__class__(scapy.compat.raw(p))))
1225 fragments_400 = fragment_rfc8200(p, info.index, 400)
1226 fragments_300 = fragment_rfc8200(p, info.index, 300)
1227 cls.pkt_infos.append((index, fragments_400, fragments_300))
1228 cls.fragments_400 = [x for _, frags, _ in cls.pkt_infos for x in frags]
1229 cls.fragments_300 = [x for _, _, frags in cls.pkt_infos for x in frags]
1231 "Fragmented %s packets into %s 400-byte fragments, "
1232 "and %s 300-byte fragments"
1233 % (len(infos), len(cls.fragments_400), len(cls.fragments_300))
1236 def verify_capture(self, capture, dropped_packet_indexes=[]):
1237 """Verify captured packet strea .
1239 :param list capture: Captured packet stream.
1243 for packet in capture:
1245 self.logger.debug(ppp("Got packet:", packet))
1248 payload_info = self.payload_to_info(packet[Raw])
1249 packet_index = payload_info.index
1251 packet_index not in dropped_packet_indexes,
1252 ppp("Packet received, but should be dropped:", packet),
1254 if packet_index in seen:
1255 raise Exception(ppp("Duplicate packet received", packet))
1256 seen.add(packet_index)
1257 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1258 info = self._packet_infos[packet_index]
1259 self.assertTrue(info is not None)
1260 self.assertEqual(packet_index, info.index)
1261 saved_packet = info.data
1262 self.assertEqual(ip.src, saved_packet[IPv6].src)
1263 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1264 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1266 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1268 for index in self._packet_infos:
1270 index in seen or index in dropped_packet_indexes,
1271 "Packet with packet_index %d not received" % index,
1274 def test_reassembly(self):
1275 """basic reassembly"""
1277 self.pg_enable_capture()
1278 self.src_if.add_stream(self.fragments_400)
1281 packets = self.dst_if.get_capture(len(self.pkt_infos))
1282 self.verify_capture(packets)
1283 self.src_if.assert_nothing_captured()
1285 # run it all again to verify correctness
1286 self.pg_enable_capture()
1287 self.src_if.add_stream(self.fragments_400)
1290 packets = self.dst_if.get_capture(len(self.pkt_infos))
1291 self.verify_capture(packets)
1292 self.src_if.assert_nothing_captured()
1294 def test_buffer_boundary(self):
1295 """fragment header crossing buffer boundary"""
1298 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1299 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1300 / IPv6ExtHdrHopByHop(options=[HBHOptUnknown(otype=0xFF, optlen=0)] * 1000)
1301 / IPv6ExtHdrFragment(m=1)
1302 / UDP(sport=1234, dport=5678)
1305 self.pg_enable_capture()
1306 self.src_if.add_stream([p])
1308 self.src_if.assert_nothing_captured()
1309 self.dst_if.assert_nothing_captured()
1311 def test_verify_clear_trace_mid_reassembly(self):
1312 """verify clear trace works mid-reassembly"""
1314 self.pg_enable_capture()
1315 self.src_if.add_stream(self.fragments_400[0:-1])
1318 self.logger.debug(self.vapi.cli("show trace"))
1319 self.vapi.cli("clear trace")
1321 self.src_if.add_stream(self.fragments_400[-1])
1323 packets = self.dst_if.get_capture(len(self.pkt_infos))
1324 self.verify_capture(packets)
1326 def test_reversed(self):
1327 """reverse order reassembly"""
1329 fragments = list(self.fragments_400)
1332 self.pg_enable_capture()
1333 self.src_if.add_stream(fragments)
1336 packets = self.dst_if.get_capture(len(self.pkt_infos))
1337 self.verify_capture(packets)
1338 self.src_if.assert_nothing_captured()
1340 # run it all again to verify correctness
1341 self.pg_enable_capture()
1342 self.src_if.add_stream(fragments)
1345 packets = self.dst_if.get_capture(len(self.pkt_infos))
1346 self.verify_capture(packets)
1347 self.src_if.assert_nothing_captured()
1349 def test_random(self):
1350 """random order reassembly"""
1352 fragments = list(self.fragments_400)
1355 self.pg_enable_capture()
1356 self.src_if.add_stream(fragments)
1359 packets = self.dst_if.get_capture(len(self.pkt_infos))
1360 self.verify_capture(packets)
1361 self.src_if.assert_nothing_captured()
1363 # run it all again to verify correctness
1364 self.pg_enable_capture()
1365 self.src_if.add_stream(fragments)
1368 packets = self.dst_if.get_capture(len(self.pkt_infos))
1369 self.verify_capture(packets)
1370 self.src_if.assert_nothing_captured()
1372 def test_duplicates(self):
1373 """duplicate fragments"""
1377 for (_, frags, _) in self.pkt_infos
1379 for _ in range(0, min(2, len(frags)))
1382 self.pg_enable_capture()
1383 self.src_if.add_stream(fragments)
1386 packets = self.dst_if.get_capture(len(self.pkt_infos))
1387 self.verify_capture(packets)
1388 self.src_if.assert_nothing_captured()
1390 def test_long_fragment_chain(self):
1391 """long fragment chain"""
1393 error_cnt_str = "/err/ip6-full-reassembly-feature/reass_fragment_chain_too_long"
1395 error_cnt = self.statistics.get_err_counter(error_cnt_str)
1397 self.vapi.ip_reassembly_set(
1399 max_reassemblies=1000,
1400 max_reassembly_length=3,
1401 expire_walk_interval_ms=50,
1406 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1407 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
1408 / UDP(sport=1234, dport=5678)
1411 frags = fragment_rfc8200(p, 1, 300) + fragment_rfc8200(p, 2, 500)
1413 self.pg_enable_capture()
1414 self.src_if.add_stream(frags)
1417 self.dst_if.get_capture(1)
1418 self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
1420 def test_overlap1(self):
1421 """overlapping fragments case #1"""
1424 for _, frags_400, frags_300 in self.pkt_infos:
1425 if len(frags_300) == 1:
1426 fragments.extend(frags_400)
1428 for i, j in zip(frags_300, frags_400):
1432 dropped_packet_indexes = set(
1433 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1436 self.pg_enable_capture()
1437 self.src_if.add_stream(fragments)
1440 packets = self.dst_if.get_capture(
1441 len(self.pkt_infos) - len(dropped_packet_indexes)
1443 self.verify_capture(packets, dropped_packet_indexes)
1444 self.src_if.assert_nothing_captured()
1446 def test_overlap2(self):
1447 """overlapping fragments case #2"""
1450 for _, frags_400, frags_300 in self.pkt_infos:
1451 if len(frags_400) == 1:
1452 fragments.extend(frags_400)
1454 # care must be taken here so that there are no fragments
1455 # received by vpp after reassembly is finished, otherwise
1456 # new reassemblies will be started and packet generator will
1457 # freak out when it detects unfreed buffers
1458 zipped = zip(frags_400, frags_300)
1464 dropped_packet_indexes = set(
1465 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1468 self.pg_enable_capture()
1469 self.src_if.add_stream(fragments)
1472 packets = self.dst_if.get_capture(
1473 len(self.pkt_infos) - len(dropped_packet_indexes)
1475 self.verify_capture(packets, dropped_packet_indexes)
1476 self.src_if.assert_nothing_captured()
1478 def test_timeout_inline(self):
1479 """timeout (inline)"""
1481 dropped_packet_indexes = set(
1482 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
1485 self.vapi.ip_reassembly_set(
1487 max_reassemblies=1000,
1488 max_reassembly_length=3,
1489 expire_walk_interval_ms=10000,
1493 self.pg_enable_capture()
1494 self.src_if.add_stream(self.fragments_400)
1497 packets = self.dst_if.get_capture(
1498 len(self.pkt_infos) - len(dropped_packet_indexes)
1500 self.verify_capture(packets, dropped_packet_indexes)
1501 pkts = self.src_if._get_capture(1)
1503 self.assertIn(ICMPv6TimeExceeded, icmp)
1504 self.assertIn(IPv6ExtHdrFragment, icmp)
1505 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1506 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1508 def test_timeout_cleanup(self):
1509 """timeout (cleanup)"""
1511 # whole packets + fragmented packets sans last fragment
1514 for (_, frags_400, _) in self.pkt_infos
1515 for x in frags_400[: -1 if len(frags_400) > 1 else None]
1518 # last fragments for fragmented packets
1520 frags_400[-1] for (_, frags_400, _) in self.pkt_infos if len(frags_400) > 1
1523 dropped_packet_indexes = set(
1524 index for (index, frags_400, _) in self.pkt_infos if len(frags_400) > 1
1527 self.vapi.ip_reassembly_set(
1529 max_reassemblies=1000,
1530 max_reassembly_length=1000,
1531 expire_walk_interval_ms=50,
1534 self.vapi.ip_reassembly_set(
1536 max_reassemblies=1000,
1537 max_reassembly_length=1000,
1538 expire_walk_interval_ms=50,
1542 self.pg_enable_capture()
1543 self.src_if.add_stream(fragments)
1546 self.virtual_sleep(0.25, "wait before sending rest of fragments")
1548 self.src_if.add_stream(fragments2)
1551 packets = self.dst_if.get_capture(
1552 len(self.pkt_infos) - len(dropped_packet_indexes)
1554 self.verify_capture(packets, dropped_packet_indexes)
1555 pkts = self.src_if._get_capture(1)
1557 self.assertIn(ICMPv6TimeExceeded, icmp)
1558 self.assertIn(IPv6ExtHdrFragment, icmp)
1559 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1560 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1562 def test_disabled(self):
1563 """reassembly disabled"""
1565 dropped_packet_indexes = set(
1566 index for (index, frags_400, _) in self.pkt_infos if len(frags_400) > 1
1569 self.vapi.ip_reassembly_set(
1572 max_reassembly_length=3,
1573 expire_walk_interval_ms=10000,
1577 self.pg_enable_capture()
1578 self.src_if.add_stream(self.fragments_400)
1581 packets = self.dst_if.get_capture(
1582 len(self.pkt_infos) - len(dropped_packet_indexes)
1584 self.verify_capture(packets, dropped_packet_indexes)
1585 self.src_if.assert_nothing_captured()
1587 def test_missing_upper(self):
1588 """missing upper layer"""
1589 optdata = "\x00" * 100
1591 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1592 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1593 / IPv6ExtHdrFragment(m=1)
1594 / IPv6ExtHdrDestOpt(
1595 nh=17, options=PadN(optdata="\101" * 255) / PadN(optdata="\102" * 255)
1599 self.pg_enable_capture()
1600 self.src_if.add_stream([p])
1602 pkts = self.src_if.get_capture(expected_count=1)
1604 self.assertIn(ICMPv6ParamProblem, icmp)
1605 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
1607 def test_invalid_frag_size(self):
1608 """fragment size not a multiple of 8"""
1610 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1611 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1612 / UDP(sport=1234, dport=5678)
1615 self.extend_packet(p, 1000, self.padding)
1616 fragments = fragment_rfc8200(p, 1, 500)
1617 bad_fragment = fragments[0]
1618 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
1619 self.pg_enable_capture()
1620 self.src_if.add_stream([bad_fragment])
1622 pkts = self.src_if.get_capture(expected_count=1)
1624 self.assertIn(ICMPv6ParamProblem, icmp)
1625 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1627 def test_invalid_packet_size(self):
1628 """total packet size > 65535"""
1630 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1631 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1632 / UDP(sport=1234, dport=5678)
1635 self.extend_packet(p, 1000, self.padding)
1636 fragments = fragment_rfc8200(p, 1, 500)
1637 bad_fragment = fragments[1]
1638 bad_fragment[IPv6ExtHdrFragment].offset = 65500
1639 self.pg_enable_capture()
1640 self.src_if.add_stream([bad_fragment])
1642 pkts = self.src_if.get_capture(expected_count=1)
1644 self.assertIn(ICMPv6ParamProblem, icmp)
1645 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1647 def test_atomic_fragment(self):
1648 """IPv6 atomic fragment"""
1650 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1651 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=65535)
1652 / IPv6ExtHdrFragment(
1653 offset=8191, m=1, res1=0xFF, res2=0xFF, nh=255, id=0xFFFF
1658 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1659 self.assertIn(ICMPv6ParamProblem, rx[0])
1661 def test_truncated_fragment(self):
1662 """IPv6 truncated fragment header"""
1664 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1665 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
1666 / IPv6ExtHdrFragment(nh=6)
1669 self.send_and_assert_no_replies(self.pg0, [pkt])
1672 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1673 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.remote_ip6)
1674 / ICMPv6EchoRequest()
1676 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1678 def test_one_fragment(self):
1679 """whole packet in one fragment processed independently"""
1681 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1682 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1683 / ICMPv6EchoRequest()
1686 frags = fragment_rfc8200(pkt, 1, 400)
1688 # send a fragment with known id
1689 self.send_and_assert_no_replies(self.pg0, [frags[0]])
1691 # send an atomic fragment with same id - should be reassembled
1693 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1694 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1695 / IPv6ExtHdrFragment(id=1)
1696 / ICMPv6EchoRequest()
1698 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1699 self.assertNotIn(IPv6ExtHdrFragment, rx)
1701 # now finish the original reassembly, this should still be possible
1702 rx = self.send_and_expect(self.pg0, frags[1:], self.pg0, n_rx=1)
1703 self.assertNotIn(IPv6ExtHdrFragment, rx)
1705 def test_bunch_of_fragments(self):
1706 """valid fragments followed by rogue fragments and atomic fragment"""
1708 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1709 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1710 / ICMPv6EchoRequest()
1713 frags = fragment_rfc8200(pkt, 1, 400)
1714 self.send_and_expect(self.pg0, frags, self.pg0, n_rx=1)
1717 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1718 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1719 / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
1723 self.send_and_assert_no_replies(self.pg0, inc_frag * 604)
1726 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1727 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1728 / IPv6ExtHdrFragment(id=1)
1729 / ICMPv6EchoRequest()
1731 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1732 self.assertNotIn(IPv6ExtHdrFragment, rx)
1734 def test_local_enable_disable(self):
1735 """local reassembly enabled/disable"""
1736 self.vapi.ip_reassembly_enable_disable(
1737 sw_if_index=self.src_if.sw_if_index, enable_ip6=False
1739 self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1741 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
1742 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1743 / ICMPv6EchoRequest(id=1234)
1746 frags = fragment_rfc8200(pkt, 1, 400)
1747 r = self.send_and_expect(self.src_if, frags, self.src_if, n_rx=1)[0]
1748 self.assertEqual(1234, r[ICMPv6EchoReply].id)
1749 self.vapi.ip_local_reass_enable_disable()
1751 self.send_and_assert_no_replies(self.src_if, frags)
1752 self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1755 class TestIPv6MWReassembly(VppTestCase):
1756 """IPv6 Reassembly (multiple workers)"""
1758 vpp_worker_count = 3
1761 def setUpClass(cls):
1762 super().setUpClass()
1764 cls.create_pg_interfaces(range(cls.vpp_worker_count + 1))
1765 cls.src_if = cls.pg0
1766 cls.send_ifs = cls.pg_interfaces[:-1]
1767 cls.dst_if = cls.pg_interfaces[-1]
1769 # setup all interfaces
1770 for i in cls.pg_interfaces:
1775 # packets sizes reduced here because we are generating packets without
1776 # Ethernet headers, which are added later (diff fragments go via
1777 # different interfaces)
1778 cls.packet_sizes = [
1781 1518 - len(Ether()),
1782 9018 - len(Ether()),
1784 cls.padding = " abcdefghijklmn"
1785 cls.create_stream(cls.packet_sizes)
1786 cls.create_fragments()
1789 def tearDownClass(cls):
1790 super().tearDownClass()
1793 """Test setup - force timeout on existing reassemblies"""
1795 for intf in self.send_ifs:
1796 self.vapi.ip_reassembly_enable_disable(
1797 sw_if_index=intf.sw_if_index, enable_ip6=True
1799 self.vapi.ip_reassembly_set(
1801 max_reassemblies=1000,
1802 max_reassembly_length=1000,
1803 expire_walk_interval_ms=10,
1806 self.virtual_sleep(0.25)
1807 self.vapi.ip_reassembly_set(
1809 max_reassemblies=1000,
1810 max_reassembly_length=1000,
1811 expire_walk_interval_ms=1000,
1816 for intf in self.send_ifs:
1817 self.vapi.ip_reassembly_enable_disable(
1818 sw_if_index=intf.sw_if_index, enable_ip6=False
1822 def show_commands_at_teardown(self):
1823 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1824 self.logger.debug(self.vapi.ppcli("show buffers"))
1827 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1828 """Create input packet stream
1830 :param list packet_sizes: Required packet sizes.
1832 for i in range(0, packet_count):
1833 info = cls.create_packet_info(cls.src_if, cls.src_if)
1834 payload = cls.info_to_payload(info)
1836 IPv6(src=cls.src_if.remote_ip6, dst=cls.dst_if.remote_ip6)
1837 / UDP(sport=1234, dport=5678)
1840 size = packet_sizes[(i // 2) % len(packet_sizes)]
1841 cls.extend_packet(p, size, cls.padding)
1845 def create_fragments(cls):
1846 infos = cls._packet_infos
1848 for index, info in infos.items():
1850 # cls.logger.debug(ppp("Packet:",
1851 # p.__class__(scapy.compat.raw(p))))
1852 fragments_400 = fragment_rfc8200(p, index, 400)
1853 cls.pkt_infos.append((index, fragments_400))
1854 cls.fragments_400 = [x for (_, frags) in cls.pkt_infos for x in frags]
1856 "Fragmented %s packets into %s 400-byte fragments, "
1857 % (len(infos), len(cls.fragments_400))
1860 def verify_capture(self, capture, dropped_packet_indexes=[]):
1861 """Verify captured packet strea .
1863 :param list capture: Captured packet stream.
1867 for packet in capture:
1869 self.logger.debug(ppp("Got packet:", packet))
1872 payload_info = self.payload_to_info(packet[Raw])
1873 packet_index = payload_info.index
1875 packet_index not in dropped_packet_indexes,
1876 ppp("Packet received, but should be dropped:", packet),
1878 if packet_index in seen:
1879 raise Exception(ppp("Duplicate packet received", packet))
1880 seen.add(packet_index)
1881 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1882 info = self._packet_infos[packet_index]
1883 self.assertTrue(info is not None)
1884 self.assertEqual(packet_index, info.index)
1885 saved_packet = info.data
1886 self.assertEqual(ip.src, saved_packet[IPv6].src)
1887 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1888 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1890 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1892 for index in self._packet_infos:
1894 index in seen or index in dropped_packet_indexes,
1895 "Packet with packet_index %d not received" % index,
1898 def send_packets(self, packets):
1899 for counter in range(self.vpp_worker_count):
1900 if 0 == len(packets[counter]):
1902 send_if = self.send_ifs[counter]
1905 Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1906 for x in packets[counter]
1912 def test_worker_conflict(self):
1913 """1st and FO=0 fragments on different workers"""
1915 # in first wave we send fragments which don't start at offset 0
1916 # then we send fragments with offset 0 on a different thread
1917 # then the rest of packets on a random thread
1918 first_packets = [[] for n in range(self.vpp_worker_count)]
1919 second_packets = [[] for n in range(self.vpp_worker_count)]
1920 rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1921 for _, p in self.pkt_infos:
1922 wi = randrange(self.vpp_worker_count)
1923 second_packets[wi].append(p[0])
1928 wi2 = randrange(self.vpp_worker_count)
1929 first_packets[wi2].append(p[1])
1930 wi3 = randrange(self.vpp_worker_count)
1931 rest_of_packets[wi3].extend(p[2:])
1933 self.pg_enable_capture()
1934 self.send_packets(first_packets)
1935 self.send_packets(second_packets)
1936 self.send_packets(rest_of_packets)
1938 packets = self.dst_if.get_capture(len(self.pkt_infos))
1939 self.verify_capture(packets)
1940 for send_if in self.send_ifs:
1941 send_if.assert_nothing_captured()
1943 self.logger.debug(self.vapi.ppcli("show trace"))
1944 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1945 self.logger.debug(self.vapi.ppcli("show buffers"))
1946 self.vapi.cli("clear trace")
1948 self.pg_enable_capture()
1949 self.send_packets(first_packets)
1950 self.send_packets(second_packets)
1951 self.send_packets(rest_of_packets)
1953 packets = self.dst_if.get_capture(len(self.pkt_infos))
1954 self.verify_capture(packets)
1955 for send_if in self.send_ifs:
1956 send_if.assert_nothing_captured()
1959 class TestIPv6SVReassembly(VppTestCase):
1960 """IPv6 Shallow Virtual Reassembly"""
1963 def setUpClass(cls):
1964 super().setUpClass()
1966 cls.create_pg_interfaces([0, 1])
1967 cls.src_if = cls.pg0
1968 cls.dst_if = cls.pg1
1970 # setup all interfaces
1971 for i in cls.pg_interfaces:
1977 """Test setup - force timeout on existing reassemblies"""
1979 self.vapi.ip_reassembly_enable_disable(
1980 sw_if_index=self.src_if.sw_if_index,
1982 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1984 self.vapi.ip_reassembly_set(
1986 max_reassemblies=1000,
1987 max_reassembly_length=1000,
1988 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1989 expire_walk_interval_ms=10,
1992 self.virtual_sleep(0.25)
1993 self.vapi.ip_reassembly_set(
1995 max_reassemblies=1000,
1996 max_reassembly_length=1000,
1997 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1998 expire_walk_interval_ms=10000,
2004 self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2005 self.logger.debug(self.vapi.ppcli("show buffers"))
2007 def test_basic(self):
2008 """basic reassembly"""
2012 while len(payload) < payload_len:
2013 payload += "%u " % counter
2017 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2018 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2019 / UDP(sport=1234, dport=5678)
2022 fragments = fragment_rfc8200(p, 1, payload_len / 4)
2024 # send fragment #2 - should be cached inside reassembly
2025 self.pg_enable_capture()
2026 self.src_if.add_stream(fragments[1])
2028 self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2029 self.logger.debug(self.vapi.ppcli("show buffers"))
2030 self.logger.debug(self.vapi.ppcli("show trace"))
2031 self.dst_if.assert_nothing_captured()
2033 # send fragment #1 - reassembly is finished now and both fragments
2035 self.pg_enable_capture()
2036 self.src_if.add_stream(fragments[0])
2038 self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2039 self.logger.debug(self.vapi.ppcli("show buffers"))
2040 self.logger.debug(self.vapi.ppcli("show trace"))
2041 c = self.dst_if.get_capture(2)
2042 for sent, recvd in zip([fragments[1], fragments[0]], c):
2043 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2044 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2045 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2047 # send rest of fragments - should be immediately forwarded
2048 self.pg_enable_capture()
2049 self.src_if.add_stream(fragments[2:])
2051 c = self.dst_if.get_capture(len(fragments[2:]))
2052 for sent, recvd in zip(fragments[2:], c):
2053 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2054 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2055 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2057 def test_verify_clear_trace_mid_reassembly(self):
2058 """verify clear trace works mid-reassembly"""
2062 while len(payload) < payload_len:
2063 payload += "%u " % counter
2067 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2068 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2069 / UDP(sport=1234, dport=5678)
2072 fragments = fragment_rfc8200(p, 1, payload_len / 4)
2074 self.pg_enable_capture()
2075 self.src_if.add_stream(fragments[1])
2078 self.logger.debug(self.vapi.cli("show trace"))
2079 self.vapi.cli("clear trace")
2081 self.pg_enable_capture()
2082 self.src_if.add_stream(fragments[0])
2084 self.dst_if.get_capture(2)
2086 self.logger.debug(self.vapi.cli("show trace"))
2087 self.vapi.cli("clear trace")
2089 self.pg_enable_capture()
2090 self.src_if.add_stream(fragments[2:])
2092 self.dst_if.get_capture(len(fragments[2:]))
2094 def test_timeout(self):
2095 """reassembly timeout"""
2099 while len(payload) < payload_len:
2100 payload += "%u " % counter
2104 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2105 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2106 / UDP(sport=1234, dport=5678)
2109 fragments = fragment_rfc8200(p, 1, payload_len / 4)
2111 self.vapi.ip_reassembly_set(
2113 max_reassemblies=1000,
2114 max_reassembly_length=1000,
2115 expire_walk_interval_ms=50,
2117 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2120 # send fragments #2 and #1 - should be forwarded
2121 self.pg_enable_capture()
2122 self.src_if.add_stream(fragments[0:2])
2124 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
2125 self.logger.debug(self.vapi.ppcli("show buffers"))
2126 self.logger.debug(self.vapi.ppcli("show trace"))
2127 c = self.dst_if.get_capture(2)
2128 for sent, recvd in zip([fragments[1], fragments[0]], c):
2129 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2130 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2131 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2134 self.virtual_sleep(0.25, "wait before sending rest of fragments")
2136 # send rest of fragments - shouldn't be forwarded
2137 self.pg_enable_capture()
2138 self.src_if.add_stream(fragments[2:])
2140 self.dst_if.assert_nothing_captured()
2143 """reassembly reuses LRU element"""
2145 self.vapi.ip_reassembly_set(
2148 max_reassembly_length=1000,
2149 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2151 expire_walk_interval_ms=10000,
2157 while len(payload) < payload_len:
2158 payload += "%u " % counter
2165 for i in range(packet_count)
2167 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2168 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2169 / UDP(sport=1234, dport=5678)
2172 for f in fragment_rfc8200(p, i, payload_len / 4)
2175 self.pg_enable_capture()
2176 self.src_if.add_stream(fragments)
2178 c = self.dst_if.get_capture(len(fragments))
2179 for sent, recvd in zip(fragments, c):
2180 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2181 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2182 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2184 def test_one_fragment(self):
2185 """whole packet in one fragment processed independently"""
2187 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2188 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2189 / ICMPv6EchoRequest()
2192 frags = fragment_rfc8200(pkt, 1, 400)
2194 # send a fragment with known id
2195 self.send_and_expect(self.src_if, [frags[0]], self.dst_if)
2197 # send an atomic fragment with same id - should be reassembled
2199 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2200 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2201 / IPv6ExtHdrFragment(id=1)
2202 / ICMPv6EchoRequest()
2204 rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2206 # now forward packets matching original reassembly, should still work
2207 rx = self.send_and_expect(self.src_if, frags[1:], self.dst_if)
2209 def test_bunch_of_fragments(self):
2210 """valid fragments followed by rogue fragments and atomic fragment"""
2212 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2213 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2214 / ICMPv6EchoRequest()
2217 frags = fragment_rfc8200(pkt, 1, 400)
2218 rx = self.send_and_expect(self.src_if, frags, self.dst_if)
2221 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2222 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2223 / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
2227 self.send_and_expect(self.src_if, rogue * 604, self.dst_if)
2230 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2231 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2232 / IPv6ExtHdrFragment(id=1)
2233 / ICMPv6EchoRequest()
2235 rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2237 def test_truncated_fragment(self):
2238 """truncated fragment"""
2240 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
2241 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
2242 / IPv6ExtHdrFragment(nh=6)
2245 self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
2248 class TestIPv4ReassemblyLocalNode(VppTestCase):
2249 """IPv4 Reassembly for packets coming to ip4-local node"""
2252 def setUpClass(cls):
2253 super().setUpClass()
2255 cls.create_pg_interfaces([0])
2256 cls.src_dst_if = cls.pg0
2258 # setup all interfaces
2259 for i in cls.pg_interfaces:
2264 cls.padding = " abcdefghijklmn"
2266 cls.create_fragments()
2269 def tearDownClass(cls):
2270 super().tearDownClass()
2273 """Test setup - force timeout on existing reassemblies"""
2275 self.vapi.ip_reassembly_set(
2277 max_reassemblies=1000,
2278 max_reassembly_length=1000,
2279 expire_walk_interval_ms=10,
2281 self.virtual_sleep(0.25)
2282 self.vapi.ip_reassembly_set(
2284 max_reassemblies=1000,
2285 max_reassembly_length=1000,
2286 expire_walk_interval_ms=10000,
2292 def show_commands_at_teardown(self):
2293 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2294 self.logger.debug(self.vapi.ppcli("show buffers"))
2297 def create_stream(cls, packet_count=test_packet_count):
2298 """Create input packet stream for defined interface.
2300 :param list packet_sizes: Required packet sizes.
2302 for i in range(0, packet_count):
2303 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
2304 payload = cls.info_to_payload(info)
2306 Ether(dst=cls.src_dst_if.local_mac, src=cls.src_dst_if.remote_mac)
2309 src=cls.src_dst_if.remote_ip4,
2310 dst=cls.src_dst_if.local_ip4,
2312 / ICMP(type="echo-request", id=1234)
2315 cls.extend_packet(p, 1518, cls.padding)
2319 def create_fragments(cls):
2320 infos = cls._packet_infos
2322 for index, info in infos.items():
2324 # cls.logger.debug(ppp("Packet:",
2325 # p.__class__(scapy.compat.raw(p))))
2326 fragments_300 = fragment_rfc791(p, 300)
2327 cls.pkt_infos.append((index, fragments_300))
2328 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
2330 "Fragmented %s packets into %s 300-byte fragments"
2331 % (len(infos), len(cls.fragments_300))
2334 def verify_capture(self, capture):
2335 """Verify captured packet stream.
2337 :param list capture: Captured packet stream.
2341 for packet in capture:
2343 self.logger.debug(ppp("Got packet:", packet))
2346 payload_info = self.payload_to_info(packet[Raw])
2347 packet_index = payload_info.index
2348 if packet_index in seen:
2349 raise Exception(ppp("Duplicate packet received", packet))
2350 seen.add(packet_index)
2351 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
2352 info = self._packet_infos[packet_index]
2353 self.assertIsNotNone(info)
2354 self.assertEqual(packet_index, info.index)
2355 saved_packet = info.data
2356 self.assertEqual(ip.src, saved_packet[IP].dst)
2357 self.assertEqual(ip.dst, saved_packet[IP].src)
2358 self.assertEqual(icmp.type, 0) # echo reply
2359 self.assertEqual(icmp.id, saved_packet[ICMP].id)
2360 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
2362 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2364 for index in self._packet_infos:
2366 index, seen, "Packet with packet_index %d not received" % index
2369 def test_reassembly(self):
2370 """basic reassembly"""
2372 self.pg_enable_capture()
2373 self.src_dst_if.add_stream(self.fragments_300)
2376 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2377 self.verify_capture(packets)
2379 # run it all again to verify correctness
2380 self.pg_enable_capture()
2381 self.src_dst_if.add_stream(self.fragments_300)
2384 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2385 self.verify_capture(packets)
2388 class TestFIFReassembly(VppTestCase):
2389 """Fragments in fragments reassembly"""
2392 def setUpClass(cls):
2393 super().setUpClass()
2395 cls.create_pg_interfaces([0, 1])
2396 cls.src_if = cls.pg0
2397 cls.dst_if = cls.pg1
2398 for i in cls.pg_interfaces:
2405 cls.packet_sizes = [64, 512, 1518, 9018]
2406 cls.padding = " abcdefghijklmn"
2409 def tearDownClass(cls):
2410 super().tearDownClass()
2413 """Test setup - force timeout on existing reassemblies"""
2415 self.vapi.ip_reassembly_enable_disable(
2416 sw_if_index=self.src_if.sw_if_index, enable_ip4=True, enable_ip6=True
2418 self.vapi.ip_reassembly_enable_disable(
2419 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True, enable_ip6=True
2421 self.vapi.ip_reassembly_set(
2423 max_reassemblies=1000,
2424 max_reassembly_length=1000,
2425 expire_walk_interval_ms=10,
2427 self.vapi.ip_reassembly_set(
2429 max_reassemblies=1000,
2430 max_reassembly_length=1000,
2431 expire_walk_interval_ms=10,
2434 self.virtual_sleep(0.25)
2435 self.vapi.ip_reassembly_set(
2437 max_reassemblies=1000,
2438 max_reassembly_length=1000,
2439 expire_walk_interval_ms=10000,
2441 self.vapi.ip_reassembly_set(
2443 max_reassemblies=1000,
2444 max_reassembly_length=1000,
2445 expire_walk_interval_ms=10000,
2452 def show_commands_at_teardown(self):
2453 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2454 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
2455 self.logger.debug(self.vapi.ppcli("show buffers"))
2457 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
2458 """Verify captured packet stream.
2460 :param list capture: Captured packet stream.
2464 for packet in capture:
2466 self.logger.debug(ppp("Got packet:", packet))
2467 ip = packet[ip_class]
2469 payload_info = self.payload_to_info(packet[Raw])
2470 packet_index = payload_info.index
2472 packet_index not in dropped_packet_indexes,
2473 ppp("Packet received, but should be dropped:", packet),
2475 if packet_index in seen:
2476 raise Exception(ppp("Duplicate packet received", packet))
2477 seen.add(packet_index)
2478 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
2479 info = self._packet_infos[packet_index]
2480 self.assertTrue(info is not None)
2481 self.assertEqual(packet_index, info.index)
2482 saved_packet = info.data
2483 self.assertEqual(ip.src, saved_packet[ip_class].src)
2484 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
2485 self.assertEqual(udp.payload, saved_packet[UDP].payload)
2487 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2489 for index in self._packet_infos:
2491 index in seen or index in dropped_packet_indexes,
2492 "Packet with packet_index %d not received" % index,
2495 def test_fif4(self):
2496 """Fragments in fragments (4o4)"""
2498 # TODO this should be ideally in setUpClass, but then we hit a bug
2499 # with VppIpRoute incorrectly reporting it's present when it's not
2500 # so we need to manually remove the vpp config, thus we cannot have
2501 # it shared for multiple test cases
2502 self.tun_ip4 = "1.1.1.2"
2504 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
2505 self.gre4.add_vpp_config()
2506 self.gre4.admin_up()
2507 self.gre4.config_ip4()
2509 self.vapi.ip_reassembly_enable_disable(
2510 sw_if_index=self.gre4.sw_if_index, enable_ip4=True
2513 self.route4 = VppIpRoute(
2517 [VppRoutePath(self.src_if.remote_ip4, self.src_if.sw_if_index)],
2519 self.route4.add_vpp_config()
2521 self.reset_packet_infos()
2522 for i in range(test_packet_count):
2523 info = self.create_packet_info(self.src_if, self.dst_if)
2524 payload = self.info_to_payload(info)
2525 # Ethernet header here is only for size calculation, thus it
2526 # doesn't matter how it's initialized. This is to ensure that
2527 # reassembled packet is not > 9000 bytes, so that it's not dropped
2530 / IP(id=i, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
2531 / UDP(sport=1234, dport=5678)
2534 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2535 self.extend_packet(p, size, self.padding)
2536 info.data = p[IP] # use only IP part, without ethernet header
2540 for _, p in self._packet_infos.items()
2541 for x in fragment_rfc791(p.data, 400)
2544 encapped_fragments = [
2545 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2546 / IP(src=self.tun_ip4, dst=self.src_if.local_ip4)
2552 fragmented_encapped_fragments = [
2553 x for p in encapped_fragments for x in fragment_rfc791(p, 200)
2556 self.src_if.add_stream(fragmented_encapped_fragments)
2558 self.pg_enable_capture(self.pg_interfaces)
2561 self.src_if.assert_nothing_captured()
2562 packets = self.dst_if.get_capture(len(self._packet_infos))
2563 self.verify_capture(packets, IP)
2565 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2566 # so that it's query_vpp_config() works as it should
2567 self.gre4.remove_vpp_config()
2568 self.logger.debug(self.vapi.ppcli("show interface"))
2570 def test_fif6(self):
2571 """Fragments in fragments (6o6)"""
2572 # TODO this should be ideally in setUpClass, but then we hit a bug
2573 # with VppIpRoute incorrectly reporting it's present when it's not
2574 # so we need to manually remove the vpp config, thus we cannot have
2575 # it shared for multiple test cases
2576 self.tun_ip6 = "1002::1"
2578 self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
2579 self.gre6.add_vpp_config()
2580 self.gre6.admin_up()
2581 self.gre6.config_ip6()
2583 self.vapi.ip_reassembly_enable_disable(
2584 sw_if_index=self.gre6.sw_if_index, enable_ip6=True
2587 self.route6 = VppIpRoute(
2591 [VppRoutePath(self.src_if.remote_ip6, self.src_if.sw_if_index)],
2593 self.route6.add_vpp_config()
2595 self.reset_packet_infos()
2596 for i in range(test_packet_count):
2597 info = self.create_packet_info(self.src_if, self.dst_if)
2598 payload = self.info_to_payload(info)
2599 # Ethernet header here is only for size calculation, thus it
2600 # doesn't matter how it's initialized. This is to ensure that
2601 # reassembled packet is not > 9000 bytes, so that it's not dropped
2604 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2605 / UDP(sport=1234, dport=5678)
2608 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2609 self.extend_packet(p, size, self.padding)
2610 info.data = p[IPv6] # use only IPv6 part, without ethernet header
2614 for _, i in self._packet_infos.items()
2615 for x in fragment_rfc8200(i.data, i.index, 400)
2618 encapped_fragments = [
2619 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2620 / IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6)
2626 fragmented_encapped_fragments = [
2628 for p in encapped_fragments
2631 p, 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id, 200
2633 if IPv6ExtHdrFragment in p
2638 self.src_if.add_stream(fragmented_encapped_fragments)
2640 self.pg_enable_capture(self.pg_interfaces)
2643 self.src_if.assert_nothing_captured()
2644 packets = self.dst_if.get_capture(len(self._packet_infos))
2645 self.verify_capture(packets, IPv6)
2647 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2648 # so that it's query_vpp_config() works as it should
2649 self.gre6.remove_vpp_config()
2652 if __name__ == "__main__":
2653 unittest.main(testRunner=VppTestRunner)