Proxy ND (RFC4389 - or a sub-set thereof). This allows the 'emulation' of bridging...
[vpp.git] / test / test_ip6.py
1 #!/usr/bin/env python
2
3 import unittest
4 from socket import AF_INET6
5
6 from framework import VppTestCase, VppTestRunner
7 from vpp_sub_interface import VppSubInterface, VppDot1QSubint
8 from vpp_pg_interface import is_ipv6_misc
9 from vpp_neighbor import find_nbr
10
11 from scapy.packet import Raw
12 from scapy.layers.l2 import Ether, Dot1Q
13 from scapy.layers.inet6 import IPv6, UDP, ICMPv6ND_NS, ICMPv6ND_RS, \
14     ICMPv6ND_RA, ICMPv6NDOptSrcLLAddr, getmacbyip6, ICMPv6MRD_Solicitation, \
15     ICMPv6NDOptMTU, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, \
16     ICMPv6ND_NA, ICMPv6NDOptDstLLAddr
17
18 from util import ppp
19 from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ptop, in6_islladdr, \
20     in6_mactoifaceid, in6_ismaddr
21 from scapy.utils import inet_pton, inet_ntop
22
23
24 def mk_ll_addr(mac):
25     euid = in6_mactoifaceid(mac)
26     addr = "fe80::" + euid
27     return addr
28
29
30 class TestIPv6ND(VppTestCase):
31     def validate_ra(self, intf, rx, dst_ip=None):
32         if not dst_ip:
33             dst_ip = intf.remote_ip6
34
35         # unicasted packets must come to the unicast mac
36         self.assertEqual(rx[Ether].dst, intf.remote_mac)
37
38         # and from the router's MAC
39         self.assertEqual(rx[Ether].src, intf.local_mac)
40
41         # the rx'd RA should be addressed to the sender's source
42         self.assertTrue(rx.haslayer(ICMPv6ND_RA))
43         self.assertEqual(in6_ptop(rx[IPv6].dst),
44                          in6_ptop(dst_ip))
45
46         # and come from the router's link local
47         self.assertTrue(in6_islladdr(rx[IPv6].src))
48         self.assertEqual(in6_ptop(rx[IPv6].src),
49                          in6_ptop(mk_ll_addr(intf.local_mac)))
50
51     def validate_na(self, intf, rx, dst_ip=None, tgt_ip=None):
52         if not dst_ip:
53             dst_ip = intf.remote_ip6
54         if not tgt_ip:
55             dst_ip = intf.local_ip6
56
57         # unicasted packets must come to the unicast mac
58         self.assertEqual(rx[Ether].dst, intf.remote_mac)
59
60         # and from the router's MAC
61         self.assertEqual(rx[Ether].src, intf.local_mac)
62
63         # the rx'd NA should be addressed to the sender's source
64         self.assertTrue(rx.haslayer(ICMPv6ND_NA))
65         self.assertEqual(in6_ptop(rx[IPv6].dst),
66                          in6_ptop(dst_ip))
67
68         # and come from the target address
69         self.assertEqual(in6_ptop(rx[IPv6].src), in6_ptop(tgt_ip))
70
71         # Dest link-layer options should have the router's MAC
72         dll = rx[ICMPv6NDOptDstLLAddr]
73         self.assertEqual(dll.lladdr, intf.local_mac)
74
75     def send_and_expect_ra(self, intf, pkts, remark, dst_ip=None,
76                            filter_out_fn=is_ipv6_misc):
77         intf.add_stream(pkts)
78         self.pg0.add_stream(pkts)
79         self.pg_enable_capture(self.pg_interfaces)
80         self.pg_start()
81         rx = intf.get_capture(1, filter_out_fn=filter_out_fn)
82
83         self.assertEqual(len(rx), 1)
84         rx = rx[0]
85         self.validate_ra(intf, rx, dst_ip)
86
87     def send_and_assert_no_replies(self, intf, pkts, remark):
88         intf.add_stream(pkts)
89         self.pg_enable_capture(self.pg_interfaces)
90         self.pg_start()
91         intf.assert_nothing_captured(remark=remark)
92
93
94 class TestIPv6(TestIPv6ND):
95     """ IPv6 Test Case """
96
97     @classmethod
98     def setUpClass(cls):
99         super(TestIPv6, cls).setUpClass()
100
101     def setUp(self):
102         """
103         Perform test setup before test case.
104
105         **Config:**
106             - create 3 pg interfaces
107                 - untagged pg0 interface
108                 - Dot1Q subinterface on pg1
109                 - Dot1AD subinterface on pg2
110             - setup interfaces:
111                 - put it into UP state
112                 - set IPv6 addresses
113                 - resolve neighbor address using NDP
114             - configure 200 fib entries
115
116         :ivar list interfaces: pg interfaces and subinterfaces.
117         :ivar dict flows: IPv4 packet flows in test.
118         :ivar list pg_if_packet_sizes: packet sizes in test.
119
120         *TODO:* Create AD sub interface
121         """
122         super(TestIPv6, self).setUp()
123
124         # create 3 pg interfaces
125         self.create_pg_interfaces(range(3))
126
127         # create 2 subinterfaces for p1 and pg2
128         self.sub_interfaces = [
129             VppDot1QSubint(self, self.pg1, 100),
130             VppDot1QSubint(self, self.pg2, 200)
131             # TODO: VppDot1ADSubint(self, self.pg2, 200, 300, 400)
132         ]
133
134         # packet flows mapping pg0 -> pg1.sub, pg2.sub, etc.
135         self.flows = dict()
136         self.flows[self.pg0] = [self.pg1.sub_if, self.pg2.sub_if]
137         self.flows[self.pg1.sub_if] = [self.pg0, self.pg2.sub_if]
138         self.flows[self.pg2.sub_if] = [self.pg0, self.pg1.sub_if]
139
140         # packet sizes
141         self.pg_if_packet_sizes = [64, 512, 1518, 9018]
142         self.sub_if_packet_sizes = [64, 512, 1518 + 4, 9018 + 4]
143
144         self.interfaces = list(self.pg_interfaces)
145         self.interfaces.extend(self.sub_interfaces)
146
147         # setup all interfaces
148         for i in self.interfaces:
149             i.admin_up()
150             i.config_ip6()
151             i.resolve_ndp()
152
153         # config 2M FIB entries
154         self.config_fib_entries(200)
155
156     def tearDown(self):
157         """Run standard test teardown and log ``show ip6 neighbors``."""
158         for i in self.sub_interfaces:
159             i.unconfig_ip6()
160             i.ip6_disable()
161             i.admin_down()
162             i.remove_vpp_config()
163
164         super(TestIPv6, self).tearDown()
165         if not self.vpp_dead:
166             self.logger.info(self.vapi.cli("show ip6 neighbors"))
167             # info(self.vapi.cli("show ip6 fib"))  # many entries
168
169     def config_fib_entries(self, count):
170         """For each interface add to the FIB table *count* routes to
171         "fd02::1/128" destination with interface's local address as next-hop
172         address.
173
174         :param int count: Number of FIB entries.
175
176         - *TODO:* check if the next-hop address shouldn't be remote address
177           instead of local address.
178         """
179         n_int = len(self.interfaces)
180         percent = 0
181         counter = 0.0
182         dest_addr = inet_pton(AF_INET6, "fd02::1")
183         dest_addr_len = 128
184         for i in self.interfaces:
185             next_hop_address = i.local_ip6n
186             for j in range(count / n_int):
187                 self.vapi.ip_add_del_route(
188                     dest_addr, dest_addr_len, next_hop_address, is_ipv6=1)
189                 counter += 1
190                 if counter / count * 100 > percent:
191                     self.logger.info("Configure %d FIB entries .. %d%% done" %
192                                      (count, percent))
193                     percent += 1
194
195     def create_stream(self, src_if, packet_sizes):
196         """Create input packet stream for defined interface.
197
198         :param VppInterface src_if: Interface to create packet stream for.
199         :param list packet_sizes: Required packet sizes.
200         """
201         pkts = []
202         for i in range(0, 257):
203             dst_if = self.flows[src_if][i % 2]
204             info = self.create_packet_info(src_if, dst_if)
205             payload = self.info_to_payload(info)
206             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
207                  IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6) /
208                  UDP(sport=1234, dport=1234) /
209                  Raw(payload))
210             info.data = p.copy()
211             if isinstance(src_if, VppSubInterface):
212                 p = src_if.add_dot1_layer(p)
213             size = packet_sizes[(i // 2) % len(packet_sizes)]
214             self.extend_packet(p, size)
215             pkts.append(p)
216         return pkts
217
218     def verify_capture(self, dst_if, capture):
219         """Verify captured input packet stream for defined interface.
220
221         :param VppInterface dst_if: Interface to verify captured packet stream
222                                     for.
223         :param list capture: Captured packet stream.
224         """
225         self.logger.info("Verifying capture on interface %s" % dst_if.name)
226         last_info = dict()
227         for i in self.interfaces:
228             last_info[i.sw_if_index] = None
229         is_sub_if = False
230         dst_sw_if_index = dst_if.sw_if_index
231         if hasattr(dst_if, 'parent'):
232             is_sub_if = True
233         for packet in capture:
234             if is_sub_if:
235                 # Check VLAN tags and Ethernet header
236                 packet = dst_if.remove_dot1_layer(packet)
237             self.assertTrue(Dot1Q not in packet)
238             try:
239                 ip = packet[IPv6]
240                 udp = packet[UDP]
241                 payload_info = self.payload_to_info(str(packet[Raw]))
242                 packet_index = payload_info.index
243                 self.assertEqual(payload_info.dst, dst_sw_if_index)
244                 self.logger.debug(
245                     "Got packet on port %s: src=%u (id=%u)" %
246                     (dst_if.name, payload_info.src, packet_index))
247                 next_info = self.get_next_packet_info_for_interface2(
248                     payload_info.src, dst_sw_if_index,
249                     last_info[payload_info.src])
250                 last_info[payload_info.src] = next_info
251                 self.assertTrue(next_info is not None)
252                 self.assertEqual(packet_index, next_info.index)
253                 saved_packet = next_info.data
254                 # Check standard fields
255                 self.assertEqual(ip.src, saved_packet[IPv6].src)
256                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
257                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
258                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
259             except:
260                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
261                 raise
262         for i in self.interfaces:
263             remaining_packet = self.get_next_packet_info_for_interface2(
264                 i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index])
265             self.assertTrue(remaining_packet is None,
266                             "Interface %s: Packet expected from interface %s "
267                             "didn't arrive" % (dst_if.name, i.name))
268
269     def test_fib(self):
270         """ IPv6 FIB test
271
272         Test scenario:
273             - Create IPv6 stream for pg0 interface
274             - Create IPv6 tagged streams for pg1's and pg2's subinterface.
275             - Send and verify received packets on each interface.
276         """
277
278         pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes)
279         self.pg0.add_stream(pkts)
280
281         for i in self.sub_interfaces:
282             pkts = self.create_stream(i, self.sub_if_packet_sizes)
283             i.parent.add_stream(pkts)
284
285         self.pg_enable_capture(self.pg_interfaces)
286         self.pg_start()
287
288         pkts = self.pg0.get_capture()
289         self.verify_capture(self.pg0, pkts)
290
291         for i in self.sub_interfaces:
292             pkts = i.parent.get_capture()
293             self.verify_capture(i, pkts)
294
295     def test_ns(self):
296         """ IPv6 Neighbour Solicitation Exceptions
297
298         Test scenario:
299            - Send an NS Sourced from an address not covered by the link sub-net
300            - Send an NS to an mcast address the router has not joined
301            - Send NS for a target address the router does not onn.
302         """
303
304         #
305         # An NS from a non link source address
306         #
307         nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6))
308         d = inet_ntop(AF_INET6, nsma)
309
310         p = (Ether(dst=in6_getnsmac(nsma)) /
311              IPv6(dst=d, src="2002::2") /
312              ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
313              ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
314         pkts = [p]
315
316         self.send_and_assert_no_replies(
317             self.pg0, pkts,
318             "No response to NS source by address not on sub-net")
319
320         #
321         # An NS for sent to a solicited mcast group the router is
322         # not a member of FAILS
323         #
324         if 0:
325             nsma = in6_getnsma(inet_pton(AF_INET6, "fd::ffff"))
326             d = inet_ntop(AF_INET6, nsma)
327
328             p = (Ether(dst=in6_getnsmac(nsma)) /
329                  IPv6(dst=d, src=self.pg0.remote_ip6) /
330                  ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
331                  ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
332             pkts = [p]
333
334             self.send_and_assert_no_replies(
335                 self.pg0, pkts,
336                 "No response to NS sent to unjoined mcast address")
337
338         #
339         # An NS whose target address is one the router does not own
340         #
341         nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6))
342         d = inet_ntop(AF_INET6, nsma)
343
344         p = (Ether(dst=in6_getnsmac(nsma)) /
345              IPv6(dst=d, src=self.pg0.remote_ip6) /
346              ICMPv6ND_NS(tgt="fd::ffff") /
347              ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
348         pkts = [p]
349
350         self.send_and_assert_no_replies(self.pg0, pkts,
351                                         "No response to NS for unknown target")
352
353     def validate_ra(self, intf, rx, dst_ip=None, mtu=9000, pi_opt=None):
354         if not dst_ip:
355             dst_ip = intf.remote_ip6
356
357         # unicasted packets must come to the unicast mac
358         self.assertEqual(rx[Ether].dst, intf.remote_mac)
359
360         # and from the router's MAC
361         self.assertEqual(rx[Ether].src, intf.local_mac)
362
363         # the rx'd RA should be addressed to the sender's source
364         self.assertTrue(rx.haslayer(ICMPv6ND_RA))
365         self.assertEqual(in6_ptop(rx[IPv6].dst),
366                          in6_ptop(dst_ip))
367
368         # and come from the router's link local
369         self.assertTrue(in6_islladdr(rx[IPv6].src))
370         self.assertEqual(in6_ptop(rx[IPv6].src),
371                          in6_ptop(mk_ll_addr(intf.local_mac)))
372
373         # it should contain the links MTU
374         ra = rx[ICMPv6ND_RA]
375         self.assertEqual(ra[ICMPv6NDOptMTU].mtu, mtu)
376
377         # it should contain the source's link layer address option
378         sll = ra[ICMPv6NDOptSrcLLAddr]
379         self.assertEqual(sll.lladdr, intf.local_mac)
380
381         if not pi_opt:
382             # the RA should not contain prefix information
383             self.assertFalse(ra.haslayer(ICMPv6NDOptPrefixInfo))
384         else:
385             raos = rx.getlayer(ICMPv6NDOptPrefixInfo, 1)
386
387             # the options are nested in the scapy packet in way that i cannot
388             # decipher how to decode. this 1st layer of option always returns
389             # nested classes, so a direct obj1=obj2 comparison always fails.
390             # however, the getlayer(.., 2) does give one instnace.
391             # so we cheat here and construct a new opt instnace for comparison
392             rd = ICMPv6NDOptPrefixInfo(prefixlen=raos.prefixlen,
393                                        prefix=raos.prefix,
394                                        L=raos.L,
395                                        A=raos.A)
396             if type(pi_opt) is list:
397                 for ii in range(len(pi_opt)):
398                     self.assertEqual(pi_opt[ii], rd)
399                     rd = rx.getlayer(ICMPv6NDOptPrefixInfo, ii+2)
400             else:
401                 self.assertEqual(pi_opt, raos)
402
403     def send_and_expect_ra(self, intf, pkts, remark, dst_ip=None,
404                            filter_out_fn=is_ipv6_misc,
405                            opt=None):
406         intf.add_stream(pkts)
407         self.pg_enable_capture(self.pg_interfaces)
408         self.pg_start()
409         rx = intf.get_capture(1, filter_out_fn=filter_out_fn)
410
411         self.assertEqual(len(rx), 1)
412         rx = rx[0]
413         self.validate_ra(intf, rx, dst_ip, pi_opt=opt)
414
415     def test_rs(self):
416         """ IPv6 Router Solicitation Exceptions
417
418         Test scenario:
419         """
420
421         #
422         # Before we begin change the IPv6 RA responses to use the unicast
423         # address - that way we will not confuse them with the periodic
424         # RAs which go to the mcast address
425         # Sit and wait for the first periodic RA.
426         #
427         # TODO
428         #
429         self.pg0.ip6_ra_config(send_unicast=1)
430
431         #
432         # An RS from a link source address
433         #  - expect an RA in return
434         #
435         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
436              IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
437              ICMPv6ND_RS())
438         pkts = [p]
439         self.send_and_expect_ra(self.pg0, pkts, "Genuine RS")
440
441         #
442         # For the next RS sent the RA should be rate limited
443         #
444         self.send_and_assert_no_replies(self.pg0, pkts, "RA rate limited")
445
446         #
447         # When we reconfiure the IPv6 RA config, we reset the RA rate limiting,
448         # so we need to do this before each test below so as not to drop
449         # packets for rate limiting reasons. Test this works here.
450         #
451         self.pg0.ip6_ra_config(send_unicast=1)
452         self.send_and_expect_ra(self.pg0, pkts, "Rate limit reset RS")
453
454         #
455         # An RS sent from a non-link local source
456         #
457         self.pg0.ip6_ra_config(send_unicast=1)
458         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
459              IPv6(dst=self.pg0.local_ip6, src="2002::ffff") /
460              ICMPv6ND_RS())
461         pkts = [p]
462         self.send_and_assert_no_replies(self.pg0, pkts,
463                                         "RS from non-link source")
464
465         #
466         # Source an RS from a link local address
467         #
468         self.pg0.ip6_ra_config(send_unicast=1)
469         ll = mk_ll_addr(self.pg0.remote_mac)
470         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
471              IPv6(dst=self.pg0.local_ip6, src=ll) /
472              ICMPv6ND_RS())
473         pkts = [p]
474         self.send_and_expect_ra(self.pg0, pkts,
475                                 "RS sourced from link-local",
476                                 dst_ip=ll)
477
478         #
479         # Send the RS multicast
480         #
481         self.pg0.ip6_ra_config(send_unicast=1)
482         dmac = in6_getnsmac(inet_pton(AF_INET6, "ff02::2"))
483         ll = mk_ll_addr(self.pg0.remote_mac)
484         p = (Ether(dst=dmac, src=self.pg0.remote_mac) /
485              IPv6(dst="ff02::2", src=ll) /
486              ICMPv6ND_RS())
487         pkts = [p]
488         self.send_and_expect_ra(self.pg0, pkts,
489                                 "RS sourced from link-local",
490                                 dst_ip=ll)
491
492         #
493         # Source from the unspecified address ::. This happens when the RS
494         # is sent before the host has a configured address/sub-net,
495         # i.e. auto-config. Since the sender has no IP address, the reply
496         # comes back mcast - so the capture needs to not filter this.
497         # If we happen to pick up the periodic RA at this point then so be it,
498         # it's not an error.
499         #
500         self.pg0.ip6_ra_config(send_unicast=1, suppress=1)
501         p = (Ether(dst=dmac, src=self.pg0.remote_mac) /
502              IPv6(dst="ff02::2", src="::") /
503              ICMPv6ND_RS())
504         pkts = [p]
505         self.send_and_expect_ra(self.pg0, pkts,
506                                 "RS sourced from unspecified",
507                                 dst_ip="ff02::1",
508                                 filter_out_fn=None)
509
510         #
511         # Configure The RA to announce the links prefix
512         #
513         self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
514                                self.pg0.local_ip6_prefix_len)
515
516         #
517         # RAs should now contain the prefix information option
518         #
519         opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
520                                     prefix=self.pg0.local_ip6,
521                                     L=1,
522                                     A=1)
523
524         self.pg0.ip6_ra_config(send_unicast=1)
525         ll = mk_ll_addr(self.pg0.remote_mac)
526         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
527              IPv6(dst=self.pg0.local_ip6, src=ll) /
528              ICMPv6ND_RS())
529         self.send_and_expect_ra(self.pg0, p,
530                                 "RA with prefix-info",
531                                 dst_ip=ll,
532                                 opt=opt)
533
534         #
535         # Change the prefix info to not off-link
536         #  L-flag is clear
537         #
538         self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
539                                self.pg0.local_ip6_prefix_len,
540                                off_link=1)
541
542         opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
543                                     prefix=self.pg0.local_ip6,
544                                     L=0,
545                                     A=1)
546
547         self.pg0.ip6_ra_config(send_unicast=1)
548         self.send_and_expect_ra(self.pg0, p,
549                                 "RA with Prefix info with L-flag=0",
550                                 dst_ip=ll,
551                                 opt=opt)
552
553         #
554         # Change the prefix info to not off-link, no-autoconfig
555         #  L and A flag are clear in the advert
556         #
557         self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
558                                self.pg0.local_ip6_prefix_len,
559                                off_link=1,
560                                no_autoconfig=1)
561
562         opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
563                                     prefix=self.pg0.local_ip6,
564                                     L=0,
565                                     A=0)
566
567         self.pg0.ip6_ra_config(send_unicast=1)
568         self.send_and_expect_ra(self.pg0, p,
569                                 "RA with Prefix info with A & L-flag=0",
570                                 dst_ip=ll,
571                                 opt=opt)
572
573         #
574         # Change the flag settings back to the defaults
575         #  L and A flag are set in the advert
576         #
577         self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
578                                self.pg0.local_ip6_prefix_len)
579
580         opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
581                                     prefix=self.pg0.local_ip6,
582                                     L=1,
583                                     A=1)
584
585         self.pg0.ip6_ra_config(send_unicast=1)
586         self.send_and_expect_ra(self.pg0, p,
587                                 "RA with Prefix info",
588                                 dst_ip=ll,
589                                 opt=opt)
590
591         #
592         # Change the prefix info to not off-link, no-autoconfig
593         #  L and A flag are clear in the advert
594         #
595         self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
596                                self.pg0.local_ip6_prefix_len,
597                                off_link=1,
598                                no_autoconfig=1)
599
600         opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
601                                     prefix=self.pg0.local_ip6,
602                                     L=0,
603                                     A=0)
604
605         self.pg0.ip6_ra_config(send_unicast=1)
606         self.send_and_expect_ra(self.pg0, p,
607                                 "RA with Prefix info with A & L-flag=0",
608                                 dst_ip=ll,
609                                 opt=opt)
610
611         #
612         # Use the reset to defults option to revert to defaults
613         #  L and A flag are clear in the advert
614         #
615         self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
616                                self.pg0.local_ip6_prefix_len,
617                                use_default=1)
618
619         opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
620                                     prefix=self.pg0.local_ip6,
621                                     L=1,
622                                     A=1)
623
624         self.pg0.ip6_ra_config(send_unicast=1)
625         self.send_and_expect_ra(self.pg0, p,
626                                 "RA with Prefix reverted to defaults",
627                                 dst_ip=ll,
628                                 opt=opt)
629
630         #
631         # Advertise Another prefix. With no L-flag/A-flag
632         #
633         self.pg0.ip6_ra_prefix(self.pg1.local_ip6n,
634                                self.pg1.local_ip6_prefix_len,
635                                off_link=1,
636                                no_autoconfig=1)
637
638         opt = [ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
639                                      prefix=self.pg0.local_ip6,
640                                      L=1,
641                                      A=1),
642                ICMPv6NDOptPrefixInfo(prefixlen=self.pg1.local_ip6_prefix_len,
643                                      prefix=self.pg1.local_ip6,
644                                      L=0,
645                                      A=0)]
646
647         self.pg0.ip6_ra_config(send_unicast=1)
648         ll = mk_ll_addr(self.pg0.remote_mac)
649         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
650              IPv6(dst=self.pg0.local_ip6, src=ll) /
651              ICMPv6ND_RS())
652         self.send_and_expect_ra(self.pg0, p,
653                                 "RA with multiple Prefix infos",
654                                 dst_ip=ll,
655                                 opt=opt)
656
657         #
658         # Remove the first refix-info - expect the second is still in the
659         # advert
660         #
661         self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
662                                self.pg0.local_ip6_prefix_len,
663                                is_no=1)
664
665         opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg1.local_ip6_prefix_len,
666                                     prefix=self.pg1.local_ip6,
667                                     L=0,
668                                     A=0)
669
670         self.pg0.ip6_ra_config(send_unicast=1)
671         self.send_and_expect_ra(self.pg0, p,
672                                 "RA with Prefix reverted to defaults",
673                                 dst_ip=ll,
674                                 opt=opt)
675
676         #
677         # Remove the second prefix-info - expect no prefix-info i nthe adverts
678         #
679         self.pg0.ip6_ra_prefix(self.pg1.local_ip6n,
680                                self.pg1.local_ip6_prefix_len,
681                                is_no=1)
682
683         self.pg0.ip6_ra_config(send_unicast=1)
684         self.send_and_expect_ra(self.pg0, p,
685                                 "RA with Prefix reverted to defaults",
686                                 dst_ip=ll)
687
688         #
689         # Reset the periodic advertisements back to default values
690         #
691         self.pg0.ip6_ra_config(no=1, suppress=1, send_unicast=0)
692
693
694 class IPv6NDProxyTest(TestIPv6ND):
695     """ IPv6 ND ProxyTest Case """
696
697     def setUp(self):
698         super(IPv6NDProxyTest, self).setUp()
699
700         # create 3 pg interfaces
701         self.create_pg_interfaces(range(3))
702
703         # pg0 is the master interface, with the configured subnet
704         self.pg0.admin_up()
705         self.pg0.config_ip6()
706         self.pg0.resolve_ndp()
707
708         self.pg1.ip6_enable()
709         self.pg2.ip6_enable()
710
711     def tearDown(self):
712         super(IPv6NDProxyTest, self).tearDown()
713
714     def test_nd_proxy(self):
715         """ IPv6 Proxy ND """
716
717         #
718         # Generate some hosts in the subnet that we are proxying
719         #
720         self.pg0.generate_remote_hosts(8)
721
722         nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6))
723         d = inet_ntop(AF_INET6, nsma)
724
725         #
726         # Send an NS for one of those remote hosts on one of the proxy links
727         # expect no response since it's from an address that is not
728         # on the link that has the prefix configured
729         #
730         ns_pg1 = (Ether(dst=in6_getnsmac(nsma), src=self.pg1.remote_mac) /
731                   IPv6(dst=d, src=self.pg0._remote_hosts[2].ip6) /
732                   ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
733                   ICMPv6NDOptSrcLLAddr(lladdr=self.pg0._remote_hosts[2].mac))
734
735         self.send_and_assert_no_replies(self.pg1, ns_pg1, "Off link NS")
736
737         #
738         # Add proxy support for the host
739         #
740         self.vapi.ip6_nd_proxy(
741             inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6),
742             self.pg1.sw_if_index)
743
744         #
745         # try that NS again. this time we expect an NA back
746         #
747         self.pg1.add_stream(ns_pg1)
748         self.pg_enable_capture(self.pg_interfaces)
749         self.pg_start()
750         rx = self.pg1.get_capture(1)
751
752         self.validate_na(self.pg1, rx[0],
753                          dst_ip=self.pg0._remote_hosts[2].ip6,
754                          tgt_ip=self.pg0.local_ip6)
755
756         #
757         # ... and that we have an entry in the ND cache
758         #
759         self.assertTrue(find_nbr(self,
760                                  self.pg1.sw_if_index,
761                                  self.pg0._remote_hosts[2].ip6,
762                                  inet=AF_INET6))
763
764         #
765         # ... and we can route traffic to it
766         #
767         t = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
768              IPv6(dst=self.pg0._remote_hosts[2].ip6,
769                   src=self.pg0.remote_ip6) /
770              UDP(sport=10000, dport=20000) /
771              Raw('\xa5' * 100))
772
773         self.pg0.add_stream(t)
774         self.pg_enable_capture(self.pg_interfaces)
775         self.pg_start()
776         rx = self.pg1.get_capture(1)
777         rx = rx[0]
778
779         self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac)
780         self.assertEqual(rx[Ether].src, self.pg1.local_mac)
781
782         self.assertEqual(rx[IPv6].src, t[IPv6].src)
783         self.assertEqual(rx[IPv6].dst, t[IPv6].dst)
784
785         #
786         # Test we proxy for the host on the main interface
787         #
788         ns_pg0 = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) /
789                   IPv6(dst=d, src=self.pg0.remote_ip6) /
790                   ICMPv6ND_NS(tgt=self.pg0._remote_hosts[2].ip6) /
791                   ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
792
793         self.pg0.add_stream(ns_pg0)
794         self.pg_enable_capture(self.pg_interfaces)
795         self.pg_start()
796         rx = self.pg0.get_capture(1)
797
798         self.validate_na(self.pg0, rx[0],
799                          tgt_ip=self.pg0._remote_hosts[2].ip6,
800                          dst_ip=self.pg0.remote_ip6)
801
802         #
803         # Setup and resolve proxy for another host on another interface
804         #
805         ns_pg2 = (Ether(dst=in6_getnsmac(nsma), src=self.pg2.remote_mac) /
806                   IPv6(dst=d, src=self.pg0._remote_hosts[3].ip6) /
807                   ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
808                   ICMPv6NDOptSrcLLAddr(lladdr=self.pg0._remote_hosts[2].mac))
809
810         self.vapi.ip6_nd_proxy(
811             inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6),
812             self.pg2.sw_if_index)
813
814         self.pg2.add_stream(ns_pg2)
815         self.pg_enable_capture(self.pg_interfaces)
816         self.pg_start()
817         rx = self.pg2.get_capture(1)
818
819         self.validate_na(self.pg2, rx[0],
820                          dst_ip=self.pg0._remote_hosts[3].ip6,
821                          tgt_ip=self.pg0.local_ip6)
822
823         self.assertTrue(find_nbr(self,
824                                  self.pg2.sw_if_index,
825                                  self.pg0._remote_hosts[3].ip6,
826                                  inet=AF_INET6))
827
828         #
829         # hosts can communicate. pg2->pg1
830         #
831         t2 = (Ether(dst=self.pg2.local_mac,
832                     src=self.pg0.remote_hosts[3].mac) /
833               IPv6(dst=self.pg0._remote_hosts[2].ip6,
834                    src=self.pg0._remote_hosts[3].ip6) /
835               UDP(sport=10000, dport=20000) /
836               Raw('\xa5' * 100))
837
838         self.pg2.add_stream(t2)
839         self.pg_enable_capture(self.pg_interfaces)
840         self.pg_start()
841         rx = self.pg1.get_capture(1)
842         rx = rx[0]
843
844         self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac)
845         self.assertEqual(rx[Ether].src, self.pg1.local_mac)
846
847         self.assertEqual(rx[IPv6].src, t2[IPv6].src)
848         self.assertEqual(rx[IPv6].dst, t2[IPv6].dst)
849
850         #
851         # remove the proxy configs
852         #
853         self.vapi.ip6_nd_proxy(
854             inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6),
855             self.pg1.sw_if_index,
856             is_del=1)
857         self.vapi.ip6_nd_proxy(
858             inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6),
859             self.pg2.sw_if_index,
860             is_del=1)
861
862         self.assertFalse(find_nbr(self,
863                                   self.pg2.sw_if_index,
864                                   self.pg0._remote_hosts[3].ip6,
865                                   inet=AF_INET6))
866         self.assertFalse(find_nbr(self,
867                                   self.pg1.sw_if_index,
868                                   self.pg0._remote_hosts[2].ip6,
869                                   inet=AF_INET6))
870
871         #
872         # no longer proxy-ing...
873         #
874         self.send_and_assert_no_replies(self.pg0, ns_pg0, "Proxy unconfigured")
875         self.send_and_assert_no_replies(self.pg1, ns_pg1, "Proxy unconfigured")
876         self.send_and_assert_no_replies(self.pg2, ns_pg2, "Proxy unconfigured")
877
878         #
879         # no longer forwarding. traffic generates NS out of the glean/main
880         # interface
881         #
882         self.pg2.add_stream(t2)
883         self.pg_enable_capture(self.pg_interfaces)
884         self.pg_start()
885
886         rx = self.pg0.get_capture(1)
887
888         self.assertTrue(rx[0].haslayer(ICMPv6ND_NS))
889
890
891 if __name__ == '__main__':
892     unittest.main(testRunner=VppTestRunner)