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