|
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 |
};
|