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()
240 def show_commands_at_teardown(self):
241 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
242 self.logger.debug(self.vapi.ppcli("show buffers"))
245 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
246 """Create input packet stream
248 :param list packet_sizes: Required packet sizes.
250 for i in range(0, packet_count):
251 info = cls.create_packet_info(cls.src_if, cls.src_if)
252 payload = cls.info_to_payload(info)
253 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
254 IP(id=info.index, src=cls.src_if.remote_ip4,
255 dst=cls.dst_if.remote_ip4) /
256 UDP(sport=1234, dport=5678) /
258 size = packet_sizes[(i // 2) % len(packet_sizes)]
259 cls.extend_packet(p, size, cls.padding)
263 def create_fragments(cls):
264 infos = cls._packet_infos
266 for index, info in six.iteritems(infos):
268 # cls.logger.debug(ppp("Packet:",
269 # p.__class__(scapy.compat.raw(p))))
270 fragments_400 = fragment_rfc791(p, 400)
271 fragments_300 = fragment_rfc791(p, 300)
273 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
274 cls.pkt_infos.append(
275 (index, fragments_400, fragments_300, fragments_200))
276 cls.fragments_400 = [
277 x for (_, frags, _, _) in cls.pkt_infos for x in frags]
278 cls.fragments_300 = [
279 x for (_, _, frags, _) in cls.pkt_infos for x in frags]
280 cls.fragments_200 = [
281 x for (_, _, _, frags) in cls.pkt_infos for x in frags]
282 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
283 "%s 300-byte fragments and %s 200-byte fragments" %
284 (len(infos), len(cls.fragments_400),
285 len(cls.fragments_300), len(cls.fragments_200)))
287 @parameterized.expand([(IP, None)])
288 def test_reassembly(self, family, stream):
289 """ basic reassembly """
290 stream = self.__class__.fragments_200
291 super(TestIPv4Reassembly, self).test_reassembly(family, stream)
293 @parameterized.expand([(IP, None)])
294 def test_reversed(self, family, stream):
295 """ reverse order reassembly """
296 stream = self.__class__.fragments_200
297 super(TestIPv4Reassembly, self).test_reversed(family, stream)
299 @parameterized.expand([(IP, None)])
300 def test_random(self, family, stream):
301 stream = self.__class__.fragments_200
302 super(TestIPv4Reassembly, self).test_random(family, stream)
305 """ fragment length + ip header size > 65535 """
306 self.vapi.cli("clear errors")
307 raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
308 '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
309 'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
310 'fset; Test-case: 5737')
312 malformed_packet = (Ether(dst=self.src_if.local_mac,
313 src=self.src_if.remote_mac) /
315 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
316 IP(id=1000, src=self.src_if.remote_ip4,
317 dst=self.dst_if.remote_ip4) /
318 UDP(sport=1234, dport=5678) /
320 valid_fragments = fragment_rfc791(p, 400)
322 self.pg_enable_capture()
323 self.src_if.add_stream([malformed_packet] + valid_fragments)
326 self.dst_if.get_capture(1)
327 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
328 # TODO remove above, uncomment below once clearing of counters
330 # self.assert_packet_counter_equal(
331 # "/err/ip4-reassembly-feature/malformed packets", 1)
333 def test_44924(self):
334 """ compress tiny fragments """
335 packets = [(Ether(dst=self.src_if.local_mac,
336 src=self.src_if.remote_mac) /
337 IP(id=24339, flags="MF", frag=0, ttl=64,
338 src=self.src_if.remote_ip4,
339 dst=self.dst_if.remote_ip4) /
340 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
341 Raw(load='Test-group: IPv4')),
342 (Ether(dst=self.src_if.local_mac,
343 src=self.src_if.remote_mac) /
344 IP(id=24339, flags="MF", frag=3, ttl=64,
345 src=self.src_if.remote_ip4,
346 dst=self.dst_if.remote_ip4) /
347 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
348 Raw(load='.IPv4.Fragmentation.vali')),
349 (Ether(dst=self.src_if.local_mac,
350 src=self.src_if.remote_mac) /
351 IP(id=24339, frag=6, ttl=64,
352 src=self.src_if.remote_ip4,
353 dst=self.dst_if.remote_ip4) /
354 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
355 Raw(load='d; Test-case: 44924'))
358 self.pg_enable_capture()
359 self.src_if.add_stream(packets)
362 self.dst_if.get_capture(1)
364 def test_frag_1(self):
365 """ fragment of size 1 """
366 self.vapi.cli("clear errors")
367 malformed_packets = [(Ether(dst=self.src_if.local_mac,
368 src=self.src_if.remote_mac) /
369 IP(id=7, len=21, flags="MF", frag=0, ttl=64,
370 src=self.src_if.remote_ip4,
371 dst=self.dst_if.remote_ip4) /
372 ICMP(type="echo-request")),
373 (Ether(dst=self.src_if.local_mac,
374 src=self.src_if.remote_mac) /
375 IP(id=7, len=21, frag=1, ttl=64,
376 src=self.src_if.remote_ip4,
377 dst=self.dst_if.remote_ip4) /
381 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
382 IP(id=1000, src=self.src_if.remote_ip4,
383 dst=self.dst_if.remote_ip4) /
384 UDP(sport=1234, dport=5678) /
386 valid_fragments = fragment_rfc791(p, 400)
388 self.pg_enable_capture()
389 self.src_if.add_stream(malformed_packets + valid_fragments)
392 self.dst_if.get_capture(1)
394 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
395 # TODO remove above, uncomment below once clearing of counters
397 # self.assert_packet_counter_equal(
398 # "/err/ip4-reassembly-feature/malformed packets", 1)
400 @parameterized.expand([(IP, None)])
401 def test_duplicates(self, family, stream):
402 """ duplicate fragments """
404 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
405 x for (_, frags, _, _) in self.pkt_infos
407 for _ in range(0, min(2, len(frags)))
409 super(TestIPv4Reassembly, self).test_duplicates(family, fragments)
411 def test_overlap1(self):
412 """ overlapping fragments case #1 """
415 for _, _, frags_300, frags_200 in self.pkt_infos:
416 if len(frags_300) == 1:
417 fragments.extend(frags_300)
419 for i, j in zip(frags_200, frags_300):
423 self.pg_enable_capture()
424 self.src_if.add_stream(fragments)
427 packets = self.dst_if.get_capture(len(self.pkt_infos))
428 self.verify_capture(IP, packets)
429 self.src_if.assert_nothing_captured()
431 # run it all to verify correctness
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(IP, packets)
438 self.src_if.assert_nothing_captured()
440 def test_overlap2(self):
441 """ overlapping fragments case #2 """
444 for _, _, frags_300, frags_200 in self.pkt_infos:
445 if len(frags_300) == 1:
446 fragments.extend(frags_300)
448 # care must be taken here so that there are no fragments
449 # received by vpp after reassembly is finished, otherwise
450 # new reassemblies will be started and packet generator will
451 # freak out when it detects unfreed buffers
452 zipped = zip(frags_300, frags_200)
453 for i, j in zipped[:-1]:
456 fragments.append(zipped[-1][0])
458 self.pg_enable_capture()
459 self.src_if.add_stream(fragments)
462 packets = self.dst_if.get_capture(len(self.pkt_infos))
463 self.verify_capture(IP, packets)
464 self.src_if.assert_nothing_captured()
466 # run it all to verify correctness
467 self.pg_enable_capture()
468 self.src_if.add_stream(fragments)
471 packets = self.dst_if.get_capture(len(self.pkt_infos))
472 self.verify_capture(IP, packets)
473 self.src_if.assert_nothing_captured()
475 @parameterized.expand([(IP, None, None)])
476 def test_timeout_inline(self, family, stream, dropped_packet_indexes):
477 """ timeout (inline) """
478 stream = self.fragments_400
480 dropped_packet_indexes = set(
481 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
483 super(TestIPv4Reassembly, self).test_timeout_inline(
484 family, stream, dropped_packet_indexes)
486 self.src_if.assert_nothing_captured()
488 def test_timeout_cleanup(self):
489 """ timeout (cleanup) """
491 # whole packets + fragmented packets sans last fragment
493 x for (_, frags_400, _, _) in self.pkt_infos
494 for x in frags_400[:-1 if len(frags_400) > 1 else None]
497 # last fragments for fragmented packets
498 fragments2 = [frags_400[-1]
499 for (_, frags_400, _, _) in self.pkt_infos
500 if len(frags_400) > 1]
502 dropped_packet_indexes = set(
503 index for (index, frags_400, _, _) in self.pkt_infos
504 if len(frags_400) > 1)
506 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
507 expire_walk_interval_ms=50)
509 self.pg_enable_capture()
510 self.src_if.add_stream(fragments)
513 self.sleep(.25, "wait before sending rest of fragments")
515 self.src_if.add_stream(fragments2)
518 packets = self.dst_if.get_capture(
519 len(self.pkt_infos) - len(dropped_packet_indexes))
520 self.verify_capture(IP, packets, dropped_packet_indexes)
521 self.src_if.assert_nothing_captured()
523 @parameterized.expand([(IP, None, None)])
524 def test_disabled(self, family, stream, dropped_packet_indexes):
525 """ reassembly disabled """
527 stream = self.__class__.fragments_400
528 dropped_packet_indexes = set(
529 index for (index, frags_400, _, _) in self.pkt_infos
530 if len(frags_400) > 1)
531 super(TestIPv4Reassembly, self).test_disabled(
532 family, stream, dropped_packet_indexes)
535 class TestIPv6Reassembly(TestIPReassemblyMixin, VppTestCase):
536 """ IPv6 Reassembly """
540 super(TestIPv6Reassembly, cls).setUpClass()
542 cls.create_pg_interfaces([0, 1])
546 # setup all interfaces
547 for i in cls.pg_interfaces:
553 cls.packet_sizes = [64, 512, 1518, 9018]
554 cls.padding = " abcdefghijklmn"
555 cls.create_stream(cls.packet_sizes)
556 cls.create_fragments()
559 def tearDownClass(cls):
560 super(TestIPv6Reassembly, cls).tearDownClass()
563 """ Test setup - force timeout on existing reassemblies """
564 super(TestIPv6Reassembly, self).setUp()
565 self.vapi.ip_reassembly_enable_disable(
566 sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
567 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
568 expire_walk_interval_ms=10, is_ip6=1)
570 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
571 expire_walk_interval_ms=10000, is_ip6=1)
572 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
573 self.logger.debug(self.vapi.ppcli("show buffers"))
576 super(TestIPv6Reassembly, self).tearDown()
578 def show_commands_at_teardown(self):
579 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
580 self.logger.debug(self.vapi.ppcli("show buffers"))
583 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
584 """Create input packet stream for defined interface.
586 :param list packet_sizes: Required packet sizes.
588 for i in range(0, packet_count):
589 info = cls.create_packet_info(cls.src_if, cls.src_if)
590 payload = cls.info_to_payload(info)
591 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
592 IPv6(src=cls.src_if.remote_ip6,
593 dst=cls.dst_if.remote_ip6) /
594 UDP(sport=1234, dport=5678) /
596 size = packet_sizes[(i // 2) % len(packet_sizes)]
597 cls.extend_packet(p, size, cls.padding)
601 def create_fragments(cls):
602 infos = cls._packet_infos
604 for index, info in six.iteritems(infos):
606 # cls.logger.debug(ppp("Packet:",
607 # p.__class__(scapy.compat.raw(p))))
608 fragments_400 = fragment_rfc8200(p, info.index, 400)
609 fragments_300 = fragment_rfc8200(p, info.index, 300)
610 cls.pkt_infos.append((index, fragments_400, fragments_300))
611 cls.fragments_400 = [
612 x for _, frags, _ in cls.pkt_infos for x in frags]
613 cls.fragments_300 = [
614 x for _, _, frags in cls.pkt_infos for x in frags]
615 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
616 "and %s 300-byte fragments" %
617 (len(infos), len(cls.fragments_400),
618 len(cls.fragments_300)))
620 @parameterized.expand([(IPv6, None)])
621 def test_reassembly(self, family, stream):
622 """ basic reassembly """
623 stream = self.__class__.fragments_400
624 super(TestIPv6Reassembly, self).test_reassembly(family, stream)
626 @parameterized.expand([(IPv6, None)])
627 def test_reversed(self, family, stream):
628 """ reverse order reassembly """
629 stream = self.__class__.fragments_400
630 super(TestIPv6Reassembly, self).test_reversed(family, stream)
632 @parameterized.expand([(IPv6, None)])
633 def test_random(self, family, stream):
634 """ random order reassembly """
635 stream = self.__class__.fragments_400
636 super(TestIPv6Reassembly, self).test_random(family, stream)
638 @parameterized.expand([(IPv6, None)])
639 def test_duplicates(self, family, stream):
640 """ duplicate fragments """
643 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
644 x for (_, frags, _) in self.pkt_infos
646 for _ in range(0, min(2, len(frags)))
648 super(TestIPv6Reassembly, self).test_duplicates(family, fragments)
650 def test_overlap1(self):
651 """ overlapping fragments case #1 (differs from IP test case)"""
654 for _, frags_400, frags_300 in self.pkt_infos:
655 if len(frags_300) == 1:
656 fragments.extend(frags_400)
658 for i, j in zip(frags_300, frags_400):
662 dropped_packet_indexes = set(
663 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
666 self.pg_enable_capture()
667 self.src_if.add_stream(fragments)
670 packets = self.dst_if.get_capture(
671 len(self.pkt_infos) - len(dropped_packet_indexes))
672 self.verify_capture(IPv6, packets, dropped_packet_indexes)
673 self.src_if.assert_nothing_captured()
675 def test_overlap2(self):
676 """ overlapping fragments case #2 (differs from IP test case)"""
679 for _, frags_400, frags_300 in self.pkt_infos:
680 if len(frags_400) == 1:
681 fragments.extend(frags_400)
683 # care must be taken here so that there are no fragments
684 # received by vpp after reassembly is finished, otherwise
685 # new reassemblies will be started and packet generator will
686 # freak out when it detects unfreed buffers
687 zipped = zip(frags_400, frags_300)
688 for i, j in zipped[:-1]:
691 fragments.append(zipped[-1][0])
693 dropped_packet_indexes = set(
694 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
697 self.pg_enable_capture()
698 self.src_if.add_stream(fragments)
701 packets = self.dst_if.get_capture(
702 len(self.pkt_infos) - len(dropped_packet_indexes))
703 self.verify_capture(IPv6, packets, dropped_packet_indexes)
704 self.src_if.assert_nothing_captured()
706 @parameterized.expand([(IPv6, None, None)])
707 def test_timeout_inline(self, family, stream, dropped_packets_index):
708 """ timeout (inline) """
709 stream = self.__class__.fragments_400
711 dropped_packet_indexes = set(
712 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
714 super(TestIPv6Reassembly, self).test_timeout_inline(
715 family, stream, dropped_packet_indexes)
717 pkts = self.src_if.get_capture(
718 expected_count=len(dropped_packet_indexes))
720 self.assertIn(ICMPv6TimeExceeded, icmp)
721 self.assertIn(IPv6ExtHdrFragment, icmp)
722 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
723 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
725 def test_timeout_cleanup(self):
726 """ timeout (cleanup) """
728 # whole packets + fragmented packets sans last fragment
730 x for (_, frags_400, _) in self.pkt_infos
731 for x in frags_400[:-1 if len(frags_400) > 1 else None]
734 # last fragments for fragmented packets
735 fragments2 = [frags_400[-1]
736 for (_, frags_400, _) in self.pkt_infos
737 if len(frags_400) > 1]
739 dropped_packet_indexes = set(
740 index for (index, frags_400, _) in self.pkt_infos
741 if len(frags_400) > 1)
743 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
744 expire_walk_interval_ms=50)
746 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
747 expire_walk_interval_ms=50, is_ip6=1)
749 self.pg_enable_capture()
750 self.src_if.add_stream(fragments)
753 self.sleep(.25, "wait before sending rest of fragments")
755 self.src_if.add_stream(fragments2)
758 packets = self.dst_if.get_capture(
759 len(self.pkt_infos) - len(dropped_packet_indexes))
760 self.verify_capture(IPv6, packets, dropped_packet_indexes)
761 pkts = self.src_if.get_capture(
762 expected_count=len(dropped_packet_indexes))
764 self.assertIn(ICMPv6TimeExceeded, icmp)
765 self.assertIn(IPv6ExtHdrFragment, icmp)
766 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
767 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
769 @parameterized.expand([(IPv6, None, None)])
770 def test_disabled(self, family, stream, dropped_packet_indexes):
771 """ reassembly disabled """
773 stream = self.__class__.fragments_400
774 dropped_packet_indexes = set(
775 index for (index, frags_400, _) in self.pkt_infos
776 if len(frags_400) > 1)
777 super(TestIPv6Reassembly, self).test_disabled(
778 family, stream, dropped_packet_indexes)
779 self.src_if.assert_nothing_captured()
781 def test_missing_upper(self):
782 """ missing upper layer """
783 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
784 IPv6(src=self.src_if.remote_ip6,
785 dst=self.src_if.local_ip6) /
786 UDP(sport=1234, dport=5678) /
788 self.extend_packet(p, 1000, self.padding)
789 fragments = fragment_rfc8200(p, 1, 500)
790 bad_fragment = p.__class__(scapy.compat.raw(fragments[1]))
791 bad_fragment[IPv6ExtHdrFragment].nh = 59
792 bad_fragment[IPv6ExtHdrFragment].offset = 0
793 self.pg_enable_capture()
794 self.src_if.add_stream([bad_fragment])
796 pkts = self.src_if.get_capture(expected_count=1)
798 self.assertIn(ICMPv6ParamProblem, icmp)
799 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
801 def test_invalid_frag_size(self):
802 """ fragment size not a multiple of 8 """
803 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
804 IPv6(src=self.src_if.remote_ip6,
805 dst=self.src_if.local_ip6) /
806 UDP(sport=1234, dport=5678) /
808 self.extend_packet(p, 1000, self.padding)
809 fragments = fragment_rfc8200(p, 1, 500)
810 bad_fragment = fragments[0]
811 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
812 self.pg_enable_capture()
813 self.src_if.add_stream([bad_fragment])
815 pkts = self.src_if.get_capture(expected_count=1)
817 self.assertIn(ICMPv6ParamProblem, icmp)
818 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
820 def test_invalid_packet_size(self):
821 """ total packet size > 65535 """
822 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
823 IPv6(src=self.src_if.remote_ip6,
824 dst=self.src_if.local_ip6) /
825 UDP(sport=1234, dport=5678) /
827 self.extend_packet(p, 1000, self.padding)
828 fragments = fragment_rfc8200(p, 1, 500)
829 bad_fragment = fragments[1]
830 bad_fragment[IPv6ExtHdrFragment].offset = 65500
831 self.pg_enable_capture()
832 self.src_if.add_stream([bad_fragment])
834 pkts = self.src_if.get_capture(expected_count=1)
836 self.assertIn(ICMPv6ParamProblem, icmp)
837 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
840 class TestIPv4ReassemblyLocalNode(VppTestCase):
841 """ IPv4 Reassembly for packets coming to ip4-local node """
845 super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
847 cls.create_pg_interfaces([0])
848 cls.src_dst_if = cls.pg0
850 # setup all interfaces
851 for i in cls.pg_interfaces:
856 cls.padding = " abcdefghijklmn"
858 cls.create_fragments()
861 def tearDownClass(cls):
862 super(TestIPv4ReassemblyLocalNode, cls).tearDownClass()
865 """ Test setup - force timeout on existing reassemblies """
866 super(TestIPv4ReassemblyLocalNode, self).setUp()
867 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
868 expire_walk_interval_ms=10)
870 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
871 expire_walk_interval_ms=10000)
874 super(TestIPv4ReassemblyLocalNode, self).tearDown()
876 def show_commands_at_teardown(self):
877 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
878 self.logger.debug(self.vapi.ppcli("show buffers"))
881 def create_stream(cls, packet_count=test_packet_count):
882 """Create input packet stream for defined interface.
884 :param list packet_sizes: Required packet sizes.
886 for i in range(0, packet_count):
887 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
888 payload = cls.info_to_payload(info)
889 p = (Ether(dst=cls.src_dst_if.local_mac,
890 src=cls.src_dst_if.remote_mac) /
891 IP(id=info.index, src=cls.src_dst_if.remote_ip4,
892 dst=cls.src_dst_if.local_ip4) /
893 ICMP(type='echo-request', id=1234) /
895 cls.extend_packet(p, 1518, cls.padding)
899 def create_fragments(cls):
900 infos = cls._packet_infos
902 for index, info in six.iteritems(infos):
904 # cls.logger.debug(ppp("Packet:",
905 # p.__class__(scapy.compat.raw(p))))
906 fragments_300 = fragment_rfc791(p, 300)
907 cls.pkt_infos.append((index, fragments_300))
908 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
909 cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
910 (len(infos), len(cls.fragments_300)))
912 def verify_capture(self, capture):
913 """Verify captured packet stream.
915 :param list capture: Captured packet stream.
919 for packet in capture:
921 self.logger.debug(ppp("Got packet:", packet))
924 payload_info = self.payload_to_info(packet[Raw])
925 packet_index = payload_info.index
926 if packet_index in seen:
927 raise Exception(ppp("Duplicate packet received", packet))
928 seen.add(packet_index)
929 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
930 info = self._packet_infos[packet_index]
931 self.assertIsNotNone(info)
932 self.assertEqual(packet_index, info.index)
933 saved_packet = info.data
934 self.assertEqual(ip.src, saved_packet[IP].dst)
935 self.assertEqual(ip.dst, saved_packet[IP].src)
936 self.assertEqual(icmp.type, 0) # echo reply
937 self.assertEqual(icmp.id, saved_packet[ICMP].id)
938 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
940 self.logger.error(ppp("Unexpected or invalid packet:", packet))
942 for index in self._packet_infos:
943 self.assertIn(index, seen,
944 "Packet with packet_index %d not received" % index)
946 def test_reassembly(self):
947 """ basic reassembly """
949 self.pg_enable_capture()
950 self.src_dst_if.add_stream(self.fragments_300)
953 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
954 self.verify_capture(packets)
956 # run it all again to verify correctness
957 self.pg_enable_capture()
958 self.src_dst_if.add_stream(self.fragments_300)
961 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
962 self.verify_capture(packets)
965 class TestFIFReassembly(VppTestCase):
966 """ Fragments in fragments reassembly """
970 super(TestFIFReassembly, cls).setUpClass()
972 cls.create_pg_interfaces([0, 1])
975 for i in cls.pg_interfaces:
982 cls.packet_sizes = [64, 512, 1518, 9018]
983 cls.padding = " abcdefghijklmn"
986 def tearDownClass(cls):
987 super(TestFIFReassembly, cls).tearDownClass()
990 """ Test setup - force timeout on existing reassemblies """
991 super(TestFIFReassembly, self).setUp()
992 self.vapi.ip_reassembly_enable_disable(
993 sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
995 self.vapi.ip_reassembly_enable_disable(
996 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
998 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
999 expire_walk_interval_ms=10)
1000 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1001 expire_walk_interval_ms=10, is_ip6=1)
1003 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1004 expire_walk_interval_ms=10000)
1005 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1006 expire_walk_interval_ms=10000, is_ip6=1)
1009 super(TestFIFReassembly, self).tearDown()
1011 def show_commands_at_teardown(self):
1012 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
1013 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
1014 self.logger.debug(self.vapi.ppcli("show buffers"))
1016 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
1017 """Verify captured packet stream.
1019 :param list capture: Captured packet stream.
1023 for packet in capture:
1025 self.logger.debug(ppp("Got packet:", packet))
1026 ip = packet[ip_class]
1028 payload_info = self.payload_to_info(packet[Raw])
1029 packet_index = payload_info.index
1031 packet_index not in dropped_packet_indexes,
1032 ppp("Packet received, but should be dropped:", packet))
1033 if packet_index in seen:
1034 raise Exception(ppp("Duplicate packet received", packet))
1035 seen.add(packet_index)
1036 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1037 info = self._packet_infos[packet_index]
1038 self.assertTrue(info is not None)
1039 self.assertEqual(packet_index, info.index)
1040 saved_packet = info.data
1041 self.assertEqual(ip.src, saved_packet[ip_class].src)
1042 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1043 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1045 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1047 for index in self._packet_infos:
1048 self.assertTrue(index in seen or index in dropped_packet_indexes,
1049 "Packet with packet_index %d not received" % index)
1051 def test_fif4(self):
1052 """ Fragments in fragments (4o4) """
1054 # TODO this should be ideally in setUpClass, but then we hit a bug
1055 # with VppIpRoute incorrectly reporting it's present when it's not
1056 # so we need to manually remove the vpp config, thus we cannot have
1057 # it shared for multiple test cases
1058 self.tun_ip4 = "1.1.1.2"
1060 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1061 self.gre4.add_vpp_config()
1062 self.gre4.admin_up()
1063 self.gre4.config_ip4()
1065 self.vapi.ip_reassembly_enable_disable(
1066 sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1068 self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1069 [VppRoutePath(self.src_if.remote_ip4,
1070 self.src_if.sw_if_index)])
1071 self.route4.add_vpp_config()
1073 self.reset_packet_infos()
1074 for i in range(test_packet_count):
1075 info = self.create_packet_info(self.src_if, self.dst_if)
1076 payload = self.info_to_payload(info)
1077 # Ethernet header here is only for size calculation, thus it
1078 # doesn't matter how it's initialized. This is to ensure that
1079 # reassembled packet is not > 9000 bytes, so that it's not dropped
1081 IP(id=i, src=self.src_if.remote_ip4,
1082 dst=self.dst_if.remote_ip4) /
1083 UDP(sport=1234, dport=5678) /
1085 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1086 self.extend_packet(p, size, self.padding)
1087 info.data = p[IP] # use only IP part, without ethernet header
1089 fragments = [x for _, p in six.iteritems(self._packet_infos)
1090 for x in fragment_rfc791(p.data, 400)]
1092 encapped_fragments = \
1093 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1094 IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1099 fragmented_encapped_fragments = \
1100 [x for p in encapped_fragments
1101 for x in fragment_rfc791(p, 200)]
1103 self.src_if.add_stream(fragmented_encapped_fragments)
1105 self.pg_enable_capture(self.pg_interfaces)
1108 self.src_if.assert_nothing_captured()
1109 packets = self.dst_if.get_capture(len(self._packet_infos))
1110 self.verify_capture(packets, IP)
1112 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1113 # so that it's query_vpp_config() works as it should
1114 self.gre4.remove_vpp_config()
1115 self.logger.debug(self.vapi.ppcli("show interface"))
1117 def test_fif6(self):
1118 """ Fragments in fragments (6o6) """
1119 # TODO this should be ideally in setUpClass, but then we hit a bug
1120 # with VppIpRoute incorrectly reporting it's present when it's not
1121 # so we need to manually remove the vpp config, thus we cannot have
1122 # it shared for multiple test cases
1123 self.tun_ip6 = "1002::1"
1125 self.gre6 = VppGre6Interface(self, self.src_if.local_ip6, self.tun_ip6)
1126 self.gre6.add_vpp_config()
1127 self.gre6.admin_up()
1128 self.gre6.config_ip6()
1130 self.vapi.ip_reassembly_enable_disable(
1131 sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1133 self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1134 [VppRoutePath(self.src_if.remote_ip6,
1135 self.src_if.sw_if_index,
1136 proto=DpoProto.DPO_PROTO_IP6)],
1138 self.route6.add_vpp_config()
1140 self.reset_packet_infos()
1141 for i in range(test_packet_count):
1142 info = self.create_packet_info(self.src_if, self.dst_if)
1143 payload = self.info_to_payload(info)
1144 # Ethernet header here is only for size calculation, thus it
1145 # doesn't matter how it's initialized. This is to ensure that
1146 # reassembled packet is not > 9000 bytes, so that it's not dropped
1148 IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1149 UDP(sport=1234, dport=5678) /
1151 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1152 self.extend_packet(p, size, self.padding)
1153 info.data = p[IPv6] # use only IPv6 part, without ethernet header
1155 fragments = [x for _, i in six.iteritems(self._packet_infos)
1156 for x in fragment_rfc8200(
1157 i.data, i.index, 400)]
1159 encapped_fragments = \
1160 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1161 IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1166 fragmented_encapped_fragments = \
1167 [x for p in encapped_fragments for x in (
1170 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1172 if IPv6ExtHdrFragment in p else [p]
1176 self.src_if.add_stream(fragmented_encapped_fragments)
1178 self.pg_enable_capture(self.pg_interfaces)
1181 self.src_if.assert_nothing_captured()
1182 packets = self.dst_if.get_capture(len(self._packet_infos))
1183 self.verify_capture(packets, IPv6)
1185 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1186 # so that it's query_vpp_config() works as it should
1187 self.gre6.remove_vpp_config()
1190 if __name__ == '__main__':
1191 unittest.main(testRunner=VppTestRunner)