ipsec: IPSec protection for multi-point tunnel interfaces
[vpp.git] / test / test_reassembly.py
1 #!/usr/bin/env python3
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(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.\
233 Ethernet-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
503 class 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
674 class 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
852 class 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
1307 class 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
1485 class 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
1655 class 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
1782 class 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
2010 if __name__ == '__main__':
2011     unittest.main(testRunner=VppTestRunner)