tests: remove duplicate SVR test case
[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_invalid_frag_size(self):
1608         """fragment size not a multiple of 8"""
1609         p = (
1610             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1611             / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1612             / UDP(sport=1234, dport=5678)
1613             / Raw()
1614         )
1615         self.extend_packet(p, 1000, self.padding)
1616         fragments = fragment_rfc8200(p, 1, 500)
1617         bad_fragment = fragments[0]
1618         self.extend_packet(bad_fragment, len(bad_fragment) + 5)
1619         self.pg_enable_capture()
1620         self.src_if.add_stream([bad_fragment])
1621         self.pg_start()
1622         pkts = self.src_if.get_capture(expected_count=1)
1623         icmp = pkts[0]
1624         self.assertIn(ICMPv6ParamProblem, icmp)
1625         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1626
1627     def test_invalid_packet_size(self):
1628         """total packet size > 65535"""
1629         p = (
1630             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
1631             / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1632             / UDP(sport=1234, dport=5678)
1633             / Raw()
1634         )
1635         self.extend_packet(p, 1000, self.padding)
1636         fragments = fragment_rfc8200(p, 1, 500)
1637         bad_fragment = fragments[1]
1638         bad_fragment[IPv6ExtHdrFragment].offset = 65500
1639         self.pg_enable_capture()
1640         self.src_if.add_stream([bad_fragment])
1641         self.pg_start()
1642         pkts = self.src_if.get_capture(expected_count=1)
1643         icmp = pkts[0]
1644         self.assertIn(ICMPv6ParamProblem, icmp)
1645         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1646
1647     def test_atomic_fragment(self):
1648         """IPv6 atomic fragment"""
1649         pkt = (
1650             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1651             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=65535)
1652             / IPv6ExtHdrFragment(
1653                 offset=8191, m=1, res1=0xFF, res2=0xFF, nh=255, id=0xFFFF
1654             )
1655             / ("X" * 1452)
1656         )
1657
1658         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1659         self.assertIn(ICMPv6ParamProblem, rx[0])
1660
1661     def test_truncated_fragment(self):
1662         """IPv6 truncated fragment header"""
1663         pkt = (
1664             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1665             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
1666             / IPv6ExtHdrFragment(nh=6)
1667         )
1668
1669         self.send_and_assert_no_replies(self.pg0, [pkt])
1670
1671         pkt = (
1672             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1673             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.remote_ip6)
1674             / ICMPv6EchoRequest()
1675         )
1676         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1677
1678     def test_one_fragment(self):
1679         """whole packet in one fragment processed independently"""
1680         pkt = (
1681             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1682             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1683             / ICMPv6EchoRequest()
1684             / Raw("X" * 1600)
1685         )
1686         frags = fragment_rfc8200(pkt, 1, 400)
1687
1688         # send a fragment with known id
1689         self.send_and_assert_no_replies(self.pg0, [frags[0]])
1690
1691         # send an atomic fragment with same id - should be reassembled
1692         pkt = (
1693             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1694             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1695             / IPv6ExtHdrFragment(id=1)
1696             / ICMPv6EchoRequest()
1697         )
1698         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1699         self.assertNotIn(IPv6ExtHdrFragment, rx)
1700
1701         # now finish the original reassembly, this should still be possible
1702         rx = self.send_and_expect(self.pg0, frags[1:], self.pg0, n_rx=1)
1703         self.assertNotIn(IPv6ExtHdrFragment, rx)
1704
1705     def test_bunch_of_fragments(self):
1706         """valid fragments followed by rogue fragments and atomic fragment"""
1707         pkt = (
1708             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1709             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1710             / ICMPv6EchoRequest()
1711             / Raw("X" * 1600)
1712         )
1713         frags = fragment_rfc8200(pkt, 1, 400)
1714         self.send_and_expect(self.pg0, frags, self.pg0, n_rx=1)
1715
1716         inc_frag = (
1717             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1718             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1719             / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
1720             / Raw("X" * 308)
1721         )
1722
1723         self.send_and_assert_no_replies(self.pg0, inc_frag * 604)
1724
1725         pkt = (
1726             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
1727             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6)
1728             / IPv6ExtHdrFragment(id=1)
1729             / ICMPv6EchoRequest()
1730         )
1731         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1732         self.assertNotIn(IPv6ExtHdrFragment, rx)
1733
1734     def test_local_enable_disable(self):
1735         """local reassembly enabled/disable"""
1736         self.vapi.ip_reassembly_enable_disable(
1737             sw_if_index=self.src_if.sw_if_index, enable_ip6=False
1738         )
1739         self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1740         pkt = (
1741             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
1742             / IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6)
1743             / ICMPv6EchoRequest(id=1234)
1744             / Raw("X" * 1600)
1745         )
1746         frags = fragment_rfc8200(pkt, 1, 400)
1747         r = self.send_and_expect(self.src_if, frags, self.src_if, n_rx=1)[0]
1748         self.assertEqual(1234, r[ICMPv6EchoReply].id)
1749         self.vapi.ip_local_reass_enable_disable()
1750
1751         self.send_and_assert_no_replies(self.src_if, frags)
1752         self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1753
1754
1755 class TestIPv6MWReassembly(VppTestCase):
1756     """IPv6 Reassembly (multiple workers)"""
1757
1758     vpp_worker_count = 3
1759
1760     @classmethod
1761     def setUpClass(cls):
1762         super().setUpClass()
1763
1764         cls.create_pg_interfaces(range(cls.vpp_worker_count + 1))
1765         cls.src_if = cls.pg0
1766         cls.send_ifs = cls.pg_interfaces[:-1]
1767         cls.dst_if = cls.pg_interfaces[-1]
1768
1769         # setup all interfaces
1770         for i in cls.pg_interfaces:
1771             i.admin_up()
1772             i.config_ip6()
1773             i.resolve_ndp()
1774
1775         # packets sizes reduced here because we are generating packets without
1776         # Ethernet headers, which are added later (diff fragments go via
1777         # different interfaces)
1778         cls.packet_sizes = [
1779             64 - len(Ether()),
1780             512 - len(Ether()),
1781             1518 - len(Ether()),
1782             9018 - len(Ether()),
1783         ]
1784         cls.padding = " abcdefghijklmn"
1785         cls.create_stream(cls.packet_sizes)
1786         cls.create_fragments()
1787
1788     @classmethod
1789     def tearDownClass(cls):
1790         super().tearDownClass()
1791
1792     def setUp(self):
1793         """Test setup - force timeout on existing reassemblies"""
1794         super().setUp()
1795         for intf in self.send_ifs:
1796             self.vapi.ip_reassembly_enable_disable(
1797                 sw_if_index=intf.sw_if_index, enable_ip6=True
1798             )
1799         self.vapi.ip_reassembly_set(
1800             timeout_ms=0,
1801             max_reassemblies=1000,
1802             max_reassembly_length=1000,
1803             expire_walk_interval_ms=10,
1804             is_ip6=1,
1805         )
1806         self.virtual_sleep(0.25)
1807         self.vapi.ip_reassembly_set(
1808             timeout_ms=1000000,
1809             max_reassemblies=1000,
1810             max_reassembly_length=1000,
1811             expire_walk_interval_ms=1000,
1812             is_ip6=1,
1813         )
1814
1815     def tearDown(self):
1816         for intf in self.send_ifs:
1817             self.vapi.ip_reassembly_enable_disable(
1818                 sw_if_index=intf.sw_if_index, enable_ip6=False
1819             )
1820         super().tearDown()
1821
1822     def show_commands_at_teardown(self):
1823         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1824         self.logger.debug(self.vapi.ppcli("show buffers"))
1825
1826     @classmethod
1827     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1828         """Create input packet stream
1829
1830         :param list packet_sizes: Required packet sizes.
1831         """
1832         for i in range(0, packet_count):
1833             info = cls.create_packet_info(cls.src_if, cls.src_if)
1834             payload = cls.info_to_payload(info)
1835             p = (
1836                 IPv6(src=cls.src_if.remote_ip6, dst=cls.dst_if.remote_ip6)
1837                 / UDP(sport=1234, dport=5678)
1838                 / Raw(payload)
1839             )
1840             size = packet_sizes[(i // 2) % len(packet_sizes)]
1841             cls.extend_packet(p, size, cls.padding)
1842             info.data = p
1843
1844     @classmethod
1845     def create_fragments(cls):
1846         infos = cls._packet_infos
1847         cls.pkt_infos = []
1848         for index, info in infos.items():
1849             p = info.data
1850             # cls.logger.debug(ppp("Packet:",
1851             #                      p.__class__(scapy.compat.raw(p))))
1852             fragments_400 = fragment_rfc8200(p, index, 400)
1853             cls.pkt_infos.append((index, fragments_400))
1854         cls.fragments_400 = [x for (_, frags) in cls.pkt_infos for x in frags]
1855         cls.logger.debug(
1856             "Fragmented %s packets into %s 400-byte fragments, "
1857             % (len(infos), len(cls.fragments_400))
1858         )
1859
1860     def verify_capture(self, capture, dropped_packet_indexes=[]):
1861         """Verify captured packet strea .
1862
1863         :param list capture: Captured packet stream.
1864         """
1865         info = None
1866         seen = set()
1867         for packet in capture:
1868             try:
1869                 self.logger.debug(ppp("Got packet:", packet))
1870                 ip = packet[IPv6]
1871                 udp = packet[UDP]
1872                 payload_info = self.payload_to_info(packet[Raw])
1873                 packet_index = payload_info.index
1874                 self.assertTrue(
1875                     packet_index not in dropped_packet_indexes,
1876                     ppp("Packet received, but should be dropped:", packet),
1877                 )
1878                 if packet_index in seen:
1879                     raise Exception(ppp("Duplicate packet received", packet))
1880                 seen.add(packet_index)
1881                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1882                 info = self._packet_infos[packet_index]
1883                 self.assertTrue(info is not None)
1884                 self.assertEqual(packet_index, info.index)
1885                 saved_packet = info.data
1886                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1887                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1888                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1889             except Exception:
1890                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1891                 raise
1892         for index in self._packet_infos:
1893             self.assertTrue(
1894                 index in seen or index in dropped_packet_indexes,
1895                 "Packet with packet_index %d not received" % index,
1896             )
1897
1898     def send_packets(self, packets):
1899         for counter in range(self.vpp_worker_count):
1900             if 0 == len(packets[counter]):
1901                 continue
1902             send_if = self.send_ifs[counter]
1903             send_if.add_stream(
1904                 (
1905                     Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1906                     for x in packets[counter]
1907                 ),
1908                 worker=counter,
1909             )
1910         self.pg_start()
1911
1912     def test_worker_conflict(self):
1913         """1st and FO=0 fragments on different workers"""
1914
1915         # in first wave we send fragments which don't start at offset 0
1916         # then we send fragments with offset 0 on a different thread
1917         # then the rest of packets on a random thread
1918         first_packets = [[] for n in range(self.vpp_worker_count)]
1919         second_packets = [[] for n in range(self.vpp_worker_count)]
1920         rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1921         for _, p in self.pkt_infos:
1922             wi = randrange(self.vpp_worker_count)
1923             second_packets[wi].append(p[0])
1924             if len(p) <= 1:
1925                 continue
1926             wi2 = wi
1927             while wi2 == wi:
1928                 wi2 = randrange(self.vpp_worker_count)
1929             first_packets[wi2].append(p[1])
1930             wi3 = randrange(self.vpp_worker_count)
1931             rest_of_packets[wi3].extend(p[2:])
1932
1933         self.pg_enable_capture()
1934         self.send_packets(first_packets)
1935         self.send_packets(second_packets)
1936         self.send_packets(rest_of_packets)
1937
1938         packets = self.dst_if.get_capture(len(self.pkt_infos))
1939         self.verify_capture(packets)
1940         for send_if in self.send_ifs:
1941             send_if.assert_nothing_captured()
1942
1943         self.logger.debug(self.vapi.ppcli("show trace"))
1944         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1945         self.logger.debug(self.vapi.ppcli("show buffers"))
1946         self.vapi.cli("clear trace")
1947
1948         self.pg_enable_capture()
1949         self.send_packets(first_packets)
1950         self.send_packets(second_packets)
1951         self.send_packets(rest_of_packets)
1952
1953         packets = self.dst_if.get_capture(len(self.pkt_infos))
1954         self.verify_capture(packets)
1955         for send_if in self.send_ifs:
1956             send_if.assert_nothing_captured()
1957
1958
1959 class TestIPv6SVReassembly(VppTestCase):
1960     """IPv6 Shallow Virtual Reassembly"""
1961
1962     @classmethod
1963     def setUpClass(cls):
1964         super().setUpClass()
1965
1966         cls.create_pg_interfaces([0, 1])
1967         cls.src_if = cls.pg0
1968         cls.dst_if = cls.pg1
1969
1970         # setup all interfaces
1971         for i in cls.pg_interfaces:
1972             i.admin_up()
1973             i.config_ip6()
1974             i.resolve_ndp()
1975
1976     def setUp(self):
1977         """Test setup - force timeout on existing reassemblies"""
1978         super().setUp()
1979         self.vapi.ip_reassembly_enable_disable(
1980             sw_if_index=self.src_if.sw_if_index,
1981             enable_ip6=True,
1982             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1983         )
1984         self.vapi.ip_reassembly_set(
1985             timeout_ms=0,
1986             max_reassemblies=1000,
1987             max_reassembly_length=1000,
1988             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1989             expire_walk_interval_ms=10,
1990             is_ip6=1,
1991         )
1992         self.virtual_sleep(0.25)
1993         self.vapi.ip_reassembly_set(
1994             timeout_ms=1000000,
1995             max_reassemblies=1000,
1996             max_reassembly_length=1000,
1997             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1998             expire_walk_interval_ms=10000,
1999             is_ip6=1,
2000         )
2001
2002     def tearDown(self):
2003         super().tearDown()
2004         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2005         self.logger.debug(self.vapi.ppcli("show buffers"))
2006
2007     def test_basic(self):
2008         """basic reassembly"""
2009         payload_len = 1000
2010         payload = ""
2011         counter = 0
2012         while len(payload) < payload_len:
2013             payload += "%u " % counter
2014             counter += 1
2015
2016         p = (
2017             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2018             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2019             / UDP(sport=1234, dport=5678)
2020             / Raw(payload)
2021         )
2022         fragments = fragment_rfc8200(p, 1, payload_len / 4)
2023
2024         # send fragment #2 - should be cached inside reassembly
2025         self.pg_enable_capture()
2026         self.src_if.add_stream(fragments[1])
2027         self.pg_start()
2028         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
2029         self.logger.debug(self.vapi.ppcli("show buffers"))
2030         self.logger.debug(self.vapi.ppcli("show trace"))
2031         self.dst_if.assert_nothing_captured()
2032
2033         # send fragment #1 - reassembly is finished now and both fragments
2034         # forwarded
2035         self.pg_enable_capture()
2036         self.src_if.add_stream(fragments[0])
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         c = self.dst_if.get_capture(2)
2042         for sent, recvd in zip([fragments[1], fragments[0]], c):
2043             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2044             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2045             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2046
2047         # send rest of fragments - should be immediately forwarded
2048         self.pg_enable_capture()
2049         self.src_if.add_stream(fragments[2:])
2050         self.pg_start()
2051         c = self.dst_if.get_capture(len(fragments[2:]))
2052         for sent, recvd in zip(fragments[2:], 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     def test_verify_clear_trace_mid_reassembly(self):
2058         """verify clear trace works mid-reassembly"""
2059         payload_len = 1000
2060         payload = ""
2061         counter = 0
2062         while len(payload) < payload_len:
2063             payload += "%u " % counter
2064             counter += 1
2065
2066         p = (
2067             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2068             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2069             / UDP(sport=1234, dport=5678)
2070             / Raw(payload)
2071         )
2072         fragments = fragment_rfc8200(p, 1, payload_len / 4)
2073
2074         self.pg_enable_capture()
2075         self.src_if.add_stream(fragments[1])
2076         self.pg_start()
2077
2078         self.logger.debug(self.vapi.cli("show trace"))
2079         self.vapi.cli("clear trace")
2080
2081         self.pg_enable_capture()
2082         self.src_if.add_stream(fragments[0])
2083         self.pg_start()
2084         self.dst_if.get_capture(2)
2085
2086         self.logger.debug(self.vapi.cli("show trace"))
2087         self.vapi.cli("clear trace")
2088
2089         self.pg_enable_capture()
2090         self.src_if.add_stream(fragments[2:])
2091         self.pg_start()
2092         self.dst_if.get_capture(len(fragments[2:]))
2093
2094     def test_timeout(self):
2095         """reassembly timeout"""
2096         payload_len = 1000
2097         payload = ""
2098         counter = 0
2099         while len(payload) < payload_len:
2100             payload += "%u " % counter
2101             counter += 1
2102
2103         p = (
2104             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2105             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2106             / UDP(sport=1234, dport=5678)
2107             / Raw(payload)
2108         )
2109         fragments = fragment_rfc8200(p, 1, payload_len / 4)
2110
2111         self.vapi.ip_reassembly_set(
2112             timeout_ms=100,
2113             max_reassemblies=1000,
2114             max_reassembly_length=1000,
2115             expire_walk_interval_ms=50,
2116             is_ip6=1,
2117             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2118         )
2119
2120         # send fragments #2 and #1 - should be forwarded
2121         self.pg_enable_capture()
2122         self.src_if.add_stream(fragments[0:2])
2123         self.pg_start()
2124         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
2125         self.logger.debug(self.vapi.ppcli("show buffers"))
2126         self.logger.debug(self.vapi.ppcli("show trace"))
2127         c = self.dst_if.get_capture(2)
2128         for sent, recvd in zip([fragments[1], fragments[0]], c):
2129             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2130             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2131             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2132
2133         # wait for cleanup
2134         self.virtual_sleep(0.25, "wait before sending rest of fragments")
2135
2136         # send rest of fragments - shouldn't be forwarded
2137         self.pg_enable_capture()
2138         self.src_if.add_stream(fragments[2:])
2139         self.pg_start()
2140         self.dst_if.assert_nothing_captured()
2141
2142     def test_lru(self):
2143         """reassembly reuses LRU element"""
2144
2145         self.vapi.ip_reassembly_set(
2146             timeout_ms=1000000,
2147             max_reassemblies=1,
2148             max_reassembly_length=1000,
2149             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
2150             is_ip6=1,
2151             expire_walk_interval_ms=10000,
2152         )
2153
2154         payload_len = 1000
2155         payload = ""
2156         counter = 0
2157         while len(payload) < payload_len:
2158             payload += "%u " % counter
2159             counter += 1
2160
2161         packet_count = 10
2162
2163         fragments = [
2164             f
2165             for i in range(packet_count)
2166             for p in (
2167                 Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2168                 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2169                 / UDP(sport=1234, dport=5678)
2170                 / Raw(payload)
2171             )
2172             for f in fragment_rfc8200(p, i, payload_len / 4)
2173         ]
2174
2175         self.pg_enable_capture()
2176         self.src_if.add_stream(fragments)
2177         self.pg_start()
2178         c = self.dst_if.get_capture(len(fragments))
2179         for sent, recvd in zip(fragments, c):
2180             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
2181             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
2182             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
2183
2184     def test_one_fragment(self):
2185         """whole packet in one fragment processed independently"""
2186         pkt = (
2187             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2188             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2189             / ICMPv6EchoRequest()
2190             / Raw("X" * 1600)
2191         )
2192         frags = fragment_rfc8200(pkt, 1, 400)
2193
2194         # send a fragment with known id
2195         self.send_and_expect(self.src_if, [frags[0]], self.dst_if)
2196
2197         # send an atomic fragment with same id - should be reassembled
2198         pkt = (
2199             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2200             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2201             / IPv6ExtHdrFragment(id=1)
2202             / ICMPv6EchoRequest()
2203         )
2204         rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2205
2206         # now forward packets matching original reassembly, should still work
2207         rx = self.send_and_expect(self.src_if, frags[1:], self.dst_if)
2208
2209     def test_bunch_of_fragments(self):
2210         """valid fragments followed by rogue fragments and atomic fragment"""
2211         pkt = (
2212             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2213             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2214             / ICMPv6EchoRequest()
2215             / Raw("X" * 1600)
2216         )
2217         frags = fragment_rfc8200(pkt, 1, 400)
2218         rx = self.send_and_expect(self.src_if, frags, self.dst_if)
2219
2220         rogue = (
2221             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2222             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2223             / IPv6ExtHdrFragment(id=1, nh=58, offset=608)
2224             / Raw("X" * 308)
2225         )
2226
2227         self.send_and_expect(self.src_if, rogue * 604, self.dst_if)
2228
2229         pkt = (
2230             Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac)
2231             / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2232             / IPv6ExtHdrFragment(id=1)
2233             / ICMPv6EchoRequest()
2234         )
2235         rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
2236
2237     def test_truncated_fragment(self):
2238         """truncated fragment"""
2239         pkt = (
2240             Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac)
2241             / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44, plen=2)
2242             / IPv6ExtHdrFragment(nh=6)
2243         )
2244
2245         self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
2246
2247
2248 class TestIPv4ReassemblyLocalNode(VppTestCase):
2249     """IPv4 Reassembly for packets coming to ip4-local node"""
2250
2251     @classmethod
2252     def setUpClass(cls):
2253         super().setUpClass()
2254
2255         cls.create_pg_interfaces([0])
2256         cls.src_dst_if = cls.pg0
2257
2258         # setup all interfaces
2259         for i in cls.pg_interfaces:
2260             i.admin_up()
2261             i.config_ip4()
2262             i.resolve_arp()
2263
2264         cls.padding = " abcdefghijklmn"
2265         cls.create_stream()
2266         cls.create_fragments()
2267
2268     @classmethod
2269     def tearDownClass(cls):
2270         super().tearDownClass()
2271
2272     def setUp(self):
2273         """Test setup - force timeout on existing reassemblies"""
2274         super().setUp()
2275         self.vapi.ip_reassembly_set(
2276             timeout_ms=0,
2277             max_reassemblies=1000,
2278             max_reassembly_length=1000,
2279             expire_walk_interval_ms=10,
2280         )
2281         self.virtual_sleep(0.25)
2282         self.vapi.ip_reassembly_set(
2283             timeout_ms=1000000,
2284             max_reassemblies=1000,
2285             max_reassembly_length=1000,
2286             expire_walk_interval_ms=10000,
2287         )
2288
2289     def tearDown(self):
2290         super().tearDown()
2291
2292     def show_commands_at_teardown(self):
2293         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2294         self.logger.debug(self.vapi.ppcli("show buffers"))
2295
2296     @classmethod
2297     def create_stream(cls, packet_count=test_packet_count):
2298         """Create input packet stream for defined interface.
2299
2300         :param list packet_sizes: Required packet sizes.
2301         """
2302         for i in range(0, packet_count):
2303             info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
2304             payload = cls.info_to_payload(info)
2305             p = (
2306                 Ether(dst=cls.src_dst_if.local_mac, src=cls.src_dst_if.remote_mac)
2307                 / IP(
2308                     id=info.index,
2309                     src=cls.src_dst_if.remote_ip4,
2310                     dst=cls.src_dst_if.local_ip4,
2311                 )
2312                 / ICMP(type="echo-request", id=1234)
2313                 / Raw(payload)
2314             )
2315             cls.extend_packet(p, 1518, cls.padding)
2316             info.data = p
2317
2318     @classmethod
2319     def create_fragments(cls):
2320         infos = cls._packet_infos
2321         cls.pkt_infos = []
2322         for index, info in infos.items():
2323             p = info.data
2324             # cls.logger.debug(ppp("Packet:",
2325             #                      p.__class__(scapy.compat.raw(p))))
2326             fragments_300 = fragment_rfc791(p, 300)
2327             cls.pkt_infos.append((index, fragments_300))
2328         cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
2329         cls.logger.debug(
2330             "Fragmented %s packets into %s 300-byte fragments"
2331             % (len(infos), len(cls.fragments_300))
2332         )
2333
2334     def verify_capture(self, capture):
2335         """Verify captured packet stream.
2336
2337         :param list capture: Captured packet stream.
2338         """
2339         info = None
2340         seen = set()
2341         for packet in capture:
2342             try:
2343                 self.logger.debug(ppp("Got packet:", packet))
2344                 ip = packet[IP]
2345                 icmp = packet[ICMP]
2346                 payload_info = self.payload_to_info(packet[Raw])
2347                 packet_index = payload_info.index
2348                 if packet_index in seen:
2349                     raise Exception(ppp("Duplicate packet received", packet))
2350                 seen.add(packet_index)
2351                 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
2352                 info = self._packet_infos[packet_index]
2353                 self.assertIsNotNone(info)
2354                 self.assertEqual(packet_index, info.index)
2355                 saved_packet = info.data
2356                 self.assertEqual(ip.src, saved_packet[IP].dst)
2357                 self.assertEqual(ip.dst, saved_packet[IP].src)
2358                 self.assertEqual(icmp.type, 0)  # echo reply
2359                 self.assertEqual(icmp.id, saved_packet[ICMP].id)
2360                 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
2361             except Exception:
2362                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2363                 raise
2364         for index in self._packet_infos:
2365             self.assertIn(
2366                 index, seen, "Packet with packet_index %d not received" % index
2367             )
2368
2369     def test_reassembly(self):
2370         """basic reassembly"""
2371
2372         self.pg_enable_capture()
2373         self.src_dst_if.add_stream(self.fragments_300)
2374         self.pg_start()
2375
2376         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2377         self.verify_capture(packets)
2378
2379         # run it all again to verify correctness
2380         self.pg_enable_capture()
2381         self.src_dst_if.add_stream(self.fragments_300)
2382         self.pg_start()
2383
2384         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2385         self.verify_capture(packets)
2386
2387
2388 class TestFIFReassembly(VppTestCase):
2389     """Fragments in fragments reassembly"""
2390
2391     @classmethod
2392     def setUpClass(cls):
2393         super().setUpClass()
2394
2395         cls.create_pg_interfaces([0, 1])
2396         cls.src_if = cls.pg0
2397         cls.dst_if = cls.pg1
2398         for i in cls.pg_interfaces:
2399             i.admin_up()
2400             i.config_ip4()
2401             i.resolve_arp()
2402             i.config_ip6()
2403             i.resolve_ndp()
2404
2405         cls.packet_sizes = [64, 512, 1518, 9018]
2406         cls.padding = " abcdefghijklmn"
2407
2408     @classmethod
2409     def tearDownClass(cls):
2410         super().tearDownClass()
2411
2412     def setUp(self):
2413         """Test setup - force timeout on existing reassemblies"""
2414         super().setUp()
2415         self.vapi.ip_reassembly_enable_disable(
2416             sw_if_index=self.src_if.sw_if_index, enable_ip4=True, enable_ip6=True
2417         )
2418         self.vapi.ip_reassembly_enable_disable(
2419             sw_if_index=self.dst_if.sw_if_index, enable_ip4=True, enable_ip6=True
2420         )
2421         self.vapi.ip_reassembly_set(
2422             timeout_ms=0,
2423             max_reassemblies=1000,
2424             max_reassembly_length=1000,
2425             expire_walk_interval_ms=10,
2426         )
2427         self.vapi.ip_reassembly_set(
2428             timeout_ms=0,
2429             max_reassemblies=1000,
2430             max_reassembly_length=1000,
2431             expire_walk_interval_ms=10,
2432             is_ip6=1,
2433         )
2434         self.virtual_sleep(0.25)
2435         self.vapi.ip_reassembly_set(
2436             timeout_ms=1000000,
2437             max_reassemblies=1000,
2438             max_reassembly_length=1000,
2439             expire_walk_interval_ms=10000,
2440         )
2441         self.vapi.ip_reassembly_set(
2442             timeout_ms=1000000,
2443             max_reassemblies=1000,
2444             max_reassembly_length=1000,
2445             expire_walk_interval_ms=10000,
2446             is_ip6=1,
2447         )
2448
2449     def tearDown(self):
2450         super().tearDown()
2451
2452     def show_commands_at_teardown(self):
2453         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2454         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
2455         self.logger.debug(self.vapi.ppcli("show buffers"))
2456
2457     def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
2458         """Verify captured packet stream.
2459
2460         :param list capture: Captured packet stream.
2461         """
2462         info = None
2463         seen = set()
2464         for packet in capture:
2465             try:
2466                 self.logger.debug(ppp("Got packet:", packet))
2467                 ip = packet[ip_class]
2468                 udp = packet[UDP]
2469                 payload_info = self.payload_to_info(packet[Raw])
2470                 packet_index = payload_info.index
2471                 self.assertTrue(
2472                     packet_index not in dropped_packet_indexes,
2473                     ppp("Packet received, but should be dropped:", packet),
2474                 )
2475                 if packet_index in seen:
2476                     raise Exception(ppp("Duplicate packet received", packet))
2477                 seen.add(packet_index)
2478                 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
2479                 info = self._packet_infos[packet_index]
2480                 self.assertTrue(info is not None)
2481                 self.assertEqual(packet_index, info.index)
2482                 saved_packet = info.data
2483                 self.assertEqual(ip.src, saved_packet[ip_class].src)
2484                 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
2485                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
2486             except Exception:
2487                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2488                 raise
2489         for index in self._packet_infos:
2490             self.assertTrue(
2491                 index in seen or index in dropped_packet_indexes,
2492                 "Packet with packet_index %d not received" % index,
2493             )
2494
2495     def test_fif4(self):
2496         """Fragments in fragments (4o4)"""
2497
2498         # TODO this should be ideally in setUpClass, but then we hit a bug
2499         # with VppIpRoute incorrectly reporting it's present when it's not
2500         # so we need to manually remove the vpp config, thus we cannot have
2501         # it shared for multiple test cases
2502         self.tun_ip4 = "1.1.1.2"
2503
2504         self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
2505         self.gre4.add_vpp_config()
2506         self.gre4.admin_up()
2507         self.gre4.config_ip4()
2508
2509         self.vapi.ip_reassembly_enable_disable(
2510             sw_if_index=self.gre4.sw_if_index, enable_ip4=True
2511         )
2512
2513         self.route4 = VppIpRoute(
2514             self,
2515             self.tun_ip4,
2516             32,
2517             [VppRoutePath(self.src_if.remote_ip4, self.src_if.sw_if_index)],
2518         )
2519         self.route4.add_vpp_config()
2520
2521         self.reset_packet_infos()
2522         for i in range(test_packet_count):
2523             info = self.create_packet_info(self.src_if, self.dst_if)
2524             payload = self.info_to_payload(info)
2525             # Ethernet header here is only for size calculation, thus it
2526             # doesn't matter how it's initialized. This is to ensure that
2527             # reassembled packet is not > 9000 bytes, so that it's not dropped
2528             p = (
2529                 Ether()
2530                 / IP(id=i, src=self.src_if.remote_ip4, dst=self.dst_if.remote_ip4)
2531                 / UDP(sport=1234, dport=5678)
2532                 / Raw(payload)
2533             )
2534             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2535             self.extend_packet(p, size, self.padding)
2536             info.data = p[IP]  # use only IP part, without ethernet header
2537
2538         fragments = [
2539             x
2540             for _, p in self._packet_infos.items()
2541             for x in fragment_rfc791(p.data, 400)
2542         ]
2543
2544         encapped_fragments = [
2545             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2546             / IP(src=self.tun_ip4, dst=self.src_if.local_ip4)
2547             / GRE()
2548             / p
2549             for p in fragments
2550         ]
2551
2552         fragmented_encapped_fragments = [
2553             x for p in encapped_fragments for x in fragment_rfc791(p, 200)
2554         ]
2555
2556         self.src_if.add_stream(fragmented_encapped_fragments)
2557
2558         self.pg_enable_capture(self.pg_interfaces)
2559         self.pg_start()
2560
2561         self.src_if.assert_nothing_captured()
2562         packets = self.dst_if.get_capture(len(self._packet_infos))
2563         self.verify_capture(packets, IP)
2564
2565         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2566         # so that it's query_vpp_config() works as it should
2567         self.gre4.remove_vpp_config()
2568         self.logger.debug(self.vapi.ppcli("show interface"))
2569
2570     def test_fif6(self):
2571         """Fragments in fragments (6o6)"""
2572         # TODO this should be ideally in setUpClass, but then we hit a bug
2573         # with VppIpRoute incorrectly reporting it's present when it's not
2574         # so we need to manually remove the vpp config, thus we cannot have
2575         # it shared for multiple test cases
2576         self.tun_ip6 = "1002::1"
2577
2578         self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
2579         self.gre6.add_vpp_config()
2580         self.gre6.admin_up()
2581         self.gre6.config_ip6()
2582
2583         self.vapi.ip_reassembly_enable_disable(
2584             sw_if_index=self.gre6.sw_if_index, enable_ip6=True
2585         )
2586
2587         self.route6 = VppIpRoute(
2588             self,
2589             self.tun_ip6,
2590             128,
2591             [VppRoutePath(self.src_if.remote_ip6, self.src_if.sw_if_index)],
2592         )
2593         self.route6.add_vpp_config()
2594
2595         self.reset_packet_infos()
2596         for i in range(test_packet_count):
2597             info = self.create_packet_info(self.src_if, self.dst_if)
2598             payload = self.info_to_payload(info)
2599             # Ethernet header here is only for size calculation, thus it
2600             # doesn't matter how it's initialized. This is to ensure that
2601             # reassembled packet is not > 9000 bytes, so that it's not dropped
2602             p = (
2603                 Ether()
2604                 / IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6)
2605                 / UDP(sport=1234, dport=5678)
2606                 / Raw(payload)
2607             )
2608             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2609             self.extend_packet(p, size, self.padding)
2610             info.data = p[IPv6]  # use only IPv6 part, without ethernet header
2611
2612         fragments = [
2613             x
2614             for _, i in self._packet_infos.items()
2615             for x in fragment_rfc8200(i.data, i.index, 400)
2616         ]
2617
2618         encapped_fragments = [
2619             Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac)
2620             / IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6)
2621             / GRE()
2622             / p
2623             for p in fragments
2624         ]
2625
2626         fragmented_encapped_fragments = [
2627             x
2628             for p in encapped_fragments
2629             for x in (
2630                 fragment_rfc8200(
2631                     p, 2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id, 200
2632                 )
2633                 if IPv6ExtHdrFragment in p
2634                 else [p]
2635             )
2636         ]
2637
2638         self.src_if.add_stream(fragmented_encapped_fragments)
2639
2640         self.pg_enable_capture(self.pg_interfaces)
2641         self.pg_start()
2642
2643         self.src_if.assert_nothing_captured()
2644         packets = self.dst_if.get_capture(len(self._packet_infos))
2645         self.verify_capture(packets, IPv6)
2646
2647         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2648         # so that it's query_vpp_config() works as it should
2649         self.gre6.remove_vpp_config()
2650
2651
2652 if __name__ == "__main__":
2653     unittest.main(testRunner=VppTestRunner)