summaryrefslogtreecommitdiffstats
path: root/vrrpd/vrrp_ndisc.c
diff options
context:
space:
mode:
Diffstat (limited to 'vrrpd/vrrp_ndisc.c')
-rw-r--r--vrrpd/vrrp_ndisc.c243
1 files changed, 243 insertions, 0 deletions
diff --git a/vrrpd/vrrp_ndisc.c b/vrrpd/vrrp_ndisc.c
new file mode 100644
index 0000000..b989e66
--- /dev/null
+++ b/vrrpd/vrrp_ndisc.c
@@ -0,0 +1,243 @@
+/*
+ * VRRP Neighbor Discovery.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * Portions:
+ * Copyright (C) 2001-2017 Alexandre Cassen
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <zebra.h>
+
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+
+#include "lib/checksum.h"
+#include "lib/if.h"
+#include "lib/ipaddr.h"
+#include "lib/log.h"
+
+#include "vrrp_debug.h"
+#include "vrrp_ndisc.h"
+
+#define VRRP_LOGPFX "[NDISC] "
+
+#define VRRP_NDISC_HOPLIMIT 255
+#define VRRP_NDISC_SIZE \
+ ETHER_HDR_LEN + sizeof(struct ip6_hdr) \
+ + sizeof(struct nd_neighbor_advert) \
+ + sizeof(struct nd_opt_hdr) + ETH_ALEN
+
+/* static vars */
+static int ndisc_fd = -1;
+
+/*
+ * Build an unsolicited Neighbour Advertisement.
+ *
+ * ifp
+ * Interface to send Neighbor Advertisement on
+ *
+ * ip
+ * IP address to send Neighbor Advertisement for
+ *
+ * buf
+ * Buffer to fill with IPv6 Neighbor Advertisement message. Includes
+ * Ethernet header.
+ *
+ * bufsiz
+ * Size of buf.
+ *
+ * Returns;
+ * -1 if bufsiz is too small
+ * 0 otherwise
+ */
+static int vrrp_ndisc_una_build(struct interface *ifp, struct ipaddr *ip,
+ uint8_t *buf, size_t bufsiz)
+{
+ if (bufsiz < VRRP_NDISC_SIZE)
+ return -1;
+
+ memset(buf, 0x00, bufsiz);
+
+ struct ether_header *eth = (struct ether_header *)buf;
+ struct ip6_hdr *ip6h = (struct ip6_hdr *)((char *)eth + ETHER_HDR_LEN);
+ struct nd_neighbor_advert *ndh =
+ (struct nd_neighbor_advert *)((char *)ip6h
+ + sizeof(struct ip6_hdr));
+ struct icmp6_hdr *icmp6h = &ndh->nd_na_hdr;
+ struct nd_opt_hdr *nd_opt_h =
+ (struct nd_opt_hdr *)((char *)ndh
+ + sizeof(struct nd_neighbor_advert));
+ char *nd_opt_lladdr = ((char *)nd_opt_h + sizeof(struct nd_opt_hdr));
+ char *lladdr = (char *)ifp->hw_addr;
+
+ /*
+ * An IPv6 packet with a multicast destination address DST, consisting
+ * of the sixteen octets DST[1] through DST[16], is transmitted to the
+ * Ethernet multicast address whose first two octets are the value 3333
+ * hexadecimal and whose last four octets are the last four octets of
+ * DST.
+ * - RFC2464.7
+ *
+ * In this case we are sending to the all nodes multicast address, so
+ * the last four octets are 0x00 0x00 0x00 0x01.
+ */
+ memset(eth->ether_dhost, 0, ETH_ALEN);
+ eth->ether_dhost[0] = 0x33;
+ eth->ether_dhost[1] = 0x33;
+ eth->ether_dhost[5] = 1;
+
+ /* Set source Ethernet address to interface link layer address */
+ memcpy(eth->ether_shost, lladdr, ETH_ALEN);
+ eth->ether_type = htons(ETHERTYPE_IPV6);
+
+ /* IPv6 Header */
+ ip6h->ip6_vfc = 6 << 4;
+ ip6h->ip6_plen = htons(sizeof(struct nd_neighbor_advert)
+ + sizeof(struct nd_opt_hdr) + ETH_ALEN);
+ ip6h->ip6_nxt = IPPROTO_ICMPV6;
+ ip6h->ip6_hlim = VRRP_NDISC_HOPLIMIT;
+ memcpy(&ip6h->ip6_src, &ip->ipaddr_v6, sizeof(struct in6_addr));
+ /* All nodes multicast address */
+ ip6h->ip6_dst.s6_addr[0] = 0xFF;
+ ip6h->ip6_dst.s6_addr[1] = 0x02;
+ ip6h->ip6_dst.s6_addr[15] = 0x01;
+
+ /* ICMPv6 Header */
+ ndh->nd_na_type = ND_NEIGHBOR_ADVERT;
+ ndh->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
+ ndh->nd_na_flags_reserved |= ND_NA_FLAG_OVERRIDE;
+ memcpy(&ndh->nd_na_target, &ip->ipaddr_v6, sizeof(struct in6_addr));
+
+ /* NDISC Option header */
+ nd_opt_h->nd_opt_type = ND_OPT_TARGET_LINKADDR;
+ nd_opt_h->nd_opt_len = 1;
+ memcpy(nd_opt_lladdr, lladdr, ETH_ALEN);
+
+ /* Compute checksum */
+ uint32_t len = sizeof(struct nd_neighbor_advert)
+ + sizeof(struct nd_opt_hdr) + ETH_ALEN;
+ struct ipv6_ph ph = {};
+
+ ph.src = ip6h->ip6_src;
+ ph.dst = ip6h->ip6_dst;
+ ph.ulpl = htonl(len);
+ ph.next_hdr = IPPROTO_ICMPV6;
+
+ /* Suppress static analysis warnings about accessing icmp6 oob */
+ void *offset = icmp6h;
+ icmp6h->icmp6_cksum = in_cksum_with_ph6(&ph, offset, len);
+
+ return 0;
+}
+
+int vrrp_ndisc_una_send(struct vrrp_router *r, struct ipaddr *ip)
+{
+ assert(r->family == AF_INET6);
+
+ int ret = 0;
+ struct interface *ifp = r->mvl_ifp;
+ uint8_t buf[VRRP_NDISC_SIZE];
+
+ ret = vrrp_ndisc_una_build(ifp, ip, buf, sizeof(buf));
+
+ if (ret == -1)
+ return ret;
+
+ struct sockaddr_ll sll;
+ ssize_t len;
+
+ /* Build the dst device */
+ memset(&sll, 0, sizeof(sll));
+ sll.sll_family = AF_PACKET;
+ memcpy(sll.sll_addr, ifp->hw_addr, ETH_ALEN);
+ sll.sll_halen = ETH_ALEN;
+ sll.sll_ifindex = (int)ifp->ifindex;
+
+ char ipbuf[INET6_ADDRSTRLEN];
+
+ ipaddr2str(ip, ipbuf, sizeof(ipbuf));
+
+ DEBUGD(&vrrp_dbg_ndisc,
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Sending unsolicited Neighbor Advertisement on %s for %s",
+ r->vr->vrid, family2str(r->family), ifp->name, ipbuf);
+
+ if (DEBUG_MODE_CHECK(&vrrp_dbg_ndisc, DEBUG_MODE_ALL)
+ && DEBUG_MODE_CHECK(&vrrp_dbg_pkt, DEBUG_MODE_ALL))
+ zlog_hexdump(buf, VRRP_NDISC_SIZE);
+
+ len = sendto(ndisc_fd, buf, VRRP_NDISC_SIZE, 0, (struct sockaddr *)&sll,
+ sizeof(sll));
+
+ if (len < 0) {
+ zlog_err(
+ VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM
+ "Error sending unsolicited Neighbor Advertisement on %s for %s",
+ r->vr->vrid, family2str(r->family), ifp->name, ipbuf);
+ ret = -1;
+ } else {
+ ++r->stats.una_tx_cnt;
+ }
+
+ return ret;
+}
+
+int vrrp_ndisc_una_send_all(struct vrrp_router *r)
+{
+ assert(r->family == AF_INET6);
+
+ struct listnode *ln;
+ struct ipaddr *ip;
+
+ for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, ip))
+ vrrp_ndisc_una_send(r, ip);
+
+ return 0;
+}
+
+void vrrp_ndisc_init(void)
+{
+ frr_with_privs(&vrrp_privs) {
+ ndisc_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6));
+ }
+
+ if (ndisc_fd > 0) {
+ DEBUGD(&vrrp_dbg_sock,
+ VRRP_LOGPFX "Initialized Neighbor Discovery socket");
+ DEBUGD(&vrrp_dbg_ndisc,
+ VRRP_LOGPFX "Initialized Neighbor Discovery subsystem");
+ } else {
+ zlog_err(VRRP_LOGPFX
+ "Error initializing Neighbor Discovery socket");
+ }
+}
+
+void vrrp_ndisc_fini(void)
+{
+ close(ndisc_fd);
+ ndisc_fd = -1;
+
+ DEBUGD(&vrrp_dbg_ndisc,
+ VRRP_LOGPFX "Deinitialized Neighbor Discovery subsystem");
+}
+
+bool vrrp_ndisc_is_init(void)
+{
+ return ndisc_fd > 0;
+}