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
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 class TestVRRP4(VppTestCase):
218 """ IPv4 VRRP Test Case """
222 super(TestVRRP4, cls).setUpClass()
225 def tearDownClass(cls):
226 super(TestVRRP4, cls).tearDownClass()
229 super(TestVRRP4, self).setUp()
231 self.create_pg_interfaces(range(2))
233 for i in self.pg_interfaces:
236 i.generate_remote_hosts(5)
237 i.configure_ipv4_neighbors()
240 self._default_flags = VRRP_VR_FLAG_PREEMPT
241 self._default_adv = 100
246 vr_api = vr.query_vpp_config()
247 if vr_api.runtime.state != VRRP_VR_STATE_INIT:
248 vr.start_stop(is_start=0)
249 vr.remove_vpp_config()
251 self.logger.error("Error cleaning up")
253 for i in self.pg_interfaces:
260 super(TestVRRP4, self).tearDown()
262 def verify_vrrp4_igmp(self, pkt):
264 self.assertEqual(ip.dst, "224.0.0.22")
265 self.assertEqual(ip.proto, 2)
268 self.assertEqual(IGMPv3.igmpv3types[igmp.type],
269 "Version 3 Membership Report")
271 igmpmr = pkt[IGMPv3mr]
272 self.assertEqual(igmpmr.numgrp, 1)
273 self.assertEqual(igmpmr.records[0].maddr, "224.0.0.18")
275 def verify_vrrp4_garp(self, pkt, vip, vmac):
278 # ARP "who-has" op == 1
279 self.assertEqual(arp.op, 1)
280 self.assertEqual(arp.pdst, arp.psrc)
281 self.assertEqual(arp.pdst, vip)
282 self.assertEqual(arp.hwsrc, vmac)
284 def verify_vrrp4_adv(self, rx_pkt, vr, prio=None):
285 vips = vr.virtual_ips()
288 vrrp = rx_pkt[VRRPv3]
290 pkt = vr.vrrp_adv_packet(prio=prio)
292 # Source MAC is virtual MAC, destination is multicast MAC
293 self.assertEqual(eth.src, vr.virtual_mac())
294 self.assertEqual(eth.dst, vr.adv_dest_mac())
296 self.assertEqual(ip.dst, "224.0.0.18")
297 self.assertEqual(ip.ttl, 255)
298 self.assertEqual(ip.proto, IPPROTO_VRRP)
300 self.assertEqual(vrrp.version, 3)
301 self.assertEqual(vrrp.type, 1)
302 self.assertEqual(vrrp.vrid, vr.vr_id())
305 self.assertEqual(vrrp.priority, prio)
306 self.assertEqual(vrrp.ipcount, len(vips))
307 self.assertEqual(vrrp.adv, vr.adv_interval())
308 self.assertListEqual(vrrp.addrlist, vips)
310 # VR with priority 255 owns the virtual address and should
311 # become master and start advertising immediately.
312 def test_vrrp4_master_adv(self):
313 """ IPv4 Master VR advertises """
314 self.pg_enable_capture(self.pg_interfaces)
318 intvl = self._default_adv
319 vr = VppVRRPVirtualRouter(self, self.pg0, 100,
320 prio=prio, intvl=intvl,
321 flags=self._default_flags)
324 vr.start_stop(is_start=1)
325 self.logger.info(self.vapi.cli("show vrrp vr"))
326 vr.start_stop(is_start=0)
327 self.logger.info(self.vapi.cli("show vrrp vr"))
329 pkts = self.pg0.get_capture(4)
331 # Init -> Master: IGMP Join, VRRP adv, gratuitous ARP are sent
332 self.verify_vrrp4_igmp(pkts[0])
333 self.verify_vrrp4_adv(pkts[1], vr, prio=prio)
334 self.verify_vrrp4_garp(pkts[2], vr.virtual_ips()[0], vr.virtual_mac())
335 # Master -> Init: Adv with priority 0 sent to force an election
336 self.verify_vrrp4_adv(pkts[3], vr, prio=0)
338 vr.remove_vpp_config()
341 # VR with priority < 255 enters backup state and does not advertise as
342 # long as it receives higher priority advertisements
343 def test_vrrp4_backup_noadv(self):
344 """ IPv4 Backup VR does not advertise """
345 self.pg_enable_capture(self.pg_interfaces)
350 intvl = self._default_adv
351 intvl_s = intvl * 0.01
352 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
353 prio=prio, intvl=intvl,
354 flags=self._default_flags,
355 vips=[self.pg0.remote_ip4])
359 vr.start_stop(is_start=1)
361 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
362 # watch for advertisements for 2x the master down preemption timeout
363 end_time = vr.start_time() + 2 * vr.master_down_seconds()
365 # Init -> Backup: An IGMP join should be sent
366 pkts = self.pg0.get_capture(1)
367 self.verify_vrrp4_igmp(pkts[0])
369 # send higher prio advertisements, should not receive any
370 src_ip = self.pg0.remote_ip4
371 pkts = [vr.vrrp_adv_packet(prio=prio+10, src_ip=src_ip)]
372 while time.time() < end_time:
373 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
374 self.logger.info(self.vapi.cli("show trace"))
376 vr.start_stop(is_start=0)
377 self.logger.info(self.vapi.cli("show vrrp vr"))
378 vr.remove_vpp_config()
381 def test_vrrp4_master_arp(self):
382 """ IPv4 Master VR replies to ARP """
385 # VR virtual IP is the default, which is the pg local IP
388 intvl = self._default_adv
389 vr = VppVRRPVirtualRouter(self, self.pg0, 100,
390 prio=prio, intvl=intvl,
391 flags=self._default_flags)
396 # before the VR is up, ARP should resolve to interface MAC
397 self.pg0.resolve_arp()
398 self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
400 # start the VR, ARP should now resolve to virtual MAC
401 vr.start_stop(is_start=1)
402 self.pg0.resolve_arp()
403 self.assertEqual(self.pg0.local_mac, vr.virtual_mac())
405 # stop the VR, ARP should resolve to interface MAC again
406 vr.start_stop(is_start=0)
407 self.pg0.resolve_arp()
408 self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
410 vr.remove_vpp_config()
413 def test_vrrp4_backup_noarp(self):
414 """ IPv4 Backup VR ignores ARP """
415 # We need an address for a virtual IP that is not the IP that
416 # ARP requests will originate from
420 intvl = self._default_adv
421 vip = self.pg0.remote_hosts[1].ip4
422 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
423 prio=prio, intvl=intvl,
424 flags=self._default_flags,
429 arp_req = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
430 ARP(op=ARP.who_has, pdst=vip,
431 psrc=self.pg0.remote_ip4, hwsrc=self.pg0.remote_mac))
433 # Before the VR is started make sure no reply to request for VIP
435 self.pg_enable_capture(self.pg_interfaces)
436 self.send_and_assert_no_replies(self.pg0, [arp_req], timeout=1)
438 # VR should start in backup state and still should not reply to ARP
439 # send a higher priority adv to make sure it does not become master
440 adv = vr.vrrp_adv_packet(prio=prio+10, src_ip=self.pg0.remote_ip4)
441 vr.start_stop(is_start=1)
442 self.send_and_assert_no_replies(self.pg0, [adv, arp_req], timeout=1)
444 vr.start_stop(is_start=0)
445 vr.remove_vpp_config()
448 def test_vrrp4_election(self):
449 """ IPv4 Backup VR becomes master if no advertisements received """
453 intvl = self._default_adv
454 intvl_s = intvl * 0.01
455 vip = self.pg0.remote_ip4
456 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
457 prio=prio, intvl=intvl,
458 flags=self._default_flags,
463 # After adding the VR, it should be in the init state
464 vr.assert_state_equals(VRRP_VR_STATE_INIT)
467 vr.start_stop(is_start=1)
469 # VR should be in backup state after starting
470 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
471 end_time = vr.start_time() + vr.master_down_seconds()
473 # should not receive adverts until timer expires & state transition
474 self.pg_enable_capture(self.pg_interfaces)
475 while (time.time() + intvl_s) < end_time:
477 self.pg0.assert_nothing_captured(filter_out_fn=is_not_adv)
479 # VR should be in master state, should send an adv
480 self.pg0.enable_capture()
481 self.pg0.wait_for_packet(intvl_s, is_not_adv)
482 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
484 def test_vrrp4_backup_preempts(self):
485 """ IPv4 Backup VR preempts lower priority master """
489 intvl = self._default_adv
490 intvl_s = intvl * 0.01
491 vip = self.pg0.remote_ip4
492 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
493 prio=prio, intvl=intvl,
494 flags=self._default_flags,
499 # After adding the VR, it should be in the init state
500 vr.assert_state_equals(VRRP_VR_STATE_INIT)
503 vr.start_stop(is_start=1)
505 # VR should be in backup state after starting
506 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
507 end_time = vr.start_time() + vr.master_down_seconds()
509 # send lower prio advertisements until timer expires
510 src_ip = self.pg0.remote_ip4
511 pkts = [vr.vrrp_adv_packet(prio=prio-10, src_ip=src_ip)]
512 while time.time() + intvl_s < end_time:
513 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
514 self.logger.info(self.vapi.cli("show trace"))
516 # when timer expires, VR should take over as master
517 self.pg0.enable_capture()
518 self.pg0.wait_for_packet(timeout=intvl_s, filter_out_fn=is_not_adv)
519 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
521 def test_vrrp4_master_preempted(self):
522 """ IPv4 Master VR preempted by higher priority backup """
524 # A prio 255 VR cannot be preempted so the prio has to be lower and
525 # we have to wait for it to take over
528 intvl = self._default_adv
529 vip = self.pg0.remote_ip4
530 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
531 prio=prio, intvl=intvl,
532 flags=self._default_flags,
537 # After adding the VR, it should be in the init state
538 vr.assert_state_equals(VRRP_VR_STATE_INIT)
541 vr.start_stop(is_start=1)
542 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
544 # wait for VR to take over as master
545 end_time = vr.start_time() + vr.master_down_seconds()
546 sleep_s = end_time - time.time()
548 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
550 # Build advertisement packet and send it
551 pkts = [vr.vrrp_adv_packet(prio=255, src_ip=self.pg0.remote_ip4)]
552 self.pg_send(self.pg0, pkts)
554 # VR should be in backup state again
555 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
557 def test_vrrp4_accept_mode_disabled(self):
558 """ IPv4 Master VR does not reply for VIP w/ accept mode off """
560 # accept mode only matters when prio < 255, so it will have to
561 # come up as a backup and take over as master after the timeout
564 intvl = self._default_adv
565 vip = self.pg0.remote_hosts[4].ip4
566 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
567 prio=prio, intvl=intvl,
568 flags=self._default_flags,
573 # After adding the VR, it should be in the init state
574 vr.assert_state_equals(VRRP_VR_STATE_INIT)
577 vr.start_stop(is_start=1)
578 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
580 # wait for VR to take over as master
581 end_time = vr.start_time() + vr.master_down_seconds()
582 sleep_s = end_time - time.time()
584 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
586 # send an ICMP echo to the VR virtual IP address
587 echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
588 IP(dst=vip, src=self.pg0.remote_ip4) /
589 ICMP(seq=1, id=self.pg0.sw_if_index, type='echo-request'))
590 self.pg_send(self.pg0, [echo])
592 # wait for an echo reply. none should be received
594 self.pg0.assert_nothing_captured(filter_out_fn=is_not_echo_reply)
596 def test_vrrp4_accept_mode_enabled(self):
597 """ IPv4 Master VR replies for VIP w/ accept mode on """
599 # A prio 255 VR cannot be preempted so the prio has to be lower and
600 # we have to wait for it to take over
603 intvl = self._default_adv
604 vip = self.pg0.remote_hosts[4].ip4
605 flags = (VRRP_VR_FLAG_PREEMPT | VRRP_VR_FLAG_ACCEPT)
606 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
607 prio=prio, intvl=intvl,
613 # After adding the VR, it should be in the init state
614 vr.assert_state_equals(VRRP_VR_STATE_INIT)
617 vr.start_stop(is_start=1)
618 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
620 # wait for VR to take over as master
621 end_time = vr.start_time() + vr.master_down_seconds()
622 sleep_s = end_time - time.time()
624 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
626 # send an ICMP echo to the VR virtual IP address
627 echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
628 IP(dst=vip, src=self.pg0.remote_ip4) /
629 ICMP(seq=1, id=self.pg0.sw_if_index, type='echo-request'))
630 self.pg_send(self.pg0, [echo])
632 # wait for an echo reply.
634 rx_pkts = self.pg0.get_capture(expected_count=1, timeout=1,
635 filter_out_fn=is_not_echo_reply)
637 self.assertEqual(rx_pkts[0][IP].src, vip)
638 self.assertEqual(rx_pkts[0][IP].dst, self.pg0.remote_ip4)
639 self.assertEqual(icmptypes[rx_pkts[0][ICMP].type], "echo-reply")
640 self.assertEqual(rx_pkts[0][ICMP].seq, 1)
641 self.assertEqual(rx_pkts[0][ICMP].id, self.pg0.sw_if_index)
643 def test_vrrp4_intf_tracking(self):
644 """ IPv4 Master VR adjusts priority based on tracked interface """
648 intvl = self._default_adv
649 intvl_s = intvl * 0.01
650 vip = self.pg0.local_ip4
651 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
652 prio=prio, intvl=intvl,
653 flags=self._default_flags,
658 # After adding the VR, it should be in the init state
659 vr.assert_state_equals(VRRP_VR_STATE_INIT)
661 # add pg1 as a tracked interface and start the VR
663 adjusted_prio = prio - adjustment
664 vr.add_del_tracked_interface(is_add=1,
665 sw_if_index=self.pg1.sw_if_index,
667 vr.start_stop(is_start=1)
668 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
670 adv_configured = vr.vrrp_adv_packet(prio=prio)
671 adv_adjusted = vr.vrrp_adv_packet(prio=adjusted_prio)
673 # tracked intf is up -> advertised priority == configured priority
674 self.pg0.enable_capture()
675 rx = self.pg0.wait_for_packet(timeout=intvl_s,
676 filter_out_fn=is_not_adv)
677 self.assertEqual(rx, adv_configured)
679 # take down pg1, verify priority is now being adjusted
680 self.pg1.admin_down()
681 self.pg0.enable_capture()
682 rx = self.pg0.wait_for_packet(timeout=intvl_s,
683 filter_out_fn=is_not_adv)
684 self.assertEqual(rx, adv_adjusted)
686 # bring up pg1, verify priority now matches configured value
688 self.pg0.enable_capture()
689 rx = self.pg0.wait_for_packet(timeout=intvl_s,
690 filter_out_fn=is_not_adv)
691 self.assertEqual(rx, adv_configured)
693 # remove IP address from pg1, verify priority now being adjusted
694 self.pg1.unconfig_ip4()
695 self.pg0.enable_capture()
696 rx = self.pg0.wait_for_packet(timeout=intvl_s,
697 filter_out_fn=is_not_adv)
698 self.assertEqual(rx, adv_adjusted)
700 # add IP address to pg1, verify priority now matches configured value
701 self.pg1.config_ip4()
702 self.pg0.enable_capture()
703 rx = self.pg0.wait_for_packet(timeout=intvl_s,
704 filter_out_fn=is_not_adv)
705 self.assertEqual(rx, adv_configured)
707 def test_vrrp4_master_adv_unicast(self):
708 """ IPv4 Master VR advertises (unicast) """
712 intvl = self._default_adv
713 intvl_s = intvl * 0.01
714 vip = self.pg0.local_ip4
715 flags = (self._default_flags | VRRP_VR_FLAG_UNICAST)
716 unicast_peer = self.pg0.remote_hosts[4]
717 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
718 prio=prio, intvl=intvl,
723 vr.set_unicast_peers([unicast_peer.ip4])
725 # After adding the VR, it should be in the init state
726 vr.assert_state_equals(VRRP_VR_STATE_INIT)
728 # Start VR, transition to master
729 vr.start_stop(is_start=1)
730 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
732 self.pg0.enable_capture()
733 rx = self.pg0.wait_for_packet(timeout=intvl_s,
734 filter_out_fn=is_not_adv)
736 self.assertTrue(rx.haslayer(Ether))
737 self.assertTrue(rx.haslayer(IP))
738 self.assertTrue(rx.haslayer(VRRPv3))
739 self.assertEqual(rx[Ether].src, self.pg0.local_mac)
740 self.assertEqual(rx[Ether].dst, unicast_peer.mac)
741 self.assertEqual(rx[IP].src, self.pg0.local_ip4)
742 self.assertEqual(rx[IP].dst, unicast_peer.ip4)
743 self.assertEqual(rx[VRRPv3].vrid, vr_id)
744 self.assertEqual(rx[VRRPv3].priority, prio)
745 self.assertEqual(rx[VRRPv3].ipcount, 1)
746 self.assertEqual(rx[VRRPv3].addrlist, [vip])
749 class TestVRRP6(VppTestCase):
750 """ IPv6 VRRP Test Case """
754 super(TestVRRP6, cls).setUpClass()
757 def tearDownClass(cls):
758 super(TestVRRP6, cls).tearDownClass()
761 super(TestVRRP6, self).setUp()
763 self.create_pg_interfaces(range(2))
765 for i in self.pg_interfaces:
768 i.generate_remote_hosts(5)
769 i.configure_ipv6_neighbors()
772 self._default_flags = (VRRP_VR_FLAG_IPV6 | VRRP_VR_FLAG_PREEMPT)
773 self._default_adv = 100
778 vr_api = vr.query_vpp_config()
779 if vr_api.runtime.state != VRRP_VR_STATE_INIT:
780 vr.start_stop(is_start=0)
781 vr.remove_vpp_config()
783 self.logger.error("Error cleaning up")
785 for i in self.pg_interfaces:
792 super(TestVRRP6, self).tearDown()
794 def verify_vrrp6_mlr(self, pkt, vr):
796 self.assertEqual(ip6.dst, "ff02::16")
797 self.assertEqual(ipv6nh[ip6.nh], "Hop-by-Hop Option Header")
799 hbh = pkt[IPv6ExtHdrHopByHop]
800 self.assertEqual(ipv6nh[hbh.nh], "ICMPv6")
802 self.assertTrue(pkt.haslayer(ICMPv6MLReport2))
803 mlr = pkt[ICMPv6MLReport2]
804 # should contain mc addr records for:
805 # - VRRPv3 multicast addr
806 # - solicited node mc addr record for each VR virtual IPv6 address
807 vips = vr.virtual_ips()
808 self.assertEqual(mlr.records_number, len(vips) + 1)
809 self.assertEqual(mlr.records[0].dst, vr.adv_dest_ip())
811 def verify_vrrp6_adv(self, rx_pkt, vr, prio=None):
812 self.assertTrue(rx_pkt.haslayer(Ether))
813 self.assertTrue(rx_pkt.haslayer(IPv6))
814 self.assertTrue(rx_pkt.haslayer(VRRPv3))
816 # generate a packet for this VR and compare it to the one received
817 pkt = vr.vrrp_adv_packet(prio=prio)
818 self.assertTrue(rx_pkt.haslayer(Ether))
819 self.assertTrue(rx_pkt.haslayer(IPv6))
820 self.assertTrue(rx_pkt.haslayer(VRRPv3))
822 self.assertEqual(pkt, rx_pkt)
824 def verify_vrrp6_gna(self, pkt, vr):
825 self.assertTrue(pkt.haslayer(Ether))
826 self.assertTrue(pkt.haslayer(IPv6))
827 self.assertTrue(pkt.haslayer(ICMPv6ND_NA))
828 self.assertTrue(pkt.haslayer(ICMPv6NDOptDstLLAddr))
830 self.assertEqual(pkt[Ether].dst, "33:33:00:00:00:01")
832 self.assertEqual(pkt[IPv6].dst, "ff02::1")
833 # convert addrs to packed format since string versions could differ
834 src_addr = inet_pton(AF_INET6, pkt[IPv6].src)
835 vr_ll_addr = inet_pton(AF_INET6, vr.interface().local_ip6_ll)
836 self.assertEqual(src_addr, vr_ll_addr)
838 self.assertTrue(pkt[ICMPv6ND_NA].tgt in vr.virtual_ips())
839 self.assertEqual(pkt[ICMPv6NDOptDstLLAddr].lladdr, vr.virtual_mac())
841 # VR with priority 255 owns the virtual address and should
842 # become master and start advertising immediately.
843 def test_vrrp6_master_adv(self):
844 """ IPv6 Master VR advertises """
845 self.pg_enable_capture(self.pg_interfaces)
849 intvl = self._default_adv
850 vr = VppVRRPVirtualRouter(self, self.pg0, 100,
851 prio=prio, intvl=intvl,
852 flags=self._default_flags)
856 self.logger.info(self.vapi.cli("show vrrp vr"))
857 vr.start_stop(is_start=1)
858 self.logger.info(self.vapi.cli("show vrrp vr"))
859 vr.start_stop(is_start=0)
860 self.logger.info(self.vapi.cli("show vrrp vr"))
862 pkts = self.pg0.get_capture(4, filter_out_fn=None)
864 # Init -> Master: Multicast group Join, VRRP adv, gratuitous NAs sent
865 self.verify_vrrp6_mlr(pkts[0], vr)
866 self.verify_vrrp6_adv(pkts[1], vr, prio=prio)
867 self.verify_vrrp6_gna(pkts[2], vr)
868 # Master -> Init: Adv with priority 0 sent to force an election
869 self.verify_vrrp6_adv(pkts[3], vr, prio=0)
871 vr.remove_vpp_config()
874 # VR with priority < 255 enters backup state and does not advertise as
875 # long as it receives higher priority advertisements
876 def test_vrrp6_backup_noadv(self):
877 """ IPv6 Backup VR does not advertise """
878 self.pg_enable_capture(self.pg_interfaces)
883 intvl = self._default_adv
884 intvl_s = intvl * 0.01
885 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
886 prio=prio, intvl=intvl,
887 flags=self._default_flags,
888 vips=[self.pg0.remote_ip6])
892 vr.start_stop(is_start=1)
894 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
895 # watch for advertisements for 2x the master down preemption timeout
896 end_time = vr.start_time() + 2 * vr.master_down_seconds()
898 # Init -> Backup: A multicast listener report should be sent
899 pkts = self.pg0.get_capture(1, filter_out_fn=None)
901 # send higher prio advertisements, should not see VPP send any
902 src_ip = self.pg0.remote_ip6_ll
904 pkts = [vr.vrrp_adv_packet(prio=prio+10, src_ip=src_ip)]
905 self.logger.info(self.vapi.cli("show vlib graph"))
906 while time.time() < end_time:
907 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
908 self.logger.info(self.vapi.cli("show trace"))
911 vr.start_stop(is_start=0)
912 self.logger.info(self.vapi.cli("show vrrp vr"))
913 vr.remove_vpp_config()
916 def test_vrrp6_master_nd(self):
917 """ IPv6 Master VR replies to NDP """
920 # VR virtual IP is the default, which is the pg local IP
923 intvl = self._default_adv
924 vr = VppVRRPVirtualRouter(self, self.pg0, 100,
925 prio=prio, intvl=intvl,
926 flags=self._default_flags)
930 # before the VR is up, NDP should resolve to interface MAC
931 self.pg0.resolve_ndp()
932 self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
934 # start the VR, NDP should now resolve to virtual MAC
935 vr.start_stop(is_start=1)
936 self.pg0.resolve_ndp()
937 self.assertEqual(self.pg0.local_mac, vr.virtual_mac())
939 # stop the VR, ARP should resolve to interface MAC again
940 vr.start_stop(is_start=0)
941 self.pg0.resolve_ndp()
942 self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
944 vr.remove_vpp_config()
947 def test_vrrp6_backup_nond(self):
948 """ IPv6 Backup VR ignores NDP """
949 # We need an address for a virtual IP that is not the IP that
950 # ARP requests will originate from
954 intvl = self._default_adv
955 intvl_s = intvl * 0.01
956 vip = self.pg0.remote_hosts[1].ip6
957 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
958 prio=prio, intvl=intvl,
959 flags=self._default_flags,
964 nsma = in6_getnsma(inet_pton(socket.AF_INET6, vip))
965 dmac = in6_getnsmac(nsma)
966 dst_ip = inet_ntop(socket.AF_INET6, nsma)
968 ndp_req = (Ether(dst=dmac, src=self.pg0.remote_mac) /
969 IPv6(dst=dst_ip, src=self.pg0.remote_ip6) /
970 ICMPv6ND_NS(tgt=vip) /
971 ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
973 # Before the VR is started make sure no reply to request for VIP
974 self.send_and_assert_no_replies(self.pg0, [ndp_req], timeout=1)
976 # VR should start in backup state and still should not reply to NDP
977 # send a higher priority adv to make sure it does not become master
978 adv = vr.vrrp_adv_packet(prio=prio+10, src_ip=self.pg0.remote_ip6)
979 pkts = [adv, ndp_req]
980 vr.start_stop(is_start=1)
981 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
983 vr.start_stop(is_start=0)
985 def test_vrrp6_election(self):
986 """ IPv6 Backup VR becomes master if no advertisements received """
990 intvl = self._default_adv
991 intvl_s = intvl * 0.01
992 vip = self.pg0.remote_ip6
993 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
994 prio=prio, intvl=intvl,
995 flags=self._default_flags,
1000 # After adding the VR, it should be in the init state
1001 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1004 vr.start_stop(is_start=1)
1006 # VR should be in backup state after starting
1007 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1008 end_time = vr.start_time() + vr.master_down_seconds()
1010 # no advertisements should arrive until timer expires
1011 self.pg0.enable_capture()
1012 while (time.time() + intvl_s) < end_time:
1014 self.pg0.assert_nothing_captured(filter_out_fn=is_not_adv)
1016 # VR should be in master state after timer expires
1017 self.pg0.enable_capture()
1018 self.pg0.wait_for_packet(intvl_s, is_not_adv)
1019 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1021 def test_vrrp6_backup_preempts(self):
1022 """ IPv6 Backup VR preempts lower priority master """
1026 intvl = self._default_adv
1027 intvl_s = intvl * 0.01
1028 vip = self.pg0.remote_ip6
1029 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1030 prio=prio, intvl=intvl,
1031 flags=self._default_flags,
1033 self._vrs.append(vr)
1036 # After adding the VR, it should be in the init state
1037 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1040 vr.start_stop(is_start=1)
1042 # VR should be in backup state after starting
1043 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1044 end_time = vr.start_time() + vr.master_down_seconds()
1046 # send lower prio advertisements until timer expires
1047 src_ip = self.pg0.remote_ip6
1048 pkts = [vr.vrrp_adv_packet(prio=prio-10, src_ip=src_ip)]
1049 while (time.time() + intvl_s) < end_time:
1050 self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
1051 self.logger.info(self.vapi.cli("show trace"))
1053 # when timer expires, VR should take over as master
1054 self.pg0.enable_capture()
1055 self.pg0.wait_for_packet(timeout=intvl_s, filter_out_fn=is_not_adv)
1056 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1058 def test_vrrp6_master_preempted(self):
1059 """ IPv6 Master VR preempted by higher priority backup """
1061 # A prio 255 VR cannot be preempted so the prio has to be lower and
1062 # we have to wait for it to take over
1065 intvl = self._default_adv
1066 vip = self.pg0.remote_ip6
1067 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1068 prio=prio, intvl=intvl,
1069 flags=self._default_flags,
1071 self._vrs.append(vr)
1074 # After adding the VR, it should be in the init state
1075 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1078 vr.start_stop(is_start=1)
1079 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1081 # wait for VR to take over as master
1082 end_time = vr.start_time() + vr.master_down_seconds()
1083 sleep_s = end_time - time.time()
1085 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1087 # Build advertisement packet and send it
1088 pkts = [vr.vrrp_adv_packet(prio=255, src_ip=self.pg0.remote_ip6)]
1089 self.pg_send(self.pg0, pkts)
1091 # VR should be in backup state again
1092 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1094 def test_vrrp6_accept_mode_disabled(self):
1095 """ IPv6 Master VR does not reply for VIP w/ accept mode off """
1097 # accept mode only matters when prio < 255, so it will have to
1098 # come up as a backup and take over as master after the timeout
1101 intvl = self._default_adv
1102 vip = self.pg0.remote_hosts[4].ip6
1103 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1104 prio=prio, intvl=intvl,
1105 flags=self._default_flags,
1107 self._vrs.append(vr)
1110 # After adding the VR, it should be in the init state
1111 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1114 vr.start_stop(is_start=1)
1115 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1117 # wait for VR to take over as master
1118 end_time = vr.start_time() + vr.master_down_seconds()
1119 sleep_s = end_time - time.time()
1121 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1123 # send an ICMPv6 echo to the VR virtual IP address
1124 echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
1125 IPv6(dst=vip, src=self.pg0.remote_ip6) /
1126 ICMPv6EchoRequest(seq=1, id=self.pg0.sw_if_index))
1127 self.pg_send(self.pg0, [echo])
1129 # wait for an echo reply. none should be received
1131 self.pg0.assert_nothing_captured(filter_out_fn=is_not_echo_reply)
1133 def test_vrrp6_accept_mode_enabled(self):
1134 """ IPv6 Master VR replies for VIP w/ accept mode on """
1136 # A prio 255 VR cannot be preempted so the prio has to be lower and
1137 # we have to wait for it to take over
1140 intvl = self._default_adv
1141 vip = self.pg0.remote_hosts[4].ip6
1142 flags = (self._default_flags | VRRP_VR_FLAG_ACCEPT)
1143 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1144 prio=prio, intvl=intvl,
1147 self._vrs.append(vr)
1150 # After adding the VR, it should be in the init state
1151 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1154 vr.start_stop(is_start=1)
1155 vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
1157 # wait for VR to take over as master
1158 end_time = vr.start_time() + vr.master_down_seconds()
1159 sleep_s = end_time - time.time()
1161 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1163 # send an ICMP echo to the VR virtual IP address
1164 echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
1165 IPv6(dst=vip, src=self.pg0.remote_ip6) /
1166 ICMPv6EchoRequest(seq=1, id=self.pg0.sw_if_index))
1167 self.pg_send(self.pg0, [echo])
1169 # wait for an echo reply.
1171 rx_pkts = self.pg0.get_capture(expected_count=1, timeout=1,
1172 filter_out_fn=is_not_echo_reply)
1174 self.assertEqual(rx_pkts[0][IPv6].src, vip)
1175 self.assertEqual(rx_pkts[0][IPv6].dst, self.pg0.remote_ip6)
1176 self.assertEqual(rx_pkts[0][ICMPv6EchoReply].seq, 1)
1177 self.assertEqual(rx_pkts[0][ICMPv6EchoReply].id, self.pg0.sw_if_index)
1179 def test_vrrp6_intf_tracking(self):
1180 """ IPv6 Master VR adjusts priority based on tracked interface """
1184 intvl = self._default_adv
1185 intvl_s = intvl * 0.01
1186 vip = self.pg0.local_ip6
1187 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1188 prio=prio, intvl=intvl,
1189 flags=self._default_flags,
1191 self._vrs.append(vr)
1194 # After adding the VR, it should be in the init state
1195 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1197 # add pg1 as a tracked interface and start the VR
1199 adjusted_prio = prio - adjustment
1200 vr.add_del_tracked_interface(is_add=1,
1201 sw_if_index=self.pg1.sw_if_index,
1203 vr.start_stop(is_start=1)
1204 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1206 adv_configured = vr.vrrp_adv_packet(prio=prio)
1207 adv_adjusted = vr.vrrp_adv_packet(prio=adjusted_prio)
1209 # tracked intf is up -> advertised priority == configured priority
1210 self.pg0.enable_capture()
1211 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1212 filter_out_fn=is_not_adv)
1213 self.assertEqual(rx, adv_configured)
1215 # take down pg1, verify priority is now being adjusted
1216 self.pg1.admin_down()
1217 self.pg0.enable_capture()
1218 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1219 filter_out_fn=is_not_adv)
1220 self.assertEqual(rx, adv_adjusted)
1222 # bring up pg1, verify priority now matches configured value
1224 self.pg0.enable_capture()
1225 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1226 filter_out_fn=is_not_adv)
1227 self.assertEqual(rx, adv_configured)
1229 # remove IP address from pg1, verify priority now being adjusted
1230 self.pg1.unconfig_ip6()
1231 self.pg0.enable_capture()
1232 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1233 filter_out_fn=is_not_adv)
1234 self.assertEqual(rx, adv_adjusted)
1236 # add IP address to pg1, verify priority now matches configured value
1237 self.pg1.config_ip6()
1238 self.pg0.enable_capture()
1239 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1240 filter_out_fn=is_not_adv)
1241 self.assertEqual(rx, adv_configured)
1243 def test_vrrp6_master_adv_unicast(self):
1244 """ IPv6 Master VR advertises (unicast) """
1248 intvl = self._default_adv
1249 intvl_s = intvl * 0.01
1250 vip = self.pg0.local_ip6
1251 flags = (self._default_flags | VRRP_VR_FLAG_UNICAST)
1252 unicast_peer = self.pg0.remote_hosts[4]
1253 vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
1254 prio=prio, intvl=intvl,
1257 self._vrs.append(vr)
1259 vr.set_unicast_peers([unicast_peer.ip6])
1261 # After adding the VR, it should be in the init state
1262 vr.assert_state_equals(VRRP_VR_STATE_INIT)
1264 # Start VR, transition to master
1265 vr.start_stop(is_start=1)
1266 vr.assert_state_equals(VRRP_VR_STATE_MASTER)
1268 self.pg0.enable_capture()
1269 rx = self.pg0.wait_for_packet(timeout=intvl_s,
1270 filter_out_fn=is_not_adv)
1272 self.assertTrue(rx.haslayer(Ether))
1273 self.assertTrue(rx.haslayer(IPv6))
1274 self.assertTrue(rx.haslayer(VRRPv3))
1275 self.assertEqual(rx[Ether].src, self.pg0.local_mac)
1276 self.assertEqual(rx[Ether].dst, unicast_peer.mac)
1277 self.assertEqual(ip6_normalize(rx[IPv6].src),
1278 ip6_normalize(self.pg0.local_ip6_ll))
1279 self.assertEqual(ip6_normalize(rx[IPv6].dst),
1280 ip6_normalize(unicast_peer.ip6))
1281 self.assertEqual(rx[VRRPv3].vrid, vr_id)
1282 self.assertEqual(rx[VRRPv3].priority, prio)
1283 self.assertEqual(rx[VRRPv3].ipcount, 1)
1284 self.assertEqual(rx[VRRPv3].addrlist, [vip])
1287 if __name__ == '__main__':
1288 unittest.main(testRunner=VppTestRunner)