4 # Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
6 # SPDX-License-Identifier: Apache-2.0
11 from socket import inet_pton, inet_ntop, AF_INET6
13 from vpp_object import VppObject
14 from vpp_papi import VppEnum
16 from scapy.packet import Raw
17 from scapy.layers.l2 import Ether, ARP
18 from scapy.layers.inet import IP, ICMP, icmptypes
19 from scapy.layers.inet6 import IPv6, ipv6nh, IPv6ExtHdrHopByHop, \
20 ICMPv6MLReport2, ICMPv6ND_NA, ICMPv6ND_NS, ICMPv6NDOptDstLLAddr, \
21 ICMPv6NDOptSrcLLAddr, ICMPv6EchoRequest, ICMPv6EchoReply
22 from scapy.contrib.igmpv3 import IGMPv3, IGMPv3mr, IGMPv3gr
23 from scapy.layers.vrrp import IPPROTO_VRRP, VRRPv3
24 from scapy.utils6 import in6_getnsma, in6_getnsmac
25 from framework import VppTestCase, VppTestRunner, running_extended_tests
26 from util import ip6_normalize
28 VRRP_VR_FLAG_PREEMPT = 1
29 VRRP_VR_FLAG_ACCEPT = 2
30 VRRP_VR_FLAG_UNICAST = 4
33 VRRP_VR_STATE_INIT = 0
34 VRRP_VR_STATE_BACKUP = 1
35 VRRP_VR_STATE_MASTER = 2
36 VRRP_VR_STATE_INTF_DOWN = 3
40 """ Want to filter out advertisements, igmp, etc"""
48 """ Filter out everything but advertisements. E.g. multicast RD/ND """
49 if p.haslayer(VRRPv3):
55 def is_not_echo_reply(p):
56 """ filter out advertisements and other while waiting for echo reply """
57 if p.haslayer(IP) and p.haslayer(ICMP):
58 if icmptypes[p[ICMP].type] == "echo-reply":
60 elif p.haslayer(IPv6) and p.haslayer(ICMPv6EchoReply):
66 class VppVRRPVirtualRouter(VppObject):
74 flags=VRRP_VR_FLAG_PREEMPT,
78 self._sw_if_index = self._intf.sw_if_index
83 if (flags & VRRP_VR_FLAG_IPV6):
85 self._adv_dest_mac = "33:33:00:00:00:12"
86 self._virtual_mac = "00:00:5e:00:02:%02x" % vr_id
87 self._adv_dest_ip = "ff02::12"
88 self._vips = ([intf.local_ip6] if vips is None else vips)
91 self._adv_dest_mac = "01:00:5e:00:00:12"
92 self._virtual_mac = "00:00:5e:00:01:%02x" % vr_id
93 self._adv_dest_ip = "224.0.0.18"
94 self._vips = ([intf.local_ip4] if vips is None else vips)
95 self._tracked_ifs = []
97 def add_vpp_config(self):
98 self._test.vapi.vrrp_vr_add_del(is_add=1,
99 sw_if_index=self._intf.sw_if_index,
102 interval=self._intvl,
104 n_addrs=len(self._vips),
107 def query_vpp_config(self):
108 vrs = self._test.vapi.vrrp_vr_dump(sw_if_index=self._intf.sw_if_index)
110 if vr.config.vr_id != self._vr_id:
113 is_ipv6 = (1 if (vr.config.flags & VRRP_VR_FLAG_IPV6) else 0)
114 if is_ipv6 != self._is_ipv6:
121 def remove_vpp_config(self):
122 self._test.vapi.vrrp_vr_add_del(is_add=0,
123 sw_if_index=self._intf.sw_if_index,
126 interval=self._intvl,
128 n_addrs=len(self._vips),
131 def start_stop(self, is_start):
132 self._test.vapi.vrrp_vr_start_stop(is_start=is_start,
133 sw_if_index=self._intf.sw_if_index,
135 is_ipv6=self._is_ipv6)
136 self._start_time = (time.time() if is_start else None)
138 def add_del_tracked_interface(self, is_add, sw_if_index, prio):
140 'sw_if_index': self._intf.sw_if_index,
141 'is_ipv6': self._is_ipv6,
142 'vr_id': self._vr_id,
145 'ifs': [{'sw_if_index': sw_if_index, 'priority': prio}]
147 self._test.vapi.vrrp_vr_track_if_add_del(**args)
148 self._tracked_ifs.append(args['ifs'][0])
150 def set_unicast_peers(self, addrs):
152 'sw_if_index': self._intf.sw_if_index,
153 'is_ipv6': self._is_ipv6,
154 'vr_id': self._vr_id,
155 'n_addrs': len(addrs),
158 self._test.vapi.vrrp_vr_set_peers(**args)
159 self._unicast_peers = addrs
161 def vrrp_adv_packet(self, prio=None, src_ip=None):
162 dst_ip = self._adv_dest_ip
165 eth = Ether(dst=self._adv_dest_mac, src=self._virtual_mac)
166 vrrp = VRRPv3(vrid=self._vr_id, priority=prio,
167 ipcount=len(self._vips), adv=self._intvl)
169 src_ip = (self._intf.local_ip6_ll if src_ip is None else src_ip)
170 ip = IPv6(src=src_ip, dst=dst_ip, nh=IPPROTO_VRRP, hlim=255)
171 vrrp.addrlist = self._vips
173 src_ip = (self._intf.local_ip4 if src_ip is None else src_ip)
174 ip = IP(src=src_ip, dst=dst_ip, proto=IPPROTO_VRRP, ttl=255, id=0)
175 vrrp.addrlist = self._vips
177 # Fill in default values & checksums
178 pkt = Ether(raw(eth / ip / vrrp))
181 def start_time(self):
182 return self._start_time
184 def virtual_mac(self):
185 return self._virtual_mac
187 def virtual_ips(self):
190 def adv_dest_mac(self):
191 return self._adv_dest_mac
193 def adv_dest_ip(self):
194 return self._adv_dest_ip
202 def adv_interval(self):
208 def assert_state_equals(self, state):
209 vr_details = self.query_vpp_config()
210 self._test.assertEqual(vr_details.runtime.state, state)
212 def master_down_seconds(self):
213 vr_details = self.query_vpp_config()
214 return (vr_details.runtime.master_down_int * 0.01)
217 @unittest.skipUnless(running_extended_tests, "part of extended tests")
218 class TestVRRP4(VppTestCase):
219 """ IPv4 VRRP Test Case """
223 super(TestVRRP4, cls).setUpClass()
226 def tearDownClass(cls):
227 super(TestVRRP4, cls).tearDownClass()
230 super(TestVRRP4, self).setUp()
232 self.create_pg_interfaces(range(2))
234 for i in self.pg_interfaces:
237 i.generate_remote_hosts(5)
238 i.configure_ipv4_neighbors()
241 self._default_flags = VRRP_VR_FLAG_PREEMPT
242 self._default_adv = 100
247 vr_api = vr.query_vpp_config()
248 if vr_api.runtime.state != VRRP_VR_STATE_INIT:
249 vr.start_stop(is_start=0)
250 vr.remove_vpp_config()
252 self.logger.error("Error cleaning up")
254 for i in self.pg_interfaces:
261 super(TestVRRP4, self).tearDown()
263 def verify_vrrp4_igmp(self, pkt):
265 self.assertEqual(ip.dst, "224.0.0.22")
266 self.assertEqual(ip.proto, 2)
269 self.assertEqual(IGMPv3.igmpv3types[igmp.type],
270 "Version 3 Membership Report")
272 igmpmr = pkt[IGMPv3mr]
273 self.assertEqual(igmpmr.numgrp, 1)
274 self.assertEqual(igmpmr.records[0].maddr, "224.0.0.18")
276 def verify_vrrp4_garp(self, pkt, vip, vmac):
279 # ARP "who-has" op == 1
280 self.assertEqual(arp.op, 1)
281 self.assertEqual(arp.pdst, arp.psrc)
282 self.assertEqual(arp.pdst, vip)
283 self.assertEqual(arp.hwsrc, vmac)
285 def verify_vrrp4_adv(self, rx_pkt, vr, prio=None):
286 vips = vr.virtual_ips()
289 vrrp = rx_pkt[VRRPv3]
291 pkt = vr.vrrp_adv_packet(prio=prio)
293 # Source MAC is virtual MAC, destination is multicast MAC
294 self.assertEqual(eth.src, vr.virtual_mac())
295 self.assertEqual(eth.dst, vr.adv_dest_mac())
297 self.assertEqual(ip.dst, "224.0.0.18")
298 self.assertEqual(ip.ttl, 255)
299 self.assertEqual(ip.proto, IPPROTO_VRRP)
301 self.assertEqual(vrrp.version, 3)
302 self.assertEqual(vrrp.type, 1)
303 self.assertEqual(vrrp.vrid, vr.vr_id())
306 self.assertEqual(vrrp.priority, prio)
307 self.assertEqual(vrrp.ipcount, len(vips))
308 self.assertEqual(vrrp.adv, vr.adv_interval())
309 self.assertListEqual(vrrp.addrlist, vips)
311 # VR with priority 255 owns the virtual address and should
312 # become master and start advertising immediately.
313 def test_vrrp4_master_adv(self):
314 """ IPv4 Master VR advertises """
315 self.pg_enable_capture(self.pg_interfaces)
319 intvl = self._default_adv
320 vr = VppVRRPVirtualRouter(self, self.pg0, 100,
321 prio=prio, intvl=intvl,
322 flags=self._default_flags)
325 vr.start_stop(is_start=1)
326 self.logger.info(self.vapi.cli("show vrrp vr"))
327 vr.start_stop(is_start=0)
328 self.logger.info(self.vapi.cli("show vrrp vr"))
330 pkts = self.pg0.get_capture(4)
332 # Init -> Master: IGMP Join, VRRP adv, gratuitous ARP are sent
333 self.verify_vrrp4_igmp(pkts[0])
334 self.verify_vrrp4_adv(pkts[1], vr, prio=prio)
335 self.verify_vrrp4_garp(pkts[2], vr.virtual_ips()[0], vr.virtual_mac())
336 # Master -> Init: Adv with priority 0 sent to force an election
337 self.verify_vrrp4_adv(pkts[3], vr, prio=0)
339 vr.remove_vpp_config()
342 # VR with priority < 255 enters backup state and does not advertise as
343 # long as it receives higher priority advertisements
344 def test_vrrp4_backup_noadv(self):
345 """ IPv4 Backup VR does not advertise """
346 self.pg_enable_capture(self.pg_interfaces)
351 intvl = self._default_adv
352 intvl_s = intvl * 0.01
353 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
354 prio=prio, intvl=intvl,
355 flags=self._default_flags,
356 vips=[self.pg0.remote_ip4])
360 vr.start_stop(is_start=1)
362 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
363 # watch for advertisements for 2x the master down preemption timeout
364 end_time = vr.start_time() + 2 * vr.master_down_seconds()
366 # Init -> Backup: An IGMP join should be sent
367 pkts = self.pg0.get_capture(1)
368 self.verify_vrrp4_igmp(pkts[0])
370 # send higher prio advertisements, should not receive any
371 src_ip = self.pg0.remote_ip4
372 pkts = [vr.vrrp_adv_packet(prio=prio+10, src_ip=src_ip)]
373 while time.time() < end_time:
374 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
375 self.logger.info(self.vapi.cli("show trace"))
377 vr.start_stop(is_start=0)
378 self.logger.info(self.vapi.cli("show vrrp vr"))
379 vr.remove_vpp_config()
382 def test_vrrp4_master_arp(self):
383 """ IPv4 Master VR replies to ARP """
386 # VR virtual IP is the default, which is the pg local IP
389 intvl = self._default_adv
390 vr = VppVRRPVirtualRouter(self, self.pg0, 100,
391 prio=prio, intvl=intvl,
392 flags=self._default_flags)
397 # before the VR is up, ARP should resolve to interface MAC
398 self.pg0.resolve_arp()
399 self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
401 # start the VR, ARP should now resolve to virtual MAC
402 vr.start_stop(is_start=1)
403 self.pg0.resolve_arp()
404 self.assertEqual(self.pg0.local_mac, vr.virtual_mac())
406 # stop the VR, ARP should resolve to interface MAC again
407 vr.start_stop(is_start=0)
408 self.pg0.resolve_arp()
409 self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
411 vr.remove_vpp_config()
414 def test_vrrp4_backup_noarp(self):
415 """ IPv4 Backup VR ignores ARP """
416 # We need an address for a virtual IP that is not the IP that
417 # ARP requests will originate from
421 intvl = self._default_adv
422 vip = self.pg0.remote_hosts[1].ip4
423 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
424 prio=prio, intvl=intvl,
425 flags=self._default_flags,
430 arp_req = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
431 ARP(op=ARP.who_has, pdst=vip,
432 psrc=self.pg0.remote_ip4, hwsrc=self.pg0.remote_mac))
434 # Before the VR is started make sure no reply to request for VIP
436 self.pg_enable_capture(self.pg_interfaces)
437 self.send_and_assert_no_replies(self.pg0, [arp_req], timeout=1)
439 # VR should start in backup state and still should not reply to ARP
440 # send a higher priority adv to make sure it does not become master
441 adv = vr.vrrp_adv_packet(prio=prio+10, src_ip=self.pg0.remote_ip4)
442 vr.start_stop(is_start=1)
443 self.send_and_assert_no_replies(self.pg0, [adv, arp_req], timeout=1)
445 vr.start_stop(is_start=0)
446 vr.remove_vpp_config()
449 def test_vrrp4_election(self):
450 """ IPv4 Backup VR becomes master if no advertisements received """
454 intvl = self._default_adv
455 intvl_s = intvl * 0.01
456 vip = self.pg0.remote_ip4
457 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
458 prio=prio, intvl=intvl,
459 flags=self._default_flags,
464 # After adding the VR, it should be in the init state
465 vr.assert_state_equals(VRRP_VR_STATE_INIT)
468 vr.start_stop(is_start=1)
470 # VR should be in backup state after starting
471 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
472 end_time = vr.start_time() + vr.master_down_seconds()
474 # should not receive adverts until timer expires & state transition
475 self.pg_enable_capture(self.pg_interfaces)
476 while (time.time() + intvl_s) < end_time:
478 self.pg0.assert_nothing_captured(filter_out_fn=is_not_adv)
480 # VR should be in master state, should send an adv
481 self.pg0.enable_capture()
482 self.pg0.wait_for_packet(intvl_s, is_not_adv)
483 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
485 def test_vrrp4_backup_preempts(self):
486 """ IPv4 Backup VR preempts lower priority master """
490 intvl = self._default_adv
491 intvl_s = intvl * 0.01
492 vip = self.pg0.remote_ip4
493 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
494 prio=prio, intvl=intvl,
495 flags=self._default_flags,
500 # After adding the VR, it should be in the init state
501 vr.assert_state_equals(VRRP_VR_STATE_INIT)
504 vr.start_stop(is_start=1)
506 # VR should be in backup state after starting
507 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
508 end_time = vr.start_time() + vr.master_down_seconds()
510 # send lower prio advertisements until timer expires
511 src_ip = self.pg0.remote_ip4
512 pkts = [vr.vrrp_adv_packet(prio=prio-10, src_ip=src_ip)]
513 while time.time() + intvl_s < end_time:
514 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
515 self.logger.info(self.vapi.cli("show trace"))
517 # when timer expires, VR should take over as master
518 self.pg0.enable_capture()
519 self.pg0.wait_for_packet(timeout=intvl_s, filter_out_fn=is_not_adv)
520 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
522 def test_vrrp4_master_preempted(self):
523 """ IPv4 Master VR preempted by higher priority backup """
525 # A prio 255 VR cannot be preempted so the prio has to be lower and
526 # we have to wait for it to take over
529 intvl = self._default_adv
530 vip = self.pg0.remote_ip4
531 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
532 prio=prio, intvl=intvl,
533 flags=self._default_flags,
538 # After adding the VR, it should be in the init state
539 vr.assert_state_equals(VRRP_VR_STATE_INIT)
542 vr.start_stop(is_start=1)
543 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
545 # wait for VR to take over as master
546 end_time = vr.start_time() + vr.master_down_seconds()
547 sleep_s = end_time - time.time()
549 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
551 # Build advertisement packet and send it
552 pkts = [vr.vrrp_adv_packet(prio=255, src_ip=self.pg0.remote_ip4)]
553 self.pg_send(self.pg0, pkts)
555 # VR should be in backup state again
556 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
558 def test_vrrp4_accept_mode_disabled(self):
559 """ IPv4 Master VR does not reply for VIP w/ accept mode off """
561 # accept mode only matters when prio < 255, so it will have to
562 # come up as a backup and take over as master after the timeout
565 intvl = self._default_adv
566 vip = self.pg0.remote_hosts[4].ip4
567 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
568 prio=prio, intvl=intvl,
569 flags=self._default_flags,
574 # After adding the VR, it should be in the init state
575 vr.assert_state_equals(VRRP_VR_STATE_INIT)
578 vr.start_stop(is_start=1)
579 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
581 # wait for VR to take over as master
582 end_time = vr.start_time() + vr.master_down_seconds()
583 sleep_s = end_time - time.time()
585 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
587 # send an ICMP echo to the VR virtual IP address
588 echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
589 IP(dst=vip, src=self.pg0.remote_ip4) /
590 ICMP(seq=1, id=self.pg0.sw_if_index, type='echo-request'))
591 self.pg_send(self.pg0, [echo])
593 # wait for an echo reply. none should be received
595 self.pg0.assert_nothing_captured(filter_out_fn=is_not_echo_reply)
597 def test_vrrp4_accept_mode_enabled(self):
598 """ IPv4 Master VR replies for VIP w/ accept mode on """
600 # A prio 255 VR cannot be preempted so the prio has to be lower and
601 # we have to wait for it to take over
604 intvl = self._default_adv
605 vip = self.pg0.remote_hosts[4].ip4
606 flags = (VRRP_VR_FLAG_PREEMPT | VRRP_VR_FLAG_ACCEPT)
607 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
608 prio=prio, intvl=intvl,
614 # After adding the VR, it should be in the init state
615 vr.assert_state_equals(VRRP_VR_STATE_INIT)
618 vr.start_stop(is_start=1)
619 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
621 # wait for VR to take over as master
622 end_time = vr.start_time() + vr.master_down_seconds()
623 sleep_s = end_time - time.time()
625 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
627 # send an ICMP echo to the VR virtual IP address
628 echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
629 IP(dst=vip, src=self.pg0.remote_ip4) /
630 ICMP(seq=1, id=self.pg0.sw_if_index, type='echo-request'))
631 self.pg_send(self.pg0, [echo])
633 # wait for an echo reply.
635 rx_pkts = self.pg0.get_capture(expected_count=1, timeout=1,
636 filter_out_fn=is_not_echo_reply)
638 self.assertEqual(rx_pkts[0][IP].src, vip)
639 self.assertEqual(rx_pkts[0][IP].dst, self.pg0.remote_ip4)
640 self.assertEqual(icmptypes[rx_pkts[0][ICMP].type], "echo-reply")
641 self.assertEqual(rx_pkts[0][ICMP].seq, 1)
642 self.assertEqual(rx_pkts[0][ICMP].id, self.pg0.sw_if_index)
644 def test_vrrp4_intf_tracking(self):
645 """ IPv4 Master VR adjusts priority based on tracked interface """
649 intvl = self._default_adv
650 intvl_s = intvl * 0.01
651 vip = self.pg0.local_ip4
652 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
653 prio=prio, intvl=intvl,
654 flags=self._default_flags,
659 # After adding the VR, it should be in the init state
660 vr.assert_state_equals(VRRP_VR_STATE_INIT)
662 # add pg1 as a tracked interface and start the VR
664 adjusted_prio = prio - adjustment
665 vr.add_del_tracked_interface(is_add=1,
666 sw_if_index=self.pg1.sw_if_index,
668 vr.start_stop(is_start=1)
669 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
671 adv_configured = vr.vrrp_adv_packet(prio=prio)
672 adv_adjusted = vr.vrrp_adv_packet(prio=adjusted_prio)
674 # tracked intf is up -> advertised priority == configured priority
675 self.pg0.enable_capture()
676 rx = self.pg0.wait_for_packet(timeout=intvl_s,
677 filter_out_fn=is_not_adv)
678 self.assertEqual(rx, adv_configured)
680 # take down pg1, verify priority is now being adjusted
681 self.pg1.admin_down()
682 self.pg0.enable_capture()
683 rx = self.pg0.wait_for_packet(timeout=intvl_s,
684 filter_out_fn=is_not_adv)
685 self.assertEqual(rx, adv_adjusted)
687 # bring up pg1, verify priority now matches configured value
689 self.pg0.enable_capture()
690 rx = self.pg0.wait_for_packet(timeout=intvl_s,
691 filter_out_fn=is_not_adv)
692 self.assertEqual(rx, adv_configured)
694 # remove IP address from pg1, verify priority now being adjusted
695 self.pg1.unconfig_ip4()
696 self.pg0.enable_capture()
697 rx = self.pg0.wait_for_packet(timeout=intvl_s,
698 filter_out_fn=is_not_adv)
699 self.assertEqual(rx, adv_adjusted)
701 # add IP address to pg1, verify priority now matches configured value
702 self.pg1.config_ip4()
703 self.pg0.enable_capture()
704 rx = self.pg0.wait_for_packet(timeout=intvl_s,
705 filter_out_fn=is_not_adv)
706 self.assertEqual(rx, adv_configured)
708 def test_vrrp4_master_adv_unicast(self):
709 """ IPv4 Master VR advertises (unicast) """
713 intvl = self._default_adv
714 intvl_s = intvl * 0.01
715 vip = self.pg0.local_ip4
716 flags = (self._default_flags | VRRP_VR_FLAG_UNICAST)
717 unicast_peer = self.pg0.remote_hosts[4]
718 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
719 prio=prio, intvl=intvl,
724 vr.set_unicast_peers([unicast_peer.ip4])
726 # After adding the VR, it should be in the init state
727 vr.assert_state_equals(VRRP_VR_STATE_INIT)
729 # Start VR, transition to master
730 vr.start_stop(is_start=1)
731 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
733 self.pg0.enable_capture()
734 rx = self.pg0.wait_for_packet(timeout=intvl_s,
735 filter_out_fn=is_not_adv)
737 self.assertTrue(rx.haslayer(Ether))
738 self.assertTrue(rx.haslayer(IP))
739 self.assertTrue(rx.haslayer(VRRPv3))
740 self.assertEqual(rx[Ether].src, self.pg0.local_mac)
741 self.assertEqual(rx[Ether].dst, unicast_peer.mac)
742 self.assertEqual(rx[IP].src, self.pg0.local_ip4)
743 self.assertEqual(rx[IP].dst, unicast_peer.ip4)
744 self.assertEqual(rx[VRRPv3].vrid, vr_id)
745 self.assertEqual(rx[VRRPv3].priority, prio)
746 self.assertEqual(rx[VRRPv3].ipcount, 1)
747 self.assertEqual(rx[VRRPv3].addrlist, [vip])
750 @unittest.skipUnless(running_extended_tests, "part of extended tests")
751 class TestVRRP6(VppTestCase):
752 """ IPv6 VRRP Test Case """
756 super(TestVRRP6, cls).setUpClass()
759 def tearDownClass(cls):
760 super(TestVRRP6, cls).tearDownClass()
763 super(TestVRRP6, self).setUp()
765 self.create_pg_interfaces(range(2))
767 for i in self.pg_interfaces:
770 i.generate_remote_hosts(5)
771 i.configure_ipv6_neighbors()
774 self._default_flags = (VRRP_VR_FLAG_IPV6 | VRRP_VR_FLAG_PREEMPT)
775 self._default_adv = 100
780 vr_api = vr.query_vpp_config()
781 if vr_api.runtime.state != VRRP_VR_STATE_INIT:
782 vr.start_stop(is_start=0)
783 vr.remove_vpp_config()
785 self.logger.error("Error cleaning up")
787 for i in self.pg_interfaces:
794 super(TestVRRP6, self).tearDown()
796 def verify_vrrp6_mlr(self, pkt, vr):
798 self.assertEqual(ip6.dst, "ff02::16")
799 self.assertEqual(ipv6nh[ip6.nh], "Hop-by-Hop Option Header")
801 hbh = pkt[IPv6ExtHdrHopByHop]
802 self.assertEqual(ipv6nh[hbh.nh], "ICMPv6")
804 self.assertTrue(pkt.haslayer(ICMPv6MLReport2))
805 mlr = pkt[ICMPv6MLReport2]
806 # should contain mc addr records for:
807 # - VRRPv3 multicast addr
808 # - solicited node mc addr record for each VR virtual IPv6 address
809 vips = vr.virtual_ips()
810 self.assertEqual(mlr.records_number, len(vips) + 1)
811 self.assertEqual(mlr.records[0].dst, vr.adv_dest_ip())
813 def verify_vrrp6_adv(self, rx_pkt, vr, prio=None):
814 self.assertTrue(rx_pkt.haslayer(Ether))
815 self.assertTrue(rx_pkt.haslayer(IPv6))
816 self.assertTrue(rx_pkt.haslayer(VRRPv3))
818 # generate a packet for this VR and compare it to the one received
819 pkt = vr.vrrp_adv_packet(prio=prio)
820 self.assertTrue(rx_pkt.haslayer(Ether))
821 self.assertTrue(rx_pkt.haslayer(IPv6))
822 self.assertTrue(rx_pkt.haslayer(VRRPv3))
824 self.assertEqual(pkt, rx_pkt)
826 def verify_vrrp6_gna(self, pkt, vr):
827 self.assertTrue(pkt.haslayer(Ether))
828 self.assertTrue(pkt.haslayer(IPv6))
829 self.assertTrue(pkt.haslayer(ICMPv6ND_NA))
830 self.assertTrue(pkt.haslayer(ICMPv6NDOptDstLLAddr))
832 self.assertEqual(pkt[Ether].dst, "33:33:00:00:00:01")
834 self.assertEqual(pkt[IPv6].dst, "ff02::1")
835 # convert addrs to packed format since string versions could differ
836 src_addr = inet_pton(AF_INET6, pkt[IPv6].src)
837 vr_ll_addr = inet_pton(AF_INET6, vr.interface().local_ip6_ll)
838 self.assertEqual(src_addr, vr_ll_addr)
840 self.assertTrue(pkt[ICMPv6ND_NA].tgt in vr.virtual_ips())
841 self.assertEqual(pkt[ICMPv6NDOptDstLLAddr].lladdr, vr.virtual_mac())
843 # VR with priority 255 owns the virtual address and should
844 # become master and start advertising immediately.
845 def test_vrrp6_master_adv(self):
846 """ IPv6 Master VR advertises """
847 self.pg_enable_capture(self.pg_interfaces)
851 intvl = self._default_adv
852 vr = VppVRRPVirtualRouter(self, self.pg0, 100,
853 prio=prio, intvl=intvl,
854 flags=self._default_flags)
858 self.logger.info(self.vapi.cli("show vrrp vr"))
859 vr.start_stop(is_start=1)
860 self.logger.info(self.vapi.cli("show vrrp vr"))
861 vr.start_stop(is_start=0)
862 self.logger.info(self.vapi.cli("show vrrp vr"))
864 pkts = self.pg0.get_capture(4, filter_out_fn=None)
866 # Init -> Master: Multicast group Join, VRRP adv, gratuitous NAs sent
867 self.verify_vrrp6_mlr(pkts[0], vr)
868 self.verify_vrrp6_adv(pkts[1], vr, prio=prio)
869 self.verify_vrrp6_gna(pkts[2], vr)
870 # Master -> Init: Adv with priority 0 sent to force an election
871 self.verify_vrrp6_adv(pkts[3], vr, prio=0)
873 vr.remove_vpp_config()
876 # VR with priority < 255 enters backup state and does not advertise as
877 # long as it receives higher priority advertisements
878 def test_vrrp6_backup_noadv(self):
879 """ IPv6 Backup VR does not advertise """
880 self.pg_enable_capture(self.pg_interfaces)
885 intvl = self._default_adv
886 intvl_s = intvl * 0.01
887 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
888 prio=prio, intvl=intvl,
889 flags=self._default_flags,
890 vips=[self.pg0.remote_ip6])
894 vr.start_stop(is_start=1)
896 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
897 # watch for advertisements for 2x the master down preemption timeout
898 end_time = vr.start_time() + 2 * vr.master_down_seconds()
900 # Init -> Backup: A multicast listener report should be sent
901 pkts = self.pg0.get_capture(1, filter_out_fn=None)
903 # send higher prio advertisements, should not see VPP send any
904 src_ip = self.pg0.remote_ip6_ll
906 pkts = [vr.vrrp_adv_packet(prio=prio+10, src_ip=src_ip)]
907 self.logger.info(self.vapi.cli("show vlib graph"))
908 while time.time() < end_time:
909 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
910 self.logger.info(self.vapi.cli("show trace"))
913 vr.start_stop(is_start=0)
914 self.logger.info(self.vapi.cli("show vrrp vr"))
915 vr.remove_vpp_config()
918 def test_vrrp6_master_nd(self):
919 """ IPv6 Master VR replies to NDP """
922 # VR virtual IP is the default, which is the pg local IP
925 intvl = self._default_adv
926 vr = VppVRRPVirtualRouter(self, self.pg0, 100,
927 prio=prio, intvl=intvl,
928 flags=self._default_flags)
932 # before the VR is up, NDP should resolve to interface MAC
933 self.pg0.resolve_ndp()
934 self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
936 # start the VR, NDP should now resolve to virtual MAC
937 vr.start_stop(is_start=1)
938 self.pg0.resolve_ndp()
939 self.assertEqual(self.pg0.local_mac, vr.virtual_mac())
941 # stop the VR, ARP should resolve to interface MAC again
942 vr.start_stop(is_start=0)
943 self.pg0.resolve_ndp()
944 self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
946 vr.remove_vpp_config()
949 def test_vrrp6_backup_nond(self):
950 """ IPv6 Backup VR ignores NDP """
951 # We need an address for a virtual IP that is not the IP that
952 # ARP requests will originate from
956 intvl = self._default_adv
957 intvl_s = intvl * 0.01
958 vip = self.pg0.remote_hosts[1].ip6
959 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
960 prio=prio, intvl=intvl,
961 flags=self._default_flags,
966 nsma = in6_getnsma(inet_pton(socket.AF_INET6, vip))
967 dmac = in6_getnsmac(nsma)
968 dst_ip = inet_ntop(socket.AF_INET6, nsma)
970 ndp_req = (Ether(dst=dmac, src=self.pg0.remote_mac) /
971 IPv6(dst=dst_ip, src=self.pg0.remote_ip6) /
972 ICMPv6ND_NS(tgt=vip) /
973 ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
975 # Before the VR is started make sure no reply to request for VIP
976 self.send_and_assert_no_replies(self.pg0, [ndp_req], timeout=1)
978 # VR should start in backup state and still should not reply to NDP
979 # send a higher priority adv to make sure it does not become master
980 adv = vr.vrrp_adv_packet(prio=prio+10, src_ip=self.pg0.remote_ip6)
981 pkts = [adv, ndp_req]
982 vr.start_stop(is_start=1)
983 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
985 vr.start_stop(is_start=0)
987 def test_vrrp6_election(self):
988 """ IPv6 Backup VR becomes master if no advertisements received """
992 intvl = self._default_adv
993 intvl_s = intvl * 0.01
994 vip = self.pg0.remote_ip6
995 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
996 prio=prio, intvl=intvl,
997 flags=self._default_flags,
1002 # After adding the VR, it should be in the init state
1003 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1006 vr.start_stop(is_start=1)
1008 # VR should be in backup state after starting
1009 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1010 end_time = vr.start_time() + vr.master_down_seconds()
1012 # no advertisements should arrive until timer expires
1013 self.pg0.enable_capture()
1014 while (time.time() + intvl_s) < end_time:
1016 self.pg0.assert_nothing_captured(filter_out_fn=is_not_adv)
1018 # VR should be in master state after timer expires
1019 self.pg0.enable_capture()
1020 self.pg0.wait_for_packet(intvl_s, is_not_adv)
1021 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1023 def test_vrrp6_backup_preempts(self):
1024 """ IPv6 Backup VR preempts lower priority master """
1028 intvl = self._default_adv
1029 intvl_s = intvl * 0.01
1030 vip = self.pg0.remote_ip6
1031 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1032 prio=prio, intvl=intvl,
1033 flags=self._default_flags,
1035 self._vrs.append(vr)
1038 # After adding the VR, it should be in the init state
1039 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1042 vr.start_stop(is_start=1)
1044 # VR should be in backup state after starting
1045 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1046 end_time = vr.start_time() + vr.master_down_seconds()
1048 # send lower prio advertisements until timer expires
1049 src_ip = self.pg0.remote_ip6
1050 pkts = [vr.vrrp_adv_packet(prio=prio-10, src_ip=src_ip)]
1051 while (time.time() + intvl_s) < end_time:
1052 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
1053 self.logger.info(self.vapi.cli("show trace"))
1055 # when timer expires, VR should take over as master
1056 self.pg0.enable_capture()
1057 self.pg0.wait_for_packet(timeout=intvl_s, filter_out_fn=is_not_adv)
1058 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1060 def test_vrrp6_master_preempted(self):
1061 """ IPv6 Master VR preempted by higher priority backup """
1063 # A prio 255 VR cannot be preempted so the prio has to be lower and
1064 # we have to wait for it to take over
1067 intvl = self._default_adv
1068 vip = self.pg0.remote_ip6
1069 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1070 prio=prio, intvl=intvl,
1071 flags=self._default_flags,
1073 self._vrs.append(vr)
1076 # After adding the VR, it should be in the init state
1077 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1080 vr.start_stop(is_start=1)
1081 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1083 # wait for VR to take over as master
1084 end_time = vr.start_time() + vr.master_down_seconds()
1085 sleep_s = end_time - time.time()
1087 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1089 # Build advertisement packet and send it
1090 pkts = [vr.vrrp_adv_packet(prio=255, src_ip=self.pg0.remote_ip6)]
1091 self.pg_send(self.pg0, pkts)
1093 # VR should be in backup state again
1094 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1096 def test_vrrp6_accept_mode_disabled(self):
1097 """ IPv6 Master VR does not reply for VIP w/ accept mode off """
1099 # accept mode only matters when prio < 255, so it will have to
1100 # come up as a backup and take over as master after the timeout
1103 intvl = self._default_adv
1104 vip = self.pg0.remote_hosts[4].ip6
1105 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1106 prio=prio, intvl=intvl,
1107 flags=self._default_flags,
1109 self._vrs.append(vr)
1112 # After adding the VR, it should be in the init state
1113 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1116 vr.start_stop(is_start=1)
1117 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1119 # wait for VR to take over as master
1120 end_time = vr.start_time() + vr.master_down_seconds()
1121 sleep_s = end_time - time.time()
1123 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1125 # send an ICMPv6 echo to the VR virtual IP address
1126 echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
1127 IPv6(dst=vip, src=self.pg0.remote_ip6) /
1128 ICMPv6EchoRequest(seq=1, id=self.pg0.sw_if_index))
1129 self.pg_send(self.pg0, [echo])
1131 # wait for an echo reply. none should be received
1133 self.pg0.assert_nothing_captured(filter_out_fn=is_not_echo_reply)
1135 def test_vrrp6_accept_mode_enabled(self):
1136 """ IPv6 Master VR replies for VIP w/ accept mode on """
1138 # A prio 255 VR cannot be preempted so the prio has to be lower and
1139 # we have to wait for it to take over
1142 intvl = self._default_adv
1143 vip = self.pg0.remote_hosts[4].ip6
1144 flags = (self._default_flags | VRRP_VR_FLAG_ACCEPT)
1145 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1146 prio=prio, intvl=intvl,
1149 self._vrs.append(vr)
1152 # After adding the VR, it should be in the init state
1153 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1156 vr.start_stop(is_start=1)
1157 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1159 # wait for VR to take over as master
1160 end_time = vr.start_time() + vr.master_down_seconds()
1161 sleep_s = end_time - time.time()
1163 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1165 # send an ICMP echo to the VR virtual IP address
1166 echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
1167 IPv6(dst=vip, src=self.pg0.remote_ip6) /
1168 ICMPv6EchoRequest(seq=1, id=self.pg0.sw_if_index))
1169 self.pg_send(self.pg0, [echo])
1171 # wait for an echo reply.
1173 rx_pkts = self.pg0.get_capture(expected_count=1, timeout=1,
1174 filter_out_fn=is_not_echo_reply)
1176 self.assertEqual(rx_pkts[0][IPv6].src, vip)
1177 self.assertEqual(rx_pkts[0][IPv6].dst, self.pg0.remote_ip6)
1178 self.assertEqual(rx_pkts[0][ICMPv6EchoReply].seq, 1)
1179 self.assertEqual(rx_pkts[0][ICMPv6EchoReply].id, self.pg0.sw_if_index)
1181 def test_vrrp6_intf_tracking(self):
1182 """ IPv6 Master VR adjusts priority based on tracked interface """
1186 intvl = self._default_adv
1187 intvl_s = intvl * 0.01
1188 vip = self.pg0.local_ip6
1189 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1190 prio=prio, intvl=intvl,
1191 flags=self._default_flags,
1193 self._vrs.append(vr)
1196 # After adding the VR, it should be in the init state
1197 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1199 # add pg1 as a tracked interface and start the VR
1201 adjusted_prio = prio - adjustment
1202 vr.add_del_tracked_interface(is_add=1,
1203 sw_if_index=self.pg1.sw_if_index,
1205 vr.start_stop(is_start=1)
1206 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1208 adv_configured = vr.vrrp_adv_packet(prio=prio)
1209 adv_adjusted = vr.vrrp_adv_packet(prio=adjusted_prio)
1211 # tracked intf is up -> advertised priority == configured priority
1212 self.pg0.enable_capture()
1213 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1214 filter_out_fn=is_not_adv)
1215 self.assertEqual(rx, adv_configured)
1217 # take down pg1, verify priority is now being adjusted
1218 self.pg1.admin_down()
1219 self.pg0.enable_capture()
1220 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1221 filter_out_fn=is_not_adv)
1222 self.assertEqual(rx, adv_adjusted)
1224 # bring up pg1, verify priority now matches configured value
1226 self.pg0.enable_capture()
1227 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1228 filter_out_fn=is_not_adv)
1229 self.assertEqual(rx, adv_configured)
1231 # remove IP address from pg1, verify priority now being adjusted
1232 self.pg1.unconfig_ip6()
1233 self.pg0.enable_capture()
1234 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1235 filter_out_fn=is_not_adv)
1236 self.assertEqual(rx, adv_adjusted)
1238 # add IP address to pg1, verify priority now matches configured value
1239 self.pg1.config_ip6()
1240 self.pg0.enable_capture()
1241 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1242 filter_out_fn=is_not_adv)
1243 self.assertEqual(rx, adv_configured)
1245 def test_vrrp6_master_adv_unicast(self):
1246 """ IPv6 Master VR advertises (unicast) """
1250 intvl = self._default_adv
1251 intvl_s = intvl * 0.01
1252 vip = self.pg0.local_ip6
1253 flags = (self._default_flags | VRRP_VR_FLAG_UNICAST)
1254 unicast_peer = self.pg0.remote_hosts[4]
1255 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1256 prio=prio, intvl=intvl,
1259 self._vrs.append(vr)
1261 vr.set_unicast_peers([unicast_peer.ip6])
1263 # After adding the VR, it should be in the init state
1264 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1266 # Start VR, transition to master
1267 vr.start_stop(is_start=1)
1268 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1270 self.pg0.enable_capture()
1271 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1272 filter_out_fn=is_not_adv)
1274 self.assertTrue(rx.haslayer(Ether))
1275 self.assertTrue(rx.haslayer(IPv6))
1276 self.assertTrue(rx.haslayer(VRRPv3))
1277 self.assertEqual(rx[Ether].src, self.pg0.local_mac)
1278 self.assertEqual(rx[Ether].dst, unicast_peer.mac)
1279 self.assertEqual(ip6_normalize(rx[IPv6].src),
1280 ip6_normalize(self.pg0.local_ip6_ll))
1281 self.assertEqual(ip6_normalize(rx[IPv6].dst),
1282 ip6_normalize(unicast_peer.ip6))
1283 self.assertEqual(rx[VRRPv3].vrid, vr_id)
1284 self.assertEqual(rx[VRRPv3].priority, prio)
1285 self.assertEqual(rx[VRRPv3].ipcount, 1)
1286 self.assertEqual(rx[VRRPv3].addrlist, [vip])
1289 if __name__ == '__main__':
1290 unittest.main(testRunner=VppTestRunner)