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