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