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