3 from random import shuffle
7 from parameterized import parameterized
9 from scapy.packet import Raw
10 from scapy.layers.l2 import Ether, GRE
11 from scapy.layers.inet import IP, UDP, ICMP
13 from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, ICMPv6ParamProblem,\
16 from framework import VppTestCase, VppTestRunner
17 from util import ppp, fragment_rfc791, fragment_rfc8200
18 from vpp_gre_interface import VppGreInterface, VppGre6Interface
19 from vpp_ip import DpoProto
20 from vpp_ip_route import VppIpRoute, VppRoutePath
22 # 35 is enough to have >257 400-byte fragments
23 test_packet_count = 35
25 # <class 'scapy.layers.inet.IP'>
26 # <class 'scapy.layers.inet6.IPv6'>
27 _scapy_ip_family_types = (IP, IPv6)
30 def validate_scapy_ip_family(scapy_ip_family):
32 if scapy_ip_family not in _scapy_ip_family_types:
33 raise ValueError("'scapy_ip_family' must be of type: %s. Got %s" %
34 (_scapy_ip_family_types, scapy_ip_family))
37 class TestIPReassemblyMixin(object):
39 def verify_capture(self, scapy_ip_family, capture,
40 dropped_packet_indexes=None):
41 """Verify captured packet stream.
43 :param list capture: Captured packet stream.
45 validate_scapy_ip_family(scapy_ip_family)
47 if dropped_packet_indexes is None:
48 dropped_packet_indexes = []
51 for packet in capture:
53 self.logger.debug(ppp("Got packet:", packet))
54 ip = packet[scapy_ip_family]
56 payload_info = self.payload_to_info(packet[Raw])
57 packet_index = payload_info.index
59 packet_index not in dropped_packet_indexes,
60 ppp("Packet received, but should be dropped:", packet))
61 if packet_index in seen:
62 raise Exception(ppp("Duplicate packet received", packet))
63 seen.add(packet_index)
64 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
65 info = self._packet_infos[packet_index]
66 self.assertTrue(info is not None)
67 self.assertEqual(packet_index, info.index)
68 saved_packet = info.data
69 self.assertEqual(ip.src, saved_packet[scapy_ip_family].src)
70 self.assertEqual(ip.dst, saved_packet[scapy_ip_family].dst)
71 self.assertEqual(udp.payload, saved_packet[UDP].payload)
73 self.logger.error(ppp("Unexpected or invalid packet:", packet))
75 for index in self._packet_infos:
76 self.assertTrue(index in seen or index in dropped_packet_indexes,
77 "Packet with packet_index %d not received" % index)
79 def test_disabled(self, scapy_ip_family, stream,
80 dropped_packet_indexes):
81 """ reassembly disabled """
82 validate_scapy_ip_family(scapy_ip_family)
83 is_ip6 = 1 if scapy_ip_family == IPv6 else 0
85 self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
86 expire_walk_interval_ms=10000,
89 self.pg_enable_capture()
90 self.src_if.add_stream(stream)
93 packets = self.dst_if.get_capture(
94 len(self.pkt_infos) - len(dropped_packet_indexes))
95 self.verify_capture(scapy_ip_family, packets, dropped_packet_indexes)
96 self.src_if.assert_nothing_captured()
98 def test_duplicates(self, scapy_ip_family, stream):
99 """ duplicate fragments """
100 validate_scapy_ip_family(scapy_ip_family)
102 self.pg_enable_capture()
103 self.src_if.add_stream(stream)
106 packets = self.dst_if.get_capture(len(self.pkt_infos))
107 self.verify_capture(scapy_ip_family, packets)
108 self.src_if.assert_nothing_captured()
110 def test_random(self, scapy_ip_family, stream):
111 """ random order reassembly """
112 validate_scapy_ip_family(scapy_ip_family)
114 fragments = list(stream)
117 self.pg_enable_capture()
118 self.src_if.add_stream(fragments)
121 packets = self.dst_if.get_capture(len(self.packet_infos))
122 self.verify_capture(scapy_ip_family, packets)
123 self.src_if.assert_nothing_captured()
125 # run it all again to verify correctness
126 self.pg_enable_capture()
127 self.src_if.add_stream(fragments)
130 packets = self.dst_if.get_capture(len(self.packet_infos))
131 self.verify_capture(scapy_ip_family, packets)
132 self.src_if.assert_nothing_captured()
134 def test_reassembly(self, scapy_ip_family, stream):
135 """ basic reassembly """
136 validate_scapy_ip_family(scapy_ip_family)
138 self.pg_enable_capture()
139 self.src_if.add_stream(stream)
142 packets = self.dst_if.get_capture(len(self.pkt_infos))
143 self.verify_capture(scapy_ip_family, packets)
144 self.src_if.assert_nothing_captured()
146 # run it all again to verify correctness
147 self.pg_enable_capture()
148 self.src_if.add_stream(stream)
151 packets = self.dst_if.get_capture(len(self.pkt_infos))
152 self.verify_capture(scapy_ip_family, packets)
153 self.src_if.assert_nothing_captured()
155 def test_reversed(self, scapy_ip_family, stream):
156 """ reverse order reassembly """
157 validate_scapy_ip_family(scapy_ip_family)
159 fragments = list(stream)
162 self.pg_enable_capture()
163 self.src_if.add_stream(fragments)
166 packets = self.dst_if.get_capture(len(self.packet_infos))
167 self.verify_capture(scapy_ip_family, packets)
168 self.src_if.assert_nothing_captured()
170 # run it all again to verify correctness
171 self.pg_enable_capture()
172 self.src_if.add_stream(fragments)
175 packets = self.dst_if.get_capture(len(self.packet_infos))
176 self.verify_capture(scapy_ip_family, packets)
177 self.src_if.assert_nothing_captured()
179 def test_timeout_inline(self, scapy_ip_family, stream,
180 dropped_packet_indexes):
181 """ timeout (inline) """
182 validate_scapy_ip_family(scapy_ip_family)
183 is_ip6 = 1 if scapy_ip_family == IPv6 else 0
185 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
186 expire_walk_interval_ms=10000,
189 self.pg_enable_capture()
190 self.src_if.add_stream(stream)
193 packets = self.dst_if.get_capture(
194 len(self.pkt_infos) - len(dropped_packet_indexes))
195 self.verify_capture(scapy_ip_family, packets,
196 dropped_packet_indexes)
199 class TestIPv4Reassembly(TestIPReassemblyMixin, VppTestCase):
200 """ IPv4 Reassembly """
204 super(TestIPv4Reassembly, cls).setUpClass()
206 cls.create_pg_interfaces([0, 1])
210 # setup all interfaces
211 for i in cls.pg_interfaces:
217 cls.packet_sizes = [64, 512, 1518, 9018]
218 cls.padding = " abcdefghijklmn"
219 cls.create_stream(cls.packet_sizes)
220 cls.create_fragments()
223 def tearDownClass(cls):
224 super(TestIPv4Reassembly, cls).tearDownClass()
227 """ Test setup - force timeout on existing reassemblies """
228 super(TestIPv4Reassembly, self).setUp()
229 self.vapi.ip_reassembly_enable_disable(
230 sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
231 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
232 expire_walk_interval_ms=10)
234 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
235 expire_walk_interval_ms=10000)
238 super(TestIPv4Reassembly, self).tearDown()
239 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
240 self.logger.debug(self.vapi.ppcli("show buffers"))
243 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
244 """Create input packet stream
246 :param list packet_sizes: Required packet sizes.
248 for i in range(0, packet_count):
249 info = cls.create_packet_info(cls.src_if, cls.src_if)
250 payload = cls.info_to_payload(info)
251 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
252 IP(id=info.index, src=cls.src_if.remote_ip4,
253 dst=cls.dst_if.remote_ip4) /
254 UDP(sport=1234, dport=5678) /
256 size = packet_sizes[(i // 2) % len(packet_sizes)]
257 cls.extend_packet(p, size, cls.padding)
261 def create_fragments(cls):
262 infos = cls._packet_infos
264 for index, info in six.iteritems(infos):
266 # cls.logger.debug(ppp("Packet:",
267 # p.__class__(scapy.compat.raw(p))))
268 fragments_400 = fragment_rfc791(p, 400)
269 fragments_300 = fragment_rfc791(p, 300)
271 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
272 cls.pkt_infos.append(
273 (index, fragments_400, fragments_300, fragments_200))
274 cls.fragments_400 = [
275 x for (_, frags, _, _) in cls.pkt_infos for x in frags]
276 cls.fragments_300 = [
277 x for (_, _, frags, _) in cls.pkt_infos for x in frags]
278 cls.fragments_200 = [
279 x for (_, _, _, frags) in cls.pkt_infos for x in frags]
280 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
281 "%s 300-byte fragments and %s 200-byte fragments" %
282 (len(infos), len(cls.fragments_400),
283 len(cls.fragments_300), len(cls.fragments_200)))
285 @parameterized.expand([(IP, None)])
286 def test_reassembly(self, family, stream):
287 """ basic reassembly """
288 stream = self.__class__.fragments_200
289 super(TestIPv4Reassembly, self).test_reassembly(family, stream)
291 @parameterized.expand([(IP, None)])
292 def test_reversed(self, family, stream):
293 """ reverse order reassembly """
294 stream = self.__class__.fragments_200
295 super(TestIPv4Reassembly, self).test_reversed(family, stream)
297 @parameterized.expand([(IP, None)])
298 def test_random(self, family, stream):
299 stream = self.__class__.fragments_200
300 super(TestIPv4Reassembly, self).test_random(family, stream)
303 """ fragment length + ip header size > 65535 """
304 self.vapi.cli("clear errors")
305 raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
306 '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
307 'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
308 'fset; Test-case: 5737')
310 malformed_packet = (Ether(dst=self.src_if.local_mac,
311 src=self.src_if.remote_mac) /
313 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
314 IP(id=1000, src=self.src_if.remote_ip4,
315 dst=self.dst_if.remote_ip4) /
316 UDP(sport=1234, dport=5678) /
318 valid_fragments = fragment_rfc791(p, 400)
320 self.pg_enable_capture()
321 self.src_if.add_stream([malformed_packet] + valid_fragments)
324 self.dst_if.get_capture(1)
325 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
326 # TODO remove above, uncomment below once clearing of counters
328 # self.assert_packet_counter_equal(
329 # "/err/ip4-reassembly-feature/malformed packets", 1)
331 def test_44924(self):
332 """ compress tiny fragments """
333 packets = [(Ether(dst=self.src_if.local_mac,
334 src=self.src_if.remote_mac) /
335 IP(id=24339, flags="MF", frag=0, ttl=64,
336 src=self.src_if.remote_ip4,
337 dst=self.dst_if.remote_ip4) /
338 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
339 Raw(load='Test-group: IPv4')),
340 (Ether(dst=self.src_if.local_mac,
341 src=self.src_if.remote_mac) /
342 IP(id=24339, flags="MF", frag=3, ttl=64,
343 src=self.src_if.remote_ip4,
344 dst=self.dst_if.remote_ip4) /
345 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
346 Raw(load='.IPv4.Fragmentation.vali')),
347 (Ether(dst=self.src_if.local_mac,
348 src=self.src_if.remote_mac) /
349 IP(id=24339, frag=6, ttl=64,
350 src=self.src_if.remote_ip4,
351 dst=self.dst_if.remote_ip4) /
352 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
353 Raw(load='d; Test-case: 44924'))
356 self.pg_enable_capture()
357 self.src_if.add_stream(packets)
360 self.dst_if.get_capture(1)
362 def test_frag_1(self):
363 """ fragment of size 1 """
364 self.vapi.cli("clear errors")
365 malformed_packets = [(Ether(dst=self.src_if.local_mac,
366 src=self.src_if.remote_mac) /
367 IP(id=7, len=21, flags="MF", frag=0, ttl=64,
368 src=self.src_if.remote_ip4,
369 dst=self.dst_if.remote_ip4) /
370 ICMP(type="echo-request")),
371 (Ether(dst=self.src_if.local_mac,
372 src=self.src_if.remote_mac) /
373 IP(id=7, len=21, frag=1, ttl=64,
374 src=self.src_if.remote_ip4,
375 dst=self.dst_if.remote_ip4) /
379 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
380 IP(id=1000, src=self.src_if.remote_ip4,
381 dst=self.dst_if.remote_ip4) /
382 UDP(sport=1234, dport=5678) /
384 valid_fragments = fragment_rfc791(p, 400)
386 self.pg_enable_capture()
387 self.src_if.add_stream(malformed_packets + valid_fragments)
390 self.dst_if.get_capture(1)
392 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
393 # TODO remove above, uncomment below once clearing of counters
395 # self.assert_packet_counter_equal(
396 # "/err/ip4-reassembly-feature/malformed packets", 1)
398 @parameterized.expand([(IP, None)])
399 def test_duplicates(self, family, stream):
400 """ duplicate fragments """
402 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
403 x for (_, frags, _, _) in self.pkt_infos
405 for _ in range(0, min(2, len(frags)))
407 super(TestIPv4Reassembly, self).test_duplicates(family, fragments)
409 def test_overlap1(self):
410 """ overlapping fragments case #1 """
413 for _, _, frags_300, frags_200 in self.pkt_infos:
414 if len(frags_300) == 1:
415 fragments.extend(frags_300)
417 for i, j in zip(frags_200, frags_300):
421 self.pg_enable_capture()
422 self.src_if.add_stream(fragments)
425 packets = self.dst_if.get_capture(len(self.pkt_infos))
426 self.verify_capture(IP, packets)
427 self.src_if.assert_nothing_captured()
429 # run it all to verify correctness
430 self.pg_enable_capture()
431 self.src_if.add_stream(fragments)
434 packets = self.dst_if.get_capture(len(self.pkt_infos))
435 self.verify_capture(IP, packets)
436 self.src_if.assert_nothing_captured()
438 def test_overlap2(self):
439 """ overlapping fragments case #2 """
442 for _, _, frags_300, frags_200 in self.pkt_infos:
443 if len(frags_300) == 1:
444 fragments.extend(frags_300)
446 # care must be taken here so that there are no fragments
447 # received by vpp after reassembly is finished, otherwise
448 # new reassemblies will be started and packet generator will
449 # freak out when it detects unfreed buffers
450 zipped = zip(frags_300, frags_200)
451 for i, j in zipped[:-1]:
454 fragments.append(zipped[-1][0])
456 self.pg_enable_capture()
457 self.src_if.add_stream(fragments)
460 packets = self.dst_if.get_capture(len(self.pkt_infos))
461 self.verify_capture(IP, packets)
462 self.src_if.assert_nothing_captured()
464 # run it all to verify correctness
465 self.pg_enable_capture()
466 self.src_if.add_stream(fragments)
469 packets = self.dst_if.get_capture(len(self.pkt_infos))
470 self.verify_capture(IP, packets)
471 self.src_if.assert_nothing_captured()
473 @parameterized.expand([(IP, None, None)])
474 def test_timeout_inline(self, family, stream, dropped_packet_indexes):
475 """ timeout (inline) """
476 stream = self.fragments_400
478 dropped_packet_indexes = set(
479 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
481 super(TestIPv4Reassembly, self).test_timeout_inline(
482 family, stream, dropped_packet_indexes)
484 self.src_if.assert_nothing_captured()
486 def test_timeout_cleanup(self):
487 """ timeout (cleanup) """
489 # whole packets + fragmented packets sans last fragment
491 x for (_, frags_400, _, _) in self.pkt_infos
492 for x in frags_400[:-1 if len(frags_400) > 1 else None]
495 # last fragments for fragmented packets
496 fragments2 = [frags_400[-1]
497 for (_, frags_400, _, _) in self.pkt_infos
498 if len(frags_400) > 1]
500 dropped_packet_indexes = set(
501 index for (index, frags_400, _, _) in self.pkt_infos
502 if len(frags_400) > 1)
504 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
505 expire_walk_interval_ms=50)
507 self.pg_enable_capture()
508 self.src_if.add_stream(fragments)
511 self.sleep(.25, "wait before sending rest of fragments")
513 self.src_if.add_stream(fragments2)
516 packets = self.dst_if.get_capture(
517 len(self.pkt_infos) - len(dropped_packet_indexes))
518 self.verify_capture(IP, packets, dropped_packet_indexes)
519 self.src_if.assert_nothing_captured()
521 @parameterized.expand([(IP, None, None)])
522 def test_disabled(self, family, stream, dropped_packet_indexes):
523 """ reassembly disabled """
525 stream = self.__class__.fragments_400
526 dropped_packet_indexes = set(
527 index for (index, frags_400, _, _) in self.pkt_infos
528 if len(frags_400) > 1)
529 super(TestIPv4Reassembly, self).test_disabled(
530 family, stream, dropped_packet_indexes)
533 class TestIPv6Reassembly(TestIPReassemblyMixin, VppTestCase):
534 """ IPv6 Reassembly """
538 super(TestIPv6Reassembly, cls).setUpClass()
540 cls.create_pg_interfaces([0, 1])
544 # setup all interfaces
545 for i in cls.pg_interfaces:
551 cls.packet_sizes = [64, 512, 1518, 9018]
552 cls.padding = " abcdefghijklmn"
553 cls.create_stream(cls.packet_sizes)
554 cls.create_fragments()
557 def tearDownClass(cls):
558 super(TestIPv6Reassembly, cls).tearDownClass()
561 """ Test setup - force timeout on existing reassemblies """
562 super(TestIPv6Reassembly, self).setUp()
563 self.vapi.ip_reassembly_enable_disable(
564 sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
565 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
566 expire_walk_interval_ms=10, is_ip6=1)
568 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
569 expire_walk_interval_ms=10000, is_ip6=1)
570 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
571 self.logger.debug(self.vapi.ppcli("show buffers"))
574 super(TestIPv6Reassembly, self).tearDown()
575 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
576 self.logger.debug(self.vapi.ppcli("show buffers"))
579 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
580 """Create input packet stream for defined interface.
582 :param list packet_sizes: Required packet sizes.
584 for i in range(0, packet_count):
585 info = cls.create_packet_info(cls.src_if, cls.src_if)
586 payload = cls.info_to_payload(info)
587 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
588 IPv6(src=cls.src_if.remote_ip6,
589 dst=cls.dst_if.remote_ip6) /
590 UDP(sport=1234, dport=5678) /
592 size = packet_sizes[(i // 2) % len(packet_sizes)]
593 cls.extend_packet(p, size, cls.padding)
597 def create_fragments(cls):
598 infos = cls._packet_infos
600 for index, info in six.iteritems(infos):
602 # cls.logger.debug(ppp("Packet:",
603 # p.__class__(scapy.compat.raw(p))))
604 fragments_400 = fragment_rfc8200(p, info.index, 400)
605 fragments_300 = fragment_rfc8200(p, info.index, 300)
606 cls.pkt_infos.append((index, fragments_400, fragments_300))
607 cls.fragments_400 = [
608 x for _, frags, _ in cls.pkt_infos for x in frags]
609 cls.fragments_300 = [
610 x for _, _, frags in cls.pkt_infos for x in frags]
611 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
612 "and %s 300-byte fragments" %
613 (len(infos), len(cls.fragments_400),
614 len(cls.fragments_300)))
616 @parameterized.expand([(IPv6, None)])
617 def test_reassembly(self, family, stream):
618 """ basic reassembly """
619 stream = self.__class__.fragments_400
620 super(TestIPv6Reassembly, self).test_reassembly(family, stream)
622 @parameterized.expand([(IPv6, None)])
623 def test_reversed(self, family, stream):
624 """ reverse order reassembly """
625 stream = self.__class__.fragments_400
626 super(TestIPv6Reassembly, self).test_reversed(family, stream)
628 @parameterized.expand([(IPv6, None)])
629 def test_random(self, family, stream):
630 """ random order reassembly """
631 stream = self.__class__.fragments_400
632 super(TestIPv6Reassembly, self).test_random(family, stream)
634 @parameterized.expand([(IPv6, None)])
635 def test_duplicates(self, family, stream):
636 """ duplicate fragments """
639 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
640 x for (_, frags, _) in self.pkt_infos
642 for _ in range(0, min(2, len(frags)))
644 super(TestIPv6Reassembly, self).test_duplicates(family, fragments)
646 def test_overlap1(self):
647 """ overlapping fragments case #1 (differs from IP test case)"""
650 for _, frags_400, frags_300 in self.pkt_infos:
651 if len(frags_300) == 1:
652 fragments.extend(frags_400)
654 for i, j in zip(frags_300, frags_400):
658 dropped_packet_indexes = set(
659 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
662 self.pg_enable_capture()
663 self.src_if.add_stream(fragments)
666 packets = self.dst_if.get_capture(
667 len(self.pkt_infos) - len(dropped_packet_indexes))
668 self.verify_capture(IPv6, packets, dropped_packet_indexes)
669 self.src_if.assert_nothing_captured()
671 def test_overlap2(self):
672 """ overlapping fragments case #2 (differs from IP test case)"""
675 for _, frags_400, frags_300 in self.pkt_infos:
676 if len(frags_400) == 1:
677 fragments.extend(frags_400)
679 # care must be taken here so that there are no fragments
680 # received by vpp after reassembly is finished, otherwise
681 # new reassemblies will be started and packet generator will
682 # freak out when it detects unfreed buffers
683 zipped = zip(frags_400, frags_300)
684 for i, j in zipped[:-1]:
687 fragments.append(zipped[-1][0])
689 dropped_packet_indexes = set(
690 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
693 self.pg_enable_capture()
694 self.src_if.add_stream(fragments)
697 packets = self.dst_if.get_capture(
698 len(self.pkt_infos) - len(dropped_packet_indexes))
699 self.verify_capture(IPv6, packets, dropped_packet_indexes)
700 self.src_if.assert_nothing_captured()
702 @parameterized.expand([(IPv6, None, None)])
703 def test_timeout_inline(self, family, stream, dropped_packets_index):
704 """ timeout (inline) """
705 stream = self.__class__.fragments_400
707 dropped_packet_indexes = set(
708 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
710 super(TestIPv6Reassembly, self).test_timeout_inline(
711 family, stream, dropped_packet_indexes)
713 pkts = self.src_if.get_capture(
714 expected_count=len(dropped_packet_indexes))
716 self.assertIn(ICMPv6TimeExceeded, icmp)
717 self.assertIn(IPv6ExtHdrFragment, icmp)
718 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
719 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
721 def test_timeout_cleanup(self):
722 """ timeout (cleanup) """
724 # whole packets + fragmented packets sans last fragment
726 x for (_, frags_400, _) in self.pkt_infos
727 for x in frags_400[:-1 if len(frags_400) > 1 else None]
730 # last fragments for fragmented packets
731 fragments2 = [frags_400[-1]
732 for (_, frags_400, _) in self.pkt_infos
733 if len(frags_400) > 1]
735 dropped_packet_indexes = set(
736 index for (index, frags_400, _) in self.pkt_infos
737 if len(frags_400) > 1)
739 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
740 expire_walk_interval_ms=50)
742 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
743 expire_walk_interval_ms=50, is_ip6=1)
745 self.pg_enable_capture()
746 self.src_if.add_stream(fragments)
749 self.sleep(.25, "wait before sending rest of fragments")
751 self.src_if.add_stream(fragments2)
754 packets = self.dst_if.get_capture(
755 len(self.pkt_infos) - len(dropped_packet_indexes))
756 self.verify_capture(IPv6, packets, dropped_packet_indexes)
757 pkts = self.src_if.get_capture(
758 expected_count=len(dropped_packet_indexes))
760 self.assertIn(ICMPv6TimeExceeded, icmp)
761 self.assertIn(IPv6ExtHdrFragment, icmp)
762 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
763 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
765 @parameterized.expand([(IPv6, None, None)])
766 def test_disabled(self, family, stream, dropped_packet_indexes):
767 """ reassembly disabled """
769 stream = self.__class__.fragments_400
770 dropped_packet_indexes = set(
771 index for (index, frags_400, _) in self.pkt_infos
772 if len(frags_400) > 1)
773 super(TestIPv6Reassembly, self).test_disabled(
774 family, stream, dropped_packet_indexes)
775 self.src_if.assert_nothing_captured()
777 def test_missing_upper(self):
778 """ missing upper layer """
779 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
780 IPv6(src=self.src_if.remote_ip6,
781 dst=self.src_if.local_ip6) /
782 UDP(sport=1234, dport=5678) /
784 self.extend_packet(p, 1000, self.padding)
785 fragments = fragment_rfc8200(p, 1, 500)
786 bad_fragment = p.__class__(scapy.compat.raw(fragments[1]))
787 bad_fragment[IPv6ExtHdrFragment].nh = 59
788 bad_fragment[IPv6ExtHdrFragment].offset = 0
789 self.pg_enable_capture()
790 self.src_if.add_stream([bad_fragment])
792 pkts = self.src_if.get_capture(expected_count=1)
794 self.assertIn(ICMPv6ParamProblem, icmp)
795 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
797 def test_invalid_frag_size(self):
798 """ fragment size not a multiple of 8 """
799 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
800 IPv6(src=self.src_if.remote_ip6,
801 dst=self.src_if.local_ip6) /
802 UDP(sport=1234, dport=5678) /
804 self.extend_packet(p, 1000, self.padding)
805 fragments = fragment_rfc8200(p, 1, 500)
806 bad_fragment = fragments[0]
807 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
808 self.pg_enable_capture()
809 self.src_if.add_stream([bad_fragment])
811 pkts = self.src_if.get_capture(expected_count=1)
813 self.assertIn(ICMPv6ParamProblem, icmp)
814 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
816 def test_invalid_packet_size(self):
817 """ total packet size > 65535 """
818 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
819 IPv6(src=self.src_if.remote_ip6,
820 dst=self.src_if.local_ip6) /
821 UDP(sport=1234, dport=5678) /
823 self.extend_packet(p, 1000, self.padding)
824 fragments = fragment_rfc8200(p, 1, 500)
825 bad_fragment = fragments[1]
826 bad_fragment[IPv6ExtHdrFragment].offset = 65500
827 self.pg_enable_capture()
828 self.src_if.add_stream([bad_fragment])
830 pkts = self.src_if.get_capture(expected_count=1)
832 self.assertIn(ICMPv6ParamProblem, icmp)
833 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
836 class TestIPv4ReassemblyLocalNode(VppTestCase):
837 """ IPv4 Reassembly for packets coming to ip4-local node """
841 super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
843 cls.create_pg_interfaces([0])
844 cls.src_dst_if = cls.pg0
846 # setup all interfaces
847 for i in cls.pg_interfaces:
852 cls.padding = " abcdefghijklmn"
854 cls.create_fragments()
857 def tearDownClass(cls):
858 super(TestIPv4ReassemblyLocalNode, cls).tearDownClass()
861 """ Test setup - force timeout on existing reassemblies """
862 super(TestIPv4ReassemblyLocalNode, self).setUp()
863 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
864 expire_walk_interval_ms=10)
866 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
867 expire_walk_interval_ms=10000)
870 super(TestIPv4ReassemblyLocalNode, self).tearDown()
871 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
872 self.logger.debug(self.vapi.ppcli("show buffers"))
875 def create_stream(cls, packet_count=test_packet_count):
876 """Create input packet stream for defined interface.
878 :param list packet_sizes: Required packet sizes.
880 for i in range(0, packet_count):
881 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
882 payload = cls.info_to_payload(info)
883 p = (Ether(dst=cls.src_dst_if.local_mac,
884 src=cls.src_dst_if.remote_mac) /
885 IP(id=info.index, src=cls.src_dst_if.remote_ip4,
886 dst=cls.src_dst_if.local_ip4) /
887 ICMP(type='echo-request', id=1234) /
889 cls.extend_packet(p, 1518, cls.padding)
893 def create_fragments(cls):
894 infos = cls._packet_infos
896 for index, info in six.iteritems(infos):
898 # cls.logger.debug(ppp("Packet:",
899 # p.__class__(scapy.compat.raw(p))))
900 fragments_300 = fragment_rfc791(p, 300)
901 cls.pkt_infos.append((index, fragments_300))
902 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
903 cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
904 (len(infos), len(cls.fragments_300)))
906 def verify_capture(self, capture):
907 """Verify captured packet stream.
909 :param list capture: Captured packet stream.
913 for packet in capture:
915 self.logger.debug(ppp("Got packet:", packet))
918 payload_info = self.payload_to_info(packet[Raw])
919 packet_index = payload_info.index
920 if packet_index in seen:
921 raise Exception(ppp("Duplicate packet received", packet))
922 seen.add(packet_index)
923 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
924 info = self._packet_infos[packet_index]
925 self.assertIsNotNone(info)
926 self.assertEqual(packet_index, info.index)
927 saved_packet = info.data
928 self.assertEqual(ip.src, saved_packet[IP].dst)
929 self.assertEqual(ip.dst, saved_packet[IP].src)
930 self.assertEqual(icmp.type, 0) # echo reply
931 self.assertEqual(icmp.id, saved_packet[ICMP].id)
932 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
934 self.logger.error(ppp("Unexpected or invalid packet:", packet))
936 for index in self._packet_infos:
937 self.assertIn(index, seen,
938 "Packet with packet_index %d not received" % index)
940 def test_reassembly(self):
941 """ basic reassembly """
943 self.pg_enable_capture()
944 self.src_dst_if.add_stream(self.fragments_300)
947 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
948 self.verify_capture(packets)
950 # run it all again to verify correctness
951 self.pg_enable_capture()
952 self.src_dst_if.add_stream(self.fragments_300)
955 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
956 self.verify_capture(packets)
959 class TestFIFReassembly(VppTestCase):
960 """ Fragments in fragments reassembly """
964 super(TestFIFReassembly, cls).setUpClass()
966 cls.create_pg_interfaces([0, 1])
969 for i in cls.pg_interfaces:
976 cls.packet_sizes = [64, 512, 1518, 9018]
977 cls.padding = " abcdefghijklmn"
980 def tearDownClass(cls):
981 super(TestFIFReassembly, cls).tearDownClass()
984 """ Test setup - force timeout on existing reassemblies """
985 super(TestFIFReassembly, self).setUp()
986 self.vapi.ip_reassembly_enable_disable(
987 sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
989 self.vapi.ip_reassembly_enable_disable(
990 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
992 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
993 expire_walk_interval_ms=10)
994 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
995 expire_walk_interval_ms=10, is_ip6=1)
997 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
998 expire_walk_interval_ms=10000)
999 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1000 expire_walk_interval_ms=10000, is_ip6=1)
1003 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
1004 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
1005 self.logger.debug(self.vapi.ppcli("show buffers"))
1006 super(TestFIFReassembly, self).tearDown()
1008 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
1009 """Verify captured packet stream.
1011 :param list capture: Captured packet stream.
1015 for packet in capture:
1017 self.logger.debug(ppp("Got packet:", packet))
1018 ip = packet[ip_class]
1020 payload_info = self.payload_to_info(packet[Raw])
1021 packet_index = payload_info.index
1023 packet_index not in dropped_packet_indexes,
1024 ppp("Packet received, but should be dropped:", packet))
1025 if packet_index in seen:
1026 raise Exception(ppp("Duplicate packet received", packet))
1027 seen.add(packet_index)
1028 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1029 info = self._packet_infos[packet_index]
1030 self.assertTrue(info is not None)
1031 self.assertEqual(packet_index, info.index)
1032 saved_packet = info.data
1033 self.assertEqual(ip.src, saved_packet[ip_class].src)
1034 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1035 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1037 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1039 for index in self._packet_infos:
1040 self.assertTrue(index in seen or index in dropped_packet_indexes,
1041 "Packet with packet_index %d not received" % index)
1043 def test_fif4(self):
1044 """ Fragments in fragments (4o4) """
1046 # TODO this should be ideally in setUpClass, but then we hit a bug
1047 # with VppIpRoute incorrectly reporting it's present when it's not
1048 # so we need to manually remove the vpp config, thus we cannot have
1049 # it shared for multiple test cases
1050 self.tun_ip4 = "1.1.1.2"
1052 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1053 self.gre4.add_vpp_config()
1054 self.gre4.admin_up()
1055 self.gre4.config_ip4()
1057 self.vapi.ip_reassembly_enable_disable(
1058 sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1060 self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1061 [VppRoutePath(self.src_if.remote_ip4,
1062 self.src_if.sw_if_index)])
1063 self.route4.add_vpp_config()
1065 self.reset_packet_infos()
1066 for i in range(test_packet_count):
1067 info = self.create_packet_info(self.src_if, self.dst_if)
1068 payload = self.info_to_payload(info)
1069 # Ethernet header here is only for size calculation, thus it
1070 # doesn't matter how it's initialized. This is to ensure that
1071 # reassembled packet is not > 9000 bytes, so that it's not dropped
1073 IP(id=i, src=self.src_if.remote_ip4,
1074 dst=self.dst_if.remote_ip4) /
1075 UDP(sport=1234, dport=5678) /
1077 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1078 self.extend_packet(p, size, self.padding)
1079 info.data = p[IP] # use only IP part, without ethernet header
1081 fragments = [x for _, p in six.iteritems(self._packet_infos)
1082 for x in fragment_rfc791(p.data, 400)]
1084 encapped_fragments = \
1085 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1086 IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1091 fragmented_encapped_fragments = \
1092 [x for p in encapped_fragments
1093 for x in fragment_rfc791(p, 200)]
1095 self.src_if.add_stream(fragmented_encapped_fragments)
1097 self.pg_enable_capture(self.pg_interfaces)
1100 self.src_if.assert_nothing_captured()
1101 packets = self.dst_if.get_capture(len(self._packet_infos))
1102 self.verify_capture(packets, IP)
1104 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1105 # so that it's query_vpp_config() works as it should
1106 self.gre4.remove_vpp_config()
1107 self.logger.debug(self.vapi.ppcli("show interface"))
1109 def test_fif6(self):
1110 """ Fragments in fragments (6o6) """
1111 # TODO this should be ideally in setUpClass, but then we hit a bug
1112 # with VppIpRoute incorrectly reporting it's present when it's not
1113 # so we need to manually remove the vpp config, thus we cannot have
1114 # it shared for multiple test cases
1115 self.tun_ip6 = "1002::1"
1117 self.gre6 = VppGre6Interface(self, self.src_if.local_ip6, self.tun_ip6)
1118 self.gre6.add_vpp_config()
1119 self.gre6.admin_up()
1120 self.gre6.config_ip6()
1122 self.vapi.ip_reassembly_enable_disable(
1123 sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1125 self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1126 [VppRoutePath(self.src_if.remote_ip6,
1127 self.src_if.sw_if_index,
1128 proto=DpoProto.DPO_PROTO_IP6)],
1130 self.route6.add_vpp_config()
1132 self.reset_packet_infos()
1133 for i in range(test_packet_count):
1134 info = self.create_packet_info(self.src_if, self.dst_if)
1135 payload = self.info_to_payload(info)
1136 # Ethernet header here is only for size calculation, thus it
1137 # doesn't matter how it's initialized. This is to ensure that
1138 # reassembled packet is not > 9000 bytes, so that it's not dropped
1140 IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1141 UDP(sport=1234, dport=5678) /
1143 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1144 self.extend_packet(p, size, self.padding)
1145 info.data = p[IPv6] # use only IPv6 part, without ethernet header
1147 fragments = [x for _, i in six.iteritems(self._packet_infos)
1148 for x in fragment_rfc8200(
1149 i.data, i.index, 400)]
1151 encapped_fragments = \
1152 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1153 IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1158 fragmented_encapped_fragments = \
1159 [x for p in encapped_fragments for x in (
1162 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1164 if IPv6ExtHdrFragment in p else [p]
1168 self.src_if.add_stream(fragmented_encapped_fragments)
1170 self.pg_enable_capture(self.pg_interfaces)
1173 self.src_if.assert_nothing_captured()
1174 packets = self.dst_if.get_capture(len(self._packet_infos))
1175 self.verify_capture(packets, IPv6)
1177 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1178 # so that it's query_vpp_config() works as it should
1179 self.gre6.remove_vpp_config()
1182 if __name__ == '__main__':
1183 unittest.main(testRunner=VppTestRunner)