ip: reassembly-separate feature and custom code
[vpp.git] / test / test_ip6.py
1 #!/usr/bin/env python
2
3 import socket
4 import unittest
5
6 from parameterized import parameterized
7 import scapy.compat
8 import scapy.layers.inet6 as inet6
9 from scapy.contrib.mpls import MPLS
10 from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_RS, \
11     ICMPv6ND_RA, ICMPv6NDOptMTU, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, \
12     ICMPv6ND_NA, ICMPv6NDOptDstLLAddr, ICMPv6DestUnreach, icmp6types, \
13     ICMPv6TimeExceeded, ICMPv6EchoRequest, ICMPv6EchoReply, IPv6ExtHdrHopByHop
14 from scapy.layers.l2 import Ether, Dot1Q
15 from scapy.packet import Raw
16 from scapy.utils import inet_pton, inet_ntop
17 from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ptop, in6_islladdr, \
18     in6_mactoifaceid
19 from six import moves
20
21 from framework import VppTestCase, VppTestRunner
22 from util import ppp, ip6_normalize, mk_ll_addr
23 from vpp_ip import DpoProto
24 from vpp_ip_route import VppIpRoute, VppRoutePath, find_route, VppIpMRoute, \
25     VppMRoutePath, MRouteItfFlags, MRouteEntryFlags, VppMplsIpBind, \
26     VppMplsRoute, VppMplsTable, VppIpTable
27 from vpp_neighbor import find_nbr, VppNeighbor
28 from vpp_pg_interface import is_ipv6_misc
29 from vpp_sub_interface import VppSubInterface, VppDot1QSubint
30 from ipaddress import IPv6Network, IPv4Network
31
32 AF_INET6 = socket.AF_INET6
33
34 try:
35     text_type = unicode
36 except NameError:
37     text_type = str
38
39 NUM_PKTS = 67
40
41
42 class TestIPv6ND(VppTestCase):
43     def validate_ra(self, intf, rx, dst_ip=None):
44         if not dst_ip:
45             dst_ip = intf.remote_ip6
46
47         # unicasted packets must come to the unicast mac
48         self.assertEqual(rx[Ether].dst, intf.remote_mac)
49
50         # and from the router's MAC
51         self.assertEqual(rx[Ether].src, intf.local_mac)
52
53         # the rx'd RA should be addressed to the sender's source
54         self.assertTrue(rx.haslayer(ICMPv6ND_RA))
55         self.assertEqual(in6_ptop(rx[IPv6].dst),
56                          in6_ptop(dst_ip))
57
58         # and come from the router's link local
59         self.assertTrue(in6_islladdr(rx[IPv6].src))
60         self.assertEqual(in6_ptop(rx[IPv6].src),
61                          in6_ptop(mk_ll_addr(intf.local_mac)))
62
63     def validate_na(self, intf, rx, dst_ip=None, tgt_ip=None):
64         if not dst_ip:
65             dst_ip = intf.remote_ip6
66         if not tgt_ip:
67             dst_ip = intf.local_ip6
68
69         # unicasted packets must come to the unicast mac
70         self.assertEqual(rx[Ether].dst, intf.remote_mac)
71
72         # and from the router's MAC
73         self.assertEqual(rx[Ether].src, intf.local_mac)
74
75         # the rx'd NA should be addressed to the sender's source
76         self.assertTrue(rx.haslayer(ICMPv6ND_NA))
77         self.assertEqual(in6_ptop(rx[IPv6].dst),
78                          in6_ptop(dst_ip))
79
80         # and come from the target address
81         self.assertEqual(
82             in6_ptop(rx[IPv6].src), in6_ptop(tgt_ip))
83
84         # Dest link-layer options should have the router's MAC
85         dll = rx[ICMPv6NDOptDstLLAddr]
86         self.assertEqual(dll.lladdr, intf.local_mac)
87
88     def validate_ns(self, intf, rx, tgt_ip):
89         nsma = in6_getnsma(inet_pton(AF_INET6, tgt_ip))
90         dst_ip = inet_ntop(AF_INET6, nsma)
91
92         # NS is broadcast
93         self.assertEqual(rx[Ether].dst, in6_getnsmac(nsma))
94
95         # and from the router's MAC
96         self.assertEqual(rx[Ether].src, intf.local_mac)
97
98         # the rx'd NS should be addressed to an mcast address
99         # derived from the target address
100         self.assertEqual(
101             in6_ptop(rx[IPv6].dst), in6_ptop(dst_ip))
102
103         # expect the tgt IP in the NS header
104         ns = rx[ICMPv6ND_NS]
105         self.assertEqual(in6_ptop(ns.tgt), in6_ptop(tgt_ip))
106
107         # packet is from the router's local address
108         self.assertEqual(
109             in6_ptop(rx[IPv6].src), intf.local_ip6)
110
111         # Src link-layer options should have the router's MAC
112         sll = rx[ICMPv6NDOptSrcLLAddr]
113         self.assertEqual(sll.lladdr, intf.local_mac)
114
115     def send_and_expect_ra(self, intf, pkts, remark, dst_ip=None,
116                            filter_out_fn=is_ipv6_misc):
117         intf.add_stream(pkts)
118         self.pg_enable_capture(self.pg_interfaces)
119         self.pg_start()
120         rx = intf.get_capture(1, filter_out_fn=filter_out_fn)
121
122         self.assertEqual(len(rx), 1)
123         rx = rx[0]
124         self.validate_ra(intf, rx, dst_ip)
125
126     def send_and_expect_na(self, intf, pkts, remark, dst_ip=None,
127                            tgt_ip=None,
128                            filter_out_fn=is_ipv6_misc):
129         intf.add_stream(pkts)
130         self.pg_enable_capture(self.pg_interfaces)
131         self.pg_start()
132         rx = intf.get_capture(1, filter_out_fn=filter_out_fn)
133
134         self.assertEqual(len(rx), 1)
135         rx = rx[0]
136         self.validate_na(intf, rx, dst_ip, tgt_ip)
137
138     def send_and_expect_ns(self, tx_intf, rx_intf, pkts, tgt_ip,
139                            filter_out_fn=is_ipv6_misc):
140         tx_intf.add_stream(pkts)
141         self.pg_enable_capture(self.pg_interfaces)
142         self.pg_start()
143         rx = rx_intf.get_capture(1, filter_out_fn=filter_out_fn)
144
145         self.assertEqual(len(rx), 1)
146         rx = rx[0]
147         self.validate_ns(rx_intf, rx, tgt_ip)
148
149     def verify_ip(self, rx, smac, dmac, sip, dip):
150         ether = rx[Ether]
151         self.assertEqual(ether.dst, dmac)
152         self.assertEqual(ether.src, smac)
153
154         ip = rx[IPv6]
155         self.assertEqual(ip.src, sip)
156         self.assertEqual(ip.dst, dip)
157
158
159 class TestIPv6(TestIPv6ND):
160     """ IPv6 Test Case """
161
162     @classmethod
163     def setUpClass(cls):
164         super(TestIPv6, cls).setUpClass()
165
166     @classmethod
167     def tearDownClass(cls):
168         super(TestIPv6, cls).tearDownClass()
169
170     def setUp(self):
171         """
172         Perform test setup before test case.
173
174         **Config:**
175             - create 3 pg interfaces
176                 - untagged pg0 interface
177                 - Dot1Q subinterface on pg1
178                 - Dot1AD subinterface on pg2
179             - setup interfaces:
180                 - put it into UP state
181                 - set IPv6 addresses
182                 - resolve neighbor address using NDP
183             - configure 200 fib entries
184
185         :ivar list interfaces: pg interfaces and subinterfaces.
186         :ivar dict flows: IPv4 packet flows in test.
187
188         *TODO:* Create AD sub interface
189         """
190         super(TestIPv6, self).setUp()
191
192         # create 3 pg interfaces
193         self.create_pg_interfaces(range(3))
194
195         # create 2 subinterfaces for p1 and pg2
196         self.sub_interfaces = [
197             VppDot1QSubint(self, self.pg1, 100),
198             VppDot1QSubint(self, self.pg2, 200)
199             # TODO: VppDot1ADSubint(self, self.pg2, 200, 300, 400)
200         ]
201
202         # packet flows mapping pg0 -> pg1.sub, pg2.sub, etc.
203         self.flows = dict()
204         self.flows[self.pg0] = [self.pg1.sub_if, self.pg2.sub_if]
205         self.flows[self.pg1.sub_if] = [self.pg0, self.pg2.sub_if]
206         self.flows[self.pg2.sub_if] = [self.pg0, self.pg1.sub_if]
207
208         # packet sizes
209         self.pg_if_packet_sizes = [64, 1500, 9020]
210
211         self.interfaces = list(self.pg_interfaces)
212         self.interfaces.extend(self.sub_interfaces)
213
214         # setup all interfaces
215         for i in self.interfaces:
216             i.admin_up()
217             i.config_ip6()
218             i.resolve_ndp()
219
220         # config 2M FIB entries
221         self.config_fib_entries(200)
222
223     def tearDown(self):
224         """Run standard test teardown and log ``show ip6 neighbors``."""
225         for i in self.interfaces:
226             i.unconfig_ip6()
227             i.ip6_disable()
228             i.admin_down()
229         for i in self.sub_interfaces:
230             i.remove_vpp_config()
231
232         super(TestIPv6, self).tearDown()
233         if not self.vpp_dead:
234             self.logger.info(self.vapi.cli("show ip6 neighbors"))
235             # info(self.vapi.cli("show ip6 fib"))  # many entries
236
237     def config_fib_entries(self, count):
238         """For each interface add to the FIB table *count* routes to
239         "fd02::1/128" destination with interface's local address as next-hop
240         address.
241
242         :param int count: Number of FIB entries.
243
244         - *TODO:* check if the next-hop address shouldn't be remote address
245           instead of local address.
246         """
247         n_int = len(self.interfaces)
248         percent = 0
249         counter = 0.0
250         dest_addr = inet_pton(AF_INET6, "fd02::1")
251         dest_addr_len = 128
252         for i in self.interfaces:
253             next_hop_address = i.local_ip6n
254             for j in range(count / n_int):
255                 self.vapi.ip_add_del_route(dst_address=dest_addr,
256                                            dst_address_length=dest_addr_len,
257                                            next_hop_address=next_hop_address,
258                                            is_ipv6=1)
259                 counter += 1
260                 if counter / count * 100 > percent:
261                     self.logger.info("Configure %d FIB entries .. %d%% done" %
262                                      (count, percent))
263                     percent += 1
264
265     def modify_packet(self, src_if, packet_size, pkt):
266         """Add load, set destination IP and extend packet to required packet
267         size for defined interface.
268
269         :param VppInterface src_if: Interface to create packet for.
270         :param int packet_size: Required packet size.
271         :param Scapy pkt: Packet to be modified.
272         """
273         dst_if_idx = packet_size / 10 % 2
274         dst_if = self.flows[src_if][dst_if_idx]
275         info = self.create_packet_info(src_if, dst_if)
276         payload = self.info_to_payload(info)
277         p = pkt / Raw(payload)
278         p[IPv6].dst = dst_if.remote_ip6
279         info.data = p.copy()
280         if isinstance(src_if, VppSubInterface):
281             p = src_if.add_dot1_layer(p)
282         self.extend_packet(p, packet_size)
283
284         return p
285
286     def create_stream(self, src_if):
287         """Create input packet stream for defined interface.
288
289         :param VppInterface src_if: Interface to create packet stream for.
290         """
291         hdr_ext = 4 if isinstance(src_if, VppSubInterface) else 0
292         pkt_tmpl = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
293                     IPv6(src=src_if.remote_ip6) /
294                     inet6.UDP(sport=1234, dport=1234))
295
296         pkts = [self.modify_packet(src_if, i, pkt_tmpl)
297                 for i in moves.range(self.pg_if_packet_sizes[0],
298                                      self.pg_if_packet_sizes[1], 10)]
299         pkts_b = [self.modify_packet(src_if, i, pkt_tmpl)
300                   for i in moves.range(self.pg_if_packet_sizes[1] + hdr_ext,
301                                        self.pg_if_packet_sizes[2] + hdr_ext,
302                                        50)]
303         pkts.extend(pkts_b)
304
305         return pkts
306
307     def verify_capture(self, dst_if, capture):
308         """Verify captured input packet stream for defined interface.
309
310         :param VppInterface dst_if: Interface to verify captured packet stream
311                                     for.
312         :param list capture: Captured packet stream.
313         """
314         self.logger.info("Verifying capture on interface %s" % dst_if.name)
315         last_info = dict()
316         for i in self.interfaces:
317             last_info[i.sw_if_index] = None
318         is_sub_if = False
319         dst_sw_if_index = dst_if.sw_if_index
320         if hasattr(dst_if, 'parent'):
321             is_sub_if = True
322         for packet in capture:
323             if is_sub_if:
324                 # Check VLAN tags and Ethernet header
325                 packet = dst_if.remove_dot1_layer(packet)
326             self.assertTrue(Dot1Q not in packet)
327             try:
328                 ip = packet[IPv6]
329                 udp = packet[inet6.UDP]
330                 payload_info = self.payload_to_info(packet[Raw])
331                 packet_index = payload_info.index
332                 self.assertEqual(payload_info.dst, dst_sw_if_index)
333                 self.logger.debug(
334                     "Got packet on port %s: src=%u (id=%u)" %
335                     (dst_if.name, payload_info.src, packet_index))
336                 next_info = self.get_next_packet_info_for_interface2(
337                     payload_info.src, dst_sw_if_index,
338                     last_info[payload_info.src])
339                 last_info[payload_info.src] = next_info
340                 self.assertTrue(next_info is not None)
341                 self.assertEqual(packet_index, next_info.index)
342                 saved_packet = next_info.data
343                 # Check standard fields
344                 self.assertEqual(
345                     ip.src, saved_packet[IPv6].src)
346                 self.assertEqual(
347                     ip.dst, saved_packet[IPv6].dst)
348                 self.assertEqual(
349                     udp.sport, saved_packet[inet6.UDP].sport)
350                 self.assertEqual(
351                     udp.dport, saved_packet[inet6.UDP].dport)
352             except:
353                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
354                 raise
355         for i in self.interfaces:
356             remaining_packet = self.get_next_packet_info_for_interface2(
357                 i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index])
358             self.assertTrue(remaining_packet is None,
359                             "Interface %s: Packet expected from interface %s "
360                             "didn't arrive" % (dst_if.name, i.name))
361
362     def test_next_header_anomaly(self):
363         """ IPv6 next header anomaly test
364
365         Test scenario:
366             - ipv6 next header field = Fragment Header (44)
367             - next header is ICMPv6 Echo Request
368             - wait for reassembly
369         """
370         pkt = (Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) /
371                IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44) /
372                ICMPv6EchoRequest())
373
374         self.pg0.add_stream(pkt)
375         self.pg_start()
376
377         # wait for reassembly
378         self.sleep(10)
379
380     def test_fib(self):
381         """ IPv6 FIB test
382
383         Test scenario:
384             - Create IPv6 stream for pg0 interface
385             - Create IPv6 tagged streams for pg1's and pg2's subinterface.
386             - Send and verify received packets on each interface.
387         """
388
389         pkts = self.create_stream(self.pg0)
390         self.pg0.add_stream(pkts)
391
392         for i in self.sub_interfaces:
393             pkts = self.create_stream(i)
394             i.parent.add_stream(pkts)
395
396         self.pg_enable_capture(self.pg_interfaces)
397         self.pg_start()
398
399         pkts = self.pg0.get_capture()
400         self.verify_capture(self.pg0, pkts)
401
402         for i in self.sub_interfaces:
403             pkts = i.parent.get_capture()
404             self.verify_capture(i, pkts)
405
406     def test_ns(self):
407         """ IPv6 Neighbour Solicitation Exceptions
408
409         Test scenario:
410            - Send an NS Sourced from an address not covered by the link sub-net
411            - Send an NS to an mcast address the router has not joined
412            - Send NS for a target address the router does not onn.
413         """
414
415         #
416         # An NS from a non link source address
417         #
418         nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6))
419         d = inet_ntop(AF_INET6, nsma)
420
421         p = (Ether(dst=in6_getnsmac(nsma)) /
422              IPv6(dst=d, src="2002::2") /
423              ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
424              ICMPv6NDOptSrcLLAddr(
425                  lladdr=self.pg0.remote_mac))
426         pkts = [p]
427
428         self.send_and_assert_no_replies(
429             self.pg0, pkts,
430             "No response to NS source by address not on sub-net")
431
432         #
433         # An NS for sent to a solicited mcast group the router is
434         # not a member of FAILS
435         #
436         if 0:
437             nsma = in6_getnsma(inet_pton(AF_INET6, "fd::ffff"))
438             d = inet_ntop(AF_INET6, nsma)
439
440             p = (Ether(dst=in6_getnsmac(nsma)) /
441                  IPv6(dst=d, src=self.pg0.remote_ip6) /
442                  ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
443                  ICMPv6NDOptSrcLLAddr(
444                      lladdr=self.pg0.remote_mac))
445             pkts = [p]
446
447             self.send_and_assert_no_replies(
448                 self.pg0, pkts,
449                 "No response to NS sent to unjoined mcast address")
450
451         #
452         # An NS whose target address is one the router does not own
453         #
454         nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6))
455         d = inet_ntop(AF_INET6, nsma)
456
457         p = (Ether(dst=in6_getnsmac(nsma)) /
458              IPv6(dst=d, src=self.pg0.remote_ip6) /
459              ICMPv6ND_NS(tgt="fd::ffff") /
460              ICMPv6NDOptSrcLLAddr(
461                  lladdr=self.pg0.remote_mac))
462         pkts = [p]
463
464         self.send_and_assert_no_replies(self.pg0, pkts,
465                                         "No response to NS for unknown target")
466
467         #
468         # A neighbor entry that has no associated FIB-entry
469         #
470         self.pg0.generate_remote_hosts(4)
471         nd_entry = VppNeighbor(self,
472                                self.pg0.sw_if_index,
473                                self.pg0.remote_hosts[2].mac,
474                                self.pg0.remote_hosts[2].ip6,
475                                is_no_fib_entry=1)
476         nd_entry.add_vpp_config()
477
478         #
479         # check we have the neighbor, but no route
480         #
481         self.assertTrue(find_nbr(self,
482                                  self.pg0.sw_if_index,
483                                  self.pg0._remote_hosts[2].ip6))
484         self.assertFalse(find_route(self,
485                                     self.pg0._remote_hosts[2].ip6,
486                                     128,
487                                     inet=AF_INET6))
488
489         #
490         # send an NS from a link local address to the interface's global
491         # address
492         #
493         p = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) /
494              IPv6(
495                  dst=d, src=self.pg0._remote_hosts[2].ip6_ll) /
496              ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
497              ICMPv6NDOptSrcLLAddr(
498                  lladdr=self.pg0.remote_mac))
499
500         self.send_and_expect_na(self.pg0, p,
501                                 "NS from link-local",
502                                 dst_ip=self.pg0._remote_hosts[2].ip6_ll,
503                                 tgt_ip=self.pg0.local_ip6)
504
505         #
506         # we should have learned an ND entry for the peer's link-local
507         # but not inserted a route to it in the FIB
508         #
509         self.assertTrue(find_nbr(self,
510                                  self.pg0.sw_if_index,
511                                  self.pg0._remote_hosts[2].ip6_ll))
512         self.assertFalse(find_route(self,
513                                     self.pg0._remote_hosts[2].ip6_ll,
514                                     128,
515                                     inet=AF_INET6))
516
517         #
518         # An NS to the router's own Link-local
519         #
520         p = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) /
521              IPv6(
522                  dst=d, src=self.pg0._remote_hosts[3].ip6_ll) /
523              ICMPv6ND_NS(tgt=self.pg0.local_ip6_ll) /
524              ICMPv6NDOptSrcLLAddr(
525                  lladdr=self.pg0.remote_mac))
526
527         self.send_and_expect_na(self.pg0, p,
528                                 "NS to/from link-local",
529                                 dst_ip=self.pg0._remote_hosts[3].ip6_ll,
530                                 tgt_ip=self.pg0.local_ip6_ll)
531
532         #
533         # we should have learned an ND entry for the peer's link-local
534         # but not inserted a route to it in the FIB
535         #
536         self.assertTrue(find_nbr(self,
537                                  self.pg0.sw_if_index,
538                                  self.pg0._remote_hosts[3].ip6_ll))
539         self.assertFalse(find_route(self,
540                                     self.pg0._remote_hosts[3].ip6_ll,
541                                     128,
542                                     inet=AF_INET6))
543
544     def test_ns_duplicates(self):
545         """ ND Duplicates"""
546
547         #
548         # Generate some hosts on the LAN
549         #
550         self.pg1.generate_remote_hosts(3)
551
552         #
553         # Add host 1 on pg1 and pg2
554         #
555         ns_pg1 = VppNeighbor(self,
556                              self.pg1.sw_if_index,
557                              self.pg1.remote_hosts[1].mac,
558                              self.pg1.remote_hosts[1].ip6)
559         ns_pg1.add_vpp_config()
560         ns_pg2 = VppNeighbor(self,
561                              self.pg2.sw_if_index,
562                              self.pg2.remote_mac,
563                              self.pg1.remote_hosts[1].ip6)
564         ns_pg2.add_vpp_config()
565
566         #
567         # IP packet destined for pg1 remote host arrives on pg1 again.
568         #
569         p = (Ether(dst=self.pg0.local_mac,
570                    src=self.pg0.remote_mac) /
571              IPv6(src=self.pg0.remote_ip6,
572                   dst=self.pg1.remote_hosts[1].ip6) /
573              inet6.UDP(sport=1234, dport=1234) /
574              Raw())
575
576         self.pg0.add_stream(p)
577         self.pg_enable_capture(self.pg_interfaces)
578         self.pg_start()
579
580         rx1 = self.pg1.get_capture(1)
581
582         self.verify_ip(rx1[0],
583                        self.pg1.local_mac,
584                        self.pg1.remote_hosts[1].mac,
585                        self.pg0.remote_ip6,
586                        self.pg1.remote_hosts[1].ip6)
587
588         #
589         # remove the duplicate on pg1
590         # packet stream should generate NSs out of pg1
591         #
592         ns_pg1.remove_vpp_config()
593
594         self.send_and_expect_ns(self.pg0, self.pg1,
595                                 p, self.pg1.remote_hosts[1].ip6)
596
597         #
598         # Add it back
599         #
600         ns_pg1.add_vpp_config()
601
602         self.pg0.add_stream(p)
603         self.pg_enable_capture(self.pg_interfaces)
604         self.pg_start()
605
606         rx1 = self.pg1.get_capture(1)
607
608         self.verify_ip(rx1[0],
609                        self.pg1.local_mac,
610                        self.pg1.remote_hosts[1].mac,
611                        self.pg0.remote_ip6,
612                        self.pg1.remote_hosts[1].ip6)
613
614     def validate_ra(self, intf, rx, dst_ip=None, mtu=9000, pi_opt=None):
615         if not dst_ip:
616             dst_ip = intf.remote_ip6
617
618         # unicasted packets must come to the unicast mac
619         self.assertEqual(rx[Ether].dst, intf.remote_mac)
620
621         # and from the router's MAC
622         self.assertEqual(rx[Ether].src, intf.local_mac)
623
624         # the rx'd RA should be addressed to the sender's source
625         self.assertTrue(rx.haslayer(ICMPv6ND_RA))
626         self.assertEqual(in6_ptop(rx[IPv6].dst),
627                          in6_ptop(dst_ip))
628
629         # and come from the router's link local
630         self.assertTrue(in6_islladdr(rx[IPv6].src))
631         self.assertEqual(in6_ptop(rx[IPv6].src),
632                          in6_ptop(mk_ll_addr(intf.local_mac)))
633
634         # it should contain the links MTU
635         ra = rx[ICMPv6ND_RA]
636         self.assertEqual(ra[ICMPv6NDOptMTU].mtu, mtu)
637
638         # it should contain the source's link layer address option
639         sll = ra[ICMPv6NDOptSrcLLAddr]
640         self.assertEqual(sll.lladdr, intf.local_mac)
641
642         if not pi_opt:
643             # the RA should not contain prefix information
644             self.assertFalse(ra.haslayer(
645                 ICMPv6NDOptPrefixInfo))
646         else:
647             raos = rx.getlayer(ICMPv6NDOptPrefixInfo, 1)
648
649             # the options are nested in the scapy packet in way that i cannot
650             # decipher how to decode. this 1st layer of option always returns
651             # nested classes, so a direct obj1=obj2 comparison always fails.
652             # however, the getlayer(.., 2) does give one instnace.
653             # so we cheat here and construct a new opt instance for comparison
654             rd = ICMPv6NDOptPrefixInfo(
655                 prefixlen=raos.prefixlen,
656                 prefix=raos.prefix,
657                 L=raos.L,
658                 A=raos.A)
659             if type(pi_opt) is list:
660                 for ii in range(len(pi_opt)):
661                     self.assertEqual(pi_opt[ii], rd)
662                     rd = rx.getlayer(
663                         ICMPv6NDOptPrefixInfo, ii + 2)
664             else:
665                 self.assertEqual(pi_opt, raos)
666
667     def send_and_expect_ra(self, intf, pkts, remark, dst_ip=None,
668                            filter_out_fn=is_ipv6_misc,
669                            opt=None):
670         intf.add_stream(pkts)
671         self.pg_enable_capture(self.pg_interfaces)
672         self.pg_start()
673         rx = intf.get_capture(1, filter_out_fn=filter_out_fn)
674
675         self.assertEqual(len(rx), 1)
676         rx = rx[0]
677         self.validate_ra(intf, rx, dst_ip, pi_opt=opt)
678
679     def test_rs(self):
680         """ IPv6 Router Solicitation Exceptions
681
682         Test scenario:
683         """
684
685         #
686         # Before we begin change the IPv6 RA responses to use the unicast
687         # address - that way we will not confuse them with the periodic
688         # RAs which go to the mcast address
689         # Sit and wait for the first periodic RA.
690         #
691         # TODO
692         #
693         self.pg0.ip6_ra_config(send_unicast=1)
694
695         #
696         # An RS from a link source address
697         #  - expect an RA in return
698         #
699         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
700              IPv6(
701                  dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
702              ICMPv6ND_RS())
703         pkts = [p]
704         self.send_and_expect_ra(self.pg0, pkts, "Genuine RS")
705
706         #
707         # For the next RS sent the RA should be rate limited
708         #
709         self.send_and_assert_no_replies(self.pg0, pkts, "RA rate limited")
710
711         #
712         # When we reconfigure the IPv6 RA config,
713         # we reset the RA rate limiting,
714         # so we need to do this before each test below so as not to drop
715         # packets for rate limiting reasons. Test this works here.
716         #
717         self.pg0.ip6_ra_config(send_unicast=1)
718         self.send_and_expect_ra(self.pg0, pkts, "Rate limit reset RS")
719
720         #
721         # An RS sent from a non-link local source
722         #
723         self.pg0.ip6_ra_config(send_unicast=1)
724         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
725              IPv6(dst=self.pg0.local_ip6,
726                   src="2002::ffff") /
727              ICMPv6ND_RS())
728         pkts = [p]
729         self.send_and_assert_no_replies(self.pg0, pkts,
730                                         "RS from non-link source")
731
732         #
733         # Source an RS from a link local address
734         #
735         self.pg0.ip6_ra_config(send_unicast=1)
736         ll = mk_ll_addr(self.pg0.remote_mac)
737         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
738              IPv6(dst=self.pg0.local_ip6, src=ll) /
739              ICMPv6ND_RS())
740         pkts = [p]
741         self.send_and_expect_ra(self.pg0, pkts,
742                                 "RS sourced from link-local",
743                                 dst_ip=ll)
744
745         #
746         # Send the RS multicast
747         #
748         self.pg0.ip6_ra_config(send_unicast=1)
749         dmac = in6_getnsmac(inet_pton(AF_INET6, "ff02::2"))
750         ll = mk_ll_addr(self.pg0.remote_mac)
751         p = (Ether(dst=dmac, src=self.pg0.remote_mac) /
752              IPv6(dst="ff02::2", src=ll) /
753              ICMPv6ND_RS())
754         pkts = [p]
755         self.send_and_expect_ra(self.pg0, pkts,
756                                 "RS sourced from link-local",
757                                 dst_ip=ll)
758
759         #
760         # Source from the unspecified address ::. This happens when the RS
761         # is sent before the host has a configured address/sub-net,
762         # i.e. auto-config. Since the sender has no IP address, the reply
763         # comes back mcast - so the capture needs to not filter this.
764         # If we happen to pick up the periodic RA at this point then so be it,
765         # it's not an error.
766         #
767         self.pg0.ip6_ra_config(send_unicast=1, suppress=1)
768         p = (Ether(dst=dmac, src=self.pg0.remote_mac) /
769              IPv6(dst="ff02::2", src="::") /
770              ICMPv6ND_RS())
771         pkts = [p]
772         self.send_and_expect_ra(self.pg0, pkts,
773                                 "RS sourced from unspecified",
774                                 dst_ip="ff02::1",
775                                 filter_out_fn=None)
776
777         #
778         # Configure The RA to announce the links prefix
779         #
780         self.pg0.ip6_ra_prefix(self.pg0.local_ip6,
781                                self.pg0.local_ip6_prefix_len)
782
783         #
784         # RAs should now contain the prefix information option
785         #
786         opt = ICMPv6NDOptPrefixInfo(
787             prefixlen=self.pg0.local_ip6_prefix_len,
788             prefix=self.pg0.local_ip6,
789             L=1,
790             A=1)
791
792         self.pg0.ip6_ra_config(send_unicast=1)
793         ll = mk_ll_addr(self.pg0.remote_mac)
794         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
795              IPv6(dst=self.pg0.local_ip6, src=ll) /
796              ICMPv6ND_RS())
797         self.send_and_expect_ra(self.pg0, p,
798                                 "RA with prefix-info",
799                                 dst_ip=ll,
800                                 opt=opt)
801
802         #
803         # Change the prefix info to not off-link
804         #  L-flag is clear
805         #
806         self.pg0.ip6_ra_prefix(self.pg0.local_ip6,
807                                self.pg0.local_ip6_prefix_len,
808                                off_link=1)
809
810         opt = ICMPv6NDOptPrefixInfo(
811             prefixlen=self.pg0.local_ip6_prefix_len,
812             prefix=self.pg0.local_ip6,
813             L=0,
814             A=1)
815
816         self.pg0.ip6_ra_config(send_unicast=1)
817         self.send_and_expect_ra(self.pg0, p,
818                                 "RA with Prefix info with L-flag=0",
819                                 dst_ip=ll,
820                                 opt=opt)
821
822         #
823         # Change the prefix info to not off-link, no-autoconfig
824         #  L and A flag are clear in the advert
825         #
826         self.pg0.ip6_ra_prefix(self.pg0.local_ip6,
827                                self.pg0.local_ip6_prefix_len,
828                                off_link=1,
829                                no_autoconfig=1)
830
831         opt = ICMPv6NDOptPrefixInfo(
832             prefixlen=self.pg0.local_ip6_prefix_len,
833             prefix=self.pg0.local_ip6,
834             L=0,
835             A=0)
836
837         self.pg0.ip6_ra_config(send_unicast=1)
838         self.send_and_expect_ra(self.pg0, p,
839                                 "RA with Prefix info with A & L-flag=0",
840                                 dst_ip=ll,
841                                 opt=opt)
842
843         #
844         # Change the flag settings back to the defaults
845         #  L and A flag are set in the advert
846         #
847         self.pg0.ip6_ra_prefix(self.pg0.local_ip6,
848                                self.pg0.local_ip6_prefix_len)
849
850         opt = ICMPv6NDOptPrefixInfo(
851             prefixlen=self.pg0.local_ip6_prefix_len,
852             prefix=self.pg0.local_ip6,
853             L=1,
854             A=1)
855
856         self.pg0.ip6_ra_config(send_unicast=1)
857         self.send_and_expect_ra(self.pg0, p,
858                                 "RA with Prefix info",
859                                 dst_ip=ll,
860                                 opt=opt)
861
862         #
863         # Change the prefix info to not off-link, no-autoconfig
864         #  L and A flag are clear in the advert
865         #
866         self.pg0.ip6_ra_prefix(self.pg0.local_ip6,
867                                self.pg0.local_ip6_prefix_len,
868                                off_link=1,
869                                no_autoconfig=1)
870
871         opt = ICMPv6NDOptPrefixInfo(
872             prefixlen=self.pg0.local_ip6_prefix_len,
873             prefix=self.pg0.local_ip6,
874             L=0,
875             A=0)
876
877         self.pg0.ip6_ra_config(send_unicast=1)
878         self.send_and_expect_ra(self.pg0, p,
879                                 "RA with Prefix info with A & L-flag=0",
880                                 dst_ip=ll,
881                                 opt=opt)
882
883         #
884         # Use the reset to defaults option to revert to defaults
885         #  L and A flag are clear in the advert
886         #
887         self.pg0.ip6_ra_prefix(self.pg0.local_ip6,
888                                self.pg0.local_ip6_prefix_len,
889                                use_default=1)
890
891         opt = ICMPv6NDOptPrefixInfo(
892             prefixlen=self.pg0.local_ip6_prefix_len,
893             prefix=self.pg0.local_ip6,
894             L=1,
895             A=1)
896
897         self.pg0.ip6_ra_config(send_unicast=1)
898         self.send_and_expect_ra(self.pg0, p,
899                                 "RA with Prefix reverted to defaults",
900                                 dst_ip=ll,
901                                 opt=opt)
902
903         #
904         # Advertise Another prefix. With no L-flag/A-flag
905         #
906         self.pg0.ip6_ra_prefix(self.pg1.local_ip6,
907                                self.pg1.local_ip6_prefix_len,
908                                off_link=1,
909                                no_autoconfig=1)
910
911         opt = [ICMPv6NDOptPrefixInfo(
912             prefixlen=self.pg0.local_ip6_prefix_len,
913             prefix=self.pg0.local_ip6,
914             L=1,
915             A=1),
916             ICMPv6NDOptPrefixInfo(
917                 prefixlen=self.pg1.local_ip6_prefix_len,
918                 prefix=self.pg1.local_ip6,
919                 L=0,
920                 A=0)]
921
922         self.pg0.ip6_ra_config(send_unicast=1)
923         ll = mk_ll_addr(self.pg0.remote_mac)
924         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
925              IPv6(dst=self.pg0.local_ip6, src=ll) /
926              ICMPv6ND_RS())
927         self.send_and_expect_ra(self.pg0, p,
928                                 "RA with multiple Prefix infos",
929                                 dst_ip=ll,
930                                 opt=opt)
931
932         #
933         # Remove the first prefix-info - expect the second is still in the
934         # advert
935         #
936         self.pg0.ip6_ra_prefix(self.pg0.local_ip6,
937                                self.pg0.local_ip6_prefix_len,
938                                is_no=1)
939
940         opt = ICMPv6NDOptPrefixInfo(
941             prefixlen=self.pg1.local_ip6_prefix_len,
942             prefix=self.pg1.local_ip6,
943             L=0,
944             A=0)
945
946         self.pg0.ip6_ra_config(send_unicast=1)
947         self.send_and_expect_ra(self.pg0, p,
948                                 "RA with Prefix reverted to defaults",
949                                 dst_ip=ll,
950                                 opt=opt)
951
952         #
953         # Remove the second prefix-info - expect no prefix-info in the adverts
954         #
955         self.pg0.ip6_ra_prefix(self.pg1.local_ip6,
956                                self.pg1.local_ip6_prefix_len,
957                                is_no=1)
958
959         self.pg0.ip6_ra_config(send_unicast=1)
960         self.send_and_expect_ra(self.pg0, p,
961                                 "RA with Prefix reverted to defaults",
962                                 dst_ip=ll)
963
964         #
965         # Reset the periodic advertisements back to default values
966         #
967         self.pg0.ip6_ra_config(no=1, suppress=1, send_unicast=0)
968
969
970 class TestICMPv6Echo(VppTestCase):
971     """ ICMPv6 Echo Test Case """
972
973     @classmethod
974     def setUpClass(cls):
975         super(TestICMPv6Echo, cls).setUpClass()
976
977     @classmethod
978     def tearDownClass(cls):
979         super(TestICMPv6Echo, cls).tearDownClass()
980
981     def setUp(self):
982         super(TestICMPv6Echo, self).setUp()
983
984         # create 1 pg interface
985         self.create_pg_interfaces(range(1))
986
987         for i in self.pg_interfaces:
988             i.admin_up()
989             i.config_ip6()
990             i.resolve_ndp()
991
992     def tearDown(self):
993         super(TestICMPv6Echo, self).tearDown()
994         for i in self.pg_interfaces:
995             i.unconfig_ip6()
996             i.ip6_disable()
997             i.admin_down()
998
999     def test_icmpv6_echo(self):
1000         """ VPP replies to ICMPv6 Echo Request
1001
1002         Test scenario:
1003
1004             - Receive ICMPv6 Echo Request message on pg0 interface.
1005             - Check outgoing ICMPv6 Echo Reply message on pg0 interface.
1006         """
1007
1008         icmpv6_id = 0xb
1009         icmpv6_seq = 5
1010         icmpv6_data = b'\x0a' * 18
1011         p_echo_request = (Ether(src=self.pg0.remote_mac,
1012                                 dst=self.pg0.local_mac) /
1013                           IPv6(src=self.pg0.remote_ip6,
1014                                dst=self.pg0.local_ip6) /
1015                           ICMPv6EchoRequest(
1016                               id=icmpv6_id,
1017                               seq=icmpv6_seq,
1018                               data=icmpv6_data))
1019
1020         self.pg0.add_stream(p_echo_request)
1021         self.pg_enable_capture(self.pg_interfaces)
1022         self.pg_start()
1023
1024         rx = self.pg0.get_capture(1)
1025         rx = rx[0]
1026         ether = rx[Ether]
1027         ipv6 = rx[IPv6]
1028         icmpv6 = rx[ICMPv6EchoReply]
1029
1030         self.assertEqual(ether.src, self.pg0.local_mac)
1031         self.assertEqual(ether.dst, self.pg0.remote_mac)
1032
1033         self.assertEqual(ipv6.src, self.pg0.local_ip6)
1034         self.assertEqual(ipv6.dst, self.pg0.remote_ip6)
1035
1036         self.assertEqual(
1037             icmp6types[icmpv6.type], "Echo Reply")
1038         self.assertEqual(icmpv6.id, icmpv6_id)
1039         self.assertEqual(icmpv6.seq, icmpv6_seq)
1040         self.assertEqual(icmpv6.data, icmpv6_data)
1041
1042
1043 class TestIPv6RD(TestIPv6ND):
1044     """ IPv6 Router Discovery Test Case """
1045
1046     @classmethod
1047     def setUpClass(cls):
1048         super(TestIPv6RD, cls).setUpClass()
1049
1050     @classmethod
1051     def tearDownClass(cls):
1052         super(TestIPv6RD, cls).tearDownClass()
1053
1054     def setUp(self):
1055         super(TestIPv6RD, self).setUp()
1056
1057         # create 2 pg interfaces
1058         self.create_pg_interfaces(range(2))
1059
1060         self.interfaces = list(self.pg_interfaces)
1061
1062         # setup all interfaces
1063         for i in self.interfaces:
1064             i.admin_up()
1065             i.config_ip6()
1066
1067     def tearDown(self):
1068         for i in self.interfaces:
1069             i.unconfig_ip6()
1070             i.admin_down()
1071         super(TestIPv6RD, self).tearDown()
1072
1073     def test_rd_send_router_solicitation(self):
1074         """ Verify router solicitation packets """
1075
1076         count = 2
1077         self.pg_enable_capture(self.pg_interfaces)
1078         self.pg_start()
1079         self.vapi.ip6nd_send_router_solicitation(self.pg1.sw_if_index,
1080                                                  mrc=count)
1081         rx_list = self.pg1.get_capture(count, timeout=3)
1082         self.assertEqual(len(rx_list), count)
1083         for packet in rx_list:
1084             self.assertEqual(packet.haslayer(IPv6), 1)
1085             self.assertEqual(packet[IPv6].haslayer(
1086                 ICMPv6ND_RS), 1)
1087             dst = ip6_normalize(packet[IPv6].dst)
1088             dst2 = ip6_normalize("ff02::2")
1089             self.assert_equal(dst, dst2)
1090             src = ip6_normalize(packet[IPv6].src)
1091             src2 = ip6_normalize(self.pg1.local_ip6_ll)
1092             self.assert_equal(src, src2)
1093             self.assertTrue(
1094                 bool(packet[ICMPv6ND_RS].haslayer(
1095                     ICMPv6NDOptSrcLLAddr)))
1096             self.assert_equal(
1097                 packet[ICMPv6NDOptSrcLLAddr].lladdr,
1098                 self.pg1.local_mac)
1099
1100     def verify_prefix_info(self, reported_prefix, prefix_option):
1101         prefix = IPv6Network(
1102             text_type(prefix_option.getfieldval("prefix") +
1103                       "/" +
1104                       text_type(prefix_option.getfieldval("prefixlen"))),
1105             strict=False)
1106         self.assert_equal(reported_prefix.prefix.network_address,
1107                           prefix.network_address)
1108         L = prefix_option.getfieldval("L")
1109         A = prefix_option.getfieldval("A")
1110         option_flags = (L << 7) | (A << 6)
1111         self.assert_equal(reported_prefix.flags, option_flags)
1112         self.assert_equal(reported_prefix.valid_time,
1113                           prefix_option.getfieldval("validlifetime"))
1114         self.assert_equal(reported_prefix.preferred_time,
1115                           prefix_option.getfieldval("preferredlifetime"))
1116
1117     def test_rd_receive_router_advertisement(self):
1118         """ Verify events triggered by received RA packets """
1119
1120         self.vapi.want_ip6_ra_events()
1121
1122         prefix_info_1 = ICMPv6NDOptPrefixInfo(
1123             prefix="1::2",
1124             prefixlen=50,
1125             validlifetime=200,
1126             preferredlifetime=500,
1127             L=1,
1128             A=1,
1129         )
1130
1131         prefix_info_2 = ICMPv6NDOptPrefixInfo(
1132             prefix="7::4",
1133             prefixlen=20,
1134             validlifetime=70,
1135             preferredlifetime=1000,
1136             L=1,
1137             A=0,
1138         )
1139
1140         p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
1141              IPv6(dst=self.pg1.local_ip6_ll,
1142                   src=mk_ll_addr(self.pg1.remote_mac)) /
1143              ICMPv6ND_RA() /
1144              prefix_info_1 /
1145              prefix_info_2)
1146         self.pg1.add_stream([p])
1147         self.pg_start()
1148
1149         ev = self.vapi.wait_for_event(10, "ip6_ra_event")
1150
1151         self.assert_equal(ev.current_hop_limit, 0)
1152         self.assert_equal(ev.flags, 8)
1153         self.assert_equal(ev.router_lifetime_in_sec, 1800)
1154         self.assert_equal(ev.neighbor_reachable_time_in_msec, 0)
1155         self.assert_equal(
1156             ev.time_in_msec_between_retransmitted_neighbor_solicitations, 0)
1157
1158         self.assert_equal(ev.n_prefixes, 2)
1159
1160         self.verify_prefix_info(ev.prefixes[0], prefix_info_1)
1161         self.verify_prefix_info(ev.prefixes[1], prefix_info_2)
1162
1163
1164 class TestIPv6RDControlPlane(TestIPv6ND):
1165     """ IPv6 Router Discovery Control Plane Test Case """
1166
1167     @classmethod
1168     def setUpClass(cls):
1169         super(TestIPv6RDControlPlane, cls).setUpClass()
1170
1171     @classmethod
1172     def tearDownClass(cls):
1173         super(TestIPv6RDControlPlane, cls).tearDownClass()
1174
1175     def setUp(self):
1176         super(TestIPv6RDControlPlane, self).setUp()
1177
1178         # create 1 pg interface
1179         self.create_pg_interfaces(range(1))
1180
1181         self.interfaces = list(self.pg_interfaces)
1182
1183         # setup all interfaces
1184         for i in self.interfaces:
1185             i.admin_up()
1186             i.config_ip6()
1187
1188     def tearDown(self):
1189         super(TestIPv6RDControlPlane, self).tearDown()
1190
1191     @staticmethod
1192     def create_ra_packet(pg, routerlifetime=None):
1193         src_ip = pg.remote_ip6_ll
1194         dst_ip = pg.local_ip6
1195         if routerlifetime is not None:
1196             ra = ICMPv6ND_RA(routerlifetime=routerlifetime)
1197         else:
1198             ra = ICMPv6ND_RA()
1199         p = (Ether(dst=pg.local_mac, src=pg.remote_mac) /
1200              IPv6(dst=dst_ip, src=src_ip) / ra)
1201         return p
1202
1203     @staticmethod
1204     def get_default_routes(fib):
1205         list = []
1206         for entry in fib:
1207             if entry.address_length == 0:
1208                 for path in entry.path:
1209                     if path.sw_if_index != 0xFFFFFFFF:
1210                         default_route = {}
1211                         default_route['sw_if_index'] = path.sw_if_index
1212                         default_route['next_hop'] = path.next_hop
1213                         list.append(default_route)
1214         return list
1215
1216     @staticmethod
1217     def get_interface_addresses(fib, pg):
1218         list = []
1219         for entry in fib:
1220             if entry.address_length == 128:
1221                 path = entry.path[0]
1222                 if path.sw_if_index == pg.sw_if_index:
1223                     list.append(entry.address)
1224         return list
1225
1226     def test_all(self):
1227         """ Test handling of SLAAC addresses and default routes """
1228
1229         fib = self.vapi.ip6_fib_dump()
1230         default_routes = self.get_default_routes(fib)
1231         initial_addresses = set(self.get_interface_addresses(fib, self.pg0))
1232         self.assertEqual(default_routes, [])
1233         router_address = self.pg0.remote_ip6n_ll
1234
1235         self.vapi.ip6_nd_address_autoconfig(self.pg0.sw_if_index, 1, 1)
1236
1237         self.sleep(0.1)
1238
1239         # send RA
1240         packet = (self.create_ra_packet(
1241             self.pg0) / ICMPv6NDOptPrefixInfo(
1242             prefix="1::",
1243             prefixlen=64,
1244             validlifetime=2,
1245             preferredlifetime=2,
1246             L=1,
1247             A=1,
1248         ) / ICMPv6NDOptPrefixInfo(
1249             prefix="7::",
1250             prefixlen=20,
1251             validlifetime=1500,
1252             preferredlifetime=1000,
1253             L=1,
1254             A=0,
1255         ))
1256         self.pg0.add_stream([packet])
1257         self.pg_start()
1258
1259         self.sleep(0.1)
1260
1261         fib = self.vapi.ip6_fib_dump()
1262
1263         # check FIB for new address
1264         addresses = set(self.get_interface_addresses(fib, self.pg0))
1265         new_addresses = addresses.difference(initial_addresses)
1266         self.assertEqual(len(new_addresses), 1)
1267         prefix = list(new_addresses)[0][:8] + '\0\0\0\0\0\0\0\0'
1268         self.assertEqual(inet_ntop(AF_INET6, prefix), '1::')
1269
1270         # check FIB for new default route
1271         default_routes = self.get_default_routes(fib)
1272         self.assertEqual(len(default_routes), 1)
1273         dr = default_routes[0]
1274         self.assertEqual(dr['sw_if_index'], self.pg0.sw_if_index)
1275         self.assertEqual(dr['next_hop'], router_address)
1276
1277         # send RA to delete default route
1278         packet = self.create_ra_packet(self.pg0, routerlifetime=0)
1279         self.pg0.add_stream([packet])
1280         self.pg_start()
1281
1282         self.sleep(0.1)
1283
1284         # check that default route is deleted
1285         fib = self.vapi.ip6_fib_dump()
1286         default_routes = self.get_default_routes(fib)
1287         self.assertEqual(len(default_routes), 0)
1288
1289         self.sleep(0.1)
1290
1291         # send RA
1292         packet = self.create_ra_packet(self.pg0)
1293         self.pg0.add_stream([packet])
1294         self.pg_start()
1295
1296         self.sleep(0.1)
1297
1298         # check FIB for new default route
1299         fib = self.vapi.ip6_fib_dump()
1300         default_routes = self.get_default_routes(fib)
1301         self.assertEqual(len(default_routes), 1)
1302         dr = default_routes[0]
1303         self.assertEqual(dr['sw_if_index'], self.pg0.sw_if_index)
1304         self.assertEqual(dr['next_hop'], router_address)
1305
1306         # send RA, updating router lifetime to 1s
1307         packet = self.create_ra_packet(self.pg0, 1)
1308         self.pg0.add_stream([packet])
1309         self.pg_start()
1310
1311         self.sleep(0.1)
1312
1313         # check that default route still exists
1314         fib = self.vapi.ip6_fib_dump()
1315         default_routes = self.get_default_routes(fib)
1316         self.assertEqual(len(default_routes), 1)
1317         dr = default_routes[0]
1318         self.assertEqual(dr['sw_if_index'], self.pg0.sw_if_index)
1319         self.assertEqual(dr['next_hop'], router_address)
1320
1321         self.sleep(1)
1322
1323         # check that default route is deleted
1324         fib = self.vapi.ip6_fib_dump()
1325         default_routes = self.get_default_routes(fib)
1326         self.assertEqual(len(default_routes), 0)
1327
1328         # check FIB still contains the SLAAC address
1329         addresses = set(self.get_interface_addresses(fib, self.pg0))
1330         new_addresses = addresses.difference(initial_addresses)
1331
1332         self.assertEqual(len(new_addresses), 1)
1333         prefix = list(new_addresses)[0][:8] + '\0\0\0\0\0\0\0\0'
1334         self.assertEqual(inet_ntop(AF_INET6, prefix), '1::')
1335
1336         self.sleep(1)
1337
1338         # check that SLAAC address is deleted
1339         fib = self.vapi.ip6_fib_dump()
1340         addresses = set(self.get_interface_addresses(fib, self.pg0))
1341         new_addresses = addresses.difference(initial_addresses)
1342         self.assertEqual(len(new_addresses), 0)
1343
1344
1345 class IPv6NDProxyTest(TestIPv6ND):
1346     """ IPv6 ND ProxyTest Case """
1347
1348     @classmethod
1349     def setUpClass(cls):
1350         super(IPv6NDProxyTest, cls).setUpClass()
1351
1352     @classmethod
1353     def tearDownClass(cls):
1354         super(IPv6NDProxyTest, cls).tearDownClass()
1355
1356     def setUp(self):
1357         super(IPv6NDProxyTest, self).setUp()
1358
1359         # create 3 pg interfaces
1360         self.create_pg_interfaces(range(3))
1361
1362         # pg0 is the master interface, with the configured subnet
1363         self.pg0.admin_up()
1364         self.pg0.config_ip6()
1365         self.pg0.resolve_ndp()
1366
1367         self.pg1.ip6_enable()
1368         self.pg2.ip6_enable()
1369
1370     def tearDown(self):
1371         super(IPv6NDProxyTest, self).tearDown()
1372
1373     def test_nd_proxy(self):
1374         """ IPv6 Proxy ND """
1375
1376         #
1377         # Generate some hosts in the subnet that we are proxying
1378         #
1379         self.pg0.generate_remote_hosts(8)
1380
1381         nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6))
1382         d = inet_ntop(AF_INET6, nsma)
1383
1384         #
1385         # Send an NS for one of those remote hosts on one of the proxy links
1386         # expect no response since it's from an address that is not
1387         # on the link that has the prefix configured
1388         #
1389         ns_pg1 = (Ether(dst=in6_getnsmac(nsma), src=self.pg1.remote_mac) /
1390                   IPv6(dst=d,
1391                        src=self.pg0._remote_hosts[2].ip6) /
1392                   ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
1393                   ICMPv6NDOptSrcLLAddr(
1394                       lladdr=self.pg0._remote_hosts[2].mac))
1395
1396         self.send_and_assert_no_replies(self.pg1, ns_pg1, "Off link NS")
1397
1398         #
1399         # Add proxy support for the host
1400         #
1401         self.vapi.ip6nd_proxy_add_del(
1402             ip=inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6),
1403             sw_if_index=self.pg1.sw_if_index)
1404
1405         #
1406         # try that NS again. this time we expect an NA back
1407         #
1408         self.send_and_expect_na(self.pg1, ns_pg1,
1409                                 "NS to proxy entry",
1410                                 dst_ip=self.pg0._remote_hosts[2].ip6,
1411                                 tgt_ip=self.pg0.local_ip6)
1412
1413         #
1414         # ... and that we have an entry in the ND cache
1415         #
1416         self.assertTrue(find_nbr(self,
1417                                  self.pg1.sw_if_index,
1418                                  self.pg0._remote_hosts[2].ip6))
1419
1420         #
1421         # ... and we can route traffic to it
1422         #
1423         t = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
1424              IPv6(dst=self.pg0._remote_hosts[2].ip6,
1425                   src=self.pg0.remote_ip6) /
1426              inet6.UDP(sport=10000, dport=20000) /
1427              Raw('\xa5' * 100))
1428
1429         self.pg0.add_stream(t)
1430         self.pg_enable_capture(self.pg_interfaces)
1431         self.pg_start()
1432         rx = self.pg1.get_capture(1)
1433         rx = rx[0]
1434
1435         self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac)
1436         self.assertEqual(rx[Ether].src, self.pg1.local_mac)
1437
1438         self.assertEqual(rx[IPv6].src,
1439                          t[IPv6].src)
1440         self.assertEqual(rx[IPv6].dst,
1441                          t[IPv6].dst)
1442
1443         #
1444         # Test we proxy for the host on the main interface
1445         #
1446         ns_pg0 = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) /
1447                   IPv6(dst=d, src=self.pg0.remote_ip6) /
1448                   ICMPv6ND_NS(
1449                       tgt=self.pg0._remote_hosts[2].ip6) /
1450                   ICMPv6NDOptSrcLLAddr(
1451                       lladdr=self.pg0.remote_mac))
1452
1453         self.send_and_expect_na(self.pg0, ns_pg0,
1454                                 "NS to proxy entry on main",
1455                                 tgt_ip=self.pg0._remote_hosts[2].ip6,
1456                                 dst_ip=self.pg0.remote_ip6)
1457
1458         #
1459         # Setup and resolve proxy for another host on another interface
1460         #
1461         ns_pg2 = (Ether(dst=in6_getnsmac(nsma), src=self.pg2.remote_mac) /
1462                   IPv6(dst=d,
1463                        src=self.pg0._remote_hosts[3].ip6) /
1464                   ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
1465                   ICMPv6NDOptSrcLLAddr(
1466                       lladdr=self.pg0._remote_hosts[2].mac))
1467
1468         self.vapi.ip6nd_proxy_add_del(
1469             ip=inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6),
1470             sw_if_index=self.pg2.sw_if_index)
1471
1472         self.send_and_expect_na(self.pg2, ns_pg2,
1473                                 "NS to proxy entry other interface",
1474                                 dst_ip=self.pg0._remote_hosts[3].ip6,
1475                                 tgt_ip=self.pg0.local_ip6)
1476
1477         self.assertTrue(find_nbr(self,
1478                                  self.pg2.sw_if_index,
1479                                  self.pg0._remote_hosts[3].ip6))
1480
1481         #
1482         # hosts can communicate. pg2->pg1
1483         #
1484         t2 = (Ether(dst=self.pg2.local_mac,
1485                     src=self.pg0.remote_hosts[3].mac) /
1486               IPv6(dst=self.pg0._remote_hosts[2].ip6,
1487                    src=self.pg0._remote_hosts[3].ip6) /
1488               inet6.UDP(sport=10000, dport=20000) /
1489               Raw('\xa5' * 100))
1490
1491         self.pg2.add_stream(t2)
1492         self.pg_enable_capture(self.pg_interfaces)
1493         self.pg_start()
1494         rx = self.pg1.get_capture(1)
1495         rx = rx[0]
1496
1497         self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac)
1498         self.assertEqual(rx[Ether].src, self.pg1.local_mac)
1499
1500         self.assertEqual(rx[IPv6].src,
1501                          t2[IPv6].src)
1502         self.assertEqual(rx[IPv6].dst,
1503                          t2[IPv6].dst)
1504
1505         #
1506         # remove the proxy configs
1507         #
1508         self.vapi.ip6nd_proxy_add_del(
1509             ip=inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6),
1510             sw_if_index=self.pg1.sw_if_index, is_del=1)
1511         self.vapi.ip6nd_proxy_add_del(
1512             ip=inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6),
1513             sw_if_index=self.pg2.sw_if_index, is_del=1)
1514
1515         self.assertFalse(find_nbr(self,
1516                                   self.pg2.sw_if_index,
1517                                   self.pg0._remote_hosts[3].ip6))
1518         self.assertFalse(find_nbr(self,
1519                                   self.pg1.sw_if_index,
1520                                   self.pg0._remote_hosts[2].ip6))
1521
1522         #
1523         # no longer proxy-ing...
1524         #
1525         self.send_and_assert_no_replies(self.pg0, ns_pg0, "Proxy unconfigured")
1526         self.send_and_assert_no_replies(self.pg1, ns_pg1, "Proxy unconfigured")
1527         self.send_and_assert_no_replies(self.pg2, ns_pg2, "Proxy unconfigured")
1528
1529         #
1530         # no longer forwarding. traffic generates NS out of the glean/main
1531         # interface
1532         #
1533         self.pg2.add_stream(t2)
1534         self.pg_enable_capture(self.pg_interfaces)
1535         self.pg_start()
1536
1537         rx = self.pg0.get_capture(1)
1538
1539         self.assertTrue(rx[0].haslayer(ICMPv6ND_NS))
1540
1541
1542 class TestIPNull(VppTestCase):
1543     """ IPv6 routes via NULL """
1544
1545     @classmethod
1546     def setUpClass(cls):
1547         super(TestIPNull, cls).setUpClass()
1548
1549     @classmethod
1550     def tearDownClass(cls):
1551         super(TestIPNull, cls).tearDownClass()
1552
1553     def setUp(self):
1554         super(TestIPNull, self).setUp()
1555
1556         # create 2 pg interfaces
1557         self.create_pg_interfaces(range(1))
1558
1559         for i in self.pg_interfaces:
1560             i.admin_up()
1561             i.config_ip6()
1562             i.resolve_ndp()
1563
1564     def tearDown(self):
1565         super(TestIPNull, self).tearDown()
1566         for i in self.pg_interfaces:
1567             i.unconfig_ip6()
1568             i.admin_down()
1569
1570     def test_ip_null(self):
1571         """ IP NULL route """
1572
1573         p = (Ether(src=self.pg0.remote_mac,
1574                    dst=self.pg0.local_mac) /
1575              IPv6(src=self.pg0.remote_ip6, dst="2001::1") /
1576              inet6.UDP(sport=1234, dport=1234) /
1577              Raw('\xa5' * 100))
1578
1579         #
1580         # A route via IP NULL that will reply with ICMP unreachables
1581         #
1582         ip_unreach = VppIpRoute(self, "2001::", 64, [], is_unreach=1, is_ip6=1)
1583         ip_unreach.add_vpp_config()
1584
1585         self.pg0.add_stream(p)
1586         self.pg_enable_capture(self.pg_interfaces)
1587         self.pg_start()
1588
1589         rx = self.pg0.get_capture(1)
1590         rx = rx[0]
1591         icmp = rx[ICMPv6DestUnreach]
1592
1593         # 0 = "No route to destination"
1594         self.assertEqual(icmp.code, 0)
1595
1596         # ICMP is rate limited. pause a bit
1597         self.sleep(1)
1598
1599         #
1600         # A route via IP NULL that will reply with ICMP prohibited
1601         #
1602         ip_prohibit = VppIpRoute(self, "2001::1", 128, [],
1603                                  is_prohibit=1, is_ip6=1)
1604         ip_prohibit.add_vpp_config()
1605
1606         self.pg0.add_stream(p)
1607         self.pg_enable_capture(self.pg_interfaces)
1608         self.pg_start()
1609
1610         rx = self.pg0.get_capture(1)
1611         rx = rx[0]
1612         icmp = rx[ICMPv6DestUnreach]
1613
1614         # 1 = "Communication with destination administratively prohibited"
1615         self.assertEqual(icmp.code, 1)
1616
1617
1618 class TestIPDisabled(VppTestCase):
1619     """ IPv6 disabled """
1620
1621     @classmethod
1622     def setUpClass(cls):
1623         super(TestIPDisabled, cls).setUpClass()
1624
1625     @classmethod
1626     def tearDownClass(cls):
1627         super(TestIPDisabled, cls).tearDownClass()
1628
1629     def setUp(self):
1630         super(TestIPDisabled, self).setUp()
1631
1632         # create 2 pg interfaces
1633         self.create_pg_interfaces(range(2))
1634
1635         # PG0 is IP enabled
1636         self.pg0.admin_up()
1637         self.pg0.config_ip6()
1638         self.pg0.resolve_ndp()
1639
1640         # PG 1 is not IP enabled
1641         self.pg1.admin_up()
1642
1643     def tearDown(self):
1644         super(TestIPDisabled, self).tearDown()
1645         for i in self.pg_interfaces:
1646             i.unconfig_ip4()
1647             i.admin_down()
1648
1649     def test_ip_disabled(self):
1650         """ IP Disabled """
1651
1652         #
1653         # An (S,G).
1654         # one accepting interface, pg0, 2 forwarding interfaces
1655         #
1656         route_ff_01 = VppIpMRoute(
1657             self,
1658             "::",
1659             "ffef::1", 128,
1660             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
1661             [VppMRoutePath(self.pg1.sw_if_index,
1662                            MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
1663              VppMRoutePath(self.pg0.sw_if_index,
1664                            MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
1665             is_ip6=1)
1666         route_ff_01.add_vpp_config()
1667
1668         pu = (Ether(src=self.pg1.remote_mac,
1669                     dst=self.pg1.local_mac) /
1670               IPv6(src="2001::1", dst=self.pg0.remote_ip6) /
1671               inet6.UDP(sport=1234, dport=1234) /
1672               Raw('\xa5' * 100))
1673         pm = (Ether(src=self.pg1.remote_mac,
1674                     dst=self.pg1.local_mac) /
1675               IPv6(src="2001::1", dst="ffef::1") /
1676               inet6.UDP(sport=1234, dport=1234) /
1677               Raw('\xa5' * 100))
1678
1679         #
1680         # PG1 does not forward IP traffic
1681         #
1682         self.send_and_assert_no_replies(self.pg1, pu, "IPv6 disabled")
1683         self.send_and_assert_no_replies(self.pg1, pm, "IPv6 disabled")
1684
1685         #
1686         # IP enable PG1
1687         #
1688         self.pg1.config_ip6()
1689
1690         #
1691         # Now we get packets through
1692         #
1693         self.pg1.add_stream(pu)
1694         self.pg_enable_capture(self.pg_interfaces)
1695         self.pg_start()
1696         rx = self.pg0.get_capture(1)
1697
1698         self.pg1.add_stream(pm)
1699         self.pg_enable_capture(self.pg_interfaces)
1700         self.pg_start()
1701         rx = self.pg0.get_capture(1)
1702
1703         #
1704         # Disable PG1
1705         #
1706         self.pg1.unconfig_ip6()
1707
1708         #
1709         # PG1 does not forward IP traffic
1710         #
1711         self.send_and_assert_no_replies(self.pg1, pu, "IPv6 disabled")
1712         self.send_and_assert_no_replies(self.pg1, pm, "IPv6 disabled")
1713
1714
1715 class TestIP6LoadBalance(VppTestCase):
1716     """ IPv6 Load-Balancing """
1717
1718     @classmethod
1719     def setUpClass(cls):
1720         super(TestIP6LoadBalance, cls).setUpClass()
1721
1722     @classmethod
1723     def tearDownClass(cls):
1724         super(TestIP6LoadBalance, cls).tearDownClass()
1725
1726     def setUp(self):
1727         super(TestIP6LoadBalance, self).setUp()
1728
1729         self.create_pg_interfaces(range(5))
1730
1731         mpls_tbl = VppMplsTable(self, 0)
1732         mpls_tbl.add_vpp_config()
1733
1734         for i in self.pg_interfaces:
1735             i.admin_up()
1736             i.config_ip6()
1737             i.resolve_ndp()
1738             i.enable_mpls()
1739
1740     def tearDown(self):
1741         for i in self.pg_interfaces:
1742             i.unconfig_ip6()
1743             i.admin_down()
1744             i.disable_mpls()
1745         super(TestIP6LoadBalance, self).tearDown()
1746
1747     def pg_send(self, input, pkts):
1748         self.vapi.cli("clear trace")
1749         input.add_stream(pkts)
1750         self.pg_enable_capture(self.pg_interfaces)
1751         self.pg_start()
1752
1753     def send_and_expect_load_balancing(self, input, pkts, outputs):
1754         self.pg_send(input, pkts)
1755         for oo in outputs:
1756             rx = oo._get_capture(1)
1757             self.assertNotEqual(0, len(rx))
1758
1759     def send_and_expect_one_itf(self, input, pkts, itf):
1760         self.pg_send(input, pkts)
1761         rx = itf.get_capture(len(pkts))
1762
1763     def test_ip6_load_balance(self):
1764         """ IPv6 Load-Balancing """
1765
1766         #
1767         # An array of packets that differ only in the destination port
1768         #  - IP only
1769         #  - MPLS EOS
1770         #  - MPLS non-EOS
1771         #  - MPLS non-EOS with an entropy label
1772         #
1773         port_ip_pkts = []
1774         port_mpls_pkts = []
1775         port_mpls_neos_pkts = []
1776         port_ent_pkts = []
1777
1778         #
1779         # An array of packets that differ only in the source address
1780         #
1781         src_ip_pkts = []
1782         src_mpls_pkts = []
1783
1784         for ii in range(NUM_PKTS):
1785             port_ip_hdr = (
1786                 IPv6(dst="3000::1", src="3000:1::1") /
1787                 inet6.UDP(sport=1234, dport=1234 + ii) /
1788                 Raw('\xa5' * 100))
1789             port_ip_pkts.append((Ether(src=self.pg0.remote_mac,
1790                                        dst=self.pg0.local_mac) /
1791                                  port_ip_hdr))
1792             port_mpls_pkts.append((Ether(src=self.pg0.remote_mac,
1793                                          dst=self.pg0.local_mac) /
1794                                    MPLS(label=66, ttl=2) /
1795                                    port_ip_hdr))
1796             port_mpls_neos_pkts.append((Ether(src=self.pg0.remote_mac,
1797                                               dst=self.pg0.local_mac) /
1798                                         MPLS(label=67, ttl=2) /
1799                                         MPLS(label=77, ttl=2) /
1800                                         port_ip_hdr))
1801             port_ent_pkts.append((Ether(src=self.pg0.remote_mac,
1802                                         dst=self.pg0.local_mac) /
1803                                   MPLS(label=67, ttl=2) /
1804                                   MPLS(label=14, ttl=2) /
1805                                   MPLS(label=999, ttl=2) /
1806                                   port_ip_hdr))
1807             src_ip_hdr = (
1808                 IPv6(dst="3000::1", src="3000:1::%d" % ii) /
1809                 inet6.UDP(sport=1234, dport=1234) /
1810                 Raw('\xa5' * 100))
1811             src_ip_pkts.append((Ether(src=self.pg0.remote_mac,
1812                                       dst=self.pg0.local_mac) /
1813                                 src_ip_hdr))
1814             src_mpls_pkts.append((Ether(src=self.pg0.remote_mac,
1815                                         dst=self.pg0.local_mac) /
1816                                   MPLS(label=66, ttl=2) /
1817                                   src_ip_hdr))
1818
1819         #
1820         # A route for the IP packets
1821         #
1822         route_3000_1 = VppIpRoute(self, "3000::1", 128,
1823                                   [VppRoutePath(self.pg1.remote_ip6,
1824                                                 self.pg1.sw_if_index,
1825                                                 proto=DpoProto.DPO_PROTO_IP6),
1826                                    VppRoutePath(self.pg2.remote_ip6,
1827                                                 self.pg2.sw_if_index,
1828                                                 proto=DpoProto.DPO_PROTO_IP6)],
1829                                   is_ip6=1)
1830         route_3000_1.add_vpp_config()
1831
1832         #
1833         # a local-label for the EOS packets
1834         #
1835         binding = VppMplsIpBind(self, 66, "3000::1", 128, is_ip6=1)
1836         binding.add_vpp_config()
1837
1838         #
1839         # An MPLS route for the non-EOS packets
1840         #
1841         route_67 = VppMplsRoute(self, 67, 0,
1842                                 [VppRoutePath(self.pg1.remote_ip6,
1843                                               self.pg1.sw_if_index,
1844                                               labels=[67],
1845                                               proto=DpoProto.DPO_PROTO_IP6),
1846                                  VppRoutePath(self.pg2.remote_ip6,
1847                                               self.pg2.sw_if_index,
1848                                               labels=[67],
1849                                               proto=DpoProto.DPO_PROTO_IP6)])
1850         route_67.add_vpp_config()
1851
1852         #
1853         # inject the packet on pg0 - expect load-balancing across the 2 paths
1854         #  - since the default hash config is to use IP src,dst and port
1855         #    src,dst
1856         # We are not going to ensure equal amounts of packets across each link,
1857         # since the hash algorithm is statistical and therefore this can never
1858         # be guaranteed. But with 64 different packets we do expect some
1859         # balancing. So instead just ensure there is traffic on each link.
1860         #
1861         self.send_and_expect_load_balancing(self.pg0, port_ip_pkts,
1862                                             [self.pg1, self.pg2])
1863         self.send_and_expect_load_balancing(self.pg0, src_ip_pkts,
1864                                             [self.pg1, self.pg2])
1865         self.send_and_expect_load_balancing(self.pg0, port_mpls_pkts,
1866                                             [self.pg1, self.pg2])
1867         self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts,
1868                                             [self.pg1, self.pg2])
1869         self.send_and_expect_load_balancing(self.pg0, port_mpls_neos_pkts,
1870                                             [self.pg1, self.pg2])
1871
1872         #
1873         # The packets with Entropy label in should not load-balance,
1874         # since the Entropy value is fixed.
1875         #
1876         self.send_and_expect_one_itf(self.pg0, port_ent_pkts, self.pg1)
1877
1878         #
1879         # change the flow hash config so it's only IP src,dst
1880         #  - now only the stream with differing source address will
1881         #    load-balance
1882         #
1883         self.vapi.set_ip_flow_hash(vrf_id=0, src=1, dst=1, sport=0, dport=0,
1884                                    is_ipv6=1)
1885
1886         self.send_and_expect_load_balancing(self.pg0, src_ip_pkts,
1887                                             [self.pg1, self.pg2])
1888         self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts,
1889                                             [self.pg1, self.pg2])
1890         self.send_and_expect_one_itf(self.pg0, port_ip_pkts, self.pg2)
1891
1892         #
1893         # change the flow hash config back to defaults
1894         #
1895         self.vapi.set_ip_flow_hash(vrf_id=0, src=1, dst=1, sport=1, dport=1,
1896                                    is_ipv6=1)
1897
1898         #
1899         # Recursive prefixes
1900         #  - testing that 2 stages of load-balancing occurs and there is no
1901         #    polarisation (i.e. only 2 of 4 paths are used)
1902         #
1903         port_pkts = []
1904         src_pkts = []
1905
1906         for ii in range(257):
1907             port_pkts.append((Ether(src=self.pg0.remote_mac,
1908                                     dst=self.pg0.local_mac) /
1909                               IPv6(dst="4000::1",
1910                                    src="4000:1::1") /
1911                               inet6.UDP(sport=1234,
1912                                         dport=1234 + ii) /
1913                               Raw('\xa5' * 100)))
1914             src_pkts.append((Ether(src=self.pg0.remote_mac,
1915                                    dst=self.pg0.local_mac) /
1916                              IPv6(dst="4000::1",
1917                                   src="4000:1::%d" % ii) /
1918                              inet6.UDP(sport=1234, dport=1234) /
1919                              Raw('\xa5' * 100)))
1920
1921         route_3000_2 = VppIpRoute(self, "3000::2", 128,
1922                                   [VppRoutePath(self.pg3.remote_ip6,
1923                                                 self.pg3.sw_if_index,
1924                                                 proto=DpoProto.DPO_PROTO_IP6),
1925                                    VppRoutePath(self.pg4.remote_ip6,
1926                                                 self.pg4.sw_if_index,
1927                                                 proto=DpoProto.DPO_PROTO_IP6)],
1928                                   is_ip6=1)
1929         route_3000_2.add_vpp_config()
1930
1931         route_4000_1 = VppIpRoute(self, "4000::1", 128,
1932                                   [VppRoutePath("3000::1",
1933                                                 0xffffffff,
1934                                                 proto=DpoProto.DPO_PROTO_IP6),
1935                                    VppRoutePath("3000::2",
1936                                                 0xffffffff,
1937                                                 proto=DpoProto.DPO_PROTO_IP6)],
1938                                   is_ip6=1)
1939         route_4000_1.add_vpp_config()
1940
1941         #
1942         # inject the packet on pg0 - expect load-balancing across all 4 paths
1943         #
1944         self.vapi.cli("clear trace")
1945         self.send_and_expect_load_balancing(self.pg0, port_pkts,
1946                                             [self.pg1, self.pg2,
1947                                              self.pg3, self.pg4])
1948         self.send_and_expect_load_balancing(self.pg0, src_pkts,
1949                                             [self.pg1, self.pg2,
1950                                              self.pg3, self.pg4])
1951
1952         #
1953         # Recursive prefixes
1954         #  - testing that 2 stages of load-balancing no choices
1955         #
1956         port_pkts = []
1957
1958         for ii in range(257):
1959             port_pkts.append((Ether(src=self.pg0.remote_mac,
1960                                     dst=self.pg0.local_mac) /
1961                               IPv6(dst="6000::1",
1962                                    src="6000:1::1") /
1963                               inet6.UDP(sport=1234,
1964                                         dport=1234 + ii) /
1965                               Raw('\xa5' * 100)))
1966
1967         route_5000_2 = VppIpRoute(self, "5000::2", 128,
1968                                   [VppRoutePath(self.pg3.remote_ip6,
1969                                                 self.pg3.sw_if_index,
1970                                                 proto=DpoProto.DPO_PROTO_IP6)],
1971                                   is_ip6=1)
1972         route_5000_2.add_vpp_config()
1973
1974         route_6000_1 = VppIpRoute(self, "6000::1", 128,
1975                                   [VppRoutePath("5000::2",
1976                                                 0xffffffff,
1977                                                 proto=DpoProto.DPO_PROTO_IP6)],
1978                                   is_ip6=1)
1979         route_6000_1.add_vpp_config()
1980
1981         #
1982         # inject the packet on pg0 - expect load-balancing across all 4 paths
1983         #
1984         self.vapi.cli("clear trace")
1985         self.send_and_expect_one_itf(self.pg0, port_pkts, self.pg3)
1986
1987
1988 class TestIP6Punt(VppTestCase):
1989     """ IPv6 Punt Police/Redirect """
1990
1991     @classmethod
1992     def setUpClass(cls):
1993         super(TestIP6Punt, cls).setUpClass()
1994
1995     @classmethod
1996     def tearDownClass(cls):
1997         super(TestIP6Punt, cls).tearDownClass()
1998
1999     def setUp(self):
2000         super(TestIP6Punt, self).setUp()
2001
2002         self.create_pg_interfaces(range(4))
2003
2004         for i in self.pg_interfaces:
2005             i.admin_up()
2006             i.config_ip6()
2007             i.resolve_ndp()
2008
2009     def tearDown(self):
2010         super(TestIP6Punt, self).tearDown()
2011         for i in self.pg_interfaces:
2012             i.unconfig_ip6()
2013             i.admin_down()
2014
2015     def test_ip_punt(self):
2016         """ IP6 punt police and redirect """
2017
2018         p = (Ether(src=self.pg0.remote_mac,
2019                    dst=self.pg0.local_mac) /
2020              IPv6(src=self.pg0.remote_ip6,
2021                   dst=self.pg0.local_ip6) /
2022              inet6.TCP(sport=1234, dport=1234) /
2023              Raw('\xa5' * 100))
2024
2025         pkts = p * 1025
2026
2027         #
2028         # Configure a punt redirect via pg1.
2029         #
2030         nh_addr = self.pg1.remote_ip6
2031         self.vapi.ip_punt_redirect(self.pg0.sw_if_index,
2032                                    self.pg1.sw_if_index,
2033                                    nh_addr)
2034
2035         self.send_and_expect(self.pg0, pkts, self.pg1)
2036
2037         #
2038         # add a policer
2039         #
2040         policer = self.vapi.policer_add_del(b"ip6-punt", 400, 0, 10, 0,
2041                                             rate_type=1)
2042         self.vapi.ip_punt_police(policer.policer_index, is_ip6=1)
2043
2044         self.vapi.cli("clear trace")
2045         self.pg0.add_stream(pkts)
2046         self.pg_enable_capture(self.pg_interfaces)
2047         self.pg_start()
2048
2049         #
2050         # the number of packet received should be greater than 0,
2051         # but not equal to the number sent, since some were policed
2052         #
2053         rx = self.pg1._get_capture(1)
2054         self.assertGreater(len(rx), 0)
2055         self.assertLess(len(rx), len(pkts))
2056
2057         #
2058         # remove the policer. back to full rx
2059         #
2060         self.vapi.ip_punt_police(policer.policer_index, is_add=0, is_ip6=1)
2061         self.vapi.policer_add_del(b"ip6-punt", 400, 0, 10, 0,
2062                                   rate_type=1, is_add=0)
2063         self.send_and_expect(self.pg0, pkts, self.pg1)
2064
2065         #
2066         # remove the redirect. expect full drop.
2067         #
2068         self.vapi.ip_punt_redirect(self.pg0.sw_if_index,
2069                                    self.pg1.sw_if_index,
2070                                    nh_addr,
2071                                    is_add=0)
2072         self.send_and_assert_no_replies(self.pg0, pkts,
2073                                         "IP no punt config")
2074
2075         #
2076         # Add a redirect that is not input port selective
2077         #
2078         self.vapi.ip_punt_redirect(0xffffffff,
2079                                    self.pg1.sw_if_index,
2080                                    nh_addr)
2081         self.send_and_expect(self.pg0, pkts, self.pg1)
2082
2083         self.vapi.ip_punt_redirect(0xffffffff,
2084                                    self.pg1.sw_if_index,
2085                                    nh_addr,
2086                                    is_add=0)
2087
2088     def test_ip_punt_dump(self):
2089         """ IP6 punt redirect dump"""
2090
2091         #
2092         # Configure a punt redirects
2093         #
2094         nh_addr = self.pg3.remote_ip6
2095         self.vapi.ip_punt_redirect(self.pg0.sw_if_index,
2096                                    self.pg3.sw_if_index,
2097                                    nh_addr)
2098         self.vapi.ip_punt_redirect(self.pg1.sw_if_index,
2099                                    self.pg3.sw_if_index,
2100                                    nh_addr)
2101         self.vapi.ip_punt_redirect(self.pg2.sw_if_index,
2102                                    self.pg3.sw_if_index,
2103                                    '0::0')
2104
2105         #
2106         # Dump pg0 punt redirects
2107         #
2108         punts = self.vapi.ip_punt_redirect_dump(self.pg0.sw_if_index,
2109                                                 is_ipv6=1)
2110         for p in punts:
2111             self.assertEqual(p.punt.rx_sw_if_index, self.pg0.sw_if_index)
2112
2113         #
2114         # Dump punt redirects for all interfaces
2115         #
2116         punts = self.vapi.ip_punt_redirect_dump(0xffffffff, is_ipv6=1)
2117         self.assertEqual(len(punts), 3)
2118         for p in punts:
2119             self.assertEqual(p.punt.tx_sw_if_index, self.pg3.sw_if_index)
2120         self.assertNotEqual(punts[1].punt.nh, self.pg3.remote_ip6)
2121         self.assertEqual(str(punts[2].punt.nh), '::')
2122
2123
2124 class TestIPDeag(VppTestCase):
2125     """ IPv6 Deaggregate Routes """
2126
2127     @classmethod
2128     def setUpClass(cls):
2129         super(TestIPDeag, cls).setUpClass()
2130
2131     @classmethod
2132     def tearDownClass(cls):
2133         super(TestIPDeag, cls).tearDownClass()
2134
2135     def setUp(self):
2136         super(TestIPDeag, self).setUp()
2137
2138         self.create_pg_interfaces(range(3))
2139
2140         for i in self.pg_interfaces:
2141             i.admin_up()
2142             i.config_ip6()
2143             i.resolve_ndp()
2144
2145     def tearDown(self):
2146         super(TestIPDeag, self).tearDown()
2147         for i in self.pg_interfaces:
2148             i.unconfig_ip6()
2149             i.admin_down()
2150
2151     def test_ip_deag(self):
2152         """ IP Deag Routes """
2153
2154         #
2155         # Create a table to be used for:
2156         #  1 - another destination address lookup
2157         #  2 - a source address lookup
2158         #
2159         table_dst = VppIpTable(self, 1, is_ip6=1)
2160         table_src = VppIpTable(self, 2, is_ip6=1)
2161         table_dst.add_vpp_config()
2162         table_src.add_vpp_config()
2163
2164         #
2165         # Add a route in the default table to point to a deag/
2166         # second lookup in each of these tables
2167         #
2168         route_to_dst = VppIpRoute(self, "1::1", 128,
2169                                   [VppRoutePath("::",
2170                                                 0xffffffff,
2171                                                 nh_table_id=1,
2172                                                 proto=DpoProto.DPO_PROTO_IP6)],
2173                                   is_ip6=1)
2174         route_to_src = VppIpRoute(self, "1::2", 128,
2175                                   [VppRoutePath("::",
2176                                                 0xffffffff,
2177                                                 nh_table_id=2,
2178                                                 is_source_lookup=1,
2179                                                 proto=DpoProto.DPO_PROTO_IP6)],
2180                                   is_ip6=1)
2181         route_to_dst.add_vpp_config()
2182         route_to_src.add_vpp_config()
2183
2184         #
2185         # packets to these destination are dropped, since they'll
2186         # hit the respective default routes in the second table
2187         #
2188         p_dst = (Ether(src=self.pg0.remote_mac,
2189                        dst=self.pg0.local_mac) /
2190                  IPv6(src="5::5", dst="1::1") /
2191                  inet6.TCP(sport=1234, dport=1234) /
2192                  Raw('\xa5' * 100))
2193         p_src = (Ether(src=self.pg0.remote_mac,
2194                        dst=self.pg0.local_mac) /
2195                  IPv6(src="2::2", dst="1::2") /
2196                  inet6.TCP(sport=1234, dport=1234) /
2197                  Raw('\xa5' * 100))
2198         pkts_dst = p_dst * 257
2199         pkts_src = p_src * 257
2200
2201         self.send_and_assert_no_replies(self.pg0, pkts_dst,
2202                                         "IP in dst table")
2203         self.send_and_assert_no_replies(self.pg0, pkts_src,
2204                                         "IP in src table")
2205
2206         #
2207         # add a route in the dst table to forward via pg1
2208         #
2209         route_in_dst = VppIpRoute(self, "1::1", 128,
2210                                   [VppRoutePath(self.pg1.remote_ip6,
2211                                                 self.pg1.sw_if_index,
2212                                                 proto=DpoProto.DPO_PROTO_IP6)],
2213                                   is_ip6=1,
2214                                   table_id=1)
2215         route_in_dst.add_vpp_config()
2216
2217         self.send_and_expect(self.pg0, pkts_dst, self.pg1)
2218
2219         #
2220         # add a route in the src table to forward via pg2
2221         #
2222         route_in_src = VppIpRoute(self, "2::2", 128,
2223                                   [VppRoutePath(self.pg2.remote_ip6,
2224                                                 self.pg2.sw_if_index,
2225                                                 proto=DpoProto.DPO_PROTO_IP6)],
2226                                   is_ip6=1,
2227                                   table_id=2)
2228         route_in_src.add_vpp_config()
2229         self.send_and_expect(self.pg0, pkts_src, self.pg2)
2230
2231         #
2232         # loop in the lookup DP
2233         #
2234         route_loop = VppIpRoute(self, "3::3", 128,
2235                                 [VppRoutePath("::",
2236                                               0xffffffff,
2237                                               proto=DpoProto.DPO_PROTO_IP6)],
2238                                 is_ip6=1)
2239         route_loop.add_vpp_config()
2240
2241         p_l = (Ether(src=self.pg0.remote_mac,
2242                      dst=self.pg0.local_mac) /
2243                IPv6(src="3::4", dst="3::3") /
2244                inet6.TCP(sport=1234, dport=1234) /
2245                Raw('\xa5' * 100))
2246
2247         self.send_and_assert_no_replies(self.pg0, p_l * 257,
2248                                         "IP lookup loop")
2249
2250
2251 class TestIP6Input(VppTestCase):
2252     """ IPv6 Input Exception Test Cases """
2253
2254     @classmethod
2255     def setUpClass(cls):
2256         super(TestIP6Input, cls).setUpClass()
2257
2258     @classmethod
2259     def tearDownClass(cls):
2260         super(TestIP6Input, cls).tearDownClass()
2261
2262     def setUp(self):
2263         super(TestIP6Input, self).setUp()
2264
2265         self.create_pg_interfaces(range(2))
2266
2267         for i in self.pg_interfaces:
2268             i.admin_up()
2269             i.config_ip6()
2270             i.resolve_ndp()
2271
2272     def tearDown(self):
2273         super(TestIP6Input, self).tearDown()
2274         for i in self.pg_interfaces:
2275             i.unconfig_ip6()
2276             i.admin_down()
2277
2278     def test_ip_input_icmp_reply(self):
2279         """ IP6 Input Exception - Return ICMP (3,0) """
2280         #
2281         # hop limit - ICMP replies
2282         #
2283         p_version = (Ether(src=self.pg0.remote_mac,
2284                            dst=self.pg0.local_mac) /
2285                      IPv6(src=self.pg0.remote_ip6,
2286                           dst=self.pg1.remote_ip6,
2287                           hlim=1) /
2288                      inet6.UDP(sport=1234, dport=1234) /
2289                      Raw('\xa5' * 100))
2290
2291         rx = self.send_and_expect(self.pg0, p_version * NUM_PKTS, self.pg0)
2292         rx = rx[0]
2293         icmp = rx[ICMPv6TimeExceeded]
2294
2295         # 0: "hop limit exceeded in transit",
2296         self.assertEqual((icmp.type, icmp.code), (3, 0))
2297
2298     icmpv6_data = '\x0a' * 18
2299     all_0s = "::"
2300     all_1s = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"
2301
2302     @parameterized.expand([
2303         # Name, src, dst, l4proto, msg, timeout
2304         ("src='iface',   dst='iface'", None, None,
2305          inet6.UDP(sport=1234, dport=1234), "funky version", None),
2306         ("src='All 0's', dst='iface'", all_0s, None,
2307          ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
2308         ("src='iface',   dst='All 0's'", None, all_0s,
2309          ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
2310         ("src='All 1's', dst='iface'", all_1s, None,
2311          ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
2312         ("src='iface',   dst='All 1's'", None, all_1s,
2313          ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
2314         ("src='All 1's', dst='All 1's'", all_1s, all_1s,
2315          ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
2316
2317     ])
2318     def test_ip_input_no_replies(self, name, src, dst, l4, msg, timeout):
2319
2320         self._testMethodDoc = 'IPv6 Input Exception - %s' % name
2321
2322         p_version = (Ether(src=self.pg0.remote_mac,
2323                            dst=self.pg0.local_mac) /
2324                      IPv6(src=src or self.pg0.remote_ip6,
2325                           dst=dst or self.pg1.remote_ip6,
2326                           version=3) /
2327                      l4 /
2328                      Raw('\xa5' * 100))
2329
2330         self.send_and_assert_no_replies(self.pg0, p_version * NUM_PKTS,
2331                                         remark=msg or "",
2332                                         timeout=timeout)
2333
2334     def test_hop_by_hop(self):
2335         """ Hop-by-hop header test """
2336
2337         p = (Ether(src=self.pg0.remote_mac,
2338                    dst=self.pg0.local_mac) /
2339              IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
2340              IPv6ExtHdrHopByHop() /
2341              inet6.UDP(sport=1234, dport=1234) /
2342              Raw('\xa5' * 100))
2343
2344         self.pg0.add_stream(p)
2345         self.pg_enable_capture(self.pg_interfaces)
2346         self.pg_start()
2347
2348 if __name__ == '__main__':
2349     unittest.main(testRunner=VppTestRunner)