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