5 from random import shuffle
7 from framework import VppTestCase, VppTestRunner
9 from scapy.packet import Raw
10 from scapy.layers.l2 import Ether, GRE
11 from scapy.layers.inet import IP, UDP, ICMP
12 from util import ppp, fragment_rfc791, fragment_rfc8200
13 from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, ICMPv6ParamProblem,\
15 from vpp_gre_interface import VppGreInterface, VppGre6Interface
16 from vpp_ip import DpoProto
17 from vpp_ip_route import VppIpRoute, VppRoutePath
19 # 35 is enough to have >257 400-byte fragments
20 test_packet_count = 35
23 class TestIPv4Reassembly(VppTestCase):
24 """ IPv4 Reassembly """
28 super(TestIPv4Reassembly, cls).setUpClass()
30 cls.create_pg_interfaces([0, 1])
34 # setup all interfaces
35 for i in cls.pg_interfaces:
41 cls.packet_sizes = [64, 512, 1518, 9018]
42 cls.padding = " abcdefghijklmn"
43 cls.create_stream(cls.packet_sizes)
44 cls.create_fragments()
47 """ Test setup - force timeout on existing reassemblies """
48 super(TestIPv4Reassembly, self).setUp()
49 self.vapi.ip_reassembly_enable_disable(
50 sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
51 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
52 expire_walk_interval_ms=10)
54 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
55 expire_walk_interval_ms=10000)
58 super(TestIPv4Reassembly, self).tearDown()
59 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
62 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
63 """Create input packet stream
65 :param list packet_sizes: Required packet sizes.
67 for i in range(0, packet_count):
68 info = cls.create_packet_info(cls.src_if, cls.src_if)
69 payload = cls.info_to_payload(info)
70 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
71 IP(id=info.index, src=cls.src_if.remote_ip4,
72 dst=cls.dst_if.remote_ip4) /
73 UDP(sport=1234, dport=5678) /
75 size = packet_sizes[(i // 2) % len(packet_sizes)]
76 cls.extend_packet(p, size, cls.padding)
80 def create_fragments(cls):
81 infos = cls._packet_infos
83 for index, info in six.iteritems(infos):
85 # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
86 fragments_400 = fragment_rfc791(p, 400)
87 fragments_300 = fragment_rfc791(p, 300)
89 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
91 (index, fragments_400, fragments_300, fragments_200))
93 x for (_, frags, _, _) in cls.pkt_infos for x in frags]
95 x for (_, _, frags, _) in cls.pkt_infos for x in frags]
97 x for (_, _, _, frags) in cls.pkt_infos for x in frags]
98 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
99 "%s 300-byte fragments and %s 200-byte fragments" %
100 (len(infos), len(cls.fragments_400),
101 len(cls.fragments_300), len(cls.fragments_200)))
103 def verify_capture(self, capture, dropped_packet_indexes=[]):
104 """Verify captured packet stream.
106 :param list capture: Captured packet stream.
110 for packet in capture:
112 self.logger.debug(ppp("Got packet:", packet))
115 payload_info = self.payload_to_info(str(packet[Raw]))
116 packet_index = payload_info.index
118 packet_index not in dropped_packet_indexes,
119 ppp("Packet received, but should be dropped:", packet))
120 if packet_index in seen:
121 raise Exception(ppp("Duplicate packet received", packet))
122 seen.add(packet_index)
123 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
124 info = self._packet_infos[packet_index]
125 self.assertTrue(info is not None)
126 self.assertEqual(packet_index, info.index)
127 saved_packet = info.data
128 self.assertEqual(ip.src, saved_packet[IP].src)
129 self.assertEqual(ip.dst, saved_packet[IP].dst)
130 self.assertEqual(udp.payload, saved_packet[UDP].payload)
132 self.logger.error(ppp("Unexpected or invalid packet:", packet))
134 for index in self._packet_infos:
135 self.assertTrue(index in seen or index in dropped_packet_indexes,
136 "Packet with packet_index %d not received" % index)
138 def test_reassembly(self):
139 """ basic reassembly """
141 self.pg_enable_capture()
142 self.src_if.add_stream(self.fragments_200)
145 packets = self.dst_if.get_capture(len(self.pkt_infos))
146 self.verify_capture(packets)
147 self.src_if.assert_nothing_captured()
149 # run it all again to verify correctness
150 self.pg_enable_capture()
151 self.src_if.add_stream(self.fragments_200)
154 packets = self.dst_if.get_capture(len(self.pkt_infos))
155 self.verify_capture(packets)
156 self.src_if.assert_nothing_captured()
158 def test_reversed(self):
159 """ reverse order reassembly """
161 fragments = list(self.fragments_200)
164 self.pg_enable_capture()
165 self.src_if.add_stream(fragments)
168 packets = self.dst_if.get_capture(len(self.packet_infos))
169 self.verify_capture(packets)
170 self.src_if.assert_nothing_captured()
172 # run it all again to verify correctness
173 self.pg_enable_capture()
174 self.src_if.add_stream(fragments)
177 packets = self.dst_if.get_capture(len(self.packet_infos))
178 self.verify_capture(packets)
179 self.src_if.assert_nothing_captured()
182 """ fragment length + ip header size > 65535 """
183 self.vapi.cli("clear errors")
184 raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
185 '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
186 'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
187 'fset; Test-case: 5737')
189 malformed_packet = (Ether(dst=self.src_if.local_mac,
190 src=self.src_if.remote_mac) /
192 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
193 IP(id=1000, src=self.src_if.remote_ip4,
194 dst=self.dst_if.remote_ip4) /
195 UDP(sport=1234, dport=5678) /
197 valid_fragments = fragment_rfc791(p, 400)
199 self.pg_enable_capture()
200 self.src_if.add_stream([malformed_packet] + valid_fragments)
203 self.dst_if.get_capture(1)
204 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
205 # TODO remove above, uncomment below once clearing of counters
207 # self.assert_packet_counter_equal(
208 # "/err/ip4-reassembly-feature/malformed packets", 1)
210 def test_44924(self):
211 """ compress tiny fragments """
212 packets = [(Ether(dst=self.src_if.local_mac,
213 src=self.src_if.remote_mac) /
214 IP(id=24339, flags="MF", frag=0, ttl=64,
215 src=self.src_if.remote_ip4,
216 dst=self.dst_if.remote_ip4) /
217 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
218 Raw(load='Test-group: IPv4')),
219 (Ether(dst=self.src_if.local_mac,
220 src=self.src_if.remote_mac) /
221 IP(id=24339, flags="MF", frag=3, ttl=64,
222 src=self.src_if.remote_ip4,
223 dst=self.dst_if.remote_ip4) /
224 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
225 Raw(load='.IPv4.Fragmentation.vali')),
226 (Ether(dst=self.src_if.local_mac,
227 src=self.src_if.remote_mac) /
228 IP(id=24339, frag=6, ttl=64,
229 src=self.src_if.remote_ip4,
230 dst=self.dst_if.remote_ip4) /
231 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
232 Raw(load='d; Test-case: 44924'))
235 self.pg_enable_capture()
236 self.src_if.add_stream(packets)
239 self.dst_if.get_capture(1)
241 def test_frag_1(self):
242 """ fragment of size 1 """
243 self.vapi.cli("clear errors")
244 malformed_packets = [(Ether(dst=self.src_if.local_mac,
245 src=self.src_if.remote_mac) /
246 IP(id=7, len=21, flags="MF", frag=0, ttl=64,
247 src=self.src_if.remote_ip4,
248 dst=self.dst_if.remote_ip4) /
249 ICMP(type="echo-request")),
250 (Ether(dst=self.src_if.local_mac,
251 src=self.src_if.remote_mac) /
252 IP(id=7, len=21, frag=1, ttl=64,
253 src=self.src_if.remote_ip4,
254 dst=self.dst_if.remote_ip4) /
258 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
259 IP(id=1000, src=self.src_if.remote_ip4,
260 dst=self.dst_if.remote_ip4) /
261 UDP(sport=1234, dport=5678) /
263 valid_fragments = fragment_rfc791(p, 400)
265 self.pg_enable_capture()
266 self.src_if.add_stream(malformed_packets + valid_fragments)
269 self.dst_if.get_capture(1)
271 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
272 # TODO remove above, uncomment below once clearing of counters
274 # self.assert_packet_counter_equal(
275 # "/err/ip4-reassembly-feature/malformed packets", 1)
277 def test_random(self):
278 """ random order reassembly """
280 fragments = list(self.fragments_200)
283 self.pg_enable_capture()
284 self.src_if.add_stream(fragments)
287 packets = self.dst_if.get_capture(len(self.packet_infos))
288 self.verify_capture(packets)
289 self.src_if.assert_nothing_captured()
291 # run it all again to verify correctness
292 self.pg_enable_capture()
293 self.src_if.add_stream(fragments)
296 packets = self.dst_if.get_capture(len(self.packet_infos))
297 self.verify_capture(packets)
298 self.src_if.assert_nothing_captured()
300 def test_duplicates(self):
301 """ duplicate fragments """
304 x for (_, frags, _, _) in self.pkt_infos
306 for _ in range(0, min(2, len(frags)))
309 self.pg_enable_capture()
310 self.src_if.add_stream(fragments)
313 packets = self.dst_if.get_capture(len(self.pkt_infos))
314 self.verify_capture(packets)
315 self.src_if.assert_nothing_captured()
317 def test_overlap1(self):
318 """ overlapping fragments case #1 """
321 for _, _, frags_300, frags_200 in self.pkt_infos:
322 if len(frags_300) == 1:
323 fragments.extend(frags_300)
325 for i, j in zip(frags_200, frags_300):
329 self.pg_enable_capture()
330 self.src_if.add_stream(fragments)
333 packets = self.dst_if.get_capture(len(self.pkt_infos))
334 self.verify_capture(packets)
335 self.src_if.assert_nothing_captured()
337 # run it all to verify correctness
338 self.pg_enable_capture()
339 self.src_if.add_stream(fragments)
342 packets = self.dst_if.get_capture(len(self.pkt_infos))
343 self.verify_capture(packets)
344 self.src_if.assert_nothing_captured()
346 def test_overlap2(self):
347 """ overlapping fragments case #2 """
350 for _, _, frags_300, frags_200 in self.pkt_infos:
351 if len(frags_300) == 1:
352 fragments.extend(frags_300)
354 # care must be taken here so that there are no fragments
355 # received by vpp after reassembly is finished, otherwise
356 # new reassemblies will be started and packet generator will
357 # freak out when it detects unfreed buffers
358 zipped = zip(frags_300, frags_200)
359 for i, j in zipped[:-1]:
362 fragments.append(zipped[-1][0])
364 self.pg_enable_capture()
365 self.src_if.add_stream(fragments)
368 packets = self.dst_if.get_capture(len(self.pkt_infos))
369 self.verify_capture(packets)
370 self.src_if.assert_nothing_captured()
372 # run it all to verify correctness
373 self.pg_enable_capture()
374 self.src_if.add_stream(fragments)
377 packets = self.dst_if.get_capture(len(self.pkt_infos))
378 self.verify_capture(packets)
379 self.src_if.assert_nothing_captured()
381 def test_timeout_inline(self):
382 """ timeout (inline) """
384 dropped_packet_indexes = set(
385 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
388 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
389 expire_walk_interval_ms=10000)
391 self.pg_enable_capture()
392 self.src_if.add_stream(self.fragments_400)
395 packets = self.dst_if.get_capture(
396 len(self.pkt_infos) - len(dropped_packet_indexes))
397 self.verify_capture(packets, dropped_packet_indexes)
398 self.src_if.assert_nothing_captured()
400 def test_timeout_cleanup(self):
401 """ timeout (cleanup) """
403 # whole packets + fragmented packets sans last fragment
405 x for (_, frags_400, _, _) in self.pkt_infos
406 for x in frags_400[:-1 if len(frags_400) > 1 else None]
409 # last fragments for fragmented packets
410 fragments2 = [frags_400[-1]
411 for (_, frags_400, _, _) in self.pkt_infos
412 if len(frags_400) > 1]
414 dropped_packet_indexes = set(
415 index for (index, frags_400, _, _) in self.pkt_infos
416 if len(frags_400) > 1)
418 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
419 expire_walk_interval_ms=50)
421 self.pg_enable_capture()
422 self.src_if.add_stream(fragments)
425 self.sleep(.25, "wait before sending rest of fragments")
427 self.src_if.add_stream(fragments2)
430 packets = self.dst_if.get_capture(
431 len(self.pkt_infos) - len(dropped_packet_indexes))
432 self.verify_capture(packets, dropped_packet_indexes)
433 self.src_if.assert_nothing_captured()
435 def test_disabled(self):
436 """ reassembly disabled """
438 dropped_packet_indexes = set(
439 index for (index, frags_400, _, _) in self.pkt_infos
440 if len(frags_400) > 1)
442 self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
443 expire_walk_interval_ms=10000)
445 self.pg_enable_capture()
446 self.src_if.add_stream(self.fragments_400)
449 packets = self.dst_if.get_capture(
450 len(self.pkt_infos) - len(dropped_packet_indexes))
451 self.verify_capture(packets, dropped_packet_indexes)
452 self.src_if.assert_nothing_captured()
455 class TestIPv6Reassembly(VppTestCase):
456 """ IPv6 Reassembly """
460 super(TestIPv6Reassembly, cls).setUpClass()
462 cls.create_pg_interfaces([0, 1])
466 # setup all interfaces
467 for i in cls.pg_interfaces:
473 cls.packet_sizes = [64, 512, 1518, 9018]
474 cls.padding = " abcdefghijklmn"
475 cls.create_stream(cls.packet_sizes)
476 cls.create_fragments()
479 """ Test setup - force timeout on existing reassemblies """
480 super(TestIPv6Reassembly, self).setUp()
481 self.vapi.ip_reassembly_enable_disable(
482 sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
483 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
484 expire_walk_interval_ms=10, is_ip6=1)
486 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
487 expire_walk_interval_ms=10000, is_ip6=1)
488 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
491 super(TestIPv6Reassembly, self).tearDown()
492 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
495 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
496 """Create input packet stream for defined interface.
498 :param list packet_sizes: Required packet sizes.
500 for i in range(0, packet_count):
501 info = cls.create_packet_info(cls.src_if, cls.src_if)
502 payload = cls.info_to_payload(info)
503 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
504 IPv6(src=cls.src_if.remote_ip6,
505 dst=cls.dst_if.remote_ip6) /
506 UDP(sport=1234, dport=5678) /
508 size = packet_sizes[(i // 2) % len(packet_sizes)]
509 cls.extend_packet(p, size, cls.padding)
513 def create_fragments(cls):
514 infos = cls._packet_infos
516 for index, info in six.iteritems(infos):
518 # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
519 fragments_400 = fragment_rfc8200(p, info.index, 400)
520 fragments_300 = fragment_rfc8200(p, info.index, 300)
521 cls.pkt_infos.append((index, fragments_400, fragments_300))
522 cls.fragments_400 = [
523 x for _, frags, _ in cls.pkt_infos for x in frags]
524 cls.fragments_300 = [
525 x for _, _, frags in cls.pkt_infos for x in frags]
526 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
527 "and %s 300-byte fragments" %
528 (len(infos), len(cls.fragments_400),
529 len(cls.fragments_300)))
531 def verify_capture(self, capture, dropped_packet_indexes=[]):
532 """Verify captured packet strea .
534 :param list capture: Captured packet stream.
538 for packet in capture:
540 self.logger.debug(ppp("Got packet:", packet))
543 payload_info = self.payload_to_info(str(packet[Raw]))
544 packet_index = payload_info.index
546 packet_index not in dropped_packet_indexes,
547 ppp("Packet received, but should be dropped:", packet))
548 if packet_index in seen:
549 raise Exception(ppp("Duplicate packet received", packet))
550 seen.add(packet_index)
551 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
552 info = self._packet_infos[packet_index]
553 self.assertTrue(info is not None)
554 self.assertEqual(packet_index, info.index)
555 saved_packet = info.data
556 self.assertEqual(ip.src, saved_packet[IPv6].src)
557 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
558 self.assertEqual(udp.payload, saved_packet[UDP].payload)
560 self.logger.error(ppp("Unexpected or invalid packet:", packet))
562 for index in self._packet_infos:
563 self.assertTrue(index in seen or index in dropped_packet_indexes,
564 "Packet with packet_index %d not received" % index)
566 def test_reassembly(self):
567 """ basic reassembly """
569 self.pg_enable_capture()
570 self.src_if.add_stream(self.fragments_400)
573 packets = self.dst_if.get_capture(len(self.pkt_infos))
574 self.verify_capture(packets)
575 self.src_if.assert_nothing_captured()
577 # run it all again to verify correctness
578 self.pg_enable_capture()
579 self.src_if.add_stream(self.fragments_400)
582 packets = self.dst_if.get_capture(len(self.pkt_infos))
583 self.verify_capture(packets)
584 self.src_if.assert_nothing_captured()
586 def test_reversed(self):
587 """ reverse order reassembly """
589 fragments = list(self.fragments_400)
592 self.pg_enable_capture()
593 self.src_if.add_stream(fragments)
596 packets = self.dst_if.get_capture(len(self.pkt_infos))
597 self.verify_capture(packets)
598 self.src_if.assert_nothing_captured()
600 # run it all again to verify correctness
601 self.pg_enable_capture()
602 self.src_if.add_stream(fragments)
605 packets = self.dst_if.get_capture(len(self.pkt_infos))
606 self.verify_capture(packets)
607 self.src_if.assert_nothing_captured()
609 def test_random(self):
610 """ random order reassembly """
612 fragments = list(self.fragments_400)
615 self.pg_enable_capture()
616 self.src_if.add_stream(fragments)
619 packets = self.dst_if.get_capture(len(self.pkt_infos))
620 self.verify_capture(packets)
621 self.src_if.assert_nothing_captured()
623 # run it all again to verify correctness
624 self.pg_enable_capture()
625 self.src_if.add_stream(fragments)
628 packets = self.dst_if.get_capture(len(self.pkt_infos))
629 self.verify_capture(packets)
630 self.src_if.assert_nothing_captured()
632 def test_duplicates(self):
633 """ duplicate fragments """
636 x for (_, frags, _) in self.pkt_infos
638 for _ in range(0, min(2, len(frags)))
641 self.pg_enable_capture()
642 self.src_if.add_stream(fragments)
645 packets = self.dst_if.get_capture(len(self.pkt_infos))
646 self.verify_capture(packets)
647 self.src_if.assert_nothing_captured()
649 def test_overlap1(self):
650 """ overlapping fragments case #1 """
653 for _, frags_400, frags_300 in self.pkt_infos:
654 if len(frags_300) == 1:
655 fragments.extend(frags_400)
657 for i, j in zip(frags_300, frags_400):
661 dropped_packet_indexes = set(
662 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
665 self.pg_enable_capture()
666 self.src_if.add_stream(fragments)
669 packets = self.dst_if.get_capture(
670 len(self.pkt_infos) - len(dropped_packet_indexes))
671 self.verify_capture(packets, dropped_packet_indexes)
672 self.src_if.assert_nothing_captured()
674 def test_overlap2(self):
675 """ overlapping fragments case #2 """
678 for _, frags_400, frags_300 in self.pkt_infos:
679 if len(frags_400) == 1:
680 fragments.extend(frags_400)
682 # care must be taken here so that there are no fragments
683 # received by vpp after reassembly is finished, otherwise
684 # new reassemblies will be started and packet generator will
685 # freak out when it detects unfreed buffers
686 zipped = zip(frags_400, frags_300)
687 for i, j in zipped[:-1]:
690 fragments.append(zipped[-1][0])
692 dropped_packet_indexes = set(
693 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
696 self.pg_enable_capture()
697 self.src_if.add_stream(fragments)
700 packets = self.dst_if.get_capture(
701 len(self.pkt_infos) - len(dropped_packet_indexes))
702 self.verify_capture(packets, dropped_packet_indexes)
703 self.src_if.assert_nothing_captured()
705 def test_timeout_inline(self):
706 """ timeout (inline) """
708 dropped_packet_indexes = set(
709 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
712 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
713 expire_walk_interval_ms=10000, is_ip6=1)
715 self.pg_enable_capture()
716 self.src_if.add_stream(self.fragments_400)
719 packets = self.dst_if.get_capture(
720 len(self.pkt_infos) - len(dropped_packet_indexes))
721 self.verify_capture(packets, dropped_packet_indexes)
722 pkts = self.src_if.get_capture(
723 expected_count=len(dropped_packet_indexes))
725 self.assertIn(ICMPv6TimeExceeded, icmp)
726 self.assertIn(IPv6ExtHdrFragment, icmp)
727 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
728 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
730 def test_timeout_cleanup(self):
731 """ timeout (cleanup) """
733 # whole packets + fragmented packets sans last fragment
735 x for (_, frags_400, _) in self.pkt_infos
736 for x in frags_400[:-1 if len(frags_400) > 1 else None]
739 # last fragments for fragmented packets
740 fragments2 = [frags_400[-1]
741 for (_, frags_400, _) in self.pkt_infos
742 if len(frags_400) > 1]
744 dropped_packet_indexes = set(
745 index for (index, frags_400, _) in self.pkt_infos
746 if len(frags_400) > 1)
748 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
749 expire_walk_interval_ms=50)
751 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
752 expire_walk_interval_ms=50, is_ip6=1)
754 self.pg_enable_capture()
755 self.src_if.add_stream(fragments)
758 self.sleep(.25, "wait before sending rest of fragments")
760 self.src_if.add_stream(fragments2)
763 packets = self.dst_if.get_capture(
764 len(self.pkt_infos) - len(dropped_packet_indexes))
765 self.verify_capture(packets, dropped_packet_indexes)
766 pkts = self.src_if.get_capture(
767 expected_count=len(dropped_packet_indexes))
769 self.assertIn(ICMPv6TimeExceeded, icmp)
770 self.assertIn(IPv6ExtHdrFragment, icmp)
771 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
772 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
774 def test_disabled(self):
775 """ reassembly disabled """
777 dropped_packet_indexes = set(
778 index for (index, frags_400, _) in self.pkt_infos
779 if len(frags_400) > 1)
781 self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
782 expire_walk_interval_ms=10000, is_ip6=1)
784 self.pg_enable_capture()
785 self.src_if.add_stream(self.fragments_400)
788 packets = self.dst_if.get_capture(
789 len(self.pkt_infos) - len(dropped_packet_indexes))
790 self.verify_capture(packets, dropped_packet_indexes)
791 self.src_if.assert_nothing_captured()
793 def test_missing_upper(self):
794 """ missing upper layer """
795 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
796 IPv6(src=self.src_if.remote_ip6,
797 dst=self.src_if.local_ip6) /
798 UDP(sport=1234, dport=5678) /
800 self.extend_packet(p, 1000, self.padding)
801 fragments = fragment_rfc8200(p, 1, 500)
802 bad_fragment = p.__class__(str(fragments[1]))
803 bad_fragment[IPv6ExtHdrFragment].nh = 59
804 bad_fragment[IPv6ExtHdrFragment].offset = 0
805 self.pg_enable_capture()
806 self.src_if.add_stream([bad_fragment])
808 pkts = self.src_if.get_capture(expected_count=1)
810 self.assertIn(ICMPv6ParamProblem, icmp)
811 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
813 def test_invalid_frag_size(self):
814 """ fragment size not a multiple of 8 """
815 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
816 IPv6(src=self.src_if.remote_ip6,
817 dst=self.src_if.local_ip6) /
818 UDP(sport=1234, dport=5678) /
820 self.extend_packet(p, 1000, self.padding)
821 fragments = fragment_rfc8200(p, 1, 500)
822 bad_fragment = fragments[0]
823 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
824 self.pg_enable_capture()
825 self.src_if.add_stream([bad_fragment])
827 pkts = self.src_if.get_capture(expected_count=1)
829 self.assertIn(ICMPv6ParamProblem, icmp)
830 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
832 def test_invalid_packet_size(self):
833 """ total packet size > 65535 """
834 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
835 IPv6(src=self.src_if.remote_ip6,
836 dst=self.src_if.local_ip6) /
837 UDP(sport=1234, dport=5678) /
839 self.extend_packet(p, 1000, self.padding)
840 fragments = fragment_rfc8200(p, 1, 500)
841 bad_fragment = fragments[1]
842 bad_fragment[IPv6ExtHdrFragment].offset = 65500
843 self.pg_enable_capture()
844 self.src_if.add_stream([bad_fragment])
846 pkts = self.src_if.get_capture(expected_count=1)
848 self.assertIn(ICMPv6ParamProblem, icmp)
849 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
852 class TestIPv4ReassemblyLocalNode(VppTestCase):
853 """ IPv4 Reassembly for packets coming to ip4-local node """
857 super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
859 cls.create_pg_interfaces([0])
860 cls.src_dst_if = cls.pg0
862 # setup all interfaces
863 for i in cls.pg_interfaces:
868 cls.padding = " abcdefghijklmn"
870 cls.create_fragments()
873 """ Test setup - force timeout on existing reassemblies """
874 super(TestIPv4ReassemblyLocalNode, self).setUp()
875 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
876 expire_walk_interval_ms=10)
878 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
879 expire_walk_interval_ms=10000)
882 super(TestIPv4ReassemblyLocalNode, self).tearDown()
883 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
886 def create_stream(cls, packet_count=test_packet_count):
887 """Create input packet stream for defined interface.
889 :param list packet_sizes: Required packet sizes.
891 for i in range(0, packet_count):
892 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
893 payload = cls.info_to_payload(info)
894 p = (Ether(dst=cls.src_dst_if.local_mac,
895 src=cls.src_dst_if.remote_mac) /
896 IP(id=info.index, src=cls.src_dst_if.remote_ip4,
897 dst=cls.src_dst_if.local_ip4) /
898 ICMP(type='echo-request', id=1234) /
900 cls.extend_packet(p, 1518, cls.padding)
904 def create_fragments(cls):
905 infos = cls._packet_infos
907 for index, info in six.iteritems(infos):
909 # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
910 fragments_300 = fragment_rfc791(p, 300)
911 cls.pkt_infos.append((index, fragments_300))
912 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
913 cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
914 (len(infos), len(cls.fragments_300)))
916 def verify_capture(self, capture):
917 """Verify captured packet stream.
919 :param list capture: Captured packet stream.
923 for packet in capture:
925 self.logger.debug(ppp("Got packet:", packet))
928 payload_info = self.payload_to_info(str(packet[Raw]))
929 packet_index = payload_info.index
930 if packet_index in seen:
931 raise Exception(ppp("Duplicate packet received", packet))
932 seen.add(packet_index)
933 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
934 info = self._packet_infos[packet_index]
935 self.assertIsNotNone(info)
936 self.assertEqual(packet_index, info.index)
937 saved_packet = info.data
938 self.assertEqual(ip.src, saved_packet[IP].dst)
939 self.assertEqual(ip.dst, saved_packet[IP].src)
940 self.assertEqual(icmp.type, 0) # echo reply
941 self.assertEqual(icmp.id, saved_packet[ICMP].id)
942 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
944 self.logger.error(ppp("Unexpected or invalid packet:", packet))
946 for index in self._packet_infos:
947 self.assertIn(index, seen,
948 "Packet with packet_index %d not received" % index)
950 def test_reassembly(self):
951 """ basic reassembly """
953 self.pg_enable_capture()
954 self.src_dst_if.add_stream(self.fragments_300)
957 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
958 self.verify_capture(packets)
960 # run it all again to verify correctness
961 self.pg_enable_capture()
962 self.src_dst_if.add_stream(self.fragments_300)
965 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
966 self.verify_capture(packets)
969 class TestFIFReassembly(VppTestCase):
970 """ Fragments in fragments reassembly """
974 super(TestFIFReassembly, cls).setUpClass()
976 cls.create_pg_interfaces([0, 1])
979 for i in cls.pg_interfaces:
986 cls.packet_sizes = [64, 512, 1518, 9018]
987 cls.padding = " abcdefghijklmn"
990 """ Test setup - force timeout on existing reassemblies """
991 super(TestFIFReassembly, self).setUp()
992 self.vapi.ip_reassembly_enable_disable(
993 sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
995 self.vapi.ip_reassembly_enable_disable(
996 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
998 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
999 expire_walk_interval_ms=10)
1000 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1001 expire_walk_interval_ms=10, is_ip6=1)
1003 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1004 expire_walk_interval_ms=10000)
1005 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1006 expire_walk_interval_ms=10000, is_ip6=1)
1009 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
1010 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
1011 super(TestFIFReassembly, self).tearDown()
1013 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
1014 """Verify captured packet stream.
1016 :param list capture: Captured packet stream.
1020 for packet in capture:
1022 self.logger.debug(ppp("Got packet:", packet))
1023 ip = packet[ip_class]
1025 payload_info = self.payload_to_info(str(packet[Raw]))
1026 packet_index = payload_info.index
1028 packet_index not in dropped_packet_indexes,
1029 ppp("Packet received, but should be dropped:", packet))
1030 if packet_index in seen:
1031 raise Exception(ppp("Duplicate packet received", packet))
1032 seen.add(packet_index)
1033 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1034 info = self._packet_infos[packet_index]
1035 self.assertTrue(info is not None)
1036 self.assertEqual(packet_index, info.index)
1037 saved_packet = info.data
1038 self.assertEqual(ip.src, saved_packet[ip_class].src)
1039 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1040 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1042 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1044 for index in self._packet_infos:
1045 self.assertTrue(index in seen or index in dropped_packet_indexes,
1046 "Packet with packet_index %d not received" % index)
1048 def test_fif4(self):
1049 """ Fragments in fragments (4o4) """
1051 # TODO this should be ideally in setUpClass, but then we hit a bug
1052 # with VppIpRoute incorrectly reporting it's present when it's not
1053 # so we need to manually remove the vpp config, thus we cannot have
1054 # it shared for multiple test cases
1055 self.tun_ip4 = "1.1.1.2"
1057 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1058 self.gre4.add_vpp_config()
1059 self.gre4.admin_up()
1060 self.gre4.config_ip4()
1062 self.vapi.ip_reassembly_enable_disable(
1063 sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1065 self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1066 [VppRoutePath(self.src_if.remote_ip4,
1067 self.src_if.sw_if_index)])
1068 self.route4.add_vpp_config()
1070 self.reset_packet_infos()
1071 for i in range(test_packet_count):
1072 info = self.create_packet_info(self.src_if, self.dst_if)
1073 payload = self.info_to_payload(info)
1074 # Ethernet header here is only for size calculation, thus it
1075 # doesn't matter how it's initialized. This is to ensure that
1076 # reassembled packet is not > 9000 bytes, so that it's not dropped
1078 IP(id=i, src=self.src_if.remote_ip4,
1079 dst=self.dst_if.remote_ip4) /
1080 UDP(sport=1234, dport=5678) /
1082 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1083 self.extend_packet(p, size, self.padding)
1084 info.data = p[IP] # use only IP part, without ethernet header
1086 fragments = [x for _, p in six.iteritems(self._packet_infos)
1087 for x in fragment_rfc791(p.data, 400)]
1089 encapped_fragments = \
1090 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1091 IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1096 fragmented_encapped_fragments = \
1097 [x for p in encapped_fragments
1098 for x in fragment_rfc791(p, 200)]
1100 self.src_if.add_stream(fragmented_encapped_fragments)
1102 self.pg_enable_capture(self.pg_interfaces)
1105 self.src_if.assert_nothing_captured()
1106 packets = self.dst_if.get_capture(len(self._packet_infos))
1107 self.verify_capture(packets, IP)
1109 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1110 # so that it's query_vpp_config() works as it should
1111 self.gre4.remove_vpp_config()
1112 self.logger.debug(self.vapi.ppcli("show interface"))
1114 def test_fif6(self):
1115 """ Fragments in fragments (6o6) """
1116 # TODO this should be ideally in setUpClass, but then we hit a bug
1117 # with VppIpRoute incorrectly reporting it's present when it's not
1118 # so we need to manually remove the vpp config, thus we cannot have
1119 # it shared for multiple test cases
1120 self.tun_ip6 = "1002::1"
1122 self.gre6 = VppGre6Interface(self, self.src_if.local_ip6, self.tun_ip6)
1123 self.gre6.add_vpp_config()
1124 self.gre6.admin_up()
1125 self.gre6.config_ip6()
1127 self.vapi.ip_reassembly_enable_disable(
1128 sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1130 self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1131 [VppRoutePath(self.src_if.remote_ip6,
1132 self.src_if.sw_if_index,
1133 proto=DpoProto.DPO_PROTO_IP6)],
1135 self.route6.add_vpp_config()
1137 self.reset_packet_infos()
1138 for i in range(test_packet_count):
1139 info = self.create_packet_info(self.src_if, self.dst_if)
1140 payload = self.info_to_payload(info)
1141 # Ethernet header here is only for size calculation, thus it
1142 # doesn't matter how it's initialized. This is to ensure that
1143 # reassembled packet is not > 9000 bytes, so that it's not dropped
1145 IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1146 UDP(sport=1234, dport=5678) /
1148 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1149 self.extend_packet(p, size, self.padding)
1150 info.data = p[IPv6] # use only IPv6 part, without ethernet header
1152 fragments = [x for _, i in six.iteritems(self._packet_infos)
1153 for x in fragment_rfc8200(
1154 i.data, i.index, 400)]
1156 encapped_fragments = \
1157 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1158 IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1163 fragmented_encapped_fragments = \
1164 [x for p in encapped_fragments for x in (
1167 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1169 if IPv6ExtHdrFragment in p else [p]
1173 self.src_if.add_stream(fragmented_encapped_fragments)
1175 self.pg_enable_capture(self.pg_interfaces)
1178 self.src_if.assert_nothing_captured()
1179 packets = self.dst_if.get_capture(len(self._packet_infos))
1180 self.verify_capture(packets, IPv6)
1182 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1183 # so that it's query_vpp_config() works as it should
1184 self.gre6.remove_vpp_config()
1187 if __name__ == '__main__':
1188 unittest.main(testRunner=VppTestRunner)