5 from random import shuffle
7 from framework import VppTestCase, VppTestRunner
10 from scapy.packet import Raw
11 from scapy.layers.l2 import Ether, GRE
12 from scapy.layers.inet import IP, UDP, ICMP
13 from util import ppp, fragment_rfc791, fragment_rfc8200
14 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
26 class TestIPv4Reassembly(VppTestCase):
27 """ IPv4 Reassembly """
31 super(TestIPv4Reassembly, cls).setUpClass()
33 cls.create_pg_interfaces([0, 1])
37 # setup all interfaces
38 for i in cls.pg_interfaces:
44 cls.packet_sizes = [64, 512, 1518, 9018]
45 cls.padding = " abcdefghijklmn"
46 cls.create_stream(cls.packet_sizes)
47 cls.create_fragments()
50 def tearDownClass(cls):
51 super(TestIPv4Reassembly, cls).tearDownClass()
54 """ Test setup - force timeout on existing reassemblies """
55 super(TestIPv4Reassembly, self).setUp()
56 self.vapi.ip_reassembly_enable_disable(
57 sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
58 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
59 max_reassembly_length=1000,
60 expire_walk_interval_ms=10)
62 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
63 max_reassembly_length=1000,
64 expire_walk_interval_ms=10000)
67 super(TestIPv4Reassembly, self).tearDown()
69 def show_commands_at_teardown(self):
70 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
71 self.logger.debug(self.vapi.ppcli("show buffers"))
74 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
75 """Create input packet stream
77 :param list packet_sizes: Required packet sizes.
79 for i in range(0, packet_count):
80 info = cls.create_packet_info(cls.src_if, cls.src_if)
81 payload = cls.info_to_payload(info)
82 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
83 IP(id=info.index, src=cls.src_if.remote_ip4,
84 dst=cls.dst_if.remote_ip4) /
85 UDP(sport=1234, dport=5678) /
87 size = packet_sizes[(i // 2) % len(packet_sizes)]
88 cls.extend_packet(p, size, cls.padding)
92 def create_fragments(cls):
93 infos = cls._packet_infos
95 for index, info in six.iteritems(infos):
97 # cls.logger.debug(ppp("Packet:",
98 # p.__class__(scapy.compat.raw(p))))
99 fragments_400 = fragment_rfc791(p, 400)
100 fragments_300 = fragment_rfc791(p, 300)
102 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
103 cls.pkt_infos.append(
104 (index, fragments_400, fragments_300, fragments_200))
105 cls.fragments_400 = [
106 x for (_, frags, _, _) in cls.pkt_infos for x in frags]
107 cls.fragments_300 = [
108 x for (_, _, frags, _) in cls.pkt_infos for x in frags]
109 cls.fragments_200 = [
110 x for (_, _, _, frags) in cls.pkt_infos for x in frags]
111 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
112 "%s 300-byte fragments and %s 200-byte fragments" %
113 (len(infos), len(cls.fragments_400),
114 len(cls.fragments_300), len(cls.fragments_200)))
116 def verify_capture(self, capture, dropped_packet_indexes=[]):
117 """Verify captured packet stream.
119 :param list capture: Captured packet stream.
123 for packet in capture:
125 self.logger.debug(ppp("Got packet:", packet))
128 payload_info = self.payload_to_info(packet[Raw])
129 packet_index = payload_info.index
131 packet_index not in dropped_packet_indexes,
132 ppp("Packet received, but should be dropped:", packet))
133 if packet_index in seen:
134 raise Exception(ppp("Duplicate packet received", packet))
135 seen.add(packet_index)
136 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
137 info = self._packet_infos[packet_index]
138 self.assertTrue(info is not None)
139 self.assertEqual(packet_index, info.index)
140 saved_packet = info.data
141 self.assertEqual(ip.src, saved_packet[IP].src)
142 self.assertEqual(ip.dst, saved_packet[IP].dst)
143 self.assertEqual(udp.payload, saved_packet[UDP].payload)
145 self.logger.error(ppp("Unexpected or invalid packet:", packet))
147 for index in self._packet_infos:
148 self.assertTrue(index in seen or index in dropped_packet_indexes,
149 "Packet with packet_index %d not received" % index)
151 def test_reassembly(self):
152 """ basic reassembly """
154 self.pg_enable_capture()
155 self.src_if.add_stream(self.fragments_200)
158 packets = self.dst_if.get_capture(len(self.pkt_infos))
159 self.verify_capture(packets)
160 self.src_if.assert_nothing_captured()
162 # run it all again to verify correctness
163 self.pg_enable_capture()
164 self.src_if.add_stream(self.fragments_200)
167 packets = self.dst_if.get_capture(len(self.pkt_infos))
168 self.verify_capture(packets)
169 self.src_if.assert_nothing_captured()
171 def test_reversed(self):
172 """ reverse order reassembly """
174 fragments = list(self.fragments_200)
177 self.pg_enable_capture()
178 self.src_if.add_stream(fragments)
181 packets = self.dst_if.get_capture(len(self.packet_infos))
182 self.verify_capture(packets)
183 self.src_if.assert_nothing_captured()
185 # run it all again to verify correctness
186 self.pg_enable_capture()
187 self.src_if.add_stream(fragments)
190 packets = self.dst_if.get_capture(len(self.packet_infos))
191 self.verify_capture(packets)
192 self.src_if.assert_nothing_captured()
194 def test_long_fragment_chain(self):
195 """ long fragment chain """
198 "/err/ip4-reassembly-feature/fragment chain too long (drop)"
200 error_cnt = self.statistics.get_err_counter(error_cnt_str)
202 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
203 max_reassembly_length=3,
204 expire_walk_interval_ms=50)
206 p1 = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
207 IP(id=1000, src=self.src_if.remote_ip4,
208 dst=self.dst_if.remote_ip4) /
209 UDP(sport=1234, dport=5678) /
211 p2 = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
212 IP(id=1001, src=self.src_if.remote_ip4,
213 dst=self.dst_if.remote_ip4) /
214 UDP(sport=1234, dport=5678) /
216 frags = fragment_rfc791(p1, 200) + fragment_rfc791(p2, 500)
218 self.pg_enable_capture()
219 self.src_if.add_stream(frags)
222 self.dst_if.get_capture(1)
223 self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
226 """ fragment length + ip header size > 65535 """
227 self.vapi.cli("clear errors")
228 raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
229 '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
230 'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
231 'fset; Test-case: 5737')
233 malformed_packet = (Ether(dst=self.src_if.local_mac,
234 src=self.src_if.remote_mac) /
236 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
237 IP(id=1000, src=self.src_if.remote_ip4,
238 dst=self.dst_if.remote_ip4) /
239 UDP(sport=1234, dport=5678) /
241 valid_fragments = fragment_rfc791(p, 400)
243 self.pg_enable_capture()
244 self.src_if.add_stream([malformed_packet] + valid_fragments)
247 self.dst_if.get_capture(1)
248 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
249 # TODO remove above, uncomment below once clearing of counters
251 # self.assert_packet_counter_equal(
252 # "/err/ip4-reassembly-feature/malformed packets", 1)
254 def test_44924(self):
255 """ compress tiny fragments """
256 packets = [(Ether(dst=self.src_if.local_mac,
257 src=self.src_if.remote_mac) /
258 IP(id=24339, flags="MF", frag=0, ttl=64,
259 src=self.src_if.remote_ip4,
260 dst=self.dst_if.remote_ip4) /
261 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
262 Raw(load='Test-group: IPv4')),
263 (Ether(dst=self.src_if.local_mac,
264 src=self.src_if.remote_mac) /
265 IP(id=24339, flags="MF", frag=3, ttl=64,
266 src=self.src_if.remote_ip4,
267 dst=self.dst_if.remote_ip4) /
268 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
269 Raw(load='.IPv4.Fragmentation.vali')),
270 (Ether(dst=self.src_if.local_mac,
271 src=self.src_if.remote_mac) /
272 IP(id=24339, frag=6, ttl=64,
273 src=self.src_if.remote_ip4,
274 dst=self.dst_if.remote_ip4) /
275 ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
276 Raw(load='d; Test-case: 44924'))
279 self.pg_enable_capture()
280 self.src_if.add_stream(packets)
283 self.dst_if.get_capture(1)
285 def test_frag_1(self):
286 """ fragment of size 1 """
287 self.vapi.cli("clear errors")
288 malformed_packets = [(Ether(dst=self.src_if.local_mac,
289 src=self.src_if.remote_mac) /
290 IP(id=7, len=21, flags="MF", frag=0, ttl=64,
291 src=self.src_if.remote_ip4,
292 dst=self.dst_if.remote_ip4) /
293 ICMP(type="echo-request")),
294 (Ether(dst=self.src_if.local_mac,
295 src=self.src_if.remote_mac) /
296 IP(id=7, len=21, frag=1, ttl=64,
297 src=self.src_if.remote_ip4,
298 dst=self.dst_if.remote_ip4) /
302 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
303 IP(id=1000, src=self.src_if.remote_ip4,
304 dst=self.dst_if.remote_ip4) /
305 UDP(sport=1234, dport=5678) /
307 valid_fragments = fragment_rfc791(p, 400)
309 self.pg_enable_capture()
310 self.src_if.add_stream(malformed_packets + valid_fragments)
313 self.dst_if.get_capture(1)
315 self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
316 # TODO remove above, uncomment below once clearing of counters
318 # self.assert_packet_counter_equal(
319 # "/err/ip4-reassembly-feature/malformed packets", 1)
321 def test_random(self):
322 """ random order reassembly """
324 fragments = list(self.fragments_200)
327 self.pg_enable_capture()
328 self.src_if.add_stream(fragments)
331 packets = self.dst_if.get_capture(len(self.packet_infos))
332 self.verify_capture(packets)
333 self.src_if.assert_nothing_captured()
335 # run it all again to verify correctness
336 self.pg_enable_capture()
337 self.src_if.add_stream(fragments)
340 packets = self.dst_if.get_capture(len(self.packet_infos))
341 self.verify_capture(packets)
342 self.src_if.assert_nothing_captured()
344 def test_duplicates(self):
345 """ duplicate fragments """
348 x for (_, frags, _, _) in self.pkt_infos
350 for _ in range(0, min(2, len(frags)))
353 self.pg_enable_capture()
354 self.src_if.add_stream(fragments)
357 packets = self.dst_if.get_capture(len(self.pkt_infos))
358 self.verify_capture(packets)
359 self.src_if.assert_nothing_captured()
361 def test_overlap1(self):
362 """ overlapping fragments case #1 """
365 for _, _, frags_300, frags_200 in self.pkt_infos:
366 if len(frags_300) == 1:
367 fragments.extend(frags_300)
369 for i, j in zip(frags_200, frags_300):
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 # run it all to verify correctness
382 self.pg_enable_capture()
383 self.src_if.add_stream(fragments)
386 packets = self.dst_if.get_capture(len(self.pkt_infos))
387 self.verify_capture(packets)
388 self.src_if.assert_nothing_captured()
390 def test_overlap2(self):
391 """ overlapping fragments case #2 """
394 for _, _, frags_300, frags_200 in self.pkt_infos:
395 if len(frags_300) == 1:
396 fragments.extend(frags_300)
398 # care must be taken here so that there are no fragments
399 # received by vpp after reassembly is finished, otherwise
400 # new reassemblies will be started and packet generator will
401 # freak out when it detects unfreed buffers
402 zipped = zip(frags_300, frags_200)
408 self.pg_enable_capture()
409 self.src_if.add_stream(fragments)
412 packets = self.dst_if.get_capture(len(self.pkt_infos))
413 self.verify_capture(packets)
414 self.src_if.assert_nothing_captured()
416 # run it all to verify correctness
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(packets)
423 self.src_if.assert_nothing_captured()
425 def test_timeout_inline(self):
426 """ timeout (inline) """
428 dropped_packet_indexes = set(
429 index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
432 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
433 max_reassembly_length=3,
434 expire_walk_interval_ms=10000)
436 self.pg_enable_capture()
437 self.src_if.add_stream(self.fragments_400)
440 packets = self.dst_if.get_capture(
441 len(self.pkt_infos) - len(dropped_packet_indexes))
442 self.verify_capture(packets, dropped_packet_indexes)
443 self.src_if.assert_nothing_captured()
445 def test_timeout_cleanup(self):
446 """ timeout (cleanup) """
448 # whole packets + fragmented packets sans last fragment
450 x for (_, frags_400, _, _) in self.pkt_infos
451 for x in frags_400[:-1 if len(frags_400) > 1 else None]
454 # last fragments for fragmented packets
455 fragments2 = [frags_400[-1]
456 for (_, frags_400, _, _) in self.pkt_infos
457 if len(frags_400) > 1]
459 dropped_packet_indexes = set(
460 index for (index, frags_400, _, _) in self.pkt_infos
461 if len(frags_400) > 1)
463 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
464 max_reassembly_length=1000,
465 expire_walk_interval_ms=50)
467 self.pg_enable_capture()
468 self.src_if.add_stream(fragments)
471 self.sleep(.25, "wait before sending rest of fragments")
473 self.src_if.add_stream(fragments2)
476 packets = self.dst_if.get_capture(
477 len(self.pkt_infos) - len(dropped_packet_indexes))
478 self.verify_capture(packets, dropped_packet_indexes)
479 self.src_if.assert_nothing_captured()
481 def test_disabled(self):
482 """ reassembly disabled """
484 dropped_packet_indexes = set(
485 index for (index, frags_400, _, _) in self.pkt_infos
486 if len(frags_400) > 1)
488 self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
489 max_reassembly_length=3,
490 expire_walk_interval_ms=10000)
492 self.pg_enable_capture()
493 self.src_if.add_stream(self.fragments_400)
496 packets = self.dst_if.get_capture(
497 len(self.pkt_infos) - len(dropped_packet_indexes))
498 self.verify_capture(packets, dropped_packet_indexes)
499 self.src_if.assert_nothing_captured()
502 class TestIPv6Reassembly(VppTestCase):
503 """ IPv6 Reassembly """
507 super(TestIPv6Reassembly, cls).setUpClass()
509 cls.create_pg_interfaces([0, 1])
513 # setup all interfaces
514 for i in cls.pg_interfaces:
520 cls.packet_sizes = [64, 512, 1518, 9018]
521 cls.padding = " abcdefghijklmn"
522 cls.create_stream(cls.packet_sizes)
523 cls.create_fragments()
526 def tearDownClass(cls):
527 super(TestIPv6Reassembly, cls).tearDownClass()
530 """ Test setup - force timeout on existing reassemblies """
531 super(TestIPv6Reassembly, self).setUp()
532 self.vapi.ip_reassembly_enable_disable(
533 sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
534 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
535 max_reassembly_length=1000,
536 expire_walk_interval_ms=10, is_ip6=1)
538 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
539 max_reassembly_length=1000,
540 expire_walk_interval_ms=10000, is_ip6=1)
541 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
542 self.logger.debug(self.vapi.ppcli("show buffers"))
545 super(TestIPv6Reassembly, self).tearDown()
547 def show_commands_at_teardown(self):
548 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
549 self.logger.debug(self.vapi.ppcli("show buffers"))
552 def create_stream(cls, packet_sizes, packet_count=test_packet_count):
553 """Create input packet stream for defined interface.
555 :param list packet_sizes: Required packet sizes.
557 for i in range(0, packet_count):
558 info = cls.create_packet_info(cls.src_if, cls.src_if)
559 payload = cls.info_to_payload(info)
560 p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
561 IPv6(src=cls.src_if.remote_ip6,
562 dst=cls.dst_if.remote_ip6) /
563 UDP(sport=1234, dport=5678) /
565 size = packet_sizes[(i // 2) % len(packet_sizes)]
566 cls.extend_packet(p, size, cls.padding)
570 def create_fragments(cls):
571 infos = cls._packet_infos
573 for index, info in six.iteritems(infos):
575 # cls.logger.debug(ppp("Packet:",
576 # p.__class__(scapy.compat.raw(p))))
577 fragments_400 = fragment_rfc8200(p, info.index, 400)
578 fragments_300 = fragment_rfc8200(p, info.index, 300)
579 cls.pkt_infos.append((index, fragments_400, fragments_300))
580 cls.fragments_400 = [
581 x for _, frags, _ in cls.pkt_infos for x in frags]
582 cls.fragments_300 = [
583 x for _, _, frags in cls.pkt_infos for x in frags]
584 cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
585 "and %s 300-byte fragments" %
586 (len(infos), len(cls.fragments_400),
587 len(cls.fragments_300)))
589 def verify_capture(self, capture, dropped_packet_indexes=[]):
590 """Verify captured packet strea .
592 :param list capture: Captured packet stream.
596 for packet in capture:
598 self.logger.debug(ppp("Got packet:", packet))
601 payload_info = self.payload_to_info(packet[Raw])
602 packet_index = payload_info.index
604 packet_index not in dropped_packet_indexes,
605 ppp("Packet received, but should be dropped:", packet))
606 if packet_index in seen:
607 raise Exception(ppp("Duplicate packet received", packet))
608 seen.add(packet_index)
609 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
610 info = self._packet_infos[packet_index]
611 self.assertTrue(info is not None)
612 self.assertEqual(packet_index, info.index)
613 saved_packet = info.data
614 self.assertEqual(ip.src, saved_packet[IPv6].src)
615 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
616 self.assertEqual(udp.payload, saved_packet[UDP].payload)
618 self.logger.error(ppp("Unexpected or invalid packet:", packet))
620 for index in self._packet_infos:
621 self.assertTrue(index in seen or index in dropped_packet_indexes,
622 "Packet with packet_index %d not received" % index)
624 def test_reassembly(self):
625 """ basic reassembly """
627 self.pg_enable_capture()
628 self.src_if.add_stream(self.fragments_400)
631 packets = self.dst_if.get_capture(len(self.pkt_infos))
632 self.verify_capture(packets)
633 self.src_if.assert_nothing_captured()
635 # run it all again to verify correctness
636 self.pg_enable_capture()
637 self.src_if.add_stream(self.fragments_400)
640 packets = self.dst_if.get_capture(len(self.pkt_infos))
641 self.verify_capture(packets)
642 self.src_if.assert_nothing_captured()
644 def test_reversed(self):
645 """ reverse order reassembly """
647 fragments = list(self.fragments_400)
650 self.pg_enable_capture()
651 self.src_if.add_stream(fragments)
654 packets = self.dst_if.get_capture(len(self.pkt_infos))
655 self.verify_capture(packets)
656 self.src_if.assert_nothing_captured()
658 # run it all again to verify correctness
659 self.pg_enable_capture()
660 self.src_if.add_stream(fragments)
663 packets = self.dst_if.get_capture(len(self.pkt_infos))
664 self.verify_capture(packets)
665 self.src_if.assert_nothing_captured()
667 def test_random(self):
668 """ random order reassembly """
670 fragments = list(self.fragments_400)
673 self.pg_enable_capture()
674 self.src_if.add_stream(fragments)
677 packets = self.dst_if.get_capture(len(self.pkt_infos))
678 self.verify_capture(packets)
679 self.src_if.assert_nothing_captured()
681 # run it all again to verify correctness
682 self.pg_enable_capture()
683 self.src_if.add_stream(fragments)
686 packets = self.dst_if.get_capture(len(self.pkt_infos))
687 self.verify_capture(packets)
688 self.src_if.assert_nothing_captured()
690 def test_duplicates(self):
691 """ duplicate fragments """
694 x for (_, frags, _) in self.pkt_infos
696 for _ in range(0, min(2, len(frags)))
699 self.pg_enable_capture()
700 self.src_if.add_stream(fragments)
703 packets = self.dst_if.get_capture(len(self.pkt_infos))
704 self.verify_capture(packets)
705 self.src_if.assert_nothing_captured()
707 def test_long_fragment_chain(self):
708 """ long fragment chain """
711 "/err/ip6-reassembly-feature/fragment chain too long (drop)"
713 error_cnt = self.statistics.get_err_counter(error_cnt_str)
715 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
716 max_reassembly_length=3,
717 expire_walk_interval_ms=50, is_ip6=1)
719 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
720 IPv6(src=self.src_if.remote_ip6,
721 dst=self.dst_if.remote_ip6) /
722 UDP(sport=1234, dport=5678) /
724 frags = fragment_rfc8200(p, 1, 300) + fragment_rfc8200(p, 2, 500)
726 self.pg_enable_capture()
727 self.src_if.add_stream(frags)
730 self.dst_if.get_capture(1)
731 self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
733 def test_overlap1(self):
734 """ overlapping fragments case #1 """
737 for _, frags_400, frags_300 in self.pkt_infos:
738 if len(frags_300) == 1:
739 fragments.extend(frags_400)
741 for i, j in zip(frags_300, frags_400):
745 dropped_packet_indexes = set(
746 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
749 self.pg_enable_capture()
750 self.src_if.add_stream(fragments)
753 packets = self.dst_if.get_capture(
754 len(self.pkt_infos) - len(dropped_packet_indexes))
755 self.verify_capture(packets, dropped_packet_indexes)
756 self.src_if.assert_nothing_captured()
758 def test_overlap2(self):
759 """ overlapping fragments case #2 """
762 for _, frags_400, frags_300 in self.pkt_infos:
763 if len(frags_400) == 1:
764 fragments.extend(frags_400)
766 # care must be taken here so that there are no fragments
767 # received by vpp after reassembly is finished, otherwise
768 # new reassemblies will be started and packet generator will
769 # freak out when it detects unfreed buffers
770 zipped = zip(frags_400, frags_300)
776 dropped_packet_indexes = set(
777 index for (index, _, frags) in self.pkt_infos if len(frags) > 1
780 self.pg_enable_capture()
781 self.src_if.add_stream(fragments)
784 packets = self.dst_if.get_capture(
785 len(self.pkt_infos) - len(dropped_packet_indexes))
786 self.verify_capture(packets, dropped_packet_indexes)
787 self.src_if.assert_nothing_captured()
789 def test_timeout_inline(self):
790 """ timeout (inline) """
792 dropped_packet_indexes = set(
793 index for (index, frags, _) in self.pkt_infos if len(frags) > 1
796 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
797 max_reassembly_length=3,
798 expire_walk_interval_ms=10000, is_ip6=1)
800 self.pg_enable_capture()
801 self.src_if.add_stream(self.fragments_400)
804 packets = self.dst_if.get_capture(
805 len(self.pkt_infos) - len(dropped_packet_indexes))
806 self.verify_capture(packets, dropped_packet_indexes)
807 pkts = self.src_if.get_capture(
808 expected_count=len(dropped_packet_indexes))
810 self.assertIn(ICMPv6TimeExceeded, icmp)
811 self.assertIn(IPv6ExtHdrFragment, icmp)
812 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
813 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
815 def test_timeout_cleanup(self):
816 """ timeout (cleanup) """
818 # whole packets + fragmented packets sans last fragment
820 x for (_, frags_400, _) in self.pkt_infos
821 for x in frags_400[:-1 if len(frags_400) > 1 else None]
824 # last fragments for fragmented packets
825 fragments2 = [frags_400[-1]
826 for (_, frags_400, _) in self.pkt_infos
827 if len(frags_400) > 1]
829 dropped_packet_indexes = set(
830 index for (index, frags_400, _) in self.pkt_infos
831 if len(frags_400) > 1)
833 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
834 max_reassembly_length=1000,
835 expire_walk_interval_ms=50)
837 self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
838 max_reassembly_length=1000,
839 expire_walk_interval_ms=50, is_ip6=1)
841 self.pg_enable_capture()
842 self.src_if.add_stream(fragments)
845 self.sleep(.25, "wait before sending rest of fragments")
847 self.src_if.add_stream(fragments2)
850 packets = self.dst_if.get_capture(
851 len(self.pkt_infos) - len(dropped_packet_indexes))
852 self.verify_capture(packets, dropped_packet_indexes)
853 pkts = self.src_if.get_capture(
854 expected_count=len(dropped_packet_indexes))
856 self.assertIn(ICMPv6TimeExceeded, icmp)
857 self.assertIn(IPv6ExtHdrFragment, icmp)
858 self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
859 dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
861 def test_disabled(self):
862 """ reassembly disabled """
864 dropped_packet_indexes = set(
865 index for (index, frags_400, _) in self.pkt_infos
866 if len(frags_400) > 1)
868 self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
869 max_reassembly_length=3,
870 expire_walk_interval_ms=10000, is_ip6=1)
872 self.pg_enable_capture()
873 self.src_if.add_stream(self.fragments_400)
876 packets = self.dst_if.get_capture(
877 len(self.pkt_infos) - len(dropped_packet_indexes))
878 self.verify_capture(packets, dropped_packet_indexes)
879 self.src_if.assert_nothing_captured()
881 def test_missing_upper(self):
882 """ missing upper layer """
883 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
884 IPv6(src=self.src_if.remote_ip6,
885 dst=self.src_if.local_ip6) /
886 UDP(sport=1234, dport=5678) /
888 self.extend_packet(p, 1000, self.padding)
889 fragments = fragment_rfc8200(p, 1, 500)
890 bad_fragment = p.__class__(scapy.compat.raw(fragments[1]))
891 bad_fragment[IPv6ExtHdrFragment].nh = 59
892 bad_fragment[IPv6ExtHdrFragment].offset = 0
893 self.pg_enable_capture()
894 self.src_if.add_stream([bad_fragment])
896 pkts = self.src_if.get_capture(expected_count=1)
898 self.assertIn(ICMPv6ParamProblem, icmp)
899 self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
901 def test_invalid_frag_size(self):
902 """ fragment size not a multiple of 8 """
903 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
904 IPv6(src=self.src_if.remote_ip6,
905 dst=self.src_if.local_ip6) /
906 UDP(sport=1234, dport=5678) /
908 self.extend_packet(p, 1000, self.padding)
909 fragments = fragment_rfc8200(p, 1, 500)
910 bad_fragment = fragments[0]
911 self.extend_packet(bad_fragment, len(bad_fragment) + 5)
912 self.pg_enable_capture()
913 self.src_if.add_stream([bad_fragment])
915 pkts = self.src_if.get_capture(expected_count=1)
917 self.assertIn(ICMPv6ParamProblem, icmp)
918 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
920 def test_invalid_packet_size(self):
921 """ total packet size > 65535 """
922 p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
923 IPv6(src=self.src_if.remote_ip6,
924 dst=self.src_if.local_ip6) /
925 UDP(sport=1234, dport=5678) /
927 self.extend_packet(p, 1000, self.padding)
928 fragments = fragment_rfc8200(p, 1, 500)
929 bad_fragment = fragments[1]
930 bad_fragment[IPv6ExtHdrFragment].offset = 65500
931 self.pg_enable_capture()
932 self.src_if.add_stream([bad_fragment])
934 pkts = self.src_if.get_capture(expected_count=1)
936 self.assertIn(ICMPv6ParamProblem, icmp)
937 self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
940 class TestIPv4ReassemblyLocalNode(VppTestCase):
941 """ IPv4 Reassembly for packets coming to ip4-local node """
945 super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
947 cls.create_pg_interfaces([0])
948 cls.src_dst_if = cls.pg0
950 # setup all interfaces
951 for i in cls.pg_interfaces:
956 cls.padding = " abcdefghijklmn"
958 cls.create_fragments()
961 def tearDownClass(cls):
962 super(TestIPv4ReassemblyLocalNode, cls).tearDownClass()
965 """ Test setup - force timeout on existing reassemblies """
966 super(TestIPv4ReassemblyLocalNode, self).setUp()
967 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
968 max_reassembly_length=1000,
969 expire_walk_interval_ms=10)
971 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
972 max_reassembly_length=1000,
973 expire_walk_interval_ms=10000)
976 super(TestIPv4ReassemblyLocalNode, self).tearDown()
978 def show_commands_at_teardown(self):
979 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
980 self.logger.debug(self.vapi.ppcli("show buffers"))
983 def create_stream(cls, packet_count=test_packet_count):
984 """Create input packet stream for defined interface.
986 :param list packet_sizes: Required packet sizes.
988 for i in range(0, packet_count):
989 info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
990 payload = cls.info_to_payload(info)
991 p = (Ether(dst=cls.src_dst_if.local_mac,
992 src=cls.src_dst_if.remote_mac) /
993 IP(id=info.index, src=cls.src_dst_if.remote_ip4,
994 dst=cls.src_dst_if.local_ip4) /
995 ICMP(type='echo-request', id=1234) /
997 cls.extend_packet(p, 1518, cls.padding)
1001 def create_fragments(cls):
1002 infos = cls._packet_infos
1004 for index, info in six.iteritems(infos):
1006 # cls.logger.debug(ppp("Packet:",
1007 # p.__class__(scapy.compat.raw(p))))
1008 fragments_300 = fragment_rfc791(p, 300)
1009 cls.pkt_infos.append((index, fragments_300))
1010 cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
1011 cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
1012 (len(infos), len(cls.fragments_300)))
1014 def verify_capture(self, capture):
1015 """Verify captured packet stream.
1017 :param list capture: Captured packet stream.
1021 for packet in capture:
1023 self.logger.debug(ppp("Got packet:", packet))
1026 payload_info = self.payload_to_info(packet[Raw])
1027 packet_index = payload_info.index
1028 if packet_index in seen:
1029 raise Exception(ppp("Duplicate packet received", packet))
1030 seen.add(packet_index)
1031 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
1032 info = self._packet_infos[packet_index]
1033 self.assertIsNotNone(info)
1034 self.assertEqual(packet_index, info.index)
1035 saved_packet = info.data
1036 self.assertEqual(ip.src, saved_packet[IP].dst)
1037 self.assertEqual(ip.dst, saved_packet[IP].src)
1038 self.assertEqual(icmp.type, 0) # echo reply
1039 self.assertEqual(icmp.id, saved_packet[ICMP].id)
1040 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
1042 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1044 for index in self._packet_infos:
1045 self.assertIn(index, seen,
1046 "Packet with packet_index %d not received" % index)
1048 def test_reassembly(self):
1049 """ basic reassembly """
1051 self.pg_enable_capture()
1052 self.src_dst_if.add_stream(self.fragments_300)
1055 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1056 self.verify_capture(packets)
1058 # run it all again to verify correctness
1059 self.pg_enable_capture()
1060 self.src_dst_if.add_stream(self.fragments_300)
1063 packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1064 self.verify_capture(packets)
1067 class TestFIFReassembly(VppTestCase):
1068 """ Fragments in fragments reassembly """
1071 def setUpClass(cls):
1072 super(TestFIFReassembly, cls).setUpClass()
1074 cls.create_pg_interfaces([0, 1])
1075 cls.src_if = cls.pg0
1076 cls.dst_if = cls.pg1
1077 for i in cls.pg_interfaces:
1084 cls.packet_sizes = [64, 512, 1518, 9018]
1085 cls.padding = " abcdefghijklmn"
1088 def tearDownClass(cls):
1089 super(TestFIFReassembly, cls).tearDownClass()
1092 """ Test setup - force timeout on existing reassemblies """
1093 super(TestFIFReassembly, self).setUp()
1094 self.vapi.ip_reassembly_enable_disable(
1095 sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
1097 self.vapi.ip_reassembly_enable_disable(
1098 sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
1100 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1101 max_reassembly_length=1000,
1102 expire_walk_interval_ms=10)
1103 self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1104 max_reassembly_length=1000,
1105 expire_walk_interval_ms=10, is_ip6=1)
1107 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1108 max_reassembly_length=1000,
1109 expire_walk_interval_ms=10000)
1110 self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1111 max_reassembly_length=1000,
1112 expire_walk_interval_ms=10000, is_ip6=1)
1115 super(TestFIFReassembly, self).tearDown()
1117 def show_commands_at_teardown(self):
1118 self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
1119 self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
1120 self.logger.debug(self.vapi.ppcli("show buffers"))
1122 def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
1123 """Verify captured packet stream.
1125 :param list capture: Captured packet stream.
1129 for packet in capture:
1131 self.logger.debug(ppp("Got packet:", packet))
1132 ip = packet[ip_class]
1134 payload_info = self.payload_to_info(packet[Raw])
1135 packet_index = payload_info.index
1137 packet_index not in dropped_packet_indexes,
1138 ppp("Packet received, but should be dropped:", packet))
1139 if packet_index in seen:
1140 raise Exception(ppp("Duplicate packet received", packet))
1141 seen.add(packet_index)
1142 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1143 info = self._packet_infos[packet_index]
1144 self.assertTrue(info is not None)
1145 self.assertEqual(packet_index, info.index)
1146 saved_packet = info.data
1147 self.assertEqual(ip.src, saved_packet[ip_class].src)
1148 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1149 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1151 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1153 for index in self._packet_infos:
1154 self.assertTrue(index in seen or index in dropped_packet_indexes,
1155 "Packet with packet_index %d not received" % index)
1157 def test_fif4(self):
1158 """ Fragments in fragments (4o4) """
1160 # TODO this should be ideally in setUpClass, but then we hit a bug
1161 # with VppIpRoute incorrectly reporting it's present when it's not
1162 # so we need to manually remove the vpp config, thus we cannot have
1163 # it shared for multiple test cases
1164 self.tun_ip4 = "1.1.1.2"
1166 self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1167 self.gre4.add_vpp_config()
1168 self.gre4.admin_up()
1169 self.gre4.config_ip4()
1171 self.vapi.ip_reassembly_enable_disable(
1172 sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1174 self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1175 [VppRoutePath(self.src_if.remote_ip4,
1176 self.src_if.sw_if_index)])
1177 self.route4.add_vpp_config()
1179 self.reset_packet_infos()
1180 for i in range(test_packet_count):
1181 info = self.create_packet_info(self.src_if, self.dst_if)
1182 payload = self.info_to_payload(info)
1183 # Ethernet header here is only for size calculation, thus it
1184 # doesn't matter how it's initialized. This is to ensure that
1185 # reassembled packet is not > 9000 bytes, so that it's not dropped
1187 IP(id=i, src=self.src_if.remote_ip4,
1188 dst=self.dst_if.remote_ip4) /
1189 UDP(sport=1234, dport=5678) /
1191 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1192 self.extend_packet(p, size, self.padding)
1193 info.data = p[IP] # use only IP part, without ethernet header
1195 fragments = [x for _, p in six.iteritems(self._packet_infos)
1196 for x in fragment_rfc791(p.data, 400)]
1198 encapped_fragments = \
1199 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1200 IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1205 fragmented_encapped_fragments = \
1206 [x for p in encapped_fragments
1207 for x in fragment_rfc791(p, 200)]
1209 self.src_if.add_stream(fragmented_encapped_fragments)
1211 self.pg_enable_capture(self.pg_interfaces)
1214 self.src_if.assert_nothing_captured()
1215 packets = self.dst_if.get_capture(len(self._packet_infos))
1216 self.verify_capture(packets, IP)
1218 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1219 # so that it's query_vpp_config() works as it should
1220 self.gre4.remove_vpp_config()
1221 self.logger.debug(self.vapi.ppcli("show interface"))
1223 def test_fif6(self):
1224 """ Fragments in fragments (6o6) """
1225 # TODO this should be ideally in setUpClass, but then we hit a bug
1226 # with VppIpRoute incorrectly reporting it's present when it's not
1227 # so we need to manually remove the vpp config, thus we cannot have
1228 # it shared for multiple test cases
1229 self.tun_ip6 = "1002::1"
1231 self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
1232 self.gre6.add_vpp_config()
1233 self.gre6.admin_up()
1234 self.gre6.config_ip6()
1236 self.vapi.ip_reassembly_enable_disable(
1237 sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1239 self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1241 self.src_if.remote_ip6,
1242 self.src_if.sw_if_index)])
1243 self.route6.add_vpp_config()
1245 self.reset_packet_infos()
1246 for i in range(test_packet_count):
1247 info = self.create_packet_info(self.src_if, self.dst_if)
1248 payload = self.info_to_payload(info)
1249 # Ethernet header here is only for size calculation, thus it
1250 # doesn't matter how it's initialized. This is to ensure that
1251 # reassembled packet is not > 9000 bytes, so that it's not dropped
1253 IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1254 UDP(sport=1234, dport=5678) /
1256 size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1257 self.extend_packet(p, size, self.padding)
1258 info.data = p[IPv6] # use only IPv6 part, without ethernet header
1260 fragments = [x for _, i in six.iteritems(self._packet_infos)
1261 for x in fragment_rfc8200(
1262 i.data, i.index, 400)]
1264 encapped_fragments = \
1265 [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1266 IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1271 fragmented_encapped_fragments = \
1272 [x for p in encapped_fragments for x in (
1275 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1277 if IPv6ExtHdrFragment in p else [p]
1281 self.src_if.add_stream(fragmented_encapped_fragments)
1283 self.pg_enable_capture(self.pg_interfaces)
1286 self.src_if.assert_nothing_captured()
1287 packets = self.dst_if.get_capture(len(self._packet_infos))
1288 self.verify_capture(packets, IPv6)
1290 # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1291 # so that it's query_vpp_config() works as it should
1292 self.gre6.remove_vpp_config()
1295 if __name__ == '__main__':
1296 unittest.main(testRunner=VppTestRunner)