/*
* libndp.c - Neighbour discovery library
* Copyright (C) 2013-2015 Jiri Pirko <jiri@resnulli.us>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <assert.h>
#include <ndp.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include "ndp_private.h"
#include "list.h"
#define pr_err(args...) fprintf(stderr, ##args)
/**
* SECTION: logging
* @short_description: libndp logging facility
*/
void ndp_log(struct ndp *ndp, int priority,
const char *file, int line, const char *fn,
const char *format, ...)
{
va_list args;
va_start(args, format);
ndp->log_fn(ndp, priority, file, line, fn, format, args);
va_end(args);
}
static void log_stderr(struct ndp *ndp, int priority,
const char *file, int line, const char *fn,
const char *format, va_list args)
{
fprintf(stderr, "libndp: %s: ", fn);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
}
static int log_priority(const char *priority)
{
char *endptr;
int prio;
prio = strtol(priority, &endptr, 10);
if (endptr[0] == '\0' || isspace(endptr[0]))
return prio;
if (strncmp(priority, "err", 3) == 0)
return LOG_ERR;
if (strncmp(priority, "info", 4) == 0)
return LOG_INFO;
if (strncmp(priority, "debug", 5) == 0)
return LOG_DEBUG;
return 0;
}
/**
* ndp_set_log_fn:
* @ndp: libndp library context
* @log_fn: function to be called for logging messages
*
* The built-in logging writes to stderr. It can be
* overridden by a custom function, to plug log messages
* into the user's logging functionality.
**/
NDP_EXPORT
void ndp_set_log_fn(struct ndp *ndp,
void (*log_fn)(struct ndp *ndp, int priority,
const char *file, int line, const char *fn,
const char *format, va_list args))
{
ndp->log_fn = log_fn;
dbg(ndp, "Custom logging function %p registered.", log_fn);
}
/**
* ndp_get_log_priority:
* @ndp: libndp library context
*
* Returns: the current logging priority.
**/
NDP_EXPORT
int ndp_get_log_priority(struct ndp *ndp)
{
return ndp->log_priority;
}
/**
* ndp_set_log_priority:
* @ndp: libndp library context
* @priority: the new logging priority
*
* Set the current logging priority. The value controls which messages
* are logged.
**/
NDP_EXPORT
void ndp_set_log_priority(struct ndp *ndp, int priority)
{
ndp->log_priority = priority;
}
/**
* SECTION: helpers
* @short_description: various internal helper functions
*/
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define BUG_ON(expr) { if (expr) assert(0); }
static void *myzalloc(size_t size)
{
return calloc(1, size);
}
static int myrecvfrom6(int sockfd, void *buf, size_t *buflen, int flags,
struct in6_addr *addr, uint32_t *ifindex, int *hoplimit)
{
struct sockaddr_in6 sin6;
unsigned char cbuf[2 * CMSG_SPACE(sizeof(struct in6_pktinfo))];
struct iovec iovec;
struct msghdr msghdr;
struct cmsghdr *cmsghdr;
ssize_t len;
iovec.iov_len = *buflen;
iovec.iov_base = buf;
memset(&msghdr, 0, sizeof(msghdr));
msghdr.msg_name = &sin6;
msghdr.msg_namelen = sizeof(sin6);
msghdr.msg_iov = &iovec;
msghdr.msg_iovlen = 1;
msghdr.msg_control = cbuf;
msghdr.msg_controllen = sizeof(cbuf);
len = recvmsg(sockfd, &msghdr, flags);
if (len == -1)
return -errno;
*buflen = len;
/* Set ifindex to scope_id now. But since scope_id gets not
* set by kernel for linklocal addresses, use pktinfo to obtain that
* value right after.
*/
*ifindex = sin6.sin6_scope_id;
for (cmsghdr = CMSG_FIRSTHDR(&msghdr); cmsghdr;
cmsghdr = CMSG_NXTHDR(&msghdr, cmsghdr)) {
if (cmsghdr->cmsg_level != IPPROTO_IPV6)
continue;
switch(cmsghdr->cmsg_type) {
case IPV6_PKTINFO:
if (cmsghdr->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
struct in6_pktinfo *pktinfo;
pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsghdr);
*ifindex = pktinfo->ipi6_ifindex;
}
break;
case IPV6_HOPLIMIT:
if (cmsghdr->cmsg_len == CMSG_LEN(sizeof(int))) {
int *val;
val = (int *) CMSG_DATA(cmsghdr);
*hoplimit = *val;
}
break;
}
}
*addr = sin6.sin6_addr;
return 0;
}
static int mysendto6(int sockfd, void *buf, size_t buflen, int flags,
struct in6_addr *addr, uint32_t ifindex)
{
struct sockaddr_in6 sin6;
ssize_t ret;
memset(&sin6, 0, sizeof(sin6));
memcpy(&sin6.sin6_addr, addr, sizeof(sin6.sin6_addr));
sin6.sin6_scope_id = ifindex;
resend:
ret = sendto(sockfd, buf, buflen, flags, &sin6, sizeof(sin6));
if (ret == -1) {
switch(errno) {
case EINTR:
goto resend;
default:
return -errno;
}
}
return 0;
}
static const char *str_in6_addr(struct in6_addr *addr)
{
static char buf[INET6_ADDRSTRLEN];
return inet_ntop(AF_INET6, addr, buf, sizeof(buf));
}
/**
* SECTION: NDP implementation
* @short_description: functions that actually implements NDP
*/
struct ndp_msggeneric {
void *dataptr; /* must be first */
};
struct ndp_msgrs {
struct nd_router_solicit *rs; /* must be first */
};
struct ndp_msgra {
struct nd_router_advert *ra; /* must be first */
};
struct ndp_msgns {
struct nd_neighbor_solicit *ns; /* must be first */
};
struct ndp_msgna {
struct nd_neighbor_advert *na; /* must be first */
};
struct ndp_msgr {
struct nd_redirect *r; /* must be first */
};
struct ndp_msg {
#define NDP_MSG_BUFLEN 1500
unsigned char buf[NDP_MSG_BUFLEN];
size_t len;
struct in6_addr addrto;
uint32_t ifindex;
int hoplimit;
struct icmp6_hdr * icmp6_hdr;
unsigned char * opts_start; /* pointer to buf at the
place where opts start */
union {
struct ndp_msggeneric generic;
struct ndp_msgrs rs;
struct ndp_msgra ra;
struct ndp_msgns ns;
struct ndp_msgna na;
struct ndp_msgr r;
} nd_msg;
};
struct ndp_msg_type_info {
#define NDP_STRABBR_SIZE 4
char strabbr[NDP_STRABBR_SIZE];
uint8_t raw_type;
size_t raw_struct_size;
void (*addrto_adjust)(struct in6_addr *addr);
bool (*addrto_validate)(struct in6_addr *addr);
};
static void ndp_msg_addrto_adjust_all_nodes(struct in6_addr *addr)
{
struct in6_addr any = IN6ADDR_ANY_INIT;
if (memcmp(addr, &any, sizeof(any)))
return;
addr->s6_addr32[0] = htonl(0xFF020000);
addr->s6_addr32[1] = 0;
addr->s6_addr32[2] = 0;
addr->s6_addr32[3] = htonl(0x1);
}
static void ndp_msg_addrto_adjust_all_routers(struct in6_addr *addr)
{
struct in6_addr any = IN6ADDR_ANY_INIT;
if (memcmp(addr, &any, sizeof(any)))
return;
addr->s6_addr32[0] = htonl(0xFF020000);
addr->s6_addr32[1] = 0;
addr->s6_addr32[2] = 0;
addr->s6_addr32[3] = htonl(0x2);
}
/*
* compute link-local solicited-node multicast address
*/
static void ndp_msg_addrto_adjust_solicit_multi(struct in6_addr *addr,
struct in6_addr *target)
{
addr->s6_addr32[0] = htonl(0xFF020000);
addr->s6_addr32[1] = 0;
addr->s6_addr32[2] = htonl(0x1);
addr->s6_addr32[3] = htonl(0xFF000000) | target->s6_addr32[3];
}
static bool ndp_msg_addrto_validate_link_local(struct in6_addr *addr)
{
return IN6_IS_ADDR_LINKLOCAL (addr);
}
static struct ndp_msg_type_info ndp_msg_type_info_list[] =
{
[NDP_MSG_RS] = {
.strabbr = "RS",
.raw_type = ND_ROUTER_SOLICIT,
.raw_struct_size = sizeof(struct nd_router_solicit),
.addrto_adjust = ndp_msg_addrto_adjust_all_routers,
},
[NDP_MSG_RA] = {
.strabbr = "RA",
.raw_type = ND_ROUTER_ADVERT,
.raw_struct_size = sizeof(struct nd_router_advert),
.addrto_validate = ndp_msg_addrto_validate_link_local,
},
[NDP_MSG_NS] = {
.strabbr = "NS",
.raw_type = ND_NEIGHBOR_SOLICIT,
.raw_struct_size = sizeof(struct nd_neighbor_solicit),
.addrto_adjust = ndp_msg_addrto_adjust_all_nodes,
},
[NDP_MSG_NA] = {
.strabbr = "NA",
.raw_type = ND_NEIGHBOR_ADVERT,
.raw_struct_size = sizeof(struct nd_neighbor_advert),
},
[NDP_MSG_R] = {
.strabbr = "R",
.raw_type = ND_REDIRECT,
.raw_struct_size = sizeof(struct nd_redirect),
.addrto_validate = ndp_msg_addrto_validate_link_local,
},
};
#define NDP_MSG_TYPE_LIST_SIZE ARRAY_SIZE(ndp_msg_type_info_list)
struct ndp_msg_type_info *ndp_msg_type_info(enum ndp_msg_type msg_type)
{
return &ndp_msg_type_info_list[msg_type];
}
static int ndp_msg_type_by_raw_type(enum ndp_msg_type *p_msg_type,
uint8_t raw_type)
{
int i;
for (i = 0; i < NDP_MSG_TYPE_LIST_SIZE; i++) {
if (ndp_msg_type_info(i)->raw_type == raw_type) {
*p_msg_type = i;
return 0;
}
}
return -ENOENT;
}
static bool ndp_msg_check_valid(struct ndp_msg *msg)
{
size_t len = ndp_msg_payload_len(msg);
enum ndp_msg_type msg_type = ndp_msg_type(msg);
if (len < ndp_msg_type_info(msg_type)->raw_struct_size)
return false;
if (ndp_msg_type_info(msg_type)->addrto_validate)
return ndp_msg_type_info(msg_type)->addrto_validate(&msg->addrto);
else
return true;
}
static struct ndp_msg *ndp_msg_alloc(void)
{
struct ndp_msg *msg;
msg = myzalloc(sizeof(*msg));
if (!msg)
return NULL;
msg->icmp6_hdr = (struct icmp6_hdr *) msg->buf;
return msg;
}
static void ndp_msg_type_set(struct ndp_msg *msg, enum ndp_msg_type msg_type);
static void ndp_msg_init(struct ndp_msg *msg, enum ndp_msg_type msg_type)
{
size_t raw_struct_size = ndp_msg_type_info(msg_type)->raw_struct_size;
ndp_msg_type_set(msg, msg_type);
msg->len = raw_struct_size;
msg->opts_start = msg->buf + raw_struct_size;
/* Set-up "first pointers" in all ndp_msgrs, ndp_msgra, ndp_msgns,
* ndp_msgna, ndp_msgr structures.
*/
msg->nd_msg.generic.dataptr = ndp_msg_payload(msg);
}
/**
* ndp_msg_new:
* @p_msg: pointer where new message structure address will be stored
* @msg_type: message type
*
* Allocate new message structure of a specified type and initialize it.
*
* Returns: zero on success or negative number in case of an error.
**/
NDP_EXPORT
int ndp_msg_new(struct ndp_msg **p_msg, enum ndp_msg_type msg_type)
{
struct ndp_msg *msg;
if (msg_type == NDP_MSG_ALL)
return -EINVAL;
msg = ndp_msg_alloc();
if (!msg)
return -ENOMEM;
ndp_msg_init(msg, msg_type);
*p_msg = msg;
return 0;
}
/**
* ndp_msg_destroy:
*
* Destroy message structure.
**/
NDP_EXPORT
void ndp_msg_destroy(struct ndp_msg *msg)
{
free(msg);
}
/**
* ndp_msg_payload:
* @msg: message structure
*
* Get raw Neighbour discovery packet data.
*
* Returns: pointer to raw data.
**/
NDP_EXPORT
void *ndp_msg_payload(struct ndp_msg *msg)
{
return msg->buf;
}
/**
* ndp_msg_payload_maxlen:
* @msg: message structure
*
* Get raw Neighbour discovery packet data maximum length.
*
* Returns: length in bytes.
**/
NDP_EXPORT
size_t ndp_msg_payload_maxlen(struct ndp_msg *msg)
{
return sizeof(msg->buf);
}
/**
* ndp_msg_payload_len:
* @msg: message structure
*
* Get raw Neighbour discovery packet data length.
*
* Returns: length in bytes.
**/
NDP_EXPORT
size_t ndp_msg_payload_len(struct ndp_msg *msg)
{
return msg->len;
}
/**
* ndp_msg_payload_len_set:
* @msg: message structure
*
* Set raw Neighbour discovery packet data length.
**/
NDP_EXPORT
void ndp_msg_payload_len_set(struct ndp_msg *msg, size_t len)
{
if (len > sizeof(msg->buf))
len = sizeof(msg->buf);
msg->len = len;
}
/**
* ndp_msg_payload_opts:
* @msg: message structure
*
* Get raw Neighbour discovery packet options part data.
*
* Returns: pointer to raw data.
**/
NDP_EXPORT
void *ndp_msg_payload_opts(struct ndp_msg *msg)
{
return msg->opts_start;
}
static void *ndp_msg_payload_opts_offset(struct ndp_msg *msg, int offset)
{
unsigned char *ptr = ndp_msg_payload_opts(msg);
return ptr + offset;
}
/**
* ndp_msg_payload_opts_len:
* @msg: message structure
*
* Get raw Neighbour discovery packet options part data length.
*
* Returns: length in bytes.
**/
NDP_EXPORT
size_t ndp_msg_payload_opts_len(struct ndp_msg *msg)
{
return msg->len - (msg->opts_start - msg->buf);
}
/**
* ndp_msgrs:
* @msg: message structure
*
* Get RS message structure by passed @msg.
*
* Returns: RS message structure or NULL in case the message is not of type RS.
**/
NDP_EXPORT
struct ndp_msgrs *ndp_msgrs(struct ndp_msg *msg)
{
if (ndp_msg_type(msg) != NDP_MSG_RS)
return NULL;
return &msg->nd_msg.rs;
}
/**
* ndp_msgra:
* @msg: message structure
*
* Get RA message structure by passed @msg.
*
* Returns: RA message structure or NULL in case the message is not of type RA.
**/
NDP_EXPORT
struct ndp_msgra *ndp_msgra(struct ndp_msg *msg)
{
if (ndp_msg_type(msg) != NDP_MSG_RA)
return NULL;
return &msg->nd_msg.ra;
}
/**
* ndp_msgns:
* @msg: message structure
*
* Get NS message structure by passed @msg.
*
* Returns: NS message structure or NULL in case the message is not of type NS.
**/
NDP_EXPORT
struct ndp_msgns *ndp_msgns(struct ndp_msg *msg)
{
if (ndp_msg_type(msg) != NDP_MSG_NS)
return NULL;
return &msg->nd_msg.ns;
}
/**
* ndp_msgna:
* @msg: message structure
*
* Get NA message structure by passed @msg.
*
* Returns: NA message structure or NULL in case the message is not of type NA.
**/
NDP_EXPORT
struct ndp_msgna *ndp_msgna(struct ndp_msg *msg)
{
if (ndp_msg_type(msg) != NDP_MSG_NA)
return NULL;
return &msg->nd_msg.na;
}
/**
* ndp_msgr:
* @msg: message structure
*
* Get R message structure by passed @msg.
*
* Returns: R message structure or NULL in case the message is not of type R.
**/
NDP_EXPORT
struct ndp_msgr *ndp_msgr(struct ndp_msg *msg)
{
if (ndp_msg_type(msg) != NDP_MSG_R)
return NULL;
return &msg->nd_msg.r;
}
/**
* ndp_msg_type:
* @msg: message structure
*
* Get type of message.
*
* Returns: Message type
**/
NDP_EXPORT
enum ndp_msg_type ndp_msg_type(struct ndp_msg *msg)
{
enum ndp_msg_type msg_type;
int err;
err = ndp_msg_type_by_raw_type(&msg_type, msg->icmp6_hdr->icmp6_type);
/* Type should be always set correctly (ensured by ndp_msg_init) */
BUG_ON(err);
return msg_type;
}
static void ndp_msg_type_set(struct ndp_msg *msg, enum ndp_msg_type msg_type)
{
msg->icmp6_hdr->icmp6_type = ndp_msg_type_info(msg_type)->raw_type;
}
/**
* ndp_msg_addrto:
* @msg: message structure
*
* Get "to address" of message.
*
* Returns: pointer to address.
**/
NDP_EXPORT
struct in6_addr *ndp_msg_addrto(struct ndp_msg *msg)
{
return &msg->addrto;
}
/**
* ndp_msg_ifindex:
* @msg: message structure
*
* Get interface index of message.
*
* Returns: Interface index
**/
NDP_EXPORT
uint32_t ndp_msg_ifindex(struct ndp_msg *msg)
{
return msg->ifindex;
}
/**
* ndp_msg_ifindex_set:
* @msg: message structure
*
* Set raw interface index of message.
**/
NDP_EXPORT
void ndp_msg_ifindex_set(struct ndp_msg *msg, uint32_t ifindex)
{
msg->ifindex = ifindex;
}
/**
* ndp_msg_dest_set:
* @msg: message structure
* @dest: ns,na dest
*
* Set dest address in IPv6 header for NS and NA.
**/
NDP_EXPORT
void ndp_msg_dest_set(struct ndp_msg *msg, struct in6_addr *dest)
{
enum ndp_msg_type msg_type = ndp_msg_type(msg);
switch (msg_type) {
case NDP_MSG_NS:
/* fall through */
case NDP_MSG_NA:
msg->addrto = *dest;
/* fall through */
default:
break;
}
}
/**
* ndp_msg_target_set:
* @msg: message structure
* @target: ns,na target
*
* Set target address in ICMPv6 header for NS and NA.
**/
NDP_EXPORT
void ndp_msg_target_set(struct ndp_msg *msg, struct in6_addr *target)
{
struct in6_addr any = IN6ADDR_ANY_INIT;
enum ndp_msg_type msg_type = ndp_msg_type(msg);
switch (msg_type) {
case NDP_MSG_NS:
((struct ndp_msgns*)&msg->nd_msg)->ns->nd_ns_target = *target;
/*
* Neighbor Solicitations are multicast when the node
* needs to resolve an address and unicast when the
* node seeks to verify the reachability of a
* neighbor.
*
* In this case we need to update the dest address in
* IPv6 header when
* a) IPv6 dest address is not set
* b) ICMPv6 target address is supplied
* */
if (!memcmp(&msg->addrto, &any, sizeof(any)) &&
memcmp(target, &any, sizeof(any)))
ndp_msg_addrto_adjust_solicit_multi(&msg->addrto, target);
break;
case NDP_MSG_NA:
((struct ndp_msgna*)&msg->nd_msg)->na->nd_na_target = *target;
break;
default:
break;
}
}
static int ndp_get_iface_mac(int ifindex, char *ptr)
{
int sockfd, err = 0;
struct ifreq ifr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
pr_err("%s: Failed to create socket", __func__);
return -errno;
}
if (if_indextoname(ifindex, (char *)&ifr.ifr_name) == NULL) {
pr_err("%s: Failed to get iface name with index %d", __func__, ifindex);
err = -errno;
goto close_sock;
}
if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) < 0) {
pr_err("%s: Failed to get iface mac with index %d\n", __func__, ifindex);
err = -errno;
goto close_sock;
}
memcpy(ptr, &ifr.ifr_hwaddr.sa_data, sizeof(ifr.ifr_hwaddr.sa_data));
close_sock:
close(sockfd);
return err;
}
static void ndp_msg_opt_set_linkaddr(struct ndp_msg *msg, int ndp_opt)
{
char *opts_start = ndp_msg_payload_opts(msg);
struct nd_opt_hdr *s_laddr_opt = (struct nd_opt_hdr *) opts_start;
char *opt_data = (char *) s_laddr_opt + sizeof(struct nd_opt_hdr);
int err;
err = ndp_get_iface_mac(ndp_msg_ifindex(msg), opt_data);
if (err)
return;
opt_data += 6;
s_laddr_opt->nd_opt_type = ndp_opt;
s_laddr_opt->nd_opt_len = (opt_data - opts_start) >> 3;
msg->len += opt_data - opts_start;
}
/**
* ndp_msg_opt_set:
* @msg: message structure
*
* Set neighbor discovery option info.
**/
NDP_EXPORT
void ndp_msg_opt_set(struct ndp_msg *msg)
{
enum ndp_msg_type msg_type = ndp_msg_type(msg);
switch (msg_type) {
case NDP_MSG_NS:
ndp_msg_opt_set_linkaddr(msg, ND_OPT_SOURCE_LINKADDR);
break;
case NDP_MSG_NA:
ndp_msg_opt_set_linkaddr(msg, ND_OPT_TARGET_LINKADDR);
break;
default:
break;
}
}
/**
* ndp_msg_send:
* @ndp: libndp library context
* @msg: message structure
*
* Send message.
*
* Returns: zero on success or negative number in case of an error.
**/
NDP_EXPORT
int ndp_msg_send(struct ndp *ndp, struct ndp_msg *msg)
{
return ndp_msg_send_with_flags(ndp, msg, ND_OPT_NORMAL);
}
/**
* ndp_msg_send_with_flags:
* @ndp: libndp library context
* @msg: message structure
* @flags: option flags within message type
*
* Send message.
*
* Returns: zero on success or negative number in case of an error.
**/
NDP_EXPORT
int ndp_msg_send_with_flags(struct ndp *ndp, struct ndp_msg *msg, uint8_t flags)
{
enum ndp_msg_type msg_type = ndp_msg_type(msg);
if (ndp_msg_type_info(msg_type)->addrto_adjust)
ndp_msg_type_info(msg_type)->addrto_adjust(&msg->addrto);
switch (msg_type) {
case NDP_MSG_NA:
if (flags & ND_OPT_NA_UNSOL) {
ndp_msgna_flag_override_set((struct ndp_msgna*)&msg->nd_msg, true);
ndp_msgna_flag_solicited_set((struct ndp_msgna*)&msg->nd_msg, false);
ndp_msg_addrto_adjust_all_nodes(&msg->addrto);
} else {
ndp_msgna_flag_solicited_set((struct ndp_msgna*)&msg->nd_msg, true);
}
break;
default:
break;
}
return mysendto6(ndp->sock, msg->buf, msg->len, 0,
&msg->addrto, msg->ifindex);
}
/**
* SECTION: msgra getters/setters
* @short_description: Getters and setters for RA message
*/
/**
* ndp_msgra_curhoplimit:
* @msgra: RA message structure
*
* Get RA curhoplimit.
*
* Returns: curhoplimit.
**/
NDP_EXPORT
uint8_t ndp_msgra_curhoplimit(struct ndp_msgra *msgra)
{
return msgra->ra->nd_ra_curhoplimit;
}
/**
* ndp_msgra_curhoplimit_set:
* @msgra: RA message structure
*
* Set RA curhoplimit.
**/
NDP_EXPORT
void ndp_msgra_curhoplimit_set(struct ndp_msgra *msgra, uint8_t curhoplimit)
{
msgra->ra->nd_ra_curhoplimit = curhoplimit;
}
/**
* ndp_msgra_flag_managed:
* @msgra: RA message structure
*
* Get RA managed flag.
*
* Returns: managed flag.
**/
NDP_EXPORT
bool ndp_msgra_flag_managed(struct ndp_msgra *msgra)
{
return msgra->ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED;
}
/**
* ndp_msgra_flag_managed_set:
* @msgra: RA message structure
*
* Set RA managed flag.
**/
NDP_EXPORT
void ndp_msgra_flag_managed_set(struct ndp_msgra *msgra, bool flag_managed)
{
if (flag_managed)
msgra->ra->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED;
else
msgra->ra->nd_ra_flags_reserved &= ~ND_RA_FLAG_MANAGED;
}
/**
* ndp_msgra_flag_other:
* @msgra: RA message structure
*
* Get RA other flag.
*
* Returns: other flag.
**/
NDP_EXPORT
bool ndp_msgra_flag_other(struct ndp_msgra *msgra)
{
return msgra->ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER;
}
/**
* ndp_msgra_flag_other_set:
* @msgra: RA message structure
*
* Set RA other flag.
**/
NDP_EXPORT
void ndp_msgra_flag_other_set(struct ndp_msgra *msgra, bool flag_other)
{
if (flag_other)
msgra->ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER;
else
msgra->ra->nd_ra_flags_reserved &= ~ND_RA_FLAG_OTHER;
}
/**
* ndp_msgra_flag_home_agent:
* @msgra: RA message structure
*
* Get RA home_agent flag.
*
* Returns: home_agent flag.
**/
NDP_EXPORT
bool ndp_msgra_flag_home_agent(struct ndp_msgra *msgra)
{
return msgra->ra->nd_ra_flags_reserved & ND_RA_FLAG_HOME_AGENT;
}
/**
* ndp_msgra_flag_home_agent_set:
* @msgra: RA message structure
*
* Set RA home_agent flag.
**/
NDP_EXPORT
void ndp_msgra_flag_home_agent_set(struct ndp_msgra *msgra,
bool flag_home_agent)
{
if (flag_home_agent)
msgra->ra->nd_ra_flags_reserved |= ND_RA_FLAG_HOME_AGENT;
else
msgra->ra->nd_ra_flags_reserved &= ~ND_RA_FLAG_HOME_AGENT;
}
/**
* ndp_msgra_route_preference:
* @msgra: RA message structure
*
* Get route preference.
*
* Returns: route preference.
**/
NDP_EXPORT
enum ndp_route_preference ndp_msgra_route_preference(struct ndp_msgra *msgra)
{
uint8_t prf = (msgra->ra->nd_ra_flags_reserved >> 3) & 3;
/* rfc4191 says:
* If the Router Lifetime is zero, the preference value MUST be set to
* (00) by the sender and MUST be ignored by the receiver.
* If the Reserved (10) value is received, the receiver MUST treat the
* value as if it were (00).
*/
if (prf == 2 || !ndp_msgra_router_lifetime(msgra))
prf = 0;
return prf;
}
/**
* ndp_msgra_route_preference_set:
* @msgra: RA message structure
* @pref: preference
*
* Set route preference.
**/
NDP_EXPORT
void ndp_msgra_route_preference_set(struct ndp_msgra *msgra,
enum ndp_route_preference pref)
{
msgra->ra->nd_ra_flags_reserved &= ~(3 << 3);
msgra->ra->nd_ra_flags_reserved |= (pref << 3);
}
/**
* ndp_msgra_router_lifetime:
* @msgra: RA message structure
*
* Get RA router lifetime.
*
* Returns: router lifetime in seconds.
**/
NDP_EXPORT
uint16_t ndp_msgra_router_lifetime(struct ndp_msgra *msgra)
{
return ntohs(msgra->ra->nd_ra_router_lifetime);
}
/**
* ndp_msgra_router_lifetime_set:
* @msgra: RA message structure
*
* Set RA router lifetime.
**/
NDP_EXPORT
void ndp_msgra_router_lifetime_set(struct ndp_msgra *msgra,
uint16_t router_lifetime)
{
msgra->ra->nd_ra_router_lifetime = htons(router_lifetime);
}
/**
* ndp_msgra_reachable_time:
* @msgra: RA message structure
*
* Get RA reachable time.
*
* Returns: reachable time in milliseconds.
**/
NDP_EXPORT
uint32_t ndp_msgra_reachable_time(struct ndp_msgra *msgra)
{
return ntohl(msgra->ra->nd_ra_reachable);
}
/**
* ndp_msgra_reachable_time_set:
* @msgra: RA message structure
*
* Set RA reachable time.
**/
NDP_EXPORT
void ndp_msgra_reachable_time_set(struct ndp_msgra *msgra,
uint32_t reachable_time)
{
msgra->ra->nd_ra_reachable = htonl(reachable_time);
}
/**
* ndp_msgra_retransmit_time:
* @msgra: RA message structure
*
* Get RA retransmit time.
*
* Returns: retransmit time in milliseconds.
**/
NDP_EXPORT
uint32_t ndp_msgra_retransmit_time(struct ndp_msgra *msgra)
{
return ntohl(msgra->ra->nd_ra_retransmit);
}
/**
* ndp_msgra_retransmit_time_set:
* @msgra: RA message structure
*
* Set RA retransmit time.
**/
NDP_EXPORT
void ndp_msgra_retransmit_time_set(struct ndp_msgra *msgra,
uint32_t retransmit_time)
{
msgra->ra->nd_ra_retransmit = htonl(retransmit_time);
}
/**
* SECTION: msgna getters/setters
* @short_description: Getters and setters for NA message
*/
/**
* ndp_msgna_flag_router:
* @msgna: NA message structure
*
* Get NA router flag.
*
* Returns: router flag.
**/
NDP_EXPORT
bool ndp_msgna_flag_router(struct ndp_msgna *msgna)
{
return msgna->na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER;
}
/**
* ndp_msgna_flag_router_set:
* @msgna: NA message structure
*
* Set NA router flag.
**/
NDP_EXPORT
void ndp_msgna_flag_router_set(struct ndp_msgna *msgna, bool flag_router)
{
if (flag_router)
msgna->na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
else
msgna->na->nd_na_flags_reserved &= ~ND_NA_FLAG_ROUTER;
}
/**
* ndp_msgna_flag_solicited:
* @msgna: NA message structure
*
* Get NA solicited flag.
*
* Returns: solicited flag.
**/
NDP_EXPORT
bool ndp_msgna_flag_solicited(struct ndp_msgna *msgna)
{
return msgna->na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED;
}
/**
* ndp_msgna_flag_solicited_set:
* @msgna: NA message structure
*
* Set NA managed flag.
**/
NDP_EXPORT
void ndp_msgna_flag_solicited_set(struct ndp_msgna *msgna, bool flag_solicited)
{
if (flag_solicited)
msgna->na->nd_na_flags_reserved |= ND_NA_FLAG_SOLICITED;
else
msgna->na->nd_na_flags_reserved &= ~ND_NA_FLAG_SOLICITED;
}
/**
* ndp_msgna_flag_override:
* @msgna: NA message structure
*
* Get NA override flag.
*
* Returns: override flag.
**/
NDP_EXPORT
bool ndp_msgna_flag_override(struct ndp_msgna *msgna)
{
return msgna->na->nd_na_flags_reserved & ND_NA_FLAG_OVERRIDE;
}
/**
* ndp_msgna_flag_override_set:
* @msgra: NA message structure
*
* Set NA override flag.
*/
NDP_EXPORT
void ndp_msgna_flag_override_set(struct ndp_msgna *msgna, bool flag_override)
{
if (flag_override)
msgna->na->nd_na_flags_reserved |= ND_NA_FLAG_OVERRIDE;
else
msgna->na->nd_na_flags_reserved &= ~ND_NA_FLAG_OVERRIDE;
}
/**
* SECTION: msg_opt infrastructure
* @short_description: Infrastructure for options
*/
struct ndp_msg_opt_type_info {
uint8_t raw_type;
size_t raw_struct_size;
bool (*check_valid)(void *opt_data);
};
static bool ndp_msg_opt_route_check_valid(void *opt_data)
{
struct __nd_opt_route_info *ri = opt_data;
/* rfc4191 says:
* If the Reserved (10) value is received, the Route Information Option
* MUST be ignored.
*/
if (((ri->nd_opt_ri_prf_reserved >> 3) & 3) == 2)
return false;
return true;
}
static struct ndp_msg_opt_type_info ndp_msg_opt_type_info_list[] =
{
[NDP_MSG_OPT_SLLADDR] = {
.raw_type = ND_OPT_SOURCE_LINKADDR,
},
[NDP_MSG_OPT_TLLADDR] = {
.raw_type = ND_OPT_TARGET_LINKADDR,
},
[NDP_MSG_OPT_PREFIX] = {
.raw_type = ND_OPT_PREFIX_INFORMATION,
.raw_struct_size = sizeof(struct nd_opt_prefix_info),
},
[NDP_MSG_OPT_REDIR] = {
.raw_type = ND_OPT_REDIRECTED_HEADER,
},
[NDP_MSG_OPT_MTU] = {
.raw_type = ND_OPT_MTU,
.raw_struct_size = sizeof(struct nd_opt_mtu),
},
[NDP_MSG_OPT_ROUTE] = {
.raw_type = __ND_OPT_ROUTE_INFO,
.raw_struct_size = sizeof(struct __nd_opt_route_info),
.check_valid = ndp_msg_opt_route_check_valid,
},
[NDP_MSG_OPT_RDNSS] = {
.raw_type = __ND_OPT_RDNSS,
.raw_struct_size = sizeof(struct __nd_opt_rdnss),
},
[NDP_MSG_OPT_DNSSL] = {
.raw_type = __ND_OPT_DNSSL,
.raw_struct_size = sizeof(struct __nd_opt_dnssl),
},
};
#define NDP_MSG_OPT_TYPE_LIST_SIZE ARRAY_SIZE(ndp_msg_opt_type_info_list)
struct ndp_msg_opt_type_info *ndp_msg_opt_type_info(enum ndp_msg_opt_type msg_opt_type)
{
return &ndp_msg_opt_type_info_list[msg_opt_type];
}
struct ndp_msg_opt_type_info *ndp_msg_opt_type_info_by_raw_type(uint8_t raw_type)
{
struct ndp_msg_opt_type_info *info;
int i;
for (i = 0; i < NDP_MSG_OPT_TYPE_LIST_SIZE; i++) {
info = &ndp_msg_opt_type_info_list[i];
if (info->raw_type == raw_type)
return info;
}
return NULL;
}
/**
* ndp_msg_next_opt_offset:
* @msg: message structure
* @offset: option payload offset
* @opt_type: option type
*
* Find next offset of option of given type. If offset is -1, start from
* beginning, otherwise start from the given offset.
* This funstion is internally used by ndp_msg_opt_for_each_offset() macro.
*
* Returns: offset in opt payload of found opt of -1 in case it was not found.
**/
NDP_EXPORT
int ndp_msg_next_opt_offset(struct ndp_msg *msg, int offset,
enum ndp_msg_opt_type opt_type)
{
unsigned char *opts_start = ndp_msg_payload_opts(msg);
unsigned char *ptr = opts_start;
size_t len = ndp_msg_payload_opts_len(msg);
uint8_t opt_raw_type = ndp_msg_opt_type_info(opt_type)->raw_type;
bool ignore = true;
if (offset == -1) {
offset = 0;
ignore = false;
}
ptr += offset;
len -= offset;
while (len > 0) {
uint8_t cur_opt_raw_type = ptr[0];
unsigned int cur_opt_len = ptr[1] << 3; /* convert to bytes */
if (!cur_opt_len || len < cur_opt_len)
break;
if (cur_opt_raw_type == opt_raw_type && !ignore)
return ptr - opts_start;
ptr += cur_opt_len;
len -= cur_opt_len;
ignore = false;
}
return -1;
}
#define __INVALID_OPT_TYPE_MAGIC 0xff
/*
* Check for validity of options and mark by magic opt type in case it is not
* so ndp_msg_next_opt_offset() will ignore it.
*/
static bool ndp_msg_check_opts(struct ndp_msg *msg)
{
unsigned char *ptr = ndp_msg_payload_opts(msg);
size_t len = ndp_msg_payload_opts_len(msg);
struct ndp_msg_opt_type_info *info;
while (len > 0) {
uint8_t cur_opt_raw_type = ptr[0];
unsigned int cur_opt_len = ptr[1] << 3; /* convert to bytes */
if (!cur_opt_len)
return false;
if (len < cur_opt_len)
break;
info = ndp_msg_opt_type_info_by_raw_type(cur_opt_raw_type);
if (info) {
if (cur_opt_len < info->raw_struct_size ||
(info->check_valid && !info->check_valid(ptr)))
ptr[0] = __INVALID_OPT_TYPE_MAGIC;
}
ptr += cur_opt_len;
len -= cur_opt_len;
}
return true;
}
/**
* SECTION: msg_opt getters/setters
* @short_description: Getters and setters for options
*/
/**
* ndp_msg_opt_slladdr:
* @msg: message structure
* @offset: in-message offset
*
* Get source linkaddr.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: pointer to source linkaddr.
**/
NDP_EXPORT
unsigned char *ndp_msg_opt_slladdr(struct ndp_msg *msg, int offset)
{
unsigned char *opt_data = ndp_msg_payload_opts_offset(msg, offset);
return &opt_data[2];
}
/**
* ndp_msg_opt_slladdr_len:
* @msg: message structure
* @offset: in-message offset
*
* Get source linkaddr length.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: source linkaddr length.
**/
NDP_EXPORT
size_t ndp_msg_opt_slladdr_len(struct ndp_msg *msg, int offset)
{
return ETH_ALEN;
}
/**
* ndp_msg_opt_tlladdr:
* @msg: message structure
* @offset: in-message offset
*
* Get target linkaddr.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: pointer to target linkaddr.
**/
NDP_EXPORT
unsigned char *ndp_msg_opt_tlladdr(struct ndp_msg *msg, int offset)
{
unsigned char *opt_data = ndp_msg_payload_opts_offset(msg, offset);
return &opt_data[2];
}
/**
* ndp_msg_opt_tlladdr_len:
* @msg: message structure
* @offset: in-message offset
*
* Get target linkaddr length.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: target linkaddr length.
**/
NDP_EXPORT
size_t ndp_msg_opt_tlladdr_len(struct ndp_msg *msg, int offset)
{
return ETH_ALEN;
}
/**
* ndp_msg_opt_prefix:
* @msg: message structure
* @offset: in-message offset
*
* Get prefix addr.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: pointer to address.
**/
NDP_EXPORT
struct in6_addr *ndp_msg_opt_prefix(struct ndp_msg *msg, int offset)
{
struct nd_opt_prefix_info *pi =
ndp_msg_payload_opts_offset(msg, offset);
return &pi->nd_opt_pi_prefix;
}
/**
* ndp_msg_opt_prefix_len:
* @msg: message structure
* @offset: in-message offset
*
* Get prefix length.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: length of prefix.
**/
NDP_EXPORT
uint8_t ndp_msg_opt_prefix_len(struct ndp_msg *msg, int offset)
{
struct nd_opt_prefix_info *pi =
ndp_msg_payload_opts_offset(msg, offset);
return pi->nd_opt_pi_prefix_len;
}
/**
* ndp_msg_opt_prefix_valid_time:
* @msg: message structure
* @offset: in-message offset
*
* Get prefix valid time.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: valid time in seconds, (uint32_t) -1 means infinity.
**/
NDP_EXPORT
uint32_t ndp_msg_opt_prefix_valid_time(struct ndp_msg *msg, int offset)
{
struct nd_opt_prefix_info *pi =
ndp_msg_payload_opts_offset(msg, offset);
return ntohl(pi->nd_opt_pi_valid_time);
}
/**
* ndp_msg_opt_prefix_preferred_time:
* @msg: message structure
* @offset: in-message offset
*
* Get prefix preferred time.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: preferred time in seconds, (uint32_t) -1 means infinity.
**/
NDP_EXPORT
uint32_t ndp_msg_opt_prefix_preferred_time(struct ndp_msg *msg, int offset)
{
struct nd_opt_prefix_info *pi =
ndp_msg_payload_opts_offset(msg, offset);
return ntohl(pi->nd_opt_pi_preferred_time);
}
/**
* ndp_msg_opt_prefix_flag_on_link:
* @msg: message structure
* @offset: in-message offset
*
* Get on-link flag.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: on-link flag.
**/
NDP_EXPORT
bool ndp_msg_opt_prefix_flag_on_link(struct ndp_msg *msg, int offset)
{
struct nd_opt_prefix_info *pi =
ndp_msg_payload_opts_offset(msg, offset);
return pi->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK;
}
/**
* ndp_msg_opt_prefix_flag_auto_addr_conf:
* @msg: message structure
* @offset: in-message offset
*
* Get autonomous address-configuration flag.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: autonomous address-configuration flag.
**/
NDP_EXPORT
bool ndp_msg_opt_prefix_flag_auto_addr_conf(struct ndp_msg *msg, int offset)
{
struct nd_opt_prefix_info *pi =
ndp_msg_payload_opts_offset(msg, offset);
return pi->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO;
}
/**
* ndp_msg_opt_prefix_flag_router_addr:
* @msg: message structure
* @offset: in-message offset
*
* Get router address flag.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: router address flag.
**/
NDP_EXPORT
bool ndp_msg_opt_prefix_flag_router_addr(struct ndp_msg *msg, int offset)
{
struct nd_opt_prefix_info *pi =
ndp_msg_payload_opts_offset(msg, offset);
return pi->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_RADDR;
}
/**
* ndp_msg_opt_mtu:
* @msg: message structure
* @offset: in-message offset
*
* Get MTU. User should check if mtu option is present before calling this.
*
* Returns: MTU.
**/
NDP_EXPORT
uint32_t ndp_msg_opt_mtu(struct ndp_msg *msg, int offset)
{
struct nd_opt_mtu *mtu = ndp_msg_payload_opts_offset(msg, offset);
return ntohl(mtu->nd_opt_mtu_mtu);
}
/**
* ndp_msg_opt_route_prefix:
* @msg: message structure
* @offset: in-message offset
*
* Get route prefix addr.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: address.
**/
NDP_EXPORT
struct in6_addr *ndp_msg_opt_route_prefix(struct ndp_msg *msg, int offset)
{
static struct in6_addr prefix;
struct __nd_opt_route_info *ri =
ndp_msg_payload_opts_offset(msg, offset);
memset(&prefix, 0, sizeof(prefix));
memcpy(&prefix, &ri->nd_opt_ri_prefix, (ri->nd_opt_ri_len - 1) << 3);
return &prefix;
}
/**
* ndp_msg_opt_route_prefix_len:
* @msg: message structure
* @offset: in-message offset
*
* Get route prefix length.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: length of route prefix.
**/
NDP_EXPORT
uint8_t ndp_msg_opt_route_prefix_len(struct ndp_msg *msg, int offset)
{
struct __nd_opt_route_info *ri =
ndp_msg_payload_opts_offset(msg, offset);
return ri->nd_opt_ri_prefix_len;
}
/**
* ndp_msg_opt_route_lifetime:
* @msg: message structure
* @offset: in-message offset
*
* Get route lifetime.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: route lifetime in seconds, (uint32_t) -1 means infinity.
**/
NDP_EXPORT
uint32_t ndp_msg_opt_route_lifetime(struct ndp_msg *msg, int offset)
{
struct __nd_opt_route_info *ri =
ndp_msg_payload_opts_offset(msg, offset);
return ntohl(ri->nd_opt_ri_lifetime);
}
/**
* ndp_msg_opt_route_preference:
* @msg: message structure
* @offset: in-message offset
*
* Get route preference.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: route preference.
**/
NDP_EXPORT
enum ndp_route_preference
ndp_msg_opt_route_preference(struct ndp_msg *msg, int offset)
{
struct __nd_opt_route_info *ri =
ndp_msg_payload_opts_offset(msg, offset);
return (ri->nd_opt_ri_prf_reserved >> 3) & 3;
}
/**
* ndp_msg_opt_rdnss_lifetime:
* @msg: message structure
* @offset: in-message offset
*
* Get Recursive DNS Server lifetime.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: route lifetime in seconds, (uint32_t) -1 means infinity.
**/
NDP_EXPORT
uint32_t ndp_msg_opt_rdnss_lifetime(struct ndp_msg *msg, int offset)
{
struct __nd_opt_rdnss *rdnss =
ndp_msg_payload_opts_offset(msg, offset);
return ntohl(rdnss->nd_opt_rdnss_lifetime);
}
/**
* ndp_msg_opt_rdnss_addr:
* @msg: message structure
* @offset: in-message offset
* @addr_index: address index
*
* Get Recursive DNS Server address.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: address.
**/
NDP_EXPORT
struct in6_addr *ndp_msg_opt_rdnss_addr(struct ndp_msg *msg, int offset,
int addr_index)
{
static struct in6_addr addr;
struct __nd_opt_rdnss *rdnss =
ndp_msg_payload_opts_offset(msg, offset);
size_t len = rdnss->nd_opt_rdnss_len << 3; /* convert to bytes */
len -= in_struct_offset(struct __nd_opt_rdnss, nd_opt_rdnss_addresses);
if ((addr_index + 1) * sizeof(addr) > len)
return NULL;
memcpy(&addr, &rdnss->nd_opt_rdnss_addresses[addr_index * sizeof(addr)],
sizeof(addr));
return &addr;
}
/**
* ndp_msg_opt_dnssl_lifetime:
* @msg: message structure
* @offset: in-message offset
*
* Get DNS Search List lifetime.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: route lifetime in seconds, (uint32_t) -1 means infinity.
**/
NDP_EXPORT
uint32_t ndp_msg_opt_dnssl_lifetime(struct ndp_msg *msg, int offset)
{
struct __nd_opt_dnssl *dnssl =
ndp_msg_payload_opts_offset(msg, offset);
return ntohl(dnssl->nd_opt_dnssl_lifetime);
}
/**
* ndp_msg_opt_dnssl_domain:
* @msg: message structure
* @offset: in-message offset
* @domain_index: domain index
*
* Get DNS Search List domain.
* User should use this function only inside ndp_msg_opt_for_each_offset()
* macro loop.
*
* Returns: address.
**/
NDP_EXPORT
char *ndp_msg_opt_dnssl_domain(struct ndp_msg *msg, int offset,
int domain_index)
{
int i;
static char buf[256];
struct __nd_opt_dnssl *dnssl =
ndp_msg_payload_opts_offset(msg, offset);
size_t len = dnssl->nd_opt_dnssl_len << 3; /* convert to bytes */
char *ptr;
len -= in_struct_offset(struct __nd_opt_dnssl, nd_opt_dnssl_domains);
ptr = dnssl->nd_opt_dnssl_domains;
i = 0;
while (len > 0) {
size_t buf_len = 0;
while (len > 0) {
uint8_t dom_len = *ptr;
ptr++;
len--;
if (!dom_len)
break;
if (dom_len > len)
return NULL;
if (buf_len + dom_len + 1 > sizeof(buf))
return NULL;
memcpy(buf + buf_len, ptr, dom_len);
buf[buf_len + dom_len] = '.';
ptr += dom_len;
len -= dom_len;
buf_len += dom_len + 1;
}
if (!buf_len)
break;
buf[buf_len - 1] = '\0'; /* overwrite final '.' */
if (i++ == domain_index)
return buf;
}
return NULL;
}
static int ndp_call_handlers(struct ndp *ndp, struct ndp_msg *msg);
static int ndp_sock_recv(struct ndp *ndp)
{
struct ndp_msg *msg;
enum ndp_msg_type msg_type;
size_t len;
int err;
msg = ndp_msg_alloc();
if (!msg)
return -ENOMEM;
len = ndp_msg_payload_maxlen(msg);
err = myrecvfrom6(ndp->sock, msg->buf, &len, 0,
&msg->addrto, &msg->ifindex, &msg->hoplimit);
if (err) {
err(ndp, "Failed to receive message");
goto free_msg;
}
dbg(ndp, "rcvd from: %s, ifindex: %u, hoplimit: %d",
str_in6_addr(&msg->addrto), msg->ifindex, msg->hoplimit);
if (msg->hoplimit != 255) {
warn(ndp, "ignoring packet with bad hop limit (%d)", msg->hoplimit);
err = 0;
goto free_msg;
}
if (len < sizeof(*msg->icmp6_hdr)) {
warn(ndp, "rcvd icmp6 packet too short (%luB)", len);
err = 0;
goto free_msg;
}
err = ndp_msg_type_by_raw_type(&msg_type, msg->icmp6_hdr->icmp6_type);
if (err) {
err = 0;
goto free_msg;
}
ndp_msg_init(msg, msg_type);
ndp_msg_payload_len_set(msg, len);
if (!ndp_msg_check_valid(msg)) {
warn(ndp, "rcvd invalid ND message");
err = 0;
goto free_msg;
}
dbg(ndp, "rcvd %s, len: %zuB",
ndp_msg_type_info(msg_type)->strabbr, len);
if (!ndp_msg_check_opts(msg)) {
err = 0;
goto free_msg;
}
err = ndp_call_handlers(ndp, msg);;
free_msg:
ndp_msg_destroy(msg);
return err;
}
/**
* SECTION: socket open/close functions
* @short_description: functions for opening and closing the ICMPv6 raw socket
*/
static int ndp_sock_open(struct ndp *ndp)
{
int sock;
struct icmp6_filter flt;
int ret;
int err;
int val;
int i;
sock = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (sock == -1) {
err(ndp, "Failed to create ICMP6 socket.");
return -errno;
}
val = 1;
ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
&val, sizeof(val));
if (ret == -1) {
err(ndp, "Failed to setsockopt IPV6_RECVPKTINFO.");
err = -errno;
goto close_sock;
}
val = 255;
ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
&val, sizeof(val));
if (ret == -1) {
err(ndp, "Failed to setsockopt IPV6_MULTICAST_HOPS.");
err = -errno;
goto close_sock;
}
val = 1;
ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
&val, sizeof(val));
if (ret == -1) {
err(ndp, "Failed to setsockopt IPV6_RECVHOPLIMIT,.");
err = -errno;
goto close_sock;
}
ICMP6_FILTER_SETBLOCKALL(&flt);
for (i = 0; i < NDP_MSG_TYPE_LIST_SIZE; i++)
ICMP6_FILTER_SETPASS(ndp_msg_type_info(i)->raw_type, &flt);
ret = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &flt,
sizeof(flt));
if (ret == -1) {
err(ndp, "Failed to setsockopt ICMP6_FILTER.");
err = -errno;
goto close_sock;
}
ndp->sock = sock;
return 0;
close_sock:
close(sock);
return err;
}
static void ndp_sock_close(struct ndp *ndp)
{
close(ndp->sock);
}
/**
* SECTION: msgrcv handler
* @short_description: msgrcv handler and related stuff
*/
struct ndp_msgrcv_handler_item {
struct list_item list;
ndp_msgrcv_handler_func_t func;
enum ndp_msg_type msg_type;
uint32_t ifindex;
void * priv;
};
static struct ndp_msgrcv_handler_item *
ndp_find_msgrcv_handler_item(struct ndp *ndp,
ndp_msgrcv_handler_func_t func,
enum ndp_msg_type msg_type, uint32_t ifindex,
void *priv)
{
struct ndp_msgrcv_handler_item *handler_item;
list_for_each_node_entry(handler_item, &ndp->msgrcv_handler_list, list)
if (handler_item->func == func &&
handler_item->msg_type == msg_type &&
handler_item->ifindex == ifindex &&
handler_item->priv == priv)
return handler_item;
return NULL;
}
static int ndp_call_handlers(struct ndp *ndp, struct ndp_msg *msg)
{
struct ndp_msgrcv_handler_item *handler_item;
int err;
list_for_each_node_entry(handler_item,
&ndp->msgrcv_handler_list, list) {
if (handler_item->msg_type != NDP_MSG_ALL &&
handler_item->msg_type != ndp_msg_type(msg))
continue;
if (handler_item->ifindex &&
handler_item->ifindex != msg->ifindex)
continue;
err = handler_item->func(ndp, msg, handler_item->priv);
if (err)
return err;
}
return 0;
}
/**
* ndp_msgrcv_handler_register:
* @ndp: libndp library context
* @func: handler function for received messages
* @msg_type: message type to match
* @ifindex: interface index to match
* @priv: func private data
*
* Registers custom @func handler which is going to be called when
* specified @msg_type is received. If one wants the function to be
* called for all message types, pass NDP_MSG_ALL,
* Note that @ifindex can be set to filter only messages received on
* specified interface. For @func to be called for messages received on
* all interfaces, just set 0.
*
* Returns: zero on success or negative number in case of an error.
**/
NDP_EXPORT
int ndp_msgrcv_handler_register(struct ndp *ndp, ndp_msgrcv_handler_func_t func,
enum ndp_msg_type msg_type, uint32_t ifindex,
void *priv)
{
struct ndp_msgrcv_handler_item *handler_item;
if (ndp_find_msgrcv_handler_item(ndp, func, msg_type,
ifindex, priv))
return -EEXIST;
if (!func)
return -EINVAL;
handler_item = malloc(sizeof(*handler_item));
if (!handler_item)
return -ENOMEM;
handler_item->func = func;
handler_item->msg_type = msg_type;
handler_item->ifindex = ifindex;
handler_item->priv = priv;
list_add_tail(&ndp->msgrcv_handler_list, &handler_item->list);
return 0;
}
/**
* ndp_msgrcv_handler_unregister:
* @ndp: libndp library context
* @func: handler function for received messages
* @msg_type: message type to match
* @ifindex: interface index to match
* @priv: func private data
*
* Unregisters custom @func handler.
*
**/
NDP_EXPORT
void ndp_msgrcv_handler_unregister(struct ndp *ndp, ndp_msgrcv_handler_func_t func,
enum ndp_msg_type msg_type, uint32_t ifindex,
void *priv)
{
struct ndp_msgrcv_handler_item *handler_item;
handler_item = ndp_find_msgrcv_handler_item(ndp, func, msg_type,
ifindex, priv);
if (!handler_item)
return;
list_del(&handler_item->list);
free(handler_item);
}
/**
* SECTION: event fd
* @short_description: event filedescriptor related stuff
*/
/**
* ndp_get_eventfd:
* @ndp: libndp library context
*
* Get eventfd filedesctiptor.
*
* Returns: fd.
**/
NDP_EXPORT
int ndp_get_eventfd(struct ndp *ndp)
{
return ndp->sock;
}
/**
* ndp_call_eventfd_handler:
* @ndp: libndp library context
*
* Call eventfd handler.
*
* Returns: zero on success or negative number in case of an error.
**/
NDP_EXPORT
int ndp_call_eventfd_handler(struct ndp *ndp)
{
return ndp_sock_recv(ndp);
}
/**
* ndp_callall_eventfd_handler:
* @ndp: libndp library context
*
* Call all pending events on eventfd handler.
*
* Returns: zero on success or negative number in case of an error.
**/
NDP_EXPORT
int ndp_callall_eventfd_handler(struct ndp *ndp)
{
fd_set rfds;
int fdmax;
struct timeval tv;
int fd = ndp_get_eventfd(ndp);
int ret;
int err;
memset(&tv, 0, sizeof(tv));
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
fdmax = fd + 1;
while (true) {
ret = select(fdmax, &rfds, NULL, NULL, &tv);
if (ret == -1)
return -errno;
if (!FD_ISSET(fd, &rfds))
return 0;
err = ndp_call_eventfd_handler(ndp);
if (err)
return err;
}
}
/**
* SECTION: Exported context functions
* @short_description: Core context functions exported to user
*/
/**
* ndp_open:
* @p_ndp: pointer where new libndp library context address will be stored
*
* Allocates and initializes library context, opens raw socket.
*
* Returns: zero on success or negative number in case of an error.
**/
NDP_EXPORT
int ndp_open(struct ndp **p_ndp)
{
struct ndp *ndp;
const char *env;
int err;
ndp = myzalloc(sizeof(*ndp));
if (!ndp)
return -ENOMEM;
ndp->log_fn = log_stderr;
ndp->log_priority = LOG_ERR;
/* environment overwrites config */
env = getenv("NDP_LOG");
if (env != NULL)
ndp_set_log_priority(ndp, log_priority(env));
dbg(ndp, "ndp context %p created.", ndp);
dbg(ndp, "log_priority=%d", ndp->log_priority);
list_init(&ndp->msgrcv_handler_list);
err = ndp_sock_open(ndp);
if (err)
goto free_ndp;
*p_ndp = ndp;
return 0;
free_ndp:
free(ndp);
return err;
}
/**
* ndp_close:
* @ndp: libndp library context
*
* Do library context cleanup.
**/
NDP_EXPORT
void ndp_close(struct ndp *ndp)
{
ndp_sock_close(ndp);
free(ndp);
}