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