VPP-1508 python3 tests: use six.iteritems
[vpp.git] / test / test_reassembly.py
1 #!/usr/bin/env python
2
3 import six
4 import unittest
5 from random import shuffle
6
7 from framework import VppTestCase, VppTestRunner
8
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,\
14     ICMPv6TimeExceeded
15 from vpp_gre_interface import VppGreInterface, VppGre6Interface
16 from vpp_ip import DpoProto
17 from vpp_ip_route import VppIpRoute, VppRoutePath
18
19 test_packet_count = 257
20
21
22 class TestIPv4Reassembly(VppTestCase):
23     """ IPv4 Reassembly """
24
25     @classmethod
26     def setUpClass(cls):
27         super(TestIPv4Reassembly, cls).setUpClass()
28
29         cls.create_pg_interfaces([0, 1])
30         cls.src_if = cls.pg0
31         cls.dst_if = cls.pg1
32
33         # setup all interfaces
34         for i in cls.pg_interfaces:
35             i.admin_up()
36             i.config_ip4()
37             i.resolve_arp()
38
39         # packet sizes
40         cls.packet_sizes = [64, 512, 1518, 9018]
41         cls.padding = " abcdefghijklmn"
42         cls.create_stream(cls.packet_sizes)
43         cls.create_fragments()
44
45     def setUp(self):
46         """ Test setup - force timeout on existing reassemblies """
47         super(TestIPv4Reassembly, self).setUp()
48         self.vapi.ip_reassembly_enable_disable(
49             sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
50         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
51                                     expire_walk_interval_ms=10)
52         self.sleep(.25)
53         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
54                                     expire_walk_interval_ms=10000)
55
56     def tearDown(self):
57         super(TestIPv4Reassembly, self).tearDown()
58         self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
59
60     @classmethod
61     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
62         """Create input packet stream for defined interface.
63
64         :param list packet_sizes: Required packet sizes.
65         """
66         for i in range(0, packet_count):
67             info = cls.create_packet_info(cls.src_if, cls.src_if)
68             payload = cls.info_to_payload(info)
69             p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
70                  IP(id=info.index, src=cls.src_if.remote_ip4,
71                     dst=cls.dst_if.remote_ip4) /
72                  UDP(sport=1234, dport=5678) /
73                  Raw(payload))
74             size = packet_sizes[(i // 2) % len(packet_sizes)]
75             cls.extend_packet(p, size, cls.padding)
76             info.data = p
77
78     @classmethod
79     def create_fragments(cls):
80         infos = cls._packet_infos
81         cls.pkt_infos = []
82         for index, info in six.iteritems(infos):
83             p = info.data
84             # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
85             fragments_400 = fragment_rfc791(p, 400)
86             fragments_300 = fragment_rfc791(p, 300)
87             fragments_200 = [
88                 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
89             cls.pkt_infos.append(
90                 (index, fragments_400, fragments_300, fragments_200))
91         cls.fragments_400 = [
92             x for (_, frags, _, _) in cls.pkt_infos for x in frags]
93         cls.fragments_300 = [
94             x for (_, _, frags, _) in cls.pkt_infos for x in frags]
95         cls.fragments_200 = [
96             x for (_, _, _, frags) in cls.pkt_infos for x in frags]
97         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
98                          "%s 300-byte fragments and %s 200-byte fragments" %
99                          (len(infos), len(cls.fragments_400),
100                              len(cls.fragments_300), len(cls.fragments_200)))
101
102     def verify_capture(self, capture, dropped_packet_indexes=[]):
103         """Verify captured packet stream.
104
105         :param list capture: Captured packet stream.
106         """
107         info = None
108         seen = set()
109         for packet in capture:
110             try:
111                 self.logger.debug(ppp("Got packet:", packet))
112                 ip = packet[IP]
113                 udp = packet[UDP]
114                 payload_info = self.payload_to_info(str(packet[Raw]))
115                 packet_index = payload_info.index
116                 self.assertTrue(
117                     packet_index not in dropped_packet_indexes,
118                     ppp("Packet received, but should be dropped:", packet))
119                 if packet_index in seen:
120                     raise Exception(ppp("Duplicate packet received", packet))
121                 seen.add(packet_index)
122                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
123                 info = self._packet_infos[packet_index]
124                 self.assertTrue(info is not None)
125                 self.assertEqual(packet_index, info.index)
126                 saved_packet = info.data
127                 self.assertEqual(ip.src, saved_packet[IP].src)
128                 self.assertEqual(ip.dst, saved_packet[IP].dst)
129                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
130             except Exception:
131                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
132                 raise
133         for index in self._packet_infos:
134             self.assertTrue(index in seen or index in dropped_packet_indexes,
135                             "Packet with packet_index %d not received" % index)
136
137     def test_reassembly(self):
138         """ basic reassembly """
139
140         self.pg_enable_capture()
141         self.src_if.add_stream(self.fragments_200)
142         self.pg_start()
143
144         packets = self.dst_if.get_capture(len(self.pkt_infos))
145         self.verify_capture(packets)
146         self.src_if.assert_nothing_captured()
147
148         # run it all again to verify correctness
149         self.pg_enable_capture()
150         self.src_if.add_stream(self.fragments_200)
151         self.pg_start()
152
153         packets = self.dst_if.get_capture(len(self.pkt_infos))
154         self.verify_capture(packets)
155         self.src_if.assert_nothing_captured()
156
157     def test_reversed(self):
158         """ reverse order reassembly """
159
160         fragments = list(self.fragments_200)
161         fragments.reverse()
162
163         self.pg_enable_capture()
164         self.src_if.add_stream(fragments)
165         self.pg_start()
166
167         packets = self.dst_if.get_capture(len(self.packet_infos))
168         self.verify_capture(packets)
169         self.src_if.assert_nothing_captured()
170
171         # run it all again to verify correctness
172         self.pg_enable_capture()
173         self.src_if.add_stream(fragments)
174         self.pg_start()
175
176         packets = self.dst_if.get_capture(len(self.packet_infos))
177         self.verify_capture(packets)
178         self.src_if.assert_nothing_captured()
179
180     def test_random(self):
181         """ random order reassembly """
182
183         fragments = list(self.fragments_200)
184         shuffle(fragments)
185
186         self.pg_enable_capture()
187         self.src_if.add_stream(fragments)
188         self.pg_start()
189
190         packets = self.dst_if.get_capture(len(self.packet_infos))
191         self.verify_capture(packets)
192         self.src_if.assert_nothing_captured()
193
194         # run it all again to verify correctness
195         self.pg_enable_capture()
196         self.src_if.add_stream(fragments)
197         self.pg_start()
198
199         packets = self.dst_if.get_capture(len(self.packet_infos))
200         self.verify_capture(packets)
201         self.src_if.assert_nothing_captured()
202
203     def test_duplicates(self):
204         """ duplicate fragments """
205
206         fragments = [
207             x for (_, frags, _, _) in self.pkt_infos
208             for x in frags
209             for _ in range(0, min(2, len(frags)))
210         ]
211
212         self.pg_enable_capture()
213         self.src_if.add_stream(fragments)
214         self.pg_start()
215
216         packets = self.dst_if.get_capture(len(self.pkt_infos))
217         self.verify_capture(packets)
218         self.src_if.assert_nothing_captured()
219
220     def test_overlap1(self):
221         """ overlapping fragments case #1 """
222
223         fragments = []
224         for _, _, frags_300, frags_200 in self.pkt_infos:
225             if len(frags_300) == 1:
226                 fragments.extend(frags_300)
227             else:
228                 for i, j in zip(frags_200, frags_300):
229                     fragments.extend(i)
230                     fragments.extend(j)
231
232         self.pg_enable_capture()
233         self.src_if.add_stream(fragments)
234         self.pg_start()
235
236         packets = self.dst_if.get_capture(len(self.pkt_infos))
237         self.verify_capture(packets)
238         self.src_if.assert_nothing_captured()
239
240         # run it all to verify correctness
241         self.pg_enable_capture()
242         self.src_if.add_stream(fragments)
243         self.pg_start()
244
245         packets = self.dst_if.get_capture(len(self.pkt_infos))
246         self.verify_capture(packets)
247         self.src_if.assert_nothing_captured()
248
249     def test_overlap2(self):
250         """ overlapping fragments case #2 """
251
252         fragments = []
253         for _, _, frags_300, frags_200 in self.pkt_infos:
254             if len(frags_300) == 1:
255                 fragments.extend(frags_300)
256             else:
257                 # care must be taken here so that there are no fragments
258                 # received by vpp after reassembly is finished, otherwise
259                 # new reassemblies will be started and packet generator will
260                 # freak out when it detects unfreed buffers
261                 zipped = zip(frags_300, frags_200)
262                 for i, j in zipped[:-1]:
263                     fragments.extend(i)
264                     fragments.extend(j)
265                 fragments.append(zipped[-1][0])
266
267         self.pg_enable_capture()
268         self.src_if.add_stream(fragments)
269         self.pg_start()
270
271         packets = self.dst_if.get_capture(len(self.pkt_infos))
272         self.verify_capture(packets)
273         self.src_if.assert_nothing_captured()
274
275         # run it all to verify correctness
276         self.pg_enable_capture()
277         self.src_if.add_stream(fragments)
278         self.pg_start()
279
280         packets = self.dst_if.get_capture(len(self.pkt_infos))
281         self.verify_capture(packets)
282         self.src_if.assert_nothing_captured()
283
284     def test_timeout_inline(self):
285         """ timeout (inline) """
286
287         dropped_packet_indexes = set(
288             index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
289         )
290
291         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
292                                     expire_walk_interval_ms=10000)
293
294         self.pg_enable_capture()
295         self.src_if.add_stream(self.fragments_400)
296         self.pg_start()
297
298         packets = self.dst_if.get_capture(
299             len(self.pkt_infos) - len(dropped_packet_indexes))
300         self.verify_capture(packets, dropped_packet_indexes)
301         self.src_if.assert_nothing_captured()
302
303     def test_timeout_cleanup(self):
304         """ timeout (cleanup) """
305
306         # whole packets + fragmented packets sans last fragment
307         fragments = [
308             x for (_, frags_400, _, _) in self.pkt_infos
309             for x in frags_400[:-1 if len(frags_400) > 1 else None]
310         ]
311
312         # last fragments for fragmented packets
313         fragments2 = [frags_400[-1]
314                       for (_, frags_400, _, _) in self.pkt_infos
315                       if len(frags_400) > 1]
316
317         dropped_packet_indexes = set(
318             index for (index, frags_400, _, _) in self.pkt_infos
319             if len(frags_400) > 1)
320
321         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
322                                     expire_walk_interval_ms=50)
323
324         self.pg_enable_capture()
325         self.src_if.add_stream(fragments)
326         self.pg_start()
327
328         self.sleep(.25, "wait before sending rest of fragments")
329
330         self.src_if.add_stream(fragments2)
331         self.pg_start()
332
333         packets = self.dst_if.get_capture(
334             len(self.pkt_infos) - len(dropped_packet_indexes))
335         self.verify_capture(packets, dropped_packet_indexes)
336         self.src_if.assert_nothing_captured()
337
338     def test_disabled(self):
339         """ reassembly disabled """
340
341         dropped_packet_indexes = set(
342             index for (index, frags_400, _, _) in self.pkt_infos
343             if len(frags_400) > 1)
344
345         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
346                                     expire_walk_interval_ms=10000)
347
348         self.pg_enable_capture()
349         self.src_if.add_stream(self.fragments_400)
350         self.pg_start()
351
352         packets = self.dst_if.get_capture(
353             len(self.pkt_infos) - len(dropped_packet_indexes))
354         self.verify_capture(packets, dropped_packet_indexes)
355         self.src_if.assert_nothing_captured()
356
357
358 class TestIPv6Reassembly(VppTestCase):
359     """ IPv6 Reassembly """
360
361     @classmethod
362     def setUpClass(cls):
363         super(TestIPv6Reassembly, cls).setUpClass()
364
365         cls.create_pg_interfaces([0, 1])
366         cls.src_if = cls.pg0
367         cls.dst_if = cls.pg1
368
369         # setup all interfaces
370         for i in cls.pg_interfaces:
371             i.admin_up()
372             i.config_ip6()
373             i.resolve_ndp()
374
375         # packet sizes
376         cls.packet_sizes = [64, 512, 1518, 9018]
377         cls.padding = " abcdefghijklmn"
378         cls.create_stream(cls.packet_sizes)
379         cls.create_fragments()
380
381     def setUp(self):
382         """ Test setup - force timeout on existing reassemblies """
383         super(TestIPv6Reassembly, self).setUp()
384         self.vapi.ip_reassembly_enable_disable(
385             sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
386         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
387                                     expire_walk_interval_ms=10, is_ip6=1)
388         self.sleep(.25)
389         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
390                                     expire_walk_interval_ms=10000, is_ip6=1)
391         self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
392
393     def tearDown(self):
394         super(TestIPv6Reassembly, self).tearDown()
395         self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
396
397     @classmethod
398     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
399         """Create input packet stream for defined interface.
400
401         :param list packet_sizes: Required packet sizes.
402         """
403         for i in range(0, packet_count):
404             info = cls.create_packet_info(cls.src_if, cls.src_if)
405             payload = cls.info_to_payload(info)
406             p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
407                  IPv6(src=cls.src_if.remote_ip6,
408                       dst=cls.dst_if.remote_ip6) /
409                  UDP(sport=1234, dport=5678) /
410                  Raw(payload))
411             size = packet_sizes[(i // 2) % len(packet_sizes)]
412             cls.extend_packet(p, size, cls.padding)
413             info.data = p
414
415     @classmethod
416     def create_fragments(cls):
417         infos = cls._packet_infos
418         cls.pkt_infos = []
419         for index, info in six.iteritems(infos):
420             p = info.data
421             # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
422             fragments_400 = fragment_rfc8200(p, info.index, 400)
423             fragments_300 = fragment_rfc8200(p, info.index, 300)
424             cls.pkt_infos.append((index, fragments_400, fragments_300))
425         cls.fragments_400 = [
426             x for _, frags, _ in cls.pkt_infos for x in frags]
427         cls.fragments_300 = [
428             x for _, _, frags in cls.pkt_infos for x in frags]
429         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
430                          "and %s 300-byte fragments" %
431                          (len(infos), len(cls.fragments_400),
432                              len(cls.fragments_300)))
433
434     def verify_capture(self, capture, dropped_packet_indexes=[]):
435         """Verify captured packet strea .
436
437         :param list capture: Captured packet stream.
438         """
439         info = None
440         seen = set()
441         for packet in capture:
442             try:
443                 self.logger.debug(ppp("Got packet:", packet))
444                 ip = packet[IPv6]
445                 udp = packet[UDP]
446                 payload_info = self.payload_to_info(str(packet[Raw]))
447                 packet_index = payload_info.index
448                 self.assertTrue(
449                     packet_index not in dropped_packet_indexes,
450                     ppp("Packet received, but should be dropped:", packet))
451                 if packet_index in seen:
452                     raise Exception(ppp("Duplicate packet received", packet))
453                 seen.add(packet_index)
454                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
455                 info = self._packet_infos[packet_index]
456                 self.assertTrue(info is not None)
457                 self.assertEqual(packet_index, info.index)
458                 saved_packet = info.data
459                 self.assertEqual(ip.src, saved_packet[IPv6].src)
460                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
461                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
462             except Exception:
463                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
464                 raise
465         for index in self._packet_infos:
466             self.assertTrue(index in seen or index in dropped_packet_indexes,
467                             "Packet with packet_index %d not received" % index)
468
469     def test_reassembly(self):
470         """ basic reassembly """
471
472         self.pg_enable_capture()
473         self.src_if.add_stream(self.fragments_400)
474         self.pg_start()
475
476         packets = self.dst_if.get_capture(len(self.pkt_infos))
477         self.verify_capture(packets)
478         self.src_if.assert_nothing_captured()
479
480         # run it all again to verify correctness
481         self.pg_enable_capture()
482         self.src_if.add_stream(self.fragments_400)
483         self.pg_start()
484
485         packets = self.dst_if.get_capture(len(self.pkt_infos))
486         self.verify_capture(packets)
487         self.src_if.assert_nothing_captured()
488
489     def test_reversed(self):
490         """ reverse order reassembly """
491
492         fragments = list(self.fragments_400)
493         fragments.reverse()
494
495         self.pg_enable_capture()
496         self.src_if.add_stream(fragments)
497         self.pg_start()
498
499         packets = self.dst_if.get_capture(len(self.pkt_infos))
500         self.verify_capture(packets)
501         self.src_if.assert_nothing_captured()
502
503         # run it all again to verify correctness
504         self.pg_enable_capture()
505         self.src_if.add_stream(fragments)
506         self.pg_start()
507
508         packets = self.dst_if.get_capture(len(self.pkt_infos))
509         self.verify_capture(packets)
510         self.src_if.assert_nothing_captured()
511
512     def test_random(self):
513         """ random order reassembly """
514
515         fragments = list(self.fragments_400)
516         shuffle(fragments)
517
518         self.pg_enable_capture()
519         self.src_if.add_stream(fragments)
520         self.pg_start()
521
522         packets = self.dst_if.get_capture(len(self.pkt_infos))
523         self.verify_capture(packets)
524         self.src_if.assert_nothing_captured()
525
526         # run it all again to verify correctness
527         self.pg_enable_capture()
528         self.src_if.add_stream(fragments)
529         self.pg_start()
530
531         packets = self.dst_if.get_capture(len(self.pkt_infos))
532         self.verify_capture(packets)
533         self.src_if.assert_nothing_captured()
534
535     def test_duplicates(self):
536         """ duplicate fragments """
537
538         fragments = [
539             x for (_, frags, _) in self.pkt_infos
540             for x in frags
541             for _ in range(0, min(2, len(frags)))
542         ]
543
544         self.pg_enable_capture()
545         self.src_if.add_stream(fragments)
546         self.pg_start()
547
548         packets = self.dst_if.get_capture(len(self.pkt_infos))
549         self.verify_capture(packets)
550         self.src_if.assert_nothing_captured()
551
552     def test_overlap1(self):
553         """ overlapping fragments case #1 """
554
555         fragments = []
556         for _, frags_400, frags_300 in self.pkt_infos:
557             if len(frags_300) == 1:
558                 fragments.extend(frags_400)
559             else:
560                 for i, j in zip(frags_300, frags_400):
561                     fragments.extend(i)
562                     fragments.extend(j)
563
564         dropped_packet_indexes = set(
565             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
566         )
567
568         self.pg_enable_capture()
569         self.src_if.add_stream(fragments)
570         self.pg_start()
571
572         packets = self.dst_if.get_capture(
573             len(self.pkt_infos) - len(dropped_packet_indexes))
574         self.verify_capture(packets, dropped_packet_indexes)
575         self.src_if.assert_nothing_captured()
576
577     def test_overlap2(self):
578         """ overlapping fragments case #2 """
579
580         fragments = []
581         for _, frags_400, frags_300 in self.pkt_infos:
582             if len(frags_400) == 1:
583                 fragments.extend(frags_400)
584             else:
585                 # care must be taken here so that there are no fragments
586                 # received by vpp after reassembly is finished, otherwise
587                 # new reassemblies will be started and packet generator will
588                 # freak out when it detects unfreed buffers
589                 zipped = zip(frags_400, frags_300)
590                 for i, j in zipped[:-1]:
591                     fragments.extend(i)
592                     fragments.extend(j)
593                 fragments.append(zipped[-1][0])
594
595         dropped_packet_indexes = set(
596             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
597         )
598
599         self.pg_enable_capture()
600         self.src_if.add_stream(fragments)
601         self.pg_start()
602
603         packets = self.dst_if.get_capture(
604             len(self.pkt_infos) - len(dropped_packet_indexes))
605         self.verify_capture(packets, dropped_packet_indexes)
606         self.src_if.assert_nothing_captured()
607
608     def test_timeout_inline(self):
609         """ timeout (inline) """
610
611         dropped_packet_indexes = set(
612             index for (index, frags, _) in self.pkt_infos if len(frags) > 1
613         )
614
615         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
616                                     expire_walk_interval_ms=10000, is_ip6=1)
617
618         self.pg_enable_capture()
619         self.src_if.add_stream(self.fragments_400)
620         self.pg_start()
621
622         packets = self.dst_if.get_capture(
623             len(self.pkt_infos) - len(dropped_packet_indexes))
624         self.verify_capture(packets, dropped_packet_indexes)
625         pkts = self.src_if.get_capture(
626             expected_count=len(dropped_packet_indexes))
627         for icmp in pkts:
628             self.assertIn(ICMPv6TimeExceeded, icmp)
629             self.assertIn(IPv6ExtHdrFragment, icmp)
630             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
631             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
632
633     def test_timeout_cleanup(self):
634         """ timeout (cleanup) """
635
636         # whole packets + fragmented packets sans last fragment
637         fragments = [
638             x for (_, frags_400, _) in self.pkt_infos
639             for x in frags_400[:-1 if len(frags_400) > 1 else None]
640         ]
641
642         # last fragments for fragmented packets
643         fragments2 = [frags_400[-1]
644                       for (_, frags_400, _) in self.pkt_infos
645                       if len(frags_400) > 1]
646
647         dropped_packet_indexes = set(
648             index for (index, frags_400, _) in self.pkt_infos
649             if len(frags_400) > 1)
650
651         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
652                                     expire_walk_interval_ms=50)
653
654         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
655                                     expire_walk_interval_ms=50, is_ip6=1)
656
657         self.pg_enable_capture()
658         self.src_if.add_stream(fragments)
659         self.pg_start()
660
661         self.sleep(.25, "wait before sending rest of fragments")
662
663         self.src_if.add_stream(fragments2)
664         self.pg_start()
665
666         packets = self.dst_if.get_capture(
667             len(self.pkt_infos) - len(dropped_packet_indexes))
668         self.verify_capture(packets, dropped_packet_indexes)
669         pkts = self.src_if.get_capture(
670             expected_count=len(dropped_packet_indexes))
671         for icmp in pkts:
672             self.assertIn(ICMPv6TimeExceeded, icmp)
673             self.assertIn(IPv6ExtHdrFragment, icmp)
674             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
675             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
676
677     def test_disabled(self):
678         """ reassembly disabled """
679
680         dropped_packet_indexes = set(
681             index for (index, frags_400, _) in self.pkt_infos
682             if len(frags_400) > 1)
683
684         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
685                                     expire_walk_interval_ms=10000, is_ip6=1)
686
687         self.pg_enable_capture()
688         self.src_if.add_stream(self.fragments_400)
689         self.pg_start()
690
691         packets = self.dst_if.get_capture(
692             len(self.pkt_infos) - len(dropped_packet_indexes))
693         self.verify_capture(packets, dropped_packet_indexes)
694         self.src_if.assert_nothing_captured()
695
696     def test_missing_upper(self):
697         """ missing upper layer """
698         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
699              IPv6(src=self.src_if.remote_ip6,
700                   dst=self.src_if.local_ip6) /
701              UDP(sport=1234, dport=5678) /
702              Raw())
703         self.extend_packet(p, 1000, self.padding)
704         fragments = fragment_rfc8200(p, 1, 500)
705         bad_fragment = p.__class__(str(fragments[1]))
706         bad_fragment[IPv6ExtHdrFragment].nh = 59
707         bad_fragment[IPv6ExtHdrFragment].offset = 0
708         self.pg_enable_capture()
709         self.src_if.add_stream([bad_fragment])
710         self.pg_start()
711         pkts = self.src_if.get_capture(expected_count=1)
712         icmp = pkts[0]
713         self.assertIn(ICMPv6ParamProblem, icmp)
714         self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
715
716     def test_invalid_frag_size(self):
717         """ fragment size not a multiple of 8 """
718         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
719              IPv6(src=self.src_if.remote_ip6,
720                   dst=self.src_if.local_ip6) /
721              UDP(sport=1234, dport=5678) /
722              Raw())
723         self.extend_packet(p, 1000, self.padding)
724         fragments = fragment_rfc8200(p, 1, 500)
725         bad_fragment = fragments[0]
726         self.extend_packet(bad_fragment, len(bad_fragment) + 5)
727         self.pg_enable_capture()
728         self.src_if.add_stream([bad_fragment])
729         self.pg_start()
730         pkts = self.src_if.get_capture(expected_count=1)
731         icmp = pkts[0]
732         self.assertIn(ICMPv6ParamProblem, icmp)
733         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
734
735     def test_invalid_packet_size(self):
736         """ total packet size > 65535 """
737         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
738              IPv6(src=self.src_if.remote_ip6,
739                   dst=self.src_if.local_ip6) /
740              UDP(sport=1234, dport=5678) /
741              Raw())
742         self.extend_packet(p, 1000, self.padding)
743         fragments = fragment_rfc8200(p, 1, 500)
744         bad_fragment = fragments[1]
745         bad_fragment[IPv6ExtHdrFragment].offset = 65500
746         self.pg_enable_capture()
747         self.src_if.add_stream([bad_fragment])
748         self.pg_start()
749         pkts = self.src_if.get_capture(expected_count=1)
750         icmp = pkts[0]
751         self.assertIn(ICMPv6ParamProblem, icmp)
752         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
753
754
755 class TestIPv4ReassemblyLocalNode(VppTestCase):
756     """ IPv4 Reassembly for packets coming to ip4-local node """
757
758     @classmethod
759     def setUpClass(cls):
760         super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
761
762         cls.create_pg_interfaces([0])
763         cls.src_dst_if = cls.pg0
764
765         # setup all interfaces
766         for i in cls.pg_interfaces:
767             i.admin_up()
768             i.config_ip4()
769             i.resolve_arp()
770
771         cls.padding = " abcdefghijklmn"
772         cls.create_stream()
773         cls.create_fragments()
774
775     def setUp(self):
776         """ Test setup - force timeout on existing reassemblies """
777         super(TestIPv4ReassemblyLocalNode, self).setUp()
778         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
779                                     expire_walk_interval_ms=10)
780         self.sleep(.25)
781         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
782                                     expire_walk_interval_ms=10000)
783
784     def tearDown(self):
785         super(TestIPv4ReassemblyLocalNode, self).tearDown()
786         self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
787
788     @classmethod
789     def create_stream(cls, packet_count=test_packet_count):
790         """Create input packet stream for defined interface.
791
792         :param list packet_sizes: Required packet sizes.
793         """
794         for i in range(0, packet_count):
795             info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
796             payload = cls.info_to_payload(info)
797             p = (Ether(dst=cls.src_dst_if.local_mac,
798                        src=cls.src_dst_if.remote_mac) /
799                  IP(id=info.index, src=cls.src_dst_if.remote_ip4,
800                     dst=cls.src_dst_if.local_ip4) /
801                  ICMP(type='echo-request', id=1234) /
802                  Raw(payload))
803             cls.extend_packet(p, 1518, cls.padding)
804             info.data = p
805
806     @classmethod
807     def create_fragments(cls):
808         infos = cls._packet_infos
809         cls.pkt_infos = []
810         for index, info in six.iteritems(infos):
811             p = info.data
812             # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
813             fragments_300 = fragment_rfc791(p, 300)
814             cls.pkt_infos.append((index, fragments_300))
815         cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
816         cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
817                          (len(infos), len(cls.fragments_300)))
818
819     def verify_capture(self, capture):
820         """Verify captured packet stream.
821
822         :param list capture: Captured packet stream.
823         """
824         info = None
825         seen = set()
826         for packet in capture:
827             try:
828                 self.logger.debug(ppp("Got packet:", packet))
829                 ip = packet[IP]
830                 icmp = packet[ICMP]
831                 payload_info = self.payload_to_info(str(packet[Raw]))
832                 packet_index = payload_info.index
833                 if packet_index in seen:
834                     raise Exception(ppp("Duplicate packet received", packet))
835                 seen.add(packet_index)
836                 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
837                 info = self._packet_infos[packet_index]
838                 self.assertTrue(info is not None)
839                 self.assertEqual(packet_index, info.index)
840                 saved_packet = info.data
841                 self.assertEqual(ip.src, saved_packet[IP].dst)
842                 self.assertEqual(ip.dst, saved_packet[IP].src)
843                 self.assertEqual(icmp.type, 0)  # echo reply
844                 self.assertEqual(icmp.id, saved_packet[ICMP].id)
845                 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
846             except Exception:
847                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
848                 raise
849         for index in self._packet_infos:
850             self.assertTrue(index in seen or index in dropped_packet_indexes,
851                             "Packet with packet_index %d not received" % index)
852
853     def test_reassembly(self):
854         """ basic reassembly """
855
856         self.pg_enable_capture()
857         self.src_dst_if.add_stream(self.fragments_300)
858         self.pg_start()
859
860         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
861         self.verify_capture(packets)
862
863         # run it all again to verify correctness
864         self.pg_enable_capture()
865         self.src_dst_if.add_stream(self.fragments_300)
866         self.pg_start()
867
868         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
869         self.verify_capture(packets)
870
871
872 class TestFIFReassembly(VppTestCase):
873     """ Fragments in fragments reassembly """
874
875     @classmethod
876     def setUpClass(cls):
877         super(TestFIFReassembly, cls).setUpClass()
878
879         cls.create_pg_interfaces([0, 1])
880         cls.src_if = cls.pg0
881         cls.dst_if = cls.pg1
882         for i in cls.pg_interfaces:
883             i.admin_up()
884             i.config_ip4()
885             i.resolve_arp()
886             i.config_ip6()
887             i.resolve_ndp()
888
889         cls.packet_sizes = [64, 512, 1518, 9018]
890         cls.padding = " abcdefghijklmn"
891
892     def setUp(self):
893         """ Test setup - force timeout on existing reassemblies """
894         super(TestFIFReassembly, self).setUp()
895         self.vapi.ip_reassembly_enable_disable(
896             sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
897             enable_ip6=True)
898         self.vapi.ip_reassembly_enable_disable(
899             sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
900             enable_ip6=True)
901         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
902                                     expire_walk_interval_ms=10)
903         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
904                                     expire_walk_interval_ms=10, is_ip6=1)
905         self.sleep(.25)
906         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
907                                     expire_walk_interval_ms=10000)
908         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
909                                     expire_walk_interval_ms=10000, is_ip6=1)
910
911     def tearDown(self):
912         self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
913         self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
914         super(TestFIFReassembly, self).tearDown()
915
916     def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
917         """Verify captured packet stream.
918
919         :param list capture: Captured packet stream.
920         """
921         info = None
922         seen = set()
923         for packet in capture:
924             try:
925                 self.logger.debug(ppp("Got packet:", packet))
926                 ip = packet[ip_class]
927                 udp = packet[UDP]
928                 payload_info = self.payload_to_info(str(packet[Raw]))
929                 packet_index = payload_info.index
930                 self.assertTrue(
931                     packet_index not in dropped_packet_indexes,
932                     ppp("Packet received, but should be dropped:", packet))
933                 if packet_index in seen:
934                     raise Exception(ppp("Duplicate packet received", packet))
935                 seen.add(packet_index)
936                 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
937                 info = self._packet_infos[packet_index]
938                 self.assertTrue(info is not None)
939                 self.assertEqual(packet_index, info.index)
940                 saved_packet = info.data
941                 self.assertEqual(ip.src, saved_packet[ip_class].src)
942                 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
943                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
944             except Exception:
945                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
946                 raise
947         for index in self._packet_infos:
948             self.assertTrue(index in seen or index in dropped_packet_indexes,
949                             "Packet with packet_index %d not received" % index)
950
951     def test_fif4(self):
952         """ Fragments in fragments (4o4) """
953
954         # TODO this should be ideally in setUpClass, but then we hit a bug
955         # with VppIpRoute incorrectly reporting it's present when it's not
956         # so we need to manually remove the vpp config, thus we cannot have
957         # it shared for multiple test cases
958         self.tun_ip4 = "1.1.1.2"
959
960         self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
961         self.gre4.add_vpp_config()
962         self.gre4.admin_up()
963         self.gre4.config_ip4()
964
965         self.vapi.ip_reassembly_enable_disable(
966             sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
967
968         self.route4 = VppIpRoute(self, self.tun_ip4, 32,
969                                  [VppRoutePath(self.src_if.remote_ip4,
970                                                self.src_if.sw_if_index)])
971         self.route4.add_vpp_config()
972
973         self.reset_packet_infos()
974         for i in range(test_packet_count):
975             info = self.create_packet_info(self.src_if, self.dst_if)
976             payload = self.info_to_payload(info)
977             # Ethernet header here is only for size calculation, thus it
978             # doesn't matter how it's initialized. This is to ensure that
979             # reassembled packet is not > 9000 bytes, so that it's not dropped
980             p = (Ether() /
981                  IP(id=i, src=self.src_if.remote_ip4,
982                     dst=self.dst_if.remote_ip4) /
983                  UDP(sport=1234, dport=5678) /
984                  Raw(payload))
985             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
986             self.extend_packet(p, size, self.padding)
987             info.data = p[IP]  # use only IP part, without ethernet header
988
989         fragments = [x for _, p in six.iteritems(self._packet_infos)
990                      for x in fragment_rfc791(p.data, 400)]
991
992         encapped_fragments = \
993             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
994              IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
995                 GRE() /
996                 p
997                 for p in fragments]
998
999         fragmented_encapped_fragments = \
1000             [x for p in encapped_fragments
1001              for x in fragment_rfc791(p, 200)]
1002
1003         self.src_if.add_stream(fragmented_encapped_fragments)
1004
1005         self.pg_enable_capture(self.pg_interfaces)
1006         self.pg_start()
1007
1008         self.src_if.assert_nothing_captured()
1009         packets = self.dst_if.get_capture(len(self._packet_infos))
1010         self.verify_capture(packets, IP)
1011
1012         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1013         # so that it's query_vpp_config() works as it should
1014         self.gre4.remove_vpp_config()
1015         self.logger.debug(self.vapi.ppcli("show interface"))
1016
1017     def test_fif6(self):
1018         """ Fragments in fragments (6o6) """
1019         # TODO this should be ideally in setUpClass, but then we hit a bug
1020         # with VppIpRoute incorrectly reporting it's present when it's not
1021         # so we need to manually remove the vpp config, thus we cannot have
1022         # it shared for multiple test cases
1023         self.tun_ip6 = "1002::1"
1024
1025         self.gre6 = VppGre6Interface(self, self.src_if.local_ip6, self.tun_ip6)
1026         self.gre6.add_vpp_config()
1027         self.gre6.admin_up()
1028         self.gre6.config_ip6()
1029
1030         self.vapi.ip_reassembly_enable_disable(
1031             sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1032
1033         self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1034                                  [VppRoutePath(self.src_if.remote_ip6,
1035                                                self.src_if.sw_if_index,
1036                                                proto=DpoProto.DPO_PROTO_IP6)],
1037                                  is_ip6=1)
1038         self.route6.add_vpp_config()
1039
1040         self.reset_packet_infos()
1041         for i in range(test_packet_count):
1042             info = self.create_packet_info(self.src_if, self.dst_if)
1043             payload = self.info_to_payload(info)
1044             # Ethernet header here is only for size calculation, thus it
1045             # doesn't matter how it's initialized. This is to ensure that
1046             # reassembled packet is not > 9000 bytes, so that it's not dropped
1047             p = (Ether() /
1048                  IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1049                  UDP(sport=1234, dport=5678) /
1050                  Raw(payload))
1051             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1052             self.extend_packet(p, size, self.padding)
1053             info.data = p[IPv6]  # use only IPv6 part, without ethernet header
1054
1055         fragments = [x for _, i in six.iteritems(self._packet_infos)
1056                      for x in fragment_rfc8200(
1057                          i.data, i.index, 400)]
1058
1059         encapped_fragments = \
1060             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1061              IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1062                 GRE() /
1063                 p
1064                 for p in fragments]
1065
1066         fragmented_encapped_fragments = \
1067             [x for p in encapped_fragments for x in (
1068                 fragment_rfc8200(
1069                     p,
1070                     2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1071                     200)
1072                 if IPv6ExtHdrFragment in p else [p]
1073             )
1074             ]
1075
1076         self.src_if.add_stream(fragmented_encapped_fragments)
1077
1078         self.pg_enable_capture(self.pg_interfaces)
1079         self.pg_start()
1080
1081         self.src_if.assert_nothing_captured()
1082         packets = self.dst_if.get_capture(len(self._packet_infos))
1083         self.verify_capture(packets, IPv6)
1084
1085         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1086         # so that it's query_vpp_config() works as it should
1087         self.gre6.remove_vpp_config()
1088
1089
1090 if __name__ == '__main__':
1091     unittest.main(testRunner=VppTestRunner)