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