Blame teamd/teamd_lw_nsna_ping.c

Packit cac203
/*
Packit cac203
 *   teamd_lw_nsna_ping.c - Team port IPv6 NS/NA ping link watcher
Packit cac203
 *   Copyright (C) 2012-2015 Jiri Pirko <jiri@resnulli.us>
Packit cac203
 *   Copyright (C) 2014 Erik Hugne <erik.hugne@ericsson.com>
Packit cac203
 *
Packit cac203
 *   This library is free software; you can redistribute it and/or
Packit cac203
 *   modify it under the terms of the GNU Lesser General Public
Packit cac203
 *   License as published by the Free Software Foundation; either
Packit cac203
 *   version 2.1 of the License, or (at your option) any later version.
Packit cac203
 *
Packit cac203
 *   This library is distributed in the hope that it will be useful,
Packit cac203
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit cac203
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit cac203
 *   Lesser General Public License for more details.
Packit cac203
 *
Packit cac203
 *   You should have received a copy of the GNU Lesser General Public
Packit cac203
 *   License along with this library; if not, write to the Free Software
Packit cac203
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
Packit cac203
 */
Packit cac203
Packit cac203
#include <netdb.h>
Packit cac203
#include <netinet/ip6.h>
Packit cac203
#include <netinet/icmp6.h>
Packit cac203
#include <linux/if_ether.h>
Packit cac203
#include <private/misc.h>
Packit cac203
#include "teamd.h"
Packit cac203
#include "teamd_link_watch.h"
Packit cac203
#include "teamd_config.h"
Packit cac203
Packit cac203
/*
Packit cac203
 * IPV6 NS/NA ping link watch
Packit cac203
 */
Packit cac203
Packit cac203
static int set_sockaddr_in6(struct sockaddr_in6 *sin6, const char *hostname)
Packit cac203
{
Packit cac203
	int err;
Packit cac203
Packit cac203
	err = __set_sockaddr((struct sockaddr *) sin6, sizeof(*sin6),
Packit cac203
			     AF_INET6, hostname);
Packit cac203
	if (err)
Packit cac203
		return err;
Packit cac203
	return 0;
Packit cac203
}
Packit cac203
Packit cac203
static char *str_sockaddr_in6(struct sockaddr_in6 *sin6)
Packit cac203
{
Packit cac203
	static char buf[NI_MAXHOST];
Packit cac203
Packit cac203
	return __str_sockaddr((struct sockaddr *) sin6, sizeof(*sin6), AF_INET6,
Packit cac203
			      buf, sizeof(buf));
Packit cac203
}
Packit cac203
Packit cac203
struct lw_nsnap_port_priv {
Packit cac203
	union {
Packit cac203
		struct lw_common_port_priv common;
Packit cac203
		struct lw_psr_port_priv psr;
Packit cac203
	} start; /* must be first */
Packit cac203
	int tx_sock;
Packit cac203
	struct sockaddr_in6 dst;
Packit cac203
};
Packit cac203
Packit cac203
static struct lw_nsnap_port_priv *
Packit cac203
lw_nsnap_ppriv_get(struct lw_psr_port_priv *psr_ppriv)
Packit cac203
{
Packit cac203
	return (struct lw_nsnap_port_priv *) psr_ppriv;
Packit cac203
}
Packit cac203
Packit cac203
static int icmp6_sock_open(int *sock_p)
Packit cac203
{
Packit cac203
	int sock;
Packit cac203
	struct icmp6_filter flt;
Packit cac203
	int ret;
Packit cac203
	int err;
Packit cac203
	int val;
Packit cac203
Packit cac203
	sock = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
Packit cac203
	if (sock == -1) {
Packit cac203
		teamd_log_err("Failed to create ICMP6 socket.");
Packit cac203
		return -errno;
Packit cac203
	}
Packit cac203
Packit cac203
	ICMP6_FILTER_SETBLOCKALL(&flt);
Packit cac203
	ret = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &flt, sizeof(flt));
Packit cac203
	if (ret == -1) {
Packit cac203
		teamd_log_err("Failed to setsockopt ICMP6_FILTER.");
Packit cac203
		err = -errno;
Packit cac203
		goto close_sock;
Packit cac203
	}
Packit cac203
Packit cac203
	val = 255;
Packit cac203
	ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
Packit cac203
			 &val, sizeof(val));
Packit cac203
	if (ret == -1) {
Packit cac203
		teamd_log_err("Failed to setsockopt IPV6_MULTICAST_HOPS.");
Packit cac203
		err = -errno;
Packit cac203
		goto close_sock;
Packit cac203
	}
Packit cac203
Packit cac203
	*sock_p = sock;
