/* * libndp.c - Neighbour discovery library * Copyright (C) 2013-2015 Jiri Pirko * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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, char buf[static INET6_ADDRSTRLEN]) { return inet_ntop(AF_INET6, addr, buf, INET6_ADDRSTRLEN); } /** * 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 NDP_THREAD 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 NDP_THREAD 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 NDP_THREAD 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; char buf[INET6_ADDRSTRLEN]; 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, buf), 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) { struct pollfd pfd; int ret; int err; pfd = (struct pollfd) { .fd = ndp_get_eventfd(ndp), .events = POLLIN, }; while (true) { ret = poll(&pfd, 1, 0); if (ret == -1) return -errno; if (!(pfd.revents & POLLIN)) 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); }