ip: svr: improve performance for non-fragments
[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     def send_mixed_and_verify_capture(self, traffic):
674         stream = []
675         for t in traffic:
676             for c in range(t['count']):
677                 stream.append(
678                     (Ether(dst=self.src_if.local_mac,
679                            src=self.src_if.remote_mac) /
680                      IP(id=self.counter,
681                         flags=t['flags'],
682                         src=self.src_if.remote_ip4,
683                         dst=self.dst_if.remote_ip4) /
684                      UDP(sport=1234, dport=5678) /
685                      Raw("abcdef")))
686                 self.counter = self.counter + 1
687
688         self.pg_enable_capture()
689         self.src_if.add_stream(stream)
690         self.pg_start()
691         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
692         self.logger.debug(self.vapi.ppcli("show buffers"))
693         self.logger.debug(self.vapi.ppcli("show trace"))
694         self.dst_if.get_capture(len(stream))
695
696     def test_mixed(self):
697         """ mixed traffic correctly passes through SVR """
698         self.counter = 1
699
700         self.send_mixed_and_verify_capture([{'count': 1, 'flags': ''}])
701         self.send_mixed_and_verify_capture([{'count': 2, 'flags': ''}])
702         self.send_mixed_and_verify_capture([{'count': 3, 'flags': ''}])
703         self.send_mixed_and_verify_capture([{'count': 8, 'flags': ''}])
704         self.send_mixed_and_verify_capture([{'count': 257, 'flags': ''}])
705
706         self.send_mixed_and_verify_capture([{'count': 1, 'flags': 'MF'}])
707         self.send_mixed_and_verify_capture([{'count': 2, 'flags': 'MF'}])
708         self.send_mixed_and_verify_capture([{'count': 3, 'flags': 'MF'}])
709         self.send_mixed_and_verify_capture([{'count': 8, 'flags': 'MF'}])
710         self.send_mixed_and_verify_capture([{'count': 257, 'flags': 'MF'}])
711
712         self.send_mixed_and_verify_capture(
713             [{'count': 1, 'flags': ''}, {'count': 1, 'flags': 'MF'}])
714         self.send_mixed_and_verify_capture(
715             [{'count': 2, 'flags': ''}, {'count': 2, 'flags': 'MF'}])
716         self.send_mixed_and_verify_capture(
717             [{'count': 3, 'flags': ''}, {'count': 3, 'flags': 'MF'}])
718         self.send_mixed_and_verify_capture(
719             [{'count': 8, 'flags': ''}, {'count': 8, 'flags': 'MF'}])
720         self.send_mixed_and_verify_capture(
721             [{'count': 129, 'flags': ''}, {'count': 129, 'flags': 'MF'}])
722
723         self.send_mixed_and_verify_capture(
724             [{'count': 1, 'flags': ''}, {'count': 1, 'flags': 'MF'},
725              {'count': 1, 'flags': ''}, {'count': 1, 'flags': 'MF'}])
726         self.send_mixed_and_verify_capture(
727             [{'count': 2, 'flags': ''}, {'count': 2, 'flags': 'MF'},
728              {'count': 2, 'flags': ''}, {'count': 2, 'flags': 'MF'}])
729         self.send_mixed_and_verify_capture(
730             [{'count': 3, 'flags': ''}, {'count': 3, 'flags': 'MF'},
731              {'count': 3, 'flags': ''}, {'count': 3, 'flags': 'MF'}])
732         self.send_mixed_and_verify_capture(
733             [{'count': 8, 'flags': ''}, {'count': 8, 'flags': 'MF'},
734              {'count': 8, 'flags': ''}, {'count': 8, 'flags': 'MF'}])
735         self.send_mixed_and_verify_capture(
736             [{'count': 65, 'flags': ''}, {'count': 65, 'flags': 'MF'},
737              {'count': 65, 'flags': ''}, {'count': 65, 'flags': 'MF'}])
738
739
740 class TestIPv4MWReassembly(VppTestCase):
741     """ IPv4 Reassembly (multiple workers) """
742     worker_config = "workers %d" % worker_count
743
744     @classmethod
745     def setUpClass(cls):
746         super(TestIPv4MWReassembly, cls).setUpClass()
747
748         cls.create_pg_interfaces(range(worker_count+1))
749         cls.src_if = cls.pg0
750         cls.send_ifs = cls.pg_interfaces[:-1]
751         cls.dst_if = cls.pg_interfaces[-1]
752
753         # setup all interfaces
754         for i in cls.pg_interfaces:
755             i.admin_up()
756             i.config_ip4()
757             i.resolve_arp()
758
759         # packets sizes reduced here because we are generating packets without
760         # Ethernet headers, which are added later (diff fragments go via
761         # different interfaces)
762         cls.packet_sizes = [64-len(Ether()), 512-len(Ether()),
763                             1518-len(Ether()), 9018-len(Ether())]
764         cls.padding = " abcdefghijklmn"
765         cls.create_stream(cls.packet_sizes)
766         cls.create_fragments()
767
768     @classmethod
769     def tearDownClass(cls):
770         super(TestIPv4MWReassembly, cls).tearDownClass()
771
772     def setUp(self):
773         """ Test setup - force timeout on existing reassemblies """
774         super(TestIPv4MWReassembly, self).setUp()
775         for intf in self.send_ifs:
776             self.vapi.ip_reassembly_enable_disable(
777                 sw_if_index=intf.sw_if_index, enable_ip4=True)
778         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
779                                     max_reassembly_length=1000,
780                                     expire_walk_interval_ms=10)
781         self.sleep(.25)
782         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
783                                     max_reassembly_length=1000,
784                                     expire_walk_interval_ms=10000)
785
786     def tearDown(self):
787         super(TestIPv4MWReassembly, self).tearDown()
788
789     def show_commands_at_teardown(self):
790         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
791         self.logger.debug(self.vapi.ppcli("show buffers"))
792
793     @classmethod
794     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
795         """Create input packet stream
796
797         :param list packet_sizes: Required packet sizes.
798         """
799         for i in range(0, packet_count):
800             info = cls.create_packet_info(cls.src_if, cls.src_if)
801             payload = cls.info_to_payload(info)
802             p = (IP(id=info.index, src=cls.src_if.remote_ip4,
803                     dst=cls.dst_if.remote_ip4) /
804                  UDP(sport=1234, dport=5678) /
805                  Raw(payload))
806             size = packet_sizes[(i // 2) % len(packet_sizes)]
807             cls.extend_packet(p, size, cls.padding)
808             info.data = p
809
810     @classmethod
811     def create_fragments(cls):
812         infos = cls._packet_infos
813         cls.pkt_infos = []
814         for index, info in six.iteritems(infos):
815             p = info.data
816             # cls.logger.debug(ppp("Packet:",
817             #                      p.__class__(scapy.compat.raw(p))))
818             fragments_400 = fragment_rfc791(p, 400)
819             cls.pkt_infos.append((index, fragments_400))
820         cls.fragments_400 = [
821             x for (_, frags) in cls.pkt_infos for x in frags]
822         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " %
823                          (len(infos), len(cls.fragments_400)))
824
825     def verify_capture(self, capture, dropped_packet_indexes=[]):
826         """Verify captured packet stream.
827
828         :param list capture: Captured packet stream.
829         """
830         info = None
831         seen = set()
832         for packet in capture:
833             try:
834                 self.logger.debug(ppp("Got packet:", packet))
835                 ip = packet[IP]
836                 udp = packet[UDP]
837                 payload_info = self.payload_to_info(packet[Raw])
838                 packet_index = payload_info.index
839                 self.assertTrue(
840                     packet_index not in dropped_packet_indexes,
841                     ppp("Packet received, but should be dropped:", packet))
842                 if packet_index in seen:
843                     raise Exception(ppp("Duplicate packet received", packet))
844                 seen.add(packet_index)
845                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
846                 info = self._packet_infos[packet_index]
847                 self.assertTrue(info is not None)
848                 self.assertEqual(packet_index, info.index)
849                 saved_packet = info.data
850                 self.assertEqual(ip.src, saved_packet[IP].src)
851                 self.assertEqual(ip.dst, saved_packet[IP].dst)
852                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
853             except Exception:
854                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
855                 raise
856         for index in self._packet_infos:
857             self.assertTrue(index in seen or index in dropped_packet_indexes,
858                             "Packet with packet_index %d not received" % index)
859
860     def send_packets(self, packets):
861         for counter in range(worker_count):
862             if 0 == len(packets[counter]):
863                 continue
864             send_if = self.send_ifs[counter]
865             send_if.add_stream(
866                 (Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
867                  for x in packets[counter]),
868                 worker=counter)
869         self.pg_start()
870
871     def test_worker_conflict(self):
872         """ 1st and FO=0 fragments on different workers """
873
874         # in first wave we send fragments which don't start at offset 0
875         # then we send fragments with offset 0 on a different thread
876         # then the rest of packets on a random thread
877         first_packets = [[] for n in range(worker_count)]
878         second_packets = [[] for n in range(worker_count)]
879         rest_of_packets = [[] for n in range(worker_count)]
880         for (_, p) in self.pkt_infos:
881             wi = randrange(worker_count)
882             second_packets[wi].append(p[0])
883             if len(p) <= 1:
884                 continue
885             wi2 = wi
886             while wi2 == wi:
887                 wi2 = randrange(worker_count)
888             first_packets[wi2].append(p[1])
889             wi3 = randrange(worker_count)
890             rest_of_packets[wi3].extend(p[2:])
891
892         self.pg_enable_capture()
893         self.send_packets(first_packets)
894         self.send_packets(second_packets)
895         self.send_packets(rest_of_packets)
896
897         packets = self.dst_if.get_capture(len(self.pkt_infos))
898         self.verify_capture(packets)
899         for send_if in self.send_ifs:
900             send_if.assert_nothing_captured()
901
902         self.logger.debug(self.vapi.ppcli("show trace"))
903         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
904         self.logger.debug(self.vapi.ppcli("show buffers"))
905         self.vapi.cli("clear trace")
906
907         self.pg_enable_capture()
908         self.send_packets(first_packets)
909         self.send_packets(second_packets)
910         self.send_packets(rest_of_packets)
911
912         packets = self.dst_if.get_capture(len(self.pkt_infos))
913         self.verify_capture(packets)
914         for send_if in self.send_ifs:
915             send_if.assert_nothing_captured()
916
917
918 class TestIPv6Reassembly(VppTestCase):
919     """ IPv6 Reassembly """
920
921     @classmethod
922     def setUpClass(cls):
923         super(TestIPv6Reassembly, cls).setUpClass()
924
925         cls.create_pg_interfaces([0, 1])
926         cls.src_if = cls.pg0
927         cls.dst_if = cls.pg1
928
929         # setup all interfaces
930         for i in cls.pg_interfaces:
931             i.admin_up()
932             i.config_ip6()
933             i.resolve_ndp()
934
935         # packet sizes
936         cls.packet_sizes = [64, 512, 1518, 9018]
937         cls.padding = " abcdefghijklmn"
938         cls.create_stream(cls.packet_sizes)
939         cls.create_fragments()
940
941     @classmethod
942     def tearDownClass(cls):
943         super(TestIPv6Reassembly, cls).tearDownClass()
944
945     def setUp(self):
946         """ Test setup - force timeout on existing reassemblies """
947         super(TestIPv6Reassembly, self).setUp()
948         self.vapi.ip_reassembly_enable_disable(
949             sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
950         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
951                                     max_reassembly_length=1000,
952                                     expire_walk_interval_ms=10, is_ip6=1)
953         self.sleep(.25)
954         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
955                                     max_reassembly_length=1000,
956                                     expire_walk_interval_ms=10000, is_ip6=1)
957         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
958         self.logger.debug(self.vapi.ppcli("show buffers"))
959
960     def tearDown(self):
961         super(TestIPv6Reassembly, self).tearDown()
962
963     def show_commands_at_teardown(self):
964         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
965         self.logger.debug(self.vapi.ppcli("show buffers"))
966
967     @classmethod
968     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
969         """Create input packet stream for defined interface.
970
971         :param list packet_sizes: Required packet sizes.
972         """
973         for i in range(0, packet_count):
974             info = cls.create_packet_info(cls.src_if, cls.src_if)
975             payload = cls.info_to_payload(info)
976             p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
977                  IPv6(src=cls.src_if.remote_ip6,
978                       dst=cls.dst_if.remote_ip6) /
979                  UDP(sport=1234, dport=5678) /
980                  Raw(payload))
981             size = packet_sizes[(i // 2) % len(packet_sizes)]
982             cls.extend_packet(p, size, cls.padding)
983             info.data = p
984
985     @classmethod
986     def create_fragments(cls):
987         infos = cls._packet_infos
988         cls.pkt_infos = []
989         for index, info in six.iteritems(infos):
990             p = info.data
991             # cls.logger.debug(ppp("Packet:",
992             #                      p.__class__(scapy.compat.raw(p))))
993             fragments_400 = fragment_rfc8200(p, info.index, 400)
994             fragments_300 = fragment_rfc8200(p, info.index, 300)
995             cls.pkt_infos.append((index, fragments_400, fragments_300))
996         cls.fragments_400 = [
997             x for _, frags, _ in cls.pkt_infos for x in frags]
998         cls.fragments_300 = [
999             x for _, _, frags in cls.pkt_infos for x in frags]
1000         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
1001                          "and %s 300-byte fragments" %
1002                          (len(infos), len(cls.fragments_400),
1003                              len(cls.fragments_300)))
1004
1005     def verify_capture(self, capture, dropped_packet_indexes=[]):
1006         """Verify captured packet strea .
1007
1008         :param list capture: Captured packet stream.
1009         """
1010         info = None
1011         seen = set()
1012         for packet in capture:
1013             try:
1014                 self.logger.debug(ppp("Got packet:", packet))
1015                 ip = packet[IPv6]
1016                 udp = packet[UDP]
1017                 payload_info = self.payload_to_info(packet[Raw])
1018                 packet_index = payload_info.index
1019                 self.assertTrue(
1020                     packet_index not in dropped_packet_indexes,
1021                     ppp("Packet received, but should be dropped:", packet))
1022                 if packet_index in seen:
1023                     raise Exception(ppp("Duplicate packet received", packet))
1024                 seen.add(packet_index)
1025                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1026                 info = self._packet_infos[packet_index]
1027                 self.assertTrue(info is not None)
1028                 self.assertEqual(packet_index, info.index)
1029                 saved_packet = info.data
1030                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1031                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1032                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1033             except Exception:
1034                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1035                 raise
1036         for index in self._packet_infos:
1037             self.assertTrue(index in seen or index in dropped_packet_indexes,
1038                             "Packet with packet_index %d not received" % index)
1039
1040     def test_reassembly(self):
1041         """ basic reassembly """
1042
1043         self.pg_enable_capture()
1044         self.src_if.add_stream(self.fragments_400)
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(self.fragments_400)
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_buffer_boundary(self):
1061         """ fragment header crossing buffer boundary """
1062
1063         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1064              IPv6(src=self.src_if.remote_ip6,
1065                   dst=self.src_if.local_ip6) /
1066              IPv6ExtHdrHopByHop(
1067                  options=[HBHOptUnknown(otype=0xff, optlen=0)] * 1000) /
1068              IPv6ExtHdrFragment(m=1) /
1069              UDP(sport=1234, dport=5678) /
1070              Raw())
1071         self.pg_enable_capture()
1072         self.src_if.add_stream([p])
1073         self.pg_start()
1074         self.src_if.assert_nothing_captured()
1075         self.dst_if.assert_nothing_captured()
1076
1077     def test_reversed(self):
1078         """ reverse order reassembly """
1079
1080         fragments = list(self.fragments_400)
1081         fragments.reverse()
1082
1083         self.pg_enable_capture()
1084         self.src_if.add_stream(fragments)
1085         self.pg_start()
1086
1087         packets = self.dst_if.get_capture(len(self.pkt_infos))
1088         self.verify_capture(packets)
1089         self.src_if.assert_nothing_captured()
1090
1091         # run it all again to verify correctness
1092         self.pg_enable_capture()
1093         self.src_if.add_stream(fragments)
1094         self.pg_start()
1095
1096         packets = self.dst_if.get_capture(len(self.pkt_infos))
1097         self.verify_capture(packets)
1098         self.src_if.assert_nothing_captured()
1099
1100     def test_random(self):
1101         """ random order reassembly """
1102
1103         fragments = list(self.fragments_400)
1104         shuffle(fragments)
1105
1106         self.pg_enable_capture()
1107         self.src_if.add_stream(fragments)
1108         self.pg_start()
1109
1110         packets = self.dst_if.get_capture(len(self.pkt_infos))
1111         self.verify_capture(packets)
1112         self.src_if.assert_nothing_captured()
1113
1114         # run it all again to verify correctness
1115         self.pg_enable_capture()
1116         self.src_if.add_stream(fragments)
1117         self.pg_start()
1118
1119         packets = self.dst_if.get_capture(len(self.pkt_infos))
1120         self.verify_capture(packets)
1121         self.src_if.assert_nothing_captured()
1122
1123     def test_duplicates(self):
1124         """ duplicate fragments """
1125
1126         fragments = [
1127             x for (_, frags, _) in self.pkt_infos
1128             for x in frags
1129             for _ in range(0, min(2, len(frags)))
1130         ]
1131
1132         self.pg_enable_capture()
1133         self.src_if.add_stream(fragments)
1134         self.pg_start()
1135
1136         packets = self.dst_if.get_capture(len(self.pkt_infos))
1137         self.verify_capture(packets)
1138         self.src_if.assert_nothing_captured()
1139
1140     def test_long_fragment_chain(self):
1141         """ long fragment chain """
1142
1143         error_cnt_str = \
1144             "/err/ip6-full-reassembly-feature/fragment chain too long (drop)"
1145
1146         error_cnt = self.statistics.get_err_counter(error_cnt_str)
1147
1148         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1149                                     max_reassembly_length=3,
1150                                     expire_walk_interval_ms=50, is_ip6=1)
1151
1152         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1153              IPv6(src=self.src_if.remote_ip6,
1154                   dst=self.dst_if.remote_ip6) /
1155              UDP(sport=1234, dport=5678) /
1156              Raw(b"X" * 1000))
1157         frags = fragment_rfc8200(p, 1, 300) + fragment_rfc8200(p, 2, 500)
1158
1159         self.pg_enable_capture()
1160         self.src_if.add_stream(frags)
1161         self.pg_start()
1162
1163         self.dst_if.get_capture(1)
1164         self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
1165
1166     def test_overlap1(self):
1167         """ overlapping fragments case #1 """
1168
1169         fragments = []
1170         for _, frags_400, frags_300 in self.pkt_infos:
1171             if len(frags_300) == 1:
1172                 fragments.extend(frags_400)
1173             else:
1174                 for i, j in zip(frags_300, frags_400):
1175                     fragments.extend(i)
1176                     fragments.extend(j)
1177
1178         dropped_packet_indexes = set(
1179             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1180         )
1181
1182         self.pg_enable_capture()
1183         self.src_if.add_stream(fragments)
1184         self.pg_start()
1185
1186         packets = self.dst_if.get_capture(
1187             len(self.pkt_infos) - len(dropped_packet_indexes))
1188         self.verify_capture(packets, dropped_packet_indexes)
1189         self.src_if.assert_nothing_captured()
1190
1191     def test_overlap2(self):
1192         """ overlapping fragments case #2 """
1193
1194         fragments = []
1195         for _, frags_400, frags_300 in self.pkt_infos:
1196             if len(frags_400) == 1:
1197                 fragments.extend(frags_400)
1198             else:
1199                 # care must be taken here so that there are no fragments
1200                 # received by vpp after reassembly is finished, otherwise
1201                 # new reassemblies will be started and packet generator will
1202                 # freak out when it detects unfreed buffers
1203                 zipped = zip(frags_400, frags_300)
1204                 for i, j in zipped:
1205                     fragments.extend(i)
1206                     fragments.extend(j)
1207                 fragments.pop()
1208
1209         dropped_packet_indexes = set(
1210             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1211         )
1212
1213         self.pg_enable_capture()
1214         self.src_if.add_stream(fragments)
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         self.src_if.assert_nothing_captured()
1221
1222     def test_timeout_inline(self):
1223         """ timeout (inline) """
1224
1225         dropped_packet_indexes = set(
1226             index for (index, frags, _) in self.pkt_infos if len(frags) > 1
1227         )
1228
1229         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1230                                     max_reassembly_length=3,
1231                                     expire_walk_interval_ms=10000, is_ip6=1)
1232
1233         self.pg_enable_capture()
1234         self.src_if.add_stream(self.fragments_400)
1235         self.pg_start()
1236
1237         packets = self.dst_if.get_capture(
1238             len(self.pkt_infos) - len(dropped_packet_indexes))
1239         self.verify_capture(packets, dropped_packet_indexes)
1240         pkts = self.src_if.get_capture(
1241             expected_count=len(dropped_packet_indexes))
1242         for icmp in pkts:
1243             self.assertIn(ICMPv6TimeExceeded, icmp)
1244             self.assertIn(IPv6ExtHdrFragment, icmp)
1245             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1246             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1247
1248     def test_timeout_cleanup(self):
1249         """ timeout (cleanup) """
1250
1251         # whole packets + fragmented packets sans last fragment
1252         fragments = [
1253             x for (_, frags_400, _) in self.pkt_infos
1254             for x in frags_400[:-1 if len(frags_400) > 1 else None]
1255         ]
1256
1257         # last fragments for fragmented packets
1258         fragments2 = [frags_400[-1]
1259                       for (_, frags_400, _) in self.pkt_infos
1260                       if len(frags_400) > 1]
1261
1262         dropped_packet_indexes = set(
1263             index for (index, frags_400, _) in self.pkt_infos
1264             if len(frags_400) > 1)
1265
1266         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1267                                     max_reassembly_length=1000,
1268                                     expire_walk_interval_ms=50)
1269
1270         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1271                                     max_reassembly_length=1000,
1272                                     expire_walk_interval_ms=50, is_ip6=1)
1273
1274         self.pg_enable_capture()
1275         self.src_if.add_stream(fragments)
1276         self.pg_start()
1277
1278         self.sleep(.25, "wait before sending rest of fragments")
1279
1280         self.src_if.add_stream(fragments2)
1281         self.pg_start()
1282
1283         packets = self.dst_if.get_capture(
1284             len(self.pkt_infos) - len(dropped_packet_indexes))
1285         self.verify_capture(packets, dropped_packet_indexes)
1286         pkts = self.src_if.get_capture(
1287             expected_count=len(dropped_packet_indexes))
1288         for icmp in pkts:
1289             self.assertIn(ICMPv6TimeExceeded, icmp)
1290             self.assertIn(IPv6ExtHdrFragment, icmp)
1291             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1292             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1293
1294     def test_disabled(self):
1295         """ reassembly disabled """
1296
1297         dropped_packet_indexes = set(
1298             index for (index, frags_400, _) in self.pkt_infos
1299             if len(frags_400) > 1)
1300
1301         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
1302                                     max_reassembly_length=3,
1303                                     expire_walk_interval_ms=10000, is_ip6=1)
1304
1305         self.pg_enable_capture()
1306         self.src_if.add_stream(self.fragments_400)
1307         self.pg_start()
1308
1309         packets = self.dst_if.get_capture(
1310             len(self.pkt_infos) - len(dropped_packet_indexes))
1311         self.verify_capture(packets, dropped_packet_indexes)
1312         self.src_if.assert_nothing_captured()
1313
1314     def test_missing_upper(self):
1315         """ missing upper layer """
1316         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1317              IPv6(src=self.src_if.remote_ip6,
1318                   dst=self.src_if.local_ip6) /
1319              UDP(sport=1234, dport=5678) /
1320              Raw())
1321         self.extend_packet(p, 1000, self.padding)
1322         fragments = fragment_rfc8200(p, 1, 500)
1323         bad_fragment = p.__class__(scapy.compat.raw(fragments[1]))
1324         bad_fragment[IPv6ExtHdrFragment].nh = 59
1325         bad_fragment[IPv6ExtHdrFragment].offset = 0
1326         self.pg_enable_capture()
1327         self.src_if.add_stream([bad_fragment])
1328         self.pg_start()
1329         pkts = self.src_if.get_capture(expected_count=1)
1330         icmp = pkts[0]
1331         self.assertIn(ICMPv6ParamProblem, icmp)
1332         self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
1333
1334     def test_invalid_frag_size(self):
1335         """ fragment size not a multiple of 8 """
1336         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1337              IPv6(src=self.src_if.remote_ip6,
1338                   dst=self.src_if.local_ip6) /
1339              UDP(sport=1234, dport=5678) /
1340              Raw())
1341         self.extend_packet(p, 1000, self.padding)
1342         fragments = fragment_rfc8200(p, 1, 500)
1343         bad_fragment = fragments[0]
1344         self.extend_packet(bad_fragment, len(bad_fragment) + 5)
1345         self.pg_enable_capture()
1346         self.src_if.add_stream([bad_fragment])
1347         self.pg_start()
1348         pkts = self.src_if.get_capture(expected_count=1)
1349         icmp = pkts[0]
1350         self.assertIn(ICMPv6ParamProblem, icmp)
1351         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1352
1353     def test_invalid_packet_size(self):
1354         """ total packet size > 65535 """
1355         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1356              IPv6(src=self.src_if.remote_ip6,
1357                   dst=self.src_if.local_ip6) /
1358              UDP(sport=1234, dport=5678) /
1359              Raw())
1360         self.extend_packet(p, 1000, self.padding)
1361         fragments = fragment_rfc8200(p, 1, 500)
1362         bad_fragment = fragments[1]
1363         bad_fragment[IPv6ExtHdrFragment].offset = 65500
1364         self.pg_enable_capture()
1365         self.src_if.add_stream([bad_fragment])
1366         self.pg_start()
1367         pkts = self.src_if.get_capture(expected_count=1)
1368         icmp = pkts[0]
1369         self.assertIn(ICMPv6ParamProblem, icmp)
1370         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1371
1372
1373 class TestIPv6MWReassembly(VppTestCase):
1374     """ IPv6 Reassembly (multiple workers) """
1375     worker_config = "workers %d" % worker_count
1376
1377     @classmethod
1378     def setUpClass(cls):
1379         super(TestIPv6MWReassembly, cls).setUpClass()
1380
1381         cls.create_pg_interfaces(range(worker_count+1))
1382         cls.src_if = cls.pg0
1383         cls.send_ifs = cls.pg_interfaces[:-1]
1384         cls.dst_if = cls.pg_interfaces[-1]
1385
1386         # setup all interfaces
1387         for i in cls.pg_interfaces:
1388             i.admin_up()
1389             i.config_ip6()
1390             i.resolve_ndp()
1391
1392         # packets sizes reduced here because we are generating packets without
1393         # Ethernet headers, which are added later (diff fragments go via
1394         # different interfaces)
1395         cls.packet_sizes = [64-len(Ether()), 512-len(Ether()),
1396                             1518-len(Ether()), 9018-len(Ether())]
1397         cls.padding = " abcdefghijklmn"
1398         cls.create_stream(cls.packet_sizes)
1399         cls.create_fragments()
1400
1401     @classmethod
1402     def tearDownClass(cls):
1403         super(TestIPv6MWReassembly, cls).tearDownClass()
1404
1405     def setUp(self):
1406         """ Test setup - force timeout on existing reassemblies """
1407         super(TestIPv6MWReassembly, self).setUp()
1408         for intf in self.send_ifs:
1409             self.vapi.ip_reassembly_enable_disable(
1410                 sw_if_index=intf.sw_if_index, enable_ip6=True)
1411         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1412                                     max_reassembly_length=1000,
1413                                     expire_walk_interval_ms=10, is_ip6=1)
1414         self.sleep(.25)
1415         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1416                                     max_reassembly_length=1000,
1417                                     expire_walk_interval_ms=1000, is_ip6=1)
1418
1419     def tearDown(self):
1420         super(TestIPv6MWReassembly, self).tearDown()
1421
1422     def show_commands_at_teardown(self):
1423         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1424         self.logger.debug(self.vapi.ppcli("show buffers"))
1425
1426     @classmethod
1427     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1428         """Create input packet stream
1429
1430         :param list packet_sizes: Required packet sizes.
1431         """
1432         for i in range(0, packet_count):
1433             info = cls.create_packet_info(cls.src_if, cls.src_if)
1434             payload = cls.info_to_payload(info)
1435             p = (IPv6(src=cls.src_if.remote_ip6,
1436                       dst=cls.dst_if.remote_ip6) /
1437                  UDP(sport=1234, dport=5678) /
1438                  Raw(payload))
1439             size = packet_sizes[(i // 2) % len(packet_sizes)]
1440             cls.extend_packet(p, size, cls.padding)
1441             info.data = p
1442
1443     @classmethod
1444     def create_fragments(cls):
1445         infos = cls._packet_infos
1446         cls.pkt_infos = []
1447         for index, info in six.iteritems(infos):
1448             p = info.data
1449             # cls.logger.debug(ppp("Packet:",
1450             #                      p.__class__(scapy.compat.raw(p))))
1451             fragments_400 = fragment_rfc8200(p, index, 400)
1452             cls.pkt_infos.append((index, fragments_400))
1453         cls.fragments_400 = [
1454             x for (_, frags) in cls.pkt_infos for x in frags]
1455         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " %
1456                          (len(infos), len(cls.fragments_400)))
1457
1458     def verify_capture(self, capture, dropped_packet_indexes=[]):
1459         """Verify captured packet strea .
1460
1461         :param list capture: Captured packet stream.
1462         """
1463         info = None
1464         seen = set()
1465         for packet in capture:
1466             try:
1467                 self.logger.debug(ppp("Got packet:", packet))
1468                 ip = packet[IPv6]
1469                 udp = packet[UDP]
1470                 payload_info = self.payload_to_info(packet[Raw])
1471                 packet_index = payload_info.index
1472                 self.assertTrue(
1473                     packet_index not in dropped_packet_indexes,
1474                     ppp("Packet received, but should be dropped:", packet))
1475                 if packet_index in seen:
1476                     raise Exception(ppp("Duplicate packet received", packet))
1477                 seen.add(packet_index)
1478                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1479                 info = self._packet_infos[packet_index]
1480                 self.assertTrue(info is not None)
1481                 self.assertEqual(packet_index, info.index)
1482                 saved_packet = info.data
1483                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1484                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1485                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1486             except Exception:
1487                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1488                 raise
1489         for index in self._packet_infos:
1490             self.assertTrue(index in seen or index in dropped_packet_indexes,
1491                             "Packet with packet_index %d not received" % index)
1492
1493     def send_packets(self, packets):
1494         for counter in range(worker_count):
1495             if 0 == len(packets[counter]):
1496                 continue
1497             send_if = self.send_ifs[counter]
1498             send_if.add_stream(
1499                 (Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1500                  for x in packets[counter]),
1501                 worker=counter)
1502         self.pg_start()
1503
1504     def test_worker_conflict(self):
1505         """ 1st and FO=0 fragments on different workers """
1506
1507         # in first wave we send fragments which don't start at offset 0
1508         # then we send fragments with offset 0 on a different thread
1509         # then the rest of packets on a random thread
1510         first_packets = [[] for n in range(worker_count)]
1511         second_packets = [[] for n in range(worker_count)]
1512         rest_of_packets = [[] for n in range(worker_count)]
1513         for (_, p) in self.pkt_infos:
1514             wi = randrange(worker_count)
1515             second_packets[wi].append(p[0])
1516             if len(p) <= 1:
1517                 continue
1518             wi2 = wi
1519             while wi2 == wi:
1520                 wi2 = randrange(worker_count)
1521             first_packets[wi2].append(p[1])
1522             wi3 = randrange(worker_count)
1523             rest_of_packets[wi3].extend(p[2:])
1524
1525         self.pg_enable_capture()
1526         self.send_packets(first_packets)
1527         self.send_packets(second_packets)
1528         self.send_packets(rest_of_packets)
1529
1530         packets = self.dst_if.get_capture(len(self.pkt_infos))
1531         self.verify_capture(packets)
1532         for send_if in self.send_ifs:
1533             send_if.assert_nothing_captured()
1534
1535         self.logger.debug(self.vapi.ppcli("show trace"))
1536         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1537         self.logger.debug(self.vapi.ppcli("show buffers"))
1538         self.vapi.cli("clear trace")
1539
1540         self.pg_enable_capture()
1541         self.send_packets(first_packets)
1542         self.send_packets(second_packets)
1543         self.send_packets(rest_of_packets)
1544
1545         packets = self.dst_if.get_capture(len(self.pkt_infos))
1546         self.verify_capture(packets)
1547         for send_if in self.send_ifs:
1548             send_if.assert_nothing_captured()
1549
1550
1551 class TestIPv6SVReassembly(VppTestCase):
1552     """ IPv6 Shallow Virtual Reassembly """
1553
1554     @classmethod
1555     def setUpClass(cls):
1556         super(TestIPv6SVReassembly, cls).setUpClass()
1557
1558         cls.create_pg_interfaces([0, 1])
1559         cls.src_if = cls.pg0
1560         cls.dst_if = cls.pg1
1561
1562         # setup all interfaces
1563         for i in cls.pg_interfaces:
1564             i.admin_up()
1565             i.config_ip6()
1566             i.resolve_ndp()
1567
1568     def setUp(self):
1569         """ Test setup - force timeout on existing reassemblies """
1570         super(TestIPv6SVReassembly, self).setUp()
1571         self.vapi.ip_reassembly_enable_disable(
1572             sw_if_index=self.src_if.sw_if_index, enable_ip6=True,
1573             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
1574         self.vapi.ip_reassembly_set(
1575             timeout_ms=0, max_reassemblies=1000,
1576             max_reassembly_length=1000,
1577             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1578             expire_walk_interval_ms=10, is_ip6=1)
1579         self.sleep(.25)
1580         self.vapi.ip_reassembly_set(
1581             timeout_ms=1000000, max_reassemblies=1000,
1582             max_reassembly_length=1000,
1583             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1584             expire_walk_interval_ms=10000, is_ip6=1)
1585
1586     def tearDown(self):
1587         super(TestIPv6SVReassembly, self).tearDown()
1588         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1589         self.logger.debug(self.vapi.ppcli("show buffers"))
1590
1591     def test_basic(self):
1592         """ basic reassembly """
1593         payload_len = 1000
1594         payload = ""
1595         counter = 0
1596         while len(payload) < payload_len:
1597             payload += "%u " % counter
1598             counter += 1
1599
1600         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1601              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1602              UDP(sport=1234, dport=5678) /
1603              Raw(payload))
1604         fragments = fragment_rfc8200(p, 1, payload_len/4)
1605
1606         # send fragment #2 - should be cached inside reassembly
1607         self.pg_enable_capture()
1608         self.src_if.add_stream(fragments[1])
1609         self.pg_start()
1610         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1611         self.logger.debug(self.vapi.ppcli("show buffers"))
1612         self.logger.debug(self.vapi.ppcli("show trace"))
1613         self.dst_if.assert_nothing_captured()
1614
1615         # send fragment #1 - reassembly is finished now and both fragments
1616         # forwarded
1617         self.pg_enable_capture()
1618         self.src_if.add_stream(fragments[0])
1619         self.pg_start()
1620         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1621         self.logger.debug(self.vapi.ppcli("show buffers"))
1622         self.logger.debug(self.vapi.ppcli("show trace"))
1623         c = self.dst_if.get_capture(2)
1624         for sent, recvd in zip([fragments[1], fragments[0]], c):
1625             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1626             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1627             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1628
1629         # send rest of fragments - should be immediately forwarded
1630         self.pg_enable_capture()
1631         self.src_if.add_stream(fragments[2:])
1632         self.pg_start()
1633         c = self.dst_if.get_capture(len(fragments[2:]))
1634         for sent, recvd in zip(fragments[2:], c):
1635             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1636             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1637             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1638
1639     def test_timeout(self):
1640         """ reassembly timeout """
1641         payload_len = 1000
1642         payload = ""
1643         counter = 0
1644         while len(payload) < payload_len:
1645             payload += "%u " % counter
1646             counter += 1
1647
1648         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1649              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1650              UDP(sport=1234, dport=5678) /
1651              Raw(payload))
1652         fragments = fragment_rfc8200(p, 1, payload_len/4)
1653
1654         self.vapi.ip_reassembly_set(
1655             timeout_ms=100, max_reassemblies=1000,
1656             max_reassembly_length=1000,
1657             expire_walk_interval_ms=50,
1658             is_ip6=1,
1659             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
1660
1661         # send fragments #2 and #1 - should be forwarded
1662         self.pg_enable_capture()
1663         self.src_if.add_stream(fragments[0:2])
1664         self.pg_start()
1665         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
1666         self.logger.debug(self.vapi.ppcli("show buffers"))
1667         self.logger.debug(self.vapi.ppcli("show trace"))
1668         c = self.dst_if.get_capture(2)
1669         for sent, recvd in zip([fragments[1], fragments[0]], c):
1670             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1671             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1672             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1673
1674         # wait for cleanup
1675         self.sleep(.25, "wait before sending rest of fragments")
1676
1677         # send rest of fragments - shouldn't be forwarded
1678         self.pg_enable_capture()
1679         self.src_if.add_stream(fragments[2:])
1680         self.pg_start()
1681         self.dst_if.assert_nothing_captured()
1682
1683     def test_lru(self):
1684         """ reassembly reuses LRU element """
1685
1686         self.vapi.ip_reassembly_set(
1687             timeout_ms=1000000, max_reassemblies=1,
1688             max_reassembly_length=1000,
1689             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1690             is_ip6=1, expire_walk_interval_ms=10000)
1691
1692         payload_len = 1000
1693         payload = ""
1694         counter = 0
1695         while len(payload) < payload_len:
1696             payload += "%u " % counter
1697             counter += 1
1698
1699         packet_count = 10
1700
1701         fragments = [f
1702                      for i in range(packet_count)
1703                      for p in (Ether(dst=self.src_if.local_mac,
1704                                      src=self.src_if.remote_mac) /
1705                                IPv6(src=self.src_if.remote_ip6,
1706                                     dst=self.dst_if.remote_ip6) /
1707                                UDP(sport=1234, dport=5678) /
1708                                Raw(payload))
1709                      for f in fragment_rfc8200(p, i, payload_len/4)]
1710
1711         self.pg_enable_capture()
1712         self.src_if.add_stream(fragments)
1713         self.pg_start()
1714         c = self.dst_if.get_capture(len(fragments))
1715         for sent, recvd in zip(fragments, c):
1716             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1717             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1718             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1719
1720
1721 class TestIPv4ReassemblyLocalNode(VppTestCase):
1722     """ IPv4 Reassembly for packets coming to ip4-local node """
1723
1724     @classmethod
1725     def setUpClass(cls):
1726         super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
1727
1728         cls.create_pg_interfaces([0])
1729         cls.src_dst_if = cls.pg0
1730
1731         # setup all interfaces
1732         for i in cls.pg_interfaces:
1733             i.admin_up()
1734             i.config_ip4()
1735             i.resolve_arp()
1736
1737         cls.padding = " abcdefghijklmn"
1738         cls.create_stream()
1739         cls.create_fragments()
1740
1741     @classmethod
1742     def tearDownClass(cls):
1743         super(TestIPv4ReassemblyLocalNode, cls).tearDownClass()
1744
1745     def setUp(self):
1746         """ Test setup - force timeout on existing reassemblies """
1747         super(TestIPv4ReassemblyLocalNode, self).setUp()
1748         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1749                                     max_reassembly_length=1000,
1750                                     expire_walk_interval_ms=10)
1751         self.sleep(.25)
1752         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1753                                     max_reassembly_length=1000,
1754                                     expire_walk_interval_ms=10000)
1755
1756     def tearDown(self):
1757         super(TestIPv4ReassemblyLocalNode, self).tearDown()
1758
1759     def show_commands_at_teardown(self):
1760         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1761         self.logger.debug(self.vapi.ppcli("show buffers"))
1762
1763     @classmethod
1764     def create_stream(cls, packet_count=test_packet_count):
1765         """Create input packet stream for defined interface.
1766
1767         :param list packet_sizes: Required packet sizes.
1768         """
1769         for i in range(0, packet_count):
1770             info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
1771             payload = cls.info_to_payload(info)
1772             p = (Ether(dst=cls.src_dst_if.local_mac,
1773                        src=cls.src_dst_if.remote_mac) /
1774                  IP(id=info.index, src=cls.src_dst_if.remote_ip4,
1775                     dst=cls.src_dst_if.local_ip4) /
1776                  ICMP(type='echo-request', id=1234) /
1777                  Raw(payload))
1778             cls.extend_packet(p, 1518, cls.padding)
1779             info.data = p
1780
1781     @classmethod
1782     def create_fragments(cls):
1783         infos = cls._packet_infos
1784         cls.pkt_infos = []
1785         for index, info in six.iteritems(infos):
1786             p = info.data
1787             # cls.logger.debug(ppp("Packet:",
1788             #                      p.__class__(scapy.compat.raw(p))))
1789             fragments_300 = fragment_rfc791(p, 300)
1790             cls.pkt_infos.append((index, fragments_300))
1791         cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
1792         cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
1793                          (len(infos), len(cls.fragments_300)))
1794
1795     def verify_capture(self, capture):
1796         """Verify captured packet stream.
1797
1798         :param list capture: Captured packet stream.
1799         """
1800         info = None
1801         seen = set()
1802         for packet in capture:
1803             try:
1804                 self.logger.debug(ppp("Got packet:", packet))
1805                 ip = packet[IP]
1806                 icmp = packet[ICMP]
1807                 payload_info = self.payload_to_info(packet[Raw])
1808                 packet_index = payload_info.index
1809                 if packet_index in seen:
1810                     raise Exception(ppp("Duplicate packet received", packet))
1811                 seen.add(packet_index)
1812                 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
1813                 info = self._packet_infos[packet_index]
1814                 self.assertIsNotNone(info)
1815                 self.assertEqual(packet_index, info.index)
1816                 saved_packet = info.data
1817                 self.assertEqual(ip.src, saved_packet[IP].dst)
1818                 self.assertEqual(ip.dst, saved_packet[IP].src)
1819                 self.assertEqual(icmp.type, 0)  # echo reply
1820                 self.assertEqual(icmp.id, saved_packet[ICMP].id)
1821                 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
1822             except Exception:
1823                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1824                 raise
1825         for index in self._packet_infos:
1826             self.assertIn(index, seen,
1827                           "Packet with packet_index %d not received" % index)
1828
1829     def test_reassembly(self):
1830         """ basic reassembly """
1831
1832         self.pg_enable_capture()
1833         self.src_dst_if.add_stream(self.fragments_300)
1834         self.pg_start()
1835
1836         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1837         self.verify_capture(packets)
1838
1839         # run it all again to verify correctness
1840         self.pg_enable_capture()
1841         self.src_dst_if.add_stream(self.fragments_300)
1842         self.pg_start()
1843
1844         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1845         self.verify_capture(packets)
1846
1847
1848 class TestFIFReassembly(VppTestCase):
1849     """ Fragments in fragments reassembly """
1850
1851     @classmethod
1852     def setUpClass(cls):
1853         super(TestFIFReassembly, cls).setUpClass()
1854
1855         cls.create_pg_interfaces([0, 1])
1856         cls.src_if = cls.pg0
1857         cls.dst_if = cls.pg1
1858         for i in cls.pg_interfaces:
1859             i.admin_up()
1860             i.config_ip4()
1861             i.resolve_arp()
1862             i.config_ip6()
1863             i.resolve_ndp()
1864
1865         cls.packet_sizes = [64, 512, 1518, 9018]
1866         cls.padding = " abcdefghijklmn"
1867
1868     @classmethod
1869     def tearDownClass(cls):
1870         super(TestFIFReassembly, cls).tearDownClass()
1871
1872     def setUp(self):
1873         """ Test setup - force timeout on existing reassemblies """
1874         super(TestFIFReassembly, self).setUp()
1875         self.vapi.ip_reassembly_enable_disable(
1876             sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
1877             enable_ip6=True)
1878         self.vapi.ip_reassembly_enable_disable(
1879             sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
1880             enable_ip6=True)
1881         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1882                                     max_reassembly_length=1000,
1883                                     expire_walk_interval_ms=10)
1884         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1885                                     max_reassembly_length=1000,
1886                                     expire_walk_interval_ms=10, is_ip6=1)
1887         self.sleep(.25)
1888         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1889                                     max_reassembly_length=1000,
1890                                     expire_walk_interval_ms=10000)
1891         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1892                                     max_reassembly_length=1000,
1893                                     expire_walk_interval_ms=10000, is_ip6=1)
1894
1895     def tearDown(self):
1896         super(TestFIFReassembly, self).tearDown()
1897
1898     def show_commands_at_teardown(self):
1899         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1900         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1901         self.logger.debug(self.vapi.ppcli("show buffers"))
1902
1903     def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
1904         """Verify captured packet stream.
1905
1906         :param list capture: Captured packet stream.
1907         """
1908         info = None
1909         seen = set()
1910         for packet in capture:
1911             try:
1912                 self.logger.debug(ppp("Got packet:", packet))
1913                 ip = packet[ip_class]
1914                 udp = packet[UDP]
1915                 payload_info = self.payload_to_info(packet[Raw])
1916                 packet_index = payload_info.index
1917                 self.assertTrue(
1918                     packet_index not in dropped_packet_indexes,
1919                     ppp("Packet received, but should be dropped:", packet))
1920                 if packet_index in seen:
1921                     raise Exception(ppp("Duplicate packet received", packet))
1922                 seen.add(packet_index)
1923                 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1924                 info = self._packet_infos[packet_index]
1925                 self.assertTrue(info is not None)
1926                 self.assertEqual(packet_index, info.index)
1927                 saved_packet = info.data
1928                 self.assertEqual(ip.src, saved_packet[ip_class].src)
1929                 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1930                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1931             except Exception:
1932                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1933                 raise
1934         for index in self._packet_infos:
1935             self.assertTrue(index in seen or index in dropped_packet_indexes,
1936                             "Packet with packet_index %d not received" % index)
1937
1938     def test_fif4(self):
1939         """ Fragments in fragments (4o4) """
1940
1941         # TODO this should be ideally in setUpClass, but then we hit a bug
1942         # with VppIpRoute incorrectly reporting it's present when it's not
1943         # so we need to manually remove the vpp config, thus we cannot have
1944         # it shared for multiple test cases
1945         self.tun_ip4 = "1.1.1.2"
1946
1947         self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1948         self.gre4.add_vpp_config()
1949         self.gre4.admin_up()
1950         self.gre4.config_ip4()
1951
1952         self.vapi.ip_reassembly_enable_disable(
1953             sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1954
1955         self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1956                                  [VppRoutePath(self.src_if.remote_ip4,
1957                                                self.src_if.sw_if_index)])
1958         self.route4.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                  IP(id=i, src=self.src_if.remote_ip4,
1969                     dst=self.dst_if.remote_ip4) /
1970                  UDP(sport=1234, dport=5678) /
1971                  Raw(payload))
1972             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1973             self.extend_packet(p, size, self.padding)
1974             info.data = p[IP]  # use only IP part, without ethernet header
1975
1976         fragments = [x for _, p in six.iteritems(self._packet_infos)
1977                      for x in fragment_rfc791(p.data, 400)]
1978
1979         encapped_fragments = \
1980             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1981              IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1982                 GRE() /
1983                 p
1984                 for p in fragments]
1985
1986         fragmented_encapped_fragments = \
1987             [x for p in encapped_fragments
1988              for x in fragment_rfc791(p, 200)]
1989
1990         self.src_if.add_stream(fragmented_encapped_fragments)
1991
1992         self.pg_enable_capture(self.pg_interfaces)
1993         self.pg_start()
1994
1995         self.src_if.assert_nothing_captured()
1996         packets = self.dst_if.get_capture(len(self._packet_infos))
1997         self.verify_capture(packets, IP)
1998
1999         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2000         # so that it's query_vpp_config() works as it should
2001         self.gre4.remove_vpp_config()
2002         self.logger.debug(self.vapi.ppcli("show interface"))
2003
2004     def test_fif6(self):
2005         """ Fragments in fragments (6o6) """
2006         # TODO this should be ideally in setUpClass, but then we hit a bug
2007         # with VppIpRoute incorrectly reporting it's present when it's not
2008         # so we need to manually remove the vpp config, thus we cannot have
2009         # it shared for multiple test cases
2010         self.tun_ip6 = "1002::1"
2011
2012         self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
2013         self.gre6.add_vpp_config()
2014         self.gre6.admin_up()
2015         self.gre6.config_ip6()
2016
2017         self.vapi.ip_reassembly_enable_disable(
2018             sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
2019
2020         self.route6 = VppIpRoute(self, self.tun_ip6, 128,
2021                                  [VppRoutePath(
2022                                      self.src_if.remote_ip6,
2023                                      self.src_if.sw_if_index)])
2024         self.route6.add_vpp_config()
2025
2026         self.reset_packet_infos()
2027         for i in range(test_packet_count):
2028             info = self.create_packet_info(self.src_if, self.dst_if)
2029             payload = self.info_to_payload(info)
2030             # Ethernet header here is only for size calculation, thus it
2031             # doesn't matter how it's initialized. This is to ensure that
2032             # reassembled packet is not > 9000 bytes, so that it's not dropped
2033             p = (Ether() /
2034                  IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
2035                  UDP(sport=1234, dport=5678) /
2036                  Raw(payload))
2037             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2038             self.extend_packet(p, size, self.padding)
2039             info.data = p[IPv6]  # use only IPv6 part, without ethernet header
2040
2041         fragments = [x for _, i in six.iteritems(self._packet_infos)
2042                      for x in fragment_rfc8200(
2043                          i.data, i.index, 400)]
2044
2045         encapped_fragments = \
2046             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
2047              IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
2048                 GRE() /
2049                 p
2050                 for p in fragments]
2051
2052         fragmented_encapped_fragments = \
2053             [x for p in encapped_fragments for x in (
2054                 fragment_rfc8200(
2055                     p,
2056                     2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
2057                     200)
2058                 if IPv6ExtHdrFragment in p else [p]
2059             )
2060             ]
2061
2062         self.src_if.add_stream(fragmented_encapped_fragments)
2063
2064         self.pg_enable_capture(self.pg_interfaces)
2065         self.pg_start()
2066
2067         self.src_if.assert_nothing_captured()
2068         packets = self.dst_if.get_capture(len(self._packet_infos))
2069         self.verify_capture(packets, IPv6)
2070
2071         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2072         # so that it's query_vpp_config() works as it should
2073         self.gre6.remove_vpp_config()
2074
2075
2076 if __name__ == '__main__':
2077     unittest.main(testRunner=VppTestRunner)