Packit cac203
	return 0;
Packit cac203
close_sock:
Packit cac203
	close(sock);
Packit cac203
	return err;
Packit cac203
}
Packit cac203
Packit cac203
#define OFFSET_NEXT_HEADER					\
Packit cac203
	in_struct_offset(struct ip6_hdr, ip6_nxt)
Packit cac203
#define OFFSET_NA_TYPE						\
Packit cac203
	sizeof (struct ip6_hdr) +				\
Packit cac203
	in_struct_offset(struct nd_neighbor_advert, nd_na_type)
Packit cac203
Packit cac203
static struct sock_filter na_flt[] = {
Packit cac203
	BPF_STMT(BPF_LD + BPF_B + BPF_ABS, SKF_AD_OFF + SKF_AD_PROTOCOL),
Packit cac203
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_P_IPV6, 0, 5),
Packit cac203
	BPF_STMT(BPF_LD + BPF_B + BPF_ABS, OFFSET_NEXT_HEADER),
Packit cac203
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_ICMPV6, 0, 3),
Packit cac203
	BPF_STMT(BPF_LD + BPF_B + BPF_ABS, OFFSET_NA_TYPE),
Packit cac203
	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ND_NEIGHBOR_ADVERT, 0, 1),
Packit cac203
	BPF_STMT(BPF_RET + BPF_K, (u_int) -1),
Packit cac203
	BPF_STMT(BPF_RET + BPF_K, 0),
Packit cac203
};
Packit cac203
Packit cac203
static const struct sock_fprog na_fprog = {
Packit cac203
	.len = ARRAY_SIZE(na_flt),
Packit cac203
	.filter = na_flt,
Packit cac203
};
Packit cac203
Packit cac203
static int lw_nsnap_sock_open(struct lw_psr_port_priv *psr_ppriv)
Packit cac203
{
Packit cac203
	struct lw_nsnap_port_priv *nsnap_ppriv = lw_nsnap_ppriv_get(psr_ppriv);
Packit cac203
	int err;
Packit cac203
Packit cac203
	/*
Packit cac203
	 * We use two sockets here. NS packets are send through ICMP6 socket.
Packit cac203
	 * With this socket, unfortunately, kernel does not provide a way to
Packit cac203
	 * deliver incoming ICMP6 packet on inactive ports into userspace.
Packit cac203
	 * So we use packet socket to get these packets.
Packit cac203
	 */
Packit cac203
	err = teamd_packet_sock_open(&psr_ppriv->sock,
Packit cac203
				     psr_ppriv->common.tdport->ifindex,
Packit cac203
				     htons(ETH_P_ALL), &na_fprog, NULL);
Packit cac203
	if (err)
Packit cac203
		return err;
Packit cac203
	err = icmp6_sock_open(&nsnap_ppriv->tx_sock);
Packit cac203
	if (err)
Packit cac203
		goto close_packet_sock;
Packit cac203
	return 0;
Packit cac203
close_packet_sock:
Packit cac203
	close(psr_ppriv->sock);
Packit cac203
	return err;
Packit cac203
}
Packit cac203
Packit cac203
static void lw_nsnap_sock_close(struct lw_psr_port_priv *psr_ppriv)
Packit cac203
{
Packit cac203
	struct lw_nsnap_port_priv *nsnap_ppriv = lw_nsnap_ppriv_get(psr_ppriv);
Packit cac203
Packit cac203
	close(nsnap_ppriv->tx_sock);
Packit cac203
	close(psr_ppriv->sock);
Packit cac203
}
Packit cac203
Packit cac203
static int lw_nsnap_load_options(struct teamd_context *ctx,
Packit cac203
				 struct teamd_port *tdport,
Packit cac203
				 struct lw_psr_port_priv *psr_ppriv)
