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
19 from vpp_ip import DpoProto
20 from vpp_ip_route import VppIpRoute, VppRoutePath, FibPathProto
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 max_reassembly_length=1000,
87 expire_walk_interval_ms=10000,
90 self.pg_enable_capture()
91 self.src_if.add_stream(stream)
94 packets = self.dst_if.get_capture(
95 len(self.pkt_infos) - len(dropped_packet_indexes))
96 self.verify_capture(scapy_ip_family, packets, dropped_packet_indexes)
97 self.src_if.assert_nothing_captured()
99 def test_duplicates(self, scapy_ip_family, stream):
100 """ duplicate fragments """
101 validate_scapy_ip_family(scapy_ip_family)
103 self.pg_enable_capture()
104 self.src_if.add_stream(stream)
107 packets = self.dst_if.get_capture(len(self.pkt_infos))
108 self.verify_capture(scapy_ip_family, packets)
109 self.src_if.assert_nothing_captured()
111 def test_random(self, scapy_ip_family, stream):
112 """ random order reassembly """
113 validate_scapy_ip_family(scapy_ip_family)
115 fragments = list(stream)
118 self.pg_enable_capture()
119 self.src_if.add_stream(fragments)
122 packets = self.dst_if.get_capture(len(self.packet_infos))
123 self.verify_capture(scapy_ip_family, packets)
124 self.src_if.assert_nothing_captured()
126 # run it all again to verify correctness
127 self.pg_enable_capture()
128 self.src_if.add_stream(fragments)
131 packets = self.dst_if.get_capture(len(self.packet_infos))
132 self.verify_capture(scapy_ip_family, packets)
133 self.src_if.assert_nothing_captured()
135 def test_reassembly(self, scapy_ip_family, stream):
136 """ basic reassembly """
137 validate_scapy_ip_family(scapy_ip_family)
139 self.pg_enable_capture()
140 self.src_if.add_stream(stream)
143 packets = self.dst_if.get_capture(len(self.pkt_infos))
144 self.verify_capture(scapy_ip_family, packets)
145 self.src_if.assert_nothing_captured()
147 # run it all again to verify correctness
148 self.pg_enable_capture()
149 self.src_if.add_stream(stream)
152 packets = self.dst_if.get_capture(len(self.pkt_infos))
153 self.verify_capture(scapy_ip_family, packets)
154 self.src_if.assert_nothing_captured()
156 def test_reversed(self, scapy_ip_family, stream):
157 """ reverse order reassembly """
158 validate_scapy_ip_family(scapy_ip_family)
160 fragments = list(stream)
163 self.pg_enable_capture()
164 self.src_if.add_stream(fragments)
167 packets = self.dst_if.get_capture(len(self.packet_infos))
168 self.verify_capture(scapy_ip_family, packets)
169 self.src_if.assert_nothing_captured()
171 # run it all again to verify correctness
172 self.pg_enable_capture()
173 self.src_if.add_stream(fragments)
176 packets = self.dst_if.get_capture(len(self.packet_infos))
177 self.verify_capture(scapy_ip_family, packets)
178 self.src_if.assert_nothing_captured()
180 def test_timeout_inline(self, scapy_ip_family, stream,
181 dropped_packet_indexes):
182 """ timeout (inline) """
183 validate_scapy_ip_family(scapy_ip_family)
184 is_ip6 = 1 if scapy_ip_family == IPv6 else 0
186 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
187 max_reassembly_length=1000,
188 expire_walk_interval_ms=10000,
191 self.pg_enable_capture()
192 self.src_if.add_stream(stream)
195 packets = self.dst_if.get_capture(
196 len(self.pkt_infos) - len(dropped_packet_indexes))
197 self.verify_capture(scapy_ip_family, packets,
198 dropped_packet_indexes)
201 class TestIPv4Reassembly(TestIPReassemblyMixin, VppTestCase):
202 """ IPv4 Reassembly """
206 super(TestIPv4Reassembly, cls).setUpClass()
208 cls.create_pg_interfaces([0, 1])
212 # setup all interfaces
213 for i in cls.pg_interfaces:
219 cls.packet_sizes = [64, 512, 1518, 9018]
220 cls.padding = " abcdefghijklmn"
221 cls.create_stream(cls.packet_sizes)
222 cls.create_fragments()
225 def tearDownClass(cls):
226 super(TestIPv4Reassembly, cls).tearDownClass()
229 """ Test setup - force timeout on existing reassemblies """
230 super(TestIPv4Reassembly, self).setUp()
231 self.vapi.ip_reassembly_enable_disable(
232 sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
233 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
234 max_reassembly_length=1000,
235 expire_walk_interval_ms=10)
237 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
238 max_reassembly_length=1000,
239 expire_walk_interval_ms=10000)
242 super(TestIPv4Reassembly, self).tearDown()
244 def show_commands_at_teardown(self):
245 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
246 self.logger.debug(self.vapi.ppcli("show buffers"))
249 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
250 """Create input packet stream
252 :param list packet_sizes: Required packet sizes.
254 for i in range(0, packet_count):
255 info = cls.create_packet_info(cls.src_if, cls.src_if)
256 payload = cls.info_to_payload(info)
257 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
258 IP(id=info.index, src=cls.src_if.remote_ip4,
259 dst=cls.dst_if.remote_ip4) /
260 UDP(sport=1234, dport=5678) /
262 size = packet_sizes[(i // 2) % len(packet_sizes)]
263 cls.extend_packet(p, size, cls.padding)
267 def create_fragments(cls):
268 infos = cls._packet_infos
270 for index, info in six.iteritems(infos):
272 # cls.logger.debug(ppp("Packet:",
273 # p.__class__(scapy.compat.raw(p))))
274 fragments_400 = fragment_rfc791(p, 400)
275 fragments_300 = fragment_rfc791(p, 300)
277 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
278 cls.pkt_infos.append(
279 (index, fragments_400, fragments_300, fragments_200))
280 cls.fragments_400 = [
281 x for (_, frags, _, _) in cls.pkt_infos for x in frags]
282 cls.fragments_300 = [
283 x for (_, _, frags, _) in cls.pkt_infos for x in frags]
284 cls.fragments_200 = [
285 x for (_, _, _, frags) in cls.pkt_infos for x in frags]
286 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
287 "%s 300-byte fragments and %s 200-byte fragments" %
288 (len(infos), len(cls.fragments_400),
289 len(cls.fragments_300), len(cls.fragments_200)))
291 @parameterized.expand([(IP, None)])
292 def test_reassembly(self, family, stream):
293 """ basic reassembly """
294 stream = self.__class__.fragments_200
295 super(TestIPv4Reassembly, self).test_reassembly(family, stream)
297 @parameterized.expand([(IP, None)])
298 def test_reversed(self, family, stream):
299 """ reverse order reassembly """
300 stream = self.__class__.fragments_200
301 super(TestIPv4Reassembly, self).test_reversed(family, stream)
303 @parameterized.expand([(IP, None)])
304 def test_random(self, family, stream):
305 stream = self.__class__.fragments_200
306 super(TestIPv4Reassembly, self).test_random(family, stream)
308 def test_long_fragment_chain(self):
309 """ long fragment chain """
312 "/err/ip4-reassembly-feature/fragment chain too long (drop)"
314 error_cnt = self.statistics.get_err_counter(error_cnt_str)
316 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
317 max_reassembly_length=3,
318 expire_walk_interval_ms=50)
320 p1 = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
321 IP(id=1000, src=self.src_if.remote_ip4,
322 dst=self.dst_if.remote_ip4) /
323 UDP(sport=1234, dport=5678) /
325 p2 = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
326 IP(id=1001, src=self.src_if.remote_ip4,
327 dst=self.dst_if.remote_ip4) /
328 UDP(sport=1234, dport=5678) /
330 frags = fragment_rfc791(p1, 200) + fragment_rfc791(p2, 500)
332 self.pg_enable_capture()
333 self.src_if.add_stream(frags)
336 self.dst_if.get_capture(1)
337 self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
340 """ fragment length + ip header size > 65535 """
341 self.vapi.cli("clear errors")
342 raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
343 '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
344 'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
345 'fset; Test-case: 5737')
347 malformed_packet = (Ether(dst=self.src_if.local_mac,
348 src=self.src_if.remote_mac) /
350 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
351 IP(id=1000, src=self.src_if.remote_ip4,
352 dst=self.dst_if.remote_ip4) /
353 UDP(sport=1234, dport=5678) /
355 valid_fragments = fragment_rfc791(p, 400)
357 self.pg_enable_capture()
358 self.src_if.add_stream([malformed_packet] + valid_fragments)
361 self.dst_if.get_capture(1)
362 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
363 # TODO remove above, uncomment below once clearing of counters
365 # self.assert_packet_counter_equal(
366 # "/err/ip4-reassembly-feature/malformed packets", 1)
368 def test_44924(self):
369 """ compress tiny fragments """
370 packets = [(Ether(dst=self.src_if.local_mac,
371 src=self.src_if.remote_mac) /
372 IP(id=24339, flags="MF", frag=0, ttl=64,
373 src=self.src_if.remote_ip4,
374 dst=self.dst_if.remote_ip4) /
375 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
376 Raw(load='Test-group: IPv4')),
377 (Ether(dst=self.src_if.local_mac,
378 src=self.src_if.remote_mac) /
379 IP(id=24339, flags="MF", frag=3, ttl=64,
380 src=self.src_if.remote_ip4,
381 dst=self.dst_if.remote_ip4) /
382 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
383 Raw(load='.IPv4.Fragmentation.vali')),
384 (Ether(dst=self.src_if.local_mac,
385 src=self.src_if.remote_mac) /
386 IP(id=24339, frag=6, ttl=64,
387 src=self.src_if.remote_ip4,
388 dst=self.dst_if.remote_ip4) /
389 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
390 Raw(load='d; Test-case: 44924'))
393 self.pg_enable_capture()
394 self.src_if.add_stream(packets)
397 self.dst_if.get_capture(1)
399 def test_frag_1(self):
400 """ fragment of size 1 """
401 self.vapi.cli("clear errors")
402 malformed_packets = [(Ether(dst=self.src_if.local_mac,
403 src=self.src_if.remote_mac) /
404 IP(id=7, len=21, flags="MF", frag=0, ttl=64,
405 src=self.src_if.remote_ip4,
406 dst=self.dst_if.remote_ip4) /
407 ICMP(type="echo-request")),
408 (Ether(dst=self.src_if.local_mac,
409 src=self.src_if.remote_mac) /
410 IP(id=7, len=21, frag=1, ttl=64,
411 src=self.src_if.remote_ip4,
412 dst=self.dst_if.remote_ip4) /
416 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
417 IP(id=1000, src=self.src_if.remote_ip4,
418 dst=self.dst_if.remote_ip4) /
419 UDP(sport=1234, dport=5678) /
421 valid_fragments = fragment_rfc791(p, 400)
423 self.pg_enable_capture()
424 self.src_if.add_stream(malformed_packets + valid_fragments)
427 self.dst_if.get_capture(1)
429 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
430 # TODO remove above, uncomment below once clearing of counters
432 # self.assert_packet_counter_equal(
433 # "/err/ip4-reassembly-feature/malformed packets", 1)
435 @parameterized.expand([(IP, None)])
436 def test_duplicates(self, family, stream):
437 """ duplicate fragments """
439 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
440 x for (_, frags, _, _) in self.pkt_infos
442 for _ in range(0, min(2, len(frags)))
444 super(TestIPv4Reassembly, self).test_duplicates(family, fragments)
446 def test_overlap1(self):
447 """ overlapping fragments case #1 """
450 for _, _, frags_300, frags_200 in self.pkt_infos:
451 if len(frags_300) == 1:
452 fragments.extend(frags_300)
454 for i, j in zip(frags_200, frags_300):
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 def test_overlap2(self):
476 """ overlapping fragments case #2 """
479 for _, _, frags_300, frags_200 in self.pkt_infos:
480 if len(frags_300) == 1:
481 fragments.extend(frags_300)
483 # care must be taken here so that there are no fragments
484 # received by vpp after reassembly is finished, otherwise
485 # new reassemblies will be started and packet generator will
486 # freak out when it detects unfreed buffers
487 zipped = zip(frags_300, frags_200)
493 self.pg_enable_capture()
494 self.src_if.add_stream(fragments)
497 packets = self.dst_if.get_capture(len(self.pkt_infos))
498 self.verify_capture(IP, packets)
499 self.src_if.assert_nothing_captured()
501 # run it all to verify correctness
502 self.pg_enable_capture()
503 self.src_if.add_stream(fragments)
506 packets = self.dst_if.get_capture(len(self.pkt_infos))
507 self.verify_capture(IP, packets)
508 self.src_if.assert_nothing_captured()
510 @parameterized.expand([(IP, None, None)])
511 def test_timeout_inline(self, family, stream, dropped_packet_indexes):
512 """ timeout (inline) """
513 stream = self.fragments_400
515 dropped_packet_indexes = set(
516 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
518 super(TestIPv4Reassembly, self).test_timeout_inline(
519 family, stream, dropped_packet_indexes)
521 self.src_if.assert_nothing_captured()
523 def test_timeout_cleanup(self):
524 """ timeout (cleanup) """
526 # whole packets + fragmented packets sans last fragment
528 x for (_, frags_400, _, _) in self.pkt_infos
529 for x in frags_400[:-1 if len(frags_400) > 1 else None]
532 # last fragments for fragmented packets
533 fragments2 = [frags_400[-1]
534 for (_, frags_400, _, _) in self.pkt_infos
535 if len(frags_400) > 1]
537 dropped_packet_indexes = set(
538 index for (index, frags_400, _, _) in self.pkt_infos
539 if len(frags_400) > 1)
541 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
542 max_reassembly_length=1000,
543 expire_walk_interval_ms=50)
545 self.pg_enable_capture()
546 self.src_if.add_stream(fragments)
549 self.sleep(.25, "wait before sending rest of fragments")
551 self.src_if.add_stream(fragments2)
554 packets = self.dst_if.get_capture(
555 len(self.pkt_infos) - len(dropped_packet_indexes))
556 self.verify_capture(IP, packets, dropped_packet_indexes)
557 self.src_if.assert_nothing_captured()
559 @parameterized.expand([(IP, None, None)])
560 def test_disabled(self, family, stream, dropped_packet_indexes):
561 """ reassembly disabled """
563 stream = self.__class__.fragments_400
564 dropped_packet_indexes = set(
565 index for (index, frags_400, _, _) in self.pkt_infos
566 if len(frags_400) > 1)
567 super(TestIPv4Reassembly, self).test_disabled(
568 family, stream, dropped_packet_indexes)
571 class TestIPv6Reassembly(TestIPReassemblyMixin, VppTestCase):
572 """ IPv6 Reassembly """
576 super(TestIPv6Reassembly, cls).setUpClass()
578 cls.create_pg_interfaces([0, 1])
582 # setup all interfaces
583 for i in cls.pg_interfaces:
589 cls.packet_sizes = [64, 512, 1518, 9018]
590 cls.padding = " abcdefghijklmn"
591 cls.create_stream(cls.packet_sizes)
592 cls.create_fragments()
595 def tearDownClass(cls):
596 super(TestIPv6Reassembly, cls).tearDownClass()
599 """ Test setup - force timeout on existing reassemblies """
600 super(TestIPv6Reassembly, self).setUp()
601 self.vapi.ip_reassembly_enable_disable(
602 sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
603 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
604 max_reassembly_length=1000,
605 expire_walk_interval_ms=10, is_ip6=1)
607 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
608 max_reassembly_length=1000,
609 expire_walk_interval_ms=10000, is_ip6=1)
610 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
611 self.logger.debug(self.vapi.ppcli("show buffers"))
614 super(TestIPv6Reassembly, self).tearDown()
616 def show_commands_at_teardown(self):
617 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
618 self.logger.debug(self.vapi.ppcli("show buffers"))
621 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
622 """Create input packet stream for defined interface.
624 :param list packet_sizes: Required packet sizes.
626 for i in range(0, packet_count):
627 info = cls.create_packet_info(cls.src_if, cls.src_if)
628 payload = cls.info_to_payload(info)
629 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
630 IPv6(src=cls.src_if.remote_ip6,
631 dst=cls.dst_if.remote_ip6) /
632 UDP(sport=1234, dport=5678) /
634 size = packet_sizes[(i // 2) % len(packet_sizes)]
635 cls.extend_packet(p, size, cls.padding)
639 def create_fragments(cls):
640 infos = cls._packet_infos
642 for index, info in six.iteritems(infos):
644 # cls.logger.debug(ppp("Packet:",
645 # p.__class__(scapy.compat.raw(p))))
646 fragments_400 = fragment_rfc8200(p, info.index, 400)
647 fragments_300 = fragment_rfc8200(p, info.index, 300)
648 cls.pkt_infos.append((index, fragments_400, fragments_300))
649 cls.fragments_400 = [
650 x for _, frags, _ in cls.pkt_infos for x in frags]
651 cls.fragments_300 = [
652 x for _, _, frags in cls.pkt_infos for x in frags]
653 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
654 "and %s 300-byte fragments" %
655 (len(infos), len(cls.fragments_400),
656 len(cls.fragments_300)))
658 @parameterized.expand([(IPv6, None)])
659 def test_reassembly(self, family, stream):
660 """ basic reassembly """
661 stream = self.__class__.fragments_400
662 super(TestIPv6Reassembly, self).test_reassembly(family, stream)
664 @parameterized.expand([(IPv6, None)])
665 def test_reversed(self, family, stream):
666 """ reverse order reassembly """
667 stream = self.__class__.fragments_400
668 super(TestIPv6Reassembly, self).test_reversed(family, stream)
670 @parameterized.expand([(IPv6, None)])
671 def test_random(self, family, stream):
672 """ random order reassembly """
673 stream = self.__class__.fragments_400
674 super(TestIPv6Reassembly, self).test_random(family, stream)
676 @parameterized.expand([(IPv6, None)])
677 def test_duplicates(self, family, stream):
678 """ duplicate fragments """
681 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
682 x for (_, frags, _) in self.pkt_infos
684 for _ in range(0, min(2, len(frags)))
686 super(TestIPv6Reassembly, self).test_duplicates(family, fragments)
688 def test_long_fragment_chain(self):
689 """ long fragment chain """
692 "/err/ip6-reassembly-feature/fragment chain too long (drop)"
694 error_cnt = self.statistics.get_err_counter(error_cnt_str)
696 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
697 max_reassembly_length=3,
698 expire_walk_interval_ms=50, is_ip6=1)
700 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
701 IPv6(src=self.src_if.remote_ip6,
702 dst=self.dst_if.remote_ip6) /
703 UDP(sport=1234, dport=5678) /
705 frags = fragment_rfc8200(p, 1, 300) + fragment_rfc8200(p, 2, 500)
707 self.pg_enable_capture()
708 self.src_if.add_stream(frags)
711 self.dst_if.get_capture(1)
712 self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
714 def test_overlap1(self):
715 """ overlapping fragments case #1 (differs from IP test case)"""
718 for _, frags_400, frags_300 in self.pkt_infos:
719 if len(frags_300) == 1:
720 fragments.extend(frags_400)
722 for i, j in zip(frags_300, frags_400):
726 dropped_packet_indexes = set(
727 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
730 self.pg_enable_capture()
731 self.src_if.add_stream(fragments)
734 packets = self.dst_if.get_capture(
735 len(self.pkt_infos) - len(dropped_packet_indexes))
736 self.verify_capture(IPv6, packets, dropped_packet_indexes)
737 self.src_if.assert_nothing_captured()
739 def test_overlap2(self):
740 """ overlapping fragments case #2 (differs from IP test case)"""
743 for _, frags_400, frags_300 in self.pkt_infos:
744 if len(frags_400) == 1:
745 fragments.extend(frags_400)
747 # care must be taken here so that there are no fragments
748 # received by vpp after reassembly is finished, otherwise
749 # new reassemblies will be started and packet generator will
750 # freak out when it detects unfreed buffers
751 zipped = zip(frags_400, frags_300)
757 dropped_packet_indexes = set(
758 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
761 self.pg_enable_capture()
762 self.src_if.add_stream(fragments)
765 packets = self.dst_if.get_capture(
766 len(self.pkt_infos) - len(dropped_packet_indexes))
767 self.verify_capture(IPv6, packets, dropped_packet_indexes)
768 self.src_if.assert_nothing_captured()
770 @parameterized.expand([(IPv6, None, None)])
771 def test_timeout_inline(self, family, stream, dropped_packets_index):
772 """ timeout (inline) """
773 stream = self.__class__.fragments_400
775 dropped_packet_indexes = set(
776 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
778 super(TestIPv6Reassembly, self).test_timeout_inline(
779 family, stream, dropped_packet_indexes)
781 pkts = self.src_if.get_capture(
782 expected_count=len(dropped_packet_indexes))
784 self.assertIn(ICMPv6TimeExceeded, icmp)
785 self.assertIn(IPv6ExtHdrFragment, icmp)
786 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
787 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
789 def test_timeout_cleanup(self):
790 """ timeout (cleanup) """
792 # whole packets + fragmented packets sans last fragment
794 x for (_, frags_400, _) in self.pkt_infos
795 for x in frags_400[:-1 if len(frags_400) > 1 else None]
798 # last fragments for fragmented packets
799 fragments2 = [frags_400[-1]
800 for (_, frags_400, _) in self.pkt_infos
801 if len(frags_400) > 1]
803 dropped_packet_indexes = set(
804 index for (index, frags_400, _) in self.pkt_infos
805 if len(frags_400) > 1)
807 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
808 max_reassembly_length=1000,
809 expire_walk_interval_ms=50)
811 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
812 max_reassembly_length=1000,
813 expire_walk_interval_ms=50, is_ip6=1)
815 self.pg_enable_capture()
816 self.src_if.add_stream(fragments)
819 self.sleep(.25, "wait before sending rest of fragments")
821 self.src_if.add_stream(fragments2)
824 packets = self.dst_if.get_capture(
825 len(self.pkt_infos) - len(dropped_packet_indexes))
826 self.verify_capture(IPv6, packets, dropped_packet_indexes)
827 pkts = self.src_if.get_capture(
828 expected_count=len(dropped_packet_indexes))
830 self.assertIn(ICMPv6TimeExceeded, icmp)
831 self.assertIn(IPv6ExtHdrFragment, icmp)
832 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
833 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
835 @parameterized.expand([(IPv6, None, None)])
836 def test_disabled(self, family, stream, dropped_packet_indexes):
837 """ reassembly disabled """
839 stream = self.__class__.fragments_400
840 dropped_packet_indexes = set(
841 index for (index, frags_400, _) in self.pkt_infos
842 if len(frags_400) > 1)
843 super(TestIPv6Reassembly, self).test_disabled(
844 family, stream, dropped_packet_indexes)
845 self.src_if.assert_nothing_captured()
847 def test_missing_upper(self):
848 """ missing upper layer """
849 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
850 IPv6(src=self.src_if.remote_ip6,
851 dst=self.src_if.local_ip6) /
852 UDP(sport=1234, dport=5678) /
854 self.extend_packet(p, 1000, self.padding)
855 fragments = fragment_rfc8200(p, 1, 500)
856 bad_fragment = p.__class__(scapy.compat.raw(fragments[1]))
857 bad_fragment[IPv6ExtHdrFragment].nh = 59
858 bad_fragment[IPv6ExtHdrFragment].offset = 0
859 self.pg_enable_capture()
860 self.src_if.add_stream([bad_fragment])
862 pkts = self.src_if.get_capture(expected_count=1)
864 self.assertIn(ICMPv6ParamProblem, icmp)
865 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
867 def test_invalid_frag_size(self):
868 """ fragment size not a multiple of 8 """
869 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
870 IPv6(src=self.src_if.remote_ip6,
871 dst=self.src_if.local_ip6) /
872 UDP(sport=1234, dport=5678) /
874 self.extend_packet(p, 1000, self.padding)
875 fragments = fragment_rfc8200(p, 1, 500)
876 bad_fragment = fragments[0]
877 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
878 self.pg_enable_capture()
879 self.src_if.add_stream([bad_fragment])
881 pkts = self.src_if.get_capture(expected_count=1)
883 self.assertIn(ICMPv6ParamProblem, icmp)
884 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
886 def test_invalid_packet_size(self):
887 """ total packet size > 65535 """
888 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
889 IPv6(src=self.src_if.remote_ip6,
890 dst=self.src_if.local_ip6) /
891 UDP(sport=1234, dport=5678) /
893 self.extend_packet(p, 1000, self.padding)
894 fragments = fragment_rfc8200(p, 1, 500)
895 bad_fragment = fragments[1]
896 bad_fragment[IPv6ExtHdrFragment].offset = 65500
897 self.pg_enable_capture()
898 self.src_if.add_stream([bad_fragment])
900 pkts = self.src_if.get_capture(expected_count=1)
902 self.assertIn(ICMPv6ParamProblem, icmp)
903 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
906 class TestIPv4ReassemblyLocalNode(VppTestCase):
907 """ IPv4 Reassembly for packets coming to ip4-local node """
911 super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
913 cls.create_pg_interfaces([0])
914 cls.src_dst_if = cls.pg0
916 # setup all interfaces
917 for i in cls.pg_interfaces:
922 cls.padding = " abcdefghijklmn"
924 cls.create_fragments()
927 def tearDownClass(cls):
928 super(TestIPv4ReassemblyLocalNode, cls).tearDownClass()
931 """ Test setup - force timeout on existing reassemblies """
932 super(TestIPv4ReassemblyLocalNode, self).setUp()
933 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
934 max_reassembly_length=1000,
935 expire_walk_interval_ms=10)
937 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
938 max_reassembly_length=1000,
939 expire_walk_interval_ms=10000)
942 super(TestIPv4ReassemblyLocalNode, self).tearDown()
944 def show_commands_at_teardown(self):
945 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
946 self.logger.debug(self.vapi.ppcli("show buffers"))
949 def create_stream(cls, packet_count=test_packet_count):
950 """Create input packet stream for defined interface.
952 :param list packet_sizes: Required packet sizes.
954 for i in range(0, packet_count):
955 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
956 payload = cls.info_to_payload(info)
957 p = (Ether(dst=cls.src_dst_if.local_mac,
958 src=cls.src_dst_if.remote_mac) /
959 IP(id=info.index, src=cls.src_dst_if.remote_ip4,
960 dst=cls.src_dst_if.local_ip4) /
961 ICMP(type='echo-request', id=1234) /
963 cls.extend_packet(p, 1518, cls.padding)
967 def create_fragments(cls):
968 infos = cls._packet_infos
970 for index, info in six.iteritems(infos):
972 # cls.logger.debug(ppp("Packet:",
973 # p.__class__(scapy.compat.raw(p))))
974 fragments_300 = fragment_rfc791(p, 300)
975 cls.pkt_infos.append((index, fragments_300))
976 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
977 cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
978 (len(infos), len(cls.fragments_300)))
980 def verify_capture(self, capture):
981 """Verify captured packet stream.
983 :param list capture: Captured packet stream.
987 for packet in capture:
989 self.logger.debug(ppp("Got packet:", packet))
992 payload_info = self.payload_to_info(packet[Raw])
993 packet_index = payload_info.index
994 if packet_index in seen:
995 raise Exception(ppp("Duplicate packet received", packet))
996 seen.add(packet_index)
997 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
998 info = self._packet_infos[packet_index]
999 self.assertIsNotNone(info)
1000 self.assertEqual(packet_index, info.index)
1001 saved_packet = info.data
1002 self.assertEqual(ip.src, saved_packet[IP].dst)
1003 self.assertEqual(ip.dst, saved_packet[IP].src)
1004 self.assertEqual(icmp.type, 0) # echo reply
1005 self.assertEqual(icmp.id, saved_packet[ICMP].id)
1006 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
1008 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1010 for index in self._packet_infos:
1011 self.assertIn(index, seen,
1012 "Packet with packet_index %d not received" % index)
1014 def test_reassembly(self):
1015 """ basic reassembly """
1017 self.pg_enable_capture()
1018 self.src_dst_if.add_stream(self.fragments_300)
1021 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1022 self.verify_capture(packets)
1024 # run it all again to verify correctness
1025 self.pg_enable_capture()
1026 self.src_dst_if.add_stream(self.fragments_300)
1029 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1030 self.verify_capture(packets)
1033 class TestFIFReassembly(VppTestCase):
1034 """ Fragments in fragments reassembly """
1037 def setUpClass(cls):
1038 super(TestFIFReassembly, cls).setUpClass()
1040 cls.create_pg_interfaces([0, 1])
1041 cls.src_if = cls.pg0
1042 cls.dst_if = cls.pg1
1043 for i in cls.pg_interfaces:
1050 cls.packet_sizes = [64, 512, 1518, 9018]
1051 cls.padding = " abcdefghijklmn"
1054 def tearDownClass(cls):
1055 super(TestFIFReassembly, cls).tearDownClass()
1058 """ Test setup - force timeout on existing reassemblies """
1059 super(TestFIFReassembly, self).setUp()
1060 self.vapi.ip_reassembly_enable_disable(
1061 sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
1063 self.vapi.ip_reassembly_enable_disable(
1064 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
1066 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1067 max_reassembly_length=1000,
1068 expire_walk_interval_ms=10)
1069 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1070 max_reassembly_length=1000,
1071 expire_walk_interval_ms=10, is_ip6=1)
1073 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1074 max_reassembly_length=1000,
1075 expire_walk_interval_ms=10000)
1076 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1077 max_reassembly_length=1000,
1078 expire_walk_interval_ms=10000, is_ip6=1)
1081 super(TestFIFReassembly, self).tearDown()
1083 def show_commands_at_teardown(self):
1084 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
1085 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
1086 self.logger.debug(self.vapi.ppcli("show buffers"))
1088 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
1089 """Verify captured packet stream.
1091 :param list capture: Captured packet stream.
1095 for packet in capture:
1097 self.logger.debug(ppp("Got packet:", packet))
1098 ip = packet[ip_class]
1100 payload_info = self.payload_to_info(packet[Raw])
1101 packet_index = payload_info.index
1103 packet_index not in dropped_packet_indexes,
1104 ppp("Packet received, but should be dropped:", packet))
1105 if packet_index in seen:
1106 raise Exception(ppp("Duplicate packet received", packet))
1107 seen.add(packet_index)
1108 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1109 info = self._packet_infos[packet_index]
1110 self.assertTrue(info is not None)
1111 self.assertEqual(packet_index, info.index)
1112 saved_packet = info.data
1113 self.assertEqual(ip.src, saved_packet[ip_class].src)
1114 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1115 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1117 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1119 for index in self._packet_infos:
1120 self.assertTrue(index in seen or index in dropped_packet_indexes,
1121 "Packet with packet_index %d not received" % index)
1123 def test_fif4(self):
1124 """ Fragments in fragments (4o4) """
1126 # TODO this should be ideally in setUpClass, but then we hit a bug
1127 # with VppIpRoute incorrectly reporting it's present when it's not
1128 # so we need to manually remove the vpp config, thus we cannot have
1129 # it shared for multiple test cases
1130 self.tun_ip4 = "1.1.1.2"
1132 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1133 self.gre4.add_vpp_config()
1134 self.gre4.admin_up()
1135 self.gre4.config_ip4()
1137 self.vapi.ip_reassembly_enable_disable(
1138 sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1140 self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1141 [VppRoutePath(self.src_if.remote_ip4,
1142 self.src_if.sw_if_index)])
1143 self.route4.add_vpp_config()
1145 self.reset_packet_infos()
1146 for i in range(test_packet_count):
1147 info = self.create_packet_info(self.src_if, self.dst_if)
1148 payload = self.info_to_payload(info)
1149 # Ethernet header here is only for size calculation, thus it
1150 # doesn't matter how it's initialized. This is to ensure that
1151 # reassembled packet is not > 9000 bytes, so that it's not dropped
1153 IP(id=i, src=self.src_if.remote_ip4,
1154 dst=self.dst_if.remote_ip4) /
1155 UDP(sport=1234, dport=5678) /
1157 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1158 self.extend_packet(p, size, self.padding)
1159 info.data = p[IP] # use only IP part, without ethernet header
1161 fragments = [x for _, p in six.iteritems(self._packet_infos)
1162 for x in fragment_rfc791(p.data, 400)]
1164 encapped_fragments = \
1165 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1166 IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1171 fragmented_encapped_fragments = \
1172 [x for p in encapped_fragments
1173 for x in fragment_rfc791(p, 200)]
1175 self.src_if.add_stream(fragmented_encapped_fragments)
1177 self.pg_enable_capture(self.pg_interfaces)
1180 self.src_if.assert_nothing_captured()
1181 packets = self.dst_if.get_capture(len(self._packet_infos))
1182 self.verify_capture(packets, IP)
1184 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1185 # so that it's query_vpp_config() works as it should
1186 self.gre4.remove_vpp_config()
1187 self.logger.debug(self.vapi.ppcli("show interface"))
1189 def test_fif6(self):
1190 """ Fragments in fragments (6o6) """
1191 # TODO this should be ideally in setUpClass, but then we hit a bug
1192 # with VppIpRoute incorrectly reporting it's present when it's not
1193 # so we need to manually remove the vpp config, thus we cannot have
1194 # it shared for multiple test cases
1195 self.tun_ip6 = "1002::1"
1197 self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
1198 self.gre6.add_vpp_config()
1199 self.gre6.admin_up()
1200 self.gre6.config_ip6()
1202 self.vapi.ip_reassembly_enable_disable(
1203 sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1205 self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1207 self.src_if.remote_ip6,
1208 self.src_if.sw_if_index)])
1209 self.route6.add_vpp_config()
1211 self.reset_packet_infos()
1212 for i in range(test_packet_count):
1213 info = self.create_packet_info(self.src_if, self.dst_if)
1214 payload = self.info_to_payload(info)
1215 # Ethernet header here is only for size calculation, thus it
1216 # doesn't matter how it's initialized. This is to ensure that
1217 # reassembled packet is not > 9000 bytes, so that it's not dropped
1219 IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1220 UDP(sport=1234, dport=5678) /
1222 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1223 self.extend_packet(p, size, self.padding)
1224 info.data = p[IPv6] # use only IPv6 part, without ethernet header
1226 fragments = [x for _, i in six.iteritems(self._packet_infos)
1227 for x in fragment_rfc8200(
1228 i.data, i.index, 400)]
1230 encapped_fragments = \
1231 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1232 IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1237 fragmented_encapped_fragments = \
1238 [x for p in encapped_fragments for x in (
1241 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1243 if IPv6ExtHdrFragment in p else [p]
1247 self.src_if.add_stream(fragmented_encapped_fragments)
1249 self.pg_enable_capture(self.pg_interfaces)
1252 self.src_if.assert_nothing_captured()
1253 packets = self.dst_if.get_capture(len(self._packet_infos))
1254 self.verify_capture(packets, IPv6)
1256 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1257 # so that it's query_vpp_config() works as it should
1258 self.gre6.remove_vpp_config()
1261 if __name__ == '__main__':
1262 unittest.main(testRunner=VppTestRunner)