ip: rate-limit the sending of ICMP error messages
[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(1)
1330         for icmp in pkts:
1331             self.assertIn(ICMPv6TimeExceeded, icmp)
1332             self.assertIn(IPv6ExtHdrFragment, icmp)
1333             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1334             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1335
1336     def test_timeout_cleanup(self):
1337         """ timeout (cleanup) """
1338
1339         # whole packets + fragmented packets sans last fragment
1340         fragments = [
1341             x for (_, frags_400, _) in self.pkt_infos
1342             for x in frags_400[:-1 if len(frags_400) > 1 else None]
1343         ]
1344
1345         # last fragments for fragmented packets
1346         fragments2 = [frags_400[-1]
1347                       for (_, frags_400, _) in self.pkt_infos
1348                       if len(frags_400) > 1]
1349
1350         dropped_packet_indexes = set(
1351             index for (index, frags_400, _) in self.pkt_infos
1352             if len(frags_400) > 1)
1353
1354         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1355                                     max_reassembly_length=1000,
1356                                     expire_walk_interval_ms=50)
1357
1358         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1359                                     max_reassembly_length=1000,
1360                                     expire_walk_interval_ms=50, is_ip6=1)
1361
1362         self.pg_enable_capture()
1363         self.src_if.add_stream(fragments)
1364         self.pg_start()
1365
1366         self.virtual_sleep(.25, "wait before sending rest of fragments")
1367
1368         self.src_if.add_stream(fragments2)
1369         self.pg_start()
1370
1371         packets = self.dst_if.get_capture(
1372             len(self.pkt_infos) - len(dropped_packet_indexes))
1373         self.verify_capture(packets, dropped_packet_indexes)
1374         pkts = self.src_if._get_capture(1)
1375         for icmp in pkts:
1376             self.assertIn(ICMPv6TimeExceeded, icmp)
1377             self.assertIn(IPv6ExtHdrFragment, icmp)
1378             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1379             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1380
1381     def test_disabled(self):
1382         """ reassembly disabled """
1383
1384         dropped_packet_indexes = set(
1385             index for (index, frags_400, _) in self.pkt_infos
1386             if len(frags_400) > 1)
1387
1388         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
1389                                     max_reassembly_length=3,
1390                                     expire_walk_interval_ms=10000, is_ip6=1)
1391
1392         self.pg_enable_capture()
1393         self.src_if.add_stream(self.fragments_400)
1394         self.pg_start()
1395
1396         packets = self.dst_if.get_capture(
1397             len(self.pkt_infos) - len(dropped_packet_indexes))
1398         self.verify_capture(packets, dropped_packet_indexes)
1399         self.src_if.assert_nothing_captured()
1400
1401     def test_missing_upper(self):
1402         """ missing upper layer """
1403         optdata = '\x00' * 100
1404         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1405              IPv6(src=self.src_if.remote_ip6,
1406                   dst=self.src_if.local_ip6) /
1407              IPv6ExtHdrFragment(m=1) /
1408              IPv6ExtHdrDestOpt(nh=17, options=PadN(optdata='\101' * 255) /
1409              PadN(optdata='\102'*255)))
1410
1411         self.pg_enable_capture()
1412         self.src_if.add_stream([p])
1413         self.pg_start()
1414         pkts = self.src_if.get_capture(expected_count=1)
1415         icmp = pkts[0]
1416         self.assertIn(ICMPv6ParamProblem, icmp)
1417         self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
1418
1419     def test_truncated_fragment(self):
1420         """ truncated fragment """
1421         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1422                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6,
1423                     nh=44, plen=2) /
1424                IPv6ExtHdrFragment(nh=6))
1425
1426         self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
1427
1428     def test_invalid_frag_size(self):
1429         """ fragment size not a multiple of 8 """
1430         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1431              IPv6(src=self.src_if.remote_ip6,
1432                   dst=self.src_if.local_ip6) /
1433              UDP(sport=1234, dport=5678) /
1434              Raw())
1435         self.extend_packet(p, 1000, self.padding)
1436         fragments = fragment_rfc8200(p, 1, 500)
1437         bad_fragment = fragments[0]
1438         self.extend_packet(bad_fragment, len(bad_fragment) + 5)
1439         self.pg_enable_capture()
1440         self.src_if.add_stream([bad_fragment])
1441         self.pg_start()
1442         pkts = self.src_if.get_capture(expected_count=1)
1443         icmp = pkts[0]
1444         self.assertIn(ICMPv6ParamProblem, icmp)
1445         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1446
1447     def test_invalid_packet_size(self):
1448         """ total packet size > 65535 """
1449         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1450              IPv6(src=self.src_if.remote_ip6,
1451                   dst=self.src_if.local_ip6) /
1452              UDP(sport=1234, dport=5678) /
1453              Raw())
1454         self.extend_packet(p, 1000, self.padding)
1455         fragments = fragment_rfc8200(p, 1, 500)
1456         bad_fragment = fragments[1]
1457         bad_fragment[IPv6ExtHdrFragment].offset = 65500
1458         self.pg_enable_capture()
1459         self.src_if.add_stream([bad_fragment])
1460         self.pg_start()
1461         pkts = self.src_if.get_capture(expected_count=1)
1462         icmp = pkts[0]
1463         self.assertIn(ICMPv6ParamProblem, icmp)
1464         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1465
1466     def test_atomic_fragment(self):
1467         """ IPv6 atomic fragment """
1468         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1469                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6,
1470                     nh=44, plen=65535) /
1471                IPv6ExtHdrFragment(offset=8191, m=1, res1=0xFF, res2=0xFF,
1472                                   nh=255, id=0xffff)/('X'*1452))
1473
1474         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1475         self.assertIn(ICMPv6ParamProblem, rx[0])
1476
1477     def test_truncated_fragment(self):
1478         """ IPv6 truncated fragment header """
1479         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1480                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6,
1481                     nh=44, plen=2) /
1482                IPv6ExtHdrFragment(nh=6))
1483
1484         self.send_and_assert_no_replies(self.pg0, [pkt])
1485
1486         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1487                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.remote_ip6) /
1488                ICMPv6EchoRequest())
1489         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1490
1491     def test_one_fragment(self):
1492         """ whole packet in one fragment processed independently """
1493         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1494                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
1495                ICMPv6EchoRequest()/Raw('X' * 1600))
1496         frags = fragment_rfc8200(pkt, 1, 400)
1497
1498         # send a fragment with known id
1499         self.send_and_assert_no_replies(self.pg0, [frags[0]])
1500
1501         # send an atomic fragment with same id - should be reassembled
1502         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1503                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
1504                IPv6ExtHdrFragment(id=1)/ICMPv6EchoRequest())
1505         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1506         self.assertNotIn(IPv6ExtHdrFragment, rx)
1507
1508         # now finish the original reassembly, this should still be possible
1509         rx = self.send_and_expect(self.pg0, frags[1:], self.pg0, n_rx=1)
1510         self.assertNotIn(IPv6ExtHdrFragment, rx)
1511
1512     def test_bunch_of_fragments(self):
1513         """ valid fragments followed by rogue fragments and atomic fragment"""
1514         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1515                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
1516                ICMPv6EchoRequest()/Raw('X' * 1600))
1517         frags = fragment_rfc8200(pkt, 1, 400)
1518         self.send_and_expect(self.pg0, frags, self.pg0, n_rx=1)
1519
1520         inc_frag = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1521                     IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
1522                     IPv6ExtHdrFragment(id=1, nh=58, offset=608)/Raw('X'*308))
1523
1524         self.send_and_assert_no_replies(self.pg0, inc_frag*604)
1525
1526         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1527                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
1528                IPv6ExtHdrFragment(id=1)/ICMPv6EchoRequest())
1529         rx = self.send_and_expect(self.pg0, [pkt], self.pg0)
1530         self.assertNotIn(IPv6ExtHdrFragment, rx)
1531
1532     def test_local_enable_disable(self):
1533         """ local reassembly enabled/disable """
1534         self.vapi.ip_reassembly_enable_disable(
1535             sw_if_index=self.src_if.sw_if_index, enable_ip6=False)
1536         self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1537         pkt = (Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac) /
1538                IPv6(src=self.src_if.remote_ip6, dst=self.src_if.local_ip6) /
1539                ICMPv6EchoRequest(id=1234)/Raw('X' * 1600))
1540         frags = fragment_rfc8200(pkt, 1, 400)
1541         r = self.send_and_expect(self.src_if, frags, self.src_if, n_rx=1)[0]
1542         self.assertEqual(1234, r[ICMPv6EchoReply].id)
1543         self.vapi.ip_local_reass_enable_disable()
1544
1545         self.send_and_assert_no_replies(self.src_if, frags)
1546         self.vapi.ip_local_reass_enable_disable(enable_ip6=True)
1547
1548
1549 class TestIPv6MWReassembly(VppTestCase):
1550     """ IPv6 Reassembly (multiple workers) """
1551     vpp_worker_count = 3
1552
1553     @classmethod
1554     def setUpClass(cls):
1555         super().setUpClass()
1556
1557         cls.create_pg_interfaces(range(cls.vpp_worker_count+1))
1558         cls.src_if = cls.pg0
1559         cls.send_ifs = cls.pg_interfaces[:-1]
1560         cls.dst_if = cls.pg_interfaces[-1]
1561
1562         # setup all interfaces
1563         for i in cls.pg_interfaces:
1564             i.admin_up()
1565             i.config_ip6()
1566             i.resolve_ndp()
1567
1568         # packets sizes reduced here because we are generating packets without
1569         # Ethernet headers, which are added later (diff fragments go via
1570         # different interfaces)
1571         cls.packet_sizes = [64-len(Ether()), 512-len(Ether()),
1572                             1518-len(Ether()), 9018-len(Ether())]
1573         cls.padding = " abcdefghijklmn"
1574         cls.create_stream(cls.packet_sizes)
1575         cls.create_fragments()
1576
1577     @classmethod
1578     def tearDownClass(cls):
1579         super().tearDownClass()
1580
1581     def setUp(self):
1582         """ Test setup - force timeout on existing reassemblies """
1583         super().setUp()
1584         for intf in self.send_ifs:
1585             self.vapi.ip_reassembly_enable_disable(
1586                 sw_if_index=intf.sw_if_index, enable_ip6=True)
1587         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1588                                     max_reassembly_length=1000,
1589                                     expire_walk_interval_ms=10, is_ip6=1)
1590         self.virtual_sleep(.25)
1591         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1592                                     max_reassembly_length=1000,
1593                                     expire_walk_interval_ms=1000, is_ip6=1)
1594
1595     def tearDown(self):
1596         for intf in self.send_ifs:
1597             self.vapi.ip_reassembly_enable_disable(
1598                 sw_if_index=intf.sw_if_index, enable_ip6=False)
1599         super().tearDown()
1600
1601     def show_commands_at_teardown(self):
1602         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1603         self.logger.debug(self.vapi.ppcli("show buffers"))
1604
1605     @classmethod
1606     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1607         """Create input packet stream
1608
1609         :param list packet_sizes: Required packet sizes.
1610         """
1611         for i in range(0, packet_count):
1612             info = cls.create_packet_info(cls.src_if, cls.src_if)
1613             payload = cls.info_to_payload(info)
1614             p = (IPv6(src=cls.src_if.remote_ip6,
1615                       dst=cls.dst_if.remote_ip6) /
1616                  UDP(sport=1234, dport=5678) /
1617                  Raw(payload))
1618             size = packet_sizes[(i // 2) % len(packet_sizes)]
1619             cls.extend_packet(p, size, cls.padding)
1620             info.data = p
1621
1622     @classmethod
1623     def create_fragments(cls):
1624         infos = cls._packet_infos
1625         cls.pkt_infos = []
1626         for index, info in infos.items():
1627             p = info.data
1628             # cls.logger.debug(ppp("Packet:",
1629             #                      p.__class__(scapy.compat.raw(p))))
1630             fragments_400 = fragment_rfc8200(p, index, 400)
1631             cls.pkt_infos.append((index, fragments_400))
1632         cls.fragments_400 = [
1633             x for (_, frags) in cls.pkt_infos for x in frags]
1634         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " %
1635                          (len(infos), len(cls.fragments_400)))
1636
1637     def verify_capture(self, capture, dropped_packet_indexes=[]):
1638         """Verify captured packet strea .
1639
1640         :param list capture: Captured packet stream.
1641         """
1642         info = None
1643         seen = set()
1644         for packet in capture:
1645             try:
1646                 self.logger.debug(ppp("Got packet:", packet))
1647                 ip = packet[IPv6]
1648                 udp = packet[UDP]
1649                 payload_info = self.payload_to_info(packet[Raw])
1650                 packet_index = payload_info.index
1651                 self.assertTrue(
1652                     packet_index not in dropped_packet_indexes,
1653                     ppp("Packet received, but should be dropped:", packet))
1654                 if packet_index in seen:
1655                     raise Exception(ppp("Duplicate packet received", packet))
1656                 seen.add(packet_index)
1657                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1658                 info = self._packet_infos[packet_index]
1659                 self.assertTrue(info is not None)
1660                 self.assertEqual(packet_index, info.index)
1661                 saved_packet = info.data
1662                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1663                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1664                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1665             except Exception:
1666                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1667                 raise
1668         for index in self._packet_infos:
1669             self.assertTrue(index in seen or index in dropped_packet_indexes,
1670                             "Packet with packet_index %d not received" % index)
1671
1672     def send_packets(self, packets):
1673         for counter in range(self.vpp_worker_count):
1674             if 0 == len(packets[counter]):
1675                 continue
1676             send_if = self.send_ifs[counter]
1677             send_if.add_stream(
1678                 (Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1679                  for x in packets[counter]),
1680                 worker=counter)
1681         self.pg_start()
1682
1683     def test_worker_conflict(self):
1684         """ 1st and FO=0 fragments on different workers """
1685
1686         # in first wave we send fragments which don't start at offset 0
1687         # then we send fragments with offset 0 on a different thread
1688         # then the rest of packets on a random thread
1689         first_packets = [[] for n in range(self.vpp_worker_count)]
1690         second_packets = [[] for n in range(self.vpp_worker_count)]
1691         rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1692         for (_, p) in self.pkt_infos:
1693             wi = randrange(self.vpp_worker_count)
1694             second_packets[wi].append(p[0])
1695             if len(p) <= 1:
1696                 continue
1697             wi2 = wi
1698             while wi2 == wi:
1699                 wi2 = randrange(self.vpp_worker_count)
1700             first_packets[wi2].append(p[1])
1701             wi3 = randrange(self.vpp_worker_count)
1702             rest_of_packets[wi3].extend(p[2:])
1703
1704         self.pg_enable_capture()
1705         self.send_packets(first_packets)
1706         self.send_packets(second_packets)
1707         self.send_packets(rest_of_packets)
1708
1709         packets = self.dst_if.get_capture(len(self.pkt_infos))
1710         self.verify_capture(packets)
1711         for send_if in self.send_ifs:
1712             send_if.assert_nothing_captured()
1713
1714         self.logger.debug(self.vapi.ppcli("show trace"))
1715         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1716         self.logger.debug(self.vapi.ppcli("show buffers"))
1717         self.vapi.cli("clear trace")
1718
1719         self.pg_enable_capture()
1720         self.send_packets(first_packets)
1721         self.send_packets(second_packets)
1722         self.send_packets(rest_of_packets)
1723
1724         packets = self.dst_if.get_capture(len(self.pkt_infos))
1725         self.verify_capture(packets)
1726         for send_if in self.send_ifs:
1727             send_if.assert_nothing_captured()
1728
1729
1730 class TestIPv6SVReassembly(VppTestCase):
1731     """ IPv6 Shallow Virtual Reassembly """
1732
1733     @classmethod
1734     def setUpClass(cls):
1735         super().setUpClass()
1736
1737         cls.create_pg_interfaces([0, 1])
1738         cls.src_if = cls.pg0
1739         cls.dst_if = cls.pg1
1740
1741         # setup all interfaces
1742         for i in cls.pg_interfaces:
1743             i.admin_up()
1744             i.config_ip6()
1745             i.resolve_ndp()
1746
1747     def setUp(self):
1748         """ Test setup - force timeout on existing reassemblies """
1749         super().setUp()
1750         self.vapi.ip_reassembly_enable_disable(
1751             sw_if_index=self.src_if.sw_if_index, enable_ip6=True,
1752             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
1753         self.vapi.ip_reassembly_set(
1754             timeout_ms=0, max_reassemblies=1000,
1755             max_reassembly_length=1000,
1756             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1757             expire_walk_interval_ms=10, is_ip6=1)
1758         self.virtual_sleep(.25)
1759         self.vapi.ip_reassembly_set(
1760             timeout_ms=1000000, max_reassemblies=1000,
1761             max_reassembly_length=1000,
1762             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1763             expire_walk_interval_ms=10000, is_ip6=1)
1764
1765     def tearDown(self):
1766         super().tearDown()
1767         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1768         self.logger.debug(self.vapi.ppcli("show buffers"))
1769
1770     def test_basic(self):
1771         """ basic reassembly """
1772         payload_len = 1000
1773         payload = ""
1774         counter = 0
1775         while len(payload) < payload_len:
1776             payload += "%u " % counter
1777             counter += 1
1778
1779         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1780              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1781              UDP(sport=1234, dport=5678) /
1782              Raw(payload))
1783         fragments = fragment_rfc8200(p, 1, payload_len/4)
1784
1785         # send fragment #2 - should be cached inside reassembly
1786         self.pg_enable_capture()
1787         self.src_if.add_stream(fragments[1])
1788         self.pg_start()
1789         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1790         self.logger.debug(self.vapi.ppcli("show buffers"))
1791         self.logger.debug(self.vapi.ppcli("show trace"))
1792         self.dst_if.assert_nothing_captured()
1793
1794         # send fragment #1 - reassembly is finished now and both fragments
1795         # forwarded
1796         self.pg_enable_capture()
1797         self.src_if.add_stream(fragments[0])
1798         self.pg_start()
1799         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1800         self.logger.debug(self.vapi.ppcli("show buffers"))
1801         self.logger.debug(self.vapi.ppcli("show trace"))
1802         c = self.dst_if.get_capture(2)
1803         for sent, recvd in zip([fragments[1], fragments[0]], c):
1804             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1805             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1806             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1807
1808         # send rest of fragments - should be immediately forwarded
1809         self.pg_enable_capture()
1810         self.src_if.add_stream(fragments[2:])
1811         self.pg_start()
1812         c = self.dst_if.get_capture(len(fragments[2:]))
1813         for sent, recvd in zip(fragments[2:], c):
1814             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1815             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1816             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1817
1818     def test_verify_clear_trace_mid_reassembly(self):
1819         """ verify clear trace works mid-reassembly """
1820         payload_len = 1000
1821         payload = ""
1822         counter = 0
1823         while len(payload) < payload_len:
1824             payload += "%u " % counter
1825             counter += 1
1826
1827         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1828              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1829              UDP(sport=1234, dport=5678) /
1830              Raw(payload))
1831         fragments = fragment_rfc8200(p, 1, payload_len/4)
1832
1833         self.pg_enable_capture()
1834         self.src_if.add_stream(fragments[1])
1835         self.pg_start()
1836
1837         self.logger.debug(self.vapi.cli("show trace"))
1838         self.vapi.cli("clear trace")
1839
1840         self.pg_enable_capture()
1841         self.src_if.add_stream(fragments[0])
1842         self.pg_start()
1843         self.dst_if.get_capture(2)
1844
1845         self.logger.debug(self.vapi.cli("show trace"))
1846         self.vapi.cli("clear trace")
1847
1848         self.pg_enable_capture()
1849         self.src_if.add_stream(fragments[2:])
1850         self.pg_start()
1851         self.dst_if.get_capture(len(fragments[2:]))
1852
1853     def test_timeout(self):
1854         """ reassembly timeout """
1855         payload_len = 1000
1856         payload = ""
1857         counter = 0
1858         while len(payload) < payload_len:
1859             payload += "%u " % counter
1860             counter += 1
1861
1862         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1863              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1864              UDP(sport=1234, dport=5678) /
1865              Raw(payload))
1866         fragments = fragment_rfc8200(p, 1, payload_len/4)
1867
1868         self.vapi.ip_reassembly_set(
1869             timeout_ms=100, max_reassemblies=1000,
1870             max_reassembly_length=1000,
1871             expire_walk_interval_ms=50,
1872             is_ip6=1,
1873             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
1874
1875         # send fragments #2 and #1 - should be forwarded
1876         self.pg_enable_capture()
1877         self.src_if.add_stream(fragments[0:2])
1878         self.pg_start()
1879         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
1880         self.logger.debug(self.vapi.ppcli("show buffers"))
1881         self.logger.debug(self.vapi.ppcli("show trace"))
1882         c = self.dst_if.get_capture(2)
1883         for sent, recvd in zip([fragments[1], fragments[0]], c):
1884             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1885             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1886             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1887
1888         # wait for cleanup
1889         self.virtual_sleep(.25, "wait before sending rest of fragments")
1890
1891         # send rest of fragments - shouldn't be forwarded
1892         self.pg_enable_capture()
1893         self.src_if.add_stream(fragments[2:])
1894         self.pg_start()
1895         self.dst_if.assert_nothing_captured()
1896
1897     def test_lru(self):
1898         """ reassembly reuses LRU element """
1899
1900         self.vapi.ip_reassembly_set(
1901             timeout_ms=1000000, max_reassemblies=1,
1902             max_reassembly_length=1000,
1903             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1904             is_ip6=1, expire_walk_interval_ms=10000)
1905
1906         payload_len = 1000
1907         payload = ""
1908         counter = 0
1909         while len(payload) < payload_len:
1910             payload += "%u " % counter
1911             counter += 1
1912
1913         packet_count = 10
1914
1915         fragments = [f
1916                      for i in range(packet_count)
1917                      for p in (Ether(dst=self.src_if.local_mac,
1918                                      src=self.src_if.remote_mac) /
1919                                IPv6(src=self.src_if.remote_ip6,
1920                                     dst=self.dst_if.remote_ip6) /
1921                                UDP(sport=1234, dport=5678) /
1922                                Raw(payload))
1923                      for f in fragment_rfc8200(p, i, payload_len/4)]
1924
1925         self.pg_enable_capture()
1926         self.src_if.add_stream(fragments)
1927         self.pg_start()
1928         c = self.dst_if.get_capture(len(fragments))
1929         for sent, recvd in zip(fragments, c):
1930             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1931             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1932             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1933
1934     def test_one_fragment(self):
1935         """ whole packet in one fragment processed independently """
1936         pkt = (Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac) /
1937                IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1938                ICMPv6EchoRequest()/Raw('X' * 1600))
1939         frags = fragment_rfc8200(pkt, 1, 400)
1940
1941         # send a fragment with known id
1942         self.send_and_expect(self.src_if, [frags[0]], self.dst_if)
1943
1944         # send an atomic fragment with same id - should be reassembled
1945         pkt = (Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac) /
1946                IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1947                IPv6ExtHdrFragment(id=1)/ICMPv6EchoRequest())
1948         rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
1949
1950         # now forward packets matching original reassembly, should still work
1951         rx = self.send_and_expect(self.src_if, frags[1:], self.dst_if)
1952
1953     def test_bunch_of_fragments(self):
1954         """ valid fragments followed by rogue fragments and atomic fragment"""
1955         pkt = (Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac) /
1956                IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1957                ICMPv6EchoRequest()/Raw('X' * 1600))
1958         frags = fragment_rfc8200(pkt, 1, 400)
1959         rx = self.send_and_expect(self.src_if, frags, self.dst_if)
1960
1961         rogue = (Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac) /
1962                  IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1963                  IPv6ExtHdrFragment(id=1, nh=58, offset=608)/Raw('X'*308))
1964
1965         self.send_and_expect(self.src_if, rogue*604, self.dst_if)
1966
1967         pkt = (Ether(src=self.src_if.local_mac, dst=self.src_if.remote_mac) /
1968                IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1969                IPv6ExtHdrFragment(id=1)/ICMPv6EchoRequest())
1970         rx = self.send_and_expect(self.src_if, [pkt], self.dst_if)
1971
1972     def test_truncated_fragment(self):
1973         """ truncated fragment """
1974         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
1975                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6,
1976                     nh=44, plen=2) /
1977                IPv6ExtHdrFragment(nh=6))
1978
1979         self.send_and_assert_no_replies(self.pg0, [pkt], self.pg0)
1980
1981
1982 class TestIPv4ReassemblyLocalNode(VppTestCase):
1983     """ IPv4 Reassembly for packets coming to ip4-local node """
1984
1985     @classmethod
1986     def setUpClass(cls):
1987         super().setUpClass()
1988
1989         cls.create_pg_interfaces([0])
1990         cls.src_dst_if = cls.pg0
1991
1992         # setup all interfaces
1993         for i in cls.pg_interfaces:
1994             i.admin_up()
1995             i.config_ip4()
1996             i.resolve_arp()
1997
1998         cls.padding = " abcdefghijklmn"
1999         cls.create_stream()
2000         cls.create_fragments()
2001
2002     @classmethod
2003     def tearDownClass(cls):
2004         super().tearDownClass()
2005
2006     def setUp(self):
2007         """ Test setup - force timeout on existing reassemblies """
2008         super().setUp()
2009         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
2010                                     max_reassembly_length=1000,
2011                                     expire_walk_interval_ms=10)
2012         self.virtual_sleep(.25)
2013         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
2014                                     max_reassembly_length=1000,
2015                                     expire_walk_interval_ms=10000)
2016
2017     def tearDown(self):
2018         super().tearDown()
2019
2020     def show_commands_at_teardown(self):
2021         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2022         self.logger.debug(self.vapi.ppcli("show buffers"))
2023
2024     @classmethod
2025     def create_stream(cls, packet_count=test_packet_count):
2026         """Create input packet stream for defined interface.
2027
2028         :param list packet_sizes: Required packet sizes.
2029         """
2030         for i in range(0, packet_count):
2031             info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
2032             payload = cls.info_to_payload(info)
2033             p = (Ether(dst=cls.src_dst_if.local_mac,
2034                        src=cls.src_dst_if.remote_mac) /
2035                  IP(id=info.index, src=cls.src_dst_if.remote_ip4,
2036                     dst=cls.src_dst_if.local_ip4) /
2037                  ICMP(type='echo-request', id=1234) /
2038                  Raw(payload))
2039             cls.extend_packet(p, 1518, cls.padding)
2040             info.data = p
2041
2042     @classmethod
2043     def create_fragments(cls):
2044         infos = cls._packet_infos
2045         cls.pkt_infos = []
2046         for index, info in infos.items():
2047             p = info.data
2048             # cls.logger.debug(ppp("Packet:",
2049             #                      p.__class__(scapy.compat.raw(p))))
2050             fragments_300 = fragment_rfc791(p, 300)
2051             cls.pkt_infos.append((index, fragments_300))
2052         cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
2053         cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
2054                          (len(infos), len(cls.fragments_300)))
2055
2056     def verify_capture(self, capture):
2057         """Verify captured packet stream.
2058
2059         :param list capture: Captured packet stream.
2060         """
2061         info = None
2062         seen = set()
2063         for packet in capture:
2064             try:
2065                 self.logger.debug(ppp("Got packet:", packet))
2066                 ip = packet[IP]
2067                 icmp = packet[ICMP]
2068                 payload_info = self.payload_to_info(packet[Raw])
2069                 packet_index = payload_info.index
2070                 if packet_index in seen:
2071                     raise Exception(ppp("Duplicate packet received", packet))
2072                 seen.add(packet_index)
2073                 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
2074                 info = self._packet_infos[packet_index]
2075                 self.assertIsNotNone(info)
2076                 self.assertEqual(packet_index, info.index)
2077                 saved_packet = info.data
2078                 self.assertEqual(ip.src, saved_packet[IP].dst)
2079                 self.assertEqual(ip.dst, saved_packet[IP].src)
2080                 self.assertEqual(icmp.type, 0)  # echo reply
2081                 self.assertEqual(icmp.id, saved_packet[ICMP].id)
2082                 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
2083             except Exception:
2084                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2085                 raise
2086         for index in self._packet_infos:
2087             self.assertIn(index, seen,
2088                           "Packet with packet_index %d not received" % index)
2089
2090     def test_reassembly(self):
2091         """ basic reassembly """
2092
2093         self.pg_enable_capture()
2094         self.src_dst_if.add_stream(self.fragments_300)
2095         self.pg_start()
2096
2097         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2098         self.verify_capture(packets)
2099
2100         # run it all again to verify correctness
2101         self.pg_enable_capture()
2102         self.src_dst_if.add_stream(self.fragments_300)
2103         self.pg_start()
2104
2105         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
2106         self.verify_capture(packets)
2107
2108
2109 class TestFIFReassembly(VppTestCase):
2110     """ Fragments in fragments reassembly """
2111
2112     @classmethod
2113     def setUpClass(cls):
2114         super().setUpClass()
2115
2116         cls.create_pg_interfaces([0, 1])
2117         cls.src_if = cls.pg0
2118         cls.dst_if = cls.pg1
2119         for i in cls.pg_interfaces:
2120             i.admin_up()
2121             i.config_ip4()
2122             i.resolve_arp()
2123             i.config_ip6()
2124             i.resolve_ndp()
2125
2126         cls.packet_sizes = [64, 512, 1518, 9018]
2127         cls.padding = " abcdefghijklmn"
2128
2129     @classmethod
2130     def tearDownClass(cls):
2131         super().tearDownClass()
2132
2133     def setUp(self):
2134         """ Test setup - force timeout on existing reassemblies """
2135         super().setUp()
2136         self.vapi.ip_reassembly_enable_disable(
2137             sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
2138             enable_ip6=True)
2139         self.vapi.ip_reassembly_enable_disable(
2140             sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
2141             enable_ip6=True)
2142         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
2143                                     max_reassembly_length=1000,
2144                                     expire_walk_interval_ms=10)
2145         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
2146                                     max_reassembly_length=1000,
2147                                     expire_walk_interval_ms=10, is_ip6=1)
2148         self.virtual_sleep(.25)
2149         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
2150                                     max_reassembly_length=1000,
2151                                     expire_walk_interval_ms=10000)
2152         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
2153                                     max_reassembly_length=1000,
2154                                     expire_walk_interval_ms=10000, is_ip6=1)
2155
2156     def tearDown(self):
2157         super().tearDown()
2158
2159     def show_commands_at_teardown(self):
2160         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2161         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
2162         self.logger.debug(self.vapi.ppcli("show buffers"))
2163
2164     def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
2165         """Verify captured packet stream.
2166
2167         :param list capture: Captured packet stream.
2168         """
2169         info = None
2170         seen = set()
2171         for packet in capture:
2172             try:
2173                 self.logger.debug(ppp("Got packet:", packet))
2174                 ip = packet[ip_class]
2175                 udp = packet[UDP]
2176                 payload_info = self.payload_to_info(packet[Raw])
2177                 packet_index = payload_info.index
2178                 self.assertTrue(
2179                     packet_index not in dropped_packet_indexes,
2180                     ppp("Packet received, but should be dropped:", packet))
2181                 if packet_index in seen:
2182                     raise Exception(ppp("Duplicate packet received", packet))
2183                 seen.add(packet_index)
2184                 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
2185                 info = self._packet_infos[packet_index]
2186                 self.assertTrue(info is not None)
2187                 self.assertEqual(packet_index, info.index)
2188                 saved_packet = info.data
2189                 self.assertEqual(ip.src, saved_packet[ip_class].src)
2190                 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
2191                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
2192             except Exception:
2193                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2194                 raise
2195         for index in self._packet_infos:
2196             self.assertTrue(index in seen or index in dropped_packet_indexes,
2197                             "Packet with packet_index %d not received" % index)
2198
2199     def test_fif4(self):
2200         """ Fragments in fragments (4o4) """
2201
2202         # TODO this should be ideally in setUpClass, but then we hit a bug
2203         # with VppIpRoute incorrectly reporting it's present when it's not
2204         # so we need to manually remove the vpp config, thus we cannot have
2205         # it shared for multiple test cases
2206         self.tun_ip4 = "1.1.1.2"
2207
2208         self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
2209         self.gre4.add_vpp_config()
2210         self.gre4.admin_up()
2211         self.gre4.config_ip4()
2212
2213         self.vapi.ip_reassembly_enable_disable(
2214             sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
2215
2216         self.route4 = VppIpRoute(self, self.tun_ip4, 32,
2217                                  [VppRoutePath(self.src_if.remote_ip4,
2218                                                self.src_if.sw_if_index)])
2219         self.route4.add_vpp_config()
2220
2221         self.reset_packet_infos()
2222         for i in range(test_packet_count):
2223             info = self.create_packet_info(self.src_if, self.dst_if)
2224             payload = self.info_to_payload(info)
2225             # Ethernet header here is only for size calculation, thus it
2226             # doesn't matter how it's initialized. This is to ensure that
2227             # reassembled packet is not > 9000 bytes, so that it's not dropped
2228             p = (Ether() /
2229                  IP(id=i, src=self.src_if.remote_ip4,
2230                     dst=self.dst_if.remote_ip4) /
2231                  UDP(sport=1234, dport=5678) /
2232                  Raw(payload))
2233             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2234             self.extend_packet(p, size, self.padding)
2235             info.data = p[IP]  # use only IP part, without ethernet header
2236
2237         fragments = [x for _, p in self._packet_infos.items()
2238                      for x in fragment_rfc791(p.data, 400)]
2239
2240         encapped_fragments = \
2241             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
2242              IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
2243                 GRE() /
2244                 p
2245                 for p in fragments]
2246
2247         fragmented_encapped_fragments = \
2248             [x for p in encapped_fragments
2249              for x in fragment_rfc791(p, 200)]
2250
2251         self.src_if.add_stream(fragmented_encapped_fragments)
2252
2253         self.pg_enable_capture(self.pg_interfaces)
2254         self.pg_start()
2255
2256         self.src_if.assert_nothing_captured()
2257         packets = self.dst_if.get_capture(len(self._packet_infos))
2258         self.verify_capture(packets, IP)
2259
2260         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2261         # so that it's query_vpp_config() works as it should
2262         self.gre4.remove_vpp_config()
2263         self.logger.debug(self.vapi.ppcli("show interface"))
2264
2265     def test_fif6(self):
2266         """ Fragments in fragments (6o6) """
2267         # TODO this should be ideally in setUpClass, but then we hit a bug
2268         # with VppIpRoute incorrectly reporting it's present when it's not
2269         # so we need to manually remove the vpp config, thus we cannot have
2270         # it shared for multiple test cases
2271         self.tun_ip6 = "1002::1"
2272
2273         self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
2274         self.gre6.add_vpp_config()
2275         self.gre6.admin_up()
2276         self.gre6.config_ip6()
2277
2278         self.vapi.ip_reassembly_enable_disable(
2279             sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
2280
2281         self.route6 = VppIpRoute(self, self.tun_ip6, 128,
2282                                  [VppRoutePath(
2283                                      self.src_if.remote_ip6,
2284                                      self.src_if.sw_if_index)])
2285         self.route6.add_vpp_config()
2286
2287         self.reset_packet_infos()
2288         for i in range(test_packet_count):
2289             info = self.create_packet_info(self.src_if, self.dst_if)
2290             payload = self.info_to_payload(info)
2291             # Ethernet header here is only for size calculation, thus it
2292             # doesn't matter how it's initialized. This is to ensure that
2293             # reassembled packet is not > 9000 bytes, so that it's not dropped
2294             p = (Ether() /
2295                  IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
2296                  UDP(sport=1234, dport=5678) /
2297                  Raw(payload))
2298             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2299             self.extend_packet(p, size, self.padding)
2300             info.data = p[IPv6]  # use only IPv6 part, without ethernet header
2301
2302         fragments = [x for _, i in self._packet_infos.items()
2303                      for x in fragment_rfc8200(
2304                          i.data, i.index, 400)]
2305
2306         encapped_fragments = \
2307             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
2308              IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
2309                 GRE() /
2310                 p
2311                 for p in fragments]
2312
2313         fragmented_encapped_fragments = \
2314             [x for p in encapped_fragments for x in (
2315                 fragment_rfc8200(
2316                     p,
2317                     2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
2318                     200)
2319                 if IPv6ExtHdrFragment in p else [p]
2320             )
2321             ]
2322
2323         self.src_if.add_stream(fragmented_encapped_fragments)
2324
2325         self.pg_enable_capture(self.pg_interfaces)
2326         self.pg_start()
2327
2328         self.src_if.assert_nothing_captured()
2329         packets = self.dst_if.get_capture(len(self._packet_infos))
2330         self.verify_capture(packets, IPv6)
2331
2332         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2333         # so that it's query_vpp_config() works as it should
2334         self.gre6.remove_vpp_config()
2335
2336
2337 if __name__ == '__main__':
2338     unittest.main(testRunner=VppTestRunner)