Packit cac203
{
Packit cac203
	struct lw_nsnap_port_priv *nsnap_ppriv = lw_nsnap_ppriv_get(psr_ppriv);
Packit cac203
	struct teamd_config_path_cookie *cpcookie = psr_ppriv->common.cpcookie;
Packit cac203
	const char *host;
Packit cac203
	int err;
Packit cac203
Packit cac203
	err = teamd_config_string_get(ctx, &host, "@.target_host", cpcookie);
Packit cac203
	if (err) {
Packit cac203
		teamd_log_err("Failed to get \"target_host\" link-watch option.");
Packit cac203
		return -EINVAL;
Packit cac203
	}
Packit cac203
	err = set_sockaddr_in6(&nsnap_ppriv->dst, host);
Packit cac203
	if (err)
Packit cac203
		return err;
Packit cac203
	teamd_log_dbg(ctx, "target address \"%s\".",
Packit cac203
		      str_sockaddr_in6(&nsnap_ppriv->dst));
Packit cac203
Packit cac203
	return 0;
Packit cac203
}
Packit cac203
Packit cac203
static void compute_multi_in6_addr(struct in6_addr *addr)
Packit cac203
{
Packit cac203
	addr->s6_addr32[0] = htonl(0xFF020000);
Packit cac203
	addr->s6_addr32[1] = 0;
Packit cac203
	addr->s6_addr32[2] = htonl(0x1);
Packit cac203
	addr->s6_addr32[3] |= htonl(0xFF000000);
Packit cac203
}
Packit cac203
Packit cac203
struct ns_packet {
Packit cac203
	struct nd_neighbor_solicit	nsh;
Packit cac203
	struct nd_opt_hdr		opt;
Packit cac203
	unsigned char			hwaddr[ETH_ALEN];
Packit cac203
};
Packit cac203
Packit cac203
static int lw_nsnap_send(struct lw_psr_port_priv *psr_ppriv)
Packit cac203
{
Packit cac203
	struct lw_nsnap_port_priv *nsnap_ppriv = lw_nsnap_ppriv_get(psr_ppriv);
Packit cac203
	int err;
Packit cac203
	struct sockaddr_ll ll_my;
Packit cac203
	struct sockaddr_in6 sendto_addr;
Packit cac203
	struct ns_packet nsp;
Packit cac203
Packit cac203
	if (!(psr_ppriv->common.forced_send))
Packit cac203
		return 0;
Packit cac203
Packit cac203
	err = teamd_getsockname_hwaddr(psr_ppriv->sock, &ll_my,
Packit cac203
				       sizeof(nsp.hwaddr));
Packit cac203
	if (err)
Packit cac203
		return err;
Packit cac203
Packit cac203
	memset(&nsp, 0, sizeof(nsp));
Packit cac203
Packit cac203
	/* setup ICMP6 header */
Packit cac203
	nsp.nsh.nd_ns_type = ND_NEIGHBOR_SOLICIT;
Packit cac203
	nsp.nsh.nd_ns_cksum = 0; /* kernel computes this */
Packit cac203
	nsp.nsh.nd_ns_target = nsnap_ppriv->dst.sin6_addr;
Packit cac203
	nsp.opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR;
Packit cac203
	nsp.opt.nd_opt_len = 1; /* 8 bytes */
Packit cac203
	memcpy(nsp.hwaddr, ll_my.sll_addr, sizeof(nsp.hwaddr));
Packit cac203
Packit cac203
	sendto_addr = nsnap_ppriv->dst;
Packit cac203
	compute_multi_in6_addr(&sendto_addr.sin6_addr);
Packit cac203
	sendto_addr.sin6_scope_id = psr_ppriv->common.tdport->ifindex;
Packit cac203
	err = teamd_sendto(nsnap_ppriv->tx_sock, &nsp, sizeof(nsp), 0,
Packit cac203
			   (struct sockaddr *) &sendto_addr,
Packit cac203
			   sizeof(sendto_addr));
Packit cac203
	return err;
Packit cac203
}
Packit cac203
Packit cac203
struct na_packet {
Packit cac203
	struct ip6_hdr			ip6h;
Packit cac203
	struct nd_neighbor_advert	nah;
Packit cac203
	struct nd_opt_hdr		opt;
Packit cac203
	unsigned char			hwaddr[ETH_ALEN];
Packit cac203
};
Packit cac203
Packit cac203
static int lw_nsnap_receive(struct lw_psr_port_priv *psr_ppriv)
Packit cac203
{
Packit cac203
	struct lw_nsnap_port_priv *nsnap_ppriv = lw_nsnap_ppriv_get(psr_ppriv);
Packit cac203
	struct na_packet nap;
Packit cac203
	struct sockaddr_ll ll_from;
Packit cac203
	int err;
Packit cac203
Packit cac203
	err = teamd_recvfrom(psr_ppriv->sock, &nap, sizeof(nap), 0,
Packit cac203
			     (struct sockaddr *) &ll_from, sizeof(ll_from));
Packit cac203
	if (err <= 0)
Packit cac203
		return err;
Packit cac203
Packit cac203
	/* check IPV6 header */
Packit cac203
	if ((nap.ip6h.ip6_vfc & 0xf0) != 0x60 /* IPV6 */ ||
Packit cac203
	    nap.ip6h.ip6_plen != htons(sizeof(nap) - sizeof(nap.ip6h)) ||
Packit cac203
	    nap.ip6h.ip6_nxt != IPPROTO_ICMPV6 ||
Packit cac203
	    nap.ip6h.ip6_hlim != 255 /* Do not route */)
Packit cac203
		return 0;
Packit cac203
Packit cac203
	/* check ICMP6 header */
Packit cac203
	if (nap.nah.nd_na_type != ND_NEIGHBOR_ADVERT ||
Packit cac203
	    memcmp(&nap.nah.nd_na_target, &nsnap_ppriv->dst.sin6_addr,
Packit cac203
		   sizeof(struct in6_addr)) ||
Packit cac203
	    nap.opt.nd_opt_type != ND_OPT_TARGET_LINKADDR ||
Packit cac203
	    nap.opt.nd_opt_len != 1 /* 8 bytes */)
Packit cac203
		return 0;
Packit cac203
Packit cac203
	psr_ppriv->reply_received = true;
Packit cac203
	return 0;
Packit cac203
}
Packit cac203
Packit cac203
static const struct lw_psr_ops lw_psr_ops_nsnap = {
Packit cac203
	.sock_open		= lw_nsnap_sock_open,
Packit cac203
	.sock_close		= lw_nsnap_sock_close,
Packit cac203
	.load_options		= lw_nsnap_load_options,
Packit cac203
	.send			= lw_nsnap_send,
Packit cac203
	.receive		= lw_nsnap_receive,
Packit cac203
};
Packit cac203
Packit cac203
static int lw_nsnap_port_added(struct teamd_context *ctx,
Packit cac203
			       struct teamd_port *tdport,
Packit cac203
			       void *priv, void *creator_priv)
