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