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