4 from random import shuffle, choice, randrange
6 from framework import VppTestCase, 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 framework import VppTestCase, VppTestRunner
25 from util import ppp, ppc, fragment_rfc791, fragment_rfc8200
26 from vpp_gre_interface import VppGreInterface
27 from vpp_ip import DpoProto
28 from vpp_ip_route import VppIpRoute, VppRoutePath, FibPathProto
29 from vpp_papi import VppEnum
31 # 35 is enough to have >257 400-byte fragments
32 test_packet_count = 35
35 class TestIPv4Reassembly(VppTestCase):
42 cls.create_pg_interfaces([0, 1])
46 # setup all interfaces
47 for i in cls.pg_interfaces:
53 cls.packet_sizes = [64, 512, 1518, 9018]
54 cls.padding = " abcdefghijklmn"
55 cls.create_stream(cls.packet_sizes)
56 cls.create_fragments()
59 def tearDownClass(cls):
60 super().tearDownClass()
63 """Test setup - force timeout on existing reassemblies"""
65 self.vapi.ip_reassembly_enable_disable(
66 sw_if_index=self.src_if.sw_if_index, enable_ip4=True
68 self.vapi.ip_reassembly_set(
70 max_reassemblies=1000,
71 max_reassembly_length=1000,
72 expire_walk_interval_ms=10,
74 self.virtual_sleep(0.25)
75 self.vapi.ip_reassembly_set(
77 max_reassemblies=1000,
78 max_reassembly_length=1000,
79 expire_walk_interval_ms=10000,
83 self.vapi.ip_reassembly_enable_disable(
84 sw_if_index=self.src_if.sw_if_index, enable_ip4=False
88 def show_commands_at_teardown(self):
89 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
90 self.logger.debug(self.vapi.ppcli("show buffers"))
93 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
94 """Create input packet stream
96 :param list packet_sizes: Required packet sizes.
98 for i in range(0, packet_count):
99 info = cls.create_packet_info(cls.src_if, cls.src_if)
100 payload = cls.info_to_payload(info)
102 Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac)
104 id=info.index, src=cls.src_if.remote_ip4, dst=cls.dst_if.remote_ip4
106 / UDP(sport=1234, dport=5678)
109 size = packet_sizes[(i // 2) % len(packet_sizes)]
110 cls.extend_packet(p, size, cls.padding)
114 def create_fragments(cls):
115 infos = cls._packet_infos
117 for index, info in infos.items():
119 # cls.logger.debug(ppp("Packet:",
120 # p.__class__(scapy.compat.raw(p))))
121 fragments_400 = fragment_rfc791(p, 400)
122 fragments_300 = fragment_rfc791(p, 300)
123 fragments_200 = [x for f in fragments_400 for x in fragment_rfc791(f, 200)]
124 cls.pkt_infos.append((index, fragments_400, fragments_300, fragments_200))
125 cls.fragments_400 = [x for (_, frags, _, _) in cls.pkt_infos for x in frags]
126 cls.fragments_300 = [x for (_, _, frags, _) in cls.pkt_infos for x in frags]
127 cls.fragments_200 = [x for (_, _, _, frags) in cls.pkt_infos for x in frags]
129 "Fragmented %s packets into %s 400-byte fragments, "
130 "%s 300-byte fragments and %s 200-byte fragments"
133 len(cls.fragments_400),
134 len(cls.fragments_300),
135 len(cls.fragments_200),
139 def verify_capture(self, capture, dropped_packet_indexes=[]):
140 """Verify captured packet stream.
142 :param list capture: Captured packet stream.
146 for packet in capture:
148 self.logger.debug(ppp("Got packet:", packet))
151 payload_info = self.payload_to_info(packet[Raw])
152 packet_index = payload_info.index
154 packet_index not in dropped_packet_indexes,
155 ppp("Packet received, but should be dropped:", packet),
157 if packet_index in seen:
158 raise Exception(ppp("Duplicate packet received", packet))
159 seen.add(packet_index)
160 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
161 info = self._packet_infos[packet_index]
162 self.assertTrue(info is not None)
163 self.assertEqual(packet_index, info.index)
164 saved_packet = info.data
165 self.assertEqual(ip.src, saved_packet[IP].src)
166 self.assertEqual(ip.dst, saved_packet[IP].dst)
167 self.assertEqual(udp.payload, saved_packet[UDP].payload)
169 self.logger.error(ppp("Unexpected or invalid packet:", packet))
171 for index in self._packet_infos:
173 index in seen or index in dropped_packet_indexes,
174 "Packet with packet_index %d not received" % index,
177 def test_reassembly(self):
178 """basic reassembly"""
180 self.pg_enable_capture()
181 self.src_if.add_stream(self.fragments_200)
184 packets = self.dst_if.get_capture(len(self.pkt_infos))
185 self.verify_capture(packets)
186 self.src_if.assert_nothing_captured()
188 # run it all again to verify correctness
189 self.pg_enable_capture()
190 self.src_if.add_stream(self.fragments_200)
193 packets = self.dst_if.get_capture(len(self.pkt_infos))
194 self.verify_capture(packets)
195 self.src_if.assert_nothing_captured()
197 def test_verify_clear_trace_mid_reassembly(self):
198 """verify clear trace works mid-reassembly"""
200 self.pg_enable_capture()
201 self.src_if.add_stream(self.fragments_200[0:-1])
204 self.logger.debug(self.vapi.cli("show trace"))
205 self.vapi.cli("clear trace")
207 self.src_if.add_stream(self.fragments_200[-1])
209 packets = self.dst_if.get_capture(len(self.pkt_infos))
210 self.verify_capture(packets)
212 def test_reversed(self):
213 """reverse order reassembly"""
215 fragments = list(self.fragments_200)
218 self.pg_enable_capture()
219 self.src_if.add_stream(fragments)
222 packets = self.dst_if.get_capture(len(self.packet_infos))
223 self.verify_capture(packets)
224 self.src_if.assert_nothing_captured()
226 # run it all again to verify correctness
227 self.pg_enable_capture()
228 self.src_if.add_stream(fragments)
231 packets = self.dst_if.get_capture(len(self.packet_infos))
232 self.verify_capture(packets)
233 self.src_if.assert_nothing_captured()
235 def test_long_fragment_chain(self):
236 """long fragment chain"""
239 "/err/ip4-full-reassembly-feature/fragment chain too long (drop)"
242 error_cnt = self.statistics.get_err_counter(error_cnt_str)
244 self.vapi.ip_reassembly_set(
246 max_reassemblies=1000,
247 max_reassembly_length=3,
248 expire_walk_interval_ms=50,
252 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
253 / IP(id=1000, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
254 / UDP(sport=1234, dport=5678)
258 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
259 / IP(id=1001, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
260 / UDP(sport=1234, dport=5678)
263 frags = fragment_rfc791(p1, 200) + fragment_rfc791(p2, 500)
265 self.pg_enable_capture()
266 self.src_if.add_stream(frags)
269 self.dst_if.get_capture(1)
270 self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
273 """fragment length + ip header size > 65535"""
274 self.vapi.cli("clear errors")
275 raw = b"""E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n\
276 \x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-message.\
277 Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Offset; Test-case: 5737"""
278 malformed_packet = Ether(
279 dst=self.src_if.local_mac, src=self.src_if.remote_mac
282 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
283 / IP(id=1000, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
284 / UDP(sport=1234, dport=5678)
287 valid_fragments = fragment_rfc791(p, 400)
289 counter = "/err/ip4-full-reassembly-feature/malformed packets"
290 error_counter = self.statistics.get_err_counter(counter)
291 self.pg_enable_capture()
292 self.src_if.add_stream([malformed_packet] + valid_fragments)
295 self.dst_if.get_capture(1)
296 self.logger.debug(self.vapi.ppcli("show error"))
297 self.assertEqual(self.statistics.get_err_counter(counter), error_counter + 1)
299 def test_44924(self):
300 """compress tiny fragments"""
303 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
309 src=self.src_if.remote_ip4,
310 dst=self.dst_if.remote_ip4,
312 / ICMP(type="echo-request", code=0, id=0x1FE6, seq=0x2407)
313 / Raw(load="Test-group: IPv4")
316 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
322 src=self.src_if.remote_ip4,
323 dst=self.dst_if.remote_ip4,
325 / ICMP(type="echo-request", code=0, id=0x1FE6, seq=0x2407)
326 / Raw(load=".IPv4.Fragmentation.vali")
329 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
334 src=self.src_if.remote_ip4,
335 dst=self.dst_if.remote_ip4,
337 / ICMP(type="echo-request", code=0, id=0x1FE6, seq=0x2407)
338 / Raw(load="d; Test-case: 44924")
342 self.pg_enable_capture()
343 self.src_if.add_stream(packets)
346 self.dst_if.get_capture(1)
348 def test_frag_1(self):
349 """fragment of size 1"""
350 self.vapi.cli("clear errors")
351 malformed_packets = [
353 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
360 src=self.src_if.remote_ip4,
361 dst=self.dst_if.remote_ip4,
363 / ICMP(type="echo-request")
366 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
372 src=self.src_if.remote_ip4,
373 dst=self.dst_if.remote_ip4,
380 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
381 / IP(id=1000, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
382 / UDP(sport=1234, dport=5678)
385 valid_fragments = fragment_rfc791(p, 400)
387 self.pg_enable_capture()
388 self.src_if.add_stream(malformed_packets + valid_fragments)
391 self.dst_if.get_capture(1)
393 self.assert_packet_counter_equal("ip4-full-reassembly-feature", 1)
394 # TODO remove above, uncomment below once clearing of counters
396 # self.assert_packet_counter_equal(
397 # "/err/ip4-full-reassembly-feature/malformed packets", 1)
399 def test_random(self):
400 """random order reassembly"""
402 fragments = list(self.fragments_200)
405 self.pg_enable_capture()
406 self.src_if.add_stream(fragments)
409 packets = self.dst_if.get_capture(len(self.packet_infos))
410 self.verify_capture(packets)
411 self.src_if.assert_nothing_captured()
413 # run it all again to verify correctness
414 self.pg_enable_capture()
415 self.src_if.add_stream(fragments)
418 packets = self.dst_if.get_capture(len(self.packet_infos))
419 self.verify_capture(packets)
420 self.src_if.assert_nothing_captured()
422 def test_duplicates(self):
423 """duplicate fragments"""
427 for (_, frags, _, _) in self.pkt_infos
429 for _ in range(0, min(2, len(frags)))
432 self.pg_enable_capture()
433 self.src_if.add_stream(fragments)
436 packets = self.dst_if.get_capture(len(self.pkt_infos))
437 self.verify_capture(packets)
438 self.src_if.assert_nothing_captured()
440 def test_overlap1(self):
441 """overlapping fragments case #1"""
444 for _, _, frags_300, frags_200 in self.pkt_infos:
445 if len(frags_300) == 1:
446 fragments.extend(frags_300)
448 for i, j in zip(frags_200, frags_300):
452 self.pg_enable_capture()
453 self.src_if.add_stream(fragments)
456 packets = self.dst_if.get_capture(len(self.pkt_infos))
457 self.verify_capture(packets)
458 self.src_if.assert_nothing_captured()
460 # run it all to verify correctness
461 self.pg_enable_capture()
462 self.src_if.add_stream(fragments)
465 packets = self.dst_if.get_capture(len(self.pkt_infos))
466 self.verify_capture(packets)
467 self.src_if.assert_nothing_captured()
469 def test_overlap2(self):
470 """overlapping fragments case #2"""
473 for _, _, frags_300, frags_200 in self.pkt_infos:
474 if len(frags_300) == 1:
475 fragments.extend(frags_300)
477 # care must be taken here so that there are no fragments
478 # received by vpp after reassembly is finished, otherwise
479 # new reassemblies will be started and packet generator will
480 # freak out when it detects unfreed buffers
481 zipped = zip(frags_300, frags_200)
487 self.pg_enable_capture()
488 self.src_if.add_stream(fragments)
491 packets = self.dst_if.get_capture(len(self.pkt_infos))
492 self.verify_capture(packets)
493 self.src_if.assert_nothing_captured()
495 # run it all to verify correctness
496 self.pg_enable_capture()
497 self.src_if.add_stream(fragments)
500 packets = self.dst_if.get_capture(len(self.pkt_infos))
501 self.verify_capture(packets)
502 self.src_if.assert_nothing_captured()
504 def test_timeout_inline(self):
505 """timeout (inline)"""
507 dropped_packet_indexes = set(
508 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
511 self.vapi.ip_reassembly_set(
513 max_reassemblies=1000,
514 max_reassembly_length=3,
515 expire_walk_interval_ms=10000,
518 self.pg_enable_capture()
519 self.src_if.add_stream(self.fragments_400)
522 packets = self.dst_if.get_capture(
523 len(self.pkt_infos) - len(dropped_packet_indexes)
525 self.verify_capture(packets, dropped_packet_indexes)
526 self.src_if.assert_nothing_captured()
528 def test_timeout_cleanup(self):
529 """timeout (cleanup)"""
531 # whole packets + fragmented packets sans last fragment
534 for (_, frags_400, _, _) in self.pkt_infos
535 for x in frags_400[: -1 if len(frags_400) > 1 else None]
538 # last fragments for fragmented packets
541 for (_, frags_400, _, _) in self.pkt_infos
542 if len(frags_400) > 1
545 dropped_packet_indexes = set(
546 index for (index, frags_400, _, _) in self.pkt_infos if len(frags_400) > 1
549 self.vapi.ip_reassembly_set(
551 max_reassemblies=1000,
552 max_reassembly_length=1000,
553 expire_walk_interval_ms=50,
556 self.pg_enable_capture()
557 self.src_if.add_stream(fragments)
560 self.virtual_sleep(0.25, "wait before sending rest of fragments")
562 self.src_if.add_stream(fragments2)
565 packets = self.dst_if.get_capture(
566 len(self.pkt_infos) - len(dropped_packet_indexes)
568 self.verify_capture(packets, dropped_packet_indexes)
569 self.src_if.assert_nothing_captured()
571 def test_disabled(self):
572 """reassembly disabled"""
574 dropped_packet_indexes = set(
575 index for (index, frags_400, _, _) in self.pkt_infos if len(frags_400) > 1
578 self.vapi.ip_reassembly_set(
581 max_reassembly_length=3,
582 expire_walk_interval_ms=10000,
585 self.pg_enable_capture()
586 self.src_if.add_stream(self.fragments_400)
589 packets = self.dst_if.get_capture(
590 len(self.pkt_infos) - len(dropped_packet_indexes)
592 self.verify_capture(packets, dropped_packet_indexes)
593 self.src_if.assert_nothing_captured()
595 def test_local_enable_disable(self):
596 """local reassembly enabled/disable"""
597 self.vapi.ip_reassembly_enable_disable(
598 sw_if_index=self.src_if.sw_if_index, enable_ip4=False
600 self.vapi.ip_local_reass_enable_disable(enable_ip4=True)
602 Ether(src=self.src_if.remote_mac, dst=self.src_if.local_mac)
603 / IP(src=self.src_if.remote_ip4, dst=self.src_if.local_ip4)
604 / ICMP(id=1234, type="echo-request")
607 frags = fragment_rfc791(p, 400)
608 r = self.send_and_expect(self.src_if, frags, self.src_if, n_rx=1)[0]
609 self.assertEqual(1234, r[ICMP].id)
610 self.assertEqual(icmptypes[r[ICMP].type], "echo-reply")
611 self.vapi.ip_local_reass_enable_disable()
613 self.send_and_assert_no_replies(self.src_if, frags)
614 self.vapi.ip_local_reass_enable_disable(enable_ip4=True)
617 class TestIPv4SVReassembly(VppTestCase):
618 """IPv4 Shallow Virtual Reassembly"""
624 cls.create_pg_interfaces([0, 1])
628 # setup all interfaces
629 for i in cls.pg_interfaces:
635 """Test setup - force timeout on existing reassemblies"""
637 self.vapi.ip_reassembly_enable_disable(
638 sw_if_index=self.src_if.sw_if_index,
640 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
642 self.vapi.ip_reassembly_set(
644 max_reassemblies=1000,
645 max_reassembly_length=1000,
646 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
647 expire_walk_interval_ms=10,
649 self.virtual_sleep(0.25)
650 self.vapi.ip_reassembly_set(
652 max_reassemblies=1000,
653 max_reassembly_length=1000,
654 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
655 expire_walk_interval_ms=10000,
660 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
661 self.logger.debug(self.vapi.ppcli("show buffers"))
663 def test_basic(self):
664 """basic reassembly"""
668 while len(payload) < payload_len:
669 payload += "%u " % counter
673 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
674 / IP(id=1, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
675 / UDP(sport=1234, dport=5678)
678 fragments = fragment_rfc791(p, payload_len / 4)
680 # send fragment #2 - should be cached inside reassembly
681 self.pg_enable_capture()
682 self.src_if.add_stream(fragments[1])
684 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
685 self.logger.debug(self.vapi.ppcli("show buffers"))
686 self.logger.debug(self.vapi.ppcli("show trace"))
687 self.dst_if.assert_nothing_captured()
689 # send fragment #1 - reassembly is finished now and both fragments
691 self.pg_enable_capture()
692 self.src_if.add_stream(fragments[0])
694 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
695 self.logger.debug(self.vapi.ppcli("show buffers"))
696 self.logger.debug(self.vapi.ppcli("show trace"))
697 c = self.dst_if.get_capture(2)
698 for sent, recvd in zip([fragments[1], fragments[0]], c):
699 self.assertEqual(sent[IP].src, recvd[IP].src)
700 self.assertEqual(sent[IP].dst, recvd[IP].dst)
701 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
703 # send rest of fragments - should be immediately forwarded
704 self.pg_enable_capture()
705 self.src_if.add_stream(fragments[2:])
707 c = self.dst_if.get_capture(len(fragments[2:]))
708 for sent, recvd in zip(fragments[2:], c):
709 self.assertEqual(sent[IP].src, recvd[IP].src)
710 self.assertEqual(sent[IP].dst, recvd[IP].dst)
711 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
713 def test_verify_clear_trace_mid_reassembly(self):
714 """verify clear trace works mid-reassembly"""
718 while len(payload) < payload_len:
719 payload += "%u " % counter
723 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
724 / IP(id=1, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
725 / UDP(sport=1234, dport=5678)
728 fragments = fragment_rfc791(p, payload_len / 4)
730 self.pg_enable_capture()
731 self.src_if.add_stream(fragments[1])
734 self.logger.debug(self.vapi.cli("show trace"))
735 self.vapi.cli("clear trace")
737 self.pg_enable_capture()
738 self.src_if.add_stream(fragments[0])
740 self.dst_if.get_capture(2)
742 self.logger.debug(self.vapi.cli("show trace"))
743 self.vapi.cli("clear trace")
745 self.pg_enable_capture()
746 self.src_if.add_stream(fragments[2:])
748 self.dst_if.get_capture(len(fragments[2:]))
750 def test_timeout(self):
751 """reassembly timeout"""
755 while len(payload) < payload_len:
756 payload += "%u " % counter
760 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
761 / IP(id=1, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
762 / UDP(sport=1234, dport=5678)
765 fragments = fragment_rfc791(p, payload_len / 4)
767 self.vapi.ip_reassembly_set(
769 max_reassemblies=1000,
770 max_reassembly_length=1000,
771 expire_walk_interval_ms=50,
772 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
775 # send fragments #2 and #1 - should be forwarded
776 self.pg_enable_capture()
777 self.src_if.add_stream(fragments[0:2])
779 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
780 self.logger.debug(self.vapi.ppcli("show buffers"))
781 self.logger.debug(self.vapi.ppcli("show trace"))
782 c = self.dst_if.get_capture(2)
783 for sent, recvd in zip([fragments[1], fragments[0]], c):
784 self.assertEqual(sent[IP].src, recvd[IP].src)
785 self.assertEqual(sent[IP].dst, recvd[IP].dst)
786 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
789 self.virtual_sleep(0.25, "wait before sending rest of fragments")
791 # send rest of fragments - shouldn't be forwarded
792 self.pg_enable_capture()
793 self.src_if.add_stream(fragments[2:])
795 self.dst_if.assert_nothing_captured()
798 """reassembly reuses LRU element"""
800 self.vapi.ip_reassembly_set(
803 max_reassembly_length=1000,
804 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
805 expire_walk_interval_ms=10000,
811 while len(payload) < payload_len:
812 payload += "%u " % counter
819 for i in range(packet_count)
821 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
822 / IP(id=i, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
823 / UDP(sport=1234, dport=5678)
826 for f in fragment_rfc791(p, payload_len / 4)
829 self.pg_enable_capture()
830 self.src_if.add_stream(fragments)
832 c = self.dst_if.get_capture(len(fragments))
833 for sent, recvd in zip(fragments, c):
834 self.assertEqual(sent[IP].src, recvd[IP].src)
835 self.assertEqual(sent[IP].dst, recvd[IP].dst)
836 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
838 def send_mixed_and_verify_capture(self, traffic):
841 for c in range(t["count"]):
844 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
848 src=self.src_if.remote_ip4,
849 dst=self.dst_if.remote_ip4,
851 / UDP(sport=1234, dport=5678)
855 self.counter = self.counter + 1
857 self.pg_enable_capture()
858 self.src_if.add_stream(stream)
860 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
861 self.logger.debug(self.vapi.ppcli("show buffers"))
862 self.logger.debug(self.vapi.ppcli("show trace"))
863 self.dst_if.get_capture(len(stream))
865 def test_mixed(self):
866 """mixed traffic correctly passes through SVR"""
869 self.send_mixed_and_verify_capture([{"count": 1, "flags": ""}])
870 self.send_mixed_and_verify_capture([{"count": 2, "flags": ""}])
871 self.send_mixed_and_verify_capture([{"count": 3, "flags": ""}])
872 self.send_mixed_and_verify_capture([{"count": 8, "flags": ""}])
873 self.send_mixed_and_verify_capture([{"count": 257, "flags": ""}])
875 self.send_mixed_and_verify_capture([{"count": 1, "flags": "MF"}])
876 self.send_mixed_and_verify_capture([{"count": 2, "flags": "MF"}])
877 self.send_mixed_and_verify_capture([{"count": 3, "flags": "MF"}])
878 self.send_mixed_and_verify_capture([{"count": 8, "flags": "MF"}])
879 self.send_mixed_and_verify_capture([{"count": 257, "flags": "MF"}])
881 self.send_mixed_and_verify_capture(
882 [{"count": 1, "flags": ""}, {"count": 1, "flags": "MF"}]
884 self.send_mixed_and_verify_capture(
885 [{"count": 2, "flags": ""}, {"count": 2, "flags": "MF"}]
887 self.send_mixed_and_verify_capture(
888 [{"count": 3, "flags": ""}, {"count": 3, "flags": "MF"}]
890 self.send_mixed_and_verify_capture(
891 [{"count": 8, "flags": ""}, {"count": 8, "flags": "MF"}]
893 self.send_mixed_and_verify_capture(
894 [{"count": 129, "flags": ""}, {"count": 129, "flags": "MF"}]
897 self.send_mixed_and_verify_capture(
899 {"count": 1, "flags": ""},
900 {"count": 1, "flags": "MF"},
901 {"count": 1, "flags": ""},
902 {"count": 1, "flags": "MF"},
905 self.send_mixed_and_verify_capture(
907 {"count": 2, "flags": ""},
908 {"count": 2, "flags": "MF"},
909 {"count": 2, "flags": ""},
910 {"count": 2, "flags": "MF"},
913 self.send_mixed_and_verify_capture(
915 {"count": 3, "flags": ""},
916 {"count": 3, "flags": "MF"},
917 {"count": 3, "flags": ""},
918 {"count": 3, "flags": "MF"},
921 self.send_mixed_and_verify_capture(
923 {"count": 8, "flags": ""},
924 {"count": 8, "flags": "MF"},
925 {"count": 8, "flags": ""},
926 {"count": 8, "flags": "MF"},
929 self.send_mixed_and_verify_capture(
931 {"count": 65, "flags": ""},
932 {"count": 65, "flags": "MF"},
933 {"count": 65, "flags": ""},
934 {"count": 65, "flags": "MF"},
939 class TestIPv4MWReassembly(VppTestCase):
940 """IPv4 Reassembly (multiple workers)"""
948 cls.create_pg_interfaces(range(cls.vpp_worker_count + 1))
950 cls.send_ifs = cls.pg_interfaces[:-1]
951 cls.dst_if = cls.pg_interfaces[-1]
953 # setup all interfaces
954 for i in cls.pg_interfaces:
959 # packets sizes reduced here because we are generating packets without
960 # Ethernet headers, which are added later (diff fragments go via
961 # different interfaces)
968 cls.padding = " abcdefghijklmn"
969 cls.create_stream(cls.packet_sizes)
970 cls.create_fragments()
973 def tearDownClass(cls):
974 super().tearDownClass()
977 """Test setup - force timeout on existing reassemblies"""
979 for intf in self.send_ifs:
980 self.vapi.ip_reassembly_enable_disable(
981 sw_if_index=intf.sw_if_index, enable_ip4=True
983 self.vapi.ip_reassembly_set(
985 max_reassemblies=1000,
986 max_reassembly_length=1000,
987 expire_walk_interval_ms=10,
989 self.virtual_sleep(0.25)
990 self.vapi.ip_reassembly_set(
992 max_reassemblies=1000,
993 max_reassembly_length=1000,
994 expire_walk_interval_ms=10000,
998 for intf in self.send_ifs:
999 self.vapi.ip_reassembly_enable_disable(
1000 sw_if_index=intf.sw_if_index, enable_ip4=False
1004 def show_commands_at_teardown(self):
1005 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1006 self.logger.debug(self.vapi.ppcli("show buffers"))
1009 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1010 """Create input packet stream
1012 :param list packet_sizes: Required packet sizes.
1014 for i in range(0, packet_count):
1015 info = cls.create_packet_info(cls.src_if, cls.src_if)
1016 payload = cls.info_to_payload(info)
1018 IP(id=info.index, src=cls.src_if.remote_ip4, dst=cls.dst_if.remote_ip4)
1019 / UDP(sport=1234, dport=5678)
1022 size = packet_sizes[(i // 2) % len(packet_sizes)]
1023 cls.extend_packet(p, size, cls.padding)
1027 def create_fragments(cls):
1028 infos = cls._packet_infos
1030 for index, info in infos.items():
1032 # cls.logger.debug(ppp("Packet:",
1033 # p.__class__(scapy.compat.raw(p))))
1034 fragments_400 = fragment_rfc791(p, 400)
1035 cls.pkt_infos.append((index, fragments_400))
1036 cls.fragments_400 = [x for (_, frags) in cls.pkt_infos for x in frags]
1038 "Fragmented %s packets into %s 400-byte fragments, "
1039 % (len(infos), len(cls.fragments_400))
1042 def verify_capture(self, capture, dropped_packet_indexes=[]):
1043 """Verify captured packet stream.
1045 :param list capture: Captured packet stream.
1049 for packet in capture:
1051 self.logger.debug(ppp("Got packet:", packet))
1054 payload_info = self.payload_to_info(packet[Raw])
1055 packet_index = payload_info.index
1057 packet_index not in dropped_packet_indexes,
1058 ppp("Packet received, but should be dropped:", packet),
1060 if packet_index in seen:
1061 raise Exception(ppp("Duplicate packet received", packet))
1062 seen.add(packet_index)
1063 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1064 info = self._packet_infos[packet_index]
1065 self.assertTrue(info is not None)
1066 self.assertEqual(packet_index, info.index)
1067 saved_packet = info.data
1068 self.assertEqual(ip.src, saved_packet[IP].src)
1069 self.assertEqual(ip.dst, saved_packet[IP].dst)
1070 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1072 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1074 for index in self._packet_infos:
1076 index in seen or index in dropped_packet_indexes,
1077 "Packet with packet_index %d not received" % index,
1080 def send_packets(self, packets):
1081 for counter in range(self.vpp_worker_count):
1082 if 0 == len(packets[counter]):
1084 send_if = self.send_ifs[counter]
1087 Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1088 for x in packets[counter]
1094 def test_worker_conflict(self):
1095 """1st and FO=0 fragments on different workers"""
1097 # in first wave we send fragments which don't start at offset 0
1098 # then we send fragments with offset 0 on a different thread
1099 # then the rest of packets on a random thread
1100 first_packets = [[] for n in range(self.vpp_worker_count)]
1101 second_packets = [[] for n in range(self.vpp_worker_count)]
1102 rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1103 for (_, p) in self.pkt_infos:
1104 wi = randrange(self.vpp_worker_count)
1105 second_packets[wi].append(p[0])
1110 wi2 = randrange(self.vpp_worker_count)
1111 first_packets[wi2].append(p[1])
1112 wi3 = randrange(self.vpp_worker_count)
1113 rest_of_packets[wi3].extend(p[2:])
1115 self.pg_enable_capture()
1116 self.send_packets(first_packets)
1117 self.send_packets(second_packets)
1118 self.send_packets(rest_of_packets)
1120 packets = self.dst_if.get_capture(len(self.pkt_infos))
1121 self.verify_capture(packets)
1122 for send_if in self.send_ifs:
1123 send_if.assert_nothing_captured()
1125 self.logger.debug(self.vapi.ppcli("show trace"))
1126 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1127 self.logger.debug(self.vapi.ppcli("show buffers"))
1128 self.vapi.cli("clear trace")
1130 self.pg_enable_capture()
1131 self.send_packets(first_packets)
1132 self.send_packets(second_packets)
1133 self.send_packets(rest_of_packets)
1135 packets = self.dst_if.get_capture(len(self.pkt_infos))
1136 self.verify_capture(packets)
1137 for send_if in self.send_ifs:
1138 send_if.assert_nothing_captured()
1141 class TestIPv6Reassembly(VppTestCase):
1142 """IPv6 Reassembly"""
1145 def setUpClass(cls):
1146 super().setUpClass()
1148 cls.create_pg_interfaces([0, 1])
1149 cls.src_if = cls.pg0
1150 cls.dst_if = cls.pg1
1152 # setup all interfaces
1153 for i in cls.pg_interfaces:
1159 cls.packet_sizes = [64, 512, 1518, 9018]
1160 cls.padding = " abcdefghijklmn"
1161 cls.create_stream(cls.packet_sizes)
1162 cls.create_fragments()
1165 def tearDownClass(cls):
1166 super().tearDownClass()
1169 """Test setup - force timeout on existing reassemblies"""
1171 self.vapi.ip_reassembly_enable_disable(
1172 sw_if_index=self.src_if.sw_if_index, enable_ip6=True
1174 self.vapi.ip_reassembly_set(
1176 max_reassemblies=1000,
1177 max_reassembly_length=1000,
1178 expire_walk_interval_ms=10,
1181 self.virtual_sleep(0.25)
1182 self.vapi.ip_reassembly_set(
1184 max_reassemblies=1000,
1185 max_reassembly_length=1000,
1186 expire_walk_interval_ms=10000,
1189 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1190 self.logger.debug(self.vapi.ppcli("show buffers"))
1193 self.vapi.ip_reassembly_enable_disable(
1194 sw_if_index=self.src_if.sw_if_index, enable_ip6=False
1198 def show_commands_at_teardown(self):
1199 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1200 self.logger.debug(self.vapi.ppcli("show buffers"))
1203 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1204 """Create input packet stream for defined interface.
1206 :param list packet_sizes: Required packet sizes.
1208 for i in range(0, packet_count):
1209 info = cls.create_packet_info(cls.src_if, cls.src_if)
1210 payload = cls.info_to_payload(info)
1212 Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac)
1213 / IPv6(src=cls.src_if.remote_ip6, dst=cls.dst_if.remote_ip6)
1214 / UDP(sport=1234, dport=5678)
1217 size = packet_sizes[(i // 2) % len(packet_sizes)]
1218 cls.extend_packet(p, size, cls.padding)
1222 def create_fragments(cls):
1223 infos = cls._packet_infos
1225 for index, info in infos.items():
1227 # cls.logger.debug(ppp("Packet:",
1228 # p.__class__(scapy.compat.raw(p))))
1229 fragments_400 = fragment_rfc8200(p, info.index, 400)
1230 fragments_300 = fragment_rfc8200(p, info.index, 300)
1231 cls.pkt_infos.append((index, fragments_400, fragments_300))
1232 cls.fragments_400 = [x for _, frags, _ in cls.pkt_infos for x in frags]
1233 cls.fragments_300 = [x for _, _, frags in cls.pkt_infos for x in frags]
1235 "Fragmented %s packets into %s 400-byte fragments, "
1236 "and %s 300-byte fragments"
1237 % (len(infos), len(cls.fragments_400), len(cls.fragments_300))
1240 def verify_capture(self, capture, dropped_packet_indexes=[]):
1241 """Verify captured packet strea .
1243 :param list capture: Captured packet stream.
1247 for packet in capture:
1249 self.logger.debug(ppp("Got packet:", packet))
1252 payload_info = self.payload_to_info(packet[Raw])
1253 packet_index = payload_info.index
1255 packet_index not in dropped_packet_indexes,
1256 ppp("Packet received, but should be dropped:", packet),
1258 if packet_index in seen:
1259 raise Exception(ppp("Duplicate packet received", packet))
1260 seen.add(packet_index)
1261 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1262 info = self._packet_infos[packet_index]
1263 self.assertTrue(info is not None)
1264 self.assertEqual(packet_index, info.index)
1265 saved_packet = info.data
1266 self.assertEqual(ip.src, saved_packet[IPv6].src)
1267 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1268 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1270 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1272 for index in self._packet_infos:
1274 index in seen or index in dropped_packet_indexes,
1275 "Packet with packet_index %d not received" % index,
1278 def test_reassembly(self):
1279 """basic reassembly"""
1281 self.pg_enable_capture()
1282 self.src_if.add_stream(self.fragments_400)
1285 packets = self.dst_if.get_capture(len(self.pkt_infos))
1286 self.verify_capture(packets)
1287 self.src_if.assert_nothing_captured()
1289 # run it all again to verify correctness
1290 self.pg_enable_capture()
1291 self.src_if.add_stream(self.fragments_400)
1294 packets = self.dst_if.get_capture(len(self.pkt_infos))
1295 self.verify_capture(packets)
1296 self.src_if.assert_nothing_captured()
1298 def test_buffer_boundary(self):
1299 """fragment header crossing buffer boundary"""
1302 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1303 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1304 / IPv6ExtHdrHopByHop(options=[HBHOptUnknown(otype=0xFF, optlen=0)] * 1000)
1305 / IPv6ExtHdrFragment(m=1)
1306 / UDP(sport=1234, dport=5678)
1309 self.pg_enable_capture()
1310 self.src_if.add_stream([p])
1312 self.src_if.assert_nothing_captured()
1313 self.dst_if.assert_nothing_captured()
1315 def test_verify_clear_trace_mid_reassembly(self):
1316 """verify clear trace works mid-reassembly"""
1318 self.pg_enable_capture()
1319 self.src_if.add_stream(self.fragments_400[0:-1])
1322 self.logger.debug(self.vapi.cli("show trace"))
1323 self.vapi.cli("clear trace")
1325 self.src_if.add_stream(self.fragments_400[-1])
1327 packets = self.dst_if.get_capture(len(self.pkt_infos))
1328 self.verify_capture(packets)
1330 def test_reversed(self):
1331 """reverse order reassembly"""
1333 fragments = list(self.fragments_400)
1336 self.pg_enable_capture()
1337 self.src_if.add_stream(fragments)
1340 packets = self.dst_if.get_capture(len(self.pkt_infos))
1341 self.verify_capture(packets)
1342 self.src_if.assert_nothing_captured()
1344 # run it all again to verify correctness
1345 self.pg_enable_capture()
1346 self.src_if.add_stream(fragments)
1349 packets = self.dst_if.get_capture(len(self.pkt_infos))
1350 self.verify_capture(packets)
1351 self.src_if.assert_nothing_captured()
1353 def test_random(self):
1354 """random order reassembly"""
1356 fragments = list(self.fragments_400)
1359 self.pg_enable_capture()
1360 self.src_if.add_stream(fragments)
1363 packets = self.dst_if.get_capture(len(self.pkt_infos))
1364 self.verify_capture(packets)
1365 self.src_if.assert_nothing_captured()
1367 # run it all again to verify correctness
1368 self.pg_enable_capture()
1369 self.src_if.add_stream(fragments)
1372 packets = self.dst_if.get_capture(len(self.pkt_infos))
1373 self.verify_capture(packets)
1374 self.src_if.assert_nothing_captured()
1376 def test_duplicates(self):
1377 """duplicate fragments"""
1381 for (_, frags, _) in self.pkt_infos
1383 for _ in range(0, min(2, len(frags)))
1386 self.pg_enable_capture()
1387 self.src_if.add_stream(fragments)
1390 packets = self.dst_if.get_capture(len(self.pkt_infos))
1391 self.verify_capture(packets)
1392 self.src_if.assert_nothing_captured()
1394 def test_long_fragment_chain(self):
1395 """long fragment chain"""
1398 "/err/ip6-full-reassembly-feature/fragment chain too long (drop)"
1401 error_cnt = self.statistics.get_err_counter(error_cnt_str)
1403 self.vapi.ip_reassembly_set(
1405 max_reassemblies=1000,
1406 max_reassembly_length=3,
1407 expire_walk_interval_ms=50,
1412 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1413 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
1414 / UDP(sport=1234, dport=5678)
1417 frags = fragment_rfc8200(p, 1, 300) + fragment_rfc8200(p, 2, 500)
1419 self.pg_enable_capture()
1420 self.src_if.add_stream(frags)
1423 self.dst_if.get_capture(1)
1424 self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
1426 def test_overlap1(self):
1427 """overlapping fragments case #1"""
1430 for _, frags_400, frags_300 in self.pkt_infos:
1431 if len(frags_300) == 1:
1432 fragments.extend(frags_400)
1434 for i, j in zip(frags_300, frags_400):
1438 dropped_packet_indexes = set(
1439 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1442 self.pg_enable_capture()
1443 self.src_if.add_stream(fragments)
1446 packets = self.dst_if.get_capture(
1447 len(self.pkt_infos) - len(dropped_packet_indexes)
1449 self.verify_capture(packets, dropped_packet_indexes)
1450 self.src_if.assert_nothing_captured()
1452 def test_overlap2(self):
1453 """overlapping fragments case #2"""
1456 for _, frags_400, frags_300 in self.pkt_infos:
1457 if len(frags_400) == 1:
1458 fragments.extend(frags_400)
1460 # care must be taken here so that there are no fragments
1461 # received by vpp after reassembly is finished, otherwise
1462 # new reassemblies will be started and packet generator will
1463 # freak out when it detects unfreed buffers
1464 zipped = zip(frags_400, frags_300)
1470 dropped_packet_indexes = set(
1471 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1474 self.pg_enable_capture()
1475 self.src_if.add_stream(fragments)
1478 packets = self.dst_if.get_capture(
1479 len(self.pkt_infos) - len(dropped_packet_indexes)
1481 self.verify_capture(packets, dropped_packet_indexes)
1482 self.src_if.assert_nothing_captured()
1484 def test_timeout_inline(self):
1485 """timeout (inline)"""
1487 dropped_packet_indexes = set(
1488 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
1491 self.vapi.ip_reassembly_set(
1493 max_reassemblies=1000,
1494 max_reassembly_length=3,
1495 expire_walk_interval_ms=10000,
1499 self.pg_enable_capture()
1500 self.src_if.add_stream(self.fragments_400)
1503 packets = self.dst_if.get_capture(
1504 len(self.pkt_infos) - len(dropped_packet_indexes)
1506 self.verify_capture(packets, dropped_packet_indexes)
1507 pkts = self.src_if._get_capture(1)
1509 self.assertIn(ICMPv6TimeExceeded, icmp)
1510 self.assertIn(IPv6ExtHdrFragment, icmp)
1511 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1512 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1514 def test_timeout_cleanup(self):
1515 """timeout (cleanup)"""
1517 # whole packets + fragmented packets sans last fragment
1520 for (_, frags_400, _) in self.pkt_infos
1521 for x in frags_400[: -1 if len(frags_400) > 1 else None]
1524 # last fragments for fragmented packets
1526 frags_400[-1] for (_, frags_400, _) in self.pkt_infos if len(frags_400) > 1
1529 dropped_packet_indexes = set(
1530 index for (index, frags_400, _) in self.pkt_infos if len(frags_400) > 1
1533 self.vapi.ip_reassembly_set(
1535 max_reassemblies=1000,
1536 max_reassembly_length=1000,
1537 expire_walk_interval_ms=50,
1540 self.vapi.ip_reassembly_set(
1542 max_reassemblies=1000,
1543 max_reassembly_length=1000,
1544 expire_walk_interval_ms=50,
1548 self.pg_enable_capture()
1549 self.src_if.add_stream(fragments)
1552 self.virtual_sleep(0.25, "wait before sending rest of fragments")
1554 self.src_if.add_stream(fragments2)
1557 packets = self.dst_if.get_capture(
1558 len(self.pkt_infos) - len(dropped_packet_indexes)
1560 self.verify_capture(packets, dropped_packet_indexes)
1561 pkts = self.src_if._get_capture(1)
1563 self.assertIn(ICMPv6TimeExceeded, icmp)
1564 self.assertIn(IPv6ExtHdrFragment, icmp)
1565 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1566 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1568 def test_disabled(self):
1569 """reassembly disabled"""
1571 dropped_packet_indexes = set(
1572 index for (index, frags_400, _) in self.pkt_infos if len(frags_400) > 1
1575 self.vapi.ip_reassembly_set(
1578 max_reassembly_length=3,
1579 expire_walk_interval_ms=10000,
1583 self.pg_enable_capture()
1584 self.src_if.add_stream(self.fragments_400)
1587 packets = self.dst_if.get_capture(
1588 len(self.pkt_infos) - len(dropped_packet_indexes)
1590 self.verify_capture(packets, dropped_packet_indexes)
1591 self.src_if.assert_nothing_captured()
1593 def test_missing_upper(self):
1594 """missing upper layer"""
1595 optdata = "\x00" * 100
1597 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1598 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1599 / IPv6ExtHdrFragment(m=1)
1600 / IPv6ExtHdrDestOpt(
1601 nh=17, options=PadN(optdata="\101" * 255) / PadN(optdata="\102" * 255)
1605 self.pg_enable_capture()
1606 self.src_if.add_stream([p])
1608 pkts = self.src_if.get_capture(expected_count=1)
1610 self.assertIn(ICMPv6ParamProblem, icmp)
1611 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
1613 def test_truncated_fragment(self):
1614 """truncated fragment"""
1616 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1617 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
1618 / IPv6ExtHdrFragment(nh=6)
1621 self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
1623 def test_invalid_frag_size(self):
1624 """fragment size not a multiple of 8"""
1626 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1627 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1628 / UDP(sport=1234, dport=5678)
1631 self.extend_packet(p, 1000, self.padding)
1632 fragments = fragment_rfc8200(p, 1, 500)
1633 bad_fragment = fragments[0]
1634 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
1635 self.pg_enable_capture()
1636 self.src_if.add_stream([bad_fragment])
1638 pkts = self.src_if.get_capture(expected_count=1)
1640 self.assertIn(ICMPv6ParamProblem, icmp)
1641 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1643 def test_invalid_packet_size(self):
1644 """total packet size > 65535"""
1646 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1647 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1648 / UDP(sport=1234, dport=5678)
1651 self.extend_packet(p, 1000, self.padding)
1652 fragments = fragment_rfc8200(p, 1, 500)
1653 bad_fragment = fragments[1]
1654 bad_fragment[IPv6ExtHdrFragment].offset = 65500
1655 self.pg_enable_capture()
1656 self.src_if.add_stream([bad_fragment])
1658 pkts = self.src_if.get_capture(expected_count=1)
1660 self.assertIn(ICMPv6ParamProblem, icmp)
1661 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1663 def test_atomic_fragment(self):
1664 """IPv6 atomic fragment"""
1666 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1667 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=65535)
1668 / IPv6ExtHdrFragment(
1669 offset=8191, m=1, res1=0xFF, res2=0xFF, nh=255, id=0xFFFF
1674 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1675 self.assertIn(ICMPv6ParamProblem, rx[0])
1677 def test_truncated_fragment(self):
1678 """IPv6 truncated fragment header"""
1680 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1681 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
1682 / IPv6ExtHdrFragment(nh=6)
1685 self.send_and_assert_no_replies(self.pg0, [pkt])
1688 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1689 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.remote_ip6)
1690 / ICMPv6EchoRequest()
1692 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1694 def test_one_fragment(self):
1695 """whole packet in one fragment processed independently"""
1697 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1698 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1699 / ICMPv6EchoRequest()
1702 frags = fragment_rfc8200(pkt, 1, 400)
1704 # send a fragment with known id
1705 self.send_and_assert_no_replies(self.pg0, [frags[0]])
1707 # send an atomic fragment with same id - should be reassembled
1709 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1710 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1711 / IPv6ExtHdrFragment(id=1)
1712 / ICMPv6EchoRequest()
1714 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1715 self.assertNotIn(IPv6ExtHdrFragment, rx)
1717 # now finish the original reassembly, this should still be possible
1718 rx = self.send_and_expect(self.pg0, frags[1:], self.pg0, n_rx=1)
1719 self.assertNotIn(IPv6ExtHdrFragment, rx)
1721 def test_bunch_of_fragments(self):
1722 """valid fragments followed by rogue fragments and atomic fragment"""
1724 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1725 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1726 / ICMPv6EchoRequest()
1729 frags = fragment_rfc8200(pkt, 1, 400)
1730 self.send_and_expect(self.pg0, frags, self.pg0, n_rx=1)
1733 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1734 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1735 / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
1739 self.send_and_assert_no_replies(self.pg0, inc_frag * 604)
1742 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1743 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1744 / IPv6ExtHdrFragment(id=1)
1745 / ICMPv6EchoRequest()
1747 rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1748 self.assertNotIn(IPv6ExtHdrFragment, rx)
1750 def test_local_enable_disable(self):
1751 """local reassembly enabled/disable"""
1752 self.vapi.ip_reassembly_enable_disable(
1753 sw_if_index=self.src_if.sw_if_index, enable_ip6=False
1755 self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1757 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
1758 / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1759 / ICMPv6EchoRequest(id=1234)
1762 frags = fragment_rfc8200(pkt, 1, 400)
1763 r = self.send_and_expect(self.src_if, frags, self.src_if, n_rx=1)[0]
1764 self.assertEqual(1234, r[ICMPv6EchoReply].id)
1765 self.vapi.ip_local_reass_enable_disable()
1767 self.send_and_assert_no_replies(self.src_if, frags)
1768 self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1771 class TestIPv6MWReassembly(VppTestCase):
1772 """IPv6 Reassembly (multiple workers)"""
1774 vpp_worker_count = 3
1777 def setUpClass(cls):
1778 super().setUpClass()
1780 cls.create_pg_interfaces(range(cls.vpp_worker_count + 1))
1781 cls.src_if = cls.pg0
1782 cls.send_ifs = cls.pg_interfaces[:-1]
1783 cls.dst_if = cls.pg_interfaces[-1]
1785 # setup all interfaces
1786 for i in cls.pg_interfaces:
1791 # packets sizes reduced here because we are generating packets without
1792 # Ethernet headers, which are added later (diff fragments go via
1793 # different interfaces)
1794 cls.packet_sizes = [
1797 1518 - len(Ether()),
1798 9018 - len(Ether()),
1800 cls.padding = " abcdefghijklmn"
1801 cls.create_stream(cls.packet_sizes)
1802 cls.create_fragments()
1805 def tearDownClass(cls):
1806 super().tearDownClass()
1809 """Test setup - force timeout on existing reassemblies"""
1811 for intf in self.send_ifs:
1812 self.vapi.ip_reassembly_enable_disable(
1813 sw_if_index=intf.sw_if_index, enable_ip6=True
1815 self.vapi.ip_reassembly_set(
1817 max_reassemblies=1000,
1818 max_reassembly_length=1000,
1819 expire_walk_interval_ms=10,
1822 self.virtual_sleep(0.25)
1823 self.vapi.ip_reassembly_set(
1825 max_reassemblies=1000,
1826 max_reassembly_length=1000,
1827 expire_walk_interval_ms=1000,
1832 for intf in self.send_ifs:
1833 self.vapi.ip_reassembly_enable_disable(
1834 sw_if_index=intf.sw_if_index, enable_ip6=False
1838 def show_commands_at_teardown(self):
1839 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1840 self.logger.debug(self.vapi.ppcli("show buffers"))
1843 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1844 """Create input packet stream
1846 :param list packet_sizes: Required packet sizes.
1848 for i in range(0, packet_count):
1849 info = cls.create_packet_info(cls.src_if, cls.src_if)
1850 payload = cls.info_to_payload(info)
1852 IPv6(src=cls.src_if.remote_ip6, dst=cls.dst_if.remote_ip6)
1853 / UDP(sport=1234, dport=5678)
1856 size = packet_sizes[(i // 2) % len(packet_sizes)]
1857 cls.extend_packet(p, size, cls.padding)
1861 def create_fragments(cls):
1862 infos = cls._packet_infos
1864 for index, info in infos.items():
1866 # cls.logger.debug(ppp("Packet:",
1867 # p.__class__(scapy.compat.raw(p))))
1868 fragments_400 = fragment_rfc8200(p, index, 400)
1869 cls.pkt_infos.append((index, fragments_400))
1870 cls.fragments_400 = [x for (_, frags) in cls.pkt_infos for x in frags]
1872 "Fragmented %s packets into %s 400-byte fragments, "
1873 % (len(infos), len(cls.fragments_400))
1876 def verify_capture(self, capture, dropped_packet_indexes=[]):
1877 """Verify captured packet strea .
1879 :param list capture: Captured packet stream.
1883 for packet in capture:
1885 self.logger.debug(ppp("Got packet:", packet))
1888 payload_info = self.payload_to_info(packet[Raw])
1889 packet_index = payload_info.index
1891 packet_index not in dropped_packet_indexes,
1892 ppp("Packet received, but should be dropped:", packet),
1894 if packet_index in seen:
1895 raise Exception(ppp("Duplicate packet received", packet))
1896 seen.add(packet_index)
1897 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1898 info = self._packet_infos[packet_index]
1899 self.assertTrue(info is not None)
1900 self.assertEqual(packet_index, info.index)
1901 saved_packet = info.data
1902 self.assertEqual(ip.src, saved_packet[IPv6].src)
1903 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1904 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1906 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1908 for index in self._packet_infos:
1910 index in seen or index in dropped_packet_indexes,
1911 "Packet with packet_index %d not received" % index,
1914 def send_packets(self, packets):
1915 for counter in range(self.vpp_worker_count):
1916 if 0 == len(packets[counter]):
1918 send_if = self.send_ifs[counter]
1921 Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1922 for x in packets[counter]
1928 def test_worker_conflict(self):
1929 """1st and FO=0 fragments on different workers"""
1931 # in first wave we send fragments which don't start at offset 0
1932 # then we send fragments with offset 0 on a different thread
1933 # then the rest of packets on a random thread
1934 first_packets = [[] for n in range(self.vpp_worker_count)]
1935 second_packets = [[] for n in range(self.vpp_worker_count)]
1936 rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1937 for (_, p) in self.pkt_infos:
1938 wi = randrange(self.vpp_worker_count)
1939 second_packets[wi].append(p[0])
1944 wi2 = randrange(self.vpp_worker_count)
1945 first_packets[wi2].append(p[1])
1946 wi3 = randrange(self.vpp_worker_count)
1947 rest_of_packets[wi3].extend(p[2:])
1949 self.pg_enable_capture()
1950 self.send_packets(first_packets)
1951 self.send_packets(second_packets)
1952 self.send_packets(rest_of_packets)
1954 packets = self.dst_if.get_capture(len(self.pkt_infos))
1955 self.verify_capture(packets)
1956 for send_if in self.send_ifs:
1957 send_if.assert_nothing_captured()
1959 self.logger.debug(self.vapi.ppcli("show trace"))
1960 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1961 self.logger.debug(self.vapi.ppcli("show buffers"))
1962 self.vapi.cli("clear trace")
1964 self.pg_enable_capture()
1965 self.send_packets(first_packets)
1966 self.send_packets(second_packets)
1967 self.send_packets(rest_of_packets)
1969 packets = self.dst_if.get_capture(len(self.pkt_infos))
1970 self.verify_capture(packets)
1971 for send_if in self.send_ifs:
1972 send_if.assert_nothing_captured()
1975 class TestIPv6SVReassembly(VppTestCase):
1976 """IPv6 Shallow Virtual Reassembly"""
1979 def setUpClass(cls):
1980 super().setUpClass()
1982 cls.create_pg_interfaces([0, 1])
1983 cls.src_if = cls.pg0
1984 cls.dst_if = cls.pg1
1986 # setup all interfaces
1987 for i in cls.pg_interfaces:
1993 """Test setup - force timeout on existing reassemblies"""
1995 self.vapi.ip_reassembly_enable_disable(
1996 sw_if_index=self.src_if.sw_if_index,
1998 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2000 self.vapi.ip_reassembly_set(
2002 max_reassemblies=1000,
2003 max_reassembly_length=1000,
2004 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2005 expire_walk_interval_ms=10,
2008 self.virtual_sleep(0.25)
2009 self.vapi.ip_reassembly_set(
2011 max_reassemblies=1000,
2012 max_reassembly_length=1000,
2013 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2014 expire_walk_interval_ms=10000,
2020 self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2021 self.logger.debug(self.vapi.ppcli("show buffers"))
2023 def test_basic(self):
2024 """basic reassembly"""
2028 while len(payload) < payload_len:
2029 payload += "%u " % counter
2033 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2034 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2035 / UDP(sport=1234, dport=5678)
2038 fragments = fragment_rfc8200(p, 1, payload_len / 4)
2040 # send fragment #2 - should be cached inside reassembly
2041 self.pg_enable_capture()
2042 self.src_if.add_stream(fragments[1])
2044 self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2045 self.logger.debug(self.vapi.ppcli("show buffers"))
2046 self.logger.debug(self.vapi.ppcli("show trace"))
2047 self.dst_if.assert_nothing_captured()
2049 # send fragment #1 - reassembly is finished now and both fragments
2051 self.pg_enable_capture()
2052 self.src_if.add_stream(fragments[0])
2054 self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2055 self.logger.debug(self.vapi.ppcli("show buffers"))
2056 self.logger.debug(self.vapi.ppcli("show trace"))
2057 c = self.dst_if.get_capture(2)
2058 for sent, recvd in zip([fragments[1], fragments[0]], c):
2059 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2060 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2061 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2063 # send rest of fragments - should be immediately forwarded
2064 self.pg_enable_capture()
2065 self.src_if.add_stream(fragments[2:])
2067 c = self.dst_if.get_capture(len(fragments[2:]))
2068 for sent, recvd in zip(fragments[2:], c):
2069 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2070 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2071 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2073 def test_verify_clear_trace_mid_reassembly(self):
2074 """verify clear trace works mid-reassembly"""
2078 while len(payload) < payload_len:
2079 payload += "%u " % counter
2083 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2084 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2085 / UDP(sport=1234, dport=5678)
2088 fragments = fragment_rfc8200(p, 1, payload_len / 4)
2090 self.pg_enable_capture()
2091 self.src_if.add_stream(fragments[1])
2094 self.logger.debug(self.vapi.cli("show trace"))
2095 self.vapi.cli("clear trace")
2097 self.pg_enable_capture()
2098 self.src_if.add_stream(fragments[0])
2100 self.dst_if.get_capture(2)
2102 self.logger.debug(self.vapi.cli("show trace"))
2103 self.vapi.cli("clear trace")
2105 self.pg_enable_capture()
2106 self.src_if.add_stream(fragments[2:])
2108 self.dst_if.get_capture(len(fragments[2:]))
2110 def test_timeout(self):
2111 """reassembly timeout"""
2115 while len(payload) < payload_len:
2116 payload += "%u " % counter
2120 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2121 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2122 / UDP(sport=1234, dport=5678)
2125 fragments = fragment_rfc8200(p, 1, payload_len / 4)
2127 self.vapi.ip_reassembly_set(
2129 max_reassemblies=1000,
2130 max_reassembly_length=1000,
2131 expire_walk_interval_ms=50,
2133 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2136 # send fragments #2 and #1 - should be forwarded
2137 self.pg_enable_capture()
2138 self.src_if.add_stream(fragments[0:2])
2140 self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
2141 self.logger.debug(self.vapi.ppcli("show buffers"))
2142 self.logger.debug(self.vapi.ppcli("show trace"))
2143 c = self.dst_if.get_capture(2)
2144 for sent, recvd in zip([fragments[1], fragments[0]], c):
2145 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2146 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2147 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2150 self.virtual_sleep(0.25, "wait before sending rest of fragments")
2152 # send rest of fragments - shouldn't be forwarded
2153 self.pg_enable_capture()
2154 self.src_if.add_stream(fragments[2:])
2156 self.dst_if.assert_nothing_captured()
2159 """reassembly reuses LRU element"""
2161 self.vapi.ip_reassembly_set(
2164 max_reassembly_length=1000,
2165 type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2167 expire_walk_interval_ms=10000,
2173 while len(payload) < payload_len:
2174 payload += "%u " % counter
2181 for i in range(packet_count)
2183 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2184 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2185 / UDP(sport=1234, dport=5678)
2188 for f in fragment_rfc8200(p, i, payload_len / 4)
2191 self.pg_enable_capture()
2192 self.src_if.add_stream(fragments)
2194 c = self.dst_if.get_capture(len(fragments))
2195 for sent, recvd in zip(fragments, c):
2196 self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2197 self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2198 self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2200 def test_one_fragment(self):
2201 """whole packet in one fragment processed independently"""
2203 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2204 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2205 / ICMPv6EchoRequest()
2208 frags = fragment_rfc8200(pkt, 1, 400)
2210 # send a fragment with known id
2211 self.send_and_expect(self.src_if, [frags[0]], self.dst_if)
2213 # send an atomic fragment with same id - should be reassembled
2215 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2216 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2217 / IPv6ExtHdrFragment(id=1)
2218 / ICMPv6EchoRequest()
2220 rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2222 # now forward packets matching original reassembly, should still work
2223 rx = self.send_and_expect(self.src_if, frags[1:], self.dst_if)
2225 def test_bunch_of_fragments(self):
2226 """valid fragments followed by rogue fragments and atomic fragment"""
2228 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2229 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2230 / ICMPv6EchoRequest()
2233 frags = fragment_rfc8200(pkt, 1, 400)
2234 rx = self.send_and_expect(self.src_if, frags, self.dst_if)
2237 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2238 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2239 / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
2243 self.send_and_expect(self.src_if, rogue * 604, self.dst_if)
2246 Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2247 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2248 / IPv6ExtHdrFragment(id=1)
2249 / ICMPv6EchoRequest()
2251 rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2253 def test_truncated_fragment(self):
2254 """truncated fragment"""
2256 Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
2257 / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
2258 / IPv6ExtHdrFragment(nh=6)
2261 self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
2264 class TestIPv4ReassemblyLocalNode(VppTestCase):
2265 """IPv4 Reassembly for packets coming to ip4-local node"""
2268 def setUpClass(cls):
2269 super().setUpClass()
2271 cls.create_pg_interfaces([0])
2272 cls.src_dst_if = cls.pg0
2274 # setup all interfaces
2275 for i in cls.pg_interfaces:
2280 cls.padding = " abcdefghijklmn"
2282 cls.create_fragments()
2285 def tearDownClass(cls):
2286 super().tearDownClass()
2289 """Test setup - force timeout on existing reassemblies"""
2291 self.vapi.ip_reassembly_set(
2293 max_reassemblies=1000,
2294 max_reassembly_length=1000,
2295 expire_walk_interval_ms=10,
2297 self.virtual_sleep(0.25)
2298 self.vapi.ip_reassembly_set(
2300 max_reassemblies=1000,
2301 max_reassembly_length=1000,
2302 expire_walk_interval_ms=10000,
2308 def show_commands_at_teardown(self):
2309 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2310 self.logger.debug(self.vapi.ppcli("show buffers"))
2313 def create_stream(cls, packet_count=test_packet_count):
2314 """Create input packet stream for defined interface.
2316 :param list packet_sizes: Required packet sizes.
2318 for i in range(0, packet_count):
2319 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
2320 payload = cls.info_to_payload(info)
2322 Ether(dst=cls.src_dst_if.local_mac, src=cls.src_dst_if.remote_mac)
2325 src=cls.src_dst_if.remote_ip4,
2326 dst=cls.src_dst_if.local_ip4,
2328 / ICMP(type="echo-request", id=1234)
2331 cls.extend_packet(p, 1518, cls.padding)
2335 def create_fragments(cls):
2336 infos = cls._packet_infos
2338 for index, info in infos.items():
2340 # cls.logger.debug(ppp("Packet:",
2341 # p.__class__(scapy.compat.raw(p))))
2342 fragments_300 = fragment_rfc791(p, 300)
2343 cls.pkt_infos.append((index, fragments_300))
2344 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
2346 "Fragmented %s packets into %s 300-byte fragments"
2347 % (len(infos), len(cls.fragments_300))
2350 def verify_capture(self, capture):
2351 """Verify captured packet stream.
2353 :param list capture: Captured packet stream.
2357 for packet in capture:
2359 self.logger.debug(ppp("Got packet:", packet))
2362 payload_info = self.payload_to_info(packet[Raw])
2363 packet_index = payload_info.index
2364 if packet_index in seen:
2365 raise Exception(ppp("Duplicate packet received", packet))
2366 seen.add(packet_index)
2367 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
2368 info = self._packet_infos[packet_index]
2369 self.assertIsNotNone(info)
2370 self.assertEqual(packet_index, info.index)
2371 saved_packet = info.data
2372 self.assertEqual(ip.src, saved_packet[IP].dst)
2373 self.assertEqual(ip.dst, saved_packet[IP].src)
2374 self.assertEqual(icmp.type, 0) # echo reply
2375 self.assertEqual(icmp.id, saved_packet[ICMP].id)
2376 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
2378 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2380 for index in self._packet_infos:
2382 index, seen, "Packet with packet_index %d not received" % index
2385 def test_reassembly(self):
2386 """basic reassembly"""
2388 self.pg_enable_capture()
2389 self.src_dst_if.add_stream(self.fragments_300)
2392 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2393 self.verify_capture(packets)
2395 # run it all again to verify correctness
2396 self.pg_enable_capture()
2397 self.src_dst_if.add_stream(self.fragments_300)
2400 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2401 self.verify_capture(packets)
2404 class TestFIFReassembly(VppTestCase):
2405 """Fragments in fragments reassembly"""
2408 def setUpClass(cls):
2409 super().setUpClass()
2411 cls.create_pg_interfaces([0, 1])
2412 cls.src_if = cls.pg0
2413 cls.dst_if = cls.pg1
2414 for i in cls.pg_interfaces:
2421 cls.packet_sizes = [64, 512, 1518, 9018]
2422 cls.padding = " abcdefghijklmn"
2425 def tearDownClass(cls):
2426 super().tearDownClass()
2429 """Test setup - force timeout on existing reassemblies"""
2431 self.vapi.ip_reassembly_enable_disable(
2432 sw_if_index=self.src_if.sw_if_index, enable_ip4=True, enable_ip6=True
2434 self.vapi.ip_reassembly_enable_disable(
2435 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True, enable_ip6=True
2437 self.vapi.ip_reassembly_set(
2439 max_reassemblies=1000,
2440 max_reassembly_length=1000,
2441 expire_walk_interval_ms=10,
2443 self.vapi.ip_reassembly_set(
2445 max_reassemblies=1000,
2446 max_reassembly_length=1000,
2447 expire_walk_interval_ms=10,
2450 self.virtual_sleep(0.25)
2451 self.vapi.ip_reassembly_set(
2453 max_reassemblies=1000,
2454 max_reassembly_length=1000,
2455 expire_walk_interval_ms=10000,
2457 self.vapi.ip_reassembly_set(
2459 max_reassemblies=1000,
2460 max_reassembly_length=1000,
2461 expire_walk_interval_ms=10000,
2468 def show_commands_at_teardown(self):
2469 self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2470 self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
2471 self.logger.debug(self.vapi.ppcli("show buffers"))
2473 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
2474 """Verify captured packet stream.
2476 :param list capture: Captured packet stream.
2480 for packet in capture:
2482 self.logger.debug(ppp("Got packet:", packet))
2483 ip = packet[ip_class]
2485 payload_info = self.payload_to_info(packet[Raw])
2486 packet_index = payload_info.index
2488 packet_index not in dropped_packet_indexes,
2489 ppp("Packet received, but should be dropped:", packet),
2491 if packet_index in seen:
2492 raise Exception(ppp("Duplicate packet received", packet))
2493 seen.add(packet_index)
2494 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
2495 info = self._packet_infos[packet_index]
2496 self.assertTrue(info is not None)
2497 self.assertEqual(packet_index, info.index)
2498 saved_packet = info.data
2499 self.assertEqual(ip.src, saved_packet[ip_class].src)
2500 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
2501 self.assertEqual(udp.payload, saved_packet[UDP].payload)
2503 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2505 for index in self._packet_infos:
2507 index in seen or index in dropped_packet_indexes,
2508 "Packet with packet_index %d not received" % index,
2511 def test_fif4(self):
2512 """Fragments in fragments (4o4)"""
2514 # TODO this should be ideally in setUpClass, but then we hit a bug
2515 # with VppIpRoute incorrectly reporting it's present when it's not
2516 # so we need to manually remove the vpp config, thus we cannot have
2517 # it shared for multiple test cases
2518 self.tun_ip4 = "1.1.1.2"
2520 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
2521 self.gre4.add_vpp_config()
2522 self.gre4.admin_up()
2523 self.gre4.config_ip4()
2525 self.vapi.ip_reassembly_enable_disable(
2526 sw_if_index=self.gre4.sw_if_index, enable_ip4=True
2529 self.route4 = VppIpRoute(
2533 [VppRoutePath(self.src_if.remote_ip4, self.src_if.sw_if_index)],
2535 self.route4.add_vpp_config()
2537 self.reset_packet_infos()
2538 for i in range(test_packet_count):
2539 info = self.create_packet_info(self.src_if, self.dst_if)
2540 payload = self.info_to_payload(info)
2541 # Ethernet header here is only for size calculation, thus it
2542 # doesn't matter how it's initialized. This is to ensure that
2543 # reassembled packet is not > 9000 bytes, so that it's not dropped
2546 / IP(id=i, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
2547 / UDP(sport=1234, dport=5678)
2550 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2551 self.extend_packet(p, size, self.padding)
2552 info.data = p[IP] # use only IP part, without ethernet header
2556 for _, p in self._packet_infos.items()
2557 for x in fragment_rfc791(p.data, 400)
2560 encapped_fragments = [
2561 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2562 / IP(src=self.tun_ip4, dst=self.src_if.local_ip4)
2568 fragmented_encapped_fragments = [
2569 x for p in encapped_fragments for x in fragment_rfc791(p, 200)
2572 self.src_if.add_stream(fragmented_encapped_fragments)
2574 self.pg_enable_capture(self.pg_interfaces)
2577 self.src_if.assert_nothing_captured()
2578 packets = self.dst_if.get_capture(len(self._packet_infos))
2579 self.verify_capture(packets, IP)
2581 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2582 # so that it's query_vpp_config() works as it should
2583 self.gre4.remove_vpp_config()
2584 self.logger.debug(self.vapi.ppcli("show interface"))
2586 def test_fif6(self):
2587 """Fragments in fragments (6o6)"""
2588 # TODO this should be ideally in setUpClass, but then we hit a bug
2589 # with VppIpRoute incorrectly reporting it's present when it's not
2590 # so we need to manually remove the vpp config, thus we cannot have
2591 # it shared for multiple test cases
2592 self.tun_ip6 = "1002::1"
2594 self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
2595 self.gre6.add_vpp_config()
2596 self.gre6.admin_up()
2597 self.gre6.config_ip6()
2599 self.vapi.ip_reassembly_enable_disable(
2600 sw_if_index=self.gre6.sw_if_index, enable_ip6=True
2603 self.route6 = VppIpRoute(
2607 [VppRoutePath(self.src_if.remote_ip6, self.src_if.sw_if_index)],
2609 self.route6.add_vpp_config()
2611 self.reset_packet_infos()
2612 for i in range(test_packet_count):
2613 info = self.create_packet_info(self.src_if, self.dst_if)
2614 payload = self.info_to_payload(info)
2615 # Ethernet header here is only for size calculation, thus it
2616 # doesn't matter how it's initialized. This is to ensure that
2617 # reassembled packet is not > 9000 bytes, so that it's not dropped
2620 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2621 / UDP(sport=1234, dport=5678)
2624 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2625 self.extend_packet(p, size, self.padding)
2626 info.data = p[IPv6] # use only IPv6 part, without ethernet header
2630 for _, i in self._packet_infos.items()
2631 for x in fragment_rfc8200(i.data, i.index, 400)
2634 encapped_fragments = [
2635 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2636 / IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6)
2642 fragmented_encapped_fragments = [
2644 for p in encapped_fragments
2647 p, 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id, 200
2649 if IPv6ExtHdrFragment in p
2654 self.src_if.add_stream(fragmented_encapped_fragments)
2656 self.pg_enable_capture(self.pg_interfaces)
2659 self.src_if.assert_nothing_captured()
2660 packets = self.dst_if.get_capture(len(self._packet_infos))
2661 self.verify_capture(packets, IPv6)
2663 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2664 # so that it's query_vpp_config() works as it should
2665 self.gre6.remove_vpp_config()
2668 if __name__ == "__main__":
2669 unittest.main(testRunner=VppTestRunner)