tests: Use errno value rather than a specific int
[vpp.git] / test / test_reassembly.py
1 #!/usr/bin/env python3
2
3 import unittest
4 from random import shuffle, randrange
5
6 from framework import VppTestCase
7 from asfframework import VppTestRunner
8
9 from scapy.packet import Raw
10 from scapy.layers.l2 import Ether, GRE
11 from scapy.layers.inet import IP, UDP, ICMP, icmptypes
12 from scapy.layers.inet6 import (
13     HBHOptUnknown,
14     ICMPv6ParamProblem,
15     ICMPv6TimeExceeded,
16     IPv6,
17     IPv6ExtHdrFragment,
18     IPv6ExtHdrHopByHop,
19     IPv6ExtHdrDestOpt,
20     PadN,
21     ICMPv6EchoRequest,
22     ICMPv6EchoReply,
23 )
24 from util import ppp, fragment_rfc791, fragment_rfc8200
25 from vpp_gre_interface import VppGreInterface
26 from vpp_ip_route import VppIpRoute, VppRoutePath
27 from vpp_papi import VppEnum
28
29 # 35 is enough to have >257 400-byte fragments
30 test_packet_count = 35
31
32
33 class TestIPv4Reassembly(VppTestCase):
34     """IPv4 Reassembly"""
35
36     @classmethod
37     def setUpClass(cls):
38         super().setUpClass()
39
40         cls.create_pg_interfaces([0, 1])
41         cls.src_if = cls.pg0
42         cls.dst_if = cls.pg1
43
44         # setup all interfaces
45         for i in cls.pg_interfaces:
46             i.admin_up()
47             i.config_ip4()
48             i.resolve_arp()
49
50         # packet sizes
51         cls.packet_sizes = [64, 512, 1518, 9018]
52         cls.padding = " abcdefghijklmn"
53         cls.create_stream(cls.packet_sizes)
54         cls.create_fragments()
55
56     @classmethod
57     def tearDownClass(cls):
58         super().tearDownClass()
59
60     def setUp(self):
61         """Test setup - force timeout on existing reassemblies"""
62         super().setUp()
63         self.vapi.ip_reassembly_enable_disable(
64             sw_if_index=self.src_if.sw_if_index, enable_ip4=True
65         )
66         self.vapi.ip_reassembly_set(
67             timeout_ms=0,
68             max_reassemblies=1000,
69             max_reassembly_length=1000,
70             expire_walk_interval_ms=10,
71         )
72         self.virtual_sleep(0.25)
73         self.vapi.ip_reassembly_set(
74             timeout_ms=1000000,
75             max_reassemblies=1000,
76             max_reassembly_length=1000,
77             expire_walk_interval_ms=10000,
78         )
79
80     def tearDown(self):
81         self.vapi.ip_reassembly_enable_disable(
82             sw_if_index=self.src_if.sw_if_index, enable_ip4=False
83         )
84         super().tearDown()
85
86     def show_commands_at_teardown(self):
87         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
88         self.logger.debug(self.vapi.ppcli("show buffers"))
89
90     @classmethod
91     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
92         """Create input packet stream
93
94         :param list packet_sizes: Required packet sizes.
95         """
96         for i in range(0, packet_count):
97             info = cls.create_packet_info(cls.src_if, cls.src_if)
98             payload = cls.info_to_payload(info)
99             p = (
100                 Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac)
101                 / IP(
102                     id=info.index, src=cls.src_if.remote_ip4, dst=cls.dst_if.remote_ip4
103                 )
104                 / UDP(sport=1234, dport=5678)
105                 / Raw(payload)
106             )
107             size = packet_sizes[(i // 2) % len(packet_sizes)]
108             cls.extend_packet(p, size, cls.padding)
109             info.data = p
110
111     @classmethod
112     def create_fragments(cls):
113         infos = cls._packet_infos
114         cls.pkt_infos = []
115         for index, info in infos.items():
116             p = info.data
117             # cls.logger.debug(ppp("Packet:",
118             #                      p.__class__(scapy.compat.raw(p))))
119             fragments_400 = fragment_rfc791(p, 400)
120             fragments_300 = fragment_rfc791(p, 300)
121             fragments_200 = [x for f in fragments_400 for x in fragment_rfc791(f, 200)]
122             cls.pkt_infos.append((index, fragments_400, fragments_300, fragments_200))
123         cls.fragments_400 = [x for (_, frags, _, _) in cls.pkt_infos for x in frags]
124         cls.fragments_300 = [x for (_, _, frags, _) in cls.pkt_infos for x in frags]
125         cls.fragments_200 = [x for (_, _, _, frags) in cls.pkt_infos for x in frags]
126         cls.logger.debug(
127             "Fragmented %s packets into %s 400-byte fragments, "
128             "%s 300-byte fragments and %s 200-byte fragments"
129             % (
130                 len(infos),
131                 len(cls.fragments_400),
132                 len(cls.fragments_300),
133                 len(cls.fragments_200),
134             )
135         )
136
137     def verify_capture(self, capture, dropped_packet_indexes=[]):
138         """Verify captured packet stream.
139
140         :param list capture: Captured packet stream.
141         """
142         info = None
143         seen = set()
144         for packet in capture:
145             try:
146                 self.logger.debug(ppp("Got packet:", packet))
147                 ip = packet[IP]
148                 udp = packet[UDP]
149                 payload_info = self.payload_to_info(packet[Raw])
150                 packet_index = payload_info.index
151                 self.assertTrue(
152                     packet_index not in dropped_packet_indexes,
153                     ppp("Packet received, but should be dropped:", packet),
154                 )
155                 if packet_index in seen:
156                     raise Exception(ppp("Duplicate packet received", packet))
157                 seen.add(packet_index)
158                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
159                 info = self._packet_infos[packet_index]
160                 self.assertTrue(info is not None)
161                 self.assertEqual(packet_index, info.index)
162                 saved_packet = info.data
163                 self.assertEqual(ip.src, saved_packet[IP].src)
164                 self.assertEqual(ip.dst, saved_packet[IP].dst)
165                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
166             except Exception:
167                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
168                 raise
169         for index in self._packet_infos:
170             self.assertTrue(
171                 index in seen or index in dropped_packet_indexes,
172                 "Packet with packet_index %d not received" % index,
173             )
174
175     def test_reassembly(self):
176         """basic reassembly"""
177
178         self.pg_enable_capture()
179         self.src_if.add_stream(self.fragments_200)
180         self.pg_start()
181
182         packets = self.dst_if.get_capture(len(self.pkt_infos))
183         self.verify_capture(packets)
184         self.src_if.assert_nothing_captured()
185
186         # run it all again to verify correctness
187         self.pg_enable_capture()
188         self.src_if.add_stream(self.fragments_200)
189         self.pg_start()
190
191         packets = self.dst_if.get_capture(len(self.pkt_infos))
192         self.verify_capture(packets)
193         self.src_if.assert_nothing_captured()
194
195     def test_verify_clear_trace_mid_reassembly(self):
196         """verify clear trace works mid-reassembly"""
197
198         self.pg_enable_capture()
199         self.src_if.add_stream(self.fragments_200[0:-1])
200         self.pg_start()
201
202         self.logger.debug(self.vapi.cli("show trace"))
203         self.vapi.cli("clear trace")
204
205         self.src_if.add_stream(self.fragments_200[-1])
206         self.pg_start()
207         packets = self.dst_if.get_capture(len(self.pkt_infos))
208         self.verify_capture(packets)
209
210     def test_reversed(self):
211         """reverse order reassembly"""
212
213         fragments = list(self.fragments_200)
214         fragments.reverse()
215
216         self.pg_enable_capture()
217         self.src_if.add_stream(fragments)
218         self.pg_start()
219
220         packets = self.dst_if.get_capture(len(self.packet_infos))
221         self.verify_capture(packets)
222         self.src_if.assert_nothing_captured()
223
224         # run it all again to verify correctness
225         self.pg_enable_capture()
226         self.src_if.add_stream(fragments)
227         self.pg_start()
228
229         packets = self.dst_if.get_capture(len(self.packet_infos))
230         self.verify_capture(packets)
231         self.src_if.assert_nothing_captured()
232
233     def test_long_fragment_chain(self):
234         """long fragment chain"""
235
236         error_cnt_str = "/err/ip4-full-reassembly-feature/reass_fragment_chain_too_long"
237
238         error_cnt = self.statistics.get_err_counter(error_cnt_str)
239
240         self.vapi.ip_reassembly_set(
241             timeout_ms=100,
242             max_reassemblies=1000,
243             max_reassembly_length=3,
244             expire_walk_interval_ms=50,
245         )
246
247         p1 = (
248             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
249             / IP(id=1000, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
250             / UDP(sport=1234, dport=5678)
251             / Raw(b"X" * 1000)
252         )
253         p2 = (
254             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
255             / IP(id=1001, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
256             / UDP(sport=1234, dport=5678)
257             / Raw(b"X" * 1000)
258         )
259         frags = fragment_rfc791(p1, 200) + fragment_rfc791(p2, 500)
260
261         self.pg_enable_capture()
262         self.src_if.add_stream(frags)
263         self.pg_start()
264
265         self.dst_if.get_capture(1)
266         self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
267
268     def test_5737(self):
269         """fragment length + ip header size > 65535"""
270         self.vapi.cli("clear errors")
271         raw = b"""E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n\
272 \x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-message.\
273 Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Offset; Test-case: 5737"""
274         malformed_packet = Ether(
275             dst=self.src_if.local_mac, src=self.src_if.remote_mac
276         ) / IP(raw)
277         p = (
278             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
279             / IP(id=1000, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
280             / UDP(sport=1234, dport=5678)
281             / Raw(b"X" * 1000)
282         )
283         valid_fragments = fragment_rfc791(p, 400)
284
285         counter = "/err/ip4-full-reassembly-feature/reass_malformed_packet"
286         error_counter = self.statistics.get_err_counter(counter)
287         self.pg_enable_capture()
288         self.src_if.add_stream([malformed_packet] + valid_fragments)
289         self.pg_start()
290
291         self.dst_if.get_capture(1)
292         self.logger.debug(self.vapi.ppcli("show error"))
293         self.assertEqual(self.statistics.get_err_counter(counter), error_counter + 1)
294
295     def test_44924(self):
296         """compress tiny fragments"""
297         packets = [
298             (
299                 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
300                 / IP(
301                     id=24339,
302                     flags="MF",
303                     frag=0,
304                     ttl=64,
305                     src=self.src_if.remote_ip4,
306                     dst=self.dst_if.remote_ip4,
307                 )
308                 / ICMP(type="echo-request", code=0, id=0x1FE6, seq=0x2407)
309                 / Raw(load="Test-group: IPv4")
310             ),
311             (
312                 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
313                 / IP(
314                     id=24339,
315                     flags="MF",
316                     frag=3,
317                     ttl=64,
318                     src=self.src_if.remote_ip4,
319                     dst=self.dst_if.remote_ip4,
320                 )
321                 / ICMP(type="echo-request", code=0, id=0x1FE6, seq=0x2407)
322                 / Raw(load=".IPv4.Fragmentation.vali")
323             ),
324             (
325                 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
326                 / IP(
327                     id=24339,
328                     frag=6,
329                     ttl=64,
330                     src=self.src_if.remote_ip4,
331                     dst=self.dst_if.remote_ip4,
332                 )
333                 / ICMP(type="echo-request", code=0, id=0x1FE6, seq=0x2407)
334                 / Raw(load="d; Test-case: 44924")
335             ),
336         ]
337
338         self.pg_enable_capture()
339         self.src_if.add_stream(packets)
340         self.pg_start()
341
342         self.dst_if.get_capture(1)
343
344     def test_frag_1(self):
345         """fragment of size 1"""
346         self.vapi.cli("clear errors")
347         malformed_packets = [
348             (
349                 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
350                 / IP(
351                     id=7,
352                     len=21,
353                     flags="MF",
354                     frag=0,
355                     ttl=64,
356                     src=self.src_if.remote_ip4,
357                     dst=self.dst_if.remote_ip4,
358                 )
359                 / ICMP(type="echo-request")
360             ),
361             (
362                 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
363                 / IP(
364                     id=7,
365                     len=21,
366                     frag=1,
367                     ttl=64,
368                     src=self.src_if.remote_ip4,
369                     dst=self.dst_if.remote_ip4,
370                 )
371                 / Raw(load=b"\x08")
372             ),
373         ]
374
375         p = (
376             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
377             / IP(id=1000, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
378             / UDP(sport=1234, dport=5678)
379             / Raw(b"X" * 1000)
380         )
381         valid_fragments = fragment_rfc791(p, 400)
382
383         self.pg_enable_capture()
384         self.src_if.add_stream(malformed_packets + valid_fragments)
385         self.pg_start()
386
387         self.dst_if.get_capture(1)
388
389         self.assert_packet_counter_equal("ip4-full-reassembly-feature", 1)
390         # TODO remove above, uncomment below once clearing of counters
391         # is supported
392         # self.assert_packet_counter_equal(
393         #     "/err/ip4-full-reassembly-feature/reass_malformed_packet", 1)
394
395     def test_random(self):
396         """random order reassembly"""
397
398         fragments = list(self.fragments_200)
399         shuffle(fragments)
400
401         self.pg_enable_capture()
402         self.src_if.add_stream(fragments)
403         self.pg_start()
404
405         packets = self.dst_if.get_capture(len(self.packet_infos))
406         self.verify_capture(packets)
407         self.src_if.assert_nothing_captured()
408
409         # run it all again to verify correctness
410         self.pg_enable_capture()
411         self.src_if.add_stream(fragments)
412         self.pg_start()
413
414         packets = self.dst_if.get_capture(len(self.packet_infos))
415         self.verify_capture(packets)
416         self.src_if.assert_nothing_captured()
417
418     def test_duplicates(self):
419         """duplicate fragments"""
420
421         fragments = [
422             x
423             for (_, frags, _, _) in self.pkt_infos
424             for x in frags
425             for _ in range(0, min(2, len(frags)))
426         ]
427
428         self.pg_enable_capture()
429         self.src_if.add_stream(fragments)
430         self.pg_start()
431
432         packets = self.dst_if.get_capture(len(self.pkt_infos))
433         self.verify_capture(packets)
434         self.src_if.assert_nothing_captured()
435
436     def test_overlap1(self):
437         """overlapping fragments case #1"""
438
439         fragments = []
440         for _, _, frags_300, frags_200 in self.pkt_infos:
441             if len(frags_300) == 1:
442                 fragments.extend(frags_300)
443             else:
444                 for i, j in zip(frags_200, frags_300):
445                     fragments.extend(i)
446                     fragments.extend(j)
447
448         self.pg_enable_capture()
449         self.src_if.add_stream(fragments)
450         self.pg_start()
451
452         packets = self.dst_if.get_capture(len(self.pkt_infos))
453         self.verify_capture(packets)
454         self.src_if.assert_nothing_captured()
455
456         # run it all to verify correctness
457         self.pg_enable_capture()
458         self.src_if.add_stream(fragments)
459         self.pg_start()
460
461         packets = self.dst_if.get_capture(len(self.pkt_infos))
462         self.verify_capture(packets)
463         self.src_if.assert_nothing_captured()
464
465     def test_overlap2(self):
466         """overlapping fragments case #2"""
467
468         fragments = []
469         for _, _, frags_300, frags_200 in self.pkt_infos:
470             if len(frags_300) == 1:
471                 fragments.extend(frags_300)
472             else:
473                 # care must be taken here so that there are no fragments
474                 # received by vpp after reassembly is finished, otherwise
475                 # new reassemblies will be started and packet generator will
476                 # freak out when it detects unfreed buffers
477                 zipped = zip(frags_300, frags_200)
478                 for i, j in zipped:
479                     fragments.extend(i)
480                     fragments.extend(j)
481                 fragments.pop()
482
483         self.pg_enable_capture()
484         self.src_if.add_stream(fragments)
485         self.pg_start()
486
487         packets = self.dst_if.get_capture(len(self.pkt_infos))
488         self.verify_capture(packets)
489         self.src_if.assert_nothing_captured()
490
491         # run it all to verify correctness
492         self.pg_enable_capture()
493         self.src_if.add_stream(fragments)
494         self.pg_start()
495
496         packets = self.dst_if.get_capture(len(self.pkt_infos))
497         self.verify_capture(packets)
498         self.src_if.assert_nothing_captured()
499
500     def test_timeout_inline(self):
501         """timeout (inline)"""
502
503         dropped_packet_indexes = set(
504             index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
505         )
506
507         self.vapi.ip_reassembly_set(
508             timeout_ms=0,
509             max_reassemblies=1000,
510             max_reassembly_length=3,
511             expire_walk_interval_ms=10000,
512         )
513
514         self.pg_enable_capture()
515         self.src_if.add_stream(self.fragments_400)
516         self.pg_start()
517
518         packets = self.dst_if.get_capture(
519             len(self.pkt_infos) - len(dropped_packet_indexes)
520         )
521         self.verify_capture(packets, dropped_packet_indexes)
522         self.src_if.assert_nothing_captured()
523
524     def test_timeout_cleanup(self):
525         """timeout (cleanup)"""
526
527         # whole packets + fragmented packets sans last fragment
528         fragments = [
529             x
530             for (_, frags_400, _, _) in self.pkt_infos
531             for x in frags_400[: -1 if len(frags_400) > 1 else None]
532         ]
533
534         # last fragments for fragmented packets
535         fragments2 = [
536             frags_400[-1]
537             for (_, frags_400, _, _) in self.pkt_infos
538             if len(frags_400) > 1
539         ]
540
541         dropped_packet_indexes = set(
542             index for (index, frags_400, _, _) in self.pkt_infos if len(frags_400) > 1
543         )
544
545         self.vapi.ip_reassembly_set(
546             timeout_ms=100,
547             max_reassemblies=1000,
548             max_reassembly_length=1000,
549             expire_walk_interval_ms=50,
550         )
551
552         self.pg_enable_capture()
553         self.src_if.add_stream(fragments)
554         self.pg_start()
555
556         self.virtual_sleep(0.25, "wait before sending rest of fragments")
557
558         self.src_if.add_stream(fragments2)
559         self.pg_start()
560
561         packets = self.dst_if.get_capture(
562             len(self.pkt_infos) - len(dropped_packet_indexes)
563         )
564         self.verify_capture(packets, dropped_packet_indexes)
565         self.src_if.assert_nothing_captured()
566
567     def test_disabled(self):
568         """reassembly disabled"""
569
570         dropped_packet_indexes = set(
571             index for (index, frags_400, _, _) in self.pkt_infos if len(frags_400) > 1
572         )
573
574         self.vapi.ip_reassembly_set(
575             timeout_ms=1000,
576             max_reassemblies=0,
577             max_reassembly_length=3,
578             expire_walk_interval_ms=10000,
579         )
580
581         self.pg_enable_capture()
582         self.src_if.add_stream(self.fragments_400)
583         self.pg_start()
584
585         packets = self.dst_if.get_capture(
586             len(self.pkt_infos) - len(dropped_packet_indexes)
587         )
588         self.verify_capture(packets, dropped_packet_indexes)
589         self.src_if.assert_nothing_captured()
590
591     def test_local_enable_disable(self):
592         """local reassembly enabled/disable"""
593         self.vapi.ip_reassembly_enable_disable(
594             sw_if_index=self.src_if.sw_if_index, enable_ip4=False
595         )
596         self.vapi.ip_local_reass_enable_disable(enable_ip4=True)
597         p = (
598             Ether(src=self.src_if.remote_mac, dst=self.src_if.local_mac)
599             / IP(src=self.src_if.remote_ip4, dst=self.src_if.local_ip4)
600             / ICMP(id=1234, type="echo-request")
601             / Raw("x" * 1000)
602         )
603         frags = fragment_rfc791(p, 400)
604         r = self.send_and_expect(self.src_if, frags, self.src_if, n_rx=1)[0]
605         self.assertEqual(1234, r[ICMP].id)
606         self.assertEqual(icmptypes[r[ICMP].type], "echo-reply")
607         self.vapi.ip_local_reass_enable_disable()
608
609         self.send_and_assert_no_replies(self.src_if, frags)
610         self.vapi.ip_local_reass_enable_disable(enable_ip4=True)
611
612
613 class TestIPv4SVReassembly(VppTestCase):
614     """IPv4 Shallow Virtual Reassembly"""
615
616     @classmethod
617     def setUpClass(cls):
618         super().setUpClass()
619
620         cls.create_pg_interfaces([0, 1])
621         cls.src_if = cls.pg0
622         cls.dst_if = cls.pg1
623
624         # setup all interfaces
625         for i in cls.pg_interfaces:
626             i.admin_up()
627             i.config_ip4()
628             i.resolve_arp()
629
630     def setUp(self):
631         """Test setup - force timeout on existing reassemblies"""
632         super().setUp()
633         self.vapi.ip_reassembly_enable_disable(
634             sw_if_index=self.src_if.sw_if_index,
635             enable_ip4=True,
636             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
637         )
638         self.vapi.ip_reassembly_set(
639             timeout_ms=0,
640             max_reassemblies=1000,
641             max_reassembly_length=1000,
642             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
643             expire_walk_interval_ms=10,
644         )
645         self.virtual_sleep(0.25)
646         self.vapi.ip_reassembly_set(
647             timeout_ms=1000000,
648             max_reassemblies=1000,
649             max_reassembly_length=1000,
650             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
651             expire_walk_interval_ms=10000,
652         )
653
654     def tearDown(self):
655         super().tearDown()
656         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
657         self.logger.debug(self.vapi.ppcli("show buffers"))
658
659     def test_basic(self):
660         """basic reassembly"""
661         payload_len = 1000
662         payload = ""
663         counter = 0
664         while len(payload) < payload_len:
665             payload += "%u " % counter
666             counter += 1
667
668         p = (
669             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
670             / IP(id=1, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
671             / UDP(sport=1234, dport=5678)
672             / Raw(payload)
673         )
674         fragments = fragment_rfc791(p, payload_len / 4)
675
676         # send fragment #2 - should be cached inside reassembly
677         self.pg_enable_capture()
678         self.src_if.add_stream(fragments[1])
679         self.pg_start()
680         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
681         self.logger.debug(self.vapi.ppcli("show buffers"))
682         self.logger.debug(self.vapi.ppcli("show trace"))
683         self.dst_if.assert_nothing_captured()
684
685         # send fragment #1 - reassembly is finished now and both fragments
686         # forwarded
687         self.pg_enable_capture()
688         self.src_if.add_stream(fragments[0])
689         self.pg_start()
690         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
691         self.logger.debug(self.vapi.ppcli("show buffers"))
692         self.logger.debug(self.vapi.ppcli("show trace"))
693         c = self.dst_if.get_capture(2)
694         for sent, recvd in zip([fragments[1], fragments[0]], c):
695             self.assertEqual(sent[IP].src, recvd[IP].src)
696             self.assertEqual(sent[IP].dst, recvd[IP].dst)
697             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
698
699         # send rest of fragments - should be immediately forwarded
700         self.pg_enable_capture()
701         self.src_if.add_stream(fragments[2:])
702         self.pg_start()
703         c = self.dst_if.get_capture(len(fragments[2:]))
704         for sent, recvd in zip(fragments[2:], c):
705             self.assertEqual(sent[IP].src, recvd[IP].src)
706             self.assertEqual(sent[IP].dst, recvd[IP].dst)
707             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
708
709     def test_verify_clear_trace_mid_reassembly(self):
710         """verify clear trace works mid-reassembly"""
711         payload_len = 1000
712         payload = ""
713         counter = 0
714         while len(payload) < payload_len:
715             payload += "%u " % counter
716             counter += 1
717
718         p = (
719             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
720             / IP(id=1, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
721             / UDP(sport=1234, dport=5678)
722             / Raw(payload)
723         )
724         fragments = fragment_rfc791(p, payload_len / 4)
725
726         self.pg_enable_capture()
727         self.src_if.add_stream(fragments[1])
728         self.pg_start()
729
730         self.logger.debug(self.vapi.cli("show trace"))
731         self.vapi.cli("clear trace")
732
733         self.pg_enable_capture()
734         self.src_if.add_stream(fragments[0])
735         self.pg_start()
736         self.dst_if.get_capture(2)
737
738         self.logger.debug(self.vapi.cli("show trace"))
739         self.vapi.cli("clear trace")
740
741         self.pg_enable_capture()
742         self.src_if.add_stream(fragments[2:])
743         self.pg_start()
744         self.dst_if.get_capture(len(fragments[2:]))
745
746     def test_timeout(self):
747         """reassembly timeout"""
748         payload_len = 1000
749         payload = ""
750         counter = 0
751         while len(payload) < payload_len:
752             payload += "%u " % counter
753             counter += 1
754
755         p = (
756             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
757             / IP(id=1, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
758             / UDP(sport=1234, dport=5678)
759             / Raw(payload)
760         )
761         fragments = fragment_rfc791(p, payload_len / 4)
762
763         self.vapi.ip_reassembly_set(
764             timeout_ms=100,
765             max_reassemblies=1000,
766             max_reassembly_length=1000,
767             expire_walk_interval_ms=50,
768             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
769         )
770
771         # send fragments #2 and #1 - should be forwarded
772         self.pg_enable_capture()
773         self.src_if.add_stream(fragments[0:2])
774         self.pg_start()
775         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
776         self.logger.debug(self.vapi.ppcli("show buffers"))
777         self.logger.debug(self.vapi.ppcli("show trace"))
778         c = self.dst_if.get_capture(2)
779         for sent, recvd in zip([fragments[1], fragments[0]], c):
780             self.assertEqual(sent[IP].src, recvd[IP].src)
781             self.assertEqual(sent[IP].dst, recvd[IP].dst)
782             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
783
784         # wait for cleanup
785         self.virtual_sleep(0.25, "wait before sending rest of fragments")
786
787         # send rest of fragments - shouldn't be forwarded
788         self.pg_enable_capture()
789         self.src_if.add_stream(fragments[2:])
790         self.pg_start()
791         self.dst_if.assert_nothing_captured()
792
793     def test_lru(self):
794         """reassembly reuses LRU element"""
795
796         self.vapi.ip_reassembly_set(
797             timeout_ms=1000000,
798             max_reassemblies=1,
799             max_reassembly_length=1000,
800             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
801             expire_walk_interval_ms=10000,
802         )
803
804         payload_len = 1000
805         payload = ""
806         counter = 0
807         while len(payload) < payload_len:
808             payload += "%u " % counter
809             counter += 1
810
811         packet_count = 10
812
813         fragments = [
814             f
815             for i in range(packet_count)
816             for p in (
817                 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
818                 / IP(id=i, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
819                 / UDP(sport=1234, dport=5678)
820                 / Raw(payload)
821             )
822             for f in fragment_rfc791(p, payload_len / 4)
823         ]
824
825         self.pg_enable_capture()
826         self.src_if.add_stream(fragments)
827         self.pg_start()
828         c = self.dst_if.get_capture(len(fragments))
829         for sent, recvd in zip(fragments, c):
830             self.assertEqual(sent[IP].src, recvd[IP].src)
831             self.assertEqual(sent[IP].dst, recvd[IP].dst)
832             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
833
834     def send_mixed_and_verify_capture(self, traffic):
835         stream = []
836         for t in traffic:
837             for c in range(t["count"]):
838                 stream.append(
839                     (
840                         Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
841                         / IP(
842                             id=self.counter,
843                             flags=t["flags"],
844                             src=self.src_if.remote_ip4,
845                             dst=self.dst_if.remote_ip4,
846                         )
847                         / UDP(sport=1234, dport=5678)
848                         / Raw("abcdef")
849                     )
850                 )
851                 self.counter = self.counter + 1
852
853         self.pg_enable_capture()
854         self.src_if.add_stream(stream)
855         self.pg_start()
856         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
857         self.logger.debug(self.vapi.ppcli("show buffers"))
858         self.logger.debug(self.vapi.ppcli("show trace"))
859         self.dst_if.get_capture(len(stream))
860
861     def test_mixed(self):
862         """mixed traffic correctly passes through SVR"""
863         self.counter = 1
864
865         self.send_mixed_and_verify_capture([{"count": 1, "flags": ""}])
866         self.send_mixed_and_verify_capture([{"count": 2, "flags": ""}])
867         self.send_mixed_and_verify_capture([{"count": 3, "flags": ""}])
868         self.send_mixed_and_verify_capture([{"count": 8, "flags": ""}])
869         self.send_mixed_and_verify_capture([{"count": 257, "flags": ""}])
870
871         self.send_mixed_and_verify_capture([{"count": 1, "flags": "MF"}])
872         self.send_mixed_and_verify_capture([{"count": 2, "flags": "MF"}])
873         self.send_mixed_and_verify_capture([{"count": 3, "flags": "MF"}])
874         self.send_mixed_and_verify_capture([{"count": 8, "flags": "MF"}])
875         self.send_mixed_and_verify_capture([{"count": 257, "flags": "MF"}])
876
877         self.send_mixed_and_verify_capture(
878             [{"count": 1, "flags": ""}, {"count": 1, "flags": "MF"}]
879         )
880         self.send_mixed_and_verify_capture(
881             [{"count": 2, "flags": ""}, {"count": 2, "flags": "MF"}]
882         )
883         self.send_mixed_and_verify_capture(
884             [{"count": 3, "flags": ""}, {"count": 3, "flags": "MF"}]
885         )
886         self.send_mixed_and_verify_capture(
887             [{"count": 8, "flags": ""}, {"count": 8, "flags": "MF"}]
888         )
889         self.send_mixed_and_verify_capture(
890             [{"count": 129, "flags": ""}, {"count": 129, "flags": "MF"}]
891         )
892
893         self.send_mixed_and_verify_capture(
894             [
895                 {"count": 1, "flags": ""},
896                 {"count": 1, "flags": "MF"},
897                 {"count": 1, "flags": ""},
898                 {"count": 1, "flags": "MF"},
899             ]
900         )
901         self.send_mixed_and_verify_capture(
902             [
903                 {"count": 2, "flags": ""},
904                 {"count": 2, "flags": "MF"},
905                 {"count": 2, "flags": ""},
906                 {"count": 2, "flags": "MF"},
907             ]
908         )
909         self.send_mixed_and_verify_capture(
910             [
911                 {"count": 3, "flags": ""},
912                 {"count": 3, "flags": "MF"},
913                 {"count": 3, "flags": ""},
914                 {"count": 3, "flags": "MF"},
915             ]
916         )
917         self.send_mixed_and_verify_capture(
918             [
919                 {"count": 8, "flags": ""},
920                 {"count": 8, "flags": "MF"},
921                 {"count": 8, "flags": ""},
922                 {"count": 8, "flags": "MF"},
923             ]
924         )
925         self.send_mixed_and_verify_capture(
926             [
927                 {"count": 65, "flags": ""},
928                 {"count": 65, "flags": "MF"},
929                 {"count": 65, "flags": ""},
930                 {"count": 65, "flags": "MF"},
931             ]
932         )
933
934
935 class TestIPv4MWReassembly(VppTestCase):
936     """IPv4 Reassembly (multiple workers)"""
937
938     vpp_worker_count = 3
939
940     @classmethod
941     def setUpClass(cls):
942         super().setUpClass()
943
944         cls.create_pg_interfaces(range(cls.vpp_worker_count + 1))
945         cls.src_if = cls.pg0
946         cls.send_ifs = cls.pg_interfaces[:-1]
947         cls.dst_if = cls.pg_interfaces[-1]
948
949         # setup all interfaces
950         for i in cls.pg_interfaces:
951             i.admin_up()
952             i.config_ip4()
953             i.resolve_arp()
954
955         # packets sizes reduced here because we are generating packets without
956         # Ethernet headers, which are added later (diff fragments go via
957         # different interfaces)
958         cls.packet_sizes = [
959             64 - len(Ether()),
960             512 - len(Ether()),
961             1518 - len(Ether()),
962             9018 - len(Ether()),
963         ]
964         cls.padding = " abcdefghijklmn"
965         cls.create_stream(cls.packet_sizes)
966         cls.create_fragments()
967
968     @classmethod
969     def tearDownClass(cls):
970         super().tearDownClass()
971
972     def setUp(self):
973         """Test setup - force timeout on existing reassemblies"""
974         super().setUp()
975         for intf in self.send_ifs:
976             self.vapi.ip_reassembly_enable_disable(
977                 sw_if_index=intf.sw_if_index, enable_ip4=True
978             )
979         self.vapi.ip_reassembly_set(
980             timeout_ms=0,
981             max_reassemblies=1000,
982             max_reassembly_length=1000,
983             expire_walk_interval_ms=10,
984         )
985         self.virtual_sleep(0.25)
986         self.vapi.ip_reassembly_set(
987             timeout_ms=1000000,
988             max_reassemblies=1000,
989             max_reassembly_length=1000,
990             expire_walk_interval_ms=10000,
991         )
992
993     def tearDown(self):
994         for intf in self.send_ifs:
995             self.vapi.ip_reassembly_enable_disable(
996                 sw_if_index=intf.sw_if_index, enable_ip4=False
997             )
998         super().tearDown()
999
1000     def show_commands_at_teardown(self):
1001         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1002         self.logger.debug(self.vapi.ppcli("show buffers"))
1003
1004     @classmethod
1005     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1006         """Create input packet stream
1007
1008         :param list packet_sizes: Required packet sizes.
1009         """
1010         for i in range(0, packet_count):
1011             info = cls.create_packet_info(cls.src_if, cls.src_if)
1012             payload = cls.info_to_payload(info)
1013             p = (
1014                 IP(id=info.index, src=cls.src_if.remote_ip4, dst=cls.dst_if.remote_ip4)
1015                 / UDP(sport=1234, dport=5678)
1016                 / Raw(payload)
1017             )
1018             size = packet_sizes[(i // 2) % len(packet_sizes)]
1019             cls.extend_packet(p, size, cls.padding)
1020             info.data = p
1021
1022     @classmethod
1023     def create_fragments(cls):
1024         infos = cls._packet_infos
1025         cls.pkt_infos = []
1026         for index, info in infos.items():
1027             p = info.data
1028             # cls.logger.debug(ppp("Packet:",
1029             #                      p.__class__(scapy.compat.raw(p))))
1030             fragments_400 = fragment_rfc791(p, 400)
1031             cls.pkt_infos.append((index, fragments_400))
1032         cls.fragments_400 = [x for (_, frags) in cls.pkt_infos for x in frags]
1033         cls.logger.debug(
1034             "Fragmented %s packets into %s 400-byte fragments, "
1035             % (len(infos), len(cls.fragments_400))
1036         )
1037
1038     def verify_capture(self, capture, dropped_packet_indexes=[]):
1039         """Verify captured packet stream.
1040
1041         :param list capture: Captured packet stream.
1042         """
1043         info = None
1044         seen = set()
1045         for packet in capture:
1046             try:
1047                 self.logger.debug(ppp("Got packet:", packet))
1048                 ip = packet[IP]
1049                 udp = packet[UDP]
1050                 payload_info = self.payload_to_info(packet[Raw])
1051                 packet_index = payload_info.index
1052                 self.assertTrue(
1053                     packet_index not in dropped_packet_indexes,
1054                     ppp("Packet received, but should be dropped:", packet),
1055                 )
1056                 if packet_index in seen:
1057                     raise Exception(ppp("Duplicate packet received", packet))
1058                 seen.add(packet_index)
1059                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1060                 info = self._packet_infos[packet_index]
1061                 self.assertTrue(info is not None)
1062                 self.assertEqual(packet_index, info.index)
1063                 saved_packet = info.data
1064                 self.assertEqual(ip.src, saved_packet[IP].src)
1065                 self.assertEqual(ip.dst, saved_packet[IP].dst)
1066                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1067             except Exception:
1068                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1069                 raise
1070         for index in self._packet_infos:
1071             self.assertTrue(
1072                 index in seen or index in dropped_packet_indexes,
1073                 "Packet with packet_index %d not received" % index,
1074             )
1075
1076     def send_packets(self, packets):
1077         for counter in range(self.vpp_worker_count):
1078             if 0 == len(packets[counter]):
1079                 continue
1080             send_if = self.send_ifs[counter]
1081             send_if.add_stream(
1082                 (
1083                     Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1084                     for x in packets[counter]
1085                 ),
1086                 worker=counter,
1087             )
1088         self.pg_start()
1089
1090     def test_worker_conflict(self):
1091         """1st and FO=0 fragments on different workers"""
1092
1093         # in first wave we send fragments which don't start at offset 0
1094         # then we send fragments with offset 0 on a different thread
1095         # then the rest of packets on a random thread
1096         first_packets = [[] for n in range(self.vpp_worker_count)]
1097         second_packets = [[] for n in range(self.vpp_worker_count)]
1098         rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1099         for _, p in self.pkt_infos:
1100             wi = randrange(self.vpp_worker_count)
1101             second_packets[wi].append(p[0])
1102             if len(p) <= 1:
1103                 continue
1104             wi2 = wi
1105             while wi2 == wi:
1106                 wi2 = randrange(self.vpp_worker_count)
1107             first_packets[wi2].append(p[1])
1108             wi3 = randrange(self.vpp_worker_count)
1109             rest_of_packets[wi3].extend(p[2:])
1110
1111         self.pg_enable_capture()
1112         self.send_packets(first_packets)
1113         self.send_packets(second_packets)
1114         self.send_packets(rest_of_packets)
1115
1116         packets = self.dst_if.get_capture(len(self.pkt_infos))
1117         self.verify_capture(packets)
1118         for send_if in self.send_ifs:
1119             send_if.assert_nothing_captured()
1120
1121         self.logger.debug(self.vapi.ppcli("show trace"))
1122         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1123         self.logger.debug(self.vapi.ppcli("show buffers"))
1124         self.vapi.cli("clear trace")
1125
1126         self.pg_enable_capture()
1127         self.send_packets(first_packets)
1128         self.send_packets(second_packets)
1129         self.send_packets(rest_of_packets)
1130
1131         packets = self.dst_if.get_capture(len(self.pkt_infos))
1132         self.verify_capture(packets)
1133         for send_if in self.send_ifs:
1134             send_if.assert_nothing_captured()
1135
1136
1137 class TestIPv6Reassembly(VppTestCase):
1138     """IPv6 Reassembly"""
1139
1140     @classmethod
1141     def setUpClass(cls):
1142         super().setUpClass()
1143
1144         cls.create_pg_interfaces([0, 1])
1145         cls.src_if = cls.pg0
1146         cls.dst_if = cls.pg1
1147
1148         # setup all interfaces
1149         for i in cls.pg_interfaces:
1150             i.admin_up()
1151             i.config_ip6()
1152             i.resolve_ndp()
1153
1154         # packet sizes
1155         cls.packet_sizes = [64, 512, 1518, 9018]
1156         cls.padding = " abcdefghijklmn"
1157         cls.create_stream(cls.packet_sizes)
1158         cls.create_fragments()
1159
1160     @classmethod
1161     def tearDownClass(cls):
1162         super().tearDownClass()
1163
1164     def setUp(self):
1165         """Test setup - force timeout on existing reassemblies"""
1166         super().setUp()
1167         self.vapi.ip_reassembly_enable_disable(
1168             sw_if_index=self.src_if.sw_if_index, enable_ip6=True
1169         )
1170         self.vapi.ip_reassembly_set(
1171             timeout_ms=0,
1172             max_reassemblies=1000,
1173             max_reassembly_length=1000,
1174             expire_walk_interval_ms=10,
1175             is_ip6=1,
1176         )
1177         self.virtual_sleep(0.25)
1178         self.vapi.ip_reassembly_set(
1179             timeout_ms=1000000,
1180             max_reassemblies=1000,
1181             max_reassembly_length=1000,
1182             expire_walk_interval_ms=10000,
1183             is_ip6=1,
1184         )
1185         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1186         self.logger.debug(self.vapi.ppcli("show buffers"))
1187
1188     def tearDown(self):
1189         self.vapi.ip_reassembly_enable_disable(
1190             sw_if_index=self.src_if.sw_if_index, enable_ip6=False
1191         )
1192         super().tearDown()
1193
1194     def show_commands_at_teardown(self):
1195         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1196         self.logger.debug(self.vapi.ppcli("show buffers"))
1197
1198     @classmethod
1199     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1200         """Create input packet stream for defined interface.
1201
1202         :param list packet_sizes: Required packet sizes.
1203         """
1204         for i in range(0, packet_count):
1205             info = cls.create_packet_info(cls.src_if, cls.src_if)
1206             payload = cls.info_to_payload(info)
1207             p = (
1208                 Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac)
1209                 / IPv6(src=cls.src_if.remote_ip6, dst=cls.dst_if.remote_ip6)
1210                 / UDP(sport=1234, dport=5678)
1211                 / Raw(payload)
1212             )
1213             size = packet_sizes[(i // 2) % len(packet_sizes)]
1214             cls.extend_packet(p, size, cls.padding)
1215             info.data = p
1216
1217     @classmethod
1218     def create_fragments(cls):
1219         infos = cls._packet_infos
1220         cls.pkt_infos = []
1221         for index, info in infos.items():
1222             p = info.data
1223             # cls.logger.debug(ppp("Packet:",
1224             #                      p.__class__(scapy.compat.raw(p))))
1225             fragments_400 = fragment_rfc8200(p, info.index, 400)
1226             fragments_300 = fragment_rfc8200(p, info.index, 300)
1227             cls.pkt_infos.append((index, fragments_400, fragments_300))
1228         cls.fragments_400 = [x for _, frags, _ in cls.pkt_infos for x in frags]
1229         cls.fragments_300 = [x for _, _, frags in cls.pkt_infos for x in frags]
1230         cls.logger.debug(
1231             "Fragmented %s packets into %s 400-byte fragments, "
1232             "and %s 300-byte fragments"
1233             % (len(infos), len(cls.fragments_400), len(cls.fragments_300))
1234         )
1235
1236     def verify_capture(self, capture, dropped_packet_indexes=[]):
1237         """Verify captured packet strea .
1238
1239         :param list capture: Captured packet stream.
1240         """
1241         info = None
1242         seen = set()
1243         for packet in capture:
1244             try:
1245                 self.logger.debug(ppp("Got packet:", packet))
1246                 ip = packet[IPv6]
1247                 udp = packet[UDP]
1248                 payload_info = self.payload_to_info(packet[Raw])
1249                 packet_index = payload_info.index
1250                 self.assertTrue(
1251                     packet_index not in dropped_packet_indexes,
1252                     ppp("Packet received, but should be dropped:", packet),
1253                 )
1254                 if packet_index in seen:
1255                     raise Exception(ppp("Duplicate packet received", packet))
1256                 seen.add(packet_index)
1257                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1258                 info = self._packet_infos[packet_index]
1259                 self.assertTrue(info is not None)
1260                 self.assertEqual(packet_index, info.index)
1261                 saved_packet = info.data
1262                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1263                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1264                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1265             except Exception:
1266                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1267                 raise
1268         for index in self._packet_infos:
1269             self.assertTrue(
1270                 index in seen or index in dropped_packet_indexes,
1271                 "Packet with packet_index %d not received" % index,
1272             )
1273
1274     def test_reassembly(self):
1275         """basic reassembly"""
1276
1277         self.pg_enable_capture()
1278         self.src_if.add_stream(self.fragments_400)
1279         self.pg_start()
1280
1281         packets = self.dst_if.get_capture(len(self.pkt_infos))
1282         self.verify_capture(packets)
1283         self.src_if.assert_nothing_captured()
1284
1285         # run it all again to verify correctness
1286         self.pg_enable_capture()
1287         self.src_if.add_stream(self.fragments_400)
1288         self.pg_start()
1289
1290         packets = self.dst_if.get_capture(len(self.pkt_infos))
1291         self.verify_capture(packets)
1292         self.src_if.assert_nothing_captured()
1293
1294     def test_buffer_boundary(self):
1295         """fragment header crossing buffer boundary"""
1296
1297         p = (
1298             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1299             / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1300             / IPv6ExtHdrHopByHop(options=[HBHOptUnknown(otype=0xFF, optlen=0)] * 1000)
1301             / IPv6ExtHdrFragment(m=1)
1302             / UDP(sport=1234, dport=5678)
1303             / Raw()
1304         )
1305         self.pg_enable_capture()
1306         self.src_if.add_stream([p])
1307         self.pg_start()
1308         self.src_if.assert_nothing_captured()
1309         self.dst_if.assert_nothing_captured()
1310
1311     def test_verify_clear_trace_mid_reassembly(self):
1312         """verify clear trace works mid-reassembly"""
1313
1314         self.pg_enable_capture()
1315         self.src_if.add_stream(self.fragments_400[0:-1])
1316         self.pg_start()
1317
1318         self.logger.debug(self.vapi.cli("show trace"))
1319         self.vapi.cli("clear trace")
1320
1321         self.src_if.add_stream(self.fragments_400[-1])
1322         self.pg_start()
1323         packets = self.dst_if.get_capture(len(self.pkt_infos))
1324         self.verify_capture(packets)
1325
1326     def test_reversed(self):
1327         """reverse order reassembly"""
1328
1329         fragments = list(self.fragments_400)
1330         fragments.reverse()
1331
1332         self.pg_enable_capture()
1333         self.src_if.add_stream(fragments)
1334         self.pg_start()
1335
1336         packets = self.dst_if.get_capture(len(self.pkt_infos))
1337         self.verify_capture(packets)
1338         self.src_if.assert_nothing_captured()
1339
1340         # run it all again to verify correctness
1341         self.pg_enable_capture()
1342         self.src_if.add_stream(fragments)
1343         self.pg_start()
1344
1345         packets = self.dst_if.get_capture(len(self.pkt_infos))
1346         self.verify_capture(packets)
1347         self.src_if.assert_nothing_captured()
1348
1349     def test_random(self):
1350         """random order reassembly"""
1351
1352         fragments = list(self.fragments_400)
1353         shuffle(fragments)
1354
1355         self.pg_enable_capture()
1356         self.src_if.add_stream(fragments)
1357         self.pg_start()
1358
1359         packets = self.dst_if.get_capture(len(self.pkt_infos))
1360         self.verify_capture(packets)
1361         self.src_if.assert_nothing_captured()
1362
1363         # run it all again to verify correctness
1364         self.pg_enable_capture()
1365         self.src_if.add_stream(fragments)
1366         self.pg_start()
1367
1368         packets = self.dst_if.get_capture(len(self.pkt_infos))
1369         self.verify_capture(packets)
1370         self.src_if.assert_nothing_captured()
1371
1372     def test_duplicates(self):
1373         """duplicate fragments"""
1374
1375         fragments = [
1376             x
1377             for (_, frags, _) in self.pkt_infos
1378             for x in frags
1379             for _ in range(0, min(2, len(frags)))
1380         ]
1381
1382         self.pg_enable_capture()
1383         self.src_if.add_stream(fragments)
1384         self.pg_start()
1385
1386         packets = self.dst_if.get_capture(len(self.pkt_infos))
1387         self.verify_capture(packets)
1388         self.src_if.assert_nothing_captured()
1389
1390     def test_long_fragment_chain(self):
1391         """long fragment chain"""
1392
1393         error_cnt_str = "/err/ip6-full-reassembly-feature/reass_fragment_chain_too_long"
1394
1395         error_cnt = self.statistics.get_err_counter(error_cnt_str)
1396
1397         self.vapi.ip_reassembly_set(
1398             timeout_ms=100,
1399             max_reassemblies=1000,
1400             max_reassembly_length=3,
1401             expire_walk_interval_ms=50,
1402             is_ip6=1,
1403         )
1404
1405         p = (
1406             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1407             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
1408             / UDP(sport=1234, dport=5678)
1409             / Raw(b"X" * 1000)
1410         )
1411         frags = fragment_rfc8200(p, 1, 300) + fragment_rfc8200(p, 2, 500)
1412
1413         self.pg_enable_capture()
1414         self.src_if.add_stream(frags)
1415         self.pg_start()
1416
1417         self.dst_if.get_capture(1)
1418         self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
1419
1420     def test_overlap1(self):
1421         """overlapping fragments case #1"""
1422
1423         fragments = []
1424         for _, frags_400, frags_300 in self.pkt_infos:
1425             if len(frags_300) == 1:
1426                 fragments.extend(frags_400)
1427             else:
1428                 for i, j in zip(frags_300, frags_400):
1429                     fragments.extend(i)
1430                     fragments.extend(j)
1431
1432         dropped_packet_indexes = set(
1433             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1434         )
1435
1436         self.pg_enable_capture()
1437         self.src_if.add_stream(fragments)
1438         self.pg_start()
1439
1440         packets = self.dst_if.get_capture(
1441             len(self.pkt_infos) - len(dropped_packet_indexes)
1442         )
1443         self.verify_capture(packets, dropped_packet_indexes)
1444         self.src_if.assert_nothing_captured()
1445
1446     def test_overlap2(self):
1447         """overlapping fragments case #2"""
1448
1449         fragments = []
1450         for _, frags_400, frags_300 in self.pkt_infos:
1451             if len(frags_400) == 1:
1452                 fragments.extend(frags_400)
1453             else:
1454                 # care must be taken here so that there are no fragments
1455                 # received by vpp after reassembly is finished, otherwise
1456                 # new reassemblies will be started and packet generator will
1457                 # freak out when it detects unfreed buffers
1458                 zipped = zip(frags_400, frags_300)
1459                 for i, j in zipped:
1460                     fragments.extend(i)
1461                     fragments.extend(j)
1462                 fragments.pop()
1463
1464         dropped_packet_indexes = set(
1465             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1466         )
1467
1468         self.pg_enable_capture()
1469         self.src_if.add_stream(fragments)
1470         self.pg_start()
1471
1472         packets = self.dst_if.get_capture(
1473             len(self.pkt_infos) - len(dropped_packet_indexes)
1474         )
1475         self.verify_capture(packets, dropped_packet_indexes)
1476         self.src_if.assert_nothing_captured()
1477
1478     def test_timeout_inline(self):
1479         """timeout (inline)"""
1480
1481         dropped_packet_indexes = set(
1482             index for (index, frags, _) in self.pkt_infos if len(frags) > 1
1483         )
1484
1485         self.vapi.ip_reassembly_set(
1486             timeout_ms=0,
1487             max_reassemblies=1000,
1488             max_reassembly_length=3,
1489             expire_walk_interval_ms=10000,
1490             is_ip6=1,
1491         )
1492
1493         self.pg_enable_capture()
1494         self.src_if.add_stream(self.fragments_400)
1495         self.pg_start()
1496
1497         packets = self.dst_if.get_capture(
1498             len(self.pkt_infos) - len(dropped_packet_indexes)
1499         )
1500         self.verify_capture(packets, dropped_packet_indexes)
1501         pkts = self.src_if._get_capture(1)
1502         for icmp in pkts:
1503             self.assertIn(ICMPv6TimeExceeded, icmp)
1504             self.assertIn(IPv6ExtHdrFragment, icmp)
1505             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1506             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1507
1508     def test_timeout_cleanup(self):
1509         """timeout (cleanup)"""
1510
1511         # whole packets + fragmented packets sans last fragment
1512         fragments = [
1513             x
1514             for (_, frags_400, _) in self.pkt_infos
1515             for x in frags_400[: -1 if len(frags_400) > 1 else None]
1516         ]
1517
1518         # last fragments for fragmented packets
1519         fragments2 = [
1520             frags_400[-1] for (_, frags_400, _) in self.pkt_infos if len(frags_400) > 1
1521         ]
1522
1523         dropped_packet_indexes = set(
1524             index for (index, frags_400, _) in self.pkt_infos if len(frags_400) > 1
1525         )
1526
1527         self.vapi.ip_reassembly_set(
1528             timeout_ms=100,
1529             max_reassemblies=1000,
1530             max_reassembly_length=1000,
1531             expire_walk_interval_ms=50,
1532         )
1533
1534         self.vapi.ip_reassembly_set(
1535             timeout_ms=100,
1536             max_reassemblies=1000,
1537             max_reassembly_length=1000,
1538             expire_walk_interval_ms=50,
1539             is_ip6=1,
1540         )
1541
1542         self.pg_enable_capture()
1543         self.src_if.add_stream(fragments)
1544         self.pg_start()
1545
1546         self.virtual_sleep(0.25, "wait before sending rest of fragments")
1547
1548         self.src_if.add_stream(fragments2)
1549         self.pg_start()
1550
1551         packets = self.dst_if.get_capture(
1552             len(self.pkt_infos) - len(dropped_packet_indexes)
1553         )
1554         self.verify_capture(packets, dropped_packet_indexes)
1555         pkts = self.src_if._get_capture(1)
1556         for icmp in pkts:
1557             self.assertIn(ICMPv6TimeExceeded, icmp)
1558             self.assertIn(IPv6ExtHdrFragment, icmp)
1559             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1560             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1561
1562     def test_disabled(self):
1563         """reassembly disabled"""
1564
1565         dropped_packet_indexes = set(
1566             index for (index, frags_400, _) in self.pkt_infos if len(frags_400) > 1
1567         )
1568
1569         self.vapi.ip_reassembly_set(
1570             timeout_ms=1000,
1571             max_reassemblies=0,
1572             max_reassembly_length=3,
1573             expire_walk_interval_ms=10000,
1574             is_ip6=1,
1575         )
1576
1577         self.pg_enable_capture()
1578         self.src_if.add_stream(self.fragments_400)
1579         self.pg_start()
1580
1581         packets = self.dst_if.get_capture(
1582             len(self.pkt_infos) - len(dropped_packet_indexes)
1583         )
1584         self.verify_capture(packets, dropped_packet_indexes)
1585         self.src_if.assert_nothing_captured()
1586
1587     def test_missing_upper(self):
1588         """missing upper layer"""
1589         optdata = "\x00" * 100
1590         p = (
1591             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1592             / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1593             / IPv6ExtHdrFragment(m=1)
1594             / IPv6ExtHdrDestOpt(
1595                 nh=17, options=PadN(optdata="\101" * 255) / PadN(optdata="\102" * 255)
1596             )
1597         )
1598
1599         self.pg_enable_capture()
1600         self.src_if.add_stream([p])
1601         self.pg_start()
1602         pkts = self.src_if.get_capture(expected_count=1)
1603         icmp = pkts[0]
1604         self.assertIn(ICMPv6ParamProblem, icmp)
1605         self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
1606
1607     def test_truncated_fragment(self):
1608         """truncated fragment"""
1609         pkt = (
1610             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1611             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
1612             / IPv6ExtHdrFragment(nh=6)
1613         )
1614
1615         self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
1616
1617     def test_invalid_frag_size(self):
1618         """fragment size not a multiple of 8"""
1619         p = (
1620             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1621             / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1622             / UDP(sport=1234, dport=5678)
1623             / Raw()
1624         )
1625         self.extend_packet(p, 1000, self.padding)
1626         fragments = fragment_rfc8200(p, 1, 500)
1627         bad_fragment = fragments[0]
1628         self.extend_packet(bad_fragment, len(bad_fragment) + 5)
1629         self.pg_enable_capture()
1630         self.src_if.add_stream([bad_fragment])
1631         self.pg_start()
1632         pkts = self.src_if.get_capture(expected_count=1)
1633         icmp = pkts[0]
1634         self.assertIn(ICMPv6ParamProblem, icmp)
1635         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1636
1637     def test_invalid_packet_size(self):
1638         """total packet size > 65535"""
1639         p = (
1640             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1641             / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1642             / UDP(sport=1234, dport=5678)
1643             / Raw()
1644         )
1645         self.extend_packet(p, 1000, self.padding)
1646         fragments = fragment_rfc8200(p, 1, 500)
1647         bad_fragment = fragments[1]
1648         bad_fragment[IPv6ExtHdrFragment].offset = 65500
1649         self.pg_enable_capture()
1650         self.src_if.add_stream([bad_fragment])
1651         self.pg_start()
1652         pkts = self.src_if.get_capture(expected_count=1)
1653         icmp = pkts[0]
1654         self.assertIn(ICMPv6ParamProblem, icmp)
1655         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1656
1657     def test_atomic_fragment(self):
1658         """IPv6 atomic fragment"""
1659         pkt = (
1660             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1661             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=65535)
1662             / IPv6ExtHdrFragment(
1663                 offset=8191, m=1, res1=0xFF, res2=0xFF, nh=255, id=0xFFFF
1664             )
1665             / ("X" * 1452)
1666         )
1667
1668         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1669         self.assertIn(ICMPv6ParamProblem, rx[0])
1670
1671     def test_truncated_fragment(self):
1672         """IPv6 truncated fragment header"""
1673         pkt = (
1674             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1675             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
1676             / IPv6ExtHdrFragment(nh=6)
1677         )
1678
1679         self.send_and_assert_no_replies(self.pg0, [pkt])
1680
1681         pkt = (
1682             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1683             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.remote_ip6)
1684             / ICMPv6EchoRequest()
1685         )
1686         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1687
1688     def test_one_fragment(self):
1689         """whole packet in one fragment processed independently"""
1690         pkt = (
1691             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1692             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1693             / ICMPv6EchoRequest()
1694             / Raw("X" * 1600)
1695         )
1696         frags = fragment_rfc8200(pkt, 1, 400)
1697
1698         # send a fragment with known id
1699         self.send_and_assert_no_replies(self.pg0, [frags[0]])
1700
1701         # send an atomic fragment with same id - should be reassembled
1702         pkt = (
1703             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1704             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1705             / IPv6ExtHdrFragment(id=1)
1706             / ICMPv6EchoRequest()
1707         )
1708         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1709         self.assertNotIn(IPv6ExtHdrFragment, rx)
1710
1711         # now finish the original reassembly, this should still be possible
1712         rx = self.send_and_expect(self.pg0, frags[1:], self.pg0, n_rx=1)
1713         self.assertNotIn(IPv6ExtHdrFragment, rx)
1714
1715     def test_bunch_of_fragments(self):
1716         """valid fragments followed by rogue fragments and atomic fragment"""
1717         pkt = (
1718             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1719             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1720             / ICMPv6EchoRequest()
1721             / Raw("X" * 1600)
1722         )
1723         frags = fragment_rfc8200(pkt, 1, 400)
1724         self.send_and_expect(self.pg0, frags, self.pg0, n_rx=1)
1725
1726         inc_frag = (
1727             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1728             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1729             / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
1730             / Raw("X" * 308)
1731         )
1732
1733         self.send_and_assert_no_replies(self.pg0, inc_frag * 604)
1734
1735         pkt = (
1736             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1737             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1738             / IPv6ExtHdrFragment(id=1)
1739             / ICMPv6EchoRequest()
1740         )
1741         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1742         self.assertNotIn(IPv6ExtHdrFragment, rx)
1743
1744     def test_local_enable_disable(self):
1745         """local reassembly enabled/disable"""
1746         self.vapi.ip_reassembly_enable_disable(
1747             sw_if_index=self.src_if.sw_if_index, enable_ip6=False
1748         )
1749         self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1750         pkt = (
1751             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
1752             / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1753             / ICMPv6EchoRequest(id=1234)
1754             / Raw("X" * 1600)
1755         )
1756         frags = fragment_rfc8200(pkt, 1, 400)
1757         r = self.send_and_expect(self.src_if, frags, self.src_if, n_rx=1)[0]
1758         self.assertEqual(1234, r[ICMPv6EchoReply].id)
1759         self.vapi.ip_local_reass_enable_disable()
1760
1761         self.send_and_assert_no_replies(self.src_if, frags)
1762         self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1763
1764
1765 class TestIPv6MWReassembly(VppTestCase):
1766     """IPv6 Reassembly (multiple workers)"""
1767
1768     vpp_worker_count = 3
1769
1770     @classmethod
1771     def setUpClass(cls):
1772         super().setUpClass()
1773
1774         cls.create_pg_interfaces(range(cls.vpp_worker_count + 1))
1775         cls.src_if = cls.pg0
1776         cls.send_ifs = cls.pg_interfaces[:-1]
1777         cls.dst_if = cls.pg_interfaces[-1]
1778
1779         # setup all interfaces
1780         for i in cls.pg_interfaces:
1781             i.admin_up()
1782             i.config_ip6()
1783             i.resolve_ndp()
1784
1785         # packets sizes reduced here because we are generating packets without
1786         # Ethernet headers, which are added later (diff fragments go via
1787         # different interfaces)
1788         cls.packet_sizes = [
1789             64 - len(Ether()),
1790             512 - len(Ether()),
1791             1518 - len(Ether()),
1792             9018 - len(Ether()),
1793         ]
1794         cls.padding = " abcdefghijklmn"
1795         cls.create_stream(cls.packet_sizes)
1796         cls.create_fragments()
1797
1798     @classmethod
1799     def tearDownClass(cls):
1800         super().tearDownClass()
1801
1802     def setUp(self):
1803         """Test setup - force timeout on existing reassemblies"""
1804         super().setUp()
1805         for intf in self.send_ifs:
1806             self.vapi.ip_reassembly_enable_disable(
1807                 sw_if_index=intf.sw_if_index, enable_ip6=True
1808             )
1809         self.vapi.ip_reassembly_set(
1810             timeout_ms=0,
1811             max_reassemblies=1000,
1812             max_reassembly_length=1000,
1813             expire_walk_interval_ms=10,
1814             is_ip6=1,
1815         )
1816         self.virtual_sleep(0.25)
1817         self.vapi.ip_reassembly_set(
1818             timeout_ms=1000000,
1819             max_reassemblies=1000,
1820             max_reassembly_length=1000,
1821             expire_walk_interval_ms=1000,
1822             is_ip6=1,
1823         )
1824
1825     def tearDown(self):
1826         for intf in self.send_ifs:
1827             self.vapi.ip_reassembly_enable_disable(
1828                 sw_if_index=intf.sw_if_index, enable_ip6=False
1829             )
1830         super().tearDown()
1831
1832     def show_commands_at_teardown(self):
1833         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1834         self.logger.debug(self.vapi.ppcli("show buffers"))
1835
1836     @classmethod
1837     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1838         """Create input packet stream
1839
1840         :param list packet_sizes: Required packet sizes.
1841         """
1842         for i in range(0, packet_count):
1843             info = cls.create_packet_info(cls.src_if, cls.src_if)
1844             payload = cls.info_to_payload(info)
1845             p = (
1846                 IPv6(src=cls.src_if.remote_ip6, dst=cls.dst_if.remote_ip6)
1847                 / UDP(sport=1234, dport=5678)
1848                 / Raw(payload)
1849             )
1850             size = packet_sizes[(i // 2) % len(packet_sizes)]
1851             cls.extend_packet(p, size, cls.padding)
1852             info.data = p
1853
1854     @classmethod
1855     def create_fragments(cls):
1856         infos = cls._packet_infos
1857         cls.pkt_infos = []
1858         for index, info in infos.items():
1859             p = info.data
1860             # cls.logger.debug(ppp("Packet:",
1861             #                      p.__class__(scapy.compat.raw(p))))
1862             fragments_400 = fragment_rfc8200(p, index, 400)
1863             cls.pkt_infos.append((index, fragments_400))
1864         cls.fragments_400 = [x for (_, frags) in cls.pkt_infos for x in frags]
1865         cls.logger.debug(
1866             "Fragmented %s packets into %s 400-byte fragments, "
1867             % (len(infos), len(cls.fragments_400))
1868         )
1869
1870     def verify_capture(self, capture, dropped_packet_indexes=[]):
1871         """Verify captured packet strea .
1872
1873         :param list capture: Captured packet stream.
1874         """
1875         info = None
1876         seen = set()
1877         for packet in capture:
1878             try:
1879                 self.logger.debug(ppp("Got packet:", packet))
1880                 ip = packet[IPv6]
1881                 udp = packet[UDP]
1882                 payload_info = self.payload_to_info(packet[Raw])
1883                 packet_index = payload_info.index
1884                 self.assertTrue(
1885                     packet_index not in dropped_packet_indexes,
1886                     ppp("Packet received, but should be dropped:", packet),
1887                 )
1888                 if packet_index in seen:
1889                     raise Exception(ppp("Duplicate packet received", packet))
1890                 seen.add(packet_index)
1891                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1892                 info = self._packet_infos[packet_index]
1893                 self.assertTrue(info is not None)
1894                 self.assertEqual(packet_index, info.index)
1895                 saved_packet = info.data
1896                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1897                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1898                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1899             except Exception:
1900                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1901                 raise
1902         for index in self._packet_infos:
1903             self.assertTrue(
1904                 index in seen or index in dropped_packet_indexes,
1905                 "Packet with packet_index %d not received" % index,
1906             )
1907
1908     def send_packets(self, packets):
1909         for counter in range(self.vpp_worker_count):
1910             if 0 == len(packets[counter]):
1911                 continue
1912             send_if = self.send_ifs[counter]
1913             send_if.add_stream(
1914                 (
1915                     Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1916                     for x in packets[counter]
1917                 ),
1918                 worker=counter,
1919             )
1920         self.pg_start()
1921
1922     def test_worker_conflict(self):
1923         """1st and FO=0 fragments on different workers"""
1924
1925         # in first wave we send fragments which don't start at offset 0
1926         # then we send fragments with offset 0 on a different thread
1927         # then the rest of packets on a random thread
1928         first_packets = [[] for n in range(self.vpp_worker_count)]
1929         second_packets = [[] for n in range(self.vpp_worker_count)]
1930         rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1931         for _, p in self.pkt_infos:
1932             wi = randrange(self.vpp_worker_count)
1933             second_packets[wi].append(p[0])
1934             if len(p) <= 1:
1935                 continue
1936             wi2 = wi
1937             while wi2 == wi:
1938                 wi2 = randrange(self.vpp_worker_count)
1939             first_packets[wi2].append(p[1])
1940             wi3 = randrange(self.vpp_worker_count)
1941             rest_of_packets[wi3].extend(p[2:])
1942
1943         self.pg_enable_capture()
1944         self.send_packets(first_packets)
1945         self.send_packets(second_packets)
1946         self.send_packets(rest_of_packets)
1947
1948         packets = self.dst_if.get_capture(len(self.pkt_infos))
1949         self.verify_capture(packets)
1950         for send_if in self.send_ifs:
1951             send_if.assert_nothing_captured()
1952
1953         self.logger.debug(self.vapi.ppcli("show trace"))
1954         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1955         self.logger.debug(self.vapi.ppcli("show buffers"))
1956         self.vapi.cli("clear trace")
1957
1958         self.pg_enable_capture()
1959         self.send_packets(first_packets)
1960         self.send_packets(second_packets)
1961         self.send_packets(rest_of_packets)
1962
1963         packets = self.dst_if.get_capture(len(self.pkt_infos))
1964         self.verify_capture(packets)
1965         for send_if in self.send_ifs:
1966             send_if.assert_nothing_captured()
1967
1968
1969 class TestIPv6SVReassembly(VppTestCase):
1970     """IPv6 Shallow Virtual Reassembly"""
1971
1972     @classmethod
1973     def setUpClass(cls):
1974         super().setUpClass()
1975
1976         cls.create_pg_interfaces([0, 1])
1977         cls.src_if = cls.pg0
1978         cls.dst_if = cls.pg1
1979
1980         # setup all interfaces
1981         for i in cls.pg_interfaces:
1982             i.admin_up()
1983             i.config_ip6()
1984             i.resolve_ndp()
1985
1986     def setUp(self):
1987         """Test setup - force timeout on existing reassemblies"""
1988         super().setUp()
1989         self.vapi.ip_reassembly_enable_disable(
1990             sw_if_index=self.src_if.sw_if_index,
1991             enable_ip6=True,
1992             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1993         )
1994         self.vapi.ip_reassembly_set(
1995             timeout_ms=0,
1996             max_reassemblies=1000,
1997             max_reassembly_length=1000,
1998             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1999             expire_walk_interval_ms=10,
2000             is_ip6=1,
2001         )
2002         self.virtual_sleep(0.25)
2003         self.vapi.ip_reassembly_set(
2004             timeout_ms=1000000,
2005             max_reassemblies=1000,
2006             max_reassembly_length=1000,
2007             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2008             expire_walk_interval_ms=10000,
2009             is_ip6=1,
2010         )
2011
2012     def tearDown(self):
2013         super().tearDown()
2014         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2015         self.logger.debug(self.vapi.ppcli("show buffers"))
2016
2017     def test_basic(self):
2018         """basic reassembly"""
2019         payload_len = 1000
2020         payload = ""
2021         counter = 0
2022         while len(payload) < payload_len:
2023             payload += "%u " % counter
2024             counter += 1
2025
2026         p = (
2027             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2028             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2029             / UDP(sport=1234, dport=5678)
2030             / Raw(payload)
2031         )
2032         fragments = fragment_rfc8200(p, 1, payload_len / 4)
2033
2034         # send fragment #2 - should be cached inside reassembly
2035         self.pg_enable_capture()
2036         self.src_if.add_stream(fragments[1])
2037         self.pg_start()
2038         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2039         self.logger.debug(self.vapi.ppcli("show buffers"))
2040         self.logger.debug(self.vapi.ppcli("show trace"))
2041         self.dst_if.assert_nothing_captured()
2042
2043         # send fragment #1 - reassembly is finished now and both fragments
2044         # forwarded
2045         self.pg_enable_capture()
2046         self.src_if.add_stream(fragments[0])
2047         self.pg_start()
2048         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2049         self.logger.debug(self.vapi.ppcli("show buffers"))
2050         self.logger.debug(self.vapi.ppcli("show trace"))
2051         c = self.dst_if.get_capture(2)
2052         for sent, recvd in zip([fragments[1], fragments[0]], c):
2053             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2054             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2055             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2056
2057         # send rest of fragments - should be immediately forwarded
2058         self.pg_enable_capture()
2059         self.src_if.add_stream(fragments[2:])
2060         self.pg_start()
2061         c = self.dst_if.get_capture(len(fragments[2:]))
2062         for sent, recvd in zip(fragments[2:], c):
2063             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2064             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2065             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2066
2067     def test_verify_clear_trace_mid_reassembly(self):
2068         """verify clear trace works mid-reassembly"""
2069         payload_len = 1000
2070         payload = ""
2071         counter = 0
2072         while len(payload) < payload_len:
2073             payload += "%u " % counter
2074             counter += 1
2075
2076         p = (
2077             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2078             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2079             / UDP(sport=1234, dport=5678)
2080             / Raw(payload)
2081         )
2082         fragments = fragment_rfc8200(p, 1, payload_len / 4)
2083
2084         self.pg_enable_capture()
2085         self.src_if.add_stream(fragments[1])
2086         self.pg_start()
2087
2088         self.logger.debug(self.vapi.cli("show trace"))
2089         self.vapi.cli("clear trace")
2090
2091         self.pg_enable_capture()
2092         self.src_if.add_stream(fragments[0])
2093         self.pg_start()
2094         self.dst_if.get_capture(2)
2095
2096         self.logger.debug(self.vapi.cli("show trace"))
2097         self.vapi.cli("clear trace")
2098
2099         self.pg_enable_capture()
2100         self.src_if.add_stream(fragments[2:])
2101         self.pg_start()
2102         self.dst_if.get_capture(len(fragments[2:]))
2103
2104     def test_timeout(self):
2105         """reassembly timeout"""
2106         payload_len = 1000
2107         payload = ""
2108         counter = 0
2109         while len(payload) < payload_len:
2110             payload += "%u " % counter
2111             counter += 1
2112
2113         p = (
2114             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2115             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2116             / UDP(sport=1234, dport=5678)
2117             / Raw(payload)
2118         )
2119         fragments = fragment_rfc8200(p, 1, payload_len / 4)
2120
2121         self.vapi.ip_reassembly_set(
2122             timeout_ms=100,
2123             max_reassemblies=1000,
2124             max_reassembly_length=1000,
2125             expire_walk_interval_ms=50,
2126             is_ip6=1,
2127             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2128         )
2129
2130         # send fragments #2 and #1 - should be forwarded
2131         self.pg_enable_capture()
2132         self.src_if.add_stream(fragments[0:2])
2133         self.pg_start()
2134         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
2135         self.logger.debug(self.vapi.ppcli("show buffers"))
2136         self.logger.debug(self.vapi.ppcli("show trace"))
2137         c = self.dst_if.get_capture(2)
2138         for sent, recvd in zip([fragments[1], fragments[0]], c):
2139             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2140             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2141             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2142
2143         # wait for cleanup
2144         self.virtual_sleep(0.25, "wait before sending rest of fragments")
2145
2146         # send rest of fragments - shouldn't be forwarded
2147         self.pg_enable_capture()
2148         self.src_if.add_stream(fragments[2:])
2149         self.pg_start()
2150         self.dst_if.assert_nothing_captured()
2151
2152     def test_lru(self):
2153         """reassembly reuses LRU element"""
2154
2155         self.vapi.ip_reassembly_set(
2156             timeout_ms=1000000,
2157             max_reassemblies=1,
2158             max_reassembly_length=1000,
2159             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2160             is_ip6=1,
2161             expire_walk_interval_ms=10000,
2162         )
2163
2164         payload_len = 1000
2165         payload = ""
2166         counter = 0
2167         while len(payload) < payload_len:
2168             payload += "%u " % counter
2169             counter += 1
2170
2171         packet_count = 10
2172
2173         fragments = [
2174             f
2175             for i in range(packet_count)
2176             for p in (
2177                 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2178                 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2179                 / UDP(sport=1234, dport=5678)
2180                 / Raw(payload)
2181             )
2182             for f in fragment_rfc8200(p, i, payload_len / 4)
2183         ]
2184
2185         self.pg_enable_capture()
2186         self.src_if.add_stream(fragments)
2187         self.pg_start()
2188         c = self.dst_if.get_capture(len(fragments))
2189         for sent, recvd in zip(fragments, c):
2190             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2191             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2192             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2193
2194     def test_one_fragment(self):
2195         """whole packet in one fragment processed independently"""
2196         pkt = (
2197             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2198             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2199             / ICMPv6EchoRequest()
2200             / Raw("X" * 1600)
2201         )
2202         frags = fragment_rfc8200(pkt, 1, 400)
2203
2204         # send a fragment with known id
2205         self.send_and_expect(self.src_if, [frags[0]], self.dst_if)
2206
2207         # send an atomic fragment with same id - should be reassembled
2208         pkt = (
2209             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2210             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2211             / IPv6ExtHdrFragment(id=1)
2212             / ICMPv6EchoRequest()
2213         )
2214         rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2215
2216         # now forward packets matching original reassembly, should still work
2217         rx = self.send_and_expect(self.src_if, frags[1:], self.dst_if)
2218
2219     def test_bunch_of_fragments(self):
2220         """valid fragments followed by rogue fragments and atomic fragment"""
2221         pkt = (
2222             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2223             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2224             / ICMPv6EchoRequest()
2225             / Raw("X" * 1600)
2226         )
2227         frags = fragment_rfc8200(pkt, 1, 400)
2228         rx = self.send_and_expect(self.src_if, frags, self.dst_if)
2229
2230         rogue = (
2231             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2232             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2233             / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
2234             / Raw("X" * 308)
2235         )
2236
2237         self.send_and_expect(self.src_if, rogue * 604, self.dst_if)
2238
2239         pkt = (
2240             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2241             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2242             / IPv6ExtHdrFragment(id=1)
2243             / ICMPv6EchoRequest()
2244         )
2245         rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2246
2247     def test_truncated_fragment(self):
2248         """truncated fragment"""
2249         pkt = (
2250             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
2251             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
2252             / IPv6ExtHdrFragment(nh=6)
2253         )
2254
2255         self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
2256
2257
2258 class TestIPv4ReassemblyLocalNode(VppTestCase):
2259     """IPv4 Reassembly for packets coming to ip4-local node"""
2260
2261     @classmethod
2262     def setUpClass(cls):
2263         super().setUpClass()
2264
2265         cls.create_pg_interfaces([0])
2266         cls.src_dst_if = cls.pg0
2267
2268         # setup all interfaces
2269         for i in cls.pg_interfaces:
2270             i.admin_up()
2271             i.config_ip4()
2272             i.resolve_arp()
2273
2274         cls.padding = " abcdefghijklmn"
2275         cls.create_stream()
2276         cls.create_fragments()
2277
2278     @classmethod
2279     def tearDownClass(cls):
2280         super().tearDownClass()
2281
2282     def setUp(self):
2283         """Test setup - force timeout on existing reassemblies"""
2284         super().setUp()
2285         self.vapi.ip_reassembly_set(
2286             timeout_ms=0,
2287             max_reassemblies=1000,
2288             max_reassembly_length=1000,
2289             expire_walk_interval_ms=10,
2290         )
2291         self.virtual_sleep(0.25)
2292         self.vapi.ip_reassembly_set(
2293             timeout_ms=1000000,
2294             max_reassemblies=1000,
2295             max_reassembly_length=1000,
2296             expire_walk_interval_ms=10000,
2297         )
2298
2299     def tearDown(self):
2300         super().tearDown()
2301
2302     def show_commands_at_teardown(self):
2303         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2304         self.logger.debug(self.vapi.ppcli("show buffers"))
2305
2306     @classmethod
2307     def create_stream(cls, packet_count=test_packet_count):
2308         """Create input packet stream for defined interface.
2309
2310         :param list packet_sizes: Required packet sizes.
2311         """
2312         for i in range(0, packet_count):
2313             info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
2314             payload = cls.info_to_payload(info)
2315             p = (
2316                 Ether(dst=cls.src_dst_if.local_mac, src=cls.src_dst_if.remote_mac)
2317                 / IP(
2318                     id=info.index,
2319                     src=cls.src_dst_if.remote_ip4,
2320                     dst=cls.src_dst_if.local_ip4,
2321                 )
2322                 / ICMP(type="echo-request", id=1234)
2323                 / Raw(payload)
2324             )
2325             cls.extend_packet(p, 1518, cls.padding)
2326             info.data = p
2327
2328     @classmethod
2329     def create_fragments(cls):
2330         infos = cls._packet_infos
2331         cls.pkt_infos = []
2332         for index, info in infos.items():
2333             p = info.data
2334             # cls.logger.debug(ppp("Packet:",
2335             #                      p.__class__(scapy.compat.raw(p))))
2336             fragments_300 = fragment_rfc791(p, 300)
2337             cls.pkt_infos.append((index, fragments_300))
2338         cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
2339         cls.logger.debug(
2340             "Fragmented %s packets into %s 300-byte fragments"
2341             % (len(infos), len(cls.fragments_300))
2342         )
2343
2344     def verify_capture(self, capture):
2345         """Verify captured packet stream.
2346
2347         :param list capture: Captured packet stream.
2348         """
2349         info = None
2350         seen = set()
2351         for packet in capture:
2352             try:
2353                 self.logger.debug(ppp("Got packet:", packet))
2354                 ip = packet[IP]
2355                 icmp = packet[ICMP]
2356                 payload_info = self.payload_to_info(packet[Raw])
2357                 packet_index = payload_info.index
2358                 if packet_index in seen:
2359                     raise Exception(ppp("Duplicate packet received", packet))
2360                 seen.add(packet_index)
2361                 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
2362                 info = self._packet_infos[packet_index]
2363                 self.assertIsNotNone(info)
2364                 self.assertEqual(packet_index, info.index)
2365                 saved_packet = info.data
2366                 self.assertEqual(ip.src, saved_packet[IP].dst)
2367                 self.assertEqual(ip.dst, saved_packet[IP].src)
2368                 self.assertEqual(icmp.type, 0)  # echo reply
2369                 self.assertEqual(icmp.id, saved_packet[ICMP].id)
2370                 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
2371             except Exception:
2372                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2373                 raise
2374         for index in self._packet_infos:
2375             self.assertIn(
2376                 index, seen, "Packet with packet_index %d not received" % index
2377             )
2378
2379     def test_reassembly(self):
2380         """basic reassembly"""
2381
2382         self.pg_enable_capture()
2383         self.src_dst_if.add_stream(self.fragments_300)
2384         self.pg_start()
2385
2386         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2387         self.verify_capture(packets)
2388
2389         # run it all again to verify correctness
2390         self.pg_enable_capture()
2391         self.src_dst_if.add_stream(self.fragments_300)
2392         self.pg_start()
2393
2394         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2395         self.verify_capture(packets)
2396
2397
2398 class TestFIFReassembly(VppTestCase):
2399     """Fragments in fragments reassembly"""
2400
2401     @classmethod
2402     def setUpClass(cls):
2403         super().setUpClass()
2404
2405         cls.create_pg_interfaces([0, 1])
2406         cls.src_if = cls.pg0
2407         cls.dst_if = cls.pg1
2408         for i in cls.pg_interfaces:
2409             i.admin_up()
2410             i.config_ip4()
2411             i.resolve_arp()
2412             i.config_ip6()
2413             i.resolve_ndp()
2414
2415         cls.packet_sizes = [64, 512, 1518, 9018]
2416         cls.padding = " abcdefghijklmn"
2417
2418     @classmethod
2419     def tearDownClass(cls):
2420         super().tearDownClass()
2421
2422     def setUp(self):
2423         """Test setup - force timeout on existing reassemblies"""
2424         super().setUp()
2425         self.vapi.ip_reassembly_enable_disable(
2426             sw_if_index=self.src_if.sw_if_index, enable_ip4=True, enable_ip6=True
2427         )
2428         self.vapi.ip_reassembly_enable_disable(
2429             sw_if_index=self.dst_if.sw_if_index, enable_ip4=True, enable_ip6=True
2430         )
2431         self.vapi.ip_reassembly_set(
2432             timeout_ms=0,
2433             max_reassemblies=1000,
2434             max_reassembly_length=1000,
2435             expire_walk_interval_ms=10,
2436         )
2437         self.vapi.ip_reassembly_set(
2438             timeout_ms=0,
2439             max_reassemblies=1000,
2440             max_reassembly_length=1000,
2441             expire_walk_interval_ms=10,
2442             is_ip6=1,
2443         )
2444         self.virtual_sleep(0.25)
2445         self.vapi.ip_reassembly_set(
2446             timeout_ms=1000000,
2447             max_reassemblies=1000,
2448             max_reassembly_length=1000,
2449             expire_walk_interval_ms=10000,
2450         )
2451         self.vapi.ip_reassembly_set(
2452             timeout_ms=1000000,
2453             max_reassemblies=1000,
2454             max_reassembly_length=1000,
2455             expire_walk_interval_ms=10000,
2456             is_ip6=1,
2457         )
2458
2459     def tearDown(self):
2460         super().tearDown()
2461
2462     def show_commands_at_teardown(self):
2463         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2464         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
2465         self.logger.debug(self.vapi.ppcli("show buffers"))
2466
2467     def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
2468         """Verify captured packet stream.
2469
2470         :param list capture: Captured packet stream.
2471         """
2472         info = None
2473         seen = set()
2474         for packet in capture:
2475             try:
2476                 self.logger.debug(ppp("Got packet:", packet))
2477                 ip = packet[ip_class]
2478                 udp = packet[UDP]
2479                 payload_info = self.payload_to_info(packet[Raw])
2480                 packet_index = payload_info.index
2481                 self.assertTrue(
2482                     packet_index not in dropped_packet_indexes,
2483                     ppp("Packet received, but should be dropped:", packet),
2484                 )
2485                 if packet_index in seen:
2486                     raise Exception(ppp("Duplicate packet received", packet))
2487                 seen.add(packet_index)
2488                 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
2489                 info = self._packet_infos[packet_index]
2490                 self.assertTrue(info is not None)
2491                 self.assertEqual(packet_index, info.index)
2492                 saved_packet = info.data
2493                 self.assertEqual(ip.src, saved_packet[ip_class].src)
2494                 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
2495                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
2496             except Exception:
2497                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2498                 raise
2499         for index in self._packet_infos:
2500             self.assertTrue(
2501                 index in seen or index in dropped_packet_indexes,
2502                 "Packet with packet_index %d not received" % index,
2503             )
2504
2505     def test_fif4(self):
2506         """Fragments in fragments (4o4)"""
2507
2508         # TODO this should be ideally in setUpClass, but then we hit a bug
2509         # with VppIpRoute incorrectly reporting it's present when it's not
2510         # so we need to manually remove the vpp config, thus we cannot have
2511         # it shared for multiple test cases
2512         self.tun_ip4 = "1.1.1.2"
2513
2514         self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
2515         self.gre4.add_vpp_config()
2516         self.gre4.admin_up()
2517         self.gre4.config_ip4()
2518
2519         self.vapi.ip_reassembly_enable_disable(
2520             sw_if_index=self.gre4.sw_if_index, enable_ip4=True
2521         )
2522
2523         self.route4 = VppIpRoute(
2524             self,
2525             self.tun_ip4,
2526             32,
2527             [VppRoutePath(self.src_if.remote_ip4, self.src_if.sw_if_index)],
2528         )
2529         self.route4.add_vpp_config()
2530
2531         self.reset_packet_infos()
2532         for i in range(test_packet_count):
2533             info = self.create_packet_info(self.src_if, self.dst_if)
2534             payload = self.info_to_payload(info)
2535             # Ethernet header here is only for size calculation, thus it
2536             # doesn't matter how it's initialized. This is to ensure that
2537             # reassembled packet is not > 9000 bytes, so that it's not dropped
2538             p = (
2539                 Ether()
2540                 / IP(id=i, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
2541                 / UDP(sport=1234, dport=5678)
2542                 / Raw(payload)
2543             )
2544             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2545             self.extend_packet(p, size, self.padding)
2546             info.data = p[IP]  # use only IP part, without ethernet header
2547
2548         fragments = [
2549             x
2550             for _, p in self._packet_infos.items()
2551             for x in fragment_rfc791(p.data, 400)
2552         ]
2553
2554         encapped_fragments = [
2555             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2556             / IP(src=self.tun_ip4, dst=self.src_if.local_ip4)
2557             / GRE()
2558             / p
2559             for p in fragments
2560         ]
2561
2562         fragmented_encapped_fragments = [
2563             x for p in encapped_fragments for x in fragment_rfc791(p, 200)
2564         ]
2565
2566         self.src_if.add_stream(fragmented_encapped_fragments)
2567
2568         self.pg_enable_capture(self.pg_interfaces)
2569         self.pg_start()
2570
2571         self.src_if.assert_nothing_captured()
2572         packets = self.dst_if.get_capture(len(self._packet_infos))
2573         self.verify_capture(packets, IP)
2574
2575         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2576         # so that it's query_vpp_config() works as it should
2577         self.gre4.remove_vpp_config()
2578         self.logger.debug(self.vapi.ppcli("show interface"))
2579
2580     def test_fif6(self):
2581         """Fragments in fragments (6o6)"""
2582         # TODO this should be ideally in setUpClass, but then we hit a bug
2583         # with VppIpRoute incorrectly reporting it's present when it's not
2584         # so we need to manually remove the vpp config, thus we cannot have
2585         # it shared for multiple test cases
2586         self.tun_ip6 = "1002::1"
2587
2588         self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
2589         self.gre6.add_vpp_config()
2590         self.gre6.admin_up()
2591         self.gre6.config_ip6()
2592
2593         self.vapi.ip_reassembly_enable_disable(
2594             sw_if_index=self.gre6.sw_if_index, enable_ip6=True
2595         )
2596
2597         self.route6 = VppIpRoute(
2598             self,
2599             self.tun_ip6,
2600             128,
2601             [VppRoutePath(self.src_if.remote_ip6, self.src_if.sw_if_index)],
2602         )
2603         self.route6.add_vpp_config()
2604
2605         self.reset_packet_infos()
2606         for i in range(test_packet_count):
2607             info = self.create_packet_info(self.src_if, self.dst_if)
2608             payload = self.info_to_payload(info)
2609             # Ethernet header here is only for size calculation, thus it
2610             # doesn't matter how it's initialized. This is to ensure that
2611             # reassembled packet is not > 9000 bytes, so that it's not dropped
2612             p = (
2613                 Ether()
2614                 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2615                 / UDP(sport=1234, dport=5678)
2616                 / Raw(payload)
2617             )
2618             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2619             self.extend_packet(p, size, self.padding)
2620             info.data = p[IPv6]  # use only IPv6 part, without ethernet header
2621
2622         fragments = [
2623             x
2624             for _, i in self._packet_infos.items()
2625             for x in fragment_rfc8200(i.data, i.index, 400)
2626         ]
2627
2628         encapped_fragments = [
2629             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2630             / IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6)
2631             / GRE()
2632             / p
2633             for p in fragments
2634         ]
2635
2636         fragmented_encapped_fragments = [
2637             x
2638             for p in encapped_fragments
2639             for x in (
2640                 fragment_rfc8200(
2641                     p, 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id, 200
2642                 )
2643                 if IPv6ExtHdrFragment in p
2644                 else [p]
2645             )
2646         ]
2647
2648         self.src_if.add_stream(fragmented_encapped_fragments)
2649
2650         self.pg_enable_capture(self.pg_interfaces)
2651         self.pg_start()
2652
2653         self.src_if.assert_nothing_captured()
2654         packets = self.dst_if.get_capture(len(self._packet_infos))
2655         self.verify_capture(packets, IPv6)
2656
2657         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2658         # so that it's query_vpp_config() works as it should
2659         self.gre6.remove_vpp_config()
2660
2661
2662 if __name__ == "__main__":
2663     unittest.main(testRunner=VppTestRunner)