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 """ Test setup - force timeout on existing reassemblies """
224 super(TestIPv4Reassembly, self).setUp()
225 self.vapi.ip_reassembly_enable_disable(
226 sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
227 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
228 expire_walk_interval_ms=10)
230 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
231 expire_walk_interval_ms=10000)
234 super(TestIPv4Reassembly, self).tearDown()
235 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
236 self.logger.debug(self.vapi.ppcli("show buffers"))
239 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
240 """Create input packet stream
242 :param list packet_sizes: Required packet sizes.
244 for i in range(0, packet_count):
245 info = cls.create_packet_info(cls.src_if, cls.src_if)
246 payload = cls.info_to_payload(info)
247 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
248 IP(id=info.index, src=cls.src_if.remote_ip4,
249 dst=cls.dst_if.remote_ip4) /
250 UDP(sport=1234, dport=5678) /
252 size = packet_sizes[(i // 2) % len(packet_sizes)]
253 cls.extend_packet(p, size, cls.padding)
257 def create_fragments(cls):
258 infos = cls._packet_infos
260 for index, info in six.iteritems(infos):
262 # cls.logger.debug(ppp("Packet:",
263 # p.__class__(scapy.compat.raw(p))))
264 fragments_400 = fragment_rfc791(p, 400)
265 fragments_300 = fragment_rfc791(p, 300)
267 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
268 cls.pkt_infos.append(
269 (index, fragments_400, fragments_300, fragments_200))
270 cls.fragments_400 = [
271 x for (_, frags, _, _) in cls.pkt_infos for x in frags]
272 cls.fragments_300 = [
273 x for (_, _, frags, _) in cls.pkt_infos for x in frags]
274 cls.fragments_200 = [
275 x for (_, _, _, frags) in cls.pkt_infos for x in frags]
276 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
277 "%s 300-byte fragments and %s 200-byte fragments" %
278 (len(infos), len(cls.fragments_400),
279 len(cls.fragments_300), len(cls.fragments_200)))
281 @parameterized.expand([(IP, None)])
282 def test_reassembly(self, family, stream):
283 """ basic reassembly """
284 stream = self.__class__.fragments_200
285 super(TestIPv4Reassembly, self).test_reassembly(family, stream)
287 @parameterized.expand([(IP, None)])
288 def test_reversed(self, family, stream):
289 """ reverse order reassembly """
290 stream = self.__class__.fragments_200
291 super(TestIPv4Reassembly, self).test_reversed(family, stream)
293 @parameterized.expand([(IP, None)])
294 def test_random(self, family, stream):
295 stream = self.__class__.fragments_200
296 super(TestIPv4Reassembly, self).test_random(family, stream)
299 """ fragment length + ip header size > 65535 """
300 self.vapi.cli("clear errors")
301 raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
302 '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
303 'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
304 'fset; Test-case: 5737')
306 malformed_packet = (Ether(dst=self.src_if.local_mac,
307 src=self.src_if.remote_mac) /
309 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
310 IP(id=1000, src=self.src_if.remote_ip4,
311 dst=self.dst_if.remote_ip4) /
312 UDP(sport=1234, dport=5678) /
314 valid_fragments = fragment_rfc791(p, 400)
316 self.pg_enable_capture()
317 self.src_if.add_stream([malformed_packet] + valid_fragments)
320 self.dst_if.get_capture(1)
321 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
322 # TODO remove above, uncomment below once clearing of counters
324 # self.assert_packet_counter_equal(
325 # "/err/ip4-reassembly-feature/malformed packets", 1)
327 def test_44924(self):
328 """ compress tiny fragments """
329 packets = [(Ether(dst=self.src_if.local_mac,
330 src=self.src_if.remote_mac) /
331 IP(id=24339, flags="MF", frag=0, ttl=64,
332 src=self.src_if.remote_ip4,
333 dst=self.dst_if.remote_ip4) /
334 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
335 Raw(load='Test-group: IPv4')),
336 (Ether(dst=self.src_if.local_mac,
337 src=self.src_if.remote_mac) /
338 IP(id=24339, flags="MF", frag=3, ttl=64,
339 src=self.src_if.remote_ip4,
340 dst=self.dst_if.remote_ip4) /
341 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
342 Raw(load='.IPv4.Fragmentation.vali')),
343 (Ether(dst=self.src_if.local_mac,
344 src=self.src_if.remote_mac) /
345 IP(id=24339, frag=6, ttl=64,
346 src=self.src_if.remote_ip4,
347 dst=self.dst_if.remote_ip4) /
348 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
349 Raw(load='d; Test-case: 44924'))
352 self.pg_enable_capture()
353 self.src_if.add_stream(packets)
356 self.dst_if.get_capture(1)
358 def test_frag_1(self):
359 """ fragment of size 1 """
360 self.vapi.cli("clear errors")
361 malformed_packets = [(Ether(dst=self.src_if.local_mac,
362 src=self.src_if.remote_mac) /
363 IP(id=7, len=21, flags="MF", frag=0, ttl=64,
364 src=self.src_if.remote_ip4,
365 dst=self.dst_if.remote_ip4) /
366 ICMP(type="echo-request")),
367 (Ether(dst=self.src_if.local_mac,
368 src=self.src_if.remote_mac) /
369 IP(id=7, len=21, frag=1, ttl=64,
370 src=self.src_if.remote_ip4,
371 dst=self.dst_if.remote_ip4) /
375 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
376 IP(id=1000, src=self.src_if.remote_ip4,
377 dst=self.dst_if.remote_ip4) /
378 UDP(sport=1234, dport=5678) /
380 valid_fragments = fragment_rfc791(p, 400)
382 self.pg_enable_capture()
383 self.src_if.add_stream(malformed_packets + valid_fragments)
386 self.dst_if.get_capture(1)
388 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
389 # TODO remove above, uncomment below once clearing of counters
391 # self.assert_packet_counter_equal(
392 # "/err/ip4-reassembly-feature/malformed packets", 1)
394 @parameterized.expand([(IP, None)])
395 def test_duplicates(self, family, stream):
396 """ duplicate fragments """
398 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
399 x for (_, frags, _, _) in self.pkt_infos
401 for _ in range(0, min(2, len(frags)))
403 super(TestIPv4Reassembly, self).test_duplicates(family, fragments)
405 def test_overlap1(self):
406 """ overlapping fragments case #1 """
409 for _, _, frags_300, frags_200 in self.pkt_infos:
410 if len(frags_300) == 1:
411 fragments.extend(frags_300)
413 for i, j in zip(frags_200, frags_300):
417 self.pg_enable_capture()
418 self.src_if.add_stream(fragments)
421 packets = self.dst_if.get_capture(len(self.pkt_infos))
422 self.verify_capture(IP, packets)
423 self.src_if.assert_nothing_captured()
425 # run it all to verify correctness
426 self.pg_enable_capture()
427 self.src_if.add_stream(fragments)
430 packets = self.dst_if.get_capture(len(self.pkt_infos))
431 self.verify_capture(IP, packets)
432 self.src_if.assert_nothing_captured()
434 def test_overlap2(self):
435 """ overlapping fragments case #2 """
438 for _, _, frags_300, frags_200 in self.pkt_infos:
439 if len(frags_300) == 1:
440 fragments.extend(frags_300)
442 # care must be taken here so that there are no fragments
443 # received by vpp after reassembly is finished, otherwise
444 # new reassemblies will be started and packet generator will
445 # freak out when it detects unfreed buffers
446 zipped = zip(frags_300, frags_200)
447 for i, j in zipped[:-1]:
450 fragments.append(zipped[-1][0])
452 self.pg_enable_capture()
453 self.src_if.add_stream(fragments)
456 packets = self.dst_if.get_capture(len(self.pkt_infos))
457 self.verify_capture(IP, packets)
458 self.src_if.assert_nothing_captured()
460 # run it all to verify correctness
461 self.pg_enable_capture()
462 self.src_if.add_stream(fragments)
465 packets = self.dst_if.get_capture(len(self.pkt_infos))
466 self.verify_capture(IP, packets)
467 self.src_if.assert_nothing_captured()
469 @parameterized.expand([(IP, None, None)])
470 def test_timeout_inline(self, family, stream, dropped_packet_indexes):
471 """ timeout (inline) """
472 stream = self.fragments_400
474 dropped_packet_indexes = set(
475 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
477 super(TestIPv4Reassembly, self).test_timeout_inline(
478 family, stream, dropped_packet_indexes)
480 self.src_if.assert_nothing_captured()
482 def test_timeout_cleanup(self):
483 """ timeout (cleanup) """
485 # whole packets + fragmented packets sans last fragment
487 x for (_, frags_400, _, _) in self.pkt_infos
488 for x in frags_400[:-1 if len(frags_400) > 1 else None]
491 # last fragments for fragmented packets
492 fragments2 = [frags_400[-1]
493 for (_, frags_400, _, _) in self.pkt_infos
494 if len(frags_400) > 1]
496 dropped_packet_indexes = set(
497 index for (index, frags_400, _, _) in self.pkt_infos
498 if len(frags_400) > 1)
500 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
501 expire_walk_interval_ms=50)
503 self.pg_enable_capture()
504 self.src_if.add_stream(fragments)
507 self.sleep(.25, "wait before sending rest of fragments")
509 self.src_if.add_stream(fragments2)
512 packets = self.dst_if.get_capture(
513 len(self.pkt_infos) - len(dropped_packet_indexes))
514 self.verify_capture(IP, packets, dropped_packet_indexes)
515 self.src_if.assert_nothing_captured()
517 @parameterized.expand([(IP, None, None)])
518 def test_disabled(self, family, stream, dropped_packet_indexes):
519 """ reassembly disabled """
521 stream = self.__class__.fragments_400
522 dropped_packet_indexes = set(
523 index for (index, frags_400, _, _) in self.pkt_infos
524 if len(frags_400) > 1)
525 super(TestIPv4Reassembly, self).test_disabled(
526 family, stream, dropped_packet_indexes)
529 class TestIPv6Reassembly(TestIPReassemblyMixin, VppTestCase):
530 """ IPv6 Reassembly """
534 super(TestIPv6Reassembly, cls).setUpClass()
536 cls.create_pg_interfaces([0, 1])
540 # setup all interfaces
541 for i in cls.pg_interfaces:
547 cls.packet_sizes = [64, 512, 1518, 9018]
548 cls.padding = " abcdefghijklmn"
549 cls.create_stream(cls.packet_sizes)
550 cls.create_fragments()
553 """ Test setup - force timeout on existing reassemblies """
554 super(TestIPv6Reassembly, self).setUp()
555 self.vapi.ip_reassembly_enable_disable(
556 sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
557 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
558 expire_walk_interval_ms=10, is_ip6=1)
560 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
561 expire_walk_interval_ms=10000, is_ip6=1)
562 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
563 self.logger.debug(self.vapi.ppcli("show buffers"))
566 super(TestIPv6Reassembly, self).tearDown()
567 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
568 self.logger.debug(self.vapi.ppcli("show buffers"))
571 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
572 """Create input packet stream for defined interface.
574 :param list packet_sizes: Required packet sizes.
576 for i in range(0, packet_count):
577 info = cls.create_packet_info(cls.src_if, cls.src_if)
578 payload = cls.info_to_payload(info)
579 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
580 IPv6(src=cls.src_if.remote_ip6,
581 dst=cls.dst_if.remote_ip6) /
582 UDP(sport=1234, dport=5678) /
584 size = packet_sizes[(i // 2) % len(packet_sizes)]
585 cls.extend_packet(p, size, cls.padding)
589 def create_fragments(cls):
590 infos = cls._packet_infos
592 for index, info in six.iteritems(infos):
594 # cls.logger.debug(ppp("Packet:",
595 # p.__class__(scapy.compat.raw(p))))
596 fragments_400 = fragment_rfc8200(p, info.index, 400)
597 fragments_300 = fragment_rfc8200(p, info.index, 300)
598 cls.pkt_infos.append((index, fragments_400, fragments_300))
599 cls.fragments_400 = [
600 x for _, frags, _ in cls.pkt_infos for x in frags]
601 cls.fragments_300 = [
602 x for _, _, frags in cls.pkt_infos for x in frags]
603 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
604 "and %s 300-byte fragments" %
605 (len(infos), len(cls.fragments_400),
606 len(cls.fragments_300)))
608 @parameterized.expand([(IPv6, None)])
609 def test_reassembly(self, family, stream):
610 """ basic reassembly """
611 stream = self.__class__.fragments_400
612 super(TestIPv6Reassembly, self).test_reassembly(family, stream)
614 @parameterized.expand([(IPv6, None)])
615 def test_reversed(self, family, stream):
616 """ reverse order reassembly """
617 stream = self.__class__.fragments_400
618 super(TestIPv6Reassembly, self).test_reversed(family, stream)
620 @parameterized.expand([(IPv6, None)])
621 def test_random(self, family, stream):
622 """ random order reassembly """
623 stream = self.__class__.fragments_400
624 super(TestIPv6Reassembly, self).test_random(family, stream)
626 @parameterized.expand([(IPv6, None)])
627 def test_duplicates(self, family, stream):
628 """ duplicate fragments """
631 # IPv4 uses 4 fields in pkt_infos, IPv6 uses 3.
632 x for (_, frags, _) in self.pkt_infos
634 for _ in range(0, min(2, len(frags)))
636 super(TestIPv6Reassembly, self).test_duplicates(family, fragments)
638 def test_overlap1(self):
639 """ overlapping fragments case #1 (differs from IP test case)"""
642 for _, frags_400, frags_300 in self.pkt_infos:
643 if len(frags_300) == 1:
644 fragments.extend(frags_400)
646 for i, j in zip(frags_300, frags_400):
650 dropped_packet_indexes = set(
651 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
654 self.pg_enable_capture()
655 self.src_if.add_stream(fragments)
658 packets = self.dst_if.get_capture(
659 len(self.pkt_infos) - len(dropped_packet_indexes))
660 self.verify_capture(IPv6, packets, dropped_packet_indexes)
661 self.src_if.assert_nothing_captured()
663 def test_overlap2(self):
664 """ overlapping fragments case #2 (differs from IP test case)"""
667 for _, frags_400, frags_300 in self.pkt_infos:
668 if len(frags_400) == 1:
669 fragments.extend(frags_400)
671 # care must be taken here so that there are no fragments
672 # received by vpp after reassembly is finished, otherwise
673 # new reassemblies will be started and packet generator will
674 # freak out when it detects unfreed buffers
675 zipped = zip(frags_400, frags_300)
676 for i, j in zipped[:-1]:
679 fragments.append(zipped[-1][0])
681 dropped_packet_indexes = set(
682 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
685 self.pg_enable_capture()
686 self.src_if.add_stream(fragments)
689 packets = self.dst_if.get_capture(
690 len(self.pkt_infos) - len(dropped_packet_indexes))
691 self.verify_capture(IPv6, packets, dropped_packet_indexes)
692 self.src_if.assert_nothing_captured()
694 @parameterized.expand([(IPv6, None, None)])
695 def test_timeout_inline(self, family, stream, dropped_packets_index):
696 """ timeout (inline) """
697 stream = self.__class__.fragments_400
699 dropped_packet_indexes = set(
700 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
702 super(TestIPv6Reassembly, self).test_timeout_inline(
703 family, stream, dropped_packet_indexes)
705 pkts = self.src_if.get_capture(
706 expected_count=len(dropped_packet_indexes))
708 self.assertIn(ICMPv6TimeExceeded, icmp)
709 self.assertIn(IPv6ExtHdrFragment, icmp)
710 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
711 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
713 def test_timeout_cleanup(self):
714 """ timeout (cleanup) """
716 # whole packets + fragmented packets sans last fragment
718 x for (_, frags_400, _) in self.pkt_infos
719 for x in frags_400[:-1 if len(frags_400) > 1 else None]
722 # last fragments for fragmented packets
723 fragments2 = [frags_400[-1]
724 for (_, frags_400, _) in self.pkt_infos
725 if len(frags_400) > 1]
727 dropped_packet_indexes = set(
728 index for (index, frags_400, _) in self.pkt_infos
729 if len(frags_400) > 1)
731 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
732 expire_walk_interval_ms=50)
734 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
735 expire_walk_interval_ms=50, is_ip6=1)
737 self.pg_enable_capture()
738 self.src_if.add_stream(fragments)
741 self.sleep(.25, "wait before sending rest of fragments")
743 self.src_if.add_stream(fragments2)
746 packets = self.dst_if.get_capture(
747 len(self.pkt_infos) - len(dropped_packet_indexes))
748 self.verify_capture(IPv6, packets, dropped_packet_indexes)
749 pkts = self.src_if.get_capture(
750 expected_count=len(dropped_packet_indexes))
752 self.assertIn(ICMPv6TimeExceeded, icmp)
753 self.assertIn(IPv6ExtHdrFragment, icmp)
754 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
755 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
757 @parameterized.expand([(IPv6, None, None)])
758 def test_disabled(self, family, stream, dropped_packet_indexes):
759 """ reassembly disabled """
761 stream = self.__class__.fragments_400
762 dropped_packet_indexes = set(
763 index for (index, frags_400, _) in self.pkt_infos
764 if len(frags_400) > 1)
765 super(TestIPv6Reassembly, self).test_disabled(
766 family, stream, dropped_packet_indexes)
767 self.src_if.assert_nothing_captured()
769 def test_missing_upper(self):
770 """ missing upper layer """
771 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
772 IPv6(src=self.src_if.remote_ip6,
773 dst=self.src_if.local_ip6) /
774 UDP(sport=1234, dport=5678) /
776 self.extend_packet(p, 1000, self.padding)
777 fragments = fragment_rfc8200(p, 1, 500)
778 bad_fragment = p.__class__(scapy.compat.raw(fragments[1]))
779 bad_fragment[IPv6ExtHdrFragment].nh = 59
780 bad_fragment[IPv6ExtHdrFragment].offset = 0
781 self.pg_enable_capture()
782 self.src_if.add_stream([bad_fragment])
784 pkts = self.src_if.get_capture(expected_count=1)
786 self.assertIn(ICMPv6ParamProblem, icmp)
787 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
789 def test_invalid_frag_size(self):
790 """ fragment size not a multiple of 8 """
791 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
792 IPv6(src=self.src_if.remote_ip6,
793 dst=self.src_if.local_ip6) /
794 UDP(sport=1234, dport=5678) /
796 self.extend_packet(p, 1000, self.padding)
797 fragments = fragment_rfc8200(p, 1, 500)
798 bad_fragment = fragments[0]
799 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
800 self.pg_enable_capture()
801 self.src_if.add_stream([bad_fragment])
803 pkts = self.src_if.get_capture(expected_count=1)
805 self.assertIn(ICMPv6ParamProblem, icmp)
806 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
808 def test_invalid_packet_size(self):
809 """ total packet size > 65535 """
810 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
811 IPv6(src=self.src_if.remote_ip6,
812 dst=self.src_if.local_ip6) /
813 UDP(sport=1234, dport=5678) /
815 self.extend_packet(p, 1000, self.padding)
816 fragments = fragment_rfc8200(p, 1, 500)
817 bad_fragment = fragments[1]
818 bad_fragment[IPv6ExtHdrFragment].offset = 65500
819 self.pg_enable_capture()
820 self.src_if.add_stream([bad_fragment])
822 pkts = self.src_if.get_capture(expected_count=1)
824 self.assertIn(ICMPv6ParamProblem, icmp)
825 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
828 class TestIPv4ReassemblyLocalNode(VppTestCase):
829 """ IPv4 Reassembly for packets coming to ip4-local node """
833 super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
835 cls.create_pg_interfaces([0])
836 cls.src_dst_if = cls.pg0
838 # setup all interfaces
839 for i in cls.pg_interfaces:
844 cls.padding = " abcdefghijklmn"
846 cls.create_fragments()
849 """ Test setup - force timeout on existing reassemblies """
850 super(TestIPv4ReassemblyLocalNode, self).setUp()
851 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
852 expire_walk_interval_ms=10)
854 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
855 expire_walk_interval_ms=10000)
858 super(TestIPv4ReassemblyLocalNode, self).tearDown()
859 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
860 self.logger.debug(self.vapi.ppcli("show buffers"))
863 def create_stream(cls, packet_count=test_packet_count):
864 """Create input packet stream for defined interface.
866 :param list packet_sizes: Required packet sizes.
868 for i in range(0, packet_count):
869 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
870 payload = cls.info_to_payload(info)
871 p = (Ether(dst=cls.src_dst_if.local_mac,
872 src=cls.src_dst_if.remote_mac) /
873 IP(id=info.index, src=cls.src_dst_if.remote_ip4,
874 dst=cls.src_dst_if.local_ip4) /
875 ICMP(type='echo-request', id=1234) /
877 cls.extend_packet(p, 1518, cls.padding)
881 def create_fragments(cls):
882 infos = cls._packet_infos
884 for index, info in six.iteritems(infos):
886 # cls.logger.debug(ppp("Packet:",
887 # p.__class__(scapy.compat.raw(p))))
888 fragments_300 = fragment_rfc791(p, 300)
889 cls.pkt_infos.append((index, fragments_300))
890 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
891 cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
892 (len(infos), len(cls.fragments_300)))
894 def verify_capture(self, capture):
895 """Verify captured packet stream.
897 :param list capture: Captured packet stream.
901 for packet in capture:
903 self.logger.debug(ppp("Got packet:", packet))
906 payload_info = self.payload_to_info(packet[Raw])
907 packet_index = payload_info.index
908 if packet_index in seen:
909 raise Exception(ppp("Duplicate packet received", packet))
910 seen.add(packet_index)
911 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
912 info = self._packet_infos[packet_index]
913 self.assertIsNotNone(info)
914 self.assertEqual(packet_index, info.index)
915 saved_packet = info.data
916 self.assertEqual(ip.src, saved_packet[IP].dst)
917 self.assertEqual(ip.dst, saved_packet[IP].src)
918 self.assertEqual(icmp.type, 0) # echo reply
919 self.assertEqual(icmp.id, saved_packet[ICMP].id)
920 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
922 self.logger.error(ppp("Unexpected or invalid packet:", packet))
924 for index in self._packet_infos:
925 self.assertIn(index, seen,
926 "Packet with packet_index %d not received" % index)
928 def test_reassembly(self):
929 """ basic reassembly """
931 self.pg_enable_capture()
932 self.src_dst_if.add_stream(self.fragments_300)
935 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
936 self.verify_capture(packets)
938 # run it all again to verify correctness
939 self.pg_enable_capture()
940 self.src_dst_if.add_stream(self.fragments_300)
943 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
944 self.verify_capture(packets)
947 class TestFIFReassembly(VppTestCase):
948 """ Fragments in fragments reassembly """
952 super(TestFIFReassembly, cls).setUpClass()
954 cls.create_pg_interfaces([0, 1])
957 for i in cls.pg_interfaces:
964 cls.packet_sizes = [64, 512, 1518, 9018]
965 cls.padding = " abcdefghijklmn"
968 """ Test setup - force timeout on existing reassemblies """
969 super(TestFIFReassembly, self).setUp()
970 self.vapi.ip_reassembly_enable_disable(
971 sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
973 self.vapi.ip_reassembly_enable_disable(
974 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
976 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
977 expire_walk_interval_ms=10)
978 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
979 expire_walk_interval_ms=10, is_ip6=1)
981 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
982 expire_walk_interval_ms=10000)
983 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
984 expire_walk_interval_ms=10000, is_ip6=1)
987 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
988 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
989 self.logger.debug(self.vapi.ppcli("show buffers"))
990 super(TestFIFReassembly, self).tearDown()
992 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
993 """Verify captured packet stream.
995 :param list capture: Captured packet stream.
999 for packet in capture:
1001 self.logger.debug(ppp("Got packet:", packet))
1002 ip = packet[ip_class]
1004 payload_info = self.payload_to_info(packet[Raw])
1005 packet_index = payload_info.index
1007 packet_index not in dropped_packet_indexes,
1008 ppp("Packet received, but should be dropped:", packet))
1009 if packet_index in seen:
1010 raise Exception(ppp("Duplicate packet received", packet))
1011 seen.add(packet_index)
1012 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1013 info = self._packet_infos[packet_index]
1014 self.assertTrue(info is not None)
1015 self.assertEqual(packet_index, info.index)
1016 saved_packet = info.data
1017 self.assertEqual(ip.src, saved_packet[ip_class].src)
1018 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1019 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1021 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1023 for index in self._packet_infos:
1024 self.assertTrue(index in seen or index in dropped_packet_indexes,
1025 "Packet with packet_index %d not received" % index)
1027 def test_fif4(self):
1028 """ Fragments in fragments (4o4) """
1030 # TODO this should be ideally in setUpClass, but then we hit a bug
1031 # with VppIpRoute incorrectly reporting it's present when it's not
1032 # so we need to manually remove the vpp config, thus we cannot have
1033 # it shared for multiple test cases
1034 self.tun_ip4 = "1.1.1.2"
1036 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1037 self.gre4.add_vpp_config()
1038 self.gre4.admin_up()
1039 self.gre4.config_ip4()
1041 self.vapi.ip_reassembly_enable_disable(
1042 sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1044 self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1045 [VppRoutePath(self.src_if.remote_ip4,
1046 self.src_if.sw_if_index)])
1047 self.route4.add_vpp_config()
1049 self.reset_packet_infos()
1050 for i in range(test_packet_count):
1051 info = self.create_packet_info(self.src_if, self.dst_if)
1052 payload = self.info_to_payload(info)
1053 # Ethernet header here is only for size calculation, thus it
1054 # doesn't matter how it's initialized. This is to ensure that
1055 # reassembled packet is not > 9000 bytes, so that it's not dropped
1057 IP(id=i, src=self.src_if.remote_ip4,
1058 dst=self.dst_if.remote_ip4) /
1059 UDP(sport=1234, dport=5678) /
1061 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1062 self.extend_packet(p, size, self.padding)
1063 info.data = p[IP] # use only IP part, without ethernet header
1065 fragments = [x for _, p in six.iteritems(self._packet_infos)
1066 for x in fragment_rfc791(p.data, 400)]
1068 encapped_fragments = \
1069 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1070 IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1075 fragmented_encapped_fragments = \
1076 [x for p in encapped_fragments
1077 for x in fragment_rfc791(p, 200)]
1079 self.src_if.add_stream(fragmented_encapped_fragments)
1081 self.pg_enable_capture(self.pg_interfaces)
1084 self.src_if.assert_nothing_captured()
1085 packets = self.dst_if.get_capture(len(self._packet_infos))
1086 self.verify_capture(packets, IP)
1088 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1089 # so that it's query_vpp_config() works as it should
1090 self.gre4.remove_vpp_config()
1091 self.logger.debug(self.vapi.ppcli("show interface"))
1093 def test_fif6(self):
1094 """ Fragments in fragments (6o6) """
1095 # TODO this should be ideally in setUpClass, but then we hit a bug
1096 # with VppIpRoute incorrectly reporting it's present when it's not
1097 # so we need to manually remove the vpp config, thus we cannot have
1098 # it shared for multiple test cases
1099 self.tun_ip6 = "1002::1"
1101 self.gre6 = VppGre6Interface(self, self.src_if.local_ip6, self.tun_ip6)
1102 self.gre6.add_vpp_config()
1103 self.gre6.admin_up()
1104 self.gre6.config_ip6()
1106 self.vapi.ip_reassembly_enable_disable(
1107 sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1109 self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1110 [VppRoutePath(self.src_if.remote_ip6,
1111 self.src_if.sw_if_index,
1112 proto=DpoProto.DPO_PROTO_IP6)],
1114 self.route6.add_vpp_config()
1116 self.reset_packet_infos()
1117 for i in range(test_packet_count):
1118 info = self.create_packet_info(self.src_if, self.dst_if)
1119 payload = self.info_to_payload(info)
1120 # Ethernet header here is only for size calculation, thus it
1121 # doesn't matter how it's initialized. This is to ensure that
1122 # reassembled packet is not > 9000 bytes, so that it's not dropped
1124 IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1125 UDP(sport=1234, dport=5678) /
1127 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1128 self.extend_packet(p, size, self.padding)
1129 info.data = p[IPv6] # use only IPv6 part, without ethernet header
1131 fragments = [x for _, i in six.iteritems(self._packet_infos)
1132 for x in fragment_rfc8200(
1133 i.data, i.index, 400)]
1135 encapped_fragments = \
1136 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1137 IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1142 fragmented_encapped_fragments = \
1143 [x for p in encapped_fragments for x in (
1146 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1148 if IPv6ExtHdrFragment in p else [p]
1152 self.src_if.add_stream(fragmented_encapped_fragments)
1154 self.pg_enable_capture(self.pg_interfaces)
1157 self.src_if.assert_nothing_captured()
1158 packets = self.dst_if.get_capture(len(self._packet_infos))
1159 self.verify_capture(packets, IPv6)
1161 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1162 # so that it's query_vpp_config() works as it should
1163 self.gre6.remove_vpp_config()
1166 if __name__ == '__main__':
1167 unittest.main(testRunner=VppTestRunner)