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