diff options
| author | Quentin Young <qlyoung@cumulusnetworks.com> | 2019-01-27 23:08:01 +0000 | 
|---|---|---|
| committer | Quentin Young <qlyoung@cumulusnetworks.com> | 2019-05-17 00:27:08 +0000 | 
| commit | 4f52e9a685177d34486aff19d9f3c486443c9b41 (patch) | |
| tree | 9b5ffcc34e48b2ab2e0a91686f27624a9b81176d /vrrpd/vrrp_ndisc.c | |
| parent | 667179cae42b6d39db7a0bb1f76b7591f26e720f (diff) | |
vrrpd: send ICMPv6 Neighbor Advertisements
Signed-off-by: Quentin Young <qlyoung@cumulusnetworks.com>
Diffstat (limited to 'vrrpd/vrrp_ndisc.c')
| -rw-r--r-- | vrrpd/vrrp_ndisc.c | 228 | 
1 files changed, 228 insertions, 0 deletions
diff --git a/vrrpd/vrrp_ndisc.c b/vrrpd/vrrp_ndisc.c new file mode 100644 index 0000000000..ce6c62c07e --- /dev/null +++ b/vrrpd/vrrp_ndisc.c @@ -0,0 +1,228 @@ +/* + * 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_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)); + +	zlog_debug(VRRP_LOGPFX VRRP_LOGPFX_VRID +		   "Sending unsolicited Neighbor Advertisement on %s for %s", +		   r->vr->vrid, ifp->name, ipbuf); + +	len = sendto(ndisc_fd, buf, VRRP_NDISC_SIZE, 0, (struct sockaddr *)&sll, +		     sizeof(sll)); + +	if (len < 0) { +		zlog_err( +			VRRP_LOGPFX VRRP_LOGPFX_VRID +			"Error sending unsolicited Neighbor Advertisement on %s for %s", +			r->vr->vrid, ifp->name, ipbuf); +		ret = -1; +	} + +	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) +{ +	vrrp_privs.change(ZPRIVS_RAISE); +	{ +		ndisc_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6)); +	} +	vrrp_privs.change(ZPRIVS_LOWER); + +	if (ndisc_fd > 0) +		zlog_info( +			VRRP_LOGPFX +			"Initialized unsolicited neighbor advertisement socket"); +	else +		zlog_err( +			VRRP_LOGPFX +			"Error initializing unsolicited neighbor advertisement socket"); +} + +void vrrp_ndisc_fini(void) +{ +	close(ndisc_fd); +	ndisc_fd = -1; +} + +bool vrrp_ndisc_is_init(void) +{ +	return ndisc_fd > 0; +}  | 
