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_truncated_fragment(self):
1608 """truncated fragment"""
1610 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1611 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
1612 / IPv6ExtHdrFragment(nh=6)
1615 self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
1617 def test_invalid_frag_size(self):
1618 """fragment size not a multiple of 8"""
1620 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1621 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1622 / UDP(sport=1234, dport=5678)
1625 self.extend_packet(p, 1000, self.padding)
1626 fragments = fragment_rfc8200(p, 1, 500)
1627 bad_fragment = fragments[0]
1628 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
1629 self.pg_enable_capture()
1630 self.src_if.add_stream([bad_fragment])
1632 pkts = self.src_if.get_capture(expected_count=1)
1634 self.assertIn(ICMPv6ParamProblem, icmp)
1635 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1637 def test_invalid_packet_size(self):
1638 """total packet size > 65535"""
1640 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1641 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1642 / UDP(sport=1234, dport=5678)
1645 self.extend_packet(p, 1000, self.padding)
1646 fragments = fragment_rfc8200(p, 1, 500)
1647 bad_fragment = fragments[1]
1648 bad_fragment[IPv6ExtHdrFragment].offset = 65500
1649 self.pg_enable_capture()
1650 self.src_if.add_stream([bad_fragment])
1652 pkts = self.src_if.get_capture(expected_count=1)
1654 self.assertIn(ICMPv6ParamProblem, icmp)
1655 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1657 def test_atomic_fragment(self):
1658 """IPv6 atomic fragment"""
1660 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1661 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=65535)
1662 / IPv6ExtHdrFragment(
1663 offset=8191, m=1, res1=0xFF, res2=0xFF, nh=255, id=0xFFFF
1668 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1669 self.assertIn(ICMPv6ParamProblem, rx[0])
1671 def test_truncated_fragment(self):
1672 """IPv6 truncated fragment header"""
1674 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1675 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
1676 / IPv6ExtHdrFragment(nh=6)
1679 self.send_and_assert_no_replies(self.pg0, [pkt])
1682 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1683 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.remote_ip6)
1684 / ICMPv6EchoRequest()
1686 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1688 def test_one_fragment(self):
1689 """whole packet in one fragment processed independently"""
1691 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1692 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1693 / ICMPv6EchoRequest()
1696 frags = fragment_rfc8200(pkt, 1, 400)
1698 # send a fragment with known id
1699 self.send_and_assert_no_replies(self.pg0, [frags[0]])
1701 # send an atomic fragment with same id - should be reassembled
1703 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1704 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1705 / IPv6ExtHdrFragment(id=1)
1706 / ICMPv6EchoRequest()
1708 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1709 self.assertNotIn(IPv6ExtHdrFragment, rx)
1711 # now finish the original reassembly, this should still be possible
1712 rx = self.send_and_expect(self.pg0, frags[1:], self.pg0, n_rx=1)
1713 self.assertNotIn(IPv6ExtHdrFragment, rx)
1715 def test_bunch_of_fragments(self):
1716 """valid fragments followed by rogue fragments and atomic fragment"""
1718 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1719 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1720 / ICMPv6EchoRequest()
1723 frags = fragment_rfc8200(pkt, 1, 400)
1724 self.send_and_expect(self.pg0, frags, self.pg0, n_rx=1)
1727 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1728 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1729 / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
1733 self.send_and_assert_no_replies(self.pg0, inc_frag * 604)
1736 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1737 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1738 / IPv6ExtHdrFragment(id=1)
1739 / ICMPv6EchoRequest()
1741 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1742 self.assertNotIn(IPv6ExtHdrFragment, rx)
1744 def test_local_enable_disable(self):
1745 """local reassembly enabled/disable"""
1746 self.vapi.ip_reassembly_enable_disable(
1747 sw_if_index=self.src_if.sw_if_index, enable_ip6=False
1749 self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1751 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
1752 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1753 / ICMPv6EchoRequest(id=1234)
1756 frags = fragment_rfc8200(pkt, 1, 400)
1757 r = self.send_and_expect(self.src_if, frags, self.src_if, n_rx=1)[0]
1758 self.assertEqual(1234, r[ICMPv6EchoReply].id)
1759 self.vapi.ip_local_reass_enable_disable()
1761 self.send_and_assert_no_replies(self.src_if, frags)
1762 self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1765 class TestIPv6MWReassembly(VppTestCase):
1766 """IPv6 Reassembly (multiple workers)"""
1768 vpp_worker_count = 3
1771 def setUpClass(cls):
1772 super().setUpClass()
1774 cls.create_pg_interfaces(range(cls.vpp_worker_count + 1))
1775 cls.src_if = cls.pg0
1776 cls.send_ifs = cls.pg_interfaces[:-1]
1777 cls.dst_if = cls.pg_interfaces[-1]
1779 # setup all interfaces
1780 for i in cls.pg_interfaces:
1785 # packets sizes reduced here because we are generating packets without
1786 # Ethernet headers, which are added later (diff fragments go via
1787 # different interfaces)
1788 cls.packet_sizes = [
1791 1518 - len(Ether()),
1792 9018 - len(Ether()),
1794 cls.padding = " abcdefghijklmn"
1795 cls.create_stream(cls.packet_sizes)
1796 cls.create_fragments()
1799 def tearDownClass(cls):
1800 super().tearDownClass()
1803 """Test setup - force timeout on existing reassemblies"""
1805 for intf in self.send_ifs:
1806 self.vapi.ip_reassembly_enable_disable(
1807 sw_if_index=intf.sw_if_index, enable_ip6=True
1809 self.vapi.ip_reassembly_set(
1811 max_reassemblies=1000,
1812 max_reassembly_length=1000,
1813 expire_walk_interval_ms=10,
1816 self.virtual_sleep(0.25)
1817 self.vapi.ip_reassembly_set(
1819 max_reassemblies=1000,
1820 max_reassembly_length=1000,
1821 expire_walk_interval_ms=1000,
1826 for intf in self.send_ifs:
1827 self.vapi.ip_reassembly_enable_disable(
1828 sw_if_index=intf.sw_if_index, enable_ip6=False
1832 def show_commands_at_teardown(self):
1833 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1834 self.logger.debug(self.vapi.ppcli("show buffers"))
1837 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1838 """Create input packet stream
1840 :param list packet_sizes: Required packet sizes.
1842 for i in range(0, packet_count):
1843 info = cls.create_packet_info(cls.src_if, cls.src_if)
1844 payload = cls.info_to_payload(info)
1846 IPv6(src=cls.src_if.remote_ip6, dst=cls.dst_if.remote_ip6)
1847 / UDP(sport=1234, dport=5678)
1850 size = packet_sizes[(i // 2) % len(packet_sizes)]
1851 cls.extend_packet(p, size, cls.padding)
1855 def create_fragments(cls):
1856 infos = cls._packet_infos
1858 for index, info in infos.items():
1860 # cls.logger.debug(ppp("Packet:",
1861 # p.__class__(scapy.compat.raw(p))))
1862 fragments_400 = fragment_rfc8200(p, index, 400)
1863 cls.pkt_infos.append((index, fragments_400))
1864 cls.fragments_400 = [x for (_, frags) in cls.pkt_infos for x in frags]
1866 "Fragmented %s packets into %s 400-byte fragments, "
1867 % (len(infos), len(cls.fragments_400))
1870 def verify_capture(self, capture, dropped_packet_indexes=[]):
1871 """Verify captured packet strea .
1873 :param list capture: Captured packet stream.
1877 for packet in capture:
1879 self.logger.debug(ppp("Got packet:", packet))
1882 payload_info = self.payload_to_info(packet[Raw])
1883 packet_index = payload_info.index
1885 packet_index not in dropped_packet_indexes,
1886 ppp("Packet received, but should be dropped:", packet),
1888 if packet_index in seen:
1889 raise Exception(ppp("Duplicate packet received", packet))
1890 seen.add(packet_index)
1891 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1892 info = self._packet_infos[packet_index]
1893 self.assertTrue(info is not None)
1894 self.assertEqual(packet_index, info.index)
1895 saved_packet = info.data
1896 self.assertEqual(ip.src, saved_packet[IPv6].src)
1897 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1898 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1900 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1902 for index in self._packet_infos:
1904 index in seen or index in dropped_packet_indexes,
1905 "Packet with packet_index %d not received" % index,
1908 def send_packets(self, packets):
1909 for counter in range(self.vpp_worker_count):
1910 if 0 == len(packets[counter]):
1912 send_if = self.send_ifs[counter]
1915 Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1916 for x in packets[counter]
1922 def test_worker_conflict(self):
1923 """1st and FO=0 fragments on different workers"""
1925 # in first wave we send fragments which don't start at offset 0
1926 # then we send fragments with offset 0 on a different thread
1927 # then the rest of packets on a random thread
1928 first_packets = [[] for n in range(self.vpp_worker_count)]
1929 second_packets = [[] for n in range(self.vpp_worker_count)]
1930 rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1931 for _, p in self.pkt_infos:
1932 wi = randrange(self.vpp_worker_count)
1933 second_packets[wi].append(p[0])
1938 wi2 = randrange(self.vpp_worker_count)
1939 first_packets[wi2].append(p[1])
1940 wi3 = randrange(self.vpp_worker_count)
1941 rest_of_packets[wi3].extend(p[2:])
1943 self.pg_enable_capture()
1944 self.send_packets(first_packets)
1945 self.send_packets(second_packets)
1946 self.send_packets(rest_of_packets)
1948 packets = self.dst_if.get_capture(len(self.pkt_infos))
1949 self.verify_capture(packets)
1950 for send_if in self.send_ifs:
1951 send_if.assert_nothing_captured()
1953 self.logger.debug(self.vapi.ppcli("show trace"))
1954 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1955 self.logger.debug(self.vapi.ppcli("show buffers"))
1956 self.vapi.cli("clear trace")
1958 self.pg_enable_capture()
1959 self.send_packets(first_packets)
1960 self.send_packets(second_packets)
1961 self.send_packets(rest_of_packets)
1963 packets = self.dst_if.get_capture(len(self.pkt_infos))
1964 self.verify_capture(packets)
1965 for send_if in self.send_ifs:
1966 send_if.assert_nothing_captured()
1969 class TestIPv6SVReassembly(VppTestCase):
1970 """IPv6 Shallow Virtual Reassembly"""
1973 def setUpClass(cls):
1974 super().setUpClass()
1976 cls.create_pg_interfaces([0, 1])
1977 cls.src_if = cls.pg0
1978 cls.dst_if = cls.pg1
1980 # setup all interfaces
1981 for i in cls.pg_interfaces:
1987 """Test setup - force timeout on existing reassemblies"""
1989 self.vapi.ip_reassembly_enable_disable(
1990 sw_if_index=self.src_if.sw_if_index,
1992 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1994 self.vapi.ip_reassembly_set(
1996 max_reassemblies=1000,
1997 max_reassembly_length=1000,
1998 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1999 expire_walk_interval_ms=10,
2002 self.virtual_sleep(0.25)
2003 self.vapi.ip_reassembly_set(
2005 max_reassemblies=1000,
2006 max_reassembly_length=1000,
2007 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2008 expire_walk_interval_ms=10000,
2014 self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2015 self.logger.debug(self.vapi.ppcli("show buffers"))
2017 def test_basic(self):
2018 """basic reassembly"""
2022 while len(payload) < payload_len:
2023 payload += "%u " % counter
2027 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2028 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2029 / UDP(sport=1234, dport=5678)
2032 fragments = fragment_rfc8200(p, 1, payload_len / 4)
2034 # send fragment #2 - should be cached inside reassembly
2035 self.pg_enable_capture()
2036 self.src_if.add_stream(fragments[1])
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 self.dst_if.assert_nothing_captured()
2043 # send fragment #1 - reassembly is finished now and both fragments
2045 self.pg_enable_capture()
2046 self.src_if.add_stream(fragments[0])
2048 self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2049 self.logger.debug(self.vapi.ppcli("show buffers"))
2050 self.logger.debug(self.vapi.ppcli("show trace"))
2051 c = self.dst_if.get_capture(2)
2052 for sent, recvd in zip([fragments[1], fragments[0]], 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 # send rest of fragments - should be immediately forwarded
2058 self.pg_enable_capture()
2059 self.src_if.add_stream(fragments[2:])
2061 c = self.dst_if.get_capture(len(fragments[2:]))
2062 for sent, recvd in zip(fragments[2:], c):
2063 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2064 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2065 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2067 def test_verify_clear_trace_mid_reassembly(self):
2068 """verify clear trace works mid-reassembly"""
2072 while len(payload) < payload_len:
2073 payload += "%u " % counter
2077 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2078 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2079 / UDP(sport=1234, dport=5678)
2082 fragments = fragment_rfc8200(p, 1, payload_len / 4)
2084 self.pg_enable_capture()
2085 self.src_if.add_stream(fragments[1])
2088 self.logger.debug(self.vapi.cli("show trace"))
2089 self.vapi.cli("clear trace")
2091 self.pg_enable_capture()
2092 self.src_if.add_stream(fragments[0])
2094 self.dst_if.get_capture(2)
2096 self.logger.debug(self.vapi.cli("show trace"))
2097 self.vapi.cli("clear trace")
2099 self.pg_enable_capture()
2100 self.src_if.add_stream(fragments[2:])
2102 self.dst_if.get_capture(len(fragments[2:]))
2104 def test_timeout(self):
2105 """reassembly timeout"""
2109 while len(payload) < payload_len:
2110 payload += "%u " % counter
2114 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2115 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2116 / UDP(sport=1234, dport=5678)
2119 fragments = fragment_rfc8200(p, 1, payload_len / 4)
2121 self.vapi.ip_reassembly_set(
2123 max_reassemblies=1000,
2124 max_reassembly_length=1000,
2125 expire_walk_interval_ms=50,
2127 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2130 # send fragments #2 and #1 - should be forwarded
2131 self.pg_enable_capture()
2132 self.src_if.add_stream(fragments[0:2])
2134 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
2135 self.logger.debug(self.vapi.ppcli("show buffers"))
2136 self.logger.debug(self.vapi.ppcli("show trace"))
2137 c = self.dst_if.get_capture(2)
2138 for sent, recvd in zip([fragments[1], fragments[0]], c):
2139 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2140 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2141 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2144 self.virtual_sleep(0.25, "wait before sending rest of fragments")
2146 # send rest of fragments - shouldn't be forwarded
2147 self.pg_enable_capture()
2148 self.src_if.add_stream(fragments[2:])
2150 self.dst_if.assert_nothing_captured()
2153 """reassembly reuses LRU element"""
2155 self.vapi.ip_reassembly_set(
2158 max_reassembly_length=1000,
2159 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2161 expire_walk_interval_ms=10000,
2167 while len(payload) < payload_len:
2168 payload += "%u " % counter
2175 for i in range(packet_count)
2177 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2178 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2179 / UDP(sport=1234, dport=5678)
2182 for f in fragment_rfc8200(p, i, payload_len / 4)
2185 self.pg_enable_capture()
2186 self.src_if.add_stream(fragments)
2188 c = self.dst_if.get_capture(len(fragments))
2189 for sent, recvd in zip(fragments, c):
2190 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2191 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2192 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2194 def test_one_fragment(self):
2195 """whole packet in one fragment processed independently"""
2197 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2198 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2199 / ICMPv6EchoRequest()
2202 frags = fragment_rfc8200(pkt, 1, 400)
2204 # send a fragment with known id
2205 self.send_and_expect(self.src_if, [frags[0]], self.dst_if)
2207 # send an atomic fragment with same id - should be reassembled
2209 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2210 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2211 / IPv6ExtHdrFragment(id=1)
2212 / ICMPv6EchoRequest()
2214 rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2216 # now forward packets matching original reassembly, should still work
2217 rx = self.send_and_expect(self.src_if, frags[1:], self.dst_if)
2219 def test_bunch_of_fragments(self):
2220 """valid fragments followed by rogue fragments and atomic fragment"""
2222 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2223 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2224 / ICMPv6EchoRequest()
2227 frags = fragment_rfc8200(pkt, 1, 400)
2228 rx = self.send_and_expect(self.src_if, frags, self.dst_if)
2231 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2232 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2233 / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
2237 self.send_and_expect(self.src_if, rogue * 604, self.dst_if)
2240 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2241 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2242 / IPv6ExtHdrFragment(id=1)
2243 / ICMPv6EchoRequest()
2245 rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2247 def test_truncated_fragment(self):
2248 """truncated fragment"""
2250 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
2251 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
2252 / IPv6ExtHdrFragment(nh=6)
2255 self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
2258 class TestIPv4ReassemblyLocalNode(VppTestCase):
2259 """IPv4 Reassembly for packets coming to ip4-local node"""
2262 def setUpClass(cls):
2263 super().setUpClass()
2265 cls.create_pg_interfaces([0])
2266 cls.src_dst_if = cls.pg0
2268 # setup all interfaces
2269 for i in cls.pg_interfaces:
2274 cls.padding = " abcdefghijklmn"
2276 cls.create_fragments()
2279 def tearDownClass(cls):
2280 super().tearDownClass()
2283 """Test setup - force timeout on existing reassemblies"""
2285 self.vapi.ip_reassembly_set(
2287 max_reassemblies=1000,
2288 max_reassembly_length=1000,
2289 expire_walk_interval_ms=10,
2291 self.virtual_sleep(0.25)
2292 self.vapi.ip_reassembly_set(
2294 max_reassemblies=1000,
2295 max_reassembly_length=1000,
2296 expire_walk_interval_ms=10000,
2302 def show_commands_at_teardown(self):
2303 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2304 self.logger.debug(self.vapi.ppcli("show buffers"))
2307 def create_stream(cls, packet_count=test_packet_count):
2308 """Create input packet stream for defined interface.
2310 :param list packet_sizes: Required packet sizes.
2312 for i in range(0, packet_count):
2313 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
2314 payload = cls.info_to_payload(info)
2316 Ether(dst=cls.src_dst_if.local_mac, src=cls.src_dst_if.remote_mac)
2319 src=cls.src_dst_if.remote_ip4,
2320 dst=cls.src_dst_if.local_ip4,
2322 / ICMP(type="echo-request", id=1234)
2325 cls.extend_packet(p, 1518, cls.padding)
2329 def create_fragments(cls):
2330 infos = cls._packet_infos
2332 for index, info in infos.items():
2334 # cls.logger.debug(ppp("Packet:",
2335 # p.__class__(scapy.compat.raw(p))))
2336 fragments_300 = fragment_rfc791(p, 300)
2337 cls.pkt_infos.append((index, fragments_300))
2338 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
2340 "Fragmented %s packets into %s 300-byte fragments"
2341 % (len(infos), len(cls.fragments_300))
2344 def verify_capture(self, capture):
2345 """Verify captured packet stream.
2347 :param list capture: Captured packet stream.
2351 for packet in capture:
2353 self.logger.debug(ppp("Got packet:", packet))
2356 payload_info = self.payload_to_info(packet[Raw])
2357 packet_index = payload_info.index
2358 if packet_index in seen:
2359 raise Exception(ppp("Duplicate packet received", packet))
2360 seen.add(packet_index)
2361 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
2362 info = self._packet_infos[packet_index]
2363 self.assertIsNotNone(info)
2364 self.assertEqual(packet_index, info.index)
2365 saved_packet = info.data
2366 self.assertEqual(ip.src, saved_packet[IP].dst)
2367 self.assertEqual(ip.dst, saved_packet[IP].src)
2368 self.assertEqual(icmp.type, 0) # echo reply
2369 self.assertEqual(icmp.id, saved_packet[ICMP].id)
2370 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
2372 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2374 for index in self._packet_infos:
2376 index, seen, "Packet with packet_index %d not received" % index
2379 def test_reassembly(self):
2380 """basic reassembly"""
2382 self.pg_enable_capture()
2383 self.src_dst_if.add_stream(self.fragments_300)
2386 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2387 self.verify_capture(packets)
2389 # run it all again to verify correctness
2390 self.pg_enable_capture()
2391 self.src_dst_if.add_stream(self.fragments_300)
2394 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2395 self.verify_capture(packets)
2398 class TestFIFReassembly(VppTestCase):
2399 """Fragments in fragments reassembly"""
2402 def setUpClass(cls):
2403 super().setUpClass()
2405 cls.create_pg_interfaces([0, 1])
2406 cls.src_if = cls.pg0
2407 cls.dst_if = cls.pg1
2408 for i in cls.pg_interfaces:
2415 cls.packet_sizes = [64, 512, 1518, 9018]
2416 cls.padding = " abcdefghijklmn"
2419 def tearDownClass(cls):
2420 super().tearDownClass()
2423 """Test setup - force timeout on existing reassemblies"""
2425 self.vapi.ip_reassembly_enable_disable(
2426 sw_if_index=self.src_if.sw_if_index, enable_ip4=True, enable_ip6=True
2428 self.vapi.ip_reassembly_enable_disable(
2429 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True, enable_ip6=True
2431 self.vapi.ip_reassembly_set(
2433 max_reassemblies=1000,
2434 max_reassembly_length=1000,
2435 expire_walk_interval_ms=10,
2437 self.vapi.ip_reassembly_set(
2439 max_reassemblies=1000,
2440 max_reassembly_length=1000,
2441 expire_walk_interval_ms=10,
2444 self.virtual_sleep(0.25)
2445 self.vapi.ip_reassembly_set(
2447 max_reassemblies=1000,
2448 max_reassembly_length=1000,
2449 expire_walk_interval_ms=10000,
2451 self.vapi.ip_reassembly_set(
2453 max_reassemblies=1000,
2454 max_reassembly_length=1000,
2455 expire_walk_interval_ms=10000,
2462 def show_commands_at_teardown(self):
2463 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2464 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
2465 self.logger.debug(self.vapi.ppcli("show buffers"))
2467 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
2468 """Verify captured packet stream.
2470 :param list capture: Captured packet stream.
2474 for packet in capture:
2476 self.logger.debug(ppp("Got packet:", packet))
2477 ip = packet[ip_class]
2479 payload_info = self.payload_to_info(packet[Raw])
2480 packet_index = payload_info.index
2482 packet_index not in dropped_packet_indexes,
2483 ppp("Packet received, but should be dropped:", packet),
2485 if packet_index in seen:
2486 raise Exception(ppp("Duplicate packet received", packet))
2487 seen.add(packet_index)
2488 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
2489 info = self._packet_infos[packet_index]
2490 self.assertTrue(info is not None)
2491 self.assertEqual(packet_index, info.index)
2492 saved_packet = info.data
2493 self.assertEqual(ip.src, saved_packet[ip_class].src)
2494 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
2495 self.assertEqual(udp.payload, saved_packet[UDP].payload)
2497 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2499 for index in self._packet_infos:
2501 index in seen or index in dropped_packet_indexes,
2502 "Packet with packet_index %d not received" % index,
2505 def test_fif4(self):
2506 """Fragments in fragments (4o4)"""
2508 # TODO this should be ideally in setUpClass, but then we hit a bug
2509 # with VppIpRoute incorrectly reporting it's present when it's not
2510 # so we need to manually remove the vpp config, thus we cannot have
2511 # it shared for multiple test cases
2512 self.tun_ip4 = "1.1.1.2"
2514 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
2515 self.gre4.add_vpp_config()
2516 self.gre4.admin_up()
2517 self.gre4.config_ip4()
2519 self.vapi.ip_reassembly_enable_disable(
2520 sw_if_index=self.gre4.sw_if_index, enable_ip4=True
2523 self.route4 = VppIpRoute(
2527 [VppRoutePath(self.src_if.remote_ip4, self.src_if.sw_if_index)],
2529 self.route4.add_vpp_config()
2531 self.reset_packet_infos()
2532 for i in range(test_packet_count):
2533 info = self.create_packet_info(self.src_if, self.dst_if)
2534 payload = self.info_to_payload(info)
2535 # Ethernet header here is only for size calculation, thus it
2536 # doesn't matter how it's initialized. This is to ensure that
2537 # reassembled packet is not > 9000 bytes, so that it's not dropped
2540 / IP(id=i, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
2541 / UDP(sport=1234, dport=5678)
2544 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2545 self.extend_packet(p, size, self.padding)
2546 info.data = p[IP] # use only IP part, without ethernet header
2550 for _, p in self._packet_infos.items()
2551 for x in fragment_rfc791(p.data, 400)
2554 encapped_fragments = [
2555 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2556 / IP(src=self.tun_ip4, dst=self.src_if.local_ip4)
2562 fragmented_encapped_fragments = [
2563 x for p in encapped_fragments for x in fragment_rfc791(p, 200)
2566 self.src_if.add_stream(fragmented_encapped_fragments)
2568 self.pg_enable_capture(self.pg_interfaces)
2571 self.src_if.assert_nothing_captured()
2572 packets = self.dst_if.get_capture(len(self._packet_infos))
2573 self.verify_capture(packets, IP)
2575 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2576 # so that it's query_vpp_config() works as it should
2577 self.gre4.remove_vpp_config()
2578 self.logger.debug(self.vapi.ppcli("show interface"))
2580 def test_fif6(self):
2581 """Fragments in fragments (6o6)"""
2582 # TODO this should be ideally in setUpClass, but then we hit a bug
2583 # with VppIpRoute incorrectly reporting it's present when it's not
2584 # so we need to manually remove the vpp config, thus we cannot have
2585 # it shared for multiple test cases
2586 self.tun_ip6 = "1002::1"
2588 self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
2589 self.gre6.add_vpp_config()
2590 self.gre6.admin_up()
2591 self.gre6.config_ip6()
2593 self.vapi.ip_reassembly_enable_disable(
2594 sw_if_index=self.gre6.sw_if_index, enable_ip6=True
2597 self.route6 = VppIpRoute(
2601 [VppRoutePath(self.src_if.remote_ip6, self.src_if.sw_if_index)],
2603 self.route6.add_vpp_config()
2605 self.reset_packet_infos()
2606 for i in range(test_packet_count):
2607 info = self.create_packet_info(self.src_if, self.dst_if)
2608 payload = self.info_to_payload(info)
2609 # Ethernet header here is only for size calculation, thus it
2610 # doesn't matter how it's initialized. This is to ensure that
2611 # reassembled packet is not > 9000 bytes, so that it's not dropped
2614 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2615 / UDP(sport=1234, dport=5678)
2618 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2619 self.extend_packet(p, size, self.padding)
2620 info.data = p[IPv6] # use only IPv6 part, without ethernet header
2624 for _, i in self._packet_infos.items()
2625 for x in fragment_rfc8200(i.data, i.index, 400)
2628 encapped_fragments = [
2629 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2630 / IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6)
2636 fragmented_encapped_fragments = [
2638 for p in encapped_fragments
2641 p, 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id, 200
2643 if IPv6ExtHdrFragment in p
2648 self.src_if.add_stream(fragmented_encapped_fragments)
2650 self.pg_enable_capture(self.pg_interfaces)
2653 self.src_if.assert_nothing_captured()
2654 packets = self.dst_if.get_capture(len(self._packet_infos))
2655 self.verify_capture(packets, IPv6)
2657 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2658 # so that it's query_vpp_config() works as it should
2659 self.gre6.remove_vpp_config()
2662 if __name__ == "__main__":
2663 unittest.main(testRunner=VppTestRunner)