diff options
Diffstat (limited to 'vrrpd')
-rw-r--r-- | vrrpd/.gitignore | 2 | ||||
-rw-r--r-- | vrrpd/Makefile | 10 | ||||
-rw-r--r-- | vrrpd/subdir.am | 40 | ||||
-rw-r--r-- | vrrpd/vrrp.c | 2421 | ||||
-rw-r--r-- | vrrpd/vrrp.h | 565 | ||||
-rw-r--r-- | vrrpd/vrrp_arp.c | 206 | ||||
-rw-r--r-- | vrrpd/vrrp_arp.h | 23 | ||||
-rw-r--r-- | vrrpd/vrrp_debug.c | 118 | ||||
-rw-r--r-- | vrrpd/vrrp_debug.h | 74 | ||||
-rw-r--r-- | vrrpd/vrrp_main.c | 159 | ||||
-rw-r--r-- | vrrpd/vrrp_ndisc.c | 230 | ||||
-rw-r--r-- | vrrpd/vrrp_ndisc.h | 61 | ||||
-rw-r--r-- | vrrpd/vrrp_northbound.c | 814 | ||||
-rw-r--r-- | vrrpd/vrrp_packet.c | 316 | ||||
-rw-r--r-- | vrrpd/vrrp_packet.h | 190 | ||||
-rw-r--r-- | vrrpd/vrrp_vty.c | 781 | ||||
-rw-r--r-- | vrrpd/vrrp_vty.h | 34 | ||||
-rw-r--r-- | vrrpd/vrrp_zebra.c | 198 | ||||
-rw-r--r-- | vrrpd/vrrp_zebra.h | 24 |
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__ */ |