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: 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: 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: 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 +.. clicmd:: [no] vrrp default + + 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 +#include + #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 @@ -239,6 +239,26 @@ extern struct list *list_dup(struct list *l); 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. * 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 + +#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 +#include + +#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 + +#include +#include +#include + +#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 + +#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 + +#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 + +#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 + +#include + +#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 + +#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 + +#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 + +#include +#include +#include +#include + +#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 +#include +#include + +#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 +#include +#include +#include + +#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 + +#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 + +#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 ", + 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 + +#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 + +#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 */