summaryrefslogtreecommitdiffstats
path: root/vrrpd/vrrp_arp.c
diff options
context:
space:
mode:
Diffstat (limited to 'vrrpd/vrrp_arp.c')
-rw-r--r--vrrpd/vrrp_arp.c206
1 files changed, 206 insertions, 0 deletions
diff --git a/vrrpd/vrrp_arp.c b/vrrpd/vrrp_arp.c
new file mode 100644
index 0000000..0072053
--- /dev/null
+++ b/vrrpd/vrrp_arp.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * VRRP ARP handling.
+ * Copyright (C) 2001-2017 Alexandre Cassen
+ * Portions:
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Quentin Young
+ */
+#include <zebra.h>
+
+#include <linux/if_packet.h>
+#include <net/if_arp.h>
+#include <netinet/if_ether.h>
+
+#include "lib/if.h"
+#include "lib/linklist.h"
+#include "lib/log.h"
+#include "lib/memory.h"
+#include "lib/prefix.h"
+
+#include "vrrp.h"
+#include "vrrp_arp.h"
+#include "vrrp_debug.h"
+
+#define VRRP_LOGPFX "[ARP] "
+
+/*
+ * The size of the garp packet buffer should be the large enough to hold the
+ * largest arp packet to be sent + the size of the link layer header for the
+ * corresponding protocol. In this case we hardcode for Ethernet.
+ */
+#define GARP_BUFFER_SIZE \
+ sizeof(struct ether_header) + sizeof(struct arphdr) + 2 * ETH_ALEN \
+ + 2 * sizeof(struct in_addr)
+
+/* static vars */
+static int garp_fd = -1;
+
+/* Send the gratuitous ARP message */
+static ssize_t vrrp_send_garp(struct interface *ifp, uint8_t *buf,
+ ssize_t pack_len)
+{
+ struct sockaddr_ll sll;
+ ssize_t len;
+
+ /* Build the dst device */
+ memset(&sll, 0, sizeof(sll));
+ sll.sll_family = AF_PACKET;
+ sll.sll_protocol = ETH_P_ARP;
+ sll.sll_ifindex = (int)ifp->ifindex;
+ sll.sll_halen = ifp->hw_addr_len;
+ memset(sll.sll_addr, 0xFF, ETH_ALEN);
+
+ /* Send packet */
+ len = sendto(garp_fd, buf, pack_len, 0, (struct sockaddr *)&sll,
+ sizeof(sll));
+
+ return len;
+}
+
+/* Build a gratuitous ARP message over a specific interface */
+static ssize_t vrrp_build_garp(uint8_t *buf, struct interface *ifp,
+ struct in_addr *v4)
+{
+ uint8_t *arp_ptr;
+
+ if (ifp->hw_addr_len == 0)
+ return -1;
+
+ /* Build Ethernet header */
+ struct ether_header *eth = (struct ether_header *)buf;
+
+ memset(eth->ether_dhost, 0xFF, ETH_ALEN);
+ memcpy(eth->ether_shost, ifp->hw_addr, ETH_ALEN);
+ eth->ether_type = htons(ETHERTYPE_ARP);
+
+ /* Build ARP payload */
+ struct arphdr *arph = (struct arphdr *)(buf + ETHER_HDR_LEN);
+
+ arph->ar_hrd = htons(HWTYPE_ETHER);
+ arph->ar_pro = htons(ETHERTYPE_IP);
+ arph->ar_hln = ifp->hw_addr_len;
+ arph->ar_pln = sizeof(struct in_addr);
+ arph->ar_op = htons(ARPOP_REQUEST);
+ arp_ptr = (uint8_t *)(arph + 1);
+ /* Source MAC: us */
+ memcpy(arp_ptr, ifp->hw_addr, ifp->hw_addr_len);
+ arp_ptr += ifp->hw_addr_len;
+ /* Source IP: us */
+ memcpy(arp_ptr, v4, sizeof(struct in_addr));
+ arp_ptr += sizeof(struct in_addr);
+ /* Dest MAC: broadcast */
+ memset(arp_ptr, 0xFF, ETH_ALEN);
+ arp_ptr += ifp->hw_addr_len;
+ /* Dest IP: us */
+ memcpy(arp_ptr, v4, sizeof(struct in_addr));
+ arp_ptr += sizeof(struct in_addr);
+
+ return arp_ptr - buf;
+}
+
+void vrrp_garp_send(struct vrrp_router *r, struct in_addr *v4)
+{
+ struct interface *ifp = r->mvl_ifp;
+ uint8_t garpbuf[GARP_BUFFER_SIZE];
+ ssize_t garpbuf_len;
+ ssize_t sent_len;
+ char astr[INET_ADDRSTRLEN];
+
+ /* If the interface doesn't support ARP, don't try sending */
+ if (ifp->flags & IFF_NOARP) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Unable to send gratuitous ARP on %s; has IFF_NOARP",
+ r->vr->vrid, family2str(r->family), ifp->name);
+ return;
+ }
+
+ /* Build garp */
+ garpbuf_len = vrrp_build_garp(garpbuf, ifp, v4);
+
+ if (garpbuf_len < 0) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Unable to send gratuitous ARP on %s; MAC address unknown",
+ r->vr->vrid, family2str(r->family), ifp->name);
+ return;
+ };
+
+ /* Send garp */
+ inet_ntop(AF_INET, v4, astr, sizeof(astr));
+
+ DEBUGD(&vrrp_dbg_arp,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Sending gratuitous ARP on %s for %s",
+ r->vr->vrid, family2str(r->family), ifp->name, astr);
+ if (DEBUG_MODE_CHECK(&vrrp_dbg_arp, DEBUG_MODE_ALL))
+ zlog_hexdump(garpbuf, garpbuf_len);
+
+ sent_len = vrrp_send_garp(ifp, garpbuf, garpbuf_len);
+
+ if (sent_len < 0)
+ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Error sending gratuitous ARP on %s for %s",
+ r->vr->vrid, family2str(r->family), ifp->name, astr);
+ else
+ ++r->stats.garp_tx_cnt;
+}
+
+void vrrp_garp_send_all(struct vrrp_router *r)
+{
+ assert(r->family == AF_INET);
+
+ struct interface *ifp = r->mvl_ifp;
+
+ /* If the interface doesn't support ARP, don't try sending */
+ if (ifp->flags & IFF_NOARP) {
+ zlog_warn(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Unable to send gratuitous ARP on %s; has IFF_NOARP",
+ r->vr->vrid, family2str(r->family), ifp->name);
+ return;
+ }
+
+ struct listnode *ln;
+ struct ipaddr *ip;
+
+ for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, ip))
+ vrrp_garp_send(r, &ip->ipaddr_v4);
+}
+
+
+void vrrp_garp_init(void)
+{
+ /* Create the socket descriptor */
+ /* FIXME: why ETH_P_RARP? */
+ errno = 0;
+ frr_with_privs(&vrrp_privs) {
+ garp_fd = socket(PF_PACKET, SOCK_RAW | SOCK_CLOEXEC,
+ htons(ETH_P_RARP));
+ }
+
+ if (garp_fd > 0) {
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX "Initialized gratuitous ARP socket");
+ DEBUGD(&vrrp_dbg_arp,
+ VRRP_LOGPFX "Initialized gratuitous ARP subsystem");
+ } else {
+ zlog_err(VRRP_LOGPFX
+ "Error initializing gratuitous ARP subsystem");
+ }
+}
+
+void vrrp_garp_fini(void)
+{
+ close(garp_fd);
+ garp_fd = -1;
+
+ DEBUGD(&vrrp_dbg_arp,
+ VRRP_LOGPFX "Deinitialized gratuitous ARP subsystem");
+}
+
+bool vrrp_garp_is_init(void)
+{
+ return garp_fd > 0;
+}