3 from random import shuffle
7 from parameterized import parameterized
8 from scapy.packet import Raw
9 from scapy.layers.l2 import Ether, GRE
10 from scapy.layers.inet import IP, UDP, ICMP
12 from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, ICMPv6ParamProblem,\
15 from framework import VppTestCase, VppTestRunner
16 from util import ppp, fragment_rfc791, fragment_rfc8200
17 from vpp_gre_interface import VppGreInterface, VppGre6Interface
18 from vpp_ip import DpoProto
19 from vpp_ip_route import VppIpRoute, VppRoutePath
21 # 35 is enough to have >257 400-byte fragments
22 test_packet_count = 35
24 # <class 'scapy.layers.inet.IP'>
25 # <class 'scapy.layers.inet6.IPv6'>
26 _scapy_ip_family_types = (IP, IPv6)
29 def validate_scapy_ip_family(scapy_ip_family):
31 if scapy_ip_family not in _scapy_ip_family_types:
32 raise ValueError("'scapy_ip_family' must be of type: %s. Got %s" %
33 (_scapy_ip_family_types, scapy_ip_family))
36 class TestIPReassemblyMixin(object):
38 def verify_capture(self, scapy_ip_family, capture,
39 dropped_packet_indexes=None):
40 """Verify captured packet stream.
42 :param list capture: Captured packet stream.
44 validate_scapy_ip_family(scapy_ip_family)
46 if dropped_packet_indexes is None:
47 dropped_packet_indexes = []
50 for packet in capture:
52 self.logger.debug(ppp("Got packet:", packet))
53 ip = packet[scapy_ip_family]
55 payload_info = self.payload_to_info(str(packet[Raw]))
56 packet_index = payload_info.index
58 packet_index not in dropped_packet_indexes,
59 ppp("Packet received, but should be dropped:", packet))
60 if packet_index in seen:
61 raise Exception(ppp("Duplicate packet received", packet))
62 seen.add(packet_index)
63 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
64 info = self._packet_infos[packet_index]
65 self.assertTrue(info is not None)
66 self.assertEqual(packet_index, info.index)
67 saved_packet = info.data
68 self.assertEqual(ip.src, saved_packet[scapy_ip_family].src)
69 self.assertEqual(ip.dst, saved_packet[scapy_ip_family].dst)
70 self.assertEqual(udp.payload, saved_packet[UDP].payload)
72 self.logger.error(ppp("Unexpected or invalid packet:", packet))
74 for index in self._packet_infos:
75 self.assertTrue(index in seen or index in dropped_packet_indexes,
76 "Packet with packet_index %d not received" % index)
78 def test_disabled(self, scapy_ip_family, stream,
79 dropped_packet_indexes):
80 """ reassembly disabled """
81 validate_scapy_ip_family(scapy_ip_family)
82 is_ip6 = 1 if scapy_ip_family == IPv6 else 0
84 self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
85 expire_walk_interval_ms=10000,
88 self.pg_enable_capture()
89 self.src_if.add_stream(stream)
92 packets = self.dst_if.get_capture(
93 len(self.pkt_infos) - len(dropped_packet_indexes))
94 self.verify_capture(scapy_ip_family, packets, dropped_packet_indexes)
95 self.src_if.assert_nothing_captured()
97 def test_duplicates(self, scapy_ip_family, stream):
98 """ duplicate fragments """
99 validate_scapy_ip_family(scapy_ip_family)
101 self.pg_enable_capture()
102 self.src_if.add_stream(stream)
105 packets = self.dst_if.get_capture(len(self.pkt_infos))
106 self.verify_capture(scapy_ip_family, packets)
107 self.src_if.assert_nothing_captured()
109 def test_random(self, scapy_ip_family, stream):
110 """ random order reassembly """
111 validate_scapy_ip_family(scapy_ip_family)
113 fragments = list(stream)
116 self.pg_enable_capture()
117 self.src_if.add_stream(fragments)
120 packets = self.dst_if.get_capture(len(self.packet_infos))
121 self.verify_capture(scapy_ip_family, packets)
122 self.src_if.assert_nothing_captured()
124 # run it all again to verify correctness
125 self.pg_enable_capture()
126 self.src_if.add_stream(fragments)
129 packets = self.dst_if.get_capture(len(self.packet_infos))
130 self.verify_capture(scapy_ip_family, packets)
131 self.src_if.assert_nothing_captured()
133 def test_reassembly(self, scapy_ip_family, stream):
134 """ basic reassembly """
135 validate_scapy_ip_family(scapy_ip_family)
137 self.pg_enable_capture()
138 self.src_if.add_stream(stream)
141 packets = self.dst_if.get_capture(len(self.pkt_infos))
142 self.verify_capture(scapy_ip_family, packets)
143 self.src_if.assert_nothing_captured()
145 # run it all again to verify correctness
146 self.pg_enable_capture()
147 self.src_if.add_stream(stream)
150 packets = self.dst_if.get_capture(len(self.pkt_infos))
151 self.verify_capture(scapy_ip_family, packets)
152 self.src_if.assert_nothing_captured()
154 def test_reversed(self, scapy_ip_family, stream):
155 """ reverse order reassembly """
156 validate_scapy_ip_family(scapy_ip_family)
158 fragments = list(stream)
161 self.pg_enable_capture()
162 self.src_if.add_stream(fragments)
165 packets = self.dst_if.get_capture(len(self.packet_infos))
166 self.verify_capture(scapy_ip_family, packets)
167 self.src_if.assert_nothing_captured()
169 # run it all again to verify correctness
170 self.pg_enable_capture()
171 self.src_if.add_stream(fragments)
174 packets = self.dst_if.get_capture(len(self.packet_infos))
175 self.verify_capture(scapy_ip_family, packets)
176 self.src_if.assert_nothing_captured()
178 def test_timeout_inline(self, scapy_ip_family, stream,
179 dropped_packet_indexes):
180 """ timeout (inline) """
181 validate_scapy_ip_family(scapy_ip_family)
182 is_ip6 = 1 if scapy_ip_family == IPv6 else 0
184 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
185 expire_walk_interval_ms=10000,
188 self.pg_enable_capture()
189 self.src_if.add_stream(stream)
192 packets = self.dst_if.get_capture(
193 len(self.pkt_infos) - len(dropped_packet_indexes))
194 self.verify_capture(scapy_ip_family, packets,
195 dropped_packet_indexes)
198 class TestIPv4Reassembly(TestIPReassemblyMixin, VppTestCase):
199 """ IPv4 Reassembly """
203 super(TestIPv4Reassembly, cls).setUpClass()
205 cls.create_pg_interfaces([0, 1])
209 # setup all interfaces
210 for i in cls.pg_interfaces:
216 cls.packet_sizes = [64, 512, 1518, 9018]
217 cls.padding = " abcdefghijklmn"
218 cls.create_stream(cls.packet_sizes)
219 cls.create_fragments()
222 """ Test setup - force timeout on existing reassemblies """
223 super(TestIPv4Reassembly, self).setUp()
224 self.vapi.ip_reassembly_enable_disable(
225 sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
226 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
227 expire_walk_interval_ms=10)
229 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
230 expire_walk_interval_ms=10000)
233 super(TestIPv4Reassembly, self).tearDown()
234 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
235 self.logger.debug(self.vapi.ppcli("show buffers"))
238 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
239 """Create input packet stream
241 :param list packet_sizes: Required packet sizes.
243 for i in range(0, packet_count):
244 info = cls.create_packet_info(cls.src_if, cls.src_if)
245 payload = cls.info_to_payload(info)
246 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
247 IP(id=info.index, src=cls.src_if.remote_ip4,
248 dst=cls.dst_if.remote_ip4) /
249 UDP(sport=1234, dport=5678) /
251 size = packet_sizes[(i // 2) % len(packet_sizes)]
252 cls.extend_packet(p, size, cls.padding)
256 def create_fragments(cls):
257 infos = cls._packet_infos
259 for index, info in six.iteritems(infos):
261 # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
262 fragments_400 = fragment_rfc791(p, 400)
263 fragments_300 = fragment_rfc791(p, 300)
265 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
266 cls.pkt_infos.append(
267 (index, fragments_400, fragments_300, fragments_200))
268 cls.fragments_400 = [
269 x for (_, frags, _, _) in cls.pkt_infos for x in frags]
270 cls.fragments_300 = [
271 x for (_, _, frags, _) in cls.pkt_infos for x in frags]
272 cls.fragments_200 = [
273 x for (_, _, _, frags) in cls.pkt_infos for x in frags]
274 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
275 "%s 300-byte fragments and %s 200-byte fragments" %
276 (len(infos), len(cls.fragments_400),
277 len(cls.fragments_300), len(cls.fragments_200)))
279 @parameterized.expand([(IP, None)])
280 def test_reassembly(self, family, stream):
281 """ basic reassembly """
282 stream = self.__class__.fragments_200
283 super(TestIPv4Reassembly, self).test_reassembly(family, stream)
285 @parameterized.expand([(IP, None)])
286 def test_reversed(self, family, stream):
287 """ reverse order reassembly """
288 stream = self.__class__.fragments_200
289 super(TestIPv4Reassembly, self).test_reversed(family, stream)
291 @parameterized.expand([(IP, None)])
292 def test_random(self, family, stream):
293 stream = self.__class__.fragments_200
294 super(TestIPv4Reassembly, self).test_random(family, stream)
297 """ fragment length + ip header size > 65535 """
298 self.vapi.cli("clear errors")
299 raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
300 '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
301 'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
302 'fset; Test-case: 5737')
304 malformed_packet = (Ether(dst=self.src_if.local_mac,
305 src=self.src_if.remote_mac) /
307 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
308 IP(id=1000, src=self.src_if.remote_ip4,
309 dst=self.dst_if.remote_ip4) /
310 UDP(sport=1234, dport=5678) /
312 valid_fragments = fragment_rfc791(p, 400)
314 self.pg_enable_capture()
315 self.src_if.add_stream([malformed_packet] + valid_fragments)
318 self.dst_if.get_capture(1)
319 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
320 # TODO remove above, uncomment below once clearing of counters
322 # self.assert_packet_counter_equal(
323 # "/err/ip4-reassembly-feature/malformed packets", 1)
325 def test_44924(self):
326 """ compress tiny fragments """
327 packets = [(Ether(dst=self.src_if.local_mac,
328 src=self.src_if.remote_mac) /
329 IP(id=24339, flags="MF", frag=0, ttl=64,
330 src=self.src_if.remote_ip4,
331 dst=self.dst_if.remote_ip4) /
332 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
333 Raw(load='Test-group: IPv4')),
334 (Ether(dst=self.src_if.local_mac,
335 src=self.src_if.remote_mac) /
336 IP(id=24339, flags="MF", frag=3, ttl=64,
337 src=self.src_if.remote_ip4,
338 dst=self.dst_if.remote_ip4) /
339 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
340 Raw(load='.IPv4.Fragmentation.vali')),
341 (Ether(dst=self.src_if.local_mac,
342 src=self.src_if.remote_mac) /
343 IP(id=24339, frag=6, ttl=64,
344 src=self.src_if.remote_ip4,
345 dst=self.dst_if.remote_ip4) /
346 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
347 Raw(load='d; Test-case: 44924'))
350 self.pg_enable_capture()
351 self.src_if.add_stream(packets)
354 self.dst_if.get_capture(1)
356 def test_frag_1(self):
357 """ fragment of size 1 """
358 self.vapi.cli("clear errors")
359 malformed_packets = [(Ether(dst=self.src_if.local_mac,
360 src=self.src_if.remote_mac) /
361 IP(id=7, len=21, flags="MF", frag=0, ttl=64,
362 src=self.src_if.remote_ip4,
363 dst=self.dst_if.remote_ip4) /
364 ICMP(type="echo-request")),
365 (Ether(dst=self.src_if.local_mac,
366 src=self.src_if.remote_mac) /
367 IP(id=7, len=21, frag=1, ttl=64,
368 src=self.src_if.remote_ip4,
369 dst=self.dst_if.remote_ip4) /
373 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
374 IP(id=1000, src=self.src_if.remote_ip4,
375 dst=self.dst_if.remote_ip4) /
376 UDP(sport=1234, dport=5678) /
378 valid_fragments = fragment_rfc791(p, 400)
380 self.pg_enable_capture()
381 self.src_if.add_stream(malformed_packets + valid_fragments)
384 self.dst_if.get_capture(1)
386 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
387 # TODO remove above, uncomment below once clearing of counters
389 # self.assert_packet_counter_equal(
390 # "/err/ip4-reassembly-feature/malformed packets", 1)
392 @parameterized.expand([(IP, None)])
393 def test_duplicates(self, family, stream):
394 """ duplicate fragments """
396 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
397 x for (_, frags, _, _) in self.pkt_infos
399 for _ in range(0, min(2, len(frags)))
401 super(TestIPv4Reassembly, self).test_duplicates(family, fragments)
403 def test_overlap1(self):
404 """ overlapping fragments case #1 """
407 for _, _, frags_300, frags_200 in self.pkt_infos:
408 if len(frags_300) == 1:
409 fragments.extend(frags_300)
411 for i, j in zip(frags_200, frags_300):
415 self.pg_enable_capture()
416 self.src_if.add_stream(fragments)
419 packets = self.dst_if.get_capture(len(self.pkt_infos))
420 self.verify_capture(IP, packets)
421 self.src_if.assert_nothing_captured()
423 # run it all to verify correctness
424 self.pg_enable_capture()
425 self.src_if.add_stream(fragments)
428 packets = self.dst_if.get_capture(len(self.pkt_infos))
429 self.verify_capture(IP, packets)
430 self.src_if.assert_nothing_captured()
432 def test_overlap2(self):
433 """ overlapping fragments case #2 """
436 for _, _, frags_300, frags_200 in self.pkt_infos:
437 if len(frags_300) == 1:
438 fragments.extend(frags_300)
440 # care must be taken here so that there are no fragments
441 # received by vpp after reassembly is finished, otherwise
442 # new reassemblies will be started and packet generator will
443 # freak out when it detects unfreed buffers
444 zipped = zip(frags_300, frags_200)
445 for i, j in zipped[:-1]:
448 fragments.append(zipped[-1][0])
450 self.pg_enable_capture()
451 self.src_if.add_stream(fragments)
454 packets = self.dst_if.get_capture(len(self.pkt_infos))
455 self.verify_capture(IP, packets)
456 self.src_if.assert_nothing_captured()
458 # run it all to verify correctness
459 self.pg_enable_capture()
460 self.src_if.add_stream(fragments)
463 packets = self.dst_if.get_capture(len(self.pkt_infos))
464 self.verify_capture(IP, packets)
465 self.src_if.assert_nothing_captured()
467 @parameterized.expand([(IP, None, None)])
468 def test_timeout_inline(self, family, stream, dropped_packet_indexes):
469 """ timeout (inline) """
470 stream = self.fragments_400
472 dropped_packet_indexes = set(
473 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
475 super(TestIPv4Reassembly, self).test_timeout_inline(
476 family, stream, dropped_packet_indexes)
478 self.src_if.assert_nothing_captured()
480 def test_timeout_cleanup(self):
481 """ timeout (cleanup) """
483 # whole packets + fragmented packets sans last fragment
485 x for (_, frags_400, _, _) in self.pkt_infos
486 for x in frags_400[:-1 if len(frags_400) > 1 else None]
489 # last fragments for fragmented packets
490 fragments2 = [frags_400[-1]
491 for (_, frags_400, _, _) in self.pkt_infos
492 if len(frags_400) > 1]
494 dropped_packet_indexes = set(
495 index for (index, frags_400, _, _) in self.pkt_infos
496 if len(frags_400) > 1)
498 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
499 expire_walk_interval_ms=50)
501 self.pg_enable_capture()
502 self.src_if.add_stream(fragments)
505 self.sleep(.25, "wait before sending rest of fragments")
507 self.src_if.add_stream(fragments2)
510 packets = self.dst_if.get_capture(
511 len(self.pkt_infos) - len(dropped_packet_indexes))
512 self.verify_capture(IP, packets, dropped_packet_indexes)
513 self.src_if.assert_nothing_captured()
515 @parameterized.expand([(IP, None, None)])
516 def test_disabled(self, family, stream, dropped_packet_indexes):
517 """ reassembly disabled """
519 stream = self.__class__.fragments_400
520 dropped_packet_indexes = set(
521 index for (index, frags_400, _, _) in self.pkt_infos
522 if len(frags_400) > 1)
523 super(TestIPv4Reassembly, self).test_disabled(
524 family, stream, dropped_packet_indexes)
527 class TestIPv6Reassembly(TestIPReassemblyMixin, VppTestCase):
528 """ IPv6 Reassembly """
532 super(TestIPv6Reassembly, cls).setUpClass()
534 cls.create_pg_interfaces([0, 1])
538 # setup all interfaces
539 for i in cls.pg_interfaces:
545 cls.packet_sizes = [64, 512, 1518, 9018]
546 cls.padding = " abcdefghijklmn"
547 cls.create_stream(cls.packet_sizes)
548 cls.create_fragments()
551 """ Test setup - force timeout on existing reassemblies """
552 super(TestIPv6Reassembly, self).setUp()
553 self.vapi.ip_reassembly_enable_disable(
554 sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
555 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
556 expire_walk_interval_ms=10, is_ip6=1)
558 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
559 expire_walk_interval_ms=10000, is_ip6=1)
560 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
561 self.logger.debug(self.vapi.ppcli("show buffers"))
564 super(TestIPv6Reassembly, self).tearDown()
565 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
566 self.logger.debug(self.vapi.ppcli("show buffers"))
569 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
570 """Create input packet stream for defined interface.
572 :param list packet_sizes: Required packet sizes.
574 for i in range(0, packet_count):
575 info = cls.create_packet_info(cls.src_if, cls.src_if)
576 payload = cls.info_to_payload(info)
577 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
578 IPv6(src=cls.src_if.remote_ip6,
579 dst=cls.dst_if.remote_ip6) /
580 UDP(sport=1234, dport=5678) /
582 size = packet_sizes[(i // 2) % len(packet_sizes)]
583 cls.extend_packet(p, size, cls.padding)
587 def create_fragments(cls):
588 infos = cls._packet_infos
590 for index, info in six.iteritems(infos):
592 # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
593 fragments_400 = fragment_rfc8200(p, info.index, 400)
594 fragments_300 = fragment_rfc8200(p, info.index, 300)
595 cls.pkt_infos.append((index, fragments_400, fragments_300))
596 cls.fragments_400 = [
597 x for _, frags, _ in cls.pkt_infos for x in frags]
598 cls.fragments_300 = [
599 x for _, _, frags in cls.pkt_infos for x in frags]
600 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
601 "and %s 300-byte fragments" %
602 (len(infos), len(cls.fragments_400),
603 len(cls.fragments_300)))
605 @parameterized.expand([(IPv6, None)])
606 def test_reassembly(self, family, stream):
607 """ basic reassembly """
608 stream = self.__class__.fragments_400
609 super(TestIPv6Reassembly, self).test_reassembly(family, stream)
611 @parameterized.expand([(IPv6, None)])
612 def test_reversed(self, family, stream):
613 """ reverse order reassembly """
614 stream = self.__class__.fragments_400
615 super(TestIPv6Reassembly, self).test_reversed(family, stream)
617 @parameterized.expand([(IPv6, None)])
618 def test_random(self, family, stream):
619 """ random order reassembly """
620 stream = self.__class__.fragments_400
621 super(TestIPv6Reassembly, self).test_random(family, stream)
623 @parameterized.expand([(IPv6, None)])
624 def test_duplicates(self, family, stream):
625 """ duplicate fragments """
628 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
629 x for (_, frags, _) in self.pkt_infos
631 for _ in range(0, min(2, len(frags)))
633 super(TestIPv6Reassembly, self).test_duplicates(family, fragments)
635 def test_overlap1(self):
636 """ overlapping fragments case #1 (differs from IP test case)"""
639 for _, frags_400, frags_300 in self.pkt_infos:
640 if len(frags_300) == 1:
641 fragments.extend(frags_400)
643 for i, j in zip(frags_300, frags_400):
647 dropped_packet_indexes = set(
648 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
651 self.pg_enable_capture()
652 self.src_if.add_stream(fragments)
655 packets = self.dst_if.get_capture(
656 len(self.pkt_infos) - len(dropped_packet_indexes))
657 self.verify_capture(IPv6, packets, dropped_packet_indexes)
658 self.src_if.assert_nothing_captured()
660 def test_overlap2(self):
661 """ overlapping fragments case #2 (differs from IP test case)"""
664 for _, frags_400, frags_300 in self.pkt_infos:
665 if len(frags_400) == 1:
666 fragments.extend(frags_400)
668 # care must be taken here so that there are no fragments
669 # received by vpp after reassembly is finished, otherwise
670 # new reassemblies will be started and packet generator will
671 # freak out when it detects unfreed buffers
672 zipped = zip(frags_400, frags_300)
673 for i, j in zipped[:-1]:
676 fragments.append(zipped[-1][0])
678 dropped_packet_indexes = set(
679 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
682 self.pg_enable_capture()
683 self.src_if.add_stream(fragments)
686 packets = self.dst_if.get_capture(
687 len(self.pkt_infos) - len(dropped_packet_indexes))
688 self.verify_capture(IPv6, packets, dropped_packet_indexes)
689 self.src_if.assert_nothing_captured()
691 @parameterized.expand([(IPv6, None, None)])
692 def test_timeout_inline(self, family, stream, dropped_packets_index):
693 """ timeout (inline) """
694 stream = self.__class__.fragments_400
696 dropped_packet_indexes = set(
697 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
699 super(TestIPv6Reassembly, self).test_timeout_inline(
700 family, stream, dropped_packet_indexes)
702 pkts = self.src_if.get_capture(
703 expected_count=len(dropped_packet_indexes))
705 self.assertIn(ICMPv6TimeExceeded, icmp)
706 self.assertIn(IPv6ExtHdrFragment, icmp)
707 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
708 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
710 def test_timeout_cleanup(self):
711 """ timeout (cleanup) """
713 # whole packets + fragmented packets sans last fragment
715 x for (_, frags_400, _) in self.pkt_infos
716 for x in frags_400[:-1 if len(frags_400) > 1 else None]
719 # last fragments for fragmented packets
720 fragments2 = [frags_400[-1]
721 for (_, frags_400, _) in self.pkt_infos
722 if len(frags_400) > 1]
724 dropped_packet_indexes = set(
725 index for (index, frags_400, _) in self.pkt_infos
726 if len(frags_400) > 1)
728 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
729 expire_walk_interval_ms=50)
731 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
732 expire_walk_interval_ms=50, is_ip6=1)
734 self.pg_enable_capture()
735 self.src_if.add_stream(fragments)
738 self.sleep(.25, "wait before sending rest of fragments")
740 self.src_if.add_stream(fragments2)
743 packets = self.dst_if.get_capture(
744 len(self.pkt_infos) - len(dropped_packet_indexes))
745 self.verify_capture(IPv6, packets, dropped_packet_indexes)
746 pkts = self.src_if.get_capture(
747 expected_count=len(dropped_packet_indexes))
749 self.assertIn(ICMPv6TimeExceeded, icmp)
750 self.assertIn(IPv6ExtHdrFragment, icmp)
751 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
752 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
754 @parameterized.expand([(IPv6, None, None)])
755 def test_disabled(self, family, stream, dropped_packet_indexes):
756 """ reassembly disabled """
758 stream = self.__class__.fragments_400
759 dropped_packet_indexes = set(
760 index for (index, frags_400, _) in self.pkt_infos
761 if len(frags_400) > 1)
762 super(TestIPv6Reassembly, self).test_disabled(
763 family, stream, dropped_packet_indexes)
764 self.src_if.assert_nothing_captured()
766 def test_missing_upper(self):
767 """ missing upper layer """
768 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
769 IPv6(src=self.src_if.remote_ip6,
770 dst=self.src_if.local_ip6) /
771 UDP(sport=1234, dport=5678) /
773 self.extend_packet(p, 1000, self.padding)
774 fragments = fragment_rfc8200(p, 1, 500)
775 bad_fragment = p.__class__(str(fragments[1]))
776 bad_fragment[IPv6ExtHdrFragment].nh = 59
777 bad_fragment[IPv6ExtHdrFragment].offset = 0
778 self.pg_enable_capture()
779 self.src_if.add_stream([bad_fragment])
781 pkts = self.src_if.get_capture(expected_count=1)
783 self.assertIn(ICMPv6ParamProblem, icmp)
784 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
786 def test_invalid_frag_size(self):
787 """ fragment size not a multiple of 8 """
788 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
789 IPv6(src=self.src_if.remote_ip6,
790 dst=self.src_if.local_ip6) /
791 UDP(sport=1234, dport=5678) /
793 self.extend_packet(p, 1000, self.padding)
794 fragments = fragment_rfc8200(p, 1, 500)
795 bad_fragment = fragments[0]
796 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
797 self.pg_enable_capture()
798 self.src_if.add_stream([bad_fragment])
800 pkts = self.src_if.get_capture(expected_count=1)
802 self.assertIn(ICMPv6ParamProblem, icmp)
803 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
805 def test_invalid_packet_size(self):
806 """ total packet size > 65535 """
807 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
808 IPv6(src=self.src_if.remote_ip6,
809 dst=self.src_if.local_ip6) /
810 UDP(sport=1234, dport=5678) /
812 self.extend_packet(p, 1000, self.padding)
813 fragments = fragment_rfc8200(p, 1, 500)
814 bad_fragment = fragments[1]
815 bad_fragment[IPv6ExtHdrFragment].offset = 65500
816 self.pg_enable_capture()
817 self.src_if.add_stream([bad_fragment])
819 pkts = self.src_if.get_capture(expected_count=1)
821 self.assertIn(ICMPv6ParamProblem, icmp)
822 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
825 class TestIPv4ReassemblyLocalNode(VppTestCase):
826 """ IPv4 Reassembly for packets coming to ip4-local node """
830 super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
832 cls.create_pg_interfaces([0])
833 cls.src_dst_if = cls.pg0
835 # setup all interfaces
836 for i in cls.pg_interfaces:
841 cls.padding = " abcdefghijklmn"
843 cls.create_fragments()
846 """ Test setup - force timeout on existing reassemblies """
847 super(TestIPv4ReassemblyLocalNode, self).setUp()
848 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
849 expire_walk_interval_ms=10)
851 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
852 expire_walk_interval_ms=10000)
855 super(TestIPv4ReassemblyLocalNode, self).tearDown()
856 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
857 self.logger.debug(self.vapi.ppcli("show buffers"))
860 def create_stream(cls, packet_count=test_packet_count):
861 """Create input packet stream for defined interface.
863 :param list packet_sizes: Required packet sizes.
865 for i in range(0, packet_count):
866 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
867 payload = cls.info_to_payload(info)
868 p = (Ether(dst=cls.src_dst_if.local_mac,
869 src=cls.src_dst_if.remote_mac) /
870 IP(id=info.index, src=cls.src_dst_if.remote_ip4,
871 dst=cls.src_dst_if.local_ip4) /
872 ICMP(type='echo-request', id=1234) /
874 cls.extend_packet(p, 1518, cls.padding)
878 def create_fragments(cls):
879 infos = cls._packet_infos
881 for index, info in six.iteritems(infos):
883 # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
884 fragments_300 = fragment_rfc791(p, 300)
885 cls.pkt_infos.append((index, fragments_300))
886 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
887 cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
888 (len(infos), len(cls.fragments_300)))
890 def verify_capture(self, capture):
891 """Verify captured packet stream.
893 :param list capture: Captured packet stream.
897 for packet in capture:
899 self.logger.debug(ppp("Got packet:", packet))
902 payload_info = self.payload_to_info(str(packet[Raw]))
903 packet_index = payload_info.index
904 if packet_index in seen:
905 raise Exception(ppp("Duplicate packet received", packet))
906 seen.add(packet_index)
907 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
908 info = self._packet_infos[packet_index]
909 self.assertIsNotNone(info)
910 self.assertEqual(packet_index, info.index)
911 saved_packet = info.data
912 self.assertEqual(ip.src, saved_packet[IP].dst)
913 self.assertEqual(ip.dst, saved_packet[IP].src)
914 self.assertEqual(icmp.type, 0) # echo reply
915 self.assertEqual(icmp.id, saved_packet[ICMP].id)
916 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
918 self.logger.error(ppp("Unexpected or invalid packet:", packet))
920 for index in self._packet_infos:
921 self.assertIn(index, seen,
922 "Packet with packet_index %d not received" % index)
924 def test_reassembly(self):
925 """ basic reassembly """
927 self.pg_enable_capture()
928 self.src_dst_if.add_stream(self.fragments_300)
931 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
932 self.verify_capture(packets)
934 # run it all again to verify correctness
935 self.pg_enable_capture()
936 self.src_dst_if.add_stream(self.fragments_300)
939 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
940 self.verify_capture(packets)
943 class TestFIFReassembly(VppTestCase):
944 """ Fragments in fragments reassembly """
948 super(TestFIFReassembly, cls).setUpClass()
950 cls.create_pg_interfaces([0, 1])
953 for i in cls.pg_interfaces:
960 cls.packet_sizes = [64, 512, 1518, 9018]
961 cls.padding = " abcdefghijklmn"
964 """ Test setup - force timeout on existing reassemblies """
965 super(TestFIFReassembly, self).setUp()
966 self.vapi.ip_reassembly_enable_disable(
967 sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
969 self.vapi.ip_reassembly_enable_disable(
970 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
972 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
973 expire_walk_interval_ms=10)
974 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
975 expire_walk_interval_ms=10, is_ip6=1)
977 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
978 expire_walk_interval_ms=10000)
979 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
980 expire_walk_interval_ms=10000, is_ip6=1)
983 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
984 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
985 self.logger.debug(self.vapi.ppcli("show buffers"))
986 super(TestFIFReassembly, self).tearDown()
988 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
989 """Verify captured packet stream.
991 :param list capture: Captured packet stream.
995 for packet in capture:
997 self.logger.debug(ppp("Got packet:", packet))
998 ip = packet[ip_class]
1000 payload_info = self.payload_to_info(str(packet[Raw]))
1001 packet_index = payload_info.index
1003 packet_index not in dropped_packet_indexes,
1004 ppp("Packet received, but should be dropped:", packet))
1005 if packet_index in seen:
1006 raise Exception(ppp("Duplicate packet received", packet))
1007 seen.add(packet_index)
1008 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1009 info = self._packet_infos[packet_index]
1010 self.assertTrue(info is not None)
1011 self.assertEqual(packet_index, info.index)
1012 saved_packet = info.data
1013 self.assertEqual(ip.src, saved_packet[ip_class].src)
1014 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1015 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1017 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1019 for index in self._packet_infos:
1020 self.assertTrue(index in seen or index in dropped_packet_indexes,
1021 "Packet with packet_index %d not received" % index)
1023 def test_fif4(self):
1024 """ Fragments in fragments (4o4) """
1026 # TODO this should be ideally in setUpClass, but then we hit a bug
1027 # with VppIpRoute incorrectly reporting it's present when it's not
1028 # so we need to manually remove the vpp config, thus we cannot have
1029 # it shared for multiple test cases
1030 self.tun_ip4 = "1.1.1.2"
1032 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1033 self.gre4.add_vpp_config()
1034 self.gre4.admin_up()
1035 self.gre4.config_ip4()
1037 self.vapi.ip_reassembly_enable_disable(
1038 sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1040 self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1041 [VppRoutePath(self.src_if.remote_ip4,
1042 self.src_if.sw_if_index)])
1043 self.route4.add_vpp_config()
1045 self.reset_packet_infos()
1046 for i in range(test_packet_count):
1047 info = self.create_packet_info(self.src_if, self.dst_if)
1048 payload = self.info_to_payload(info)
1049 # Ethernet header here is only for size calculation, thus it
1050 # doesn't matter how it's initialized. This is to ensure that
1051 # reassembled packet is not > 9000 bytes, so that it's not dropped
1053 IP(id=i, src=self.src_if.remote_ip4,
1054 dst=self.dst_if.remote_ip4) /
1055 UDP(sport=1234, dport=5678) /
1057 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1058 self.extend_packet(p, size, self.padding)
1059 info.data = p[IP] # use only IP part, without ethernet header
1061 fragments = [x for _, p in six.iteritems(self._packet_infos)
1062 for x in fragment_rfc791(p.data, 400)]
1064 encapped_fragments = \
1065 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1066 IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1071 fragmented_encapped_fragments = \
1072 [x for p in encapped_fragments
1073 for x in fragment_rfc791(p, 200)]
1075 self.src_if.add_stream(fragmented_encapped_fragments)
1077 self.pg_enable_capture(self.pg_interfaces)
1080 self.src_if.assert_nothing_captured()
1081 packets = self.dst_if.get_capture(len(self._packet_infos))
1082 self.verify_capture(packets, IP)
1084 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1085 # so that it's query_vpp_config() works as it should
1086 self.gre4.remove_vpp_config()
1087 self.logger.debug(self.vapi.ppcli("show interface"))
1089 def test_fif6(self):
1090 """ Fragments in fragments (6o6) """
1091 # TODO this should be ideally in setUpClass, but then we hit a bug
1092 # with VppIpRoute incorrectly reporting it's present when it's not
1093 # so we need to manually remove the vpp config, thus we cannot have
1094 # it shared for multiple test cases
1095 self.tun_ip6 = "1002::1"
1097 self.gre6 = VppGre6Interface(self, self.src_if.local_ip6, self.tun_ip6)
1098 self.gre6.add_vpp_config()
1099 self.gre6.admin_up()
1100 self.gre6.config_ip6()
1102 self.vapi.ip_reassembly_enable_disable(
1103 sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1105 self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1106 [VppRoutePath(self.src_if.remote_ip6,
1107 self.src_if.sw_if_index,
1108 proto=DpoProto.DPO_PROTO_IP6)],
1110 self.route6.add_vpp_config()
1112 self.reset_packet_infos()
1113 for i in range(test_packet_count):
1114 info = self.create_packet_info(self.src_if, self.dst_if)
1115 payload = self.info_to_payload(info)
1116 # Ethernet header here is only for size calculation, thus it
1117 # doesn't matter how it's initialized. This is to ensure that
1118 # reassembled packet is not > 9000 bytes, so that it's not dropped
1120 IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1121 UDP(sport=1234, dport=5678) /
1123 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1124 self.extend_packet(p, size, self.padding)
1125 info.data = p[IPv6] # use only IPv6 part, without ethernet header
1127 fragments = [x for _, i in six.iteritems(self._packet_infos)
1128 for x in fragment_rfc8200(
1129 i.data, i.index, 400)]
1131 encapped_fragments = \
1132 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1133 IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1138 fragmented_encapped_fragments = \
1139 [x for p in encapped_fragments for x in (
1142 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1144 if IPv6ExtHdrFragment in p else [p]
1148 self.src_if.add_stream(fragmented_encapped_fragments)
1150 self.pg_enable_capture(self.pg_interfaces)
1153 self.src_if.assert_nothing_captured()
1154 packets = self.dst_if.get_capture(len(self._packet_infos))
1155 self.verify_capture(packets, IPv6)
1157 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1158 # so that it's query_vpp_config() works as it should
1159 self.gre6.remove_vpp_config()
1162 if __name__ == '__main__':
1163 unittest.main(testRunner=VppTestRunner)