summaryrefslogtreecommitdiffstats
path: root/vrrpd
diff options
context:
space:
mode:
Diffstat (limited to 'vrrpd')
-rw-r--r--vrrpd/.gitignore2
-rw-r--r--vrrpd/Makefile10
-rw-r--r--vrrpd/subdir.am40
-rw-r--r--vrrpd/vrrp.c2421
-rw-r--r--vrrpd/vrrp.h565
-rw-r--r--vrrpd/vrrp_arp.c206
-rw-r--r--vrrpd/vrrp_arp.h23
-rw-r--r--vrrpd/vrrp_debug.c118
-rw-r--r--vrrpd/vrrp_debug.h74
-rw-r--r--vrrpd/vrrp_main.c159
-rw-r--r--vrrpd/vrrp_ndisc.c230
-rw-r--r--vrrpd/vrrp_ndisc.h61
-rw-r--r--vrrpd/vrrp_northbound.c814
-rw-r--r--vrrpd/vrrp_packet.c316
-rw-r--r--vrrpd/vrrp_packet.h190
-rw-r--r--vrrpd/vrrp_vty.c781
-rw-r--r--vrrpd/vrrp_vty.h34
-rw-r--r--vrrpd/vrrp_zebra.c198
-rw-r--r--vrrpd/vrrp_zebra.h24
19 files changed, 6266 insertions, 0 deletions
diff --git a/vrrpd/.gitignore b/vrrpd/.gitignore
new file mode 100644
index 0000000..e1751b2
--- /dev/null
+++ b/vrrpd/.gitignore
@@ -0,0 +1,2 @@
+libvrrp.a
+vrrpd
diff --git a/vrrpd/Makefile b/vrrpd/Makefile
new file mode 100644
index 0000000..0abb1a6
--- /dev/null
+++ b/vrrpd/Makefile
@@ -0,0 +1,10 @@
+all: ALWAYS
+ @$(MAKE) -s -C .. vrrpd/vrrpd
+%: ALWAYS
+ @$(MAKE) -s -C .. vrrpd/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
diff --git a/vrrpd/subdir.am b/vrrpd/subdir.am
new file mode 100644
index 0000000..03b4042
--- /dev/null
+++ b/vrrpd/subdir.am
@@ -0,0 +1,40 @@
+#
+# vrrpd
+#
+
+if VRRPD
+sbin_PROGRAMS += vrrpd/vrrpd
+vtysh_daemons += vrrpd
+man8 += $(MANBUILD)/frr-vrrpd.8
+endif
+
+vrrpd_vrrpd_SOURCES = \
+ vrrpd/vrrp.c \
+ vrrpd/vrrp_arp.c \
+ vrrpd/vrrp_debug.c \
+ vrrpd/vrrp_main.c \
+ vrrpd/vrrp_ndisc.c \
+ vrrpd/vrrp_northbound.c \
+ vrrpd/vrrp_packet.c \
+ vrrpd/vrrp_vty.c \
+ vrrpd/vrrp_zebra.c \
+ # end
+
+noinst_HEADERS += \
+ vrrpd/vrrp.h \
+ vrrpd/vrrp_arp.h \
+ vrrpd/vrrp_debug.h \
+ vrrpd/vrrp_ndisc.h \
+ vrrpd/vrrp_packet.h \
+ vrrpd/vrrp_vty.h \
+ vrrpd/vrrp_zebra.h \
+ # end
+
+clippy_scan += \
+ vrrpd/vrrp_vty.c \
+ # end
+
+vrrpd_vrrpd_LDADD = lib/libfrr.la @LIBCAP@
+nodist_vrrpd_vrrpd_SOURCES = \
+ yang/frr-vrrpd.yang.c \
+ # end
diff --git a/vrrpd/vrrp.c b/vrrpd/vrrp.c
new file mode 100644
index 0000000..b14a6ec
--- /dev/null
+++ b/vrrpd/vrrp.c
@@ -0,0 +1,2421 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP global definitions and state machine.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#include <zebra.h>
+
+#include "lib/hash.h"
+#include "lib/hook.h"
+#include "lib/if.h"
+#include "lib/linklist.h"
+#include "lib/memory.h"
+#include "lib/network.h"
+#include "lib/prefix.h"
+#include "lib/sockopt.h"
+#include "lib/sockunion.h"
+#include "lib/vrf.h"
+#include "lib/vty.h"
+
+#include "vrrp.h"
+#include "vrrp_arp.h"
+#include "vrrp_debug.h"
+#include "vrrp_ndisc.h"
+#include "vrrp_packet.h"
+#include "vrrp_zebra.h"
+
+#define VRRP_LOGPFX "[CORE] "
+
+DEFINE_MTYPE_STATIC(VRRPD, VRRP_IP, "VRRP IP address");
+DEFINE_MTYPE_STATIC(VRRPD, VRRP_RTR, "VRRP Router");
+
+/* statics */
+struct hash *vrrp_vrouters_hash;
+bool vrrp_autoconfig_is_on;
+int vrrp_autoconfig_version;
+
+struct vrrp_defaults vd;
+
+const char *const vrrp_state_names[3] = {
+ [VRRP_STATE_INITIALIZE] = "Initialize",
+ [VRRP_STATE_MASTER] = "Master",
+ [VRRP_STATE_BACKUP] = "Backup",
+};
+
+static const char *const vrrp_event_names[2] = {
+ [VRRP_EVENT_STARTUP] = "Startup",
+ [VRRP_EVENT_SHUTDOWN] = "Shutdown",
+};
+
+
+/* Utility functions ------------------------------------------------------- */
+
+/*
+ * Sets an ethaddr to RFC-defined Virtual Router MAC address.
+ *
+ * mac
+ * ethaddr to set
+ *
+ * v6
+ * Whether this is a V6 or V4 Virtual Router MAC
+ *
+ * vrid
+ * Virtual Router Identifier
+ */
+static void vrrp_mac_set(struct ethaddr *mac, bool v6, uint8_t vrid)
+{
+ /*
+ * V4: 00-00-5E-00-01-{VRID}
+ * V6: 00-00-5E-00-02-{VRID}
+ */
+ mac->octet[0] = 0x00;
+ mac->octet[1] = 0x00;
+ mac->octet[2] = 0x5E;
+ mac->octet[3] = 0x00;
+ mac->octet[4] = v6 ? 0x02 : 0x01;
+ mac->octet[5] = vrid;
+}
+
+/*
+ * Recalculates and sets skew_time and master_down_interval based
+ * values.
+ *
+ * r
+ * VRRP Router to operate on
+ */
+static void vrrp_recalculate_timers(struct vrrp_router *r)
+{
+ uint16_t mdiadv = r->vr->version == 3 ? r->master_adver_interval
+ : r->vr->advertisement_interval;
+ uint16_t skm = (r->vr->version == 3) ? r->master_adver_interval : 100;
+
+ r->skew_time = ((256 - r->vr->priority) * skm) / 256;
+ r->master_down_interval = 3 * mdiadv;
+ r->master_down_interval += r->skew_time;
+}
+
+/*
+ * Determines if a VRRP router is the owner of the specified address.
+ *
+ * The determining factor for whether an interface is the address owner is
+ * simply whether the address is assigned to the VRRP base interface by someone
+ * other than vrrpd.
+ *
+ * This function should always return the correct answer regardless of
+ * master/backup status.
+ *
+ * ifp
+ * The interface to check owernship of. This should be the base interface of
+ * a VRRP router.
+ *
+ * vr
+ * Virtual Router
+ *
+ * Returns:
+ * whether or not vr owns the specified address
+ */
+static bool vrrp_is_owner(struct interface *ifp, struct ipaddr *addr)
+{
+ /*
+ * This code sanity checks implicit ownership configuration. Ideally,
+ * the way we determine address ownership status for this VRRP router
+ * is by looking at whether our VIPs are also assigned to the base
+ * interface, and therefore count as "real" addresses. This frees the
+ * user from having to manually configure priority 255 to indicate
+ * address ownership. However, this means one of the VIPs will be used
+ * as the source address for VRRP advertisements, which in turn means
+ * that other VRRP routers will be receiving packets with a source
+ * address they themselves have. This causes lots of different issues
+ * so for now we're disabling this and forcing the user to configure
+ * priority 255 to indicate ownership.
+ */
+
+ return false;
+
+#if 0
+ struct prefix p;
+
+ p.family = IS_IPADDR_V4(addr) ? AF_INET : AF_INET6;
+ p.prefixlen = IS_IPADDR_V4(addr) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN;
+ memcpy(&p.u, &addr->ip, sizeof(addr->ip));
+
+ return !!connected_lookup_prefix_exact(ifp, &p);
+#endif
+}
+
+/*
+ * Whether an interface has a MAC address that matches the VRRP RFC.
+ *
+ * ifp
+ * Interface to check
+ *
+ * Returns:
+ * Whether the interface has a VRRP mac or not
+ */
+static bool vrrp_ifp_has_vrrp_mac(struct interface *ifp)
+{
+ struct ethaddr vmac4;
+ struct ethaddr vmac6;
+
+ vrrp_mac_set(&vmac4, 0, 0x00);
+ vrrp_mac_set(&vmac6, 1, 0x00);
+
+ return !memcmp(ifp->hw_addr, vmac4.octet, sizeof(vmac4.octet) - 1)
+ || !memcmp(ifp->hw_addr, vmac6.octet, sizeof(vmac6.octet) - 1);
+}
+
+/*
+ * Lookup a Virtual Router instance given a macvlan subinterface.
+ *
+ * The VRID is extracted from the interface MAC and the 2-tuple (iface, vrid)
+ * is used to look up any existing instances that match the interface. It does
+ * not matter whether the instance is already bound to the interface or not.
+ *
+ * Note that the interface linkages must be correct for this to work. In other
+ * words, the macvlan must have a valid VRRP MAC, and its link_ifindex must be
+ * be equal to the ifindex of another interface in the interface RB trees (its
+ * parent). If these conditions aren't satisfied we won't find the VR.
+ *
+ * mvl_ifp
+ * Interface pointer to use to lookup. Should be a macvlan device.
+ *
+ * Returns:
+ * Virtual Router, if found
+ * NULL otherwise
+ */
+static struct vrrp_vrouter *vrrp_lookup_by_if_mvl(struct interface *mvl_ifp)
+{
+ struct interface *p;
+
+ if (!mvl_ifp || mvl_ifp->link_ifindex == 0
+ || !vrrp_ifp_has_vrrp_mac(mvl_ifp)) {
+ if (mvl_ifp && mvl_ifp->link_ifindex == 0)
+ DEBUGD(&vrrp_dbg_zebra,
+ VRRP_LOGPFX
+ "Interface %s has no parent ifindex; disregarding",
+ mvl_ifp->name);
+ if (mvl_ifp && !vrrp_ifp_has_vrrp_mac(mvl_ifp))
+ DEBUGD(&vrrp_dbg_zebra,
+ VRRP_LOGPFX
+ "Interface %s has a non-VRRP MAC; disregarding",
+ mvl_ifp->name);
+ return NULL;
+ }
+
+ p = if_lookup_by_index(mvl_ifp->link_ifindex, mvl_ifp->vrf->vrf_id);
+
+ if (!p) {
+ DEBUGD(&vrrp_dbg_zebra,
+ VRRP_LOGPFX
+ "Tried to lookup interface %d, parent of %s, but it doesn't exist",
+ mvl_ifp->link_ifindex, mvl_ifp->name);
+ return NULL;
+ }
+
+ uint8_t vrid = mvl_ifp->hw_addr[5];
+
+ return vrrp_lookup(p, vrid);
+}
+
+/*
+ * Lookup the Virtual Router instances configured on a particular interface.
+ *
+ * ifp
+ * Interface pointer to use to lookup. Should not be a macvlan device.
+ *
+ * Returns:
+ * List of virtual routers found
+ */
+static struct list *vrrp_lookup_by_if(struct interface *ifp)
+{
+ struct list *l = hash_to_list(vrrp_vrouters_hash);
+ struct listnode *ln, *nn;
+ struct vrrp_vrouter *vr;
+
+ for (ALL_LIST_ELEMENTS(l, ln, nn, vr))
+ if (vr->ifp != ifp)
+ list_delete_node(l, ln);
+
+ return l;
+}
+
+/*
+ * Lookup any Virtual Router instances associated with a particular interface.
+ * This is a combination of the results from vrrp_lookup_by_if_mvl and
+ * vrrp_lookup_by_if.
+ *
+ * Suppose the system interface list looks like the following:
+ *
+ * eth0
+ * \- eth0-v0 00:00:5e:00:01:01
+ * \- eth0-v1 00:00:5e:00:02:01
+ * \- eth0-v2 00:00:5e:00:01:0a
+ *
+ * Passing eth0-v2 to this function will give you the VRRP instance configured
+ * on eth0 with VRID 10. Passing eth0-v0 or eth0-v1 will give you the VRRP
+ * instance configured on eth0 with VRID 1. Passing eth0 will give you both.
+ *
+ * ifp
+ * Interface pointer to use to lookup. Can be any interface.
+ *
+ * Returns:
+ * List of virtual routers found
+ */
+static struct list *vrrp_lookup_by_if_any(struct interface *ifp)
+{
+ struct vrrp_vrouter *vr;
+ struct list *vrs;
+
+ vr = vrrp_lookup_by_if_mvl(ifp);
+ vrs = vr ? list_new() : vrrp_lookup_by_if(ifp);
+
+ if (vr)
+ listnode_add(vrs, vr);
+
+ return vrs;
+}
+
+/* Configuration controllers ----------------------------------------------- */
+
+void vrrp_check_start(struct vrrp_vrouter *vr)
+{
+ struct vrrp_router *r;
+ bool start;
+ const char *whynot = NULL;
+
+ if (vr->shutdown || vr->ifp == NULL)
+ return;
+
+ r = vr->v4;
+ /* Must not already be started */
+ start = r->fsm.state == VRRP_STATE_INITIALIZE;
+ whynot = (!start && !whynot) ? "Already running" : whynot;
+ /* Must have a parent interface */
+ start = start && (vr->ifp != NULL);
+ whynot = (!start && !whynot) ? "No base interface" : whynot;
+#if 0
+ /* Parent interface must be up */
+ start = start && if_is_operative(vr->ifp);
+ start = (!start && !whynot) ? "Base interface inoperative" : whynot;
+#endif
+ /* Parent interface must have at least one v4 */
+ start = start && connected_count_by_family(vr->ifp, AF_INET) > 0;
+ whynot = (!start && !whynot) ? "No primary IPv4 address" : whynot;
+ /* Must have a macvlan interface */
+ start = start && (r->mvl_ifp != NULL);
+ whynot = (!start && !whynot) ? "No VRRP interface" : whynot;
+#if 0
+ /* Macvlan interface must be admin up */
+ start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP);
+ start = (!start && !whynot) ? "Macvlan device admin down" : whynot;
+#endif
+ /* Must have at least one VIP configured */
+ start = start && r->addrs->count > 0;
+ whynot = (!start && !whynot) ? "No Virtual IP address configured"
+ : whynot;
+ if (start)
+ vrrp_event(r, VRRP_EVENT_STARTUP);
+ else if (whynot)
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Refusing to start Virtual Router: %s",
+ vr->vrid, family2str(r->family), whynot);
+
+ whynot = NULL;
+
+ r = vr->v6;
+ /* Must not already be started */
+ start = r->fsm.state == VRRP_STATE_INITIALIZE;
+ whynot = (!start && !whynot) ? "Already running" : whynot;
+ /* Must not be v2 */
+ start = start && vr->version != 2;
+ whynot = (!start && !whynot) ? "VRRPv2 does not support v6" : whynot;
+ /* Must have a parent interface */
+ start = start && (vr->ifp != NULL);
+ whynot = (!start && !whynot) ? "No base interface" : whynot;
+#if 0
+ /* Parent interface must be up */
+ start = start && if_is_operative(vr->ifp);
+ start = (!start && !whynot) ? "Base interface inoperative" : whynot;
+#endif
+ /* Must have a macvlan interface */
+ start = start && (r->mvl_ifp != NULL);
+ whynot = (!start && !whynot) ? "No VRRP interface" : whynot;
+#if 0
+ /* Macvlan interface must be admin up */
+ start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP);
+ start = (!start && !whynot) ? "Macvlan device admin down" : whynot;
+ /* Macvlan interface must have a link local */
+ start = start && connected_get_linklocal(r->mvl_ifp);
+ whynot =
+ (!start && !whynot) ? "No link local address configured" : whynot;
+ /* Macvlan interface must have a v6 IP besides the link local */
+ start = start && (connected_count_by_family(r->mvl_ifp, AF_INET6) > 1);
+ whynot = (!start && !whynot)
+ ? "No Virtual IPv6 address configured on macvlan device"
+ : whynot;
+#endif
+ /* Must have at least one VIP configured */
+ start = start && r->addrs->count > 0;
+ whynot =
+ (!start && !whynot) ? "No Virtual IP address configured" : whynot;
+ if (start)
+ vrrp_event(r, VRRP_EVENT_STARTUP);
+ else if (whynot)
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Refusing to start Virtual Router: %s",
+ vr->vrid, family2str(r->family), whynot);
+}
+
+void vrrp_set_priority(struct vrrp_vrouter *vr, uint8_t priority)
+{
+ vr->priority = priority;
+ vr->v4->priority = priority;
+ vr->v6->priority = priority;
+}
+
+void vrrp_set_advertisement_interval(struct vrrp_vrouter *vr,
+ uint16_t advertisement_interval)
+{
+ if (vr->advertisement_interval == advertisement_interval)
+ return;
+
+ vr->advertisement_interval = advertisement_interval;
+ vrrp_recalculate_timers(vr->v4);
+ vrrp_recalculate_timers(vr->v6);
+}
+
+static bool vrrp_has_ip(struct vrrp_vrouter *vr, struct ipaddr *ip)
+{
+ struct vrrp_router *r = ip->ipa_type == IPADDR_V4 ? vr->v4 : vr->v6;
+ struct listnode *ln;
+ struct ipaddr *iter;
+
+ for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, iter))
+ if (!ipaddr_cmp(iter, ip))
+ return true;
+
+ return false;
+}
+
+int vrrp_add_ip(struct vrrp_vrouter *vr, struct ipaddr *ip)
+{
+ struct vrrp_router *r = IS_IPADDR_V4(ip) ? vr->v4 : vr->v6;
+ int af = r->family;
+
+ assert(r->family == af);
+ assert(!(r->vr->version == 2 && ip->ipa_type == IPADDR_V6));
+
+ if (vrrp_has_ip(r->vr, ip))
+ return 0;
+
+ if (!vrrp_is_owner(r->vr->ifp, ip) && r->is_owner) {
+ char ipbuf[INET6_ADDRSTRLEN];
+
+ inet_ntop(r->family, &ip->ip, ipbuf, sizeof(ipbuf));
+ zlog_err(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "This VRRP router is not the address owner of %s, but is the address owner of other addresses; this config is unsupported.",
+ r->vr->vrid, family2str(r->family), ipbuf);
+ return -1;
+ }
+
+ struct ipaddr *new = XCALLOC(MTYPE_VRRP_IP, sizeof(struct ipaddr));
+
+ *new = *ip;
+ listnode_add(r->addrs, new);
+
+ if (r->fsm.state == VRRP_STATE_MASTER) {
+ switch (r->family) {
+ case AF_INET:
+ vrrp_garp_send(r, &new->ipaddr_v4);
+ break;
+ case AF_INET6:
+ vrrp_ndisc_una_send(r, new);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int vrrp_add_ipv4(struct vrrp_vrouter *vr, struct in_addr v4)
+{
+ struct ipaddr ip;
+
+ ip.ipa_type = IPADDR_V4;
+ ip.ipaddr_v4 = v4;
+ return vrrp_add_ip(vr, &ip);
+}
+
+int vrrp_add_ipv6(struct vrrp_vrouter *vr, struct in6_addr v6)
+{
+ assert(vr->version != 2);
+
+ struct ipaddr ip;
+
+ ip.ipa_type = IPADDR_V6;
+ ip.ipaddr_v6 = v6;
+ return vrrp_add_ip(vr, &ip);
+}
+
+int vrrp_del_ip(struct vrrp_vrouter *vr, struct ipaddr *ip)
+{
+ struct listnode *ln, *nn;
+ struct ipaddr *iter;
+ int ret = 0;
+
+ struct vrrp_router *r = IS_IPADDR_V4(ip) ? vr->v4 : vr->v6;
+
+ if (!vrrp_has_ip(r->vr, ip))
+ return 0;
+
+ for (ALL_LIST_ELEMENTS(r->addrs, ln, nn, iter))
+ if (!ipaddr_cmp(iter, ip))
+ list_delete_node(r->addrs, ln);
+
+ /*
+ * NB: Deleting the last address and then issuing a shutdown will cause
+ * transmission of a priority 0 VRRP Advertisement - as per the RFC -
+ * but it will have no addresses. This is not forbidden in the RFC but
+ * might confuse other implementations.
+ */
+ if (r->addrs->count == 0 && r->fsm.state != VRRP_STATE_INITIALIZE)
+ ret = vrrp_event(r, VRRP_EVENT_SHUTDOWN);
+
+ return ret;
+}
+
+int vrrp_del_ipv6(struct vrrp_vrouter *vr, struct in6_addr v6)
+{
+ struct ipaddr ip;
+
+ ip.ipa_type = IPADDR_V6;
+ ip.ipaddr_v6 = v6;
+ return vrrp_del_ip(vr, &ip);
+}
+
+int vrrp_del_ipv4(struct vrrp_vrouter *vr, struct in_addr v4)
+{
+ struct ipaddr ip;
+
+ ip.ipa_type = IPADDR_V4;
+ ip.ipaddr_v4 = v4;
+ return vrrp_del_ip(vr, &ip);
+}
+
+
+/* Creation and destruction ------------------------------------------------ */
+
+static void vrrp_router_addr_list_del_cb(void *val)
+{
+ struct ipaddr *ip = val;
+
+ XFREE(MTYPE_VRRP_IP, ip);
+}
+
+/*
+ * Search for a suitable macvlan subinterface we can attach to, and if found,
+ * attach to it.
+ *
+ * r
+ * Router to attach to interface
+ *
+ * Returns:
+ * Whether an interface was successfully attached
+ */
+static bool vrrp_attach_interface(struct vrrp_router *r)
+{
+ /* Search for existing interface with computed MAC address */
+ struct interface **ifps;
+
+ size_t ifps_cnt =
+ if_lookup_by_hwaddr(r->vmac.octet, sizeof(r->vmac.octet), &ifps,
+ r->vr->ifp->vrf->vrf_id);
+
+ /*
+ * Filter to only those macvlan interfaces whose parent is the base
+ * interface this VRRP router is configured on.
+ *
+ * If there are still multiple interfaces we just select the first one,
+ * as it should be functionally identical to the others.
+ */
+ unsigned int candidates = 0;
+ struct interface *selection = NULL;
+
+ for (unsigned int i = 0; i < ifps_cnt; i++) {
+ if (ifps[i]->link_ifindex != r->vr->ifp->ifindex)
+ ifps[i] = NULL;
+ else {
+ selection = selection ? selection : ifps[i];
+ candidates++;
+ }
+ }
+
+ if (ifps_cnt)
+ XFREE(MTYPE_TMP, ifps);
+
+ char ethstr[ETHER_ADDR_STRLEN];
+
+ prefix_mac2str(&r->vmac, ethstr, sizeof(ethstr));
+
+ assert(!!selection == !!candidates);
+
+ if (candidates == 0)
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Interface: None (no interface found w/ MAC %s)",
+ r->vr->vrid, family2str(r->family), ethstr);
+ else if (candidates > 1)
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Interface: Multiple interfaces found; using %s",
+ r->vr->vrid, family2str(r->family), selection->name);
+ else
+ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Interface: %s",
+ r->vr->vrid, family2str(r->family), selection->name);
+
+ r->mvl_ifp = selection;
+
+ return !!r->mvl_ifp;
+}
+
+static struct vrrp_router *vrrp_router_create(struct vrrp_vrouter *vr,
+ int family)
+{
+ struct vrrp_router *r =
+ XCALLOC(MTYPE_VRRP_RTR, sizeof(struct vrrp_router));
+
+ r->family = family;
+ r->sock_rx = -1;
+ r->sock_tx = -1;
+ r->vr = vr;
+ r->addrs = list_new();
+ r->addrs->del = vrrp_router_addr_list_del_cb;
+ r->priority = vr->priority;
+ r->fsm.state = VRRP_STATE_INITIALIZE;
+ vrrp_mac_set(&r->vmac, family == AF_INET6, vr->vrid);
+
+ vrrp_attach_interface(r);
+
+ return r;
+}
+
+static void vrrp_router_destroy(struct vrrp_router *r)
+{
+ if (r->is_active)
+ vrrp_event(r, VRRP_EVENT_SHUTDOWN);
+
+ if (r->sock_rx >= 0)
+ close(r->sock_rx);
+ if (r->sock_tx >= 0)
+ close(r->sock_tx);
+
+ /* FIXME: also delete list elements */
+ list_delete(&r->addrs);
+ XFREE(MTYPE_VRRP_RTR, r);
+}
+
+struct vrrp_vrouter *vrrp_vrouter_create(struct interface *ifp, uint8_t vrid,
+ uint8_t version)
+{
+ struct vrrp_vrouter *vr = vrrp_lookup(ifp, vrid);
+
+ if (vr)
+ return vr;
+
+ if (version != 2 && version != 3)
+ return NULL;
+
+ vr = XCALLOC(MTYPE_VRRP_RTR, sizeof(struct vrrp_vrouter));
+
+ vr->ifp = ifp;
+ vr->version = version;
+ vr->vrid = vrid;
+ vr->priority = vd.priority;
+ vr->preempt_mode = vd.preempt_mode;
+ vr->accept_mode = vd.accept_mode;
+ vr->checksum_with_ipv4_pseudoheader =
+ vd.checksum_with_ipv4_pseudoheader;
+ vr->shutdown = vd.shutdown;
+
+ vr->v4 = vrrp_router_create(vr, AF_INET);
+ vr->v6 = vrrp_router_create(vr, AF_INET6);
+
+ vrrp_set_advertisement_interval(vr, vd.advertisement_interval);
+
+ (void)hash_get(vrrp_vrouters_hash, vr, hash_alloc_intern);
+
+ return vr;
+}
+
+void vrrp_vrouter_destroy(struct vrrp_vrouter *vr)
+{
+ vrrp_router_destroy(vr->v4);
+ vrrp_router_destroy(vr->v6);
+ hash_release(vrrp_vrouters_hash, vr);
+ XFREE(MTYPE_VRRP_RTR, vr);
+}
+
+struct vrrp_vrouter *vrrp_lookup(const struct interface *ifp, uint8_t vrid)
+{
+ if (!ifp)
+ return NULL;
+
+ struct vrrp_vrouter vr;
+
+ vr.vrid = vrid;
+ vr.ifp = (struct interface *)ifp;
+
+ return hash_lookup(vrrp_vrouters_hash, &vr);
+}
+
+/* Network ----------------------------------------------------------------- */
+
+/* Forward decls */
+static void vrrp_change_state(struct vrrp_router *r, int to);
+static void vrrp_adver_timer_expire(struct event *thread);
+static void vrrp_master_down_timer_expire(struct event *thread);
+
+/*
+ * Finds the first connected address of the appropriate family on a VRRP
+ * router's interface and binds the Tx socket of the VRRP router to that
+ * address.
+ *
+ * Also sets src field of vrrp_router.
+ *
+ * r
+ * VRRP router to operate on
+ *
+ * Returns:
+ * 0 on success
+ * -1 on failure
+ */
+static int vrrp_bind_to_primary_connected(struct vrrp_router *r)
+{
+ struct interface *ifp;
+
+ /*
+ * A slight quirk: the RFC specifies that advertisements under IPv6 must
+ * be transmitted using the link local address of the source interface
+ */
+ ifp = r->family == AF_INET ? r->vr->ifp : r->mvl_ifp;
+
+ struct listnode *ln;
+ struct connected *c = NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, ln, c))
+ if (c->address->family == r->family) {
+ if (r->family == AF_INET6
+ && IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6))
+ break;
+ else if (r->family == AF_INET)
+ break;
+ }
+
+ if (c == NULL) {
+ zlog_err(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to find address to bind on %s",
+ r->vr->vrid, family2str(r->family), ifp->name);
+ return -1;
+ }
+
+ union sockunion su;
+
+ memset(&su, 0x00, sizeof(su));
+
+ switch (r->family) {
+ case AF_INET:
+ r->src.ipa_type = IPADDR_V4;
+ r->src.ipaddr_v4 = c->address->u.prefix4;
+ su.sin.sin_family = AF_INET;
+ su.sin.sin_addr = c->address->u.prefix4;
+ break;
+ case AF_INET6:
+ r->src.ipa_type = IPADDR_V6;
+ r->src.ipaddr_v6 = c->address->u.prefix6;
+ su.sin6.sin6_family = AF_INET6;
+ su.sin6.sin6_scope_id = ifp->ifindex;
+ su.sin6.sin6_addr = c->address->u.prefix6;
+ break;
+ }
+
+ int ret = 0;
+
+ sockopt_reuseaddr(r->sock_tx);
+ if (bind(r->sock_tx, (const struct sockaddr *)&su, sizeof(su)) < 0) {
+ zlog_err(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to bind Tx socket to primary IP address %pFX: %s",
+ r->vr->vrid, family2str(r->family), c->address,
+ safe_strerror(errno));
+ ret = -1;
+ } else {
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Bound Tx socket to primary IP address %pFX",
+ r->vr->vrid, family2str(r->family), c->address);
+ }
+
+ return ret;
+}
+
+
+/*
+ * Create and multicast a VRRP ADVERTISEMENT message.
+ *
+ * r
+ * VRRP Router for which to send ADVERTISEMENT
+ */
+static void vrrp_send_advertisement(struct vrrp_router *r)
+{
+ struct vrrp_pkt *pkt;
+ ssize_t pktsz;
+ struct ipaddr *addrs[r->addrs->count];
+ union sockunion dest;
+
+ if (r->src.ipa_type == IPADDR_NONE
+ && vrrp_bind_to_primary_connected(r) < 0)
+ return;
+
+ list_to_array(r->addrs, (void **)addrs, r->addrs->count);
+
+ pktsz = vrrp_pkt_adver_build(&pkt, &r->src, r->vr->version, r->vr->vrid,
+ r->priority, r->vr->advertisement_interval,
+ r->addrs->count, (struct ipaddr **)&addrs,
+ r->vr->checksum_with_ipv4_pseudoheader);
+
+ if (DEBUG_MODE_CHECK(&vrrp_dbg_pkt, DEBUG_MODE_ALL))
+ zlog_hexdump(pkt, (size_t)pktsz);
+
+ const char *group = r->family == AF_INET ? VRRP_MCASTV4_GROUP_STR
+ : VRRP_MCASTV6_GROUP_STR;
+ (void)str2sockunion(group, &dest);
+
+ ssize_t sent = sendto(r->sock_tx, pkt, (size_t)pktsz, 0, &dest.sa,
+ sockunion_sizeof(&dest));
+
+ vrrp_pkt_free(pkt);
+
+ if (sent < 0) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to send VRRP Advertisement: %s",
+ r->vr->vrid, family2str(r->family),
+ safe_strerror(errno));
+ } else {
+ ++r->stats.adver_tx_cnt;
+ }
+}
+
+/*
+ * Receive and parse VRRP advertisement.
+ *
+ * By the time we get here all fields have been validated for basic correctness
+ * and the packet is a valid VRRP packet.
+ *
+ * However, we have not validated whether the VRID is correct for this virtual
+ * router, nor whether the priority is correct (i.e. is not 255 when we are the
+ * address owner), nor whether the advertisement interval equals our own
+ * configured value (this check is only performed in VRRPv2).
+ *
+ * r
+ * VRRP Router associated with the socket this advertisement was received on
+ *
+ * src
+ * Source address of sender
+ *
+ * pkt
+ * The advertisement they sent
+ *
+ * pktsize
+ * Size of advertisement
+ *
+ * Returns:
+ * -1 if advertisement is invalid
+ * 0 otherwise
+ */
+static int vrrp_recv_advertisement(struct vrrp_router *r, struct ipaddr *src,
+ struct vrrp_pkt *pkt, size_t pktsize)
+{
+ char sipstr[INET6_ADDRSTRLEN];
+ char dipstr[INET6_ADDRSTRLEN];
+
+ ipaddr2str(src, sipstr, sizeof(sipstr));
+ ipaddr2str(&r->src, dipstr, sizeof(dipstr));
+
+ char dumpbuf[BUFSIZ];
+
+ vrrp_pkt_adver_dump(dumpbuf, sizeof(dumpbuf), pkt);
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Received VRRP Advertisement from %s: %s",
+ r->vr->vrid, family2str(r->family), sipstr, dumpbuf);
+
+ /* Check that VRID matches our configured VRID */
+ if (pkt->hdr.vrid != r->vr->vrid) {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Datagram invalid: Advertisement contains VRID %hhu which does not match our instance",
+ r->vr->vrid, family2str(r->family), pkt->hdr.vrid);
+ return -1;
+ }
+
+ /* Verify that we are not the IPvX address owner */
+ if (r->is_owner) {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Datagram invalid: Received advertisement but we are the address owner",
+ r->vr->vrid, family2str(r->family));
+ return -1;
+ }
+
+ /* If v2, verify that adver time matches ours */
+ bool adveq = (pkt->hdr.v2.adver_int
+ == MAX(r->vr->advertisement_interval / 100, 1));
+ if (r->vr->version == 2 && !adveq) {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Datagram invalid: Received advertisement with advertisement interval %hhu unequal to our configured value %u",
+ r->vr->vrid, family2str(r->family),
+ pkt->hdr.v2.adver_int,
+ MAX(r->vr->advertisement_interval / 100, 1));
+ return -1;
+ }
+
+
+ /* Check that # IPs received matches our # configured IPs */
+ if (pkt->hdr.naddr != r->addrs->count)
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Datagram has %hhu addresses, but this VRRP instance has %u",
+ r->vr->vrid, family2str(r->family), pkt->hdr.naddr,
+ r->addrs->count);
+
+ ++r->stats.adver_rx_cnt;
+
+ int addrcmp;
+
+ switch (r->fsm.state) {
+ case VRRP_STATE_MASTER:
+ addrcmp = ipaddr_cmp(src, &r->src);
+
+ if (pkt->hdr.priority == 0) {
+ vrrp_send_advertisement(r);
+ EVENT_OFF(r->t_adver_timer);
+ event_add_timer_msec(master, vrrp_adver_timer_expire, r,
+ r->vr->advertisement_interval *
+ CS2MS,
+ &r->t_adver_timer);
+ } else if (pkt->hdr.priority > r->priority
+ || ((pkt->hdr.priority == r->priority)
+ && addrcmp > 0)) {
+ zlog_info(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Received advertisement from %s w/ priority %hhu; switching to Backup",
+ r->vr->vrid, family2str(r->family), sipstr,
+ pkt->hdr.priority);
+ EVENT_OFF(r->t_adver_timer);
+ if (r->vr->version == 3) {
+ r->master_adver_interval =
+ htons(pkt->hdr.v3.adver_int);
+ }
+ vrrp_recalculate_timers(r);
+ EVENT_OFF(r->t_master_down_timer);
+ event_add_timer_msec(master,
+ vrrp_master_down_timer_expire, r,
+ r->master_down_interval * CS2MS,
+ &r->t_master_down_timer);
+ vrrp_change_state(r, VRRP_STATE_BACKUP);
+ } else {
+ /* Discard advertisement */
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Discarding advertisement from %s (%hhu <= %hhu & %s <= %s)",
+ r->vr->vrid, family2str(r->family), sipstr,
+ pkt->hdr.priority, r->priority, sipstr, dipstr);
+ }
+ break;
+ case VRRP_STATE_BACKUP:
+ if (pkt->hdr.priority == 0) {
+ EVENT_OFF(r->t_master_down_timer);
+ event_add_timer_msec(
+ master, vrrp_master_down_timer_expire, r,
+ r->skew_time * CS2MS, &r->t_master_down_timer);
+ } else if (!r->vr->preempt_mode
+ || pkt->hdr.priority >= r->priority) {
+ if (r->vr->version == 3) {
+ r->master_adver_interval =
+ ntohs(pkt->hdr.v3.adver_int);
+ }
+ vrrp_recalculate_timers(r);
+ EVENT_OFF(r->t_master_down_timer);
+ event_add_timer_msec(master,
+ vrrp_master_down_timer_expire, r,
+ r->master_down_interval * CS2MS,
+ &r->t_master_down_timer);
+ } else if (r->vr->preempt_mode
+ && pkt->hdr.priority < r->priority) {
+ /* Discard advertisement */
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Discarding advertisement from %s (%hhu < %hhu & preempt = true)",
+ r->vr->vrid, family2str(r->family), sipstr,
+ pkt->hdr.priority, r->priority);
+ }
+ break;
+ case VRRP_STATE_INITIALIZE:
+ zlog_err(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Received ADVERTISEMENT in state %s; this is a bug",
+ r->vr->vrid, family2str(r->family),
+ vrrp_state_names[r->fsm.state]);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Read and process next IPvX datagram.
+ */
+static void vrrp_read(struct event *thread)
+{
+ struct vrrp_router *r = EVENT_ARG(thread);
+
+ struct vrrp_pkt *pkt;
+ ssize_t pktsize;
+ ssize_t nbytes;
+ bool resched;
+ char errbuf[BUFSIZ];
+ struct sockaddr_storage sa;
+ uint8_t control[64];
+ struct ipaddr src = {};
+
+ struct msghdr m = {};
+ struct iovec iov;
+
+ iov.iov_base = r->ibuf;
+ iov.iov_len = sizeof(r->ibuf);
+ m.msg_name = &sa;
+ m.msg_namelen = sizeof(sa);
+ m.msg_iov = &iov;
+ m.msg_iovlen = 1;
+ m.msg_control = control;
+ m.msg_controllen = sizeof(control);
+
+ nbytes = recvmsg(r->sock_rx, &m, MSG_DONTWAIT);
+
+ if ((nbytes < 0 && ERRNO_IO_RETRY(errno))) {
+ resched = true;
+ goto done;
+ } else if (nbytes <= 0) {
+ vrrp_event(r, VRRP_EVENT_SHUTDOWN);
+ resched = false;
+ goto done;
+ }
+
+ if (DEBUG_MODE_CHECK(&vrrp_dbg_pkt, DEBUG_MODE_ALL)) {
+ DEBUGD(&vrrp_dbg_pkt,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Datagram rx: ",
+ r->vr->vrid, family2str(r->family));
+ zlog_hexdump(r->ibuf, nbytes);
+ }
+
+ pktsize = vrrp_pkt_parse_datagram(
+ r->family, r->vr->version,
+ r->vr->checksum_with_ipv4_pseudoheader, &m, nbytes, &src, &pkt,
+ errbuf, sizeof(errbuf));
+
+ if (pktsize < 0)
+ DEBUGD(&vrrp_dbg_pkt,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Datagram invalid: %s",
+ r->vr->vrid, family2str(r->family), errbuf);
+ else
+ vrrp_recv_advertisement(r, &src, pkt, pktsize);
+
+ resched = true;
+
+done:
+ memset(r->ibuf, 0x00, sizeof(r->ibuf));
+
+ if (resched)
+ event_add_read(master, vrrp_read, r, r->sock_rx, &r->t_read);
+}
+
+/*
+ * Creates and configures VRRP router sockets.
+ *
+ * This function:
+ * - Creates two sockets, one for Tx, one for Rx
+ * - Binds the Tx socket to the macvlan device, if necessary (VRF case)
+ * - Binds the Rx socket to the base interface
+ * - Joins the Rx socket to the appropriate VRRP multicast group
+ * - Sets the Tx socket to set the TTL (v4) or Hop Limit (v6) field to 255 for
+ * all transmitted IPvX packets
+ * - Requests the kernel to deliver IPv6 header values needed to validate VRRP
+ * packets
+ *
+ * If any of the above fail, the sockets are closed. The only exception is if
+ * the TTL / Hop Limit settings fail; these are logged, but configuration
+ * proceeds.
+ *
+ * The first connected address on the Virtual Router's interface is used as the
+ * interface address.
+ *
+ * r
+ * VRRP Router for which to create listen socket
+ *
+ * Returns:
+ * 0 on success
+ * -1 on failure
+ */
+static int vrrp_socket(struct vrrp_router *r)
+{
+ int ret;
+ bool failed = false;
+
+ frr_with_privs(&vrrp_privs) {
+ r->sock_rx = vrf_socket(r->family, SOCK_RAW, IPPROTO_VRRP,
+ r->vr->ifp->vrf->vrf_id, NULL);
+ r->sock_tx = vrf_socket(r->family, SOCK_RAW, IPPROTO_VRRP,
+ r->vr->ifp->vrf->vrf_id, NULL);
+ }
+
+ if (r->sock_rx < 0 || r->sock_tx < 0) {
+ const char *rxtx = r->sock_rx < 0 ? "Rx" : "Tx";
+
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Can't create VRRP %s socket",
+ r->vr->vrid, family2str(r->family), rxtx);
+ failed = true;
+ goto done;
+ }
+
+ /*
+ * Bind Tx socket to macvlan device - necessary for VRF support,
+ * otherwise the kernel will select the vrf device
+ */
+ if (r->vr->ifp->vrf->vrf_id != VRF_DEFAULT) {
+ frr_with_privs (&vrrp_privs) {
+ ret = setsockopt(r->sock_tx, SOL_SOCKET,
+ SO_BINDTODEVICE, r->mvl_ifp->name,
+ strlen(r->mvl_ifp->name));
+ }
+
+ if (ret < 0) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to bind Tx socket to macvlan device '%s'",
+ r->vr->vrid, family2str(r->family),
+ r->mvl_ifp->name);
+ failed = true;
+ goto done;
+ }
+ }
+ /* Configure sockets */
+ if (r->family == AF_INET) {
+ /* Set Tx socket to always Tx with TTL set to 255 */
+ int ttl = 255;
+
+ ret = setsockopt(r->sock_tx, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
+ sizeof(ttl));
+ if (ret < 0) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to set outgoing multicast TTL count to 255; RFC 5798 compliant implementations will drop our packets",
+ r->vr->vrid, family2str(r->family));
+ }
+
+ /* Set Tx socket DSCP byte */
+ setsockopt_ipv4_tos(r->sock_tx, IPTOS_PREC_INTERNETCONTROL);
+
+ /* Turn off multicast loop on Tx */
+ setsockopt_ipv4_multicast_loop(r->sock_tx, 0);
+
+ /* Bind Rx socket to exact interface */
+ frr_with_privs(&vrrp_privs) {
+ ret = setsockopt(r->sock_rx, SOL_SOCKET,
+ SO_BINDTODEVICE, r->vr->ifp->name,
+ strlen(r->vr->ifp->name));
+ }
+ if (ret) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to bind Rx socket to %s: %s",
+ r->vr->vrid, family2str(r->family),
+ r->vr->ifp->name, safe_strerror(errno));
+ failed = true;
+ goto done;
+ }
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Bound Rx socket to %s",
+ r->vr->vrid, family2str(r->family), r->vr->ifp->name);
+
+ /* Bind Rx socket to v4 multicast address */
+ struct sockaddr_in sa = {0};
+
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(VRRP_MCASTV4_GROUP);
+ if (bind(r->sock_rx, (struct sockaddr *)&sa, sizeof(sa))) {
+ zlog_err(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to bind Rx socket to VRRP multicast group: %s",
+ r->vr->vrid, family2str(r->family),
+ safe_strerror(errno));
+ failed = true;
+ goto done;
+ }
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Bound Rx socket to VRRP multicast group",
+ r->vr->vrid, family2str(r->family));
+
+ /* Join Rx socket to VRRP IPv4 multicast group */
+ assert(listhead(r->vr->ifp->connected));
+ struct connected *c = listhead(r->vr->ifp->connected)->data;
+ struct in_addr v4 = c->address->u.prefix4;
+
+ ret = setsockopt_ipv4_multicast(r->sock_rx, IP_ADD_MEMBERSHIP,
+ v4, htonl(VRRP_MCASTV4_GROUP),
+ r->vr->ifp->ifindex);
+ if (ret < 0) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID
+ "Failed to join VRRP %s multicast group",
+ r->vr->vrid, family2str(r->family));
+ failed = true;
+ goto done;
+ }
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Joined VRRP multicast group",
+ r->vr->vrid, family2str(r->family));
+
+ /* Set outgoing interface for advertisements */
+ struct ip_mreqn mreqn = {};
+
+ mreqn.imr_ifindex = r->mvl_ifp->ifindex;
+ ret = setsockopt(r->sock_tx, IPPROTO_IP, IP_MULTICAST_IF,
+ (void *)&mreqn, sizeof(mreqn));
+ if (ret < 0) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Could not set %s as outgoing multicast interface",
+ r->vr->vrid, family2str(r->family),
+ r->mvl_ifp->name);
+ failed = true;
+ goto done;
+ }
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Set %s as outgoing multicast interface",
+ r->vr->vrid, family2str(r->family), r->mvl_ifp->name);
+
+ /* Select and bind source address */
+ if (vrrp_bind_to_primary_connected(r) < 0) {
+ failed = true;
+ goto done;
+ }
+
+ } else if (r->family == AF_INET6) {
+ /* Always transmit IPv6 packets with hop limit set to 255 */
+ ret = setsockopt_ipv6_multicast_hops(r->sock_tx, 255);
+ if (ret < 0) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to set outgoing multicast hop count to 255; RFC 5798 compliant implementations will drop our packets",
+ r->vr->vrid, family2str(r->family));
+ }
+
+ /* Set Tx socket DSCP byte */
+ setsockopt_ipv6_tclass(r->sock_tx, IPTOS_PREC_INTERNETCONTROL);
+
+ /* Request hop limit delivery */
+ setsockopt_ipv6_hoplimit(r->sock_rx, 1);
+ if (ret < 0) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to request IPv6 Hop Limit delivery",
+ r->vr->vrid, family2str(r->family));
+ failed = true;
+ goto done;
+ }
+
+ /* Turn off multicast loop on Tx */
+ if (setsockopt_ipv6_multicast_loop(r->sock_tx, 0) < 0) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to turn off IPv6 multicast",
+ r->vr->vrid, family2str(r->family));
+ failed = true;
+ goto done;
+ }
+
+ /* Bind Rx socket to exact interface */
+ frr_with_privs(&vrrp_privs) {
+ ret = setsockopt(r->sock_rx, SOL_SOCKET,
+ SO_BINDTODEVICE, r->vr->ifp->name,
+ strlen(r->vr->ifp->name));
+ }
+ if (ret) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to bind Rx socket to %s: %s",
+ r->vr->vrid, family2str(r->family),
+ r->vr->ifp->name, safe_strerror(errno));
+ failed = true;
+ goto done;
+ }
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Bound Rx socket to %s",
+ r->vr->vrid, family2str(r->family), r->vr->ifp->name);
+
+ /* Bind Rx socket to v6 multicast address */
+ struct sockaddr_in6 sa = {0};
+
+ sa.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, VRRP_MCASTV6_GROUP_STR, &sa.sin6_addr);
+ if (bind(r->sock_rx, (struct sockaddr *)&sa, sizeof(sa))) {
+ zlog_err(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to bind Rx socket to VRRP multicast group: %s",
+ r->vr->vrid, family2str(r->family),
+ safe_strerror(errno));
+ failed = true;
+ goto done;
+ }
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Bound Rx socket to VRRP multicast group",
+ r->vr->vrid, family2str(r->family));
+
+ /* Join VRRP IPv6 multicast group */
+ struct ipv6_mreq mreq;
+
+ inet_pton(AF_INET6, VRRP_MCASTV6_GROUP_STR,
+ &mreq.ipv6mr_multiaddr);
+ mreq.ipv6mr_interface = r->vr->ifp->ifindex;
+ ret = setsockopt(r->sock_rx, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq));
+ if (ret < 0) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to join VRRP multicast group",
+ r->vr->vrid, family2str(r->family));
+ failed = true;
+ goto done;
+ }
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Joined VRRP multicast group",
+ r->vr->vrid, family2str(r->family));
+
+ /* Set outgoing interface for advertisements */
+ ret = setsockopt(r->sock_tx, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+ &r->mvl_ifp->ifindex, sizeof(ifindex_t));
+ if (ret < 0) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Could not set %s as outgoing multicast interface",
+ r->vr->vrid, family2str(r->family),
+ r->mvl_ifp->name);
+ failed = true;
+ goto done;
+ }
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Set %s as outgoing multicast interface",
+ r->vr->vrid, family2str(r->family), r->mvl_ifp->name);
+ }
+
+done:
+ ret = 0;
+ if (failed) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to initialize VRRP router",
+ r->vr->vrid, family2str(r->family));
+ if (r->sock_rx >= 0) {
+ close(r->sock_rx);
+ r->sock_rx = -1;
+ }
+ if (r->sock_tx >= 0) {
+ close(r->sock_tx);
+ r->sock_tx = -1;
+ }
+ ret = -1;
+ }
+
+ return ret;
+}
+
+
+/* State machine ----------------------------------------------------------- */
+
+DEFINE_HOOK(vrrp_change_state_hook, (struct vrrp_router *r, int to), (r, to));
+
+/*
+ * Handle any necessary actions during state change to MASTER state.
+ *
+ * r
+ * VRRP Router to operate on
+ */
+static void vrrp_change_state_master(struct vrrp_router *r)
+{
+ /* Enable ND Router Advertisements */
+ if (r->family == AF_INET6)
+ vrrp_zebra_radv_set(r, true);
+
+ /* Set protodown off */
+ vrrp_zclient_send_interface_protodown(r->mvl_ifp, false);
+
+ /*
+ * If protodown is already off, we can send our stuff, otherwise we
+ * have to delay until the interface is all the way up
+ */
+ if (if_is_operative(r->mvl_ifp)) {
+ vrrp_send_advertisement(r);
+
+ if (r->family == AF_INET)
+ vrrp_garp_send_all(r);
+ else if (r->family == AF_INET6)
+ vrrp_ndisc_una_send_all(r);
+ } else {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Delaying VRRP advertisement until interface is up",
+ r->vr->vrid, family2str(r->family));
+ r->advert_pending = true;
+
+ if (r->family == AF_INET) {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Delaying VRRP gratuitous ARPs until interface is up",
+ r->vr->vrid, family2str(r->family));
+ r->garp_pending = true;
+ } else if (r->family == AF_INET6) {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Delaying VRRP unsolicited neighbor advertisement until interface is up",
+ r->vr->vrid, family2str(r->family));
+ r->ndisc_pending = true;
+ }
+ }
+}
+
+/*
+ * Handle any necessary actions during state change to BACKUP state.
+ *
+ * r
+ * Virtual Router to operate on
+ */
+static void vrrp_change_state_backup(struct vrrp_router *r)
+{
+ /* Disable ND Router Advertisements */
+ if (r->family == AF_INET6)
+ vrrp_zebra_radv_set(r, false);
+
+ /* Disable Adver_Timer */
+ EVENT_OFF(r->t_adver_timer);
+
+ r->advert_pending = false;
+ r->garp_pending = false;
+ r->ndisc_pending = false;
+ memset(&r->src, 0x00, sizeof(r->src));
+
+ vrrp_zclient_send_interface_protodown(r->mvl_ifp, true);
+}
+
+/*
+ * Handle any necessary actions during state change to INITIALIZE state.
+ *
+ * This is not called for initial startup, only when transitioning from MASTER
+ * or BACKUP.
+ *
+ * r
+ * VRRP Router to operate on
+ */
+static void vrrp_change_state_initialize(struct vrrp_router *r)
+{
+ r->master_adver_interval = 0;
+ vrrp_recalculate_timers(r);
+
+ r->advert_pending = false;
+ r->garp_pending = false;
+ r->ndisc_pending = false;
+
+ /* Disable ND Router Advertisements */
+ if (r->family == AF_INET6 && r->mvl_ifp)
+ vrrp_zebra_radv_set(r, false);
+}
+
+void (*const vrrp_change_state_handlers[])(struct vrrp_router *vr) = {
+ [VRRP_STATE_MASTER] = vrrp_change_state_master,
+ [VRRP_STATE_BACKUP] = vrrp_change_state_backup,
+ [VRRP_STATE_INITIALIZE] = vrrp_change_state_initialize,
+};
+
+/*
+ * Change Virtual Router FSM position. Handles transitional actions and calls
+ * any subscribers to the state change hook.
+ *
+ * r
+ * Virtual Router for which to change state
+ *
+ * to
+ * State to change to
+ */
+static void vrrp_change_state(struct vrrp_router *r, int to)
+{
+ if (r->fsm.state == to)
+ return;
+
+ /* Call our handlers, then any subscribers */
+ vrrp_change_state_handlers[to](r);
+ hook_call(vrrp_change_state_hook, r, to);
+ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "%s -> %s",
+ r->vr->vrid, family2str(r->family),
+ vrrp_state_names[r->fsm.state], vrrp_state_names[to]);
+ r->fsm.state = to;
+
+ ++r->stats.trans_cnt;
+}
+
+/*
+ * Called when Adver_Timer expires.
+ */
+static void vrrp_adver_timer_expire(struct event *thread)
+{
+ struct vrrp_router *r = EVENT_ARG(thread);
+
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Adver_Timer expired",
+ r->vr->vrid, family2str(r->family));
+
+ if (r->fsm.state == VRRP_STATE_MASTER) {
+ /* Send an ADVERTISEMENT */
+ vrrp_send_advertisement(r);
+
+ /* Reset the Adver_Timer to Advertisement_Interval */
+ event_add_timer_msec(master, vrrp_adver_timer_expire, r,
+ r->vr->advertisement_interval * CS2MS,
+ &r->t_adver_timer);
+ } else {
+ zlog_err(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Adver_Timer expired in state '%s'; this is a bug",
+ r->vr->vrid, family2str(r->family),
+ vrrp_state_names[r->fsm.state]);
+ }
+}
+
+/*
+ * Called when Master_Down_Timer expires.
+ */
+static void vrrp_master_down_timer_expire(struct event *thread)
+{
+ struct vrrp_router *r = EVENT_ARG(thread);
+
+ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Master_Down_Timer expired",
+ r->vr->vrid, family2str(r->family));
+
+ event_add_timer_msec(master, vrrp_adver_timer_expire, r,
+ r->vr->advertisement_interval * CS2MS,
+ &r->t_adver_timer);
+ vrrp_change_state(r, VRRP_STATE_MASTER);
+}
+
+/*
+ * Event handler for Startup event.
+ *
+ * Creates sockets, sends advertisements and ARP requests, starts timers,
+ * and transitions the Virtual Router to either Master or Backup states.
+ *
+ * This function will also initialize the program's global ARP subsystem if it
+ * has not yet been initialized.
+ *
+ * r
+ * VRRP Router on which to apply Startup event
+ *
+ * Returns:
+ * < 0 if the session socket could not be created, or the state is not
+ * Initialize
+ * 0 on success
+ */
+static int vrrp_startup(struct vrrp_router *r)
+{
+ /* May only be called when the state is Initialize */
+ if (r->fsm.state != VRRP_STATE_INITIALIZE)
+ return -1;
+
+ /* Must have a valid macvlan interface available */
+ if (r->mvl_ifp == NULL && !vrrp_attach_interface(r)) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "No appropriate interface found",
+ r->vr->vrid, family2str(r->family));
+ return -1;
+ }
+
+ /* Initialize global gratuitous ARP socket if necessary */
+ if (r->family == AF_INET && !vrrp_garp_is_init())
+ vrrp_garp_init();
+ if (r->family == AF_INET6 && !vrrp_ndisc_is_init())
+ vrrp_ndisc_init();
+
+ /* Create socket */
+ if (r->sock_rx < 0 || r->sock_tx < 0) {
+ int ret = vrrp_socket(r);
+
+ if (ret < 0 || r->sock_tx < 0 || r->sock_rx < 0)
+ return ret;
+ }
+
+ /* Schedule listener */
+ event_add_read(master, vrrp_read, r, r->sock_rx, &r->t_read);
+
+ /* Configure effective priority */
+ assert(listhead(r->addrs));
+ struct ipaddr *primary = (struct ipaddr *)listhead(r->addrs)->data;
+ char ipbuf[INET6_ADDRSTRLEN];
+
+ inet_ntop(r->family, &primary->ip.addr, ipbuf, sizeof(ipbuf));
+
+ if (r->vr->priority == VRRP_PRIO_MASTER
+ || vrrp_is_owner(r->vr->ifp, primary)) {
+ r->priority = VRRP_PRIO_MASTER;
+ vrrp_recalculate_timers(r);
+
+ zlog_info(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "%s has priority set to 255 or owns primary Virtual Router IP %s; electing self as Master",
+ r->vr->vrid, family2str(r->family), r->vr->ifp->name,
+ ipbuf);
+ }
+
+ if (r->priority == VRRP_PRIO_MASTER) {
+ event_add_timer_msec(master, vrrp_adver_timer_expire, r,
+ r->vr->advertisement_interval * CS2MS,
+ &r->t_adver_timer);
+ vrrp_change_state(r, VRRP_STATE_MASTER);
+ } else {
+ r->master_adver_interval = r->vr->advertisement_interval;
+ vrrp_recalculate_timers(r);
+ event_add_timer_msec(master, vrrp_master_down_timer_expire, r,
+ r->master_down_interval * CS2MS,
+ &r->t_master_down_timer);
+ vrrp_change_state(r, VRRP_STATE_BACKUP);
+ }
+
+ r->is_active = true;
+
+ return 0;
+}
+
+/*
+ * Shuts down a Virtual Router and transitions it to Initialize.
+ *
+ * This call must be idempotent; it is safe to call multiple times on the same
+ * VRRP Router.
+ */
+static int vrrp_shutdown(struct vrrp_router *r)
+{
+ uint8_t saved_prio;
+
+ switch (r->fsm.state) {
+ case VRRP_STATE_MASTER:
+ /* Send an ADVERTISEMENT with Priority = 0 */
+ saved_prio = r->priority;
+ r->priority = 0;
+ vrrp_send_advertisement(r);
+ r->priority = saved_prio;
+ break;
+ case VRRP_STATE_BACKUP:
+ break;
+ case VRRP_STATE_INITIALIZE:
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Received '%s' event in '%s' state; ignoring",
+ r->vr->vrid, family2str(r->family),
+ vrrp_event_names[VRRP_EVENT_SHUTDOWN],
+ vrrp_state_names[VRRP_STATE_INITIALIZE]);
+ return 0;
+ }
+
+ /* Cancel all timers */
+ EVENT_OFF(r->t_adver_timer);
+ EVENT_OFF(r->t_master_down_timer);
+ EVENT_OFF(r->t_read);
+ EVENT_OFF(r->t_write);
+
+ /* Protodown macvlan */
+ if (r->mvl_ifp)
+ vrrp_zclient_send_interface_protodown(r->mvl_ifp, true);
+
+ /* Throw away our source address */
+ memset(&r->src, 0x00, sizeof(r->src));
+
+ if (r->sock_rx > 0) {
+ close(r->sock_rx);
+ r->sock_rx = -1;
+ }
+ if (r->sock_tx > 0) {
+ close(r->sock_tx);
+ r->sock_tx = -1;
+ }
+
+ vrrp_change_state(r, VRRP_STATE_INITIALIZE);
+
+ r->is_active = false;
+
+ return 0;
+}
+
+static int (*const vrrp_event_handlers[])(struct vrrp_router *r) = {
+ [VRRP_EVENT_STARTUP] = vrrp_startup,
+ [VRRP_EVENT_SHUTDOWN] = vrrp_shutdown,
+};
+
+/*
+ * Spawn a VRRP FSM event on a VRRP Router.
+ *
+ * vr
+ * VRRP Router on which to spawn event
+ *
+ * event
+ * The event to spawn
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ */
+int vrrp_event(struct vrrp_router *r, int event)
+{
+ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "'%s' event",
+ r->vr->vrid, family2str(r->family), vrrp_event_names[event]);
+ return vrrp_event_handlers[event](r);
+}
+
+
+/* Autoconfig -------------------------------------------------------------- */
+
+/*
+ * Set the configured addresses for this VRRP instance to exactly the addresses
+ * present on its macvlan subinterface(s).
+ *
+ * vr
+ * VRRP router to act on
+ */
+static void vrrp_autoconfig_autoaddrupdate(struct vrrp_router *r)
+{
+ struct listnode *ln;
+ struct connected *c = NULL;
+ bool is_v6_ll;
+
+ if (!r->mvl_ifp)
+ return;
+
+ DEBUGD(&vrrp_dbg_auto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Setting Virtual IP list to match IPv4 addresses on %s",
+ r->vr->vrid, family2str(r->family), r->mvl_ifp->name);
+ for (ALL_LIST_ELEMENTS_RO(r->mvl_ifp->connected, ln, c)) {
+ is_v6_ll = (c->address->family == AF_INET6
+ && IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6));
+ if (c->address->family == r->family && !is_v6_ll) {
+ DEBUGD(&vrrp_dbg_auto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Adding %pFX",
+ r->vr->vrid, family2str(r->family), c->address);
+ if (r->family == AF_INET)
+ vrrp_add_ipv4(r->vr, c->address->u.prefix4);
+ else if (r->vr->version == 3)
+ vrrp_add_ipv6(r->vr, c->address->u.prefix6);
+ }
+ }
+
+ vrrp_check_start(r->vr);
+
+ if (r->addrs->count == 0 && r->fsm.state != VRRP_STATE_INITIALIZE) {
+ DEBUGD(&vrrp_dbg_auto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Virtual IP list is empty; shutting down",
+ r->vr->vrid, family2str(r->family));
+ vrrp_event(r, VRRP_EVENT_SHUTDOWN);
+ }
+}
+
+static struct vrrp_vrouter *
+vrrp_autoconfig_autocreate(struct interface *mvl_ifp)
+{
+ struct interface *p;
+ struct vrrp_vrouter *vr;
+
+ p = if_lookup_by_index(mvl_ifp->link_ifindex, mvl_ifp->vrf->vrf_id);
+
+ if (!p)
+ return NULL;
+
+ uint8_t vrid = mvl_ifp->hw_addr[5];
+ uint8_t fam = mvl_ifp->hw_addr[4];
+
+ DEBUGD(&vrrp_dbg_auto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Autoconfiguring VRRP on %s",
+ vrid, family2str(fam), p->name);
+
+ vr = vrrp_vrouter_create(p, vrid, vrrp_autoconfig_version);
+
+ if (!vr) {
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Failed to autoconfigure VRRP on %s",
+ vrid, family2str(fam), p->name);
+ return NULL;
+ }
+
+ vr->autoconf = true;
+
+ /*
+ * If these interfaces are protodown on, we need to un-protodown them
+ * in order to get Zebra to send us their addresses so we can
+ * autoconfigure them.
+ */
+ if (vr->v4->mvl_ifp)
+ vrrp_zclient_send_interface_protodown(vr->v4->mvl_ifp, false);
+ if (vr->v6->mvl_ifp)
+ vrrp_zclient_send_interface_protodown(vr->v6->mvl_ifp, false);
+
+ /* If they're not, we can go ahead and add the addresses we have */
+ vrrp_autoconfig_autoaddrupdate(vr->v4);
+ vrrp_autoconfig_autoaddrupdate(vr->v6);
+
+ return vr;
+}
+
+/*
+ * Callback to notify autoconfig of interface add.
+ *
+ * If the interface is a VRRP-compatible device, and there is no existing VRRP
+ * router running on it, one is created. All addresses on the interface are
+ * added to the router.
+ *
+ * ifp
+ * Interface to operate on
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ */
+static int vrrp_autoconfig_if_add(struct interface *ifp)
+{
+ bool created = false;
+ struct vrrp_vrouter *vr;
+
+ if (!vrrp_autoconfig_is_on)
+ return 0;
+
+ if (!ifp || !ifp->link_ifindex || !vrrp_ifp_has_vrrp_mac(ifp))
+ return -1;
+
+ vr = vrrp_lookup_by_if_mvl(ifp);
+
+ if (!vr) {
+ vr = vrrp_autoconfig_autocreate(ifp);
+ created = true;
+ }
+
+ if (!vr || !vr->autoconf)
+ return 0;
+
+ if (!created) {
+ /*
+ * We didn't create it, but it has already been autoconfigured.
+ * Try to attach this interface to the existing instance.
+ */
+ if (!vr->v4->mvl_ifp) {
+ vrrp_attach_interface(vr->v4);
+ /* If we just attached it, make sure it's turned on */
+ if (vr->v4->mvl_ifp) {
+ vrrp_zclient_send_interface_protodown(
+ vr->v4->mvl_ifp, false);
+ /*
+ * If it's already up, we can go ahead and add
+ * the addresses we have
+ */
+ vrrp_autoconfig_autoaddrupdate(vr->v4);
+ }
+ }
+ if (!vr->v6->mvl_ifp) {
+ vrrp_attach_interface(vr->v6);
+ /* If we just attached it, make sure it's turned on */
+ if (vr->v6->mvl_ifp) {
+ vrrp_zclient_send_interface_protodown(
+ vr->v6->mvl_ifp, false);
+ /*
+ * If it's already up, we can go ahead and add
+ * the addresses we have
+ */
+ vrrp_autoconfig_autoaddrupdate(vr->v6);
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Callback to notify autoconfig of interface delete.
+ *
+ * If the interface is a VRRP-compatible device, and a VRRP router is running
+ * on it, and that VRRP router was automatically configured, it will be
+ * deleted. If that was the last router for the corresponding VRID (i.e., if
+ * this interface was a v4 VRRP interface and no v6 router is configured for
+ * the same VRID) then the entire virtual router is deleted.
+ *
+ * ifp
+ * Interface to operate on
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ */
+static int vrrp_autoconfig_if_del(struct interface *ifp)
+{
+ if (!vrrp_autoconfig_is_on)
+ return 0;
+
+ struct vrrp_vrouter *vr;
+ struct listnode *ln;
+ struct list *vrs;
+
+ vrs = vrrp_lookup_by_if_any(ifp);
+
+ for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr))
+ if (vr->autoconf
+ && (!vr->ifp || (!vr->v4->mvl_ifp && !vr->v6->mvl_ifp))) {
+ DEBUGD(&vrrp_dbg_auto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID
+ "All VRRP interfaces for instance deleted; destroying autoconfigured VRRP router",
+ vr->vrid);
+ vrrp_vrouter_destroy(vr);
+ }
+
+ list_delete(&vrs);
+
+ return 0;
+}
+
+/*
+ * Callback to notify autoconfig of interface up.
+ *
+ * Creates VRRP instance on interface if it does not exist. Otherwise does
+ * nothing.
+ *
+ * ifp
+ * Interface to operate on
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ */
+static int vrrp_autoconfig_if_up(struct interface *ifp)
+{
+ if (!vrrp_autoconfig_is_on)
+ return 0;
+
+ struct vrrp_vrouter *vr = vrrp_lookup_by_if_mvl(ifp);
+
+ if (vr && !vr->autoconf)
+ return 0;
+
+ if (!vr) {
+ vrrp_autoconfig_if_add(ifp);
+ return 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Callback to notify autoconfig of interface down.
+ *
+ * Does nothing. An interface down event is accompanied by address deletion
+ * events for all the addresses on the interface; if an autoconfigured VRRP
+ * router exists on this interface, then it will have all its addresses deleted
+ * and end up in Initialize.
+ *
+ * ifp
+ * Interface to operate on
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ */
+static int vrrp_autoconfig_if_down(struct interface *ifp)
+{
+ if (!vrrp_autoconfig_is_on)
+ return 0;
+
+ return 0;
+}
+
+/*
+ * Callback to notify autoconfig of a new interface address.
+ *
+ * If a VRRP router exists on this interface, its address list is updated to
+ * match the new address list. If no addresses remain, a Shutdown event is
+ * issued to the VRRP router.
+ *
+ * ifp
+ * Interface to operate on
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ *
+ */
+static int vrrp_autoconfig_if_address_add(struct interface *ifp)
+{
+ if (!vrrp_autoconfig_is_on)
+ return 0;
+
+ struct vrrp_vrouter *vr = vrrp_lookup_by_if_mvl(ifp);
+
+ if (vr && vr->autoconf) {
+ if (vr->v4->mvl_ifp == ifp)
+ vrrp_autoconfig_autoaddrupdate(vr->v4);
+ else if (vr->v6->mvl_ifp == ifp)
+ vrrp_autoconfig_autoaddrupdate(vr->v6);
+ }
+
+ return 0;
+}
+
+/*
+ * Callback to notify autoconfig of a removed interface address.
+ *
+ * If a VRRP router exists on this interface, its address list is updated to
+ * match the new address list. If no addresses remain, a Shutdown event is
+ * issued to the VRRP router.
+ *
+ * ifp
+ * Interface to operate on
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ *
+ */
+static int vrrp_autoconfig_if_address_del(struct interface *ifp)
+{
+ if (!vrrp_autoconfig_is_on)
+ return 0;
+
+ struct vrrp_vrouter *vr = vrrp_lookup_by_if_mvl(ifp);
+
+ if (vr && vr->autoconf) {
+ if (vr->v4->mvl_ifp == ifp)
+ vrrp_autoconfig_autoaddrupdate(vr->v4);
+ else if (vr->v6->mvl_ifp == ifp)
+ vrrp_autoconfig_autoaddrupdate(vr->v6);
+ }
+
+ return 0;
+}
+
+int vrrp_autoconfig(void)
+{
+ if (!vrrp_autoconfig_is_on)
+ return 0;
+
+ struct vrf *vrf;
+ struct interface *ifp;
+
+ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
+ FOR_ALL_INTERFACES (vrf, ifp)
+ vrrp_autoconfig_if_add(ifp);
+ }
+
+ return 0;
+}
+
+void vrrp_autoconfig_on(int version)
+{
+ vrrp_autoconfig_is_on = true;
+ vrrp_autoconfig_version = version;
+
+ vrrp_autoconfig();
+}
+
+void vrrp_autoconfig_off(void)
+{
+ vrrp_autoconfig_is_on = false;
+
+ struct list *ll = hash_to_list(vrrp_vrouters_hash);
+
+ struct listnode *ln;
+ struct vrrp_vrouter *vr;
+
+ for (ALL_LIST_ELEMENTS_RO(ll, ln, vr))
+ if (vr->autoconf)
+ vrrp_vrouter_destroy(vr);
+
+ list_delete(&ll);
+}
+
+/* Interface tracking ------------------------------------------------------ */
+
+/*
+ * Bind any pending interfaces.
+ *
+ * mvl_ifp
+ * macvlan interface that some VRRP instances might want to bind to
+ */
+static void vrrp_bind_pending(struct interface *mvl_ifp)
+{
+ struct vrrp_vrouter *vr;
+
+ DEBUGD(&vrrp_dbg_zebra,
+ VRRP_LOGPFX
+ "Searching for instances that could use interface %s",
+ mvl_ifp->name);
+
+ vr = vrrp_lookup_by_if_mvl(mvl_ifp);
+
+ if (vr) {
+ DEBUGD(&vrrp_dbg_zebra,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID
+ "<-- This instance can probably use interface %s",
+ vr->vrid, mvl_ifp->name);
+
+ if (mvl_ifp->hw_addr[4] == 0x01 && !vr->v4->mvl_ifp)
+ vrrp_attach_interface(vr->v4);
+ else if (mvl_ifp->hw_addr[4] == 0x02 && !vr->v6->mvl_ifp)
+ vrrp_attach_interface(vr->v6);
+ }
+}
+
+void vrrp_if_up(struct interface *ifp)
+{
+ struct vrrp_vrouter *vr;
+ struct listnode *ln;
+ struct list *vrs;
+
+ vrrp_bind_pending(ifp);
+
+ vrs = vrrp_lookup_by_if_any(ifp);
+
+ for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) {
+ vrrp_check_start(vr);
+
+ if (!if_is_operative(ifp))
+ continue;
+
+ /*
+ * Handle the situation in which we performed a state
+ * transition on this VRRP router but needed to wait for the
+ * macvlan interface to come up to perform some actions
+ */
+ if (ifp == vr->v4->mvl_ifp) {
+ if (vr->v4->advert_pending) {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID
+ VRRP_LOGPFX_FAM
+ "Interface up; sending pending advertisement",
+ vr->vrid, family2str(vr->v4->family));
+ vrrp_send_advertisement(vr->v4);
+ vr->v4->advert_pending = false;
+ }
+ if (vr->v4->garp_pending) {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID
+ VRRP_LOGPFX_FAM
+ "Interface up; sending pending gratuitous ARP",
+ vr->vrid, family2str(vr->v4->family));
+ vrrp_garp_send_all(vr->v4);
+ vr->v4->garp_pending = false;
+ }
+ }
+ if (ifp == vr->v6->mvl_ifp) {
+ if (vr->v6->advert_pending) {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID
+ VRRP_LOGPFX_FAM
+ "Interface up; sending pending advertisement",
+ vr->vrid, family2str(vr->v6->family));
+ vrrp_send_advertisement(vr->v6);
+ vr->v6->advert_pending = false;
+ }
+ if (vr->v6->ndisc_pending) {
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID
+ VRRP_LOGPFX_FAM
+ "Interface up; sending pending Unsolicited Neighbor Advertisement",
+ vr->vrid, family2str(vr->v6->family));
+ vrrp_ndisc_una_send_all(vr->v6);
+ vr->v6->ndisc_pending = false;
+ }
+ }
+ }
+
+ list_delete(&vrs);
+
+ vrrp_autoconfig_if_up(ifp);
+}
+
+void vrrp_if_down(struct interface *ifp)
+{
+ struct vrrp_vrouter *vr;
+ struct listnode *ln;
+ struct list *vrs;
+
+ vrrp_bind_pending(ifp);
+
+ vrs = vrrp_lookup_by_if_any(ifp);
+
+ for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) {
+ vrrp_check_start(vr);
+
+ if (vr->ifp == ifp || vr->v4->mvl_ifp == ifp
+ || vr->v6->mvl_ifp == ifp) {
+ DEBUGD(&vrrp_dbg_auto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID "Interface %s down",
+ vr->vrid, ifp->name);
+ }
+ }
+
+ list_delete(&vrs);
+
+ vrrp_autoconfig_if_down(ifp);
+}
+
+void vrrp_if_add(struct interface *ifp)
+{
+ vrrp_bind_pending(ifp);
+
+ /* thanks, zebra */
+ if (CHECK_FLAG(ifp->flags, IFF_UP))
+ vrrp_if_up(ifp);
+
+ vrrp_autoconfig_if_add(ifp);
+}
+
+void vrrp_if_del(struct interface *ifp)
+{
+ struct listnode *ln;
+ struct vrrp_vrouter *vr;
+
+ vrrp_if_down(ifp);
+
+ /*
+ * You think we'd be able use vrrp_lookup_by_if_any to find interfaces?
+ * Nah. FRR's interface management is insane. There are no ordering
+ * guarantees about what interfaces are deleted when. Maybe this is a
+ * macvlan and its parent was already deleted, in which case its
+ * ifindex is now IFINDEX_INTERNAL, so ifp->link_ifindex - while still
+ * valid - doesn't match any interface on the system, meaning we can't
+ * use any of the vrrp_lookup* functions since they rely on finding the
+ * base interface of what they're given by following link_ifindex.
+ *
+ * Since we need to actually NULL out pointers in this function to
+ * avoid a UAF - since the caller will (might) free ifp after we return
+ * - we need to look up based on pointers.
+ */
+ struct list *vrs = hash_to_list(vrrp_vrouters_hash);
+
+ for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) {
+ if (ifp == vr->ifp) {
+ vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN);
+ vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN);
+ /*
+ * Stands to reason if the base was deleted, so were
+ * (or will be) its children
+ */
+ vr->v4->mvl_ifp = NULL;
+ vr->v6->mvl_ifp = NULL;
+ /*
+ * We shouldn't need to lose the reference if it's the
+ * primary interface, because that was configured
+ * explicitly in our config, and thus will be kept as a
+ * stub; to avoid stupid bugs, double check that
+ */
+ assert(ifp->configured);
+ } else if (ifp == vr->v4->mvl_ifp) {
+ vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN);
+ /*
+ * If this is a macvlan, then it wasn't explicitly
+ * configured and will be deleted when we return from
+ * this function, so we need to lose the reference
+ */
+ vr->v4->mvl_ifp = NULL;
+ } else if (ifp == vr->v6->mvl_ifp) {
+ vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN);
+ /*
+ * If this is a macvlan, then it wasn't explicitly
+ * configured and will be deleted when we return from
+ * this function, so we need to lose the reference
+ */
+ vr->v6->mvl_ifp = NULL;
+ }
+ }
+
+ list_delete(&vrs);
+
+ vrrp_autoconfig_if_del(ifp);
+}
+
+void vrrp_if_address_add(struct interface *ifp)
+{
+ struct vrrp_vrouter *vr;
+ struct listnode *ln;
+ struct list *vrs;
+
+ /*
+ * We have to do a wide search here, because we need to know when a v6
+ * macvlan device gets a new address. This is because the macvlan link
+ * local is used as the source address for v6 advertisements, and hence
+ * "do I have a link local" constitutes an activation condition for v6
+ * virtual routers.
+ */
+ vrs = vrrp_lookup_by_if_any(ifp);
+
+ for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr))
+ vrrp_check_start(vr);
+
+ list_delete(&vrs);
+
+ vrrp_autoconfig_if_address_add(ifp);
+}
+
+void vrrp_if_address_del(struct interface *ifp)
+{
+ /*
+ * Zebra is stupid and sends us address deletion notifications
+ * when any of the following condition sets are met:
+ *
+ * - if_is_operative && address deleted
+ * - if_is_operative -> !if_is_operative
+ *
+ * Note that the second one is nonsense, because Zebra behaves as
+ * though an interface going down means all the addresses on that
+ * interface got deleted. Which is a problem for autoconfig because all
+ * the addresses on an interface going away means the VRRP session goes
+ * to Initialize. However interfaces go down whenever we transition to
+ * Backup, so this effectively means that for autoconfigured instances
+ * we actually end up in Initialize whenever we try to go into Backup.
+ *
+ * Also, Zebra does NOT send us notifications when:
+ * - !if_is_operative && address deleted
+ *
+ * Which means if we're in backup and an address is deleted out from
+ * under us, we won't even know.
+ *
+ * The only solution here is to only resynchronize our address list
+ * when:
+ *
+ * - An interfaces comes up
+ * - An interface address is added
+ * - An interface address is deleted AND the interface is up
+ *
+ * Even though this is only a problem with autoconfig at the moment I'm
+ * papering over Zebra's braindead semantics here. Every piece of code
+ * in this function should be protected by a check that the interface
+ * is up.
+ */
+ if (if_is_operative(ifp))
+ vrrp_autoconfig_if_address_del(ifp);
+}
+
+/* Other ------------------------------------------------------------------- */
+
+int vrrp_config_write_global(struct vty *vty)
+{
+ unsigned int writes = 0;
+
+ if (vrrp_autoconfig_is_on && ++writes)
+ vty_out(vty, "vrrp autoconfigure%s\n",
+ vrrp_autoconfig_version == 2 ? " version 2" : "");
+
+ /* FIXME: needs to be udpated for full YANG conversion. */
+ if (vd.priority != VRRP_DEFAULT_PRIORITY && ++writes)
+ vty_out(vty, "vrrp default priority %hhu\n", vd.priority);
+
+ if (vd.advertisement_interval != VRRP_DEFAULT_ADVINT && ++writes)
+ vty_out(vty,
+ "vrrp default advertisement-interval %u\n",
+ vd.advertisement_interval * CS2MS);
+
+ if (vd.preempt_mode != VRRP_DEFAULT_PREEMPT && ++writes)
+ vty_out(vty, "%svrrp default preempt\n",
+ !vd.preempt_mode ? "no " : "");
+
+ if (vd.accept_mode != VRRP_DEFAULT_ACCEPT && ++writes)
+ vty_out(vty, "%svrrp default accept\n",
+ !vd.accept_mode ? "no " : "");
+
+ if (vd.checksum_with_ipv4_pseudoheader !=
+ VRRP_DEFAULT_CHECKSUM_WITH_IPV4_PSEUDOHEADER &&
+ ++writes)
+ vty_out(vty, "%svrrp default checksum-with-ipv4-pseudoheader\n",
+ !vd.checksum_with_ipv4_pseudoheader ? "no " : "");
+
+ if (vd.shutdown != VRRP_DEFAULT_SHUTDOWN && ++writes)
+ vty_out(vty, "%svrrp default shutdown\n",
+ !vd.shutdown ? "no " : "");
+
+ return writes;
+}
+
+static unsigned int vrrp_hash_key(const void *arg)
+{
+ const struct vrrp_vrouter *vr = arg;
+ char key[IFNAMSIZ + 64];
+
+ snprintf(key, sizeof(key), "%s@%u", vr->ifp->name, vr->vrid);
+
+ return string_hash_make(key);
+}
+
+static bool vrrp_hash_cmp(const void *arg1, const void *arg2)
+{
+ const struct vrrp_vrouter *vr1 = arg1;
+ const struct vrrp_vrouter *vr2 = arg2;
+
+ if (vr1->ifp != vr2->ifp)
+ return false;
+ if (vr1->vrid != vr2->vrid)
+ return false;
+
+ return true;
+}
+
+void vrrp_init(void)
+{
+ /* Set default defaults */
+ vd.version = yang_get_default_uint8("%s/version", VRRP_XPATH_FULL);
+ vd.priority = yang_get_default_uint8("%s/priority", VRRP_XPATH_FULL);
+ vd.advertisement_interval = yang_get_default_uint16(
+ "%s/advertisement-interval", VRRP_XPATH_FULL);
+ vd.preempt_mode = yang_get_default_bool("%s/preempt", VRRP_XPATH_FULL);
+ vd.accept_mode =
+ yang_get_default_bool("%s/accept-mode", VRRP_XPATH_FULL);
+ vd.checksum_with_ipv4_pseudoheader = yang_get_default_bool(
+ "%s/checksum-with-ipv4-pseudoheader", VRRP_XPATH_FULL);
+ vd.shutdown = VRRP_DEFAULT_SHUTDOWN;
+
+ vrrp_autoconfig_version = 3;
+ vrrp_vrouters_hash = hash_create(&vrrp_hash_key, vrrp_hash_cmp,
+ "VRRP virtual router hash");
+ vrf_init(NULL, NULL, NULL, NULL);
+}
+
+void vrrp_fini(void)
+{
+ /* Destroy all instances */
+ struct list *vrs = hash_to_list(vrrp_vrouters_hash);
+
+ struct listnode *ln;
+ struct vrrp_vrouter *vr;
+
+ for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr))
+ vrrp_vrouter_destroy(vr);
+
+ list_delete(&vrs);
+
+ hash_clean_and_free(&vrrp_vrouters_hash, NULL);
+}
diff --git a/vrrpd/vrrp.h b/vrrpd/vrrp.h
new file mode 100644
index 0000000..0ac9b1f
--- /dev/null
+++ b/vrrpd/vrrp.h
@@ -0,0 +1,565 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP global definitions and state machine.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#ifndef __VRRP_H__
+#define __VRRP_H__
+
+#include <zebra.h>
+#include <netinet/ip.h>
+
+#include "lib/memory.h"
+#include "lib/hash.h"
+#include "lib/hook.h"
+#include "lib/if.h"
+#include "lib/linklist.h"
+#include "lib/northbound.h"
+#include "lib/privs.h"
+#include "lib/stream.h"
+#include "lib/frrevent.h"
+#include "lib/vty.h"
+
+/* Global definitions */
+#define VRRP_RADV_INT 16
+#define VRRP_PRIO_MASTER 255
+#define VRRP_MCASTV4_GROUP_STR "224.0.0.18"
+#define VRRP_MCASTV6_GROUP_STR "ff02:0:0:0:0:0:0:12"
+#define VRRP_MCASTV4_GROUP 0xe0000012
+#define VRRP_MCASTV6_GROUP 0xff020000000000000000000000000012
+#define IPPROTO_VRRP 112
+
+#define VRRP_LOGPFX_VRID "[VRID %u] "
+#define VRRP_LOGPFX_FAM "[%s] "
+
+/* Default defaults */
+#define VRRP_XPATH_FULL "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group"
+#define VRRP_XPATH "./frr-vrrpd:vrrp/vrrp-group"
+#define VRRP_DEFAULT_PRIORITY 100
+#define VRRP_DEFAULT_ADVINT 100
+#define VRRP_DEFAULT_PREEMPT true
+#define VRRP_DEFAULT_ACCEPT true
+#define VRRP_DEFAULT_CHECKSUM_WITH_IPV4_PSEUDOHEADER true
+#define VRRP_DEFAULT_SHUTDOWN false
+
+/* User compatibility constant */
+#define CS2MS 10
+
+DECLARE_MGROUP(VRRPD);
+
+/* Northbound */
+extern const struct frr_yang_module_info frr_vrrpd_info;
+
+/* Configured defaults */
+struct vrrp_defaults {
+ uint8_t version;
+ uint8_t priority;
+ uint16_t advertisement_interval;
+ bool preempt_mode;
+ bool accept_mode;
+ bool checksum_with_ipv4_pseudoheader;
+ bool shutdown;
+};
+
+extern struct vrrp_defaults vd;
+
+/* threadmaster */
+extern struct event_loop *master;
+
+/* privileges */
+extern struct zebra_privs_t vrrp_privs;
+
+/* Global hash of all Virtual Routers */
+extern struct hash *vrrp_vrouters_hash;
+
+/*
+ * VRRP Router.
+ *
+ * This struct contains all state for a particular VRRP Router operating
+ * in a Virtual Router for either IPv4 or IPv6.
+ */
+struct vrrp_router {
+ /*
+ * Whether this VRRP Router is active.
+ */
+ bool is_active;
+
+ /* Whether we are the address owner */
+ bool is_owner;
+
+ /* Rx socket: Rx from parent of mvl_ifp */
+ int sock_rx;
+ /* Tx socket; Tx from mvl_ifp */
+ int sock_tx;
+
+ /* macvlan interface */
+ struct interface *mvl_ifp;
+
+ /* Source address for advertisements */
+ struct ipaddr src;
+
+ /* Socket read buffer */
+ uint8_t ibuf[IP_MAXPACKET];
+
+ /*
+ * Address family of this Virtual Router.
+ * Either AF_INET or AF_INET6.
+ */
+ int family;
+
+ /*
+ * Virtual Router this VRRP Router is participating in.
+ */
+ struct vrrp_vrouter *vr;
+
+ /*
+ * One or more IPvX addresses associated with this Virtual
+ * Router. The first address must be the "primary" address this
+ * Virtual Router is backing up in the case of IPv4. In the case of
+ * IPv6 it must be the link-local address of vr->ifp.
+ *
+ * Type: struct ipaddr *
+ */
+ struct list *addrs;
+
+ /*
+ * This flag says whether we are waiting on an interface up
+ * notification from Zebra before we send an ADVERTISEMENT.
+ */
+ bool advert_pending;
+
+ /*
+ * If this is an IPv4 VRRP router, this flag says whether we are
+ * waiting on an interface up notification from Zebra before we send
+ * gratuitous ARP packets for all our addresses. Should never be true
+ * if family == AF_INET6.
+ */
+ bool garp_pending;
+ /*
+ * If this is an IPv6 VRRP router, this flag says whether we are
+ * waiting on an interface up notification from Zebra before we send
+ * Unsolicited Neighbor Advertisement packets for all our addresses.
+ * Should never be true if family == AF_INET.
+ */
+ bool ndisc_pending;
+
+ /*
+ * Effective priority
+ * => vr->priority if we are Backup
+ * => 255 if we are Master
+ */
+ uint8_t priority;
+
+ /*
+ * Advertisement interval contained in ADVERTISEMENTS received from the
+ * Master (centiseconds)
+ */
+ uint16_t master_adver_interval;
+
+ /*
+ * Time to skew Master_Down_Interval in centiseconds. Calculated as:
+ * (((256 - priority) * Master_Adver_Interval) / 256)
+ */
+ uint16_t skew_time;
+
+ /*
+ * Time interval for Backup to declare Master down (centiseconds).
+ * Calculated as:
+ * (3 * Master_Adver_Interval) + Skew_time
+ */
+ uint16_t master_down_interval;
+
+ /*
+ * The MAC address used for the source MAC address in VRRP
+ * advertisements, advertised in ARP requests/responses, and advertised
+ * in ND Neighbor Advertisements.
+ */
+ struct ethaddr vmac;
+
+ struct {
+ int state;
+ } fsm;
+
+ struct {
+ /* Total number of advertisements sent and received */
+ uint32_t adver_tx_cnt;
+ uint32_t adver_rx_cnt;
+ /* Total number of gratuitous ARPs sent */
+ uint32_t garp_tx_cnt;
+ /* Total number of unsolicited Neighbor Advertisements sent */
+ uint32_t una_tx_cnt;
+ /* Total number of state transitions */
+ uint32_t trans_cnt;
+ } stats;
+
+ struct event *t_master_down_timer;
+ struct event *t_adver_timer;
+ struct event *t_read;
+ struct event *t_write;
+};
+
+/*
+ * VRRP Virtual Router.
+ *
+ * This struct contains all state and configuration for a given Virtual Router
+ * Identifier on a given interface, both v4 and v6.
+ *
+ * RFC5798 s. 1 states:
+ * "Within a VRRP router, the virtual routers in each of the IPv4 and IPv6
+ * address families are a domain unto themselves and do not overlap."
+ *
+ * This implementation has chosen the tuple (interface, VRID) as the key for a
+ * particular VRRP Router, and the rest of the program is designed around this
+ * assumption. Additionally, base protocol configuration parameters such as the
+ * advertisement interval and (configured) priority are shared between v4 and
+ * v6 instances. This corresponds to the choice made by other industrial
+ * implementations.
+ */
+struct vrrp_vrouter {
+ /* Whether this instance was automatically configured */
+ bool autoconf;
+
+ /* Whether this VRRP router is in administrative shutdown */
+ bool shutdown;
+
+ /* Interface */
+ struct interface *ifp;
+
+ /* Version */
+ uint8_t version;
+
+ /* Virtual Router Identifier */
+ uint32_t vrid;
+
+ /* Configured priority */
+ uint8_t priority;
+
+ /*
+ * Time interval between ADVERTISEMENTS (centiseconds). Default is 100
+ * centiseconds (1 second).
+ */
+ uint16_t advertisement_interval;
+
+ /*
+ * Controls whether a (starting or restarting) higher-priority Backup
+ * router preempts a lower-priority Master router. Values are True to
+ * allow preemption and False to prohibit preemption. Default is True.
+ */
+ bool preempt_mode;
+
+ /*
+ * Controls whether a virtual router in Master state will accept
+ * packets addressed to the address owner's IPvX address as its own if
+ * it is not the IPvX address owner. The default is False.
+ */
+ bool accept_mode;
+
+ /*
+ * Indicates whether this router computes and accepts VRRPv3 checksums
+ * without pseudoheader, for device interoperability.
+ *
+ * This option should only affect IPv4 virtual routers.
+ */
+ bool checksum_with_ipv4_pseudoheader;
+
+ struct vrrp_router *v4;
+ struct vrrp_router *v6;
+};
+
+/*
+ * Initialize VRRP global datastructures.
+ */
+void vrrp_init(void);
+
+/*
+ * Destroy all VRRP instances and gracefully shutdown.
+ *
+ * For instances in Master state, VRRP advertisements with 0 priority will be
+ * sent if possible to notify Backup routers that we are going away.
+ */
+void vrrp_fini(void);
+
+
+/* Creation and destruction ------------------------------------------------ */
+
+/*
+ * Create and register a new VRRP Virtual Router.
+ *
+ * ifp
+ * Base interface to configure VRRP on
+ *
+ * vrid
+ * Virtual Router Identifier
+ */
+struct vrrp_vrouter *vrrp_vrouter_create(struct interface *ifp, uint8_t vrid,
+ uint8_t version);
+
+/*
+ * Destroy a VRRP Virtual Router, freeing all its resources.
+ *
+ * If there are any running VRRP instances, these are stopped and destroyed.
+ */
+void vrrp_vrouter_destroy(struct vrrp_vrouter *vr);
+
+
+/* Configuration controllers ----------------------------------------------- */
+
+/*
+ * Check if a Virtual Router ought to be started, and if so, start it.
+ *
+ * vr
+ * Virtual Router to checkstart
+ */
+void vrrp_check_start(struct vrrp_vrouter *vr);
+
+/*
+ * Change the configured priority of a VRRP Virtual Router.
+ *
+ * Note that this only changes the configured priority of the Virtual Router.
+ * The currently effective priority will not be changed; to change the
+ * effective priority, the Virtual Router must be restarted by issuing a
+ * VRRP_EVENT_SHUTDOWN followed by a VRRP_EVENT_STARTUP.
+ *
+ * vr
+ * Virtual Router to change priority of
+ *
+ * priority
+ * New priority
+ */
+void vrrp_set_priority(struct vrrp_vrouter *vr, uint8_t priority);
+
+/*
+ * Set Advertisement Interval on this Virtual Router.
+ *
+ * vr
+ * Virtual Router to change priority of
+ *
+ * advertisement_interval
+ * New advertisement interval
+ */
+void vrrp_set_advertisement_interval(struct vrrp_vrouter *vr,
+ uint16_t advertisement_interval);
+
+/*
+ * Add an IPvX address to a VRRP Virtual Router.
+ *
+ * vr
+ * Virtual Router to add IPvx address to
+ *
+ * ip
+ * Address to add
+ *
+ * activate
+ * Whether to automatically start the VRRP router if this is the first IP
+ * address added.
+ *
+ * Returns:
+ * -1 on error
+ * 0 otherwise
+ */
+int vrrp_add_ip(struct vrrp_vrouter *vr, struct ipaddr *ip);
+
+/*
+ * Add an IPv4 address to a VRRP Virtual Router.
+ *
+ * vr
+ * Virtual Router to add IPv4 address to
+ *
+ * v4
+ * Address to add
+ *
+ * activate
+ * Whether to automatically start the VRRP router if this is the first IP
+ * address added.
+ *
+ * Returns:
+ * -1 on error
+ * 0 otherwise
+ */
+int vrrp_add_ipv4(struct vrrp_vrouter *vr, struct in_addr v4);
+
+/*
+ * Add an IPv6 address to a VRRP Virtual Router.
+ *
+ * vr
+ * Virtual Router to add IPv6 address to
+ *
+ * v6
+ * Address to add
+ *
+ * activate
+ * Whether to automatically start the VRRP router if this is the first IP
+ * address added.
+ *
+ * Returns:
+ * -1 on error
+ * 0 otherwise
+ */
+int vrrp_add_ipv6(struct vrrp_vrouter *vr, struct in6_addr v6);
+
+/*
+ * Remove an IP address from a VRRP Virtual Router.
+ *
+ * vr
+ * Virtual Router to remove IP address from
+ *
+ * ip
+ * Address to remove
+ *
+ * deactivate
+ * Whether to automatically stop the VRRP router if removing v4 would leave
+ * us with an empty address list. If this is not true and ip is the only IP
+ * address backed up by this virtual router, this function will not remove
+ * the address and return failure.
+ *
+ * Returns:
+ * -1 on error
+ * 0 otherwise
+ */
+int vrrp_del_ip(struct vrrp_vrouter *vr, struct ipaddr *ip);
+
+/*
+ * Remove an IPv4 address from a VRRP Virtual Router.
+ *
+ * vr
+ * Virtual Router to remove IPv4 address from
+ *
+ * v4
+ * Address to remove
+ *
+ * deactivate
+ * Whether to automatically stop the VRRP router if removing v4 would leave
+ * us with an empty address list. If this is not true and v4 is the only
+ * IPv4 address backed up by this virtual router, this function will not
+ * remove the address and return failure.
+ *
+ * Returns:
+ * -1 on error
+ * 0 otherwise
+ */
+int vrrp_del_ipv4(struct vrrp_vrouter *vr, struct in_addr v4);
+
+/*
+ * Remove an IPv6 address from a VRRP Virtual Router.
+ *
+ * vr
+ * Virtual Router to remove IPv6 address from
+ *
+ * v6
+ * Address to remove
+ *
+ * deactivate
+ * Whether to automatically stop the VRRP router if removing v5 would leave
+ * us with an empty address list. If this is not true and v4 is the only
+ * IPv6 address backed up by this virtual router, this function will not
+ * remove the address and return failure.
+ *
+ * Returns:
+ * -1 on error
+ * 0 otherwise
+ */
+int vrrp_del_ipv6(struct vrrp_vrouter *vr, struct in6_addr v6);
+
+/* State machine ----------------------------------------------------------- */
+
+#define VRRP_STATE_INITIALIZE 0
+#define VRRP_STATE_MASTER 1
+#define VRRP_STATE_BACKUP 2
+#define VRRP_EVENT_STARTUP 0
+#define VRRP_EVENT_SHUTDOWN 1
+
+extern const char *const vrrp_state_names[3];
+
+/*
+ * This hook called whenever the state of a Virtual Router changes, after the
+ * specific internal state handlers have run.
+ *
+ * Use this if you need to react to state changes to perform non-critical
+ * tasks. Critical tasks should go in the internal state change handlers.
+ */
+DECLARE_HOOK(vrrp_change_state_hook, (struct vrrp_router *r, int to), (r, to));
+
+/*
+ * Trigger a VRRP event on a given Virtual Router..
+ *
+ * vr
+ * Virtual Router to operate on
+ *
+ * event
+ * Event to kick off. All event related processing will have completed upon
+ * return of this function.
+ *
+ * Returns:
+ * < 0 if the event created an error
+ * 0 otherwise
+ */
+int vrrp_event(struct vrrp_router *r, int event);
+
+/* Autoconfig -------------------------------------------------------------- */
+
+/*
+ * Search for and automatically configure VRRP instances on interfaces.
+ *
+ * ifp
+ * Interface to autoconfig. If it is a macvlan interface and has a VRRP MAC,
+ * a VRRP instance corresponding to VMAC assigned to macvlan will be created
+ * on the parent interface and all addresses on the macvlan interface except
+ * the v6 link local will be configured as VRRP addresses. If NULL, this
+ * treatment will be applied to all existing interfaces matching the above
+ * criterion.
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ */
+int vrrp_autoconfig(void);
+
+/*
+ * Enable autoconfiguration.
+ *
+ * Calling this function will cause vrrpd to automatically configure VRRP
+ * instances on existing compatible macvlan interfaces. These instances will
+ * react to interface up/down and address add/delete events to keep themselves
+ * in sync with the available interfaces.
+ *
+ * version
+ * VRRP version to use for autoconfigured instances. Must be 2 or 3.
+ */
+void vrrp_autoconfig_on(int version);
+
+/*
+ * Disable autoconfiguration.
+ *
+ * Calling this function will delete all existing autoconfigured VRRP instances.
+ */
+void vrrp_autoconfig_off(void);
+
+/* Interface Tracking ------------------------------------------------------ */
+
+void vrrp_if_add(struct interface *ifp);
+void vrrp_if_del(struct interface *ifp);
+void vrrp_if_up(struct interface *ifp);
+void vrrp_if_down(struct interface *ifp);
+void vrrp_if_address_add(struct interface *ifp);
+void vrrp_if_address_del(struct interface *ifp);
+
+/* Other ------------------------------------------------------------------- */
+
+/*
+ * Write global level configuration to vty.
+ *
+ * vty
+ * vty to write config to
+ *
+ * Returns:
+ * # of lines written
+ */
+int vrrp_config_write_global(struct vty *vty);
+
+/*
+ * Find VRRP Virtual Router by Virtual Router ID
+ */
+struct vrrp_vrouter *vrrp_lookup(const struct interface *ifp, uint8_t vrid);
+
+#endif /* __VRRP_H__ */
diff --git a/vrrpd/vrrp_arp.c b/vrrpd/vrrp_arp.c
new file mode 100644
index 0000000..0072053
--- /dev/null
+++ b/vrrpd/vrrp_arp.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP ARP handling.
+ * Copyright (C) 2001-2017 Alexandre Cassen
+ * Portions:
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#include <zebra.h>
+
+#include <linux/if_packet.h>
+#include <net/if_arp.h>
+#include <netinet/if_ether.h>
+
+#include "lib/if.h"
+#include "lib/linklist.h"
+#include "lib/log.h"
+#include "lib/memory.h"
+#include "lib/prefix.h"
+
+#include "vrrp.h"
+#include "vrrp_arp.h"
+#include "vrrp_debug.h"
+
+#define VRRP_LOGPFX "[ARP] "
+
+/*
+ * The size of the garp packet buffer should be the large enough to hold the
+ * largest arp packet to be sent + the size of the link layer header for the
+ * corresponding protocol. In this case we hardcode for Ethernet.
+ */
+#define GARP_BUFFER_SIZE \
+ sizeof(struct ether_header) + sizeof(struct arphdr) + 2 * ETH_ALEN \
+ + 2 * sizeof(struct in_addr)
+
+/* static vars */
+static int garp_fd = -1;
+
+/* Send the gratuitous ARP message */
+static ssize_t vrrp_send_garp(struct interface *ifp, uint8_t *buf,
+ ssize_t pack_len)
+{
+ struct sockaddr_ll sll;
+ ssize_t len;
+
+ /* Build the dst device */
+ memset(&sll, 0, sizeof(sll));
+ sll.sll_family = AF_PACKET;
+ sll.sll_protocol = ETH_P_ARP;
+ sll.sll_ifindex = (int)ifp->ifindex;
+ sll.sll_halen = ifp->hw_addr_len;
+ memset(sll.sll_addr, 0xFF, ETH_ALEN);
+
+ /* Send packet */
+ len = sendto(garp_fd, buf, pack_len, 0, (struct sockaddr *)&sll,
+ sizeof(sll));
+
+ return len;
+}
+
+/* Build a gratuitous ARP message over a specific interface */
+static ssize_t vrrp_build_garp(uint8_t *buf, struct interface *ifp,
+ struct in_addr *v4)
+{
+ uint8_t *arp_ptr;
+
+ if (ifp->hw_addr_len == 0)
+ return -1;
+
+ /* Build Ethernet header */
+ struct ether_header *eth = (struct ether_header *)buf;
+
+ memset(eth->ether_dhost, 0xFF, ETH_ALEN);
+ memcpy(eth->ether_shost, ifp->hw_addr, ETH_ALEN);
+ eth->ether_type = htons(ETHERTYPE_ARP);
+
+ /* Build ARP payload */
+ struct arphdr *arph = (struct arphdr *)(buf + ETHER_HDR_LEN);
+
+ arph->ar_hrd = htons(HWTYPE_ETHER);
+ arph->ar_pro = htons(ETHERTYPE_IP);
+ arph->ar_hln = ifp->hw_addr_len;
+ arph->ar_pln = sizeof(struct in_addr);
+ arph->ar_op = htons(ARPOP_REQUEST);
+ arp_ptr = (uint8_t *)(arph + 1);
+ /* Source MAC: us */
+ memcpy(arp_ptr, ifp->hw_addr, ifp->hw_addr_len);
+ arp_ptr += ifp->hw_addr_len;
+ /* Source IP: us */
+ memcpy(arp_ptr, v4, sizeof(struct in_addr));
+ arp_ptr += sizeof(struct in_addr);
+ /* Dest MAC: broadcast */
+ memset(arp_ptr, 0xFF, ETH_ALEN);
+ arp_ptr += ifp->hw_addr_len;
+ /* Dest IP: us */
+ memcpy(arp_ptr, v4, sizeof(struct in_addr));
+ arp_ptr += sizeof(struct in_addr);
+
+ return arp_ptr - buf;
+}
+
+void vrrp_garp_send(struct vrrp_router *r, struct in_addr *v4)
+{
+ struct interface *ifp = r->mvl_ifp;
+ uint8_t garpbuf[GARP_BUFFER_SIZE];
+ ssize_t garpbuf_len;
+ ssize_t sent_len;
+ char astr[INET_ADDRSTRLEN];
+
+ /* If the interface doesn't support ARP, don't try sending */
+ if (ifp->flags & IFF_NOARP) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Unable to send gratuitous ARP on %s; has IFF_NOARP",
+ r->vr->vrid, family2str(r->family), ifp->name);
+ return;
+ }
+
+ /* Build garp */
+ garpbuf_len = vrrp_build_garp(garpbuf, ifp, v4);
+
+ if (garpbuf_len < 0) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Unable to send gratuitous ARP on %s; MAC address unknown",
+ r->vr->vrid, family2str(r->family), ifp->name);
+ return;
+ };
+
+ /* Send garp */
+ inet_ntop(AF_INET, v4, astr, sizeof(astr));
+
+ DEBUGD(&vrrp_dbg_arp,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Sending gratuitous ARP on %s for %s",
+ r->vr->vrid, family2str(r->family), ifp->name, astr);
+ if (DEBUG_MODE_CHECK(&vrrp_dbg_arp, DEBUG_MODE_ALL))
+ zlog_hexdump(garpbuf, garpbuf_len);
+
+ sent_len = vrrp_send_garp(ifp, garpbuf, garpbuf_len);
+
+ if (sent_len < 0)
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Error sending gratuitous ARP on %s for %s",
+ r->vr->vrid, family2str(r->family), ifp->name, astr);
+ else
+ ++r->stats.garp_tx_cnt;
+}
+
+void vrrp_garp_send_all(struct vrrp_router *r)
+{
+ assert(r->family == AF_INET);
+
+ struct interface *ifp = r->mvl_ifp;
+
+ /* If the interface doesn't support ARP, don't try sending */
+ if (ifp->flags & IFF_NOARP) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Unable to send gratuitous ARP on %s; has IFF_NOARP",
+ r->vr->vrid, family2str(r->family), ifp->name);
+ return;
+ }
+
+ struct listnode *ln;
+ struct ipaddr *ip;
+
+ for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, ip))
+ vrrp_garp_send(r, &ip->ipaddr_v4);
+}
+
+
+void vrrp_garp_init(void)
+{
+ /* Create the socket descriptor */
+ /* FIXME: why ETH_P_RARP? */
+ errno = 0;
+ frr_with_privs(&vrrp_privs) {
+ garp_fd = socket(PF_PACKET, SOCK_RAW | SOCK_CLOEXEC,
+ htons(ETH_P_RARP));
+ }
+
+ if (garp_fd > 0) {
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX "Initialized gratuitous ARP socket");
+ DEBUGD(&vrrp_dbg_arp,
+ VRRP_LOGPFX "Initialized gratuitous ARP subsystem");
+ } else {
+ zlog_err(VRRP_LOGPFX
+ "Error initializing gratuitous ARP subsystem");
+ }
+}
+
+void vrrp_garp_fini(void)
+{
+ close(garp_fd);
+ garp_fd = -1;
+
+ DEBUGD(&vrrp_dbg_arp,
+ VRRP_LOGPFX "Deinitialized gratuitous ARP subsystem");
+}
+
+bool vrrp_garp_is_init(void)
+{
+ return garp_fd > 0;
+}
diff --git a/vrrpd/vrrp_arp.h b/vrrpd/vrrp_arp.h
new file mode 100644
index 0000000..46aa128
--- /dev/null
+++ b/vrrpd/vrrp_arp.h
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP ARP handling.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#ifndef __VRRP_ARP_H__
+#define __VRRP_ARP_H__
+
+#include <zebra.h>
+
+#include "vrrp.h"
+
+/* FIXME: Use the kernel define for this */
+#define HWTYPE_ETHER 1
+
+extern void vrrp_garp_init(void);
+extern void vrrp_garp_fini(void);
+extern bool vrrp_garp_is_init(void);
+extern void vrrp_garp_send(struct vrrp_router *vr, struct in_addr *v4);
+extern void vrrp_garp_send_all(struct vrrp_router *vr);
+
+#endif /* __VRRP_ARP_H__ */
diff --git a/vrrpd/vrrp_debug.c b/vrrpd/vrrp_debug.c
new file mode 100644
index 0000000..a772b3b
--- /dev/null
+++ b/vrrpd/vrrp_debug.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP debugging.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#include <zebra.h>
+
+#include "lib/command.h"
+#include "lib/debug.h"
+#include "lib/vector.h"
+
+#include "vrrp_debug.h"
+
+/* clang-format off */
+struct debug vrrp_dbg_arp = {0, "VRRP ARP"};
+struct debug vrrp_dbg_auto = {0, "VRRP autoconfiguration events"};
+struct debug vrrp_dbg_ndisc = {0, "VRRP Neighbor Discovery"};
+struct debug vrrp_dbg_pkt = {0, "VRRP packets"};
+struct debug vrrp_dbg_proto = {0, "VRRP protocol events"};
+struct debug vrrp_dbg_sock = {0, "VRRP sockets"};
+struct debug vrrp_dbg_zebra = {0, "VRRP Zebra events"};
+
+struct debug *vrrp_debugs[] = {
+ &vrrp_dbg_arp,
+ &vrrp_dbg_auto,
+ &vrrp_dbg_ndisc,
+ &vrrp_dbg_pkt,
+ &vrrp_dbg_proto,
+ &vrrp_dbg_sock,
+ &vrrp_dbg_zebra
+};
+
+const char *vrrp_debugs_conflines[] = {
+ "debug vrrp arp",
+ "debug vrrp autoconfigure",
+ "debug vrrp ndisc",
+ "debug vrrp packets",
+ "debug vrrp protocol",
+ "debug vrrp sockets",
+ "debug vrrp zebra",
+};
+/* clang-format on */
+
+/*
+ * Set or unset flags on all debugs for vrrpd.
+ *
+ * flags
+ * The flags to set
+ *
+ * set
+ * Whether to set or unset the specified flags
+ */
+static void vrrp_debug_set_all(uint32_t flags, bool set)
+{
+ for (unsigned int i = 0; i < array_size(vrrp_debugs); i++) {
+ DEBUG_FLAGS_SET(vrrp_debugs[i], flags, set);
+
+ /* if all modes have been turned off, don't preserve options */
+ if (!DEBUG_MODE_CHECK(vrrp_debugs[i], DEBUG_MODE_ALL))
+ DEBUG_CLEAR(vrrp_debugs[i]);
+ }
+}
+
+static int vrrp_debug_config_write_helper(struct vty *vty, bool config)
+{
+ uint32_t mode = DEBUG_MODE_ALL;
+
+ if (config)
+ mode = DEBUG_MODE_CONF;
+
+ for (unsigned int i = 0; i < array_size(vrrp_debugs); i++)
+ if (DEBUG_MODE_CHECK(vrrp_debugs[i], mode))
+ vty_out(vty, "%s\n", vrrp_debugs_conflines[i]);
+
+ return 0;
+}
+
+int vrrp_config_write_debug(struct vty *vty)
+{
+ return vrrp_debug_config_write_helper(vty, true);
+}
+
+int vrrp_debug_status_write(struct vty *vty)
+{
+ return vrrp_debug_config_write_helper(vty, false);
+}
+
+void vrrp_debug_set(struct interface *ifp, uint8_t vrid, int vtynode,
+ bool onoff, bool proto, bool autoconf, bool pkt, bool sock,
+ bool ndisc, bool arp, bool zebra)
+{
+ uint32_t mode = DEBUG_NODE2MODE(vtynode);
+
+ if (proto)
+ DEBUG_MODE_SET(&vrrp_dbg_proto, mode, onoff);
+ if (autoconf)
+ DEBUG_MODE_SET(&vrrp_dbg_auto, mode, onoff);
+ if (pkt)
+ DEBUG_MODE_SET(&vrrp_dbg_pkt, mode, onoff);
+ if (sock)
+ DEBUG_MODE_SET(&vrrp_dbg_sock, mode, onoff);
+ if (ndisc)
+ DEBUG_MODE_SET(&vrrp_dbg_ndisc, mode, onoff);
+ if (arp)
+ DEBUG_MODE_SET(&vrrp_dbg_arp, mode, onoff);
+ if (zebra)
+ DEBUG_MODE_SET(&vrrp_dbg_zebra, mode, onoff);
+}
+
+/* ------------------------------------------------------------------------- */
+
+struct debug_callbacks vrrp_dbg_cbs = {.debug_set_all = vrrp_debug_set_all};
+
+void vrrp_debug_init(void)
+{
+ debug_init(&vrrp_dbg_cbs);
+}
diff --git a/vrrpd/vrrp_debug.h b/vrrpd/vrrp_debug.h
new file mode 100644
index 0000000..c1421eb
--- /dev/null
+++ b/vrrpd/vrrp_debug.h
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP debugging.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#ifndef __VRRP_DEBUG_H__
+#define __VRRP_DEBUG_H__
+
+#include <zebra.h>
+
+#include "lib/debug.h"
+
+/* VRRP debugging records */
+extern struct debug vrrp_dbg_arp;
+extern struct debug vrrp_dbg_auto;
+extern struct debug vrrp_dbg_ndisc;
+extern struct debug vrrp_dbg_pkt;
+extern struct debug vrrp_dbg_proto;
+extern struct debug vrrp_dbg_sock;
+extern struct debug vrrp_dbg_zebra;
+
+/*
+ * Initialize VRRP debugging.
+ *
+ * Installs VTY commands and registers callbacks.
+ */
+void vrrp_debug_init(void);
+
+/*
+ * Print VRRP debugging configuration.
+ *
+ * vty
+ * VTY to print debugging configuration to.
+ */
+int vrrp_config_write_debug(struct vty *vty);
+
+/*
+ * Print VRRP debugging configuration, human readable form.
+ *
+ * vty
+ * VTY to print debugging configuration to.
+ */
+int vrrp_debug_status_write(struct vty *vty);
+
+/*
+ * Set debugging status.
+ *
+ * ifp
+ * Interface to set status on
+ *
+ * vrid
+ * VRID of instance to set status on
+ *
+ * vtynode
+ * vty->node
+ *
+ * onoff
+ * Whether to turn the specified debugs on or off
+ *
+ * proto
+ * Turn protocol debugging on or off
+ *
+ * autoconf
+ * Turn autoconfiguration debugging on or off
+ *
+ * pkt
+ * Turn packet debugging on or off
+ */
+void vrrp_debug_set(struct interface *ifp, uint8_t vrid, int vtynode,
+ bool onoff, bool proto, bool autoconf, bool pkt, bool sock,
+ bool ndisc, bool arp, bool zebra);
+
+#endif /* __VRRP_DEBUG_H__ */
diff --git a/vrrpd/vrrp_main.c b/vrrpd/vrrp_main.c
new file mode 100644
index 0000000..5245c74
--- /dev/null
+++ b/vrrpd/vrrp_main.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP entry point.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#include <zebra.h>
+
+#include <getopt.h>
+
+#include <lib/version.h>
+
+#include "lib/command.h"
+#include "lib/filter.h"
+#include "lib/if.h"
+#include "lib/libfrr.h"
+#include "lib/log.h"
+#include "lib/memory.h"
+#include "lib/nexthop.h"
+#include "lib/privs.h"
+#include "lib/sigevent.h"
+#include "lib/frrevent.h"
+#include "lib/vrf.h"
+#include "lib/vty.h"
+
+#include "vrrp.h"
+#include "vrrp_debug.h"
+#include "vrrp_vty.h"
+#include "vrrp_zebra.h"
+
+DEFINE_MGROUP(VRRPD, "vrrpd");
+
+char backup_config_file[256];
+
+zebra_capabilities_t _caps_p[] = {
+ ZCAP_NET_RAW,
+};
+
+struct zebra_privs_t vrrp_privs = {
+#if defined(FRR_USER) && defined(FRR_GROUP)
+ .user = FRR_USER,
+ .group = FRR_GROUP,
+#endif
+#if defined(VTY_GROUP)
+ .vty_group = VTY_GROUP,
+#endif
+ .caps_p = _caps_p,
+ .cap_num_p = array_size(_caps_p),
+ .cap_num_i = 0};
+
+struct option longopts[] = { {0} };
+
+/* Master of threads. */
+struct event_loop *master;
+
+static struct frr_daemon_info vrrpd_di;
+
+/* SIGHUP handler. */
+static void sighup(void)
+{
+ zlog_info("SIGHUP received");
+
+ vty_read_config(NULL, vrrpd_di.config_file, config_default);
+}
+
+/* SIGINT / SIGTERM handler. */
+static void __attribute__((noreturn)) sigint(void)
+{
+ zlog_notice("Terminating on signal");
+
+ vrrp_fini();
+
+ frr_fini();
+
+ exit(0);
+}
+
+/* SIGUSR1 handler. */
+static void sigusr1(void)
+{
+ zlog_rotate();
+}
+
+struct frr_signal_t vrrp_signals[] = {
+ {
+ .signal = SIGHUP,
+ .handler = &sighup,
+ },
+ {
+ .signal = SIGUSR1,
+ .handler = &sigusr1,
+ },
+ {
+ .signal = SIGINT,
+ .handler = &sigint,
+ },
+ {
+ .signal = SIGTERM,
+ .handler = &sigint,
+ },
+};
+
+static const struct frr_yang_module_info *const vrrp_yang_modules[] = {
+ &frr_filter_info,
+ &frr_vrf_info,
+ &frr_interface_info,
+ &frr_vrrpd_info,
+};
+
+#define VRRP_VTY_PORT 2619
+
+FRR_DAEMON_INFO(vrrpd, VRRP, .vty_port = VRRP_VTY_PORT,
+ .proghelp = "Virtual Router Redundancy Protocol",
+ .signals = vrrp_signals,
+ .n_signals = array_size(vrrp_signals),
+ .privs = &vrrp_privs,
+ .yang_modules = vrrp_yang_modules,
+ .n_yang_modules = array_size(vrrp_yang_modules),
+);
+
+int main(int argc, char **argv, char **envp)
+{
+ frr_preinit(&vrrpd_di, argc, argv);
+ frr_opt_add("", longopts, "");
+
+ while (1) {
+ int opt;
+
+ opt = frr_getopt(argc, argv, NULL);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 0:
+ break;
+ default:
+ frr_help_exit(1);
+ }
+ }
+
+ master = frr_init();
+
+ access_list_init();
+ vrrp_debug_init();
+ vrrp_zebra_init();
+ vrrp_vty_init();
+ vrrp_init();
+
+ snprintf(backup_config_file, sizeof(backup_config_file),
+ "%s/vrrpd.conf", frr_sysconfdir);
+ vrrpd_di.backup_config_file = backup_config_file;
+
+ frr_config_fork();
+ frr_run(master);
+
+ /* Not reached. */
+ return 0;
+}
diff --git a/vrrpd/vrrp_ndisc.c b/vrrpd/vrrp_ndisc.c
new file mode 100644
index 0000000..038d293
--- /dev/null
+++ b/vrrpd/vrrp_ndisc.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP Neighbor Discovery.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * Portions:
+ * Copyright (C) 2001-2017 Alexandre Cassen
+ */
+#include <zebra.h>
+
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+
+#include "lib/checksum.h"
+#include "lib/if.h"
+#include "lib/ipaddr.h"
+#include "lib/log.h"
+
+#include "vrrp_debug.h"
+#include "vrrp_ndisc.h"
+
+#define VRRP_LOGPFX "[NDISC] "
+
+#define VRRP_NDISC_HOPLIMIT 255
+#define VRRP_NDISC_SIZE \
+ ETHER_HDR_LEN + sizeof(struct ip6_hdr) \
+ + sizeof(struct nd_neighbor_advert) \
+ + sizeof(struct nd_opt_hdr) + ETH_ALEN
+
+/* static vars */
+static int ndisc_fd = -1;
+
+/*
+ * Build an unsolicited Neighbour Advertisement.
+ *
+ * ifp
+ * Interface to send Neighbor Advertisement on
+ *
+ * ip
+ * IP address to send Neighbor Advertisement for
+ *
+ * buf
+ * Buffer to fill with IPv6 Neighbor Advertisement message. Includes
+ * Ethernet header.
+ *
+ * bufsiz
+ * Size of buf.
+ *
+ * Returns;
+ * -1 if bufsiz is too small
+ * 0 otherwise
+ */
+static int vrrp_ndisc_una_build(struct interface *ifp, struct ipaddr *ip,
+ uint8_t *buf, size_t bufsiz)
+{
+ if (bufsiz < VRRP_NDISC_SIZE)
+ return -1;
+
+ memset(buf, 0x00, bufsiz);
+
+ struct ether_header *eth = (struct ether_header *)buf;
+ struct ip6_hdr *ip6h = (struct ip6_hdr *)((char *)eth + ETHER_HDR_LEN);
+ struct nd_neighbor_advert *ndh =
+ (struct nd_neighbor_advert *)((char *)ip6h
+ + sizeof(struct ip6_hdr));
+ struct icmp6_hdr *icmp6h = &ndh->nd_na_hdr;
+ struct nd_opt_hdr *nd_opt_h =
+ (struct nd_opt_hdr *)((char *)ndh
+ + sizeof(struct nd_neighbor_advert));
+ char *nd_opt_lladdr = ((char *)nd_opt_h + sizeof(struct nd_opt_hdr));
+ char *lladdr = (char *)ifp->hw_addr;
+
+ /*
+ * An IPv6 packet with a multicast destination address DST, consisting
+ * of the sixteen octets DST[1] through DST[16], is transmitted to the
+ * Ethernet multicast address whose first two octets are the value 3333
+ * hexadecimal and whose last four octets are the last four octets of
+ * DST.
+ * - RFC2464.7
+ *
+ * In this case we are sending to the all nodes multicast address, so
+ * the last four octets are 0x00 0x00 0x00 0x01.
+ */
+ memset(eth->ether_dhost, 0, ETH_ALEN);
+ eth->ether_dhost[0] = 0x33;
+ eth->ether_dhost[1] = 0x33;
+ eth->ether_dhost[5] = 1;
+
+ /* Set source Ethernet address to interface link layer address */
+ memcpy(eth->ether_shost, lladdr, ETH_ALEN);
+ eth->ether_type = htons(ETHERTYPE_IPV6);
+
+ /* IPv6 Header */
+ ip6h->ip6_vfc = 6 << 4;
+ ip6h->ip6_plen = htons(sizeof(struct nd_neighbor_advert)
+ + sizeof(struct nd_opt_hdr) + ETH_ALEN);
+ ip6h->ip6_nxt = IPPROTO_ICMPV6;
+ ip6h->ip6_hlim = VRRP_NDISC_HOPLIMIT;
+ memcpy(&ip6h->ip6_src, &ip->ipaddr_v6, sizeof(struct in6_addr));
+ /* All nodes multicast address */
+ ip6h->ip6_dst.s6_addr[0] = 0xFF;
+ ip6h->ip6_dst.s6_addr[1] = 0x02;
+ ip6h->ip6_dst.s6_addr[15] = 0x01;
+
+ /* ICMPv6 Header */
+ ndh->nd_na_type = ND_NEIGHBOR_ADVERT;
+ ndh->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
+ ndh->nd_na_flags_reserved |= ND_NA_FLAG_OVERRIDE;
+ memcpy(&ndh->nd_na_target, &ip->ipaddr_v6, sizeof(struct in6_addr));
+
+ /* NDISC Option header */
+ nd_opt_h->nd_opt_type = ND_OPT_TARGET_LINKADDR;
+ nd_opt_h->nd_opt_len = 1;
+ memcpy(nd_opt_lladdr, lladdr, ETH_ALEN);
+
+ /* Compute checksum */
+ uint32_t len = sizeof(struct nd_neighbor_advert)
+ + sizeof(struct nd_opt_hdr) + ETH_ALEN;
+ struct ipv6_ph ph = {};
+
+ ph.src = ip6h->ip6_src;
+ ph.dst = ip6h->ip6_dst;
+ ph.ulpl = htonl(len);
+ ph.next_hdr = IPPROTO_ICMPV6;
+
+ /* Suppress static analysis warnings about accessing icmp6 oob */
+ void *offset = icmp6h;
+ icmp6h->icmp6_cksum = in_cksum_with_ph6(&ph, offset, len);
+
+ return 0;
+}
+
+int vrrp_ndisc_una_send(struct vrrp_router *r, struct ipaddr *ip)
+{
+ assert(r->family == AF_INET6);
+
+ int ret = 0;
+ struct interface *ifp = r->mvl_ifp;
+ uint8_t buf[VRRP_NDISC_SIZE];
+
+ ret = vrrp_ndisc_una_build(ifp, ip, buf, sizeof(buf));
+
+ if (ret == -1)
+ return ret;
+
+ struct sockaddr_ll sll;
+ ssize_t len;
+
+ /* Build the dst device */
+ memset(&sll, 0, sizeof(sll));
+ sll.sll_family = AF_PACKET;
+ memcpy(sll.sll_addr, ifp->hw_addr, ETH_ALEN);
+ sll.sll_halen = ETH_ALEN;
+ sll.sll_ifindex = (int)ifp->ifindex;
+
+ char ipbuf[INET6_ADDRSTRLEN];
+
+ ipaddr2str(ip, ipbuf, sizeof(ipbuf));
+
+ DEBUGD(&vrrp_dbg_ndisc,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Sending unsolicited Neighbor Advertisement on %s for %s",
+ r->vr->vrid, family2str(r->family), ifp->name, ipbuf);
+
+ if (DEBUG_MODE_CHECK(&vrrp_dbg_ndisc, DEBUG_MODE_ALL)
+ && DEBUG_MODE_CHECK(&vrrp_dbg_pkt, DEBUG_MODE_ALL))
+ zlog_hexdump(buf, VRRP_NDISC_SIZE);
+
+ len = sendto(ndisc_fd, buf, VRRP_NDISC_SIZE, 0, (struct sockaddr *)&sll,
+ sizeof(sll));
+
+ if (len < 0) {
+ zlog_err(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Error sending unsolicited Neighbor Advertisement on %s for %s",
+ r->vr->vrid, family2str(r->family), ifp->name, ipbuf);
+ ret = -1;
+ } else {
+ ++r->stats.una_tx_cnt;
+ }
+
+ return ret;
+}
+
+int vrrp_ndisc_una_send_all(struct vrrp_router *r)
+{
+ assert(r->family == AF_INET6);
+
+ struct listnode *ln;
+ struct ipaddr *ip;
+
+ for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, ip))
+ vrrp_ndisc_una_send(r, ip);
+
+ return 0;
+}
+
+void vrrp_ndisc_init(void)
+{
+ frr_with_privs(&vrrp_privs) {
+ ndisc_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6));
+ }
+
+ if (ndisc_fd > 0) {
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX "Initialized Neighbor Discovery socket");
+ DEBUGD(&vrrp_dbg_ndisc,
+ VRRP_LOGPFX "Initialized Neighbor Discovery subsystem");
+ } else {
+ zlog_err(VRRP_LOGPFX
+ "Error initializing Neighbor Discovery socket");
+ }
+}
+
+void vrrp_ndisc_fini(void)
+{
+ close(ndisc_fd);
+ ndisc_fd = -1;
+
+ DEBUGD(&vrrp_dbg_ndisc,
+ VRRP_LOGPFX "Deinitialized Neighbor Discovery subsystem");
+}
+
+bool vrrp_ndisc_is_init(void)
+{
+ return ndisc_fd > 0;
+}
diff --git a/vrrpd/vrrp_ndisc.h b/vrrpd/vrrp_ndisc.h
new file mode 100644
index 0000000..15fa719
--- /dev/null
+++ b/vrrpd/vrrp_ndisc.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP Neighbor Discovery.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#ifndef __VRRP_NDISC_H__
+#define __VRRP_NDISC_H__
+
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+
+#include "vrrp.h"
+
+/*
+ * Initialize VRRP neighbor discovery.
+ */
+extern void vrrp_ndisc_init(void);
+
+/*
+ * Check whether VRRP Neighbor Discovery is initialized.
+ *
+ * Returns:
+ * True if initialized, false otherwise
+ */
+extern bool vrrp_ndisc_is_init(void);
+
+/*
+ * Finish VRRP Neighbor Discovery.
+ */
+extern void vrrp_ndisc_fini(void);
+
+/*
+ * Send VRRP Neighbor Advertisement.
+ *
+ * ifp
+ * Interface to transmit on
+ *
+ * ip
+ * IPv6 address to send Neighbor Advertisement for
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ */
+extern int vrrp_ndisc_una_send(struct vrrp_router *r, struct ipaddr *ip);
+
+/*
+ * Send VRRP Neighbor Advertisements for all virtual IPs.
+ *
+ * r
+ * Virtual Router to send NA's for
+ *
+ * Returns:
+ * -1 on failure
+ * 0 otherwise
+ */
+extern int vrrp_ndisc_una_send_all(struct vrrp_router *r);
+
+#endif /* __VRRP_NDISC_H__ */
diff --git a/vrrpd/vrrp_northbound.c b/vrrpd/vrrp_northbound.c
new file mode 100644
index 0000000..1f8da4c
--- /dev/null
+++ b/vrrpd/vrrp_northbound.c
@@ -0,0 +1,814 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP northbound bindings.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+
+#include <zebra.h>
+
+#include "if.h"
+#include "log.h"
+#include "prefix.h"
+#include "table.h"
+#include "command.h"
+#include "northbound.h"
+#include "libfrr.h"
+#include "vrrp.h"
+#include "vrrp_vty.h"
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group
+ */
+static int lib_interface_vrrp_vrrp_group_create(struct nb_cb_create_args *args)
+{
+ struct interface *ifp;
+ uint8_t vrid;
+ uint8_t version = 3;
+ struct vrrp_vrouter *vr;
+
+ vrid = yang_dnode_get_uint8(args->dnode, "./virtual-router-id");
+ version = yang_dnode_get_enum(args->dnode, "./version");
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ ifp = nb_running_get_entry(args->dnode, NULL, false);
+ if (ifp) {
+ vr = vrrp_lookup(ifp, vrid);
+ if (vr && vr->autoconf) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "Virtual Router with ID %d already exists on interface '%s'; created by VRRP autoconfiguration",
+ vrid, ifp->name);
+ return NB_ERR_VALIDATION;
+ }
+ }
+ return NB_OK;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ return NB_OK;
+ case NB_EV_APPLY:
+ break;
+ }
+
+ ifp = nb_running_get_entry(args->dnode, NULL, true);
+ vr = vrrp_vrouter_create(ifp, vrid, version);
+ nb_running_set_entry(args->dnode, vr);
+
+ return NB_OK;
+}
+
+static int
+lib_interface_vrrp_vrrp_group_destroy(struct nb_cb_destroy_args *args)
+{
+ struct vrrp_vrouter *vr;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ vr = nb_running_unset_entry(args->dnode);
+ vrrp_vrouter_destroy(vr);
+
+ return NB_OK;
+}
+
+static const void *
+lib_interface_vrrp_vrrp_group_get_next(struct nb_cb_get_next_args *args)
+{
+ struct list *l = hash_to_list(vrrp_vrouters_hash);
+ struct listnode *ln;
+ const struct vrrp_vrouter *curr;
+ const struct interface *ifp = args->parent_list_entry;
+
+ /*
+ * If list_entry is null, we return the first vrrp instance with a
+ * matching interface
+ */
+ bool nextone = args->list_entry ? false : true;
+
+ for (ALL_LIST_ELEMENTS_RO(l, ln, curr)) {
+ if (curr == args->list_entry) {
+ nextone = true;
+ continue;
+ }
+
+ if (nextone && curr->ifp == ifp)
+ goto done;
+ }
+
+ curr = NULL;
+
+done:
+ list_delete(&l);
+ return curr;
+}
+
+static int
+lib_interface_vrrp_vrrp_group_get_keys(struct nb_cb_get_keys_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ args->keys->num = 1;
+ snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%u",
+ vr->vrid);
+
+ return NB_OK;
+}
+
+static const void *
+lib_interface_vrrp_vrrp_group_lookup_entry(struct nb_cb_lookup_entry_args *args)
+{
+ uint32_t vrid = strtoul(args->keys->key[0], NULL, 10);
+ const struct interface *ifp = args->parent_list_entry;
+
+ return vrrp_lookup(ifp, vrid);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/version
+ */
+static int
+lib_interface_vrrp_vrrp_group_version_modify(struct nb_cb_modify_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ struct vrrp_vrouter *vr;
+ uint8_t version;
+
+ vr = nb_running_get_entry(args->dnode, NULL, true);
+ vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN);
+ vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN);
+ version = yang_dnode_get_enum(args->dnode, NULL);
+ vr->version = version;
+
+ vrrp_check_start(vr);
+
+ return NB_OK;
+}
+
+/*
+ * Helper function for address list OP_MODIFY callbacks.
+ */
+static void vrrp_yang_add_del_virtual_address(const struct lyd_node *dnode,
+ bool add)
+{
+ struct vrrp_vrouter *vr;
+ struct ipaddr ip;
+
+ vr = nb_running_get_entry(dnode, NULL, true);
+ yang_dnode_get_ip(&ip, dnode, NULL);
+ if (add)
+ vrrp_add_ip(vr, &ip);
+ else
+ vrrp_del_ip(vr, &ip);
+
+ vrrp_check_start(vr);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/virtual-address
+ */
+static int lib_interface_vrrp_vrrp_group_v4_virtual_address_create(
+ struct nb_cb_create_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ vrrp_yang_add_del_virtual_address(args->dnode, true);
+
+ return NB_OK;
+}
+
+static int lib_interface_vrrp_vrrp_group_v4_virtual_address_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ vrrp_yang_add_del_virtual_address(args->dnode, false);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/current-priority
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v4_current_priority_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint8(args->xpath, vr->v4->priority);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/vrrp-interface
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v4_vrrp_interface_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ struct yang_data *val = NULL;
+
+ if (vr->v4->mvl_ifp)
+ val = yang_data_new_string(args->xpath, vr->v4->mvl_ifp->name);
+
+ return val;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/source-address
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v4_source_address_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+ struct yang_data *val = NULL;
+
+ if (!ipaddr_is_zero(&vr->v4->src))
+ val = yang_data_new_ip(args->xpath, &vr->v4->src);
+
+ return val;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/state
+ */
+static struct yang_data *lib_interface_vrrp_vrrp_group_v4_state_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_enum(args->xpath, vr->v4->fsm.state);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/master-advertisement-interval
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v4_master_advertisement_interval_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint16(args->xpath, vr->v4->master_adver_interval);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/skew-time
+ */
+static struct yang_data *lib_interface_vrrp_vrrp_group_v4_skew_time_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint16(args->xpath, vr->v4->skew_time);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/counter/state-transition
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v4_counter_state_transition_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, vr->v4->stats.trans_cnt);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/counter/tx/advertisement
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v4_counter_tx_advertisement_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, vr->v4->stats.adver_tx_cnt);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/counter/tx/gratuitous-arp
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v4_counter_tx_gratuitous_arp_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, vr->v4->stats.garp_tx_cnt);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/counter/rx/advertisement
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v4_counter_rx_advertisement_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, vr->v4->stats.adver_rx_cnt);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/virtual-address
+ */
+static int lib_interface_vrrp_vrrp_group_v6_virtual_address_create(
+ struct nb_cb_create_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ vrrp_yang_add_del_virtual_address(args->dnode, true);
+
+ return NB_OK;
+}
+
+static int lib_interface_vrrp_vrrp_group_v6_virtual_address_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ vrrp_yang_add_del_virtual_address(args->dnode, false);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/current-priority
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v6_current_priority_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint8(args->xpath, vr->v6->priority);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/vrrp-interface
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v6_vrrp_interface_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+ struct yang_data *val = NULL;
+
+ if (vr->v6->mvl_ifp)
+ val = yang_data_new_string(args->xpath, vr->v6->mvl_ifp->name);
+
+ return val;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/source-address
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v6_source_address_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+ struct yang_data *val = NULL;
+
+ if (!ipaddr_is_zero(&vr->v6->src))
+ val = yang_data_new_ip(args->xpath, &vr->v6->src);
+
+ return val;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/state
+ */
+static struct yang_data *lib_interface_vrrp_vrrp_group_v6_state_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_enum(args->xpath, vr->v6->fsm.state);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/master-advertisement-interval
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v6_master_advertisement_interval_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint16(args->xpath, vr->v6->master_adver_interval);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/skew-time
+ */
+static struct yang_data *lib_interface_vrrp_vrrp_group_v6_skew_time_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint16(args->xpath, vr->v6->skew_time);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/state-transition
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v6_counter_state_transition_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, vr->v6->stats.trans_cnt);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/tx/advertisement
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v6_counter_tx_advertisement_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct vrrp_vrouter *vr = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, vr->v6->stats.adver_tx_cnt);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/tx/neighbor-advertisement
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v6_counter_tx_neighbor_advertisement_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ /* TODO: implement me. */
+ return NULL;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/rx/advertisement
+ */
+static struct yang_data *
+lib_interface_vrrp_vrrp_group_v6_counter_rx_advertisement_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ /* TODO: implement me. */
+ return NULL;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/priority
+ */
+static int
+lib_interface_vrrp_vrrp_group_priority_modify(struct nb_cb_modify_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ struct vrrp_vrouter *vr;
+ uint8_t priority;
+
+ vr = nb_running_get_entry(args->dnode, NULL, true);
+ priority = yang_dnode_get_uint8(args->dnode, NULL);
+ vrrp_set_priority(vr, priority);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/preempt
+ */
+static int
+lib_interface_vrrp_vrrp_group_preempt_modify(struct nb_cb_modify_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ struct vrrp_vrouter *vr;
+ bool preempt;
+
+ vr = nb_running_get_entry(args->dnode, NULL, true);
+ preempt = yang_dnode_get_bool(args->dnode, NULL);
+ vr->preempt_mode = preempt;
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/accept-mode
+ */
+static int
+lib_interface_vrrp_vrrp_group_accept_mode_modify(struct nb_cb_modify_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ struct vrrp_vrouter *vr;
+ bool accept;
+
+ vr = nb_running_get_entry(args->dnode, NULL, true);
+ accept = yang_dnode_get_bool(args->dnode, NULL);
+ vr->accept_mode = accept;
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/advertisement-interval
+ */
+static int lib_interface_vrrp_vrrp_group_advertisement_interval_modify(
+ struct nb_cb_modify_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ struct vrrp_vrouter *vr;
+ uint16_t advert_int;
+
+ vr = nb_running_get_entry(args->dnode, NULL, true);
+ advert_int = yang_dnode_get_uint16(args->dnode, NULL);
+ vrrp_set_advertisement_interval(vr, advert_int);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/shutdown
+ */
+static int
+lib_interface_vrrp_vrrp_group_shutdown_modify(struct nb_cb_modify_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ struct vrrp_vrouter *vr;
+ bool shutdown;
+
+ vr = nb_running_get_entry(args->dnode, NULL, true);
+ shutdown = yang_dnode_get_bool(args->dnode, NULL);
+
+ vr->shutdown = shutdown;
+
+ if (shutdown) {
+ vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN);
+ vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN);
+ } else {
+ vrrp_check_start(vr);
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/checksum-with-
+ * ipv4-pseudoheader
+ */
+static int lib_interface_vrrp_vrrp_group_checksum_with_ipv4_pseudoheader_modify(
+ struct nb_cb_modify_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ struct vrrp_vrouter *vr;
+ bool checksum_with_ipv4_ph;
+
+ vr = nb_running_get_entry(args->dnode, NULL, true);
+ checksum_with_ipv4_ph = yang_dnode_get_bool(args->dnode, NULL);
+ vr->checksum_with_ipv4_pseudoheader = checksum_with_ipv4_ph;
+
+ return NB_OK;
+}
+
+/* clang-format off */
+const struct frr_yang_module_info frr_vrrpd_info = {
+ .name = "frr-vrrpd",
+ .nodes = {
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group",
+ .cbs = {
+ .create = lib_interface_vrrp_vrrp_group_create,
+ .destroy = lib_interface_vrrp_vrrp_group_destroy,
+ .get_next = lib_interface_vrrp_vrrp_group_get_next,
+ .get_keys = lib_interface_vrrp_vrrp_group_get_keys,
+ .lookup_entry = lib_interface_vrrp_vrrp_group_lookup_entry,
+ .cli_show = cli_show_vrrp,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/version",
+ .cbs = {
+ .modify = lib_interface_vrrp_vrrp_group_version_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/priority",
+ .cbs = {
+ .modify = lib_interface_vrrp_vrrp_group_priority_modify,
+ .cli_show = cli_show_priority,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/preempt",
+ .cbs = {
+ .modify = lib_interface_vrrp_vrrp_group_preempt_modify,
+ .cli_show = cli_show_preempt,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/accept-mode",
+ .cbs = {
+ .modify = lib_interface_vrrp_vrrp_group_accept_mode_modify,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/checksum-with-ipv4-pseudoheader",
+ .cbs = {
+ .modify = lib_interface_vrrp_vrrp_group_checksum_with_ipv4_pseudoheader_modify,
+ .cli_show = cli_show_checksum_with_ipv4_pseudoheader,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/advertisement-interval",
+ .cbs = {
+ .modify = lib_interface_vrrp_vrrp_group_advertisement_interval_modify,
+ .cli_show = cli_show_advertisement_interval,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/shutdown",
+ .cbs = {
+ .modify = lib_interface_vrrp_vrrp_group_shutdown_modify,
+ .cli_show = cli_show_shutdown,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/virtual-address",
+ .cbs = {
+ .create = lib_interface_vrrp_vrrp_group_v4_virtual_address_create,
+ .destroy = lib_interface_vrrp_vrrp_group_v4_virtual_address_destroy,
+ .cli_show = cli_show_ip,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/current-priority",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_current_priority_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/vrrp-interface",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_vrrp_interface_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/source-address",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_source_address_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/state",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_state_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/master-advertisement-interval",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_master_advertisement_interval_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/skew-time",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_skew_time_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/counter/state-transition",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_counter_state_transition_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/counter/tx/advertisement",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_counter_tx_advertisement_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/counter/tx/gratuitous-arp",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_counter_tx_gratuitous_arp_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/counter/rx/advertisement",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v4_counter_rx_advertisement_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/virtual-address",
+ .cbs = {
+ .create = lib_interface_vrrp_vrrp_group_v6_virtual_address_create,
+ .destroy = lib_interface_vrrp_vrrp_group_v6_virtual_address_destroy,
+ .cli_show = cli_show_ipv6,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/current-priority",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_current_priority_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/vrrp-interface",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_vrrp_interface_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/source-address",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_source_address_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/state",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_state_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/master-advertisement-interval",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_master_advertisement_interval_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/skew-time",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_skew_time_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/state-transition",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_counter_state_transition_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/tx/advertisement",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_counter_tx_advertisement_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/tx/neighbor-advertisement",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_counter_tx_neighbor_advertisement_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/rx/advertisement",
+ .cbs = {
+ .get_elem = lib_interface_vrrp_vrrp_group_v6_counter_rx_advertisement_get_elem,
+ }
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
diff --git a/vrrpd/vrrp_packet.c b/vrrpd/vrrp_packet.c
new file mode 100644
index 0000000..36494c7
--- /dev/null
+++ b/vrrpd/vrrp_packet.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP packet crafting.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#include <zebra.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
+#include "lib/checksum.h"
+#include "lib/ipaddr.h"
+#include "lib/memory.h"
+
+#include "vrrp.h"
+#include "vrrp_debug.h"
+#include "vrrp_packet.h"
+
+DEFINE_MTYPE_STATIC(VRRPD, VRRP_PKT, "VRRP packet");
+
+/* clang-format off */
+static const char *const vrrp_packet_names[16] = {
+ [0] = "Unknown",
+ [VRRP_TYPE_ADVERTISEMENT] = "ADVERTISEMENT",
+ [2] = "Unknown",
+ [3] = "Unknown",
+ [4] = "Unknown",
+ [5] = "Unknown",
+ [6] = "Unknown",
+ [7] = "Unknown",
+ [8] = "Unknown",
+ [9] = "Unknown",
+ [10] = "Unknown",
+ [11] = "Unknown",
+ [12] = "Unknown",
+ [13] = "Unknown",
+ [14] = "Unknown",
+ [15] = "Unknown",
+};
+/* clang-format on */
+
+/*
+ * Compute the VRRP checksum.
+ *
+ * Checksum is not set in the packet, just computed.
+ *
+ * pkt
+ * VRRP packet, fully filled out except for checksum field.
+ *
+ * pktsize
+ * sizeof(*pkt)
+ *
+ * src
+ * IP address that pkt will be transmitted from.
+ *
+ * Returns:
+ * VRRP checksum in network byte order.
+ */
+static uint16_t vrrp_pkt_checksum(struct vrrp_pkt *pkt, size_t pktsize,
+ struct ipaddr *src, bool ipv4_ph)
+{
+ uint16_t chksum;
+ bool v6 = (src->ipa_type == IPADDR_V6);
+
+ uint16_t chksum_pre = pkt->hdr.chksum;
+
+ pkt->hdr.chksum = 0;
+
+ if (v6) {
+ struct ipv6_ph ph = {};
+
+ ph.src = src->ipaddr_v6;
+ inet_pton(AF_INET6, VRRP_MCASTV6_GROUP_STR, &ph.dst);
+ ph.ulpl = htons(pktsize);
+ ph.next_hdr = IPPROTO_VRRP;
+ chksum = in_cksum_with_ph6(&ph, pkt, pktsize);
+ } else if (!v6 && ((pkt->hdr.vertype >> 4) == 3)) {
+ if (ipv4_ph) {
+ struct ipv4_ph ph = {};
+
+ ph.src = src->ipaddr_v4;
+ inet_pton(AF_INET, VRRP_MCASTV4_GROUP_STR, &ph.dst);
+ ph.proto = IPPROTO_VRRP;
+ ph.len = htons(pktsize);
+ chksum = in_cksum_with_ph4(&ph, pkt, pktsize);
+ } else
+ chksum = in_cksum(pkt, pktsize);
+ } else if (!v6 && ((pkt->hdr.vertype >> 4) == 2)) {
+ chksum = in_cksum(pkt, pktsize);
+ } else {
+ assert(!"Invalid VRRP protocol version");
+ }
+
+ pkt->hdr.chksum = chksum_pre;
+
+ return chksum;
+}
+
+ssize_t vrrp_pkt_adver_build(struct vrrp_pkt **pkt, struct ipaddr *src,
+ uint8_t version, uint8_t vrid, uint8_t prio,
+ uint16_t max_adver_int, uint8_t numip,
+ struct ipaddr **ips, bool ipv4_ph)
+{
+ bool v6 = false;
+ size_t addrsz = 0;
+
+ assert(version >= 2 && version <= 3);
+
+ if (numip > 0) {
+ v6 = IS_IPADDR_V6(ips[0]);
+ addrsz = IPADDRSZ(ips[0]);
+ }
+
+ assert(!(version == 2 && v6));
+
+ size_t pktsize = VRRP_PKT_SIZE(v6 ? AF_INET6 : AF_INET, version, numip);
+
+ *pkt = XCALLOC(MTYPE_VRRP_PKT, pktsize);
+
+ (*pkt)->hdr.vertype |= version << 4;
+ (*pkt)->hdr.vertype |= VRRP_TYPE_ADVERTISEMENT;
+ (*pkt)->hdr.vrid = vrid;
+ (*pkt)->hdr.priority = prio;
+ (*pkt)->hdr.naddr = numip;
+ if (version == 3)
+ (*pkt)->hdr.v3.adver_int = htons(max_adver_int);
+ else if (version == 2) {
+ (*pkt)->hdr.v2.auth_type = 0;
+ (*pkt)->hdr.v2.adver_int = MAX(max_adver_int / 100, 1);
+ }
+
+ uint8_t *aptr = (void *)(*pkt)->addrs;
+
+ for (int i = 0; i < numip; i++) {
+ memcpy(aptr, &ips[i]->ip.addr, addrsz);
+ aptr += addrsz;
+ }
+
+ (*pkt)->hdr.chksum = vrrp_pkt_checksum(*pkt, pktsize, src, ipv4_ph);
+
+ return pktsize;
+}
+
+void vrrp_pkt_free(struct vrrp_pkt *pkt)
+{
+ XFREE(MTYPE_VRRP_PKT, pkt);
+}
+
+size_t vrrp_pkt_adver_dump(char *buf, size_t buflen, struct vrrp_pkt *pkt)
+{
+ if (buflen < 1)
+ return 0;
+
+ char tmpbuf[BUFSIZ];
+ size_t rs = 0;
+ struct vrrp_hdr *hdr = &pkt->hdr;
+
+ buf[0] = 0x00;
+ snprintf(tmpbuf, sizeof(tmpbuf), "version %u, ", (hdr->vertype >> 4));
+ rs += strlcat(buf, tmpbuf, buflen);
+ snprintf(tmpbuf, sizeof(tmpbuf), "type %u (%s), ",
+ (hdr->vertype & 0x0F),
+ vrrp_packet_names[(hdr->vertype & 0x0F)]);
+ rs += strlcat(buf, tmpbuf, buflen);
+ snprintf(tmpbuf, sizeof(tmpbuf), "vrid %u, ", hdr->vrid);
+ rs += strlcat(buf, tmpbuf, buflen);
+ snprintf(tmpbuf, sizeof(tmpbuf), "priority %u, ", hdr->priority);
+ rs += strlcat(buf, tmpbuf, buflen);
+ snprintf(tmpbuf, sizeof(tmpbuf), "#%u addresses, ", hdr->naddr);
+ rs += strlcat(buf, tmpbuf, buflen);
+ snprintf(tmpbuf, sizeof(tmpbuf), "max adver int %u, ",
+ ntohs(hdr->v3.adver_int));
+ rs += strlcat(buf, tmpbuf, buflen);
+ snprintf(tmpbuf, sizeof(tmpbuf), "checksum %x", ntohs(hdr->chksum));
+ rs += strlcat(buf, tmpbuf, buflen);
+
+ return rs;
+}
+
+ssize_t vrrp_pkt_parse_datagram(int family, int version, bool ipv4_ph,
+ struct msghdr *m, size_t read,
+ struct ipaddr *src, struct vrrp_pkt **pkt,
+ char *errmsg, size_t errmsg_len)
+{
+ /* Source (MAC & IP), Dest (MAC & IP) TTL validation done by kernel */
+ size_t addrsz = (family == AF_INET) ? sizeof(struct in_addr)
+ : sizeof(struct in6_addr);
+
+ size_t pktsize;
+ uint8_t *buf = m->msg_iov->iov_base;
+
+#define VRRP_PKT_VCHECK(cond, _f, ...) \
+ do { \
+ if (!(cond)) { \
+ if (errmsg) \
+ snprintf(errmsg, errmsg_len, (_f), \
+ ##__VA_ARGS__); \
+ return -1; \
+ } \
+ } while (0)
+
+ /* IPvX header check */
+
+ if (family == AF_INET) {
+ VRRP_PKT_VCHECK(
+ read >= sizeof(struct ip),
+ "Datagram not large enough to contain IP header");
+
+ struct ip *ip = (struct ip *)buf;
+
+ /* IP total length check */
+ VRRP_PKT_VCHECK(
+ ntohs(ip->ip_len) == read,
+ "IPv4 packet length field does not match # received bytes; %hu!= %zu",
+ ntohs(ip->ip_len), read);
+
+ /* TTL check */
+ VRRP_PKT_VCHECK(ip->ip_ttl == 255,
+ "IPv4 TTL is %hhu; should be 255",
+ ip->ip_ttl);
+
+ *pkt = (struct vrrp_pkt *)(buf + (ip->ip_hl << 2));
+ pktsize = read - (ip->ip_hl << 2);
+
+ /* IP empty packet check */
+ VRRP_PKT_VCHECK(pktsize > 0, "IPv4 packet has no payload");
+
+ /* Extract source address */
+ struct sockaddr_in *sa = m->msg_name;
+
+ src->ipa_type = IPADDR_V4;
+ src->ipaddr_v4 = sa->sin_addr;
+ } else if (family == AF_INET6) {
+ struct cmsghdr *c;
+
+ for (c = CMSG_FIRSTHDR(m); c != NULL; CMSG_NXTHDR(m, c)) {
+ if (c->cmsg_level == IPPROTO_IPV6
+ && c->cmsg_type == IPV6_HOPLIMIT)
+ break;
+ }
+
+ VRRP_PKT_VCHECK(!!c, "IPv6 Hop Limit not received");
+
+ uint8_t *hoplimit = CMSG_DATA(c);
+
+ VRRP_PKT_VCHECK(*hoplimit == 255,
+ "IPv6 Hop Limit is %hhu; should be 255",
+ *hoplimit);
+
+ *pkt = (struct vrrp_pkt *)buf;
+ pktsize = read;
+
+ /* Extract source address */
+ struct sockaddr_in6 *sa = m->msg_name;
+
+ src->ipa_type = IPADDR_V6;
+ memcpy(&src->ipaddr_v6, &sa->sin6_addr,
+ sizeof(struct in6_addr));
+ } else {
+ assert(!"Unknown address family");
+ }
+
+ /* Size check */
+ size_t minsize = (family == AF_INET) ? VRRP_MIN_PKT_SIZE_V4
+ : VRRP_MIN_PKT_SIZE_V6;
+ size_t maxsize = (family == AF_INET) ? VRRP_MAX_PKT_SIZE_V4
+ : VRRP_MAX_PKT_SIZE_V6;
+ VRRP_PKT_VCHECK(pktsize >= minsize,
+ "VRRP packet is undersized (%zu < %zu)", pktsize,
+ minsize);
+ VRRP_PKT_VCHECK(pktsize <= maxsize,
+ "VRRP packet is oversized (%zu > %zu)", pktsize,
+ maxsize);
+
+ /* Version check */
+ uint8_t pktver = (*pkt)->hdr.vertype >> 4;
+
+ VRRP_PKT_VCHECK(pktver == version, "Bad version %u", pktver);
+
+ /* Checksum check */
+ uint16_t chksum = vrrp_pkt_checksum(*pkt, pktsize, src, ipv4_ph);
+
+ VRRP_PKT_VCHECK((*pkt)->hdr.chksum == chksum,
+ "Bad VRRP checksum %hx; should be %hx",
+ (*pkt)->hdr.chksum, chksum);
+
+ /* Type check */
+ VRRP_PKT_VCHECK(((*pkt)->hdr.vertype & 0x0F) == 1, "Bad type %u",
+ (*pkt)->hdr.vertype & 0x0f);
+
+ /* Exact size check */
+ size_t ves = VRRP_PKT_SIZE(family, pktver, (*pkt)->hdr.naddr);
+
+ VRRP_PKT_VCHECK(pktsize == ves, "Packet has incorrect # addresses%s",
+ pktver == 2 ? " or missing auth fields" : "");
+
+ /* auth type check */
+ if (version == 2)
+ VRRP_PKT_VCHECK((*pkt)->hdr.v2.auth_type == 0,
+ "Bad authentication type %hhu",
+ (*pkt)->hdr.v2.auth_type);
+
+ /* Addresses check */
+ char vbuf[INET6_ADDRSTRLEN];
+ uint8_t *p = (uint8_t *)(*pkt)->addrs;
+
+ for (uint8_t i = 0; i < (*pkt)->hdr.naddr; i++) {
+ VRRP_PKT_VCHECK(inet_ntop(family, p, vbuf, sizeof(vbuf)),
+ "Bad IP address, #%hhu", i);
+ p += addrsz;
+ }
+
+ /* Everything checks out */
+ return pktsize;
+}
diff --git a/vrrpd/vrrp_packet.h b/vrrpd/vrrp_packet.h
new file mode 100644
index 0000000..99136da
--- /dev/null
+++ b/vrrpd/vrrp_packet.h
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP packet crafting.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#ifndef __VRRP_PACKET_H__
+#define __VRRP_PACKET_H__
+
+#include <zebra.h>
+
+#include "lib/ipaddr.h"
+#include "lib/memory.h"
+#include "lib/prefix.h"
+
+#define VRRP_TYPE_ADVERTISEMENT 1
+
+/*
+ * Shared header for VRRPv2/v3 packets.
+ */
+struct vrrp_hdr {
+ /*
+ * H L H L
+ * 0000 0000
+ * ver type
+ */
+ uint8_t vertype;
+ uint8_t vrid;
+ uint8_t priority;
+ uint8_t naddr;
+ union {
+ struct {
+ uint8_t auth_type;
+ /* advertisement interval (in sec) */
+ uint8_t adver_int;
+ } v2;
+ struct {
+ /*
+ * advertisement interval (in centiseconds)
+ * H L H L
+ * 0000 000000000000
+ * rsvd adver_int
+ */
+ uint16_t adver_int;
+ } v3;
+ };
+ uint16_t chksum;
+} __attribute__((packed));
+
+#define VRRP_HDR_SIZE sizeof(struct vrrp_hdr)
+
+struct vrrp_pkt {
+ struct vrrp_hdr hdr;
+ /*
+ * When used, this is actually an array of one or the other, not an
+ * array of union. If N v4 addresses are stored then
+ * sizeof(addrs) == N * sizeof(struct in_addr).
+ *
+ * Under v2, the last 2 entries in this array are the authentication
+ * data fields. We don't support auth in v2 so these are always just 8
+ * bytes of 0x00.
+ */
+ union {
+ struct in_addr v4;
+ struct in6_addr v6;
+ } addrs[];
+} __attribute__((packed));
+
+#define VRRP_PKT_SIZE(_f, _ver, _naddr) \
+ ({ \
+ size_t _asz = ((_f) == AF_INET) ? sizeof(struct in_addr) \
+ : sizeof(struct in6_addr); \
+ size_t _auth = 2 * sizeof(uint32_t) * (3 - (_ver)); \
+ sizeof(struct vrrp_hdr) + (_asz * (_naddr)) + _auth; \
+ })
+
+#define VRRP_MIN_PKT_SIZE_V4 VRRP_PKT_SIZE(AF_INET, 3, 1)
+#define VRRP_MAX_PKT_SIZE_V4 VRRP_PKT_SIZE(AF_INET, 2, 255)
+#define VRRP_MIN_PKT_SIZE_V6 VRRP_PKT_SIZE(AF_INET6, 3, 1)
+#define VRRP_MAX_PKT_SIZE_V6 VRRP_PKT_SIZE(AF_INET6, 3, 255)
+
+#define VRRP_MIN_PKT_SIZE VRRP_MIN_PKT_SIZE_V4
+#define VRRP_MAX_PKT_SIZE VRRP_MAX_PKT_SIZE_V6
+
+/*
+ * Builds a VRRP ADVERTISEMENT packet.
+ *
+ * pkt
+ * Pointer to store pointer to result buffer in
+ *
+ * src
+ * Source address packet will be transmitted from. This is needed to compute
+ * the VRRP checksum. The returned packet must be sent in an IP datagram with
+ * the source address equal to this field, or the checksum will be invalid.
+ *
+ * version
+ * VRRP version; must be 2 or 3
+ *
+ * vrid
+ * Virtual Router Identifier
+ *
+ * prio
+ * Virtual Router Priority
+ *
+ * max_adver_int
+ * time between ADVERTISEMENTs
+ *
+ * v6
+ * whether 'ips' is an array of v4 or v6 addresses
+ *
+ * numip
+ * number of IPvX addresses in 'ips'
+ *
+ * ips
+ * array of pointer to either struct in_addr (v6 = false) or struct in6_addr
+ * (v6 = true)
+ */
+ssize_t vrrp_pkt_adver_build(struct vrrp_pkt **pkt, struct ipaddr *src,
+ uint8_t version, uint8_t vrid, uint8_t prio,
+ uint16_t max_adver_int, uint8_t numip,
+ struct ipaddr **ips, bool ipv4_ph);
+
+/* free memory allocated by vrrp_pkt_adver_build's pkt arg */
+void vrrp_pkt_free(struct vrrp_pkt *pkt);
+
+/*
+ * Dumps a VRRP ADVERTISEMENT packet to a string.
+ *
+ * Currently only dumps the header.
+ *
+ * buf
+ * Buffer to store string representation
+ *
+ * buflen
+ * Size of buf
+ *
+ * pkt
+ * Packet to dump to a string
+ *
+ * Returns:
+ * # bytes written to buf
+ */
+size_t vrrp_pkt_adver_dump(char *buf, size_t buflen, struct vrrp_pkt *pkt);
+
+
+/*
+ * Parses a VRRP packet, checking for illegal or invalid data.
+ *
+ * This function parses both VRRPv2 and VRRPv3 packets. Which version is
+ * expected is determined by the version argument. For example, if version is 3
+ * and the received packet has version field 2 it will fail to parse.
+ *
+ * Note that this function only checks whether the packet itself is a valid
+ * VRRP packet. It is up to the caller to validate whether the VRID is correct,
+ * priority and timer values are correct, etc.
+ *
+ * family
+ * Address family of received packet
+ *
+ * version
+ * VRRP version to use for validation
+ *
+ * m
+ * msghdr containing results of recvmsg() on VRRP router socket
+ *
+ * read
+ * Return value of recvmsg() on VRRP router socket; must be non-negative
+ *
+ * src
+ * Pointer to struct ipaddr to store address of datagram sender
+ *
+ * pkt
+ * Pointer to pointer to set to location of VRRP packet within buf
+ *
+ * errmsg
+ * Buffer to store human-readable error message in case of error; may be
+ * NULL, in which case no message will be stored
+ *
+ * errmsg_len
+ * Size of errmsg
+ *
+ * Returns:
+ * Size of VRRP packet, or -1 upon error
+ */
+ssize_t vrrp_pkt_parse_datagram(int family, int version, bool ipv4_ph,
+ struct msghdr *m, size_t read,
+ struct ipaddr *src, struct vrrp_pkt **pkt,
+ char *errmsg, size_t errmsg_len);
+
+#endif /* __VRRP_PACKET_H__ */
diff --git a/vrrpd/vrrp_vty.c b/vrrpd/vrrp_vty.c
new file mode 100644
index 0000000..9971df5
--- /dev/null
+++ b/vrrpd/vrrp_vty.c
@@ -0,0 +1,781 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP CLI commands.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#include <zebra.h>
+
+#include "lib/command.h"
+#include "lib/if.h"
+#include "lib/ipaddr.h"
+#include "lib/json.h"
+#include "lib/northbound_cli.h"
+#include "lib/prefix.h"
+#include "lib/termtable.h"
+#include "lib/vty.h"
+#include "lib/vrf.h"
+
+#include "vrrp.h"
+#include "vrrp_debug.h"
+#include "vrrp_vty.h"
+#include "vrrp_zebra.h"
+#include "vrrpd/vrrp_vty_clippy.c"
+
+
+#define VRRP_STR "Virtual Router Redundancy Protocol\n"
+#define VRRP_VRID_STR "Virtual Router ID\n"
+#define VRRP_PRIORITY_STR "Virtual Router Priority\n"
+#define VRRP_ADVINT_STR "Virtual Router Advertisement Interval\n"
+#define VRRP_IP_STR "Virtual Router IP address\n"
+#define VRRP_VERSION_STR "VRRP protocol version\n"
+
+#define VRRP_XPATH_ENTRY VRRP_XPATH "[virtual-router-id='%ld']"
+
+/* clang-format off */
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group
+ */
+DEFPY_YANG(vrrp_vrid,
+ vrrp_vrid_cmd,
+ "[no] vrrp (1-255)$vrid [version (2-3)]",
+ NO_STR
+ VRRP_STR
+ VRRP_VRID_STR
+ VRRP_VERSION_STR
+ VRRP_VERSION_STR)
+{
+ char valbuf[20];
+
+ snprintf(valbuf, sizeof(valbuf), "%ld", version ? version : vd.version);
+
+ if (no)
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+ else {
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ nb_cli_enqueue_change(vty, "./version", NB_OP_MODIFY, valbuf);
+ }
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+void cli_show_vrrp(struct vty *vty, const struct lyd_node *dnode, bool show_defaults)
+{
+ const char *vrid = yang_dnode_get_string(dnode, "./virtual-router-id");
+ const char *ver = yang_dnode_get_string(dnode, "./version");
+
+ vty_out(vty, " vrrp %s", vrid);
+ if (show_defaults || !yang_dnode_is_default(dnode, "./version"))
+ vty_out(vty, " version %s", ver);
+ vty_out(vty, "\n");
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/shutdown
+ */
+DEFPY_YANG(vrrp_shutdown,
+ vrrp_shutdown_cmd,
+ "[no] vrrp (1-255)$vrid shutdown",
+ NO_STR
+ VRRP_STR
+ VRRP_VRID_STR
+ "Force VRRP router into administrative shutdown\n")
+{
+ nb_cli_enqueue_change(vty, "./shutdown", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+void cli_show_shutdown(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id");
+ const bool shut = yang_dnode_get_bool(dnode, NULL);
+
+ vty_out(vty, " %svrrp %s shutdown\n", shut ? "" : "no ", vrid);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/priority
+ */
+DEFPY_YANG(vrrp_priority,
+ vrrp_priority_cmd,
+ "vrrp (1-255)$vrid priority (1-254)",
+ VRRP_STR
+ VRRP_VRID_STR
+ VRRP_PRIORITY_STR
+ "Priority value\n")
+{
+ nb_cli_enqueue_change(vty, "./priority", NB_OP_MODIFY, priority_str);
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/priority
+ */
+DEFPY_YANG(no_vrrp_priority,
+ no_vrrp_priority_cmd,
+ "no vrrp (1-255)$vrid priority [(1-254)]",
+ NO_STR
+ VRRP_STR
+ VRRP_VRID_STR
+ VRRP_PRIORITY_STR
+ "Priority value\n")
+{
+ nb_cli_enqueue_change(vty, "./priority", NB_OP_MODIFY, NULL);
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+void cli_show_priority(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id");
+ const char *prio = yang_dnode_get_string(dnode, NULL);
+
+ vty_out(vty, " vrrp %s priority %s\n", vrid, prio);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/advertisement-interval
+ */
+DEFPY_YANG(vrrp_advertisement_interval,
+ vrrp_advertisement_interval_cmd,
+ "vrrp (1-255)$vrid advertisement-interval (10-40950)",
+ VRRP_STR VRRP_VRID_STR VRRP_ADVINT_STR
+ "Advertisement interval in milliseconds; must be multiple of 10\n")
+{
+ char val[20];
+
+ /* all internal computations are in centiseconds */
+ advertisement_interval /= CS2MS;
+ snprintf(val, sizeof(val), "%ld", advertisement_interval);
+ nb_cli_enqueue_change(vty, "./advertisement-interval", NB_OP_MODIFY,
+ val);
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/advertisement-interval
+ */
+DEFPY_YANG(no_vrrp_advertisement_interval,
+ no_vrrp_advertisement_interval_cmd,
+ "no vrrp (1-255)$vrid advertisement-interval [(10-40950)]",
+ NO_STR VRRP_STR VRRP_VRID_STR VRRP_ADVINT_STR
+ "Advertisement interval in milliseconds; must be multiple of 10\n")
+{
+ nb_cli_enqueue_change(vty, "./advertisement-interval", NB_OP_MODIFY,
+ NULL);
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+void cli_show_advertisement_interval(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id");
+ uint16_t advint = yang_dnode_get_uint16(dnode, NULL);
+
+ vty_out(vty, " vrrp %s advertisement-interval %u\n", vrid,
+ advint * CS2MS);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/virtual-address
+ */
+DEFPY_YANG(vrrp_ip,
+ vrrp_ip_cmd,
+ "[no] vrrp (1-255)$vrid ip A.B.C.D",
+ NO_STR
+ VRRP_STR
+ VRRP_VRID_STR
+ "Add IPv4 address\n"
+ VRRP_IP_STR)
+{
+ int op = no ? NB_OP_DESTROY : NB_OP_CREATE;
+ nb_cli_enqueue_change(vty, "./v4/virtual-address", op, ip_str);
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+void cli_show_ip(struct vty *vty, const struct lyd_node *dnode, bool show_defaults)
+{
+ const char *vrid =
+ yang_dnode_get_string(dnode, "../../virtual-router-id");
+ const char *ipv4 = yang_dnode_get_string(dnode, NULL);
+
+ vty_out(vty, " vrrp %s ip %s\n", vrid, ipv4);
+}
+
+/*
+ * XPath:
+ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/virtual-address
+ */
+DEFPY_YANG(vrrp_ip6,
+ vrrp_ip6_cmd,
+ "[no] vrrp (1-255)$vrid ipv6 X:X::X:X",
+ NO_STR
+ VRRP_STR
+ VRRP_VRID_STR
+ "Add IPv6 address\n"
+ VRRP_IP_STR)
+{
+ int op = no ? NB_OP_DESTROY : NB_OP_CREATE;
+ nb_cli_enqueue_change(vty, "./v6/virtual-address", op, ipv6_str);
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+void cli_show_ipv6(struct vty *vty, const struct lyd_node *dnode, bool show_defaults)
+{
+ const char *vrid =
+ yang_dnode_get_string(dnode, "../../virtual-router-id");
+ const char *ipv6 = yang_dnode_get_string(dnode, NULL);
+
+ vty_out(vty, " vrrp %s ipv6 %s\n", vrid, ipv6);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/preempt
+ */
+DEFPY_YANG(vrrp_preempt,
+ vrrp_preempt_cmd,
+ "[no] vrrp (1-255)$vrid preempt",
+ NO_STR
+ VRRP_STR
+ VRRP_VRID_STR
+ "Preempt mode\n")
+{
+ nb_cli_enqueue_change(vty, "./preempt", NB_OP_MODIFY,
+ no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+void cli_show_preempt(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id");
+ const bool pre = yang_dnode_get_bool(dnode, NULL);
+
+ vty_out(vty, " %svrrp %s preempt\n", pre ? "" : "no ", vrid);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/checksum-with-
+ * ipv4-pseudoheader
+ */
+DEFPY_YANG(vrrp_checksum_with_ipv4_pseudoheader,
+ vrrp_checksum_with_ipv4_pseudoheader_cmd,
+ "[no] vrrp (1-255)$vrid checksum-with-ipv4-pseudoheader",
+ NO_STR
+ VRRP_STR
+ VRRP_VRID_STR
+ "Checksum mode in VRRPv3\n")
+{
+ nb_cli_enqueue_change(vty, "./checksum-with-ipv4-pseudoheader",
+ NB_OP_MODIFY, no ? "false" : "true");
+
+ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid);
+}
+
+void cli_show_checksum_with_ipv4_pseudoheader(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id");
+ const bool pre = yang_dnode_get_bool(dnode, NULL);
+
+ vty_out(vty, " %svrrp %s checksum-with-ipv4-pseudoheader\n",
+ pre ? "" : "no ", vrid);
+}
+
+/* XXX: yang conversion */
+DEFPY_YANG(vrrp_autoconfigure,
+ vrrp_autoconfigure_cmd,
+ "[no] vrrp autoconfigure [version (2-3)]",
+ NO_STR
+ VRRP_STR
+ "Automatically set up VRRP instances on VRRP-compatible interfaces\n"
+ "Version for automatically configured instances\n"
+ VRRP_VERSION_STR)
+{
+ version = version ? version : 3;
+
+ if (!no)
+ vrrp_autoconfig_on(version);
+ else
+ vrrp_autoconfig_off();
+
+ return CMD_SUCCESS;
+}
+
+/* XXX: yang conversion */
+DEFPY_YANG(vrrp_default,
+ vrrp_default_cmd,
+ "[no] vrrp default <advertisement-interval$adv (10-40950)$advint|preempt$p|priority$prio (1-254)$prioval|checksum-with-ipv4-pseudoheader$ipv4ph|shutdown$s>",
+ NO_STR
+ VRRP_STR
+ "Configure defaults for new VRRP instances\n"
+ VRRP_ADVINT_STR
+ "Advertisement interval in milliseconds\n"
+ "Preempt mode\n"
+ VRRP_PRIORITY_STR
+ "Priority value\n"
+ "Checksum mode in VRRPv3\n"
+ "Force VRRP router into administrative shutdown\n")
+{
+ if (adv) {
+ if (advint % CS2MS != 0) {
+ vty_out(vty, "%% Value must be a multiple of %u\n",
+ (unsigned int)CS2MS);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ /* all internal computations are in centiseconds */
+ advint /= CS2MS;
+ vd.advertisement_interval = no ? VRRP_DEFAULT_ADVINT : advint;
+ }
+ if (p)
+ vd.preempt_mode = !no;
+ if (prio)
+ vd.priority = no ? VRRP_DEFAULT_PRIORITY : prioval;
+ if (ipv4ph)
+ vd.checksum_with_ipv4_pseudoheader = !no;
+ if (s)
+ vd.shutdown = !no;
+
+ return CMD_SUCCESS;
+}
+
+/* clang-format on */
+
+/*
+ * Build JSON representation of VRRP instance.
+ *
+ * vr
+ * VRRP router to build json object from
+ *
+ * Returns:
+ * JSON representation of VRRP instance. Must be freed by caller.
+ */
+static struct json_object *vrrp_build_json(struct vrrp_vrouter *vr)
+{
+ char ethstr4[ETHER_ADDR_STRLEN];
+ char ethstr6[ETHER_ADDR_STRLEN];
+ char ipstr[INET6_ADDRSTRLEN];
+ const char *stastr4 = vrrp_state_names[vr->v4->fsm.state];
+ const char *stastr6 = vrrp_state_names[vr->v6->fsm.state];
+ char sipstr4[INET6_ADDRSTRLEN] = {};
+ char sipstr6[INET6_ADDRSTRLEN] = {};
+ struct listnode *ln;
+ struct ipaddr *ip;
+ struct json_object *j = json_object_new_object();
+ struct json_object *v4 = json_object_new_object();
+ struct json_object *v4_stats = json_object_new_object();
+ struct json_object *v4_addrs = json_object_new_array();
+ struct json_object *v6 = json_object_new_object();
+ struct json_object *v6_stats = json_object_new_object();
+ struct json_object *v6_addrs = json_object_new_array();
+
+ prefix_mac2str(&vr->v4->vmac, ethstr4, sizeof(ethstr4));
+ prefix_mac2str(&vr->v6->vmac, ethstr6, sizeof(ethstr6));
+
+ json_object_int_add(j, "vrid", vr->vrid);
+ json_object_int_add(j, "version", vr->version);
+ json_object_boolean_add(j, "autoconfigured", vr->autoconf);
+ json_object_boolean_add(j, "shutdown", vr->shutdown);
+ json_object_boolean_add(j, "preemptMode", vr->preempt_mode);
+ json_object_boolean_add(j, "acceptMode", vr->accept_mode);
+ json_object_boolean_add(j, "checksumWithIpv4Pseudoheader",
+ vr->checksum_with_ipv4_pseudoheader);
+ json_object_string_add(j, "interface", vr->ifp->name);
+ json_object_int_add(j, "advertisementInterval",
+ vr->advertisement_interval * CS2MS);
+ json_object_int_add(j, "priority", vr->priority);
+ /* v4 */
+ json_object_string_add(v4, "interface",
+ vr->v4->mvl_ifp ? vr->v4->mvl_ifp->name : "");
+ json_object_string_add(v4, "vmac", ethstr4);
+ ipaddr2str(&vr->v4->src, sipstr4, sizeof(sipstr4));
+ json_object_string_add(v4, "primaryAddress", sipstr4);
+ json_object_string_add(v4, "status", stastr4);
+ json_object_int_add(v4, "effectivePriority", vr->v4->priority);
+ json_object_int_add(v4, "masterAdverInterval",
+ vr->v4->master_adver_interval * CS2MS);
+ json_object_int_add(v4, "skewTime", vr->v4->skew_time * CS2MS);
+ json_object_int_add(v4, "masterDownInterval",
+ vr->v4->master_down_interval * CS2MS);
+ /* v4 stats */
+ json_object_int_add(v4_stats, "adverTx", vr->v4->stats.adver_tx_cnt);
+ json_object_int_add(v4_stats, "adverRx", vr->v4->stats.adver_rx_cnt);
+ json_object_int_add(v4_stats, "garpTx", vr->v4->stats.garp_tx_cnt);
+ json_object_int_add(v4_stats, "transitions", vr->v4->stats.trans_cnt);
+ json_object_object_add(v4, "stats", v4_stats);
+ /* v4 addrs */
+ if (vr->v4->addrs->count) {
+ for (ALL_LIST_ELEMENTS_RO(vr->v4->addrs, ln, ip)) {
+ inet_ntop(vr->v4->family, &ip->ipaddr_v4, ipstr,
+ sizeof(ipstr));
+ json_object_array_add(v4_addrs,
+ json_object_new_string(ipstr));
+ }
+ }
+ json_object_object_add(v4, "addresses", v4_addrs);
+ json_object_object_add(j, "v4", v4);
+
+ /* v6 */
+ json_object_string_add(v6, "interface",
+ vr->v6->mvl_ifp ? vr->v6->mvl_ifp->name : "");
+ json_object_string_add(v6, "vmac", ethstr6);
+ ipaddr2str(&vr->v6->src, sipstr6, sizeof(sipstr6));
+ if (strlen(sipstr6) == 0 && vr->v6->src.ip.addr == 0x00)
+ strlcat(sipstr6, "::", sizeof(sipstr6));
+ json_object_string_add(v6, "primaryAddress", sipstr6);
+ json_object_string_add(v6, "status", stastr6);
+ json_object_int_add(v6, "effectivePriority", vr->v6->priority);
+ json_object_int_add(v6, "masterAdverInterval",
+ vr->v6->master_adver_interval * CS2MS);
+ json_object_int_add(v6, "skewTime", vr->v6->skew_time * CS2MS);
+ json_object_int_add(v6, "masterDownInterval",
+ vr->v6->master_down_interval * CS2MS);
+ /* v6 stats */
+ json_object_int_add(v6_stats, "adverTx", vr->v6->stats.adver_tx_cnt);
+ json_object_int_add(v6_stats, "adverRx", vr->v6->stats.adver_rx_cnt);
+ json_object_int_add(v6_stats, "neighborAdverTx",
+ vr->v6->stats.una_tx_cnt);
+ json_object_int_add(v6_stats, "transitions", vr->v6->stats.trans_cnt);
+ json_object_object_add(v6, "stats", v6_stats);
+ /* v6 addrs */
+ if (vr->v6->addrs->count) {
+ for (ALL_LIST_ELEMENTS_RO(vr->v6->addrs, ln, ip)) {
+ inet_ntop(vr->v6->family, &ip->ipaddr_v6, ipstr,
+ sizeof(ipstr));
+ json_object_array_add(v6_addrs,
+ json_object_new_string(ipstr));
+ }
+ }
+ json_object_object_add(v6, "addresses", v6_addrs);
+ json_object_object_add(j, "v6", v6);
+
+ return j;
+}
+
+/*
+ * Dump VRRP instance status to VTY.
+ *
+ * vty
+ * vty to dump to
+ *
+ * vr
+ * VRRP router to dump
+ */
+static void vrrp_show(struct vty *vty, struct vrrp_vrouter *vr)
+{
+ char ethstr4[ETHER_ADDR_STRLEN];
+ char ethstr6[ETHER_ADDR_STRLEN];
+ char ipstr[INET6_ADDRSTRLEN];
+ const char *stastr4 = vrrp_state_names[vr->v4->fsm.state];
+ const char *stastr6 = vrrp_state_names[vr->v6->fsm.state];
+ char sipstr4[INET6_ADDRSTRLEN] = {};
+ char sipstr6[INET6_ADDRSTRLEN] = {};
+ struct listnode *ln;
+ struct ipaddr *ip;
+
+ struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+
+ ttable_add_row(tt, "%s|%u", "Virtual Router ID", vr->vrid);
+ ttable_add_row(tt, "%s|%hhu", "Protocol Version", vr->version);
+ ttable_add_row(tt, "%s|%s", "Autoconfigured",
+ vr->autoconf ? "Yes" : "No");
+ ttable_add_row(tt, "%s|%s", "Shutdown", vr->shutdown ? "Yes" : "No");
+ ttable_add_row(tt, "%s|%s", "Interface", vr->ifp->name);
+ prefix_mac2str(&vr->v4->vmac, ethstr4, sizeof(ethstr4));
+ prefix_mac2str(&vr->v6->vmac, ethstr6, sizeof(ethstr6));
+ ttable_add_row(tt, "%s|%s", "VRRP interface (v4)",
+ vr->v4->mvl_ifp ? vr->v4->mvl_ifp->name : "None");
+ ttable_add_row(tt, "%s|%s", "VRRP interface (v6)",
+ vr->v6->mvl_ifp ? vr->v6->mvl_ifp->name : "None");
+ ipaddr2str(&vr->v4->src, sipstr4, sizeof(sipstr4));
+ ipaddr2str(&vr->v6->src, sipstr6, sizeof(sipstr6));
+ if (strlen(sipstr6) == 0 && vr->v6->src.ip.addr == 0x00)
+ strlcat(sipstr6, "::", sizeof(sipstr6));
+ ttable_add_row(tt, "%s|%s", "Primary IP (v4)", sipstr4);
+ ttable_add_row(tt, "%s|%s", "Primary IP (v6)", sipstr6);
+ ttable_add_row(tt, "%s|%s", "Virtual MAC (v4)", ethstr4);
+ ttable_add_row(tt, "%s|%s", "Virtual MAC (v6)", ethstr6);
+ ttable_add_row(tt, "%s|%s", "Status (v4)", stastr4);
+ ttable_add_row(tt, "%s|%s", "Status (v6)", stastr6);
+ ttable_add_row(tt, "%s|%hhu", "Priority", vr->priority);
+ ttable_add_row(tt, "%s|%hhu", "Effective Priority (v4)",
+ vr->v4->priority);
+ ttable_add_row(tt, "%s|%hhu", "Effective Priority (v6)",
+ vr->v6->priority);
+ ttable_add_row(tt, "%s|%s", "Preempt Mode",
+ vr->preempt_mode ? "Yes" : "No");
+ ttable_add_row(tt, "%s|%s", "Accept Mode",
+ vr->accept_mode ? "Yes" : "No");
+ ttable_add_row(tt, "%s|%s", "Checksum with IPv4 Pseudoheader",
+ vr->checksum_with_ipv4_pseudoheader ? "Yes" : "No");
+ ttable_add_row(tt, "%s|%d ms", "Advertisement Interval",
+ vr->advertisement_interval * CS2MS);
+ ttable_add_row(tt, "%s|%d ms (stale)",
+ "Master Advertisement Interval (v4) Rx",
+ vr->v4->master_adver_interval * CS2MS);
+ ttable_add_row(tt, "%s|%d ms (stale)",
+ "Master Advertisement Interval (v6) Rx",
+ vr->v6->master_adver_interval * CS2MS);
+ ttable_add_row(tt, "%s|%u", "Advertisements Tx (v4)",
+ vr->v4->stats.adver_tx_cnt);
+ ttable_add_row(tt, "%s|%u", "Advertisements Tx (v6)",
+ vr->v6->stats.adver_tx_cnt);
+ ttable_add_row(tt, "%s|%u", "Advertisements Rx (v4)",
+ vr->v4->stats.adver_rx_cnt);
+ ttable_add_row(tt, "%s|%u", "Advertisements Rx (v6)",
+ vr->v6->stats.adver_rx_cnt);
+ ttable_add_row(tt, "%s|%u", "Gratuitous ARP Tx (v4)",
+ vr->v4->stats.garp_tx_cnt);
+ ttable_add_row(tt, "%s|%u", "Neigh. Adverts Tx (v6)",
+ vr->v6->stats.una_tx_cnt);
+ ttable_add_row(tt, "%s|%u", "State transitions (v4)",
+ vr->v4->stats.trans_cnt);
+ ttable_add_row(tt, "%s|%u", "State transitions (v6)",
+ vr->v6->stats.trans_cnt);
+ ttable_add_row(tt, "%s|%d ms", "Skew Time (v4)",
+ vr->v4->skew_time * CS2MS);
+ ttable_add_row(tt, "%s|%d ms", "Skew Time (v6)",
+ vr->v6->skew_time * CS2MS);
+ ttable_add_row(tt, "%s|%d ms", "Master Down Interval (v4)",
+ vr->v4->master_down_interval * CS2MS);
+ ttable_add_row(tt, "%s|%d ms", "Master Down Interval (v6)",
+ vr->v6->master_down_interval * CS2MS);
+ ttable_add_row(tt, "%s|%u", "IPv4 Addresses", vr->v4->addrs->count);
+
+ char fill[35];
+
+ memset(fill, '.', sizeof(fill));
+ fill[sizeof(fill) - 1] = 0x00;
+ if (vr->v4->addrs->count) {
+ for (ALL_LIST_ELEMENTS_RO(vr->v4->addrs, ln, ip)) {
+ inet_ntop(vr->v4->family, &ip->ipaddr_v4, ipstr,
+ sizeof(ipstr));
+ ttable_add_row(tt, "%s|%s", fill, ipstr);
+ }
+ }
+
+ ttable_add_row(tt, "%s|%u", "IPv6 Addresses", vr->v6->addrs->count);
+
+ if (vr->v6->addrs->count) {
+ for (ALL_LIST_ELEMENTS_RO(vr->v6->addrs, ln, ip)) {
+ inet_ntop(vr->v6->family, &ip->ipaddr_v6, ipstr,
+ sizeof(ipstr));
+ ttable_add_row(tt, "%s|%s", fill, ipstr);
+ }
+ }
+
+ char *table = ttable_dump(tt, "\n");
+
+ vty_out(vty, "\n%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ ttable_del(tt);
+}
+
+/*
+ * Sort comparator, used when sorting VRRP instances for display purposes.
+ *
+ * Sorts by interface name first, then by VRID ascending.
+ */
+static int vrrp_instance_display_sort_cmp(const void **d1, const void **d2)
+{
+ const struct vrrp_vrouter *vr1 = *d1;
+ const struct vrrp_vrouter *vr2 = *d2;
+ int result;
+
+ result = strcmp(vr1->ifp->name, vr2->ifp->name);
+ result += !result * (vr1->vrid - vr2->vrid);
+
+ return result;
+}
+
+/* clang-format off */
+
+DEFPY_YANG(vrrp_vrid_show,
+ vrrp_vrid_show_cmd,
+ "show vrrp [interface INTERFACE$ifn] [(1-255)$vrid] [json$json]",
+ SHOW_STR
+ VRRP_STR
+ INTERFACE_STR
+ "Only show VRRP instances on this interface\n"
+ VRRP_VRID_STR
+ JSON_STR)
+{
+ struct vrrp_vrouter *vr;
+ struct listnode *ln;
+ struct list *ll = hash_to_list(vrrp_vrouters_hash);
+ struct json_object *j = json_object_new_array();
+
+ list_sort(ll, vrrp_instance_display_sort_cmp);
+
+ for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) {
+ if (ifn && !strmatch(ifn, vr->ifp->name))
+ continue;
+ if (vrid && ((uint8_t) vrid) != vr->vrid)
+ continue;
+
+ if (!json)
+ vrrp_show(vty, vr);
+ else
+ json_object_array_add(j, vrrp_build_json(vr));
+ }
+
+ if (json)
+ vty_out(vty, "%s\n",
+ json_object_to_json_string_ext(
+ j, JSON_C_TO_STRING_PRETTY));
+
+ json_object_free(j);
+
+ list_delete(&ll);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY_YANG(vrrp_vrid_show_summary,
+ vrrp_vrid_show_summary_cmd,
+ "show vrrp [interface INTERFACE$ifn] [(1-255)$vrid] summary",
+ SHOW_STR
+ VRRP_STR
+ INTERFACE_STR
+ "Only show VRRP instances on this interface\n"
+ VRRP_VRID_STR
+ "Summarize all VRRP instances\n")
+{
+ struct vrrp_vrouter *vr;
+ struct listnode *ln;
+ struct list *ll = hash_to_list(vrrp_vrouters_hash);
+
+ list_sort(ll, vrrp_instance_display_sort_cmp);
+
+ struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+
+ ttable_add_row(
+ tt, "Interface|VRID|Priority|IPv4|IPv6|State (v4)|State (v6)");
+ ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+ for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) {
+ if (ifn && !strmatch(ifn, vr->ifp->name))
+ continue;
+ if (vrid && ((uint8_t)vrid) != vr->vrid)
+ continue;
+
+ ttable_add_row(
+ tt, "%s|%u|%hhu|%d|%d|%s|%s",
+ vr->ifp->name, vr->vrid, vr->priority,
+ vr->v4->addrs->count, vr->v6->addrs->count,
+ vr->v4->fsm.state == VRRP_STATE_MASTER ? "Master"
+ : "Backup",
+ vr->v6->fsm.state == VRRP_STATE_MASTER ? "Master"
+ : "Backup");
+ }
+
+ char *table = ttable_dump(tt, "\n");
+
+ vty_out(vty, "\n%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ ttable_del(tt);
+
+ list_delete(&ll);
+
+ return CMD_SUCCESS;
+}
+
+
+DEFPY_YANG(debug_vrrp,
+ debug_vrrp_cmd,
+ "[no] debug vrrp [{protocol$proto|autoconfigure$ac|packets$pkt|sockets$sock|ndisc$ndisc|arp$arp|zebra$zebra}]",
+ NO_STR
+ DEBUG_STR
+ VRRP_STR
+ "Debug protocol state\n"
+ "Debug autoconfiguration\n"
+ "Debug sent and received packets\n"
+ "Debug socket creation and configuration\n"
+ "Debug Neighbor Discovery\n"
+ "Debug ARP\n"
+ "Debug Zebra events\n")
+{
+ /* If no specific are given on/off them all */
+ if (strmatch(argv[argc - 1]->text, "vrrp"))
+ vrrp_debug_set(NULL, 0, vty->node, !no, true, true, true, true,
+ true, true, true);
+ else
+ vrrp_debug_set(NULL, 0, vty->node, !no, !!proto, !!ac, !!pkt,
+ !!sock, !!ndisc, !!arp, !!zebra);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH (show_debugging_vrrp,
+ show_debugging_vrrp_cmd,
+ "show debugging [vrrp]",
+ SHOW_STR
+ DEBUG_STR
+ "VRRP information\n")
+{
+ vty_out(vty, "VRRP debugging status:\n");
+
+ vrrp_debug_status_write(vty);
+
+ cmd_show_lib_debugs(vty);
+
+ return CMD_SUCCESS;
+}
+
+/* clang-format on */
+
+static struct cmd_node debug_node = {
+ .name = "debug",
+ .node = DEBUG_NODE,
+ .prompt = "",
+ .config_write = vrrp_config_write_debug,
+};
+
+static struct cmd_node vrrp_node = {
+ .name = "vrrp",
+ .node = VRRP_NODE,
+ .prompt = "",
+ .config_write = vrrp_config_write_global,
+};
+
+void vrrp_vty_init(void)
+{
+ install_node(&debug_node);
+ install_node(&vrrp_node);
+ vrf_cmd_init(NULL);
+ if_cmd_init_default();
+
+ install_element(VIEW_NODE, &vrrp_vrid_show_cmd);
+ install_element(VIEW_NODE, &vrrp_vrid_show_summary_cmd);
+ install_element(ENABLE_NODE, &show_debugging_vrrp_cmd);
+ install_element(ENABLE_NODE, &debug_vrrp_cmd);
+ install_element(CONFIG_NODE, &debug_vrrp_cmd);
+ install_element(CONFIG_NODE, &vrrp_autoconfigure_cmd);
+ install_element(CONFIG_NODE, &vrrp_default_cmd);
+ install_element(INTERFACE_NODE, &vrrp_vrid_cmd);
+ install_element(INTERFACE_NODE, &vrrp_shutdown_cmd);
+ install_element(INTERFACE_NODE, &vrrp_priority_cmd);
+ install_element(INTERFACE_NODE, &no_vrrp_priority_cmd);
+ install_element(INTERFACE_NODE, &vrrp_advertisement_interval_cmd);
+ install_element(INTERFACE_NODE, &no_vrrp_advertisement_interval_cmd);
+ install_element(INTERFACE_NODE, &vrrp_ip_cmd);
+ install_element(INTERFACE_NODE, &vrrp_ip6_cmd);
+ install_element(INTERFACE_NODE, &vrrp_preempt_cmd);
+ install_element(INTERFACE_NODE,
+ &vrrp_checksum_with_ipv4_pseudoheader_cmd);
+}
diff --git a/vrrpd/vrrp_vty.h b/vrrpd/vrrp_vty.h
new file mode 100644
index 0000000..6feb8e8
--- /dev/null
+++ b/vrrpd/vrrp_vty.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP CLI commands.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#ifndef __VRRP_VTY_H__
+#define __VRRP_VTY_H__
+
+#include "lib/northbound.h"
+
+void vrrp_vty_init(void);
+
+/* Northbound callbacks */
+void cli_show_vrrp(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_shutdown(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_priority(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_advertisement_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ip(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_ipv6(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_preempt(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void cli_show_checksum_with_ipv4_pseudoheader(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+
+#endif /* __VRRP_VTY_H__ */
diff --git a/vrrpd/vrrp_zebra.c b/vrrpd/vrrp_zebra.c
new file mode 100644
index 0000000..6d753d2
--- /dev/null
+++ b/vrrpd/vrrp_zebra.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP Zebra interfacing.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#include <zebra.h>
+
+#include "lib/if.h"
+#include "lib/linklist.h"
+#include "lib/log.h"
+#include "lib/prefix.h"
+#include "lib/vty.h"
+#include "lib/zclient.h"
+
+#include "vrrp.h"
+#include "vrrp_debug.h"
+#include "vrrp_zebra.h"
+
+#define VRRP_LOGPFX "[ZEBRA] "
+
+static struct zclient *zclient;
+
+static void vrrp_zebra_debug_if_state(struct interface *ifp, const char *func)
+{
+ DEBUGD(&vrrp_dbg_zebra,
+ "%s: %s index %d vrf %s(%u) parent %d mac %02x:%02x:%02x:%02x:%02x:%02x flags %ld metric %d mtu %d operative %d",
+ func, ifp->name, ifp->ifindex, ifp->vrf->name, ifp->vrf->vrf_id,
+ ifp->link_ifindex, ifp->hw_addr[0], ifp->hw_addr[1],
+ ifp->hw_addr[2], ifp->hw_addr[3], ifp->hw_addr[4],
+ ifp->hw_addr[5], (long)ifp->flags, ifp->metric, ifp->mtu,
+ if_is_operative(ifp));
+}
+
+static void vrrp_zebra_debug_if_dump_address(struct interface *ifp,
+ const char *func)
+{
+ struct connected *ifc;
+ struct listnode *node;
+
+ DEBUGD(&vrrp_dbg_zebra, "%s: interface %s addresses:", func, ifp->name);
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) {
+ struct prefix *p = ifc->address;
+
+ DEBUGD(&vrrp_dbg_zebra, "%s: interface %s address %pFX %s",
+ func, ifp->name, p,
+ CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) ? "secondary"
+ : "primary");
+ }
+}
+
+
+static void vrrp_zebra_connected(struct zclient *zclient)
+{
+ zclient_send_reg_requests(zclient, VRF_DEFAULT);
+}
+
+/* Router-id update message from zebra. */
+static int vrrp_router_id_update_zebra(int command, struct zclient *zclient,
+ zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct prefix router_id;
+
+ zebra_router_id_update_read(zclient->ibuf, &router_id);
+
+ return 0;
+}
+
+int vrrp_ifp_create(struct interface *ifp)
+{
+ vrrp_zebra_debug_if_state(ifp, __func__);
+
+ vrrp_if_add(ifp);
+
+ return 0;
+}
+
+int vrrp_ifp_destroy(struct interface *ifp)
+{
+ vrrp_zebra_debug_if_state(ifp, __func__);
+
+ vrrp_if_del(ifp);
+
+ return 0;
+}
+
+int vrrp_ifp_up(struct interface *ifp)
+{
+ vrrp_zebra_debug_if_state(ifp, __func__);
+
+ vrrp_if_up(ifp);
+
+ return 0;
+}
+
+int vrrp_ifp_down(struct interface *ifp)
+{
+ vrrp_zebra_debug_if_state(ifp, __func__);
+
+ vrrp_if_down(ifp);
+
+ return 0;
+}
+
+static int vrrp_zebra_if_address_add(int command, struct zclient *zclient,
+ zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct connected *c;
+
+ /*
+ * zebra api notifies address adds/dels events by using the same call
+ * interface_add_read below, see comments in lib/zclient.c
+ *
+ * zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, ...)
+ * will add address to interface list by calling
+ * connected_add_by_prefix()
+ */
+ c = zebra_interface_address_read(command, zclient->ibuf, vrf_id);
+
+ if (!c)
+ return 0;
+
+ vrrp_zebra_debug_if_state(c->ifp, __func__);
+ vrrp_zebra_debug_if_dump_address(c->ifp, __func__);
+
+ vrrp_if_address_add(c->ifp);
+
+ return 0;
+}
+
+static int vrrp_zebra_if_address_del(int command, struct zclient *client,
+ zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct connected *c;
+
+ /*
+ * zebra api notifies address adds/dels events by using the same call
+ * interface_add_read below, see comments in lib/zclient.c
+ *
+ * zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, ...)
+ * will remove address from interface list by calling
+ * connected_delete_by_prefix()
+ */
+ c = zebra_interface_address_read(command, client->ibuf, vrf_id);
+
+ if (!c)
+ return 0;
+
+ vrrp_zebra_debug_if_state(c->ifp, __func__);
+ vrrp_zebra_debug_if_dump_address(c->ifp, __func__);
+
+ vrrp_if_address_del(c->ifp);
+
+ return 0;
+}
+
+void vrrp_zebra_radv_set(struct vrrp_router *r, bool enable)
+{
+ DEBUGD(&vrrp_dbg_zebra,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID
+ "Requesting Zebra to turn router advertisements %s for %s",
+ r->vr->vrid, enable ? "on" : "off", r->mvl_ifp->name);
+
+ zclient_send_interface_radv_req(zclient, r->mvl_ifp->vrf->vrf_id,
+ r->mvl_ifp, enable, VRRP_RADV_INT);
+}
+
+void vrrp_zclient_send_interface_protodown(struct interface *ifp, bool down)
+{
+ DEBUGD(&vrrp_dbg_zebra,
+ VRRP_LOGPFX "Requesting Zebra to set %s protodown %s", ifp->name,
+ down ? "on" : "off");
+
+ zclient_send_interface_protodown(zclient, ifp->vrf->vrf_id, ifp, down);
+}
+
+static zclient_handler *const vrrp_handlers[] = {
+ [ZEBRA_ROUTER_ID_UPDATE] = vrrp_router_id_update_zebra,
+ [ZEBRA_INTERFACE_ADDRESS_ADD] = vrrp_zebra_if_address_add,
+ [ZEBRA_INTERFACE_ADDRESS_DELETE] = vrrp_zebra_if_address_del,
+};
+
+void vrrp_zebra_init(void)
+{
+ if_zapi_callbacks(vrrp_ifp_create, vrrp_ifp_up,
+ vrrp_ifp_down, vrrp_ifp_destroy);
+
+ /* Socket for receiving updates from Zebra daemon */
+ zclient = zclient_new(master, &zclient_options_default, vrrp_handlers,
+ array_size(vrrp_handlers));
+
+ zclient->zebra_connected = vrrp_zebra_connected;
+
+ zclient_init(zclient, ZEBRA_ROUTE_VRRP, 0, &vrrp_privs);
+
+ zlog_notice("%s: zclient socket initialized", __func__);
+}
diff --git a/vrrpd/vrrp_zebra.h b/vrrpd/vrrp_zebra.h
new file mode 100644
index 0000000..5e59256
--- /dev/null
+++ b/vrrpd/vrrp_zebra.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP Zebra interfacing.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#ifndef __VRRP_ZEBRA_H__
+#define __VRRP_ZEBRA_H__
+
+#include <zebra.h>
+
+#include "lib/if.h"
+
+extern void vrrp_zebra_init(void);
+extern void vrrp_zebra_radv_set(struct vrrp_router *r, bool enable);
+extern void vrrp_zclient_send_interface_protodown(struct interface *ifp,
+ bool down);
+
+extern int vrrp_ifp_create(struct interface *ifp);
+extern int vrrp_ifp_up(struct interface *ifp);
+extern int vrrp_ifp_down(struct interface *ifp);
+extern int vrrp_ifp_destroy(struct interface *ifp);
+
+#endif /* __VRRP_ZEBRA_H__ */