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