5 from random import shuffle
7 from framework import VppTestCase, VppTestRunner, is_skip_aarch64_set,\
10 from scapy.packet import Raw
11 from scapy.layers.l2 import Ether, GRE
12 from scapy.layers.inet import IP, UDP, ICMP
13 from util import ppp, fragment_rfc791, fragment_rfc8200
14 from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, ICMPv6ParamProblem,\
16 from vpp_gre_interface import VppGreInterface, VppGre6Interface
17 from vpp_ip import DpoProto
18 from vpp_ip_route import VppIpRoute, VppRoutePath
20 test_packet_count = 257
23 class TestIPv4Reassembly(VppTestCase):
24 """ IPv4 Reassembly """
28 super(TestIPv4Reassembly, cls).setUpClass()
30 cls.create_pg_interfaces([0, 1])
34 # setup all interfaces
35 for i in cls.pg_interfaces:
41 cls.packet_sizes = [64, 512, 1518, 9018]
42 cls.padding = " abcdefghijklmn"
43 cls.create_stream(cls.packet_sizes)
44 cls.create_fragments()
47 """ Test setup - force timeout on existing reassemblies """
48 super(TestIPv4Reassembly, self).setUp()
49 self.vapi.ip_reassembly_enable_disable(
50 sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
51 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
52 expire_walk_interval_ms=10)
54 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
55 expire_walk_interval_ms=10000)
58 super(TestIPv4Reassembly, self).tearDown()
59 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
62 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
63 """Create input packet stream for defined interface.
65 :param list packet_sizes: Required packet sizes.
67 for i in range(0, packet_count):
68 info = cls.create_packet_info(cls.src_if, cls.src_if)
69 payload = cls.info_to_payload(info)
70 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
71 IP(id=info.index, src=cls.src_if.remote_ip4,
72 dst=cls.dst_if.remote_ip4) /
73 UDP(sport=1234, dport=5678) /
75 size = packet_sizes[(i // 2) % len(packet_sizes)]
76 cls.extend_packet(p, size, cls.padding)
80 def create_fragments(cls):
81 infos = cls._packet_infos
83 for index, info in six.iteritems(infos):
85 # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
86 fragments_400 = fragment_rfc791(p, 400)
87 fragments_300 = fragment_rfc791(p, 300)
89 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
91 (index, fragments_400, fragments_300, fragments_200))
93 x for (_, frags, _, _) in cls.pkt_infos for x in frags]
95 x for (_, _, frags, _) in cls.pkt_infos for x in frags]
97 x for (_, _, _, frags) in cls.pkt_infos for x in frags]
98 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
99 "%s 300-byte fragments and %s 200-byte fragments" %
100 (len(infos), len(cls.fragments_400),
101 len(cls.fragments_300), len(cls.fragments_200)))
103 def verify_capture(self, capture, dropped_packet_indexes=[]):
104 """Verify captured packet stream.
106 :param list capture: Captured packet stream.
110 for packet in capture:
112 self.logger.debug(ppp("Got packet:", packet))
115 payload_info = self.payload_to_info(str(packet[Raw]))
116 packet_index = payload_info.index
118 packet_index not in dropped_packet_indexes,
119 ppp("Packet received, but should be dropped:", packet))
120 if packet_index in seen:
121 raise Exception(ppp("Duplicate packet received", packet))
122 seen.add(packet_index)
123 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
124 info = self._packet_infos[packet_index]
125 self.assertTrue(info is not None)
126 self.assertEqual(packet_index, info.index)
127 saved_packet = info.data
128 self.assertEqual(ip.src, saved_packet[IP].src)
129 self.assertEqual(ip.dst, saved_packet[IP].dst)
130 self.assertEqual(udp.payload, saved_packet[UDP].payload)
132 self.logger.error(ppp("Unexpected or invalid packet:", packet))
134 for index in self._packet_infos:
135 self.assertTrue(index in seen or index in dropped_packet_indexes,
136 "Packet with packet_index %d not received" % index)
138 def test_reassembly(self):
139 """ basic reassembly """
141 self.pg_enable_capture()
142 self.src_if.add_stream(self.fragments_200)
145 packets = self.dst_if.get_capture(len(self.pkt_infos))
146 self.verify_capture(packets)
147 self.src_if.assert_nothing_captured()
149 # run it all again to verify correctness
150 self.pg_enable_capture()
151 self.src_if.add_stream(self.fragments_200)
154 packets = self.dst_if.get_capture(len(self.pkt_infos))
155 self.verify_capture(packets)
156 self.src_if.assert_nothing_captured()
158 def test_reversed(self):
159 """ reverse order reassembly """
161 fragments = list(self.fragments_200)
164 self.pg_enable_capture()
165 self.src_if.add_stream(fragments)
168 packets = self.dst_if.get_capture(len(self.packet_infos))
169 self.verify_capture(packets)
170 self.src_if.assert_nothing_captured()
172 # run it all again to verify correctness
173 self.pg_enable_capture()
174 self.src_if.add_stream(fragments)
177 packets = self.dst_if.get_capture(len(self.packet_infos))
178 self.verify_capture(packets)
179 self.src_if.assert_nothing_captured()
182 """ fragment length + ip header size > 65535 """
183 self.vapi.cli("clear errors")
184 raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
185 '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
186 'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
187 'fset; Test-case: 5737')
189 malformed_packet = (Ether(dst=self.src_if.local_mac,
190 src=self.src_if.remote_mac) /
192 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
193 IP(id=1000, src=self.src_if.remote_ip4,
194 dst=self.dst_if.remote_ip4) /
195 UDP(sport=1234, dport=5678) /
197 valid_fragments = fragment_rfc791(p, 400)
199 self.pg_enable_capture()
200 self.src_if.add_stream([malformed_packet] + valid_fragments)
203 self.dst_if.get_capture(1)
204 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
205 # TODO remove above, uncomment below once clearing of counters
207 # self.assert_packet_counter_equal(
208 # "/err/ip4-reassembly-feature/malformed packets", 1)
210 def test_44924(self):
211 """ compress tiny fragments """
212 packets = [(Ether(dst=self.src_if.local_mac,
213 src=self.src_if.remote_mac) /
214 IP(id=24339, flags="MF", frag=0, ttl=64,
215 src=self.src_if.remote_ip4,
216 dst=self.dst_if.remote_ip4) /
217 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
218 Raw(load='Test-group: IPv4')),
219 (Ether(dst=self.src_if.local_mac,
220 src=self.src_if.remote_mac) /
221 IP(id=24339, flags="MF", frag=3, ttl=64,
222 src=self.src_if.remote_ip4,
223 dst=self.dst_if.remote_ip4) /
224 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
225 Raw(load='.IPv4.Fragmentation.vali')),
226 (Ether(dst=self.src_if.local_mac,
227 src=self.src_if.remote_mac) /
228 IP(id=24339, frag=6, ttl=64,
229 src=self.src_if.remote_ip4,
230 dst=self.dst_if.remote_ip4) /
231 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
232 Raw(load='d; Test-case: 44924'))
235 self.pg_enable_capture()
236 self.src_if.add_stream(packets)
239 self.dst_if.get_capture(1)
241 def test_frag_1(self):
242 """ fragment of size 1 """
243 self.vapi.cli("clear errors")
244 malformed_packets = [(Ether(dst=self.src_if.local_mac,
245 src=self.src_if.remote_mac) /
246 IP(id=7, len=21, flags="MF", frag=0, ttl=64,
247 src=self.src_if.remote_ip4,
248 dst=self.dst_if.remote_ip4) /
249 ICMP(type="echo-request")),
250 (Ether(dst=self.src_if.local_mac,
251 src=self.src_if.remote_mac) /
252 IP(id=7, len=21, frag=1, ttl=64,
253 src=self.src_if.remote_ip4,
254 dst=self.dst_if.remote_ip4) /
258 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
259 IP(id=1000, src=self.src_if.remote_ip4,
260 dst=self.dst_if.remote_ip4) /
261 UDP(sport=1234, dport=5678) /
263 valid_fragments = fragment_rfc791(p, 400)
265 self.pg_enable_capture()
266 self.src_if.add_stream(malformed_packets + valid_fragments)
269 self.dst_if.get_capture(1)
271 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
272 # TODO remove above, uncomment below once clearing of counters
274 # self.assert_packet_counter_equal(
275 # "/err/ip4-reassembly-feature/malformed packets", 1)
277 @unittest.skipIf(is_skip_aarch64_set() and is_platform_aarch64(),
278 "test doesn't work on aarch64")
279 def test_random(self):
280 """ random order reassembly """
282 fragments = list(self.fragments_200)
285 self.pg_enable_capture()
286 self.src_if.add_stream(fragments)
289 packets = self.dst_if.get_capture(len(self.packet_infos))
290 self.verify_capture(packets)
291 self.src_if.assert_nothing_captured()
293 # run it all again to verify correctness
294 self.pg_enable_capture()
295 self.src_if.add_stream(fragments)
298 packets = self.dst_if.get_capture(len(self.packet_infos))
299 self.verify_capture(packets)
300 self.src_if.assert_nothing_captured()
302 def test_duplicates(self):
303 """ duplicate fragments """
306 x for (_, frags, _, _) in self.pkt_infos
308 for _ in range(0, min(2, len(frags)))
311 self.pg_enable_capture()
312 self.src_if.add_stream(fragments)
315 packets = self.dst_if.get_capture(len(self.pkt_infos))
316 self.verify_capture(packets)
317 self.src_if.assert_nothing_captured()
319 def test_overlap1(self):
320 """ overlapping fragments case #1 """
323 for _, _, frags_300, frags_200 in self.pkt_infos:
324 if len(frags_300) == 1:
325 fragments.extend(frags_300)
327 for i, j in zip(frags_200, frags_300):
331 self.pg_enable_capture()
332 self.src_if.add_stream(fragments)
335 packets = self.dst_if.get_capture(len(self.pkt_infos))
336 self.verify_capture(packets)
337 self.src_if.assert_nothing_captured()
339 # run it all to verify correctness
340 self.pg_enable_capture()
341 self.src_if.add_stream(fragments)
344 packets = self.dst_if.get_capture(len(self.pkt_infos))
345 self.verify_capture(packets)
346 self.src_if.assert_nothing_captured()
348 def test_overlap2(self):
349 """ overlapping fragments case #2 """
352 for _, _, frags_300, frags_200 in self.pkt_infos:
353 if len(frags_300) == 1:
354 fragments.extend(frags_300)
356 # care must be taken here so that there are no fragments
357 # received by vpp after reassembly is finished, otherwise
358 # new reassemblies will be started and packet generator will
359 # freak out when it detects unfreed buffers
360 zipped = zip(frags_300, frags_200)
361 for i, j in zipped[:-1]:
364 fragments.append(zipped[-1][0])
366 self.pg_enable_capture()
367 self.src_if.add_stream(fragments)
370 packets = self.dst_if.get_capture(len(self.pkt_infos))
371 self.verify_capture(packets)
372 self.src_if.assert_nothing_captured()
374 # run it all to verify correctness
375 self.pg_enable_capture()
376 self.src_if.add_stream(fragments)
379 packets = self.dst_if.get_capture(len(self.pkt_infos))
380 self.verify_capture(packets)
381 self.src_if.assert_nothing_captured()
383 def test_timeout_inline(self):
384 """ timeout (inline) """
386 dropped_packet_indexes = set(
387 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
390 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
391 expire_walk_interval_ms=10000)
393 self.pg_enable_capture()
394 self.src_if.add_stream(self.fragments_400)
397 packets = self.dst_if.get_capture(
398 len(self.pkt_infos) - len(dropped_packet_indexes))
399 self.verify_capture(packets, dropped_packet_indexes)
400 self.src_if.assert_nothing_captured()
402 def test_timeout_cleanup(self):
403 """ timeout (cleanup) """
405 # whole packets + fragmented packets sans last fragment
407 x for (_, frags_400, _, _) in self.pkt_infos
408 for x in frags_400[:-1 if len(frags_400) > 1 else None]
411 # last fragments for fragmented packets
412 fragments2 = [frags_400[-1]
413 for (_, frags_400, _, _) in self.pkt_infos
414 if len(frags_400) > 1]
416 dropped_packet_indexes = set(
417 index for (index, frags_400, _, _) in self.pkt_infos
418 if len(frags_400) > 1)
420 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
421 expire_walk_interval_ms=50)
423 self.pg_enable_capture()
424 self.src_if.add_stream(fragments)
427 self.sleep(.25, "wait before sending rest of fragments")
429 self.src_if.add_stream(fragments2)
432 packets = self.dst_if.get_capture(
433 len(self.pkt_infos) - len(dropped_packet_indexes))
434 self.verify_capture(packets, dropped_packet_indexes)
435 self.src_if.assert_nothing_captured()
437 def test_disabled(self):
438 """ reassembly disabled """
440 dropped_packet_indexes = set(
441 index for (index, frags_400, _, _) in self.pkt_infos
442 if len(frags_400) > 1)
444 self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
445 expire_walk_interval_ms=10000)
447 self.pg_enable_capture()
448 self.src_if.add_stream(self.fragments_400)
451 packets = self.dst_if.get_capture(
452 len(self.pkt_infos) - len(dropped_packet_indexes))
453 self.verify_capture(packets, dropped_packet_indexes)
454 self.src_if.assert_nothing_captured()
457 class TestIPv6Reassembly(VppTestCase):
458 """ IPv6 Reassembly """
462 super(TestIPv6Reassembly, cls).setUpClass()
464 cls.create_pg_interfaces([0, 1])
468 # setup all interfaces
469 for i in cls.pg_interfaces:
475 cls.packet_sizes = [64, 512, 1518, 9018]
476 cls.padding = " abcdefghijklmn"
477 cls.create_stream(cls.packet_sizes)
478 cls.create_fragments()
481 """ Test setup - force timeout on existing reassemblies """
482 super(TestIPv6Reassembly, self).setUp()
483 self.vapi.ip_reassembly_enable_disable(
484 sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
485 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
486 expire_walk_interval_ms=10, is_ip6=1)
488 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
489 expire_walk_interval_ms=10000, is_ip6=1)
490 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
493 super(TestIPv6Reassembly, self).tearDown()
494 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
497 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
498 """Create input packet stream for defined interface.
500 :param list packet_sizes: Required packet sizes.
502 for i in range(0, packet_count):
503 info = cls.create_packet_info(cls.src_if, cls.src_if)
504 payload = cls.info_to_payload(info)
505 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
506 IPv6(src=cls.src_if.remote_ip6,
507 dst=cls.dst_if.remote_ip6) /
508 UDP(sport=1234, dport=5678) /
510 size = packet_sizes[(i // 2) % len(packet_sizes)]
511 cls.extend_packet(p, size, cls.padding)
515 def create_fragments(cls):
516 infos = cls._packet_infos
518 for index, info in six.iteritems(infos):
520 # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
521 fragments_400 = fragment_rfc8200(p, info.index, 400)
522 fragments_300 = fragment_rfc8200(p, info.index, 300)
523 cls.pkt_infos.append((index, fragments_400, fragments_300))
524 cls.fragments_400 = [
525 x for _, frags, _ in cls.pkt_infos for x in frags]
526 cls.fragments_300 = [
527 x for _, _, frags in cls.pkt_infos for x in frags]
528 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
529 "and %s 300-byte fragments" %
530 (len(infos), len(cls.fragments_400),
531 len(cls.fragments_300)))
533 def verify_capture(self, capture, dropped_packet_indexes=[]):
534 """Verify captured packet strea .
536 :param list capture: Captured packet stream.
540 for packet in capture:
542 self.logger.debug(ppp("Got packet:", packet))
545 payload_info = self.payload_to_info(str(packet[Raw]))
546 packet_index = payload_info.index
548 packet_index not in dropped_packet_indexes,
549 ppp("Packet received, but should be dropped:", packet))
550 if packet_index in seen:
551 raise Exception(ppp("Duplicate packet received", packet))
552 seen.add(packet_index)
553 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
554 info = self._packet_infos[packet_index]
555 self.assertTrue(info is not None)
556 self.assertEqual(packet_index, info.index)
557 saved_packet = info.data
558 self.assertEqual(ip.src, saved_packet[IPv6].src)
559 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
560 self.assertEqual(udp.payload, saved_packet[UDP].payload)
562 self.logger.error(ppp("Unexpected or invalid packet:", packet))
564 for index in self._packet_infos:
565 self.assertTrue(index in seen or index in dropped_packet_indexes,
566 "Packet with packet_index %d not received" % index)
568 def test_reassembly(self):
569 """ basic reassembly """
571 self.pg_enable_capture()
572 self.src_if.add_stream(self.fragments_400)
575 packets = self.dst_if.get_capture(len(self.pkt_infos))
576 self.verify_capture(packets)
577 self.src_if.assert_nothing_captured()
579 # run it all again to verify correctness
580 self.pg_enable_capture()
581 self.src_if.add_stream(self.fragments_400)
584 packets = self.dst_if.get_capture(len(self.pkt_infos))
585 self.verify_capture(packets)
586 self.src_if.assert_nothing_captured()
588 def test_reversed(self):
589 """ reverse order reassembly """
591 fragments = list(self.fragments_400)
594 self.pg_enable_capture()
595 self.src_if.add_stream(fragments)
598 packets = self.dst_if.get_capture(len(self.pkt_infos))
599 self.verify_capture(packets)
600 self.src_if.assert_nothing_captured()
602 # run it all again to verify correctness
603 self.pg_enable_capture()
604 self.src_if.add_stream(fragments)
607 packets = self.dst_if.get_capture(len(self.pkt_infos))
608 self.verify_capture(packets)
609 self.src_if.assert_nothing_captured()
611 def test_random(self):
612 """ random order reassembly """
614 fragments = list(self.fragments_400)
617 self.pg_enable_capture()
618 self.src_if.add_stream(fragments)
621 packets = self.dst_if.get_capture(len(self.pkt_infos))
622 self.verify_capture(packets)
623 self.src_if.assert_nothing_captured()
625 # run it all again to verify correctness
626 self.pg_enable_capture()
627 self.src_if.add_stream(fragments)
630 packets = self.dst_if.get_capture(len(self.pkt_infos))
631 self.verify_capture(packets)
632 self.src_if.assert_nothing_captured()
634 def test_duplicates(self):
635 """ duplicate fragments """
638 x for (_, frags, _) in self.pkt_infos
640 for _ in range(0, min(2, len(frags)))
643 self.pg_enable_capture()
644 self.src_if.add_stream(fragments)
647 packets = self.dst_if.get_capture(len(self.pkt_infos))
648 self.verify_capture(packets)
649 self.src_if.assert_nothing_captured()
651 def test_overlap1(self):
652 """ overlapping fragments case #1 """
655 for _, frags_400, frags_300 in self.pkt_infos:
656 if len(frags_300) == 1:
657 fragments.extend(frags_400)
659 for i, j in zip(frags_300, frags_400):
663 dropped_packet_indexes = set(
664 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
667 self.pg_enable_capture()
668 self.src_if.add_stream(fragments)
671 packets = self.dst_if.get_capture(
672 len(self.pkt_infos) - len(dropped_packet_indexes))
673 self.verify_capture(packets, dropped_packet_indexes)
674 self.src_if.assert_nothing_captured()
676 def test_overlap2(self):
677 """ overlapping fragments case #2 """
680 for _, frags_400, frags_300 in self.pkt_infos:
681 if len(frags_400) == 1:
682 fragments.extend(frags_400)
684 # care must be taken here so that there are no fragments
685 # received by vpp after reassembly is finished, otherwise
686 # new reassemblies will be started and packet generator will
687 # freak out when it detects unfreed buffers
688 zipped = zip(frags_400, frags_300)
689 for i, j in zipped[:-1]:
692 fragments.append(zipped[-1][0])
694 dropped_packet_indexes = set(
695 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
698 self.pg_enable_capture()
699 self.src_if.add_stream(fragments)
702 packets = self.dst_if.get_capture(
703 len(self.pkt_infos) - len(dropped_packet_indexes))
704 self.verify_capture(packets, dropped_packet_indexes)
705 self.src_if.assert_nothing_captured()
707 def test_timeout_inline(self):
708 """ timeout (inline) """
710 dropped_packet_indexes = set(
711 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
714 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
715 expire_walk_interval_ms=10000, is_ip6=1)
717 self.pg_enable_capture()
718 self.src_if.add_stream(self.fragments_400)
721 packets = self.dst_if.get_capture(
722 len(self.pkt_infos) - len(dropped_packet_indexes))
723 self.verify_capture(packets, dropped_packet_indexes)
724 pkts = self.src_if.get_capture(
725 expected_count=len(dropped_packet_indexes))
727 self.assertIn(ICMPv6TimeExceeded, icmp)
728 self.assertIn(IPv6ExtHdrFragment, icmp)
729 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
730 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
732 def test_timeout_cleanup(self):
733 """ timeout (cleanup) """
735 # whole packets + fragmented packets sans last fragment
737 x for (_, frags_400, _) in self.pkt_infos
738 for x in frags_400[:-1 if len(frags_400) > 1 else None]
741 # last fragments for fragmented packets
742 fragments2 = [frags_400[-1]
743 for (_, frags_400, _) in self.pkt_infos
744 if len(frags_400) > 1]
746 dropped_packet_indexes = set(
747 index for (index, frags_400, _) in self.pkt_infos
748 if len(frags_400) > 1)
750 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
751 expire_walk_interval_ms=50)
753 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
754 expire_walk_interval_ms=50, is_ip6=1)
756 self.pg_enable_capture()
757 self.src_if.add_stream(fragments)
760 self.sleep(.25, "wait before sending rest of fragments")
762 self.src_if.add_stream(fragments2)
765 packets = self.dst_if.get_capture(
766 len(self.pkt_infos) - len(dropped_packet_indexes))
767 self.verify_capture(packets, dropped_packet_indexes)
768 pkts = self.src_if.get_capture(
769 expected_count=len(dropped_packet_indexes))
771 self.assertIn(ICMPv6TimeExceeded, icmp)
772 self.assertIn(IPv6ExtHdrFragment, icmp)
773 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
774 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
776 def test_disabled(self):
777 """ reassembly disabled """
779 dropped_packet_indexes = set(
780 index for (index, frags_400, _) in self.pkt_infos
781 if len(frags_400) > 1)
783 self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
784 expire_walk_interval_ms=10000, is_ip6=1)
786 self.pg_enable_capture()
787 self.src_if.add_stream(self.fragments_400)
790 packets = self.dst_if.get_capture(
791 len(self.pkt_infos) - len(dropped_packet_indexes))
792 self.verify_capture(packets, dropped_packet_indexes)
793 self.src_if.assert_nothing_captured()
795 def test_missing_upper(self):
796 """ missing upper layer """
797 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
798 IPv6(src=self.src_if.remote_ip6,
799 dst=self.src_if.local_ip6) /
800 UDP(sport=1234, dport=5678) /
802 self.extend_packet(p, 1000, self.padding)
803 fragments = fragment_rfc8200(p, 1, 500)
804 bad_fragment = p.__class__(str(fragments[1]))
805 bad_fragment[IPv6ExtHdrFragment].nh = 59
806 bad_fragment[IPv6ExtHdrFragment].offset = 0
807 self.pg_enable_capture()
808 self.src_if.add_stream([bad_fragment])
810 pkts = self.src_if.get_capture(expected_count=1)
812 self.assertIn(ICMPv6ParamProblem, icmp)
813 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
815 def test_invalid_frag_size(self):
816 """ fragment size not a multiple of 8 """
817 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
818 IPv6(src=self.src_if.remote_ip6,
819 dst=self.src_if.local_ip6) /
820 UDP(sport=1234, dport=5678) /
822 self.extend_packet(p, 1000, self.padding)
823 fragments = fragment_rfc8200(p, 1, 500)
824 bad_fragment = fragments[0]
825 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
826 self.pg_enable_capture()
827 self.src_if.add_stream([bad_fragment])
829 pkts = self.src_if.get_capture(expected_count=1)
831 self.assertIn(ICMPv6ParamProblem, icmp)
832 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
834 def test_invalid_packet_size(self):
835 """ total packet size > 65535 """
836 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
837 IPv6(src=self.src_if.remote_ip6,
838 dst=self.src_if.local_ip6) /
839 UDP(sport=1234, dport=5678) /
841 self.extend_packet(p, 1000, self.padding)
842 fragments = fragment_rfc8200(p, 1, 500)
843 bad_fragment = fragments[1]
844 bad_fragment[IPv6ExtHdrFragment].offset = 65500
845 self.pg_enable_capture()
846 self.src_if.add_stream([bad_fragment])
848 pkts = self.src_if.get_capture(expected_count=1)
850 self.assertIn(ICMPv6ParamProblem, icmp)
851 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
854 class TestIPv4ReassemblyLocalNode(VppTestCase):
855 """ IPv4 Reassembly for packets coming to ip4-local node """
859 super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
861 cls.create_pg_interfaces([0])
862 cls.src_dst_if = cls.pg0
864 # setup all interfaces
865 for i in cls.pg_interfaces:
870 cls.padding = " abcdefghijklmn"
872 cls.create_fragments()
875 """ Test setup - force timeout on existing reassemblies """
876 super(TestIPv4ReassemblyLocalNode, self).setUp()
877 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
878 expire_walk_interval_ms=10)
880 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
881 expire_walk_interval_ms=10000)
884 super(TestIPv4ReassemblyLocalNode, self).tearDown()
885 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
888 def create_stream(cls, packet_count=test_packet_count):
889 """Create input packet stream for defined interface.
891 :param list packet_sizes: Required packet sizes.
893 for i in range(0, packet_count):
894 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
895 payload = cls.info_to_payload(info)
896 p = (Ether(dst=cls.src_dst_if.local_mac,
897 src=cls.src_dst_if.remote_mac) /
898 IP(id=info.index, src=cls.src_dst_if.remote_ip4,
899 dst=cls.src_dst_if.local_ip4) /
900 ICMP(type='echo-request', id=1234) /
902 cls.extend_packet(p, 1518, cls.padding)
906 def create_fragments(cls):
907 infos = cls._packet_infos
909 for index, info in six.iteritems(infos):
911 # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
912 fragments_300 = fragment_rfc791(p, 300)
913 cls.pkt_infos.append((index, fragments_300))
914 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
915 cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
916 (len(infos), len(cls.fragments_300)))
918 def verify_capture(self, capture):
919 """Verify captured packet stream.
921 :param list capture: Captured packet stream.
925 for packet in capture:
927 self.logger.debug(ppp("Got packet:", packet))
930 payload_info = self.payload_to_info(str(packet[Raw]))
931 packet_index = payload_info.index
932 if packet_index in seen:
933 raise Exception(ppp("Duplicate packet received", packet))
934 seen.add(packet_index)
935 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
936 info = self._packet_infos[packet_index]
937 self.assertIsNotNone(info)
938 self.assertEqual(packet_index, info.index)
939 saved_packet = info.data
940 self.assertEqual(ip.src, saved_packet[IP].dst)
941 self.assertEqual(ip.dst, saved_packet[IP].src)
942 self.assertEqual(icmp.type, 0) # echo reply
943 self.assertEqual(icmp.id, saved_packet[ICMP].id)
944 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
946 self.logger.error(ppp("Unexpected or invalid packet:", packet))
948 for index in self._packet_infos:
949 self.assertIn(index, seen,
950 "Packet with packet_index %d not received" % index)
952 def test_reassembly(self):
953 """ basic reassembly """
955 self.pg_enable_capture()
956 self.src_dst_if.add_stream(self.fragments_300)
959 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
960 self.verify_capture(packets)
962 # run it all again to verify correctness
963 self.pg_enable_capture()
964 self.src_dst_if.add_stream(self.fragments_300)
967 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
968 self.verify_capture(packets)
971 class TestFIFReassembly(VppTestCase):
972 """ Fragments in fragments reassembly """
976 super(TestFIFReassembly, cls).setUpClass()
978 cls.create_pg_interfaces([0, 1])
981 for i in cls.pg_interfaces:
988 cls.packet_sizes = [64, 512, 1518, 9018]
989 cls.padding = " abcdefghijklmn"
992 """ Test setup - force timeout on existing reassemblies """
993 super(TestFIFReassembly, self).setUp()
994 self.vapi.ip_reassembly_enable_disable(
995 sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
997 self.vapi.ip_reassembly_enable_disable(
998 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
1000 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1001 expire_walk_interval_ms=10)
1002 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1003 expire_walk_interval_ms=10, is_ip6=1)
1005 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1006 expire_walk_interval_ms=10000)
1007 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1008 expire_walk_interval_ms=10000, is_ip6=1)
1011 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
1012 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
1013 super(TestFIFReassembly, self).tearDown()
1015 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
1016 """Verify captured packet stream.
1018 :param list capture: Captured packet stream.
1022 for packet in capture:
1024 self.logger.debug(ppp("Got packet:", packet))
1025 ip = packet[ip_class]
1027 payload_info = self.payload_to_info(str(packet[Raw]))
1028 packet_index = payload_info.index
1030 packet_index not in dropped_packet_indexes,
1031 ppp("Packet received, but should be dropped:", packet))
1032 if packet_index in seen:
1033 raise Exception(ppp("Duplicate packet received", packet))
1034 seen.add(packet_index)
1035 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1036 info = self._packet_infos[packet_index]
1037 self.assertTrue(info is not None)
1038 self.assertEqual(packet_index, info.index)
1039 saved_packet = info.data
1040 self.assertEqual(ip.src, saved_packet[ip_class].src)
1041 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1042 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1044 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1046 for index in self._packet_infos:
1047 self.assertTrue(index in seen or index in dropped_packet_indexes,
1048 "Packet with packet_index %d not received" % index)
1050 def test_fif4(self):
1051 """ Fragments in fragments (4o4) """
1053 # TODO this should be ideally in setUpClass, but then we hit a bug
1054 # with VppIpRoute incorrectly reporting it's present when it's not
1055 # so we need to manually remove the vpp config, thus we cannot have
1056 # it shared for multiple test cases
1057 self.tun_ip4 = "1.1.1.2"
1059 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1060 self.gre4.add_vpp_config()
1061 self.gre4.admin_up()
1062 self.gre4.config_ip4()
1064 self.vapi.ip_reassembly_enable_disable(
1065 sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1067 self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1068 [VppRoutePath(self.src_if.remote_ip4,
1069 self.src_if.sw_if_index)])
1070 self.route4.add_vpp_config()
1072 self.reset_packet_infos()
1073 for i in range(test_packet_count):
1074 info = self.create_packet_info(self.src_if, self.dst_if)
1075 payload = self.info_to_payload(info)
1076 # Ethernet header here is only for size calculation, thus it
1077 # doesn't matter how it's initialized. This is to ensure that
1078 # reassembled packet is not > 9000 bytes, so that it's not dropped
1080 IP(id=i, src=self.src_if.remote_ip4,
1081 dst=self.dst_if.remote_ip4) /
1082 UDP(sport=1234, dport=5678) /
1084 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1085 self.extend_packet(p, size, self.padding)
1086 info.data = p[IP] # use only IP part, without ethernet header
1088 fragments = [x for _, p in six.iteritems(self._packet_infos)
1089 for x in fragment_rfc791(p.data, 400)]
1091 encapped_fragments = \
1092 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1093 IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1098 fragmented_encapped_fragments = \
1099 [x for p in encapped_fragments
1100 for x in fragment_rfc791(p, 200)]
1102 self.src_if.add_stream(fragmented_encapped_fragments)
1104 self.pg_enable_capture(self.pg_interfaces)
1107 self.src_if.assert_nothing_captured()
1108 packets = self.dst_if.get_capture(len(self._packet_infos))
1109 self.verify_capture(packets, IP)
1111 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1112 # so that it's query_vpp_config() works as it should
1113 self.gre4.remove_vpp_config()
1114 self.logger.debug(self.vapi.ppcli("show interface"))
1116 def test_fif6(self):
1117 """ Fragments in fragments (6o6) """
1118 # TODO this should be ideally in setUpClass, but then we hit a bug
1119 # with VppIpRoute incorrectly reporting it's present when it's not
1120 # so we need to manually remove the vpp config, thus we cannot have
1121 # it shared for multiple test cases
1122 self.tun_ip6 = "1002::1"
1124 self.gre6 = VppGre6Interface(self, self.src_if.local_ip6, self.tun_ip6)
1125 self.gre6.add_vpp_config()
1126 self.gre6.admin_up()
1127 self.gre6.config_ip6()
1129 self.vapi.ip_reassembly_enable_disable(
1130 sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1132 self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1133 [VppRoutePath(self.src_if.remote_ip6,
1134 self.src_if.sw_if_index,
1135 proto=DpoProto.DPO_PROTO_IP6)],
1137 self.route6.add_vpp_config()
1139 self.reset_packet_infos()
1140 for i in range(test_packet_count):
1141 info = self.create_packet_info(self.src_if, self.dst_if)
1142 payload = self.info_to_payload(info)
1143 # Ethernet header here is only for size calculation, thus it
1144 # doesn't matter how it's initialized. This is to ensure that
1145 # reassembled packet is not > 9000 bytes, so that it's not dropped
1147 IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1148 UDP(sport=1234, dport=5678) /
1150 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1151 self.extend_packet(p, size, self.padding)
1152 info.data = p[IPv6] # use only IPv6 part, without ethernet header
1154 fragments = [x for _, i in six.iteritems(self._packet_infos)
1155 for x in fragment_rfc8200(
1156 i.data, i.index, 400)]
1158 encapped_fragments = \
1159 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1160 IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1165 fragmented_encapped_fragments = \
1166 [x for p in encapped_fragments for x in (
1169 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1171 if IPv6ExtHdrFragment in p else [p]
1175 self.src_if.add_stream(fragmented_encapped_fragments)
1177 self.pg_enable_capture(self.pg_interfaces)
1180 self.src_if.assert_nothing_captured()
1181 packets = self.dst_if.get_capture(len(self._packet_infos))
1182 self.verify_capture(packets, IPv6)
1184 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1185 # so that it's query_vpp_config() works as it should
1186 self.gre6.remove_vpp_config()
1189 if __name__ == '__main__':
1190 unittest.main(testRunner=VppTestRunner)