Files
2026-07-30 11:19:44 +05:30

715 lines
19 KiB
C

/* wrnad_neigh.c - Wind River network acceleration daemon (NAD) */
/*
* Copyright (c) 2010 Wind River Systems, Inc.
*
* The right to copy, distribute or otherwise make use of this software
* may be licensed only pursuant to the terms of an applicable Wind River
* license agreement.
*/
/* includes */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <syslog.h>
#include <string.h>
#include "wrnad.h"
#ifndef IPWRNAD
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#endif
/* defines */
/* typedefs */
/* imports */
/* globals */
/* locals */
#ifdef WRNAD_IDENTIFY_ROUTERS
/******************************************************************************
*
* wrnad_neighbor_is_router - check if the neigbor is a router
*
* RETURNS: 1 if the neighbor is a router, 0 if not, but -1 if the address
* family is neither AF_INET nor AF_INET6.
*
* ERRNO: N/A
*/
static int
wrnad_neighbor_is_router(Wrnad_proxy_interface *proxy,
int family, uint32_t *addr)
{
unsigned int ix;
if (family == AF_INET)
{
ip4_item_t *ip4 = hash_lookup_ip4addr(&proxy->v4_routers, addr, &ix);
return ip4 != NULL;
}
if (family == AF_INET6)
{
ip6_item_t *ip6 = hash_lookup_ip6addr(&proxy->v6_routers, addr, NULL);
return ip6 != NULL;
}
return -1;
}
#endif /* WRNAD_IDENTIFY_ROUTERS */
/*******************************************************************************
*
* wrnad_rtnl_neigh_statestr - find rtnetlink neighbour state in string format
*
* This routine returns the rtnetlink neighbour state as a string
*
* RETURNS: string representation of rtnetlink neighbour state.
*
* ERRNO: N/A
*/
static const char *
wrnad_rtnl_neigh_statestr(int state)
{
static char statestr[32];
switch (state)
{
case NUD_INCOMPLETE:
return "INCOMPLETE";
case NUD_REACHABLE:
return "REACHABLE";
case NUD_STALE:
return "STALE";
case NUD_DELAY:
return "DELAY";
case NUD_PROBE:
return "PROBE";
case NUD_FAILED:
return "FAILED";
case NUD_NOARP:
return "NOARP";
case NUD_PERMANENT:
return "PERMANENT";
default:
sprintf(statestr, "%d", state);
return statestr;
}
}
/*******************************************************************************
*
* wrnad_rtnl_neigh_flagsstr - find rtnetlink neigh flags in string format
*
* This routine returns the rtnetlink neigh flags as a string
*
* RETURNS: string representation of rtnetlink neigh flags.
*
* ERRNO: N/A
*/
static const char *
wrnad_rtnl_neigh_flagsstr(int flags)
{
static char flagsstr[256];
int i;
flagsstr[0] = '<';
i = 1;
if (flags == 0)
i += sprintf(&flagsstr[i], "%s", "0,");
#if 0
if (flags & NTF_USE)
i += sprintf(&flagsstr[i], "%s", "NTF,");
#endif
if (flags & NTF_PROXY)
i += sprintf(&flagsstr[i], "%s", "PROXY,");
if (flags & NTF_ROUTER)
i += sprintf(&flagsstr[i], "%s", "ROUTER,");
flagsstr[i-1] = '>'; /* remove last comma */
return flagsstr;
}
/*******************************************************************************
*
* wrnad_rtnl_neigh_typestr - find rtnetlink neighbour type in string format
*
* This routine returns the rtnetlink neighbour type as a string
*
* RETURNS: string representation of rtnetlink neighbour type.
*
* ERRNO: N/A
*/
static const char *
wrnad_rtnl_neigh_typestr(int type)
{
static char typestr[32];
switch (type)
{
case RTN_UNSPEC:
return "RTN_UNSPEC";
case RTN_UNICAST:
return "RTN_UNICAST";
case RTN_LOCAL:
return "RTN_LOCAL";
case RTN_BROADCAST:
return "RTN_BROADCAST";
case RTN_ANYCAST:
return "RTN_ANYCAST";
case RTN_MULTICAST:
return "RTN_MULTICAST";
case RTN_BLACKHOLE:
return "RTN_BLACKHOLE";
case RTN_UNREACHABLE:
return "RTN_UNREACHABLE";
case RTN_PROHIBIT:
return "RTN_PROHIBIT";
case RTN_THROW:
return "RTN_THROW";
case RTN_NAT:
return "RTN_NAT";
case RTN_XRESOLVE:
return "RTN_XRESOLVE";
default:
sprintf(typestr, "%d", type);
return typestr;
}
}
/*******************************************************************************
*
* wrnad_rtnl_neigh_attrstr - find rtnetlink neighbour attribute in string format
*
* This routine returns the rtnetlink neighbour attribute as a string
*
* RETURNS: string representation of rtnetlink neighbour attribute.
*
* ERRNO: N/A
*/
static const char *
wrnad_rtnl_neigh_attrstr(int attr)
{
static char attrstr[32];
switch (attr)
{
case NDA_UNSPEC:
return "NDA_UNSPEC";
case NDA_DST:
return "NDA_DST";
case NDA_LLADDR:
return "NDA_LLADDR";
case NDA_CACHEINFO:
return "NDA_CACHEINFO";
#ifdef NDA_PROBES
case NDA_PROBES:
return "NDA_PROBES";
#endif
default:
sprintf(attrstr, "%d", attr);
return attrstr;
}
}
/*******************************************************************************
*
* wrnad_rtnl_neigh_scan - scan rtnetlink socket for neighbour messages
*
* This routine scans neighbour messages received on a rtnetlink socket
*
* RETURNS: 0 if exited normally, else -1.
*
* ERRNO: N/A
*/
static int
wrnad_rtnl_neigh_scan(struct ndmsg *nd, int len, uint32_t **ppipaddr)
{
struct rtattr *rta;
char buf[64];
rta = (struct rtattr *)((char *)nd + NLMSG_ALIGN(sizeof(struct ndmsg)));
while (RTA_OK(rta, len))
{
wrnad_log(LOG_DEBUG, "Rtnetlink NEIGH rtattr: rta_len=%d, rta_type=%s.",
rta->rta_len, wrnad_rtnl_neigh_attrstr(rta->rta_type));
if (rta->rta_type == NDA_DST)
{
*ppipaddr = RTA_DATA(rta);
if (inet_ntop(nd->ndm_family, RTA_DATA(rta), buf, sizeof(buf)) != NULL)
{
wrnad_log(LOG_DEBUG, "Rtnetlink NEIGH attribute NDA_DST: %s", buf);
}
else
{
wrnad_log(LOG_WARNING, "Rtnetlink NEIGH attribute NDA_DST: address conversion failed!");
}
}
else if (rta->rta_type == NDA_LLADDR)
{
wrnad_rtnl_eth_ntop(RTA_DATA(rta), buf);
wrnad_log(LOG_DEBUG, "Rtnetlink NEIGH attribute NDA_LLADDR: %s", buf);
}
rta = RTA_NEXT(rta, len);
}
if (len)
{
wrnad_log(LOG_WARNING, "Rtnetlink NEIGH attribute malformed! len=%d rta_len=%d", len, rta->rta_len);
return -1;
}
wrnad_log(LOG_DEBUG, "Rtnetlink NEIGH message scanned ok.");
return 0;
}
/*******************************************************************************
*
* wrnad_rtnl_neigh_handler - rtnetlink handler for neighbour messages
*
* This routine handles rtnetlink neighbour messages.
*
* RETURNS: -1 = failed
* 0 = skip
* 1 = action
*
* ERRNO: N/A
*/
int
wrnad_rtnl_neigh_handler(struct nlmsghdr *nlh)
{
struct ndmsg *nd;
int len, ret;
uint32_t *pipaddr = NULL;
Wrnad_proxy_interface *proxy;
nd = NLMSG_DATA(nlh);
len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg));
if (len < 0)
{
wrnad_log(LOG_WARNING, "Rtnetlink message type %s has wrong nlmsg length %d!",
wrnad_rtnl_typestr(nlh->nlmsg_type), nlh->nlmsg_len);
return -1;
}
wrnad_log(LOG_DEBUG, "Rtnetlink NEIGH operation header: family=%s, ifindex=%d, state=%s, flags=%s, type=%s.",
wrnad_rtnl_familystr(nd->ndm_family),
nd->ndm_ifindex,
wrnad_rtnl_neigh_statestr(nd->ndm_state),
wrnad_rtnl_neigh_flagsstr(nd->ndm_flags),
wrnad_rtnl_neigh_typestr(nd->ndm_type));
ret = wrnad_rtnl_neigh_scan(nd, len, &pipaddr);
if (ret != 0)
return ret;
proxy = wrnad_rtnl_check_proxy_interface(nd->ndm_ifindex);
if (proxy == NULL)
{
wrnad_log(LOG_DEBUG, "Rtnetlink message type %s skipped on interface %d",
wrnad_rtnl_typestr(nlh->nlmsg_type), nd->ndm_ifindex);
return 0;
}
if (pipaddr == NULL)
{
wrnad_log(LOG_WARNING, "No NDA_DST address in %s message on interface %d\n",
wrnad_rtnl_typestr(nlh->nlmsg_type), nd->ndm_ifindex);
return 0;
}
/* Accept message */
wrnad_log(LOG_INFO, "Rtnetlink message type %s accepted on interface %s",
wrnad_rtnl_typestr(nlh->nlmsg_type), proxy->ifname);
/* IPNET has no FAILED state so change the request into a DELNEIGH */
if (nd->ndm_state == NUD_FAILED)
nlh->nlmsg_type = RTM_DELNEIGH;
#ifdef WRNAD_IDENTIFY_ROUTERS
if ((nd->ndm_flags & NTF_ROUTER) ||
wrnad_neighbor_is_router(proxy, nd->ndm_family, pipaddr))
{
wrnad_log(LOG_INFO, "Marking as a router!\n");
nd->ndm_flags |= NTF_ROUTER;
}
#endif
/* AF_INET6 has a different value in VxWorks */
nd->ndm_family = wrnad_af_xlat(nd->ndm_family);
/* Change into request */
nlh->nlmsg_flags = NLM_F_REQUEST;
if (nlh->nlmsg_type == RTM_NEWNEIGH)
nlh->nlmsg_flags |= NLM_F_CREATE;
return 1;
}
#ifndef IPWRNAD
int wrnad_neigh_ndp_open(void)
{
int fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (fd != -1)
{
struct icmp6_filter filter;
int val;
/* Setup filtering and ancillary data */
/* Block everything */
ICMP6_FILTER_SETBLOCKALL(&filter);
/* Standard neighbor advertisements */
ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter);
/* Receive pktinfo */
val = 1;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val)) < 0)
{
wrnad_log(LOG_WARNING, "No IPv6 NDP unable to set RECVPKTINFO");
close(fd);
return -1;
}
if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, sizeof(val)) < 0)
{
wrnad_log(LOG_WARNING, "No IPv6 NDP unable to set RECVHOPLIMIT");
close(fd);
return -1;
}
/* Set the ICMP filter */
if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) < 0)
{
wrnad_log(LOG_WARNING, "No IPv6 NDP unable to set FILTER");
close(fd);
return -1;
}
return fd;
}
else
{
wrnad_log(LOG_WARNING, "No IPv6 NDP support possible");
return -1;
}
}
static int wrnad_neigh_ndp_validate_options_len(struct nd_opt_hdr *first_opt, int tot_opt_len)
{
int opt_len;
struct nd_opt_hdr *opt = first_opt;
while (tot_opt_len > 0)
{
opt_len = opt->nd_opt_len;
tot_opt_len -= opt_len << 3;
if (opt_len == 0 || tot_opt_len < 0)
return 0;
opt = (struct nd_opt_hdr *) ((uint16_t*) opt + (opt_len << 2));
}
return 1;
}
static struct nd_opt_hdr *wrnad_neigh_ndp_get_option(struct nd_opt_hdr *first_opt, int tot_opt_len, uint16_t type)
{
int opt_len;
struct nd_opt_hdr *opt = first_opt;
while (tot_opt_len > 0)
{
opt_len = opt->nd_opt_len;
if (opt->nd_opt_type == type)
return opt;
tot_opt_len -= opt_len << 3;
opt = (struct nd_opt_hdr *) ((uint16_t*) opt + (opt_len << 2));
}
return NULL;
}
static char recvaux[256];
void wrnad_neigh_ndp_read(int fd, int nlfd, char *recvbuf, int size)
{
struct cmsghdr *cmsghdr;
int len;
struct sockaddr_in6 src;
struct msghdr msg;
struct iovec vec;
struct nd_neighbor_advert *hdr = (struct nd_neighbor_advert *)recvbuf;
/* The read buffer */
vec.iov_base = recvbuf;
vec.iov_len = size;
/* Initialize name */
msg.msg_name = &src;
msg.msg_namelen = sizeof(src);
/* Initialize io */
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
/* Initialize control */
msg.msg_control = recvaux;
msg.msg_controllen = 256;
/* Clear flags */
msg.msg_flags = 0;
if ((len = recvmsg(fd, &msg, MSG_DONTWAIT)) < 0)
{
wrnad_log(LOG_WARNING, "Read error on ICMPv6 NDP socket");
return;
}
/* Check 5 */
if (len < sizeof(*hdr))
{
wrnad_log(LOG_WARNING, "Read on ICMPv6 NDP socket produced a too short frame");
return;
}
if ((msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) != 0)
{
wrnad_log(LOG_WARNING, "Read on ICMPv6 NDP socket was truncated");
return;
}
/* Received one solicitation message */
/* Did we get any ancillary data? */
if (msg.msg_controllen > 0)
{
struct in6_pktinfo *pktinfo = NULL;
int hoplimit = 0;
Wrnad_proxy_interface *ifp;
struct nd_opt_hdr *opt;
/* Loop the ancillary data, see what we find */
for (cmsghdr = CMSG_FIRSTHDR(&msg); cmsghdr != 0; cmsghdr = CMSG_NXTHDR(&msg, cmsghdr))
{
/* Correct level? */
if (cmsghdr->cmsg_level == IPPROTO_IPV6)
{
/* Type? */
switch (cmsghdr->cmsg_type)
{
case IPV6_PKTINFO:
pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsghdr);
break;
case IPV6_HOPLIMIT:
/* Got some destination options */
hoplimit = *((int *)CMSG_DATA(cmsghdr));
break;
default:
break;
}
}
}
if (NULL == pktinfo)
{
wrnad_log(LOG_WARNING, "Read on ICMPv6 NDP socket produced no packet information");
return;
}
/* OK, first sanities passed */
/* RFC 2461
7.1.2. Validation of Neighbor Advertisements
A node MUST silently discard any received Neighbor Advertisement
messages that do not satisfy all of the following validity checks:
1. The IP Hop Limit field has a value of 255, i.e., the packet
could not possibly have been forwarded by a router.
2. If the message includes an IP Authentication Header, the message
authenticates correctly.
3. ICMP Checksum is valid.
4. ICMP Code is 0.
5. ICMP length (derived from the IP length) is 24 or more octets.
6. Target Address is not a multicast address.
7. If the IP Destination Address is a multicast address the
Solicited flag is zero.
8. All included options have a length that is greater than zero.
*/
/* Check 1 */
if (hoplimit != 0xFF)
{
wrnad_log(LOG_WARNING, "Read on ICMPv6 NA had illegal hoplimit");
return;
}
/* Check 2 is done by Linux */
/* Check 3 is done by Linux */
/* Check 4 */
if (hdr->nd_na_hdr.icmp6_code != 0)
{
wrnad_log(LOG_WARNING, "Read on ICMPv6 NDP frame had bad icmp6 code");
return;
}
/* Check 5 is done immediately after recvmsg */
/* Check 6 */
if (IN6_IS_ADDR_MULTICAST(&hdr->nd_na_target))
{
wrnad_log(LOG_DEBUG, "Read on ICMPv6 NDP frame had multicast target");
return;
}
/* Check 7 */
if (IN6_IS_ADDR_MULTICAST(&pktinfo->ipi6_addr))
{
/* Dealt with by Linux - We do not do unsolicited advertisements here */
wrnad_log(LOG_DEBUG, "Read on ICMPv6 NDP frame had multicast destination");
return;
}
/* Check 8 */
if (0 == wrnad_neigh_ndp_validate_options_len((struct nd_opt_hdr *) (hdr + 1), len - sizeof(*hdr)))
{
wrnad_log(LOG_WARNING, "Read on ICMPv6 NDP frame had bad icmp6 options");
return;
}
/* Retrieve the link layer address */
opt = wrnad_neigh_ndp_get_option((struct nd_opt_hdr *) (hdr + 1), len - sizeof(*hdr), ND_OPT_TARGET_LINKADDR);
if (NULL == opt)
{
wrnad_log(LOG_DEBUG, "Read on ICMPv6 NDP frame had bad no target link address");
return;
}
ifp = wrnad_rtnl_check_proxy_interface(pktinfo->ipi6_ifindex);
if (NULL == ifp)
{
wrnad_log(LOG_DEBUG, "Read on ICMPv6 NDP frame correct, but received on Non-Proxy interface");
return;
}
else
{
struct nlmsghdr *nlh;
struct ndmsg *ndm;
struct rtattr *rta;
unsigned int ll_size;
struct sockaddr_nl nladdr;
static int seq = 0;
/* Create the NETLINK header */
nlh = (struct nlmsghdr *)recvaux;
memset(nlh, 0, sizeof(*nlh));
nlh->nlmsg_flags = NLM_F_EXCL|NLM_F_CREATE|NLM_F_REQUEST;
nlh->nlmsg_type = RTM_NEWNEIGH;
nlh->nlmsg_seq = ++seq;
nlh->nlmsg_len = NLMSG_LENGTH(0);
/* Create the NDM header */
ndm = (struct ndmsg *)((char *)nlh + nlh->nlmsg_len);
memset(ndm, 0, sizeof(*ndm));
ndm->ndm_family = AF_INET6;
ndm->ndm_flags = 0;
ndm->ndm_ifindex = ifp->ifindex;
ndm->ndm_state = NUD_STALE;
ndm->ndm_type = 0;
/* move past the NDM header */
nlh->nlmsg_len += sizeof(*ndm);
/* Add the DST address */
rta = (struct rtattr *)((char *)nlh + nlh->nlmsg_len);
rta->rta_len = RTA_LENGTH(sizeof(struct in6_addr));
rta->rta_type = NDA_DST;
memcpy(RTA_DATA(rta), &hdr->nd_na_target, sizeof(struct in6_addr));
/* Increase */
nlh->nlmsg_len += NLMSG_ALIGN(rta->rta_len);
/* Add the link layer address option */
ll_size = opt->nd_opt_len << 3;
ll_size -= sizeof(*opt);
rta = (struct rtattr *)((char *)nlh + nlh->nlmsg_len);
rta->rta_len = RTA_LENGTH(ll_size);
rta->rta_type = NDA_LLADDR;
memcpy(RTA_DATA(rta), opt + 1, ll_size);
/* Increase */
nlh->nlmsg_len += NLMSG_ALIGN(rta->rta_len);
memset(&msg, 0, sizeof(msg));
memset(&nladdr, 0, sizeof(nladdr));
/* Setup the sockaddress destination structure */
nladdr.nl_family = AF_NETLINK;
/* Setup the vector */
vec.iov_base = nlh;
vec.iov_len = nlh->nlmsg_len;
/* Initiate the msg structure */
msg.msg_name = &nladdr;
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
/* Talk with the kernel */
if (sendmsg(nlfd, &msg, 0) < 0)
{
/* Failed to communicate */
wrnad_log(LOG_WARNING, "Read on ICMPv6 NDP frame failed to send");
}
}
}
else
{
/**/
wrnad_log(LOG_WARNING, "Read on ICMPv6 NDP socket produced no ancillary data?");
}
}
#endif