Packit cac203
{
Packit cac203
	struct lw_psr_port_priv *psr_port_priv = priv;
Packit cac203
Packit cac203
	psr_port_priv->ops = &lw_psr_ops_nsnap;
Packit cac203
	return lw_psr_port_added(ctx, tdport, priv, creator_priv);
Packit cac203
}
Packit cac203
Packit cac203
static int lw_nsnap_state_target_host_get(struct teamd_context *ctx,
Packit cac203
					  struct team_state_gsc *gsc,
Packit cac203
				          void *priv)
Packit cac203
{
Packit cac203
	struct lw_common_port_priv *common_ppriv = priv;
Packit cac203
	struct lw_psr_port_priv *psr_ppriv = lw_psr_ppriv_get(common_ppriv);
Packit cac203
	struct lw_nsnap_port_priv *nsnap_ppriv = lw_nsnap_ppriv_get(psr_ppriv);
Packit cac203
Packit cac203
	gsc->data.str_val.ptr = str_sockaddr_in6(&nsnap_ppriv->dst);
Packit cac203
	return 0;
Packit cac203
}
Packit cac203
Packit cac203
static const struct teamd_state_val lw_nsnap_state_vals[] = {
Packit cac203
	{
Packit cac203
		.subpath = "target_host",
Packit cac203
		.type = TEAMD_STATE_ITEM_TYPE_STRING,
Packit cac203
		.getter = lw_nsnap_state_target_host_get,
Packit cac203
	},
Packit cac203
	{
Packit cac203
		.subpath = "interval",
Packit cac203
		.type = TEAMD_STATE_ITEM_TYPE_INT,
Packit cac203
		.getter = lw_psr_state_interval_get,
Packit cac203
	},
Packit cac203
	{
Packit cac203
		.subpath = "init_wait",
Packit cac203
		.type = TEAMD_STATE_ITEM_TYPE_INT,
Packit cac203
		.getter = lw_psr_state_init_wait_get,
Packit cac203
	},
Packit cac203
	{
Packit cac203
		.subpath = "missed_max",
Packit cac203
		.type = TEAMD_STATE_ITEM_TYPE_INT,
Packit cac203
		.getter = lw_psr_state_missed_max_get,
Packit cac203
	},
Packit cac203
	{
Packit cac203
		.subpath = "missed",
Packit cac203
		.type = TEAMD_STATE_ITEM_TYPE_INT,
Packit cac203
		.getter = lw_psr_state_missed_get,
Packit cac203
	},
Packit cac203
};
Packit cac203
Packit cac203
const struct teamd_link_watch teamd_link_watch_nsnap = {
Packit cac203
	.name			= "nsna_ping",
Packit cac203
	.state_vg		= {
Packit cac203
		.vals		= lw_nsnap_state_vals,
Packit cac203
		.vals_count	= ARRAY_SIZE(lw_nsnap_state_vals),
Packit cac203
	},
Packit cac203
	.port_priv = {
Packit cac203
		.init		= lw_nsnap_port_added,
Packit cac203
		.fini		= lw_psr_port_removed,
Packit cac203
		.priv_size	= sizeof(struct lw_nsnap_port_priv),
Packit cac203
	},
Packit cac203
};