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