summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSri Mohana Singamsetty <srimohans@gmail.com>2019-05-17 11:39:27 -0700
committerGitHub <noreply@github.com>2019-05-17 11:39:27 -0700
commit7cfaf4b3394de78bc1a48b22d84f118a80f88b98 (patch)
tree87ddfdf3ed07c95ca01c470b49dfaa46b26ea2ce
parent02f4c3ab5b33f5f17b592ef7787797a0e43d0785 (diff)
parent53ca01e52c487954f68ab7a76f163cf465fb9209 (diff)
Merge pull request #4168 from qlyoung/vrrp
VRRP
-rw-r--r--Makefile.am3
-rwxr-xr-xconfigure.ac3
-rw-r--r--doc/extra/spelling_wordlist.txt6
-rw-r--r--doc/manpages/common-options.rst1
-rw-r--r--doc/manpages/conf.py1
-rw-r--r--doc/manpages/defines.rst2
-rw-r--r--doc/manpages/index.rst1
-rw-r--r--doc/manpages/subdir.am1
-rw-r--r--doc/manpages/vrrpd.rst40
-rw-r--r--doc/user/index.rst1
-rw-r--r--doc/user/subdir.am1
-rw-r--r--doc/user/vrrp.rst506
-rw-r--r--lib/checksum.c18
-rw-r--r--lib/checksum.h27
-rw-r--r--lib/command.c1
-rw-r--r--lib/command.h1
-rw-r--r--lib/if.c41
-rw-r--r--lib/if.h7
-rw-r--r--lib/ipaddr.h3
-rw-r--r--lib/json.c5
-rw-r--r--lib/json.h2
-rw-r--r--lib/linklist.c15
-rw-r--r--lib/linklist.h20
-rw-r--r--lib/route_types.txt2
-rw-r--r--lib/sockunion.c2
-rw-r--r--lib/sockunion.h1
-rw-r--r--lib/zclient.c22
-rw-r--r--lib/zclient.h4
-rw-r--r--redhat/frr.spec.in20
-rw-r--r--tools/etc/frr/daemons2
-rw-r--r--tools/etc/rsyslog.d/45-frr.conf2
-rwxr-xr-xtools/frr-reload.py3
-rwxr-xr-xtools/frr.in2
-rw-r--r--tools/frrcommon.sh.in2
-rw-r--r--vrrpd/.gitignore2
-rw-r--r--vrrpd/Makefile10
-rw-r--r--vrrpd/subdir.am39
-rw-r--r--vrrpd/vrrp.c2379
-rw-r--r--vrrpd/vrrp.h570
-rw-r--r--vrrpd/vrrp_arp.c211
-rw-r--r--vrrpd/vrrp_arp.h36
-rw-r--r--vrrpd/vrrp_debug.c131
-rw-r--r--vrrpd/vrrp_debug.h87
-rw-r--r--vrrpd/vrrp_main.c159
-rw-r--r--vrrpd/vrrp_memory.c29
-rw-r--r--vrrpd/vrrp_memory.h32
-rw-r--r--vrrpd/vrrp_ndisc.c242
-rw-r--r--vrrpd/vrrp_ndisc.h74
-rw-r--r--vrrpd/vrrp_packet.c321
-rw-r--r--vrrpd/vrrp_packet.h202
-rw-r--r--vrrpd/vrrp_vty.c751
-rw-r--r--vrrpd/vrrp_vty.h25
-rw-r--r--vrrpd/vrrp_zebra.c252
-rw-r--r--vrrpd/vrrp_zebra.h32
-rw-r--r--vtysh/vtysh.c1
-rw-r--r--vtysh/vtysh.h5
-rw-r--r--vtysh/vtysh_config.c4
-rw-r--r--zebra/if_netlink.c26
-rw-r--r--zebra/if_netlink.h14
-rw-r--r--zebra/interface.c10
-rw-r--r--zebra/interface.h1
-rw-r--r--zebra/zapi_msg.c35
-rw-r--r--zebra/zebra_rib.c1
63 files changed, 6438 insertions, 11 deletions
diff --git a/Makefile.am b/Makefile.am
index 11188ea157..166ac29d1c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -147,6 +147,7 @@ include staticd/subdir.am
include bfdd/subdir.am
include yang/subdir.am
include yang/libyang_plugins/subdir.am
+include vrrpd/subdir.am
include vtysh/subdir.am
include tests/subdir.am
@@ -188,7 +189,6 @@ EXTRA_DIST += \
snapcraft/defaults \
snapcraft/helpers \
snapcraft/snap \
- \
babeld/Makefile \
bgpd/Makefile \
bgpd/rfp-example/librfp/Makefile \
@@ -218,6 +218,7 @@ EXTRA_DIST += \
vtysh/Makefile \
watchfrr/Makefile \
zebra/Makefile \
+ vrrpd/Makefile \
# end
noinst_HEADERS += defaults.h
diff --git a/configure.ac b/configure.ac
index b7ddf87b43..c228ff0c91 100755
--- a/configure.ac
+++ b/configure.ac
@@ -443,6 +443,8 @@ AC_ARG_ENABLE([fabricd],
AS_HELP_STRING([--disable-fabricd], [do not build fabricd]))
AC_ARG_ENABLE([bgp-announce],
AS_HELP_STRING([--disable-bgp-announce,], [turn off BGP route announcement]))
+AC_ARG_ENABLE([vrrpd],
+ AS_HELP_STRING([--disable-vrrpd], [do not build vrrpd]))
AC_ARG_ENABLE([bgp-vnc],
AS_HELP_STRING([--disable-bgp-vnc],[turn off BGP VNC support]))
AC_ARG_ENABLE([snmp],
@@ -1602,6 +1604,7 @@ AM_CONDITIONAL([PBRD], [test "${enable_pbrd}" != "no"])
AM_CONDITIONAL([SHARPD], [test "${enable_sharpd}" = "yes"])
AM_CONDITIONAL([STATICD], [test "${enable_staticd}" != "no"])
AM_CONDITIONAL([FABRICD], [test "${enable_fabricd}" != "no"])
+AM_CONDITIONAL([VRRPD], [test "${enable_vrrpd}" != "no"])
if test "${enable_bgp_announce}" = "no";then
AC_DEFINE([DISABLE_BGP_ANNOUNCE], [1], [Disable BGP installation to zebra])
diff --git a/doc/extra/spelling_wordlist.txt b/doc/extra/spelling_wordlist.txt
index 2944592962..271f5e49f1 100644
--- a/doc/extra/spelling_wordlist.txt
+++ b/doc/extra/spelling_wordlist.txt
@@ -80,6 +80,9 @@ IP
iptables
ipv
IPv
+IPvX
+IPv4
+IPv6
isis
isisd
lan
@@ -99,6 +102,8 @@ LSAs
Masaki
Mbit
Mbits
+macvlan
+macvlans
mib
motd
mpls
@@ -227,6 +232,7 @@ VN
VNC
vrf
vrfs
+vrrp
vty
Vty
vtysh
diff --git a/doc/manpages/common-options.rst b/doc/manpages/common-options.rst
index a5977a6ebb..a47a233c08 100644
--- a/doc/manpages/common-options.rst
+++ b/doc/manpages/common-options.rst
@@ -126,6 +126,7 @@ These following options control the daemon's VTY (interactive command line) inte
staticd 2616
bfdd 2617
fabricd 2618
+ vrrpd 2619
Port 2607 is used for ospfd's Opaque LSA API.
diff --git a/doc/manpages/conf.py b/doc/manpages/conf.py
index 46240de1c0..e7813d8176 100644
--- a/doc/manpages/conf.py
+++ b/doc/manpages/conf.py
@@ -334,6 +334,7 @@ man_pages = [
('frr', 'frr', 'a systemd interaction script', [], 1),
('bfdd', 'bfdd', fwfrr.format("a bfd"), [], 8),
('fabricd', 'fabricd', fwfrr.format("an OpenFabric "), [], 8),
+ ('vrrpd', 'vrrpd', fwfrr.format("a VRRP"), [], 8),
]
# -- Options for Texinfo output -------------------------------------------
diff --git a/doc/manpages/defines.rst b/doc/manpages/defines.rst
index cdf5e1967e..2a6a9fd1bd 100644
--- a/doc/manpages/defines.rst
+++ b/doc/manpages/defines.rst
@@ -1,3 +1,3 @@
.. |synopsis-options| replace:: [-d|-t|-dt] [-C] [-f config-file] [-i pid-file] [-z zclient-path] [-u user] [-g group] [-A vty-addr] [-P vty-port] [-M module[:options]] [-N pathspace] [--vty_socket vty-path] [--moduledir module-path]
.. |synopsis-options-hv| replace:: [-h] [-v]
-.. |seealso-programs| replace:: zebra(8), vtysh(1), ripd(8), ripngd(8), ospfd(8), ospf6d(8), bgpd(8), isisd(8), babeld(8), nhrpd(8), pimd(8), pbrd(8), ldpd(8), eigrpd(8), staticd(8), fabricd(8), mtracebis(8)
+.. |seealso-programs| replace:: zebra(8), vtysh(1), ripd(8), ripngd(8), ospfd(8), ospf6d(8), bgpd(8), isisd(8), babeld(8), nhrpd(8), pimd(8), pbrd(8), ldpd(8), eigrpd(8), staticd(8), fabricd(8), vrrpd(8), mtracebis(8)
diff --git a/doc/manpages/index.rst b/doc/manpages/index.rst
index 053555c4e4..40f06efdfe 100644
--- a/doc/manpages/index.rst
+++ b/doc/manpages/index.rst
@@ -26,4 +26,5 @@
watchfrr
zebra
vtysh
+ vrrpd
frr
diff --git a/doc/manpages/subdir.am b/doc/manpages/subdir.am
index a4457c9c45..19d2d8d6ae 100644
--- a/doc/manpages/subdir.am
+++ b/doc/manpages/subdir.am
@@ -30,6 +30,7 @@ man_RSTFILES = \
doc/manpages/zebra.rst \
doc/manpages/bfdd.rst \
doc/manpages/bfd-options.rst \
+ doc/manpages/vrrpd.rst \
# end
EXTRA_DIST += $(man_RSTFILES)
diff --git a/doc/manpages/vrrpd.rst b/doc/manpages/vrrpd.rst
new file mode 100644
index 0000000000..0e73b07cda
--- /dev/null
+++ b/doc/manpages/vrrpd.rst
@@ -0,0 +1,40 @@
+*****
+VRRPD
+*****
+
+.. include:: defines.rst
+.. |DAEMON| replace:: vrrpd
+
+SYNOPSIS
+========
+|DAEMON| |synopsis-options-hv|
+
+|DAEMON| |synopsis-options|
+
+DESCRIPTION
+===========
+|DAEMON| is a routing component that works with the FRRouting routing engine.
+It implements the Virtual Router Redundancy Protocol. Support for both VRRPv2
+and VRRPv3 is present.
+
+OPTIONS
+=======
+OPTIONS available for the |DAEMON| command:
+
+.. include:: common-options.rst
+
+FILES
+=====
+
+|INSTALL_PREFIX_SBIN|/|DAEMON|
+ The default location of the |DAEMON| binary.
+
+|INSTALL_PREFIX_ETC|/|DAEMON|.conf
+ The default location of the |DAEMON| config file.
+
+$(PWD)/|DAEMON|.log
+ If the |DAEMON| process is configured to output logs to a file, then you
+ will find this file in the directory where you started |DAEMON|.
+
+.. include:: epilogue.rst
+
diff --git a/doc/user/index.rst b/doc/user/index.rst
index 4c218c6580..4e14de6737 100644
--- a/doc/user/index.rst
+++ b/doc/user/index.rst
@@ -56,6 +56,7 @@ Protocols
sharp
static
vnc
+ vrrp
########
Appendix
diff --git a/doc/user/subdir.am b/doc/user/subdir.am
index 08b5dc954c..1e4d86c722 100644
--- a/doc/user/subdir.am
+++ b/doc/user/subdir.am
@@ -37,6 +37,7 @@ user_RSTFILES = \
doc/user/snmptrap.rst \
doc/user/static.rst \
doc/user/vnc.rst \
+ doc/user/vrrp.rst \
doc/user/vtysh.rst \
doc/user/zebra.rst \
doc/user/bfd.rst \
diff --git a/doc/user/vrrp.rst b/doc/user/vrrp.rst
new file mode 100644
index 0000000000..a2dd950987
--- /dev/null
+++ b/doc/user/vrrp.rst
@@ -0,0 +1,506 @@
+.. _vrrp:
+
+****
+VRRP
+****
+
+:abbr:`VRRP` stands for Virtual Router Redundancy Protocol. This protocol is
+used to allow multiple backup routers on the same segment to take over
+operation of each others' IP addresses if the primary router fails. This is
+typically used to provide fault-tolerant gateways to hosts on the segment.
+
+FRR implements VRRPv2 (:rfc:`3768`) and VRRPv3 (:rfc:`5798`). For VRRPv2, no
+authentication methods are supported; these are deprecated in the VRRPv2
+specification as they do not provide any additional security over the base
+protocol.
+
+.. note::
+
+ - VRRP is supported on Linux 5.1+
+ - VRRP does not implement Accept_Mode
+
+.. _vrrp-starting:
+
+Starting VRRP
+=============
+
+The configuration file for *vrrpd* is :file:`vrrpd.conf`. The typical location
+of :file:`vrrpd.conf` is |INSTALL_PREFIX_ETC|/vrrpd.conf.
+
+If using integrated config, then :file:`vrrpd.conf` need not be present and
+:file:`frr.conf` is read instead.
+
+.. program:: vrrpd
+
+:abbr:`VRRP` supports all the common FRR daemon start options which are
+documented elsewhere.
+
+.. _vrrp-protocol-overview:
+
+Protocol Overview
+=================
+
+From :rfc:`5798`:
+
+ VRRP specifies an election protocol that dynamically assigns responsibility
+ for a virtual router to one of the VRRP routers on a LAN. The VRRP router
+ controlling the IPv4 or IPv6 address(es) associated with a virtual router is
+ called the Master, and it forwards packets sent to these IPv4 or IPv6
+ addresses. VRRP Master routers are configured with virtual IPv4 or IPv6
+ addresses, and VRRP Backup routers infer the address family of the virtual
+ addresses being carried based on the transport protocol. 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. The election process
+ provides dynamic failover in the forwarding responsibility should the Master
+ become unavailable. For IPv4, the advantage gained from using VRRP is a
+ higher-availability default path without requiring configuration of dynamic
+ routing or router discovery protocols on every end-host. For IPv6, the
+ advantage gained from using VRRP for IPv6 is a quicker switchover to Backup
+ routers than can be obtained with standard IPv6 Neighbor Discovery
+ mechanisms.
+
+VRRP accomplishes these goals primarily by using a virtual MAC address shared
+between the physical routers participating in a VRRP virtual router. This
+reduces churn in the neighbor tables of hosts and downstream switches and makes
+router failover theoretically transparent to these devices.
+
+FRR implements the election protocol and handles changing the operating system
+interface configuration in response to protocol state changes.
+
+As a consequence of the shared virtual MAC requirement, VRRP is currently
+supported only on Linux, as Linux is the only operating system that provides
+the necessary features in its network stack to make implementing this protocol
+feasible.
+
+When a VRRP router is acting as the Master router, FRR allows the interface(s)
+with the backed-up IP addresses to remain up and functional. When the router
+transitions to Backup state, these interfaces are set into ``protodown`` mode.
+This is an interface mode that is functionally equivalent to ``NO-CARRIER``.
+Physical drivers typically use this state indication to drop traffic on an
+interface. In the case of VRRP, the interfaces in question are macvlan devices,
+which are virtual interfaces. Since the IP addresses managed by VRRP are on
+these interfaces, this has the same effect as removing these addresses from the
+interface, but is implemented as a state flag.
+
+.. _vrrp-configuration:
+
+Configuring VRRP
+================
+
+VRRP is configured on a per-interface basis, with some global defaults
+accessible outside the interface context.
+
+.. _vrrp-system-configuration:
+
+System Configuration
+--------------------
+
+FRR's VRRP implementation uses Linux macvlan devices to to implement the shared
+virtual MAC feature of the protocol. Currently, it does not create those system
+interfaces - they must be configured outside of FRR before VRRP can be enabled
+on them.
+
+Each interface on which VRRP will be enabled must have at least one macvlan
+device configured with the virtual MAC and placed in the proper operation mode.
+The addresses backed up by VRRP are assigned to these interfaces.
+
+Suppose you have an interface ``eth0`` with the following configuration:
+
+.. code-block:: console
+
+ $ ip link show eth0
+ 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
+ link/ether 02:17:45:00:aa:aa brd ff:ff:ff:ff:ff:ff
+ inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic eth0
+ valid_lft 72532sec preferred_lft 72532sec
+ inet 10.0.2.16/24 brd 10.0.2.255 scope global dynamic eth0
+ valid_lft 72532sec preferred_lft 72532sec
+ inet6 fe80::17:45ff:fe00:aaaa/64 scope link
+ valid_lft forever preferred_lft forever
+
+Suppose the address you want to back up is ``10.0.2.16``, and will be managed
+by the virtual router with id ``5``. A macvlan device with the appropriate MAC
+address must be created before VRRP can begin to operate.
+
+If you are using ``ifupdown2``, the configuration is as follows:
+
+.. code-block:: console
+
+ iface eth0
+ ...
+ vrrp 5 10.0.2.16/24 2001:0db8::0370:7334/64
+
+Applying this configuration with ``ifreload -a`` will create the appropriate
+macvlan device. If you are using ``iproute2``, the equivalent configuration is:
+
+.. code-block:: console
+
+ ip link add vrrp4-2-1 link eth0 addrgenmode random type macvlan mode bridge
+ ip link set dev vrrp4-2-1 address 00:00:5e:00:01:05
+ ip addr add 10.0.2.16/24 dev vrrp4-2-1
+ ip link set dev vrrp4-2-1 up
+
+ ip link add vrrp6-2-1 link eth0 addrgenmode random type macvlan mode bridge
+ ip link set dev vrrp4-2-1 address 00:00:5e:00:02:05
+ ip addr add 2001:db8::370:7334/64 dev vrrp6-2-1
+ ip link set dev vrrp6-2-1 up
+
+In either case, the created interfaces will look like this:
+
+.. code-block:: console
+
+ $ ip addr show vrrp4-2-1
+ 5: vrrp4-2-1@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
+ link/ether 00:00:5e:00:01:05 brd ff:ff:ff:ff:ff:ff
+ inet 10.0.2.16/24 scope global vrrp4-2-1
+ valid_lft forever preferred_lft forever
+ inet6 fe80::dc56:d11a:e69d:ea72/64 scope link stable-privacy
+ valid_lft forever preferred_lft forever
+
+ $ ip addr show vrrp6-2-1
+ 8: vrrp6-2-1@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
+ link/ether 00:00:5e:00:02:05 brd ff:ff:ff:ff:ff:ff
+ inet6 2001:db8::370:7334/64 scope global
+ valid_lft forever preferred_lft forever
+ inet6 fe80::f8b7:c9dd:a1e8:9844/64 scope link stable-privacy
+ valid_lft forever preferred_lft forever
+
+Using ``vrrp4-2-1`` as an example, a few things to note about this interface:
+
+- It is slaved to ``eth0``; any packets transmitted on this interface will
+ egress via ``eth0``
+- Its MAC address is set to the VRRP IPv4 virtual MAC specified by the RFC for
+ :abbr:`VRID (Virtual Router ID)` ``5``
+- The link local address on the interface is not derived from the interface
+ MAC
+
+First to note is that packets transmitted on this interface will egress via
+``eth0``, but with their Ethernet source MAC set to the VRRP virtual MAC. This
+is how FRR's VRRP implementation accomplishes the virtual MAC requirement on
+real hardware.
+
+Ingress traffic is a more complicated matter. Macvlan devices have multiple
+operating modes that change how ingress traffic is handled. Of relevance to
+FRR's implementation are the ``bridge`` and ``private`` modes. In ``private``
+mode, any ingress traffic on ``eth0`` (in our example) with a source MAC
+address equal to the MAC address on any of ``eth0``'s macvlan devices will be
+placed *only* on that macvlan device. This curious behavior is undesirable,
+since FRR's implementation of VRRP needs to be able to receive advertisements
+from neighbors while in Backup mode - i.e., while its macvlan devices are in
+``protodown on``. If the macvlan devices are instead set to ``bridge`` mode,
+all ingress traffic shows up on all interfaces - including ``eth0`` -
+regardless of source MAC or any other factor. Consequently, macvlans used by
+FRR for VRRP must be set to ``bridge`` mode or the protocol will not function
+correctly.
+
+As for the MAC address assigned to this interface, the last byte of the address
+holds the :abbr:`VRID (Virtual Router Identifier)`, in this case ``0x05``. The
+second to last byte is ``0x01``, as specified by the RFC for IPv4 operation.
+The IPv6 MAC address is be identical except that the second to last byte is
+defined to be ``0x02``. Two things to note from this arrangement:
+
+1. There can only be up to 255 unique Virtual Routers on an interface (only 1
+ byte is available for the VRID)
+2. IPv4 and IPv6 addresses must be assigned to different macvlan devices,
+ because they have different MAC addresses
+
+Finally, take note of the generated IPv6 link local address on the interface.
+For interfaces on which VRRP will operate in IPv6 mode, this link local
+*cannot* be derived using the usual EUI-64 method. This is because VRRP
+advertisements are sent from the link local address of this interface, and VRRP
+uses the source address of received advertisements as part of its election
+algorithm. If the IPv6 link local of a router is equivalent to the IPv6 link
+local in a received advertisement, this can cause both routers to assume the
+Master role (very bad). ``ifupdown`` knows to set the ``addrgenmode`` of the
+interface properly, but when using ``iproute2`` to create the macvlan devices,
+you must be careful to manually specify ``addrgenmode random``.
+
+A brief note on the Backup state
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+It is worth noting here that an alternate choice for the implementation of the
+Backup state, such as removing all the IP addresses assigned to the macvlan
+device or deleting their local routes instead of setting the device into
+``protodown on``, would allow the protocol to function regardless of whether
+the macvlan device(s) are set to ``private`` or ``bridge`` mode. Indeed, the
+strange behavior of the kernel macvlan driver in ``private`` mode, whereby it
+performs what may be thought of as a sort of interface-level layer 2 "NAT"
+based on source MAC, can be traced back to a patch clearly designed to
+accommodate a VRRP implementation from a different vendor. However, the
+``protodown`` based implementation allows for a configuration model in which
+FRR does not dynamically manage the addresses assigned on a system, but instead
+just manages interface state. Such a scenario was in mind when this protocol
+implementation was initially built, which is why the other choices are not
+currently present. Since support for placing macvlan devices into ``protodown``
+was not added to Linux until version 5.1, this also explains the relatively
+restrictive kernel versioning requirement.
+
+In the future other methods of implementing Backup state may be added along
+with a configuration knob to choose between them.
+
+.. _vrrp-interface-configuration:
+
+Interface Configuration
+-----------------------
+
+Continuing with the example from the previous section, we assume the macvlan
+interfaces have been properly configured with the proper MAC addresses and the
+IPvX addresses assigned.
+
+In FRR, a possible VRRPv3 configuration for this interface is:
+
+.. code-block:: frr
+
+ interface eth0
+ vrrp 5 version 3
+ vrrp 5 priority 200
+ vrrp 5 advertisement-interval 1500
+ vrrp 5 ip 10.0.2.16
+ vrrp 5 ipv6 2001:0db8::0370:7334
+
+VRRP will activate as soon as the first IPvX address configuration line is
+encountered. If you do not want this behavior, use the :clicmd:`vrrp (1-255)
+shutdown` command, and apply the ``no`` form when you are ready to activate
+VRRP.
+
+At this point executing ``show vrrp`` will display the following:
+
+.. code-block:: console
+
+ ubuntu-bionic# show vrrp
+
+ Virtual Router ID 5
+ Protocol Version 3
+ Autoconfigured Yes
+ Shutdown No
+ Interface eth0
+ VRRP interface (v4) vrrp4-2-5
+ VRRP interface (v6) vrrp6-2-5
+ Primary IP (v4) 10.0.2.15
+ Primary IP (v6) fe80::9b91:7155:bf6a:d386
+ Virtual MAC (v4) 00:00:5e:00:01:05
+ Virtual MAC (v6) 00:00:5e:00:02:05
+ Status (v4) Master
+ Status (v6) Master
+ Priority 200
+ Effective Priority (v4) 200
+ Effective Priority (v6) 200
+ Preempt Mode Yes
+ Accept Mode Yes
+ Advertisement Interval 1500 ms
+ Master Advertisement Interval (v4) 1000 ms
+ Master Advertisement Interval (v6) 1000 ms
+ Advertisements Tx (v4) 14
+ Advertisements Tx (v6) 14
+ Advertisements Rx (v4) 0
+ Advertisements Rx (v6) 0
+ Gratuitous ARP Tx (v4) 1
+ Neigh. Adverts Tx (v6) 1
+ State transitions (v4) 2
+ State transitions (v6) 2
+ Skew Time (v4) 210 ms
+ Skew Time (v6) 210 ms
+ Master Down Interval (v4) 3210 ms
+ Master Down Interval (v6) 3210 ms
+ IPv4 Addresses 1
+ .................................. 10.0.2.16
+ IPv6 Addresses 1
+ .................................. 2001:db8::370:7334
+
+At this point, VRRP has sent gratuitous ARP requests for the IPv4 address,
+Unsolicited Neighbor Advertisements for the IPv6 address, and has asked Zebra
+to send Router Advertisements on its behalf. It is also transmitting VRRPv3
+advertisements on the macvlan interfaces.
+
+The Primary IP fields are of some interest, as the behavior may be
+counterintuitive. These fields show the source address used for VRRP
+advertisements. Although VRRPv3 advertisements are always transmitted on the
+macvlan interfaces, in the IPv4 case the source address is set to the primary
+IPv4 address on the base interface, ``eth0`` in this case. This is a protocol
+requirement, and IPv4 VRRP will not function unless the base interface has an
+IPv4 address assigned. In the IPv6 case the link local of the macvlan interface
+is used.
+
+If any misconfiguration errors are detected, VRRP for the misconfigured address
+family will not come up and the configuration issue will be logged to FRR's
+configured logging destination.
+
+Per the RFC, IPv4 and IPv6 virtual routers are independent of each other. For
+instance, it is possible for the IPv4 router to be in Backup state while the
+IPv6 router is in Master state; or for either to be completely inoperative
+while the other is operative, etc. Instances sharing the same base interface
+and VRID are shown together in the show output for conceptual convenience.
+
+To complete your VRRP deployment, configure other routers on the segment with
+the exact same system and FRR configuration as shown above. Provided each
+router receives the others' VRRP advertisements, the Master election protocol
+will run, one Master will be elected, and the other routers will place their
+macvlan interfaces into ``protodown on`` until Master fails or priority values
+are changed to favor another router.
+
+Switching the protocol version to VRRPv2 is accomplished simply by changing
+``version 3`` to ``version 2`` in the VRID configuration line. Note that VRRPv2
+does not support IPv6, so any IPv6 configuration will be rejected by FRR when
+using VRRPv2.
+
+.. note::
+
+ All VRRP routers initially start in Backup state, and wait for the
+ calculated Master Down Interval to pass before they assume Master status.
+ This prevents downstream neighbor table churn if another router is already
+ Master with higher priority, meaning this box will ultimately assume Backup
+ status once the first advertisement is received. However, if the calculated
+ Master Down Interval is high and this router is configured such that it will
+ ultimately assume Master status, then it will take a while for this to
+ happen. This is a known issue.
+
+
+All interface configuration commands are documented below.
+
+.. index:: [no] vrrp (1-255) [version (2-3)]
+.. clicmd:: [no] vrrp (1-255) [version (2-3)]
+
+ Create a VRRP router with the specified VRID on the interface. Optionally
+ specify the protocol version. If the protocol version is not specified, the
+ default is VRRPv3.
+
+.. index:: [no] vrrp (1-255) advertisement-interval (10-40950)
+.. clicmd:: [no] vrrp (1-255) advertisement-interval (10-40950)
+
+ Set the advertisement interval. This is the interval at which VRRP
+ advertisements will be sent. Values are given in milliseconds, but must be
+ multiples of 10, as VRRP itself uses centiseconds.
+
+.. index:: [no] vrrp (1-255) ip A.B.C.D
+.. clicmd:: [no] vrrp (1-255) ip A.B.C.D
+
+ Add an IPv4 address to the router. This address must already be configured
+ on the appropriate macvlan device. Adding an IP address to the router will
+ implicitly activate the router; see :clicmd:`[no] vrrp (1-255) shutdown` to
+ override this behavior.
+
+.. index:: [no] vrrp (1-255) ipv6 X:X::X:X
+.. clicmd:: [no] vrrp (1-255) ipv6 X:X::X:X
+
+ Add an IPv6 address to the router. This address must already be configured
+ on the appropriate macvlan device. Adding an IP address to the router will
+ implicitly activate the router; see :clicmd:`[no] vrrp (1-255) shutdown` to
+ override this behavior.
+
+ This command will fail if the protocol version is set to VRRPv2, as VRRPv2
+ does not support IPv6.
+
+.. index:: [no] vrrp (1-255) preempt
+.. clicmd:: [no] vrrp (1-255) preempt
+
+ Toggle preempt mode. When enabled, preemption allows Backup routers with
+ higher priority to take over Master status from the existing Master. Enabled
+ by default.
+
+.. index:: [no] vrrp (1-255) priority (1-254)
+.. clicmd:: [no] vrrp (1-255) priority (1-254)
+
+ Set the router priority. The router with the highest priority is elected as
+ the Master. If all routers in the VRRP virtual router are configured with
+ the same priority, the router with the highest primary IP address is elected
+ as the Master. Priority value 255 is reserved for the acting Master router.
+
+.. index:: [no] vrrp (1-255) shutdown
+.. clicmd:: [no] vrrp (1-255) shutdown
+
+ Place the router into administrative shutdown. VRRP will not activate for
+ this router until this command is removed with the ``no`` form.
+
+.. _vrrp-global-configuration:
+
+Global Configuration
+--------------------
+
+Show commands, global defaults and debugging configuration commands.
+
+.. index:: show vrrp [interface INTERFACE] [(1-255)] [json]
+.. clicmd:: show vrrp [interface INTERFACE] [(1-255)] [json]
+
+ Shows VRRP status for some or all configured VRRP routers. Specifying an
+ interface will only show routers configured on that interface. Specifying a
+ VRID will only show routers with that VRID. Specifying ``json`` will dump
+ each router state in a JSON array.
+
+.. index:: debug vrrp [{protocol|autoconfigure|packets|sockets|ndisc|arp|zebra}]
+.. clicmd:: debug vrrp [{protocol|autoconfigure|packets|sockets|ndisc|arp|zebra}]
+
+ Toggle debugging logs for some or all components of VRRP.
+
+ protocol
+ Logs state changes, election protocol decisions, and interface status
+ changes.
+
+ autoconfigure
+ Logs actions taken by the autoconfiguration procedures. See
+ :ref:`vrrp-autoconfiguration`.
+
+ packets
+ Logs details of ingress and egress packets. Includes packet decodes and
+ hex dumps.
+
+ sockets
+ Logs details of socket configuration and initialization.
+
+ ndisc
+ Logs actions taken by the Neighbor Discovery component of VRRP.
+
+ arp
+ Logs actions taken by the ARP component of VRRP.
+
+ zebra
+ Logs communications with Zebra.
+
+.. index:: [no] vrrp default <advertisement-interval (1-4096)|preempt|priority (1-254)|shutdown>
+.. clicmd:: [no] vrrp default <advertisement-interval (1-4096)|preempt|priority (1-254)|shutdown>
+
+ Configure defaults for new VRRP routers. These values will not affect
+ already configured VRRP routers, but will be applied to newly configured
+ ones.
+
+.. _vrrp-autoconfiguration:
+
+Autoconfiguration
+-----------------
+
+In light of the complicated configuration required on the base system before
+VRRP can be enabled, FRR has the ability to automatically configure VRRP
+sessions by inspecting the interfaces present on the system. Since it is quite
+unlikely that macvlan devices with VRRP virtual MACs will exist on systems not
+using VRRP, this can be a convenient shortcut to automatically generate FRR
+configuration.
+
+After configuring the interfaces as described in
+:ref:`vrrp-system-configuration`, and configuring any defaults you may want,
+execute the following command:
+
+.. index:: [no] vrrp autoconfigure [version (2-3)]
+.. clicmd:: [no] vrrp autoconfigure [version (2-3)]
+
+ Generates VRRP configuration based on the interface configuration on the
+ base system. Any existing interfaces that are configured properly for VRRP -
+ i.e. have the correct MAC address, link local address (when required), IPv4
+ and IPv6 addresses - are used to create a VRRP router on their parent
+ interfaces, with VRRP IPvX addresses taken from the addresses assigned to
+ the macvlan devices. The generated configuration appears in the output of
+ ``show run``, which can then be modified as needed and written to the config
+ file. The ``version`` parameter controls the protocol version; if using
+ VRRPv2, keep in mind that IPv6 is not supported and will not be configured.
+
+The following configuration is then generated for you:
+
+.. code-block:: frr
+
+ interface eth0
+ vrrp 5
+ vrrp 5 ip 10.0.2.16
+ vrrp 5 ipv6 2001:db8::370:7334
+
+VRRP is automatically activated. Global defaults, if set, are applied.
+
+You can then edit this configuration with **vtysh** as needed, and commit it by
+writing to the configuration file.
diff --git a/lib/checksum.c b/lib/checksum.c
index 18e3850474..3473370041 100644
--- a/lib/checksum.c
+++ b/lib/checksum.c
@@ -46,6 +46,24 @@ int /* return checksum in low-order 16 bits */
return (answer);
}
+int in_cksum_with_ph4(struct ipv4_ph *ph, void *data, int nbytes)
+{
+ uint8_t dat[sizeof(struct ipv4_ph) + nbytes];
+
+ memcpy(dat, ph, sizeof(struct ipv4_ph));
+ memcpy(dat + sizeof(struct ipv4_ph), data, nbytes);
+ return in_cksum(dat, sizeof(dat));
+}
+
+int in_cksum_with_ph6(struct ipv6_ph *ph, void *data, int nbytes)
+{
+ uint8_t dat[sizeof(struct ipv6_ph) + nbytes];
+
+ memcpy(dat, ph, sizeof(struct ipv6_ph));
+ memcpy(dat + sizeof(struct ipv6_ph), data, nbytes);
+ return in_cksum(dat, sizeof(dat));
+}
+
/* Fletcher Checksum -- Refer to RFC1008. */
#define MODX 4102U /* 5802 should be fine */
diff --git a/lib/checksum.h b/lib/checksum.h
index 7d50371439..56771d4f24 100644
--- a/lib/checksum.h
+++ b/lib/checksum.h
@@ -1,8 +1,33 @@
+#include <stdint.h>
+#include <netinet/in.h>
+
#ifdef __cplusplus
extern "C" {
#endif
-extern int in_cksum(void *, int);
+
+/* IPv4 pseudoheader */
+struct ipv4_ph {
+ struct in_addr src;
+ struct in_addr dst;
+ uint8_t rsvd;
+ uint8_t proto;
+ uint16_t len;
+} __attribute__((packed));
+
+/* IPv6 pseudoheader */
+struct ipv6_ph {
+ struct in6_addr src;
+ struct in6_addr dst;
+ uint32_t ulpl;
+ uint8_t zero[3];
+ uint8_t next_hdr;
+} __attribute__((packed));
+
+extern int in_cksum(void *data, int nbytes);
+extern int in_cksum_with_ph4(struct ipv4_ph *ph, void *data, int nbytes);
+extern int in_cksum_with_ph6(struct ipv6_ph *ph, void *data, int nbytes);
+
#define FLETCHER_CHECKSUM_VALIDATE 0xffff
extern uint16_t fletcher_checksum(uint8_t *, const size_t len,
const uint16_t offset);
diff --git a/lib/command.c b/lib/command.c
index b7a323e358..18426e0c51 100644
--- a/lib/command.c
+++ b/lib/command.c
@@ -149,6 +149,7 @@ const char *node_names[] = {
"bfd", /* BFD_NODE */
"bfd peer", /* BFD_PEER_NODE */
"openfabric", // OPENFABRIC_NODE
+ "vrrp", /* VRRP_NODE */
};
/* clang-format on */
diff --git a/lib/command.h b/lib/command.h
index a5f9616dbf..d96ec97e67 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -147,6 +147,7 @@ enum node_type {
BFD_NODE, /* BFD protocol mode. */
BFD_PEER_NODE, /* BFD peer configuration mode. */
OPENFABRIC_NODE, /* OpenFabric router configuration node */
+ VRRP_NODE, /* VRRP node */
NODE_TYPE_MAX, /* maximum */
};
diff --git a/lib/if.c b/lib/if.c
index 38f3f45ed1..3f489e0c3e 100644
--- a/lib/if.c
+++ b/lib/if.c
@@ -389,6 +389,34 @@ struct interface *if_lookup_prefix(struct prefix *prefix, vrf_id_t vrf_id)
return NULL;
}
+size_t if_lookup_by_hwaddr(const uint8_t *hw_addr, size_t addrsz,
+ struct interface ***result, vrf_id_t vrf_id)
+{
+ struct vrf *vrf = vrf_lookup_by_id(vrf_id);
+
+ struct list *rs = list_new();
+ struct interface *ifp;
+
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ if (ifp->hw_addr_len == (int)addrsz
+ && !memcmp(hw_addr, ifp->hw_addr, addrsz))
+ listnode_add(rs, ifp);
+ }
+
+ if (rs->count) {
+ *result = XCALLOC(MTYPE_TMP,
+ sizeof(struct interface *) * rs->count);
+ list_to_array(rs, (void **)*result, rs->count);
+ }
+
+ int count = rs->count;
+
+ list_delete(&rs);
+
+ return count;
+}
+
+
/* Get interface by name if given name interface doesn't exist create
one. */
struct interface *if_get_by_name(const char *name, vrf_id_t vrf_id)
@@ -876,6 +904,19 @@ struct connected *connected_add_by_prefix(struct interface *ifp,
return ifc;
}
+struct connected *connected_get_linklocal(struct interface *ifp)
+{
+ struct listnode *n;
+ struct connected *c = NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, n, c)) {
+ if (c->address->family == AF_INET6
+ && IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6))
+ break;
+ }
+ return c;
+}
+
#if 0 /* this route_table of struct connected's is unused \
* however, it would be good to use a route_table rather than \
* a list.. \
diff --git a/lib/if.h b/lib/if.h
index d26d4dd68b..2dc1a7b2de 100644
--- a/lib/if.h
+++ b/lib/if.h
@@ -225,6 +225,10 @@ struct interface {
not work as expected.
*/
ifindex_t ifindex;
+ /*
+ * ifindex of parent interface, if any
+ */
+ ifindex_t link_ifindex;
#define IFINDEX_INTERNAL 0
/* Zebra internal interface status */
@@ -482,6 +486,8 @@ extern struct connected *if_lookup_address(void *matchaddr, int family,
vrf_id_t vrf_id);
extern struct interface *if_lookup_prefix(struct prefix *prefix,
vrf_id_t vrf_id);
+size_t if_lookup_by_hwaddr(const uint8_t *hw_addr, size_t addrsz,
+ struct interface ***result, vrf_id_t vrf_id);
/* These 3 functions are to be used when the ifname argument is terminated
by a '\0' character: */
@@ -540,6 +546,7 @@ extern struct connected *connected_lookup_prefix_exact(struct interface *,
extern struct nbr_connected *nbr_connected_new(void);
extern void nbr_connected_free(struct nbr_connected *);
struct nbr_connected *nbr_connected_check(struct interface *, struct prefix *);
+struct connected *connected_get_linklocal(struct interface *ifp);
/* link parameters */
struct if_link_params *if_link_params_get(struct interface *);
diff --git a/lib/ipaddr.h b/lib/ipaddr.h
index f4ddadc66e..1c2399fdd3 100644
--- a/lib/ipaddr.h
+++ b/lib/ipaddr.h
@@ -56,6 +56,9 @@ struct ipaddr {
#define SET_IPADDR_V4(p) (p)->ipa_type = IPADDR_V4
#define SET_IPADDR_V6(p) (p)->ipa_type = IPADDR_V6
+#define IPADDRSZ(p) \
+ (IS_IPADDR_V4((p)) ? sizeof(struct in_addr) : sizeof(struct in6_addr))
+
static inline int str2ipaddr(const char *str, struct ipaddr *ip)
{
int ret;
diff --git a/lib/json.c b/lib/json.c
index 4ea20ba178..efc3794040 100644
--- a/lib/json.c
+++ b/lib/json.c
@@ -64,6 +64,11 @@ void json_object_boolean_true_add(struct json_object *obj, const char *key)
json_object_object_add(obj, key, json_object_new_boolean(1));
}
+void json_object_boolean_add(struct json_object *obj, const char *key, bool val)
+{
+ json_object_object_add(obj, key, json_object_new_boolean(val));
+}
+
struct json_object *json_object_lock(struct json_object *obj)
{
return json_object_get(obj);
diff --git a/lib/json.h b/lib/json.h
index a5251662be..c4d566b318 100644
--- a/lib/json.h
+++ b/lib/json.h
@@ -61,6 +61,8 @@ extern void json_object_string_add(struct json_object *obj, const char *key,
const char *s);
extern void json_object_int_add(struct json_object *obj, const char *key,
int64_t i);
+void json_object_boolean_add(struct json_object *obj, const char *key,
+ bool val);
extern void json_object_boolean_false_add(struct json_object *obj,
const char *key);
extern void json_object_boolean_true_add(struct json_object *obj,
diff --git a/lib/linklist.c b/lib/linklist.c
index 40c4b27169..43bc709325 100644
--- a/lib/linklist.c
+++ b/lib/linklist.c
@@ -334,3 +334,18 @@ struct listnode *listnode_add_force(struct list **list, void *val)
*list = list_new();
return listnode_add(*list, val);
}
+
+void **list_to_array(struct list *list, void **arr, size_t arrlen)
+{
+ struct listnode *ln;
+ void *vp;
+ size_t idx = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(list, ln, vp)) {
+ arr[idx++] = vp;
+ if (idx == arrlen)
+ break;
+ }
+
+ return arr;
+}
diff --git a/lib/linklist.h b/lib/linklist.h
index c30d8d314a..c2b289596d 100644
--- a/lib/linklist.h
+++ b/lib/linklist.h
@@ -240,6 +240,26 @@ extern void list_sort(struct list *list,
int (*cmp)(const void **, const void **));
/*
+ * Convert a list to an array of void pointers.
+ *
+ * Starts from the list head and ends either on the last node of the list or
+ * when the provided array cannot store any more elements.
+ *
+ * list
+ * list to convert
+ *
+ * arr
+ * Pre-allocated array of void *
+ *
+ * arrlen
+ * Number of elements in arr
+ *
+ * Returns:
+ * arr
+ */
+void **list_to_array(struct list *list, void **arr, size_t arrlen);
+
+/*
* Delete a list and NULL its pointer.
*
* If non-null, list->del is called with each data element.
diff --git a/lib/route_types.txt b/lib/route_types.txt
index c5eff44ca7..59f3a91cf0 100644
--- a/lib/route_types.txt
+++ b/lib/route_types.txt
@@ -83,6 +83,7 @@ ZEBRA_ROUTE_SHARP, sharp, sharpd, 'D', 1, 1, 1, "SHARP"
ZEBRA_ROUTE_PBR, pbr, pbrd, 'F', 1, 1, 0, "PBR"
ZEBRA_ROUTE_BFD, bfd, bfdd, '-', 0, 0, 0, "BFD"
ZEBRA_ROUTE_OPENFABRIC, openfabric, fabricd, 'f', 1, 1, 1, "OpenFabric"
+ZEBRA_ROUTE_VRRP, vrrp, vrrpd, '-', 0, 0, 0, "VRRP"
ZEBRA_ROUTE_ALL, wildcard, none, '-', 0, 0, 0, "-"
@@ -110,4 +111,5 @@ ZEBRA_ROUTE_BABEL, "Babel routing protocol (Babel)"
ZEBRA_ROUTE_SHARP, "Super Happy Advanced Routing Protocol (sharpd)"
ZEBRA_ROUTE_PBR, "Policy Based Routing (PBR)"
ZEBRA_ROUTE_BFD, "Bidirectional Fowarding Detection (BFD)"
+ZEBRA_ROUTE_VRRP, "Virtual Router Redundancy Protocol (VRRP)"
ZEBRA_ROUTE_OPENFABRIC, "OpenFabric Routing Protocol"
diff --git a/lib/sockunion.c b/lib/sockunion.c
index bb5426d74a..5afd10eb48 100644
--- a/lib/sockunion.c
+++ b/lib/sockunion.c
@@ -163,7 +163,7 @@ int sockunion_accept(int sock, union sockunion *su)
}
/* Return sizeof union sockunion. */
-static int sockunion_sizeof(const union sockunion *su)
+int sockunion_sizeof(const union sockunion *su)
{
int ret;
diff --git a/lib/sockunion.h b/lib/sockunion.h
index d7d26ba85a..b996735550 100644
--- a/lib/sockunion.h
+++ b/lib/sockunion.h
@@ -83,6 +83,7 @@ extern void sockunion_set(union sockunion *, int family, const uint8_t *addr,
extern union sockunion *sockunion_str2su(const char *str);
extern int sockunion_accept(int sock, union sockunion *);
+extern int sockunion_sizeof(const union sockunion *su);
extern int sockunion_stream_socket(union sockunion *);
extern int sockopt_reuseaddr(int);
extern int sockopt_reuseport(int);
diff --git a/lib/zclient.c b/lib/zclient.c
index 96a78efad6..0972590ca6 100644
--- a/lib/zclient.c
+++ b/lib/zclient.c
@@ -555,6 +555,25 @@ void zclient_send_interface_radv_req(struct zclient *zclient, vrf_id_t vrf_id,
zclient_send_message(zclient);
}
+int zclient_send_interface_protodown(struct zclient *zclient, vrf_id_t vrf_id,
+ struct interface *ifp, bool down)
+{
+ struct stream *s;
+
+ if (zclient->sock < 0)
+ return -1;
+
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_INTERFACE_SET_PROTODOWN, vrf_id);
+ stream_putl(s, ifp->ifindex);
+ stream_putc(s, !!down);
+ stream_putw_at(s, 0, stream_get_endp(s));
+ zclient_send_message(zclient);
+
+ return 0;
+}
+
/* Make connection to zebra daemon. */
int zclient_start(struct zclient *zclient)
{
@@ -1381,6 +1400,8 @@ stream_failure:
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | bandwidth |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | parent ifindex |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Link Layer Type |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Harware Address Length |
@@ -1561,6 +1582,7 @@ void zebra_interface_if_set_value(struct stream *s, struct interface *ifp)
ifp->mtu = stream_getl(s);
ifp->mtu6 = stream_getl(s);
ifp->bandwidth = stream_getl(s);
+ ifp->link_ifindex = stream_getl(s);
ifp->ll_type = stream_getl(s);
ifp->hw_addr_len = stream_getl(s);
if (ifp->hw_addr_len)
diff --git a/lib/zclient.h b/lib/zclient.h
index c46d63bfab..09f0acad84 100644
--- a/lib/zclient.h
+++ b/lib/zclient.h
@@ -76,6 +76,7 @@ typedef enum {
ZEBRA_INTERFACE_UP,
ZEBRA_INTERFACE_DOWN,
ZEBRA_INTERFACE_SET_MASTER,
+ ZEBRA_INTERFACE_SET_PROTODOWN,
ZEBRA_ROUTE_ADD,
ZEBRA_ROUTE_DELETE,
ZEBRA_ROUTE_NOTIFY_OWNER,
@@ -466,6 +467,9 @@ extern void zclient_send_interface_radv_req(struct zclient *zclient,
vrf_id_t vrf_id,
struct interface *ifp, int enable,
int ra_interval);
+extern int zclient_send_interface_protodown(struct zclient *zclient,
+ vrf_id_t vrf_id,
+ struct interface *ifp, bool down);
/* Send redistribute command to zebra daemon. Do not update zclient state. */
extern int zebra_redistribute_send(int command, struct zclient *, afi_t,
diff --git a/redhat/frr.spec.in b/redhat/frr.spec.in
index 36f9259865..ebd9ac3f47 100644
--- a/redhat/frr.spec.in
+++ b/redhat/frr.spec.in
@@ -24,6 +24,7 @@
%{!?with_pam: %global with_pam 0 }
%{!?with_pbrd: %global with_pbrd 1 }
%{!?with_pimd: %global with_pimd 1 }
+%{!?with_vrrpd: %global with_vrrpd 1 }
%{!?with_rpki: %global with_rpki 0 }
%{!?with_rtadv: %global with_rtadv 1 }
%{!?with_watchfrr: %global with_watchfrr 1 }
@@ -124,6 +125,12 @@
%define daemon_babeld ""
%endif
+%if %{with_vrrpd}
+ %define daemon_vrrpd vrrpd
+%else
+ %define daemon_vrrpd ""
+%endif
+
%if %{with_watchfrr}
%define daemon_watchfrr watchfrr
%else
@@ -136,7 +143,7 @@
%define daemon_bfdd ""
%endif
-%define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} %{daemon_bfdd}
+%define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} %{daemon_bfdd} %{daemon_vrrpd}
#release sub-revision (the two digits after the CONFDATE)
%{!?release_rev: %global release_rev 01 }
@@ -306,6 +313,11 @@ developing OSPF-API and frr applications.
%else
--disable-babeld \
%endif
+%if %{with_vrrpd}
+ --enable-vrrpd \
+%else
+ --disable-vrrpd \
+%endif
%if %{with_pam}
--with-libpam \
%endif
@@ -461,6 +473,9 @@ zebra_spec_add_service isisd 2608/tcp "ISISd vty"
zebra_spec_add_service bfdd 2617/tcp "BFDd vty"
%endif
zebra_spec_add_service fabricd 2618/tcp "Fabricd vty"
+%if %{with_vrrpd}
+ zebra_spec_add_service vrrpd 2619/tcp "VRRPd vty"
+%endif
%if "%{initsystem}" == "systemd"
for daemon in %all_daemons ; do
@@ -596,6 +611,9 @@ fi
%if %{with_pbrd}
%{_sbindir}/pbrd
%endif
+%if %{with_vrrpd}
+ %{_sbindir}/vrrpd
+%endif
%{_sbindir}/isisd
%{_sbindir}/fabricd
%if %{with_ldpd}
diff --git a/tools/etc/frr/daemons b/tools/etc/frr/daemons
index 2abff422c9..b920621d70 100644
--- a/tools/etc/frr/daemons
+++ b/tools/etc/frr/daemons
@@ -29,6 +29,7 @@ sharpd=no
pbrd=no
bfdd=no
fabricd=no
+vrrpd=no
#
# If this option is set the /etc/init.d/frr script automatically loads
@@ -53,6 +54,7 @@ pbrd_options=" -A 127.0.0.1"
staticd_options="-A 127.0.0.1"
bfdd_options=" -A 127.0.0.1"
fabricd_options="-A 127.0.0.1"
+vrrpd_options=" -A 127.0.0.1"
# The list of daemons to watch is automatically generated by the init script.
#watchfrr_options=""
diff --git a/tools/etc/rsyslog.d/45-frr.conf b/tools/etc/rsyslog.d/45-frr.conf
index 4612e8beaf..feeeb13f13 100644
--- a/tools/etc/rsyslog.d/45-frr.conf
+++ b/tools/etc/rsyslog.d/45-frr.conf
@@ -16,6 +16,7 @@ if $programname == 'babeld' or
$programname == 'pimd' or
$programname == 'ripd' or
$programname == 'ripngd' or
+ $programname == 'vrrpd' or
$programname == 'watchfrr' or
$programname == 'zebra'
then :omfile:$frr_log
@@ -33,6 +34,7 @@ if $programname == 'babeld' or
$programname == 'pimd' or
$programname == 'ripd' or
$programname == 'ripngd' or
+ $programname == 'vrrpd' or
$programname == 'watchfrr' or
$programname == 'zebra'
then stop
diff --git a/tools/frr-reload.py b/tools/frr-reload.py
index ce1db770d2..23e8f3000d 100755
--- a/tools/frr-reload.py
+++ b/tools/frr-reload.py
@@ -410,7 +410,8 @@ end
"service ",
"table ",
"username ",
- "zebra ")
+ "zebra ",
+ "vrrp autoconfigure")
for line in self.lines:
diff --git a/tools/frr.in b/tools/frr.in
index 2e3a094589..d871afa42b 100755
--- a/tools/frr.in
+++ b/tools/frr.in
@@ -25,7 +25,7 @@ FRR_VTY_GROUP="@enable_vty_group@" # frrvty
# Local Daemon selection may be done by using /etc/frr/daemons.
# See /usr/share/doc/frr/README.Debian.gz for further information.
# Keep zebra first and do not list watchfrr!
-DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd"
+DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd vrrpd"
MAX_INSTANCES=5
RELOAD_SCRIPT="$D_PATH/frr-reload.py"
diff --git a/tools/frrcommon.sh.in b/tools/frrcommon.sh.in
index 897e6d6558..3fc38d4bed 100644
--- a/tools/frrcommon.sh.in
+++ b/tools/frrcommon.sh.in
@@ -29,7 +29,7 @@ FRR_VTY_GROUP="@enable_vty_group@" # frrvty
# - keep zebra first
# - watchfrr does NOT belong in this list
-DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd"
+DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd vrrpd"
RELOAD_SCRIPT="$D_PATH/frr-reload.py"
#
diff --git a/vrrpd/.gitignore b/vrrpd/.gitignore
new file mode 100644
index 0000000000..e1751b28ac
--- /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 0000000000..027c6ee1f8
--- /dev/null
+++ b/vrrpd/Makefile
@@ -0,0 +1,10 @@
+all: ALWAYS
+ @$(MAKE) -s -C .. vrrp/vrrp
+%: ALWAYS
+ @$(MAKE) -s -C .. vrrp/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
diff --git a/vrrpd/subdir.am b/vrrpd/subdir.am
new file mode 100644
index 0000000000..a328f969d6
--- /dev/null
+++ b/vrrpd/subdir.am
@@ -0,0 +1,39 @@
+#
+# vrrpd
+#
+
+if VRRPD
+noinst_LIBRARIES += vrrpd/libvrrp.a
+sbin_PROGRAMS += vrrpd/vrrpd
+# dist_examples_DATA += staticd/staticd.conf.sample
+vtysh_scan += $(top_srcdir)/vrrpd/vrrp_vty.c
+man8 += $(MANBUILD)/vrrpd.8
+endif
+
+vrrpd_libvrrp_a_SOURCES = \
+ vrrpd/vrrp.c \
+ vrrpd/vrrp_arp.c \
+ vrrpd/vrrp_debug.c \
+ vrrpd/vrrp_memory.c \
+ vrrpd/vrrp_ndisc.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_memory.h \
+ vrrpd/vrrp_ndisc.h \
+ vrrpd/vrrp_packet.h \
+ vrrpd/vrrp_vty.h \
+ vrrpd/vrrp_zebra.h \
+ # end
+
+vrrpd/vrrp_vty_clippy.c: $(CLIPPY_DEPS)
+vrrpd/vrrp_vty.$(OBJEXT): vrrpd/vrrp_vty_clippy.c
+
+vrrpd_vrrpd_SOURCES = vrrpd/vrrp_main.c
+vrrpd_vrrpd_LDADD = vrrpd/libvrrp.a lib/libfrr.la @LIBCAP@
diff --git a/vrrpd/vrrp.c b/vrrpd/vrrp.c
new file mode 100644
index 0000000000..ad09caf359
--- /dev/null
+++ b/vrrpd/vrrp.c
@@ -0,0 +1,2379 @@
+/*
+ * VRRP global definitions and state machine.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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_memory.h"
+#include "vrrp_ndisc.h"
+#include "vrrp_packet.h"
+#include "vrrp_zebra.h"
+
+#define VRRP_LOGPFX "[CORE] "
+
+/* statics */
+struct hash *vrrp_vrouters_hash;
+bool vrrp_autoconfig_is_on;
+int vrrp_autoconfig_version;
+
+struct vrrp_defaults vd;
+
+const char *vrrp_state_names[3] = {
+ [VRRP_STATE_INITIALIZE] = "Initialize",
+ [VRRP_STATE_MASTER] = "Master",
+ [VRRP_STATE_BACKUP] = "Backup",
+};
+
+const char *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.
+ *
+ * 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
+ || !vrrp_ifp_has_vrrp_mac(mvl_ifp))
+ return NULL;
+
+ p = if_lookup_by_index(mvl_ifp->link_ifindex, VRF_DEFAULT);
+ 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;
+ /* Must have a parent interface */
+ start = start && (vr->ifp != NULL);
+ whynot = (!start && !whynot) ? "No base interface" : NULL;
+#if 0
+ /* Parent interface must be up */
+ start = start && if_is_operative(vr->ifp);
+#endif
+ /* Parent interface must have at least one v4 */
+ start = start && vr->ifp->connected->count > 1;
+ whynot = (!start && !whynot) ? "No primary IPv4 address" : NULL;
+ /* Must have a macvlan interface */
+ start = start && (r->mvl_ifp != NULL);
+ whynot = (!start && !whynot) ? "No VRRP interface" : NULL;
+#if 0
+ /* Macvlan interface must be admin up */
+ start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP);
+#endif
+ /* Must have at least one VIP configured */
+ start = start && r->addrs->count > 0;
+ whynot =
+ (!start && !whynot) ? "No Virtual IP address configured" : NULL;
+ 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);
+
+ r = vr->v6;
+ /* Must not already be started */
+ start = r->fsm.state == VRRP_STATE_INITIALIZE;
+ /* Must not be v2 */
+ start = start && vr->version != 2;
+ whynot = (!start && !whynot) ? "VRRPv2 does not support v6" : NULL;
+ /* Must have a parent interface */
+ start = start && (vr->ifp != NULL);
+ whynot = (!start && !whynot) ? "No base interface" : NULL;
+#if 0
+ /* Parent interface must be up */
+ start = start && if_is_operative(vr->ifp);
+#endif
+ /* Must have a macvlan interface */
+ start = start && (r->mvl_ifp != NULL);
+ whynot = (!start && !whynot) ? "No VRRP interface" : NULL;
+#if 0
+ /* Macvlan interface must be admin up */
+ start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP);
+ /* Macvlan interface must have a link local */
+ start = start && connected_get_linklocal(r->mvl_ifp);
+ whynot =
+ (!start && !whynot) ? "No link local address configured" : NULL;
+ /* Macvlan interface must have a v6 IP besides the link local */
+ start = start && (r->mvl_ifp->connected->count >= 2);
+ whynot = (!start && !whynot)
+ ? "No Virtual IP configured on macvlan device"
+ : NULL;
+#endif
+ /* Must have at least one VIP configured */
+ start = start && r->addrs->count > 0;
+ whynot =
+ (!start && !whynot) ? "No Virtual IP address configured" : NULL;
+ 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 (!memcmp(&iter->ip, &ip->ip, IPADDRSZ(ip)))
+ return true;
+
+ return false;
+}
+
+int vrrp_add_ip(struct vrrp_router *r, struct ipaddr *ip)
+{
+ int af = (ip->ipa_type == IPADDR_V6) ? AF_INET6 : AF_INET;
+
+ 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->v4, &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->v6, &ip);
+}
+
+int vrrp_del_ip(struct vrrp_router *r, struct ipaddr *ip)
+{
+ struct listnode *ln, *nn;
+ struct ipaddr *iter;
+ int ret = 0;
+
+ if (!vrrp_has_ip(r->vr, ip))
+ return 0;
+
+ for (ALL_LIST_ELEMENTS(r->addrs, ln, nn, iter))
+ if (!memcmp(&iter->ip, &ip->ip, IPADDRSZ(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->v6, &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->v4, &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, VRF_DEFAULT);
+
+ /*
+ * 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->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);
+
+ 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(struct interface *ifp, uint8_t vrid)
+{
+ struct vrrp_vrouter vr;
+
+ vr.vrid = vrid;
+ vr.ifp = ifp;
+
+ return hash_lookup(vrrp_vrouters_hash, &vr);
+}
+
+/* Network ----------------------------------------------------------------- */
+
+/* Forward decls */
+static void vrrp_change_state(struct vrrp_router *r, int to);
+static int vrrp_adver_timer_expire(struct thread *thread);
+static int vrrp_master_down_timer_expire(struct thread *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)
+{
+ char ipstr[INET6_ADDRSTRLEN];
+ 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 %s: %s",
+ r->vr->vrid, family2str(r->family),
+ inet_ntop(r->family,
+ (const void *)&c->address->u.prefix, ipstr,
+ sizeof(ipstr)),
+ 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 %s",
+ r->vr->vrid, family2str(r->family),
+ inet_ntop(r->family, (const void *)&c->address->u.prefix,
+ ipstr, sizeof(ipstr)));
+ }
+
+ 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);
+
+ 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;
+ str2sockunion(group, &dest);
+
+ ssize_t sent = sendto(r->sock_tx, pkt, (size_t)pktsz, 0, &dest.sa,
+ sockunion_sizeof(&dest));
+
+ XFREE(MTYPE_VRRP_PKT, 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:\n%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 %" PRIu8
+ " 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 %" PRIu8
+ " 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 %" PRIu8
+ " 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 = memcmp(&src->ip, &r->src.ip, IPADDRSZ(src));
+
+ if (pkt->hdr.priority == 0) {
+ vrrp_send_advertisement(r);
+ THREAD_OFF(r->t_adver_timer);
+ thread_add_timer_msec(
+ master, vrrp_adver_timer_expire, r,
+ r->vr->advertisement_interval * 10,
+ &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 %" PRIu8
+ "; switching to Backup",
+ r->vr->vrid, family2str(r->family), sipstr,
+ pkt->hdr.priority);
+ THREAD_OFF(r->t_adver_timer);
+ if (r->vr->version == 3) {
+ r->master_adver_interval =
+ htons(pkt->hdr.v3.adver_int);
+ }
+ vrrp_recalculate_timers(r);
+ THREAD_OFF(r->t_master_down_timer);
+ thread_add_timer_msec(master,
+ vrrp_master_down_timer_expire, r,
+ r->master_down_interval * 10,
+ &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 (%" PRIu8
+ " <= %" PRIu8 " & %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) {
+ THREAD_OFF(r->t_master_down_timer);
+ thread_add_timer_msec(
+ master, vrrp_master_down_timer_expire, r,
+ r->skew_time * 10, &r->t_master_down_timer);
+ } else if (r->vr->preempt_mode == false
+ || pkt->hdr.priority >= r->priority) {
+ if (r->vr->version == 3) {
+ r->master_adver_interval =
+ ntohs(pkt->hdr.v3.adver_int);
+ }
+ vrrp_recalculate_timers(r);
+ THREAD_OFF(r->t_master_down_timer);
+ thread_add_timer_msec(master,
+ vrrp_master_down_timer_expire, r,
+ r->master_down_interval * 10,
+ &r->t_master_down_timer);
+ } else if (r->vr->preempt_mode == true
+ && pkt->hdr.priority < r->priority) {
+ /* Discard advertisement */
+ DEBUGD(&vrrp_dbg_proto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Discarding advertisement from %s (%" PRIu8
+ " < %" PRIu8 " & 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 int vrrp_read(struct thread *thread)
+{
+ struct vrrp_router *r = thread->arg;
+
+ 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, &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)
+ thread_add_read(master, vrrp_read, r, r->sock_rx, &r->t_read);
+
+ return 0;
+}
+
+/*
+ * Creates and configures VRRP router sockets.
+ *
+ * This function:
+ * - Creates two sockets, one for Tx, one for Rx
+ * - 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_elevate_privs(&vrrp_privs)
+ {
+ r->sock_rx = socket(r->family, SOCK_RAW, IPPROTO_VRRP);
+ r->sock_tx = socket(r->family, SOCK_RAW, IPPROTO_VRRP);
+ }
+
+ 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;
+ }
+
+ /* 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_elevate_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 */
+ setsockopt_ipv6_multicast_loop(r->sock_tx, 0);
+
+ /* Bind Rx socket to exact interface */
+ frr_elevate_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 */
+ THREAD_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->vr->advertisement_interval = r->vr->advertisement_interval;
+ 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)
+ vrrp_zebra_radv_set(r, false);
+}
+
+void (*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 int vrrp_adver_timer_expire(struct thread *thread)
+{
+ struct vrrp_router *r = thread->arg;
+
+ 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 */
+ thread_add_timer_msec(master, vrrp_adver_timer_expire, r,
+ r->vr->advertisement_interval * 10,
+ &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]);
+ }
+
+ return 0;
+}
+
+/*
+ * Called when Master_Down_Timer expires.
+ */
+static int vrrp_master_down_timer_expire(struct thread *thread)
+{
+ struct vrrp_router *r = thread->arg;
+
+ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Master_Down_Timer expired",
+ r->vr->vrid, family2str(r->family));
+
+ thread_add_timer_msec(master, vrrp_adver_timer_expire, r,
+ r->vr->advertisement_interval * 10,
+ &r->t_adver_timer);
+ vrrp_change_state(r, VRRP_STATE_MASTER);
+
+ return 0;
+}
+
+/*
+ * 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 */
+ thread_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) {
+ thread_add_timer_msec(master, vrrp_adver_timer_expire, r,
+ r->vr->advertisement_interval * 10,
+ &r->t_adver_timer);
+ vrrp_change_state(r, VRRP_STATE_MASTER);
+ } else {
+ r->master_adver_interval = r->vr->advertisement_interval;
+ vrrp_recalculate_timers(r);
+ thread_add_timer_msec(master, vrrp_master_down_timer_expire, r,
+ r->master_down_interval * 10,
+ &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]);
+ break;
+ }
+
+ /* Cancel all timers */
+ THREAD_OFF(r->t_adver_timer);
+ THREAD_OFF(r->t_master_down_timer);
+ THREAD_OFF(r->t_read);
+ THREAD_OFF(r->t_write);
+
+ /* Protodown macvlan */
+ 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 (*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;
+ char ipbuf[INET6_ADDRSTRLEN];
+
+ 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) {
+ inet_ntop(r->family, &c->address->u.prefix, ipbuf,
+ sizeof(ipbuf));
+ DEBUGD(&vrrp_dbg_auto,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Adding %s",
+ r->vr->vrid, family2str(r->family), ipbuf);
+ 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, VRF_DEFAULT);
+
+ 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 == false)
+ 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 = vrf_lookup_by_id(VRF_DEFAULT);
+ struct interface *ifp;
+
+ 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;
+
+ vr = vrrp_lookup_by_if_mvl(mvl_ifp);
+
+ if (vr) {
+ 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;
+
+ vrs = vrrp_lookup_by_if_any(ifp);
+
+ for (ALL_LIST_ELEMENTS_RO(vrs, ln, 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;
+ struct list *vrs = vrrp_lookup_by_if_any(ifp);
+
+ vrrp_if_down(ifp);
+
+ for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) {
+ if ((vr->v4->mvl_ifp == ifp || vr->ifp == ifp)
+ && vr->v4->fsm.state != VRRP_STATE_INITIALIZE) {
+ vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN);
+ vr->v4->mvl_ifp = NULL;
+ } else if ((vr->v6->mvl_ifp == ifp || vr->ifp == ifp)
+ && vr->v6->fsm.state != VRRP_STATE_INITIALIZE) {
+ vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN);
+ 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_interface(struct vty *vty)
+{
+ struct list *vrs = hash_to_list(vrrp_vrouters_hash);
+ struct listnode *ln, *ipln;
+ struct vrrp_vrouter *vr;
+ int writes = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) {
+ vty_frame(vty, "interface %s\n", vr->ifp->name);
+ ++writes;
+
+ vty_out(vty, " vrrp %" PRIu8 "%s\n", vr->vrid,
+ vr->version == 2 ? " version 2" : "");
+ ++writes;
+
+ if (vr->shutdown != vd.shutdown && ++writes)
+ vty_out(vty, " %svrrp %" PRIu8 " shutdown\n",
+ vr->shutdown ? "" : "no ", vr->vrid);
+
+ if (vr->preempt_mode != vd.preempt_mode && ++writes)
+ vty_out(vty, " %svrrp %" PRIu8 " preempt\n",
+ vr->preempt_mode ? "" : "no ", vr->vrid);
+
+ if (vr->accept_mode != vd.accept_mode && ++writes)
+ vty_out(vty, " %svrrp %" PRIu8 " accept\n",
+ vr->accept_mode ? "" : "no ", vr->vrid);
+
+ if (vr->advertisement_interval != vd.advertisement_interval
+ && ++writes)
+ vty_out(vty,
+ " vrrp %" PRIu8
+ " advertisement-interval %d\n",
+ vr->vrid, vr->advertisement_interval * CS2MS);
+
+ if (vr->priority != vd.priority && ++writes)
+ vty_out(vty, " vrrp %" PRIu8 " priority %" PRIu8 "\n",
+ vr->vrid, vr->priority);
+
+ struct ipaddr *ip;
+
+ for (ALL_LIST_ELEMENTS_RO(vr->v4->addrs, ipln, ip)) {
+ char ipbuf[INET6_ADDRSTRLEN];
+
+ ipaddr2str(ip, ipbuf, sizeof(ipbuf));
+ vty_out(vty, " vrrp %" PRIu8 " ip %s\n", vr->vrid,
+ ipbuf);
+ ++writes;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(vr->v6->addrs, ipln, ip)) {
+ char ipbuf[INET6_ADDRSTRLEN];
+
+ ipaddr2str(ip, ipbuf, sizeof(ipbuf));
+ vty_out(vty, " vrrp %" PRIu8 " ipv6 %s\n", vr->vrid,
+ ipbuf);
+ ++writes;
+ }
+ vty_endframe(vty, "!\n");
+ }
+
+ list_delete(&vrs);
+
+ return writes;
+}
+
+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" : "");
+
+ if (vd.priority != VRRP_DEFAULT_PRIORITY && ++writes)
+ vty_out(vty, "vrrp default priority %" PRIu8 "\n", vd.priority);
+
+ if (vd.advertisement_interval != VRRP_DEFAULT_ADVINT && ++writes)
+ vty_out(vty,
+ "vrrp default advertisement-interval %" PRIu16 "\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.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@%" PRIu8, 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 0;
+ if (vr1->vrid != vr2->vrid)
+ return 0;
+
+ return 1;
+}
+
+void vrrp_init(void)
+{
+ /* Set default defaults */
+ vd.priority = VRRP_DEFAULT_PRIORITY;
+ vd.advertisement_interval = VRRP_DEFAULT_ADVINT;
+ vd.preempt_mode = VRRP_DEFAULT_PREEMPT;
+ vd.accept_mode = VRRP_DEFAULT_ACCEPT;
+ 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, 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(vrrp_vrouters_hash, NULL);
+ hash_free(vrrp_vrouters_hash);
+}
diff --git a/vrrpd/vrrp.h b/vrrpd/vrrp.h
new file mode 100644
index 0000000000..fd4901fe22
--- /dev/null
+++ b/vrrpd/vrrp.h
@@ -0,0 +1,570 @@
+/*
+ * VRRP global definitions and state machine.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef __VRRP_H__
+#define __VRRP_H__
+
+#include <zebra.h>
+#include <netinet/ip.h>
+
+#include "lib/hash.h"
+#include "lib/hook.h"
+#include "lib/if.h"
+#include "lib/linklist.h"
+#include "lib/privs.h"
+#include "lib/stream.h"
+#include "lib/thread.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_DEFAULT_PRIORITY 100
+#define VRRP_DEFAULT_ADVINT 100
+#define VRRP_DEFAULT_PREEMPT true
+#define VRRP_DEFAULT_ACCEPT true
+#define VRRP_DEFAULT_SHUTDOWN false
+
+/* User compatibility constant */
+#define CS2MS 10
+
+/* Configured defaults */
+struct vrrp_defaults {
+ uint8_t priority;
+ uint16_t advertisement_interval;
+ bool preempt_mode;
+ bool accept_mode;
+ bool shutdown;
+};
+
+extern struct vrrp_defaults vd;
+
+/* threadmaster */
+extern struct thread_master *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 thread *t_master_down_timer;
+ struct thread *t_adver_timer;
+ struct thread *t_read;
+ struct thread *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;
+
+ 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.
+ *
+ * r
+ * 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_router *r, 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.
+ *
+ * r
+ * 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_router *r, 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 *vrrp_state_names[3];
+extern const char *vrrp_event_names[2];
+
+/*
+ * 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 interface block-level configuration to vty.
+ *
+ * vty
+ * vty to write config to
+ *
+ * Returns:
+ * # of lines written
+ */
+int vrrp_config_write_interface(struct vty *vty);
+
+/*
+ * 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(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 0000000000..8e903e137f
--- /dev/null
+++ b/vrrpd/vrrp_arp.c
@@ -0,0 +1,211 @@
+/*
+ * VRRP ARP handling.
+ * Copyright (C) 2001-2017 Alexandre Cassen
+ * Portions:
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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\n",
+ r->vr->vrid, family2str(r->family), ifp->name);
+ return;
+ }
+
+ /* Build garp */
+ garpbuf_len = vrrp_build_garp(garpbuf, ifp, v4);
+
+ /* 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\n",
+ 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_elevate_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 0000000000..21f2c4edd1
--- /dev/null
+++ b/vrrpd/vrrp_arp.h
@@ -0,0 +1,36 @@
+/*
+ * VRRP ARP handling.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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 0000000000..09d5e780cf
--- /dev/null
+++ b/vrrpd/vrrp_debug.c
@@ -0,0 +1,131 @@
+/*
+ * VRRP debugging.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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 0000000000..20f9930955
--- /dev/null
+++ b/vrrpd/vrrp_debug.h
@@ -0,0 +1,87 @@
+/*
+ * VRRP debugging.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef __VRRP_DEBUG_H__
+#define __VRRP_DEBUG_H__
+
+#include <zebra.h>
+
+#include "lib/debug.h"
+
+/* VRRP debugging records */
+struct debug vrrp_dbg_arp;
+struct debug vrrp_dbg_auto;
+struct debug vrrp_dbg_ndisc;
+struct debug vrrp_dbg_pkt;
+struct debug vrrp_dbg_proto;
+struct debug vrrp_dbg_sock;
+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 0000000000..46a92d936a
--- /dev/null
+++ b/vrrpd/vrrp_main.c
@@ -0,0 +1,159 @@
+/*
+ * VRRP entry point.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <zebra.h>
+
+#include <lib/version.h>
+
+#include "lib/command.h"
+#include "lib/filter.h"
+#include "lib/getopt.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/thread.h"
+#include "lib/vrf.h"
+
+#include "vrrp.h"
+#include "vrrp_debug.h"
+#include "vrrp_vty.h"
+#include "vrrp_zebra.h"
+
+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 thread_master *master;
+
+/* SIGHUP handler. */
+static void sighup(void)
+{
+ zlog_info("SIGHUP received");
+}
+
+/* SIGINT / SIGTERM handler. */
+static void __attribute__((noreturn)) sigint(void)
+{
+ zlog_notice("Terminating on signal");
+
+ vrrp_fini();
+
+ exit(0);
+}
+
+/* SIGUSR1 handler. */
+static void sigusr1(void)
+{
+ zlog_rotate();
+}
+
+struct quagga_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 *vrrp_yang_modules[] = {
+ &frr_interface_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);
+ break;
+ }
+ }
+
+ master = frr_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_memory.c b/vrrpd/vrrp_memory.c
new file mode 100644
index 0000000000..30eef523cd
--- /dev/null
+++ b/vrrpd/vrrp_memory.c
@@ -0,0 +1,29 @@
+/*
+ * VRRP memory types.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <zebra.h>
+
+#include "lib/memory.h"
+
+#include "vrrp_memory.h"
+
+DEFINE_MGROUP(VRRPD, "vrrpd");
+DEFINE_MTYPE(VRRPD, VRRP_IP, "VRRP IP address");
+DEFINE_MTYPE(VRRPD, VRRP_PKT, "VRRP packet");
+DEFINE_MTYPE(VRRPD, VRRP_RTR, "VRRP Router");
diff --git a/vrrpd/vrrp_memory.h b/vrrpd/vrrp_memory.h
new file mode 100644
index 0000000000..c3025d1acb
--- /dev/null
+++ b/vrrpd/vrrp_memory.h
@@ -0,0 +1,32 @@
+/*
+ * VRRP memory types.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef __VRRP_MEMORY_H__
+#define __VRRP_MEMORY_H__
+
+#include <zebra.h>
+
+#include "lib/memory.h"
+
+DECLARE_MGROUP(VRRPD);
+DECLARE_MTYPE(VRRP_IP);
+DECLARE_MTYPE(VRRP_PKT);
+DECLARE_MTYPE(VRRP_RTR);
+
+#endif /* __VRRP_MEMORY_H__ */
diff --git a/vrrpd/vrrp_ndisc.c b/vrrpd/vrrp_ndisc.c
new file mode 100644
index 0000000000..8081533ebc
--- /dev/null
+++ b/vrrpd/vrrp_ndisc.c
@@ -0,0 +1,242 @@
+/*
+ * VRRP Neighbor Discovery.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * Portions:
+ * Copyright (C) 2001-2017 Alexandre Cassen
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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 *)((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;
+ icmp6h->icmp6_cksum = in_cksum_with_ph6(&ph, (void *)icmp6h, 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_elevate_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 0000000000..efbef348d0
--- /dev/null
+++ b/vrrpd/vrrp_ndisc.h
@@ -0,0 +1,74 @@
+/*
+ * VRRP Neighbor Discovery.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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_packet.c b/vrrpd/vrrp_packet.c
new file mode 100644
index 0000000000..c3f2afba4c
--- /dev/null
+++ b/vrrpd/vrrp_packet.c
@@ -0,0 +1,321 @@
+/*
+ * VRRP packet crafting.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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_memory.h"
+#include "vrrp_packet.h"
+
+/* clang-format off */
+const char *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)
+{
+ 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 = 112;
+ chksum = in_cksum_with_ph6(&ph, pkt, pktsize);
+ } else if (!v6 && ((pkt->hdr.vertype >> 4) == 3)) {
+ struct ipv4_ph ph = {};
+
+ ph.src = src->ipaddr_v4;
+ inet_pton(AF_INET, VRRP_MCASTV4_GROUP_STR, &ph.dst);
+ ph.proto = 112;
+ ph.len = htons(pktsize);
+ chksum = in_cksum_with_ph4(&ph, 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 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);
+
+ return pktsize;
+}
+
+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, 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; %" PRIu16
+ "!= %zu",
+ ntohs(ip->ip_len), read);
+
+ /* TTL check */
+ VRRP_PKT_VCHECK(ip->ip_ttl == 255,
+ "IPv4 TTL is %" PRIu8 "; 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 %" PRIu8 "; 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);
+
+ VRRP_PKT_VCHECK((*pkt)->hdr.chksum == chksum,
+ "Bad VRRP checksum %" PRIx16 "; should be %" PRIx16 "",
+ (*pkt)->hdr.chksum, chksum);
+
+ /* Type check */
+ VRRP_PKT_VCHECK(((*pkt)->hdr.vertype & 0x0F) == 1, "Bad type %" PRIu8,
+ (*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 %" PRIu8,
+ (*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, #%" PRIu8, 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 0000000000..475e4780d5
--- /dev/null
+++ b/vrrpd/vrrp_packet.h
@@ -0,0 +1,202 @@
+/*
+ * VRRP packet crafting.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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
+
+extern const char *vrrp_packet_names[16];
+
+/*
+ * 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);
+
+/*
+ * 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, 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 0000000000..48d81b0258
--- /dev/null
+++ b/vrrpd/vrrp_vty.c
@@ -0,0 +1,751 @@
+/*
+ * VRRP CLI commands.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <zebra.h>
+
+#include "lib/command.h"
+#include "lib/if.h"
+#include "lib/ipaddr.h"
+#include "lib/json.h"
+#include "lib/prefix.h"
+#include "lib/termtable.h"
+#include "lib/vty.h"
+
+#include "vrrp.h"
+#include "vrrp_debug.h"
+#include "vrrp_memory.h"
+#include "vrrp_vty.h"
+#ifndef VTYSH_EXTRACT_PL
+#include "vrrpd/vrrp_vty_clippy.c"
+#endif
+
+
+#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 IPv4 address\n"
+#define VRRP_VERSION_STR "VRRP protocol version\n"
+
+#define VROUTER_GET_VTY(_vty, _ifp, _vrid, _vr) \
+ do { \
+ _vr = vrrp_lookup(_ifp, _vrid); \
+ if (!_vr) { \
+ vty_out(_vty, \
+ "%% Please configure VRRP instance %u\n", \
+ (unsigned int)_vrid); \
+ return CMD_WARNING_CONFIG_FAILED; \
+ } \
+ } while (0)
+
+/* clang-format off */
+
+DEFPY(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)
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+
+ struct vrrp_vrouter *vr = vrrp_lookup(ifp, vrid);
+
+ if (version == 0)
+ version = 3;
+
+ if (no && vr)
+ vrrp_vrouter_destroy(vr);
+ else if (no && !vr)
+ vty_out(vty, "%% VRRP instance %ld does not exist on %s\n",
+ vrid, ifp->name);
+ else if (!vr)
+ vrrp_vrouter_create(ifp, vrid, version);
+ else if (vr)
+ vty_out(vty, "%% VRRP instance %ld already exists on %s\n",
+ vrid, ifp->name);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(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")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+
+ struct vrrp_vrouter *vr;
+
+ VROUTER_GET_VTY(vty, ifp, vrid, vr);
+
+ if (!no) {
+ if (vr->v4->fsm.state != VRRP_STATE_INITIALIZE)
+ vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN);
+ if (vr->v6->fsm.state != VRRP_STATE_INITIALIZE)
+ vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN);
+ vr->shutdown = true;
+ } else {
+ vr->shutdown = false;
+ vrrp_check_start(vr);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(vrrp_priority,
+ vrrp_priority_cmd,
+ "[no] vrrp (1-255)$vrid priority (1-254)",
+ NO_STR
+ VRRP_STR
+ VRRP_VRID_STR
+ VRRP_PRIORITY_STR
+ "Priority value")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+
+ struct vrrp_vrouter *vr;
+ uint8_t newprio = no ? vd.priority : priority;
+
+ VROUTER_GET_VTY(vty, ifp, vrid, vr);
+
+ vrrp_set_priority(vr, newprio);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(vrrp_advertisement_interval,
+ 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")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+
+ struct vrrp_vrouter *vr;
+ uint16_t newadvint =
+ no ? vd.advertisement_interval * 10 : advertisement_interval;
+
+ if (newadvint % 10 != 0) {
+ vty_out(vty, "%% Value must be a multiple of 10\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ /* all internal computations are in centiseconds */
+ newadvint /= CS2MS;
+
+ VROUTER_GET_VTY(vty, ifp, vrid, vr);
+ vrrp_set_advertisement_interval(vr, newadvint);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(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)
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+
+ struct vrrp_vrouter *vr;
+ bool deactivated = false;
+ bool activated = false;
+ bool failed = false;
+ int ret = CMD_SUCCESS;
+ int oldstate;
+
+ VROUTER_GET_VTY(vty, ifp, vrid, vr);
+
+ bool will_activate = (vr->v4->fsm.state == VRRP_STATE_INITIALIZE);
+
+ if (no) {
+ oldstate = vr->v4->fsm.state;
+ failed = vrrp_del_ipv4(vr, ip);
+ vrrp_check_start(vr);
+ deactivated = (vr->v4->fsm.state == VRRP_STATE_INITIALIZE
+ && oldstate != VRRP_STATE_INITIALIZE);
+ } else {
+ oldstate = vr->v4->fsm.state;
+ failed = vrrp_add_ipv4(vr, ip);
+ vrrp_check_start(vr);
+ activated = (vr->v4->fsm.state != VRRP_STATE_INITIALIZE
+ && oldstate == VRRP_STATE_INITIALIZE);
+ }
+
+ if (activated)
+ vty_out(vty, "%% Activated IPv4 Virtual Router %ld\n", vrid);
+ if (deactivated)
+ vty_out(vty, "%% Deactivated IPv4 Virtual Router %ld\n", vrid);
+ if (failed) {
+ vty_out(vty, "%% Failed to %s virtual IP\n",
+ no ? "remove" : "add");
+ ret = CMD_WARNING_CONFIG_FAILED;
+ if (will_activate && !activated) {
+ vty_out(vty,
+ "%% Failed to activate IPv4 Virtual Router %ld\n",
+ vrid);
+ }
+ }
+
+ return ret;
+}
+
+DEFPY(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)
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+
+ struct vrrp_vrouter *vr;
+ bool deactivated = false;
+ bool activated = false;
+ bool failed = false;
+ int ret = CMD_SUCCESS;
+ int oldstate;
+
+ VROUTER_GET_VTY(vty, ifp, vrid, vr);
+
+ if (vr->version != 3) {
+ vty_out(vty,
+ "%% Cannot add IPv6 address to VRRPv2 virtual router\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ bool will_activate = (vr->v6->fsm.state == VRRP_STATE_INITIALIZE);
+
+ if (no) {
+ oldstate = vr->v6->fsm.state;
+ failed = vrrp_del_ipv6(vr, ipv6);
+ vrrp_check_start(vr);
+ deactivated = (vr->v6->fsm.state == VRRP_STATE_INITIALIZE
+ && oldstate != VRRP_STATE_INITIALIZE);
+ } else {
+ oldstate = vr->v6->fsm.state;
+ failed = vrrp_add_ipv6(vr, ipv6);
+ vrrp_check_start(vr);
+ activated = (vr->v6->fsm.state != VRRP_STATE_INITIALIZE
+ && oldstate == VRRP_STATE_INITIALIZE);
+ }
+
+ if (activated)
+ vty_out(vty, "%% Activated IPv6 Virtual Router %ld\n", vrid);
+ if (deactivated)
+ vty_out(vty, "%% Deactivated IPv6 Virtual Router %ld\n", vrid);
+ if (failed) {
+ vty_out(vty, "%% Failed to %s virtual IP\n",
+ no ? "remove" : "add");
+ ret = CMD_WARNING_CONFIG_FAILED;
+ if (will_activate && !activated) {
+ vty_out(vty,
+ "%% Failed to activate IPv6 Virtual Router %ld\n",
+ vrid);
+ }
+ }
+
+ return ret;
+}
+
+DEFPY(vrrp_preempt,
+ vrrp_preempt_cmd,
+ "[no] vrrp (1-255)$vrid preempt",
+ NO_STR
+ VRRP_STR
+ VRRP_VRID_STR
+ "Preempt mode\n")
+{
+ VTY_DECLVAR_CONTEXT(interface, ifp);
+
+ struct vrrp_vrouter *vr;
+
+ VROUTER_GET_VTY(vty, ifp, vrid, vr);
+
+ vr->preempt_mode = !no;
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(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;
+}
+
+DEFPY(vrrp_default,
+ vrrp_default_cmd,
+ "[no] vrrp default <advertisement-interval$adv (10-40950)$advint|preempt$p|priority$prio (1-254)$prioval|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"
+ "Force VRRP router into administrative shutdown\n")
+{
+ if (adv) {
+ if (advint % 10 != 0) {
+ vty_out(vty, "%% Value must be a multiple of 10\n");
+ 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 (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_string_add(j, "interface", vr->ifp->name);
+ json_object_int_add(j, "advertisementInterval",
+ vr->advertisement_interval * CS2MS);
+ /* 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|%" PRIu32, "Virtual Router ID", vr->vrid);
+ ttable_add_row(tt, "%s|%" PRIu8, "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|%" PRIu8, "Priority", vr->priority);
+ ttable_add_row(tt, "%s|%" PRIu8, "Effective Priority (v4)",
+ vr->v4->priority);
+ ttable_add_row(tt, "%s|%" PRIu8, "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|%d ms", "Advertisement Interval",
+ vr->advertisement_interval * CS2MS);
+ ttable_add_row(tt, "%s|%d ms",
+ "Master Advertisement Interval (v4)",
+ vr->v4->master_adver_interval * CS2MS);
+ ttable_add_row(tt, "%s|%d ms",
+ "Master Advertisement Interval (v6)",
+ vr->v6->master_adver_interval * CS2MS);
+ ttable_add_row(tt, "%s|%" PRIu32, "Advertisements Tx (v4)",
+ vr->v4->stats.adver_tx_cnt);
+ ttable_add_row(tt, "%s|%" PRIu32, "Advertisements Tx (v6)",
+ vr->v6->stats.adver_tx_cnt);
+ ttable_add_row(tt, "%s|%" PRIu32, "Advertisements Rx (v4)",
+ vr->v4->stats.adver_rx_cnt);
+ ttable_add_row(tt, "%s|%" PRIu32, "Advertisements Rx (v6)",
+ vr->v6->stats.adver_rx_cnt);
+ ttable_add_row(tt, "%s|%" PRIu32, "Gratuitous ARP Tx (v4)",
+ vr->v4->stats.garp_tx_cnt);
+ ttable_add_row(tt, "%s|%" PRIu32, "Neigh. Adverts Tx (v6)",
+ vr->v6->stats.una_tx_cnt);
+ ttable_add_row(tt, "%s|%" PRIu32, "State transitions (v4)",
+ vr->v4->stats.trans_cnt);
+ ttable_add_row(tt, "%s|%" PRIu32, "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(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(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|%" PRIu8 "|%" PRIu8 "|%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(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);
+
+ return CMD_SUCCESS;
+}
+
+/* clang-format on */
+
+static struct cmd_node interface_node = {INTERFACE_NODE, "%s(config-if)# ", 1};
+static struct cmd_node debug_node = {DEBUG_NODE, "", 1};
+static struct cmd_node vrrp_node = {VRRP_NODE, "", 1};
+
+void vrrp_vty_init(void)
+{
+ install_node(&debug_node, vrrp_config_write_debug);
+ install_node(&interface_node, vrrp_config_write_interface);
+ install_node(&vrrp_node, vrrp_config_write_global);
+ if_cmd_init();
+
+ install_element(VIEW_NODE, &vrrp_vrid_show_cmd);
+ install_element(VIEW_NODE, &vrrp_vrid_show_summary_cmd);
+ install_element(VIEW_NODE, &show_debugging_vrrp_cmd);
+ install_element(VIEW_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, &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);
+}
diff --git a/vrrpd/vrrp_vty.h b/vrrpd/vrrp_vty.h
new file mode 100644
index 0000000000..377321ec4a
--- /dev/null
+++ b/vrrpd/vrrp_vty.h
@@ -0,0 +1,25 @@
+/*
+ * VRRP CLI commands.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef __VRRP_VTY_H__
+#define __VRRP_VTY_H__
+
+void vrrp_vty_init(void);
+
+#endif /* __VRRP_VTY_H__ */
diff --git a/vrrpd/vrrp_zebra.c b/vrrpd/vrrp_zebra.c
new file mode 100644
index 0000000000..7503034de3
--- /dev/null
+++ b/vrrpd/vrrp_zebra.c
@@ -0,0 +1,252 @@
+/*
+ * VRRP Zebra interfacing.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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, vrf_id_t vrf_id,
+ const char *func)
+{
+ DEBUGD(&vrrp_dbg_zebra,
+ "%s: %s index %d(%u) flags %ld metric %d mtu %d operative %d",
+ func, ifp->name, ifp->ifindex, vrf_id, (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 %s %s", func,
+ ifp->name, inet_ntoa(p->u.prefix4),
+ 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;
+}
+
+static int vrrp_zebra_if_add(int command, struct zclient *zclient,
+ zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct interface *ifp;
+
+ /*
+ * zebra api adds/dels interfaces using the same call
+ * interface_add_read below, see comments in lib/zclient.c
+ */
+ ifp = zebra_interface_add_read(zclient->ibuf, vrf_id);
+
+ if (!ifp)
+ return 0;
+
+ vrrp_zebra_debug_if_state(ifp, vrf_id, __func__);
+
+ vrrp_if_add(ifp);
+
+ return 0;
+}
+
+static int vrrp_zebra_if_del(int command, struct zclient *zclient,
+ zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct interface *ifp;
+
+ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id);
+
+ if (!ifp)
+ return 0;
+
+ vrrp_zebra_debug_if_state(ifp, vrf_id, __func__);
+
+ vrrp_if_del(ifp);
+
+ return 0;
+}
+
+static int vrrp_zebra_if_state_up(int command, struct zclient *zclient,
+ zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct interface *ifp;
+
+ /*
+ * zebra api notifies interface up/down events by using the same call
+ * zebra_interface_state_read below, see comments in lib/zclient.c ifp =
+ * zebra_interface_state_read(zclient->ibuf, vrf_id);
+ */
+ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id);
+
+ if (!ifp)
+ return 0;
+
+ vrrp_zebra_debug_if_state(ifp, vrf_id, __func__);
+
+ vrrp_if_up(ifp);
+
+ return 0;
+}
+
+static int vrrp_zebra_if_state_down(int command, struct zclient *zclient,
+ zebra_size_t length, vrf_id_t vrf_id)
+{
+ struct interface *ifp;
+
+ /*
+ * zebra api notifies interface up/down events by using the same call
+ * zebra_interface_state_read below, see comments in lib/zclient.c
+ */
+ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id);
+
+ if (!ifp)
+ return 0;
+
+ vrrp_zebra_debug_if_state(ifp, vrf_id, __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, vrf_id, __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, vrf_id, __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, VRF_DEFAULT, r->mvl_ifp,
+ enable, VRRP_RADV_INT);
+}
+
+int 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");
+
+ return zclient_send_interface_protodown(zclient, VRF_DEFAULT, ifp,
+ down);
+}
+
+void vrrp_zebra_init(void)
+{
+ /* Socket for receiving updates from Zebra daemon */
+ zclient = zclient_new(master, &zclient_options_default);
+
+ zclient->zebra_connected = vrrp_zebra_connected;
+ zclient->router_id_update = vrrp_router_id_update_zebra;
+ zclient->interface_add = vrrp_zebra_if_add;
+ zclient->interface_delete = vrrp_zebra_if_del;
+ zclient->interface_up = vrrp_zebra_if_state_up;
+ zclient->interface_down = vrrp_zebra_if_state_down;
+ zclient->interface_address_add = vrrp_zebra_if_address_add;
+ zclient->interface_address_delete = vrrp_zebra_if_address_del;
+
+ zclient_init(zclient, ZEBRA_ROUTE_VRRP, 0, &vrrp_privs);
+
+ zlog_notice("%s: zclient socket initialized", __PRETTY_FUNCTION__);
+}
diff --git a/vrrpd/vrrp_zebra.h b/vrrpd/vrrp_zebra.h
new file mode 100644
index 0000000000..84bcba23c1
--- /dev/null
+++ b/vrrpd/vrrp_zebra.h
@@ -0,0 +1,32 @@
+/*
+ * VRRP Zebra interfacing.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#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 int vrrp_zclient_send_interface_protodown(struct interface *ifp,
+ bool down);
+
+#endif /* __VRRP_ZEBRA_H__ */
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index 24effa7047..f1a5eca74b 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -137,6 +137,7 @@ struct vtysh_client vtysh_client[] = {
{.fd = -1, .name = "pbrd", .flag = VTYSH_PBRD, .next = NULL},
{.fd = -1, .name = "staticd", .flag = VTYSH_STATICD, .next = NULL},
{.fd = -1, .name = "bfdd", .flag = VTYSH_BFDD, .next = NULL},
+ {.fd = -1, .name = "vrrpd", .flag = VTYSH_VRRPD, .next = NULL},
};
enum vtysh_write_integrated vtysh_write_integrated =
diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index eb69a20b83..3b0b570a56 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -42,6 +42,7 @@ DECLARE_MGROUP(MVTYSH)
#define VTYSH_STATICD 0x08000
#define VTYSH_BFDD 0x10000
#define VTYSH_FABRICD 0x20000
+#define VTYSH_VRRPD 0x40000
#define VTYSH_WAS_ACTIVE (-2)
@@ -50,9 +51,9 @@ DECLARE_MGROUP(MVTYSH)
/* watchfrr is not in ALL since library CLI functions should not be
* run on it (logging & co. should stay in a fixed/frozen config, and
* things like prefix lists are not even initialised) */
-#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_SHARPD|VTYSH_PBRD|VTYSH_STATICD|VTYSH_BFDD|VTYSH_FABRICD
+#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_SHARPD|VTYSH_PBRD|VTYSH_STATICD|VTYSH_BFDD|VTYSH_FABRICD|VTYSH_VRRPD
#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_EIGRPD|VTYSH_SHARPD|VTYSH_FABRICD
-#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_PBRD|VTYSH_FABRICD
+#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_PBRD|VTYSH_FABRICD|VTYSH_VRRPD
#define VTYSH_NS VTYSH_ZEBRA
#define VTYSH_VRF VTYSH_ZEBRA|VTYSH_PIMD|VTYSH_STATICD
#define VTYSH_KEYS VTYSH_RIPD|VTYSH_EIGRPD
diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c
index 7ca3ed9c5e..cf94ab643a 100644
--- a/vtysh/vtysh_config.c
+++ b/vtysh/vtysh_config.c
@@ -257,6 +257,10 @@ void vtysh_config_parse_line(void *arg, const char *line)
strlen(" exit-vrf"))
== 0) {
config_add_line_uniq_end(config->line, line);
+ } else if (!strncmp(line, " vrrp", strlen(" vrrp"))
+ || !strncmp(line, " no vrrp",
+ strlen(" no vrrp"))) {
+ config_add_line(config->line, line);
} else if (config->index == RMAP_NODE
|| config->index == INTERFACE_NODE
|| config->index == LOGICALROUTER_NODE
diff --git a/zebra/if_netlink.c b/zebra/if_netlink.c
index ce0834f190..df8d4bfe15 100644
--- a/zebra/if_netlink.c
+++ b/zebra/if_netlink.c
@@ -1396,6 +1396,32 @@ int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup)
return 0;
}
+int netlink_protodown(struct interface *ifp, bool down)
+{
+ struct zebra_ns *zns = zebra_ns_lookup(NS_DEFAULT);
+
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifa;
+ char buf[NL_PKT_BUF_SIZE];
+ } req;
+
+ memset(&req, 0, sizeof(req));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_SETLINK;
+ req.n.nlmsg_pid = zns->netlink_cmd.snl.nl_pid;
+
+ req.ifa.ifi_index = ifp->ifindex;
+
+ addattr_l(&req.n, sizeof(req), IFLA_PROTO_DOWN, &down, 4);
+ addattr_l(&req.n, sizeof(req), IFLA_LINK, &ifp->ifindex, 4);
+
+ return netlink_talk(netlink_talk_filter, &req.n, &zns->netlink_cmd, zns,
+ 0);
+}
+
/* Interface information read by netlink. */
void interface_list(struct zebra_ns *zns)
{
diff --git a/zebra/if_netlink.h b/zebra/if_netlink.h
index 710fd52558..29fd2aca35 100644
--- a/zebra/if_netlink.h
+++ b/zebra/if_netlink.h
@@ -32,6 +32,20 @@ extern int netlink_interface_addr(struct nlmsghdr *h, ns_id_t ns_id,
extern int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup);
extern int interface_lookup_netlink(struct zebra_ns *zns);
+/*
+ * Set protodown status of interface.
+ *
+ * ifp
+ * Interface to set protodown on.
+ *
+ * down
+ * If true, set protodown on. If false, set protodown off.
+ *
+ * Returns:
+ * 0
+ */
+int netlink_protodown(struct interface *ifp, bool down);
+
#ifdef __cplusplus
}
#endif
diff --git a/zebra/interface.c b/zebra/interface.c
index b0ddcaf8bc..13582008a7 100644
--- a/zebra/interface.c
+++ b/zebra/interface.c
@@ -47,6 +47,7 @@
#include "zebra/irdp.h"
#include "zebra/zebra_ptm.h"
#include "zebra/rt_netlink.h"
+#include "zebra/if_netlink.h"
#include "zebra/interface.h"
#include "zebra/zebra_vxlan.h"
#include "zebra/zebra_errors.h"
@@ -1063,7 +1064,14 @@ void zebra_if_update_all_links(void)
}
}
-
+void zebra_if_set_protodown(struct interface *ifp, bool down)
+{
+#ifdef HAVE_NETLINK
+ netlink_protodown(ifp, down);
+#else
+ zlog_warn("Protodown is not supported on this platform");
+#endif
+}
/* Output prefix string to vty. */
static int prefix_vty_out(struct vty *vty, struct prefix *p)
diff --git a/zebra/interface.h b/zebra/interface.h
index bbb5445cc6..6a3914451a 100644
--- a/zebra/interface.h
+++ b/zebra/interface.h
@@ -422,6 +422,7 @@ extern void if_handle_vrf_change(struct interface *ifp, vrf_id_t vrf_id);
extern void zebra_if_update_link(struct interface *ifp, ifindex_t link_ifindex,
ns_id_t ns_id);
extern void zebra_if_update_all_links(void);
+extern void zebra_if_set_protodown(struct interface *ifp, bool down);
extern void vrf_add_update(struct vrf *vrfp);
diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c
index 03b9653ce6..94bfa34b38 100644
--- a/zebra/zapi_msg.c
+++ b/zebra/zapi_msg.c
@@ -71,6 +71,8 @@
static void zserv_encode_interface(struct stream *s, struct interface *ifp)
{
/* Interface information. */
+ struct zebra_if *zif = ifp->info;
+
stream_put(s, ifp->name, INTERFACE_NAMSIZ);
stream_putl(s, ifp->ifindex);
stream_putc(s, ifp->status);
@@ -82,6 +84,7 @@ static void zserv_encode_interface(struct stream *s, struct interface *ifp)
stream_putl(s, ifp->mtu);
stream_putl(s, ifp->mtu6);
stream_putl(s, ifp->bandwidth);
+ stream_putl(s, zif->link_ifindex);
stream_putl(s, ifp->ll_type);
stream_putl(s, ifp->hw_addr_len);
if (ifp->hw_addr_len)
@@ -1336,6 +1339,37 @@ static void zread_interface_delete(ZAPI_HANDLER_ARGS)
{
}
+/*
+ * Handle message requesting interface be set up or down.
+ */
+static void zread_interface_set_protodown(ZAPI_HANDLER_ARGS)
+{
+ ifindex_t ifindex;
+ struct interface *ifp;
+ char down;
+
+ STREAM_GETL(msg, ifindex);
+ STREAM_GETC(msg, down);
+
+ /* set ifdown */
+ ifp = if_lookup_by_index_per_ns(zebra_ns_lookup(NS_DEFAULT), ifindex);
+
+ if (ifp) {
+ zlog_info("Setting interface %s (%u): protodown %s", ifp->name,
+ ifindex, down ? "on" : "off");
+ zebra_if_set_protodown(ifp, down);
+ } else {
+ zlog_warn(
+ "Cannot set protodown %s for interface %u; does not exist",
+ down ? "on" : "off", ifindex);
+ }
+
+
+stream_failure:
+ return;
+}
+
+
void zserv_nexthop_num_warn(const char *caller, const struct prefix *p,
const unsigned int nexthop_num)
{
@@ -2412,6 +2446,7 @@ void (*zserv_handlers[])(ZAPI_HANDLER_ARGS) = {
[ZEBRA_ROUTER_ID_DELETE] = zread_router_id_delete,
[ZEBRA_INTERFACE_ADD] = zread_interface_add,
[ZEBRA_INTERFACE_DELETE] = zread_interface_delete,
+ [ZEBRA_INTERFACE_SET_PROTODOWN] = zread_interface_set_protodown,
[ZEBRA_ROUTE_ADD] = zread_route_add,
[ZEBRA_ROUTE_DELETE] = zread_route_del,
[ZEBRA_REDISTRIBUTE_ADD] = zebra_redistribute_add,
diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
index 8d4f49e3ee..3b305f6e3d 100644
--- a/zebra/zebra_rib.c
+++ b/zebra/zebra_rib.c
@@ -101,6 +101,7 @@ static const struct {
[ZEBRA_ROUTE_PBR] = {ZEBRA_ROUTE_PBR, 200, 4},
[ZEBRA_ROUTE_BFD] = {ZEBRA_ROUTE_BFD, 255, 4},
[ZEBRA_ROUTE_OPENFABRIC] = {ZEBRA_ROUTE_OPENFABRIC, 115, 2},
+ [ZEBRA_ROUTE_VRRP] = {ZEBRA_ROUTE_VRRP, 255, 4}
/* Any new route type added to zebra, should be mirrored here */
/* no entry/default: 150 */