diff --git a/Makefile b/Makefile index 1a4e518..7f37eca 100644 --- a/Makefile +++ b/Makefile @@ -9,13 +9,15 @@ CXX ?= g++ GZIP ?= /bin/gzip MANDIR ?= ${DESTDIR}${PREFIX}/share/man SBINDIR ?= ${DESTDIR}${PREFIX}/sbin +PKG_CONFIG ?= pkg-config -LIBS = +LIBS = `${PKG_CONFIG} --libs glib-2.0 libnl-3.0 libnl-route-3.0` -pthread +CPPFLAGS = `${PKG_CONFIG} --cflags glib-2.0 libnl-3.0 libnl-route-3.0` OBJS = src/logger.o src/ndppd.o src/iface.o src/proxy.o src/address.o \ - src/rule.o src/session.o src/conf.o src/route.o + src/rule.o src/session.o src/conf.o src/route.o src/nd-netlink.o -all: ndppd ndppd.1.gz ndppd.conf.5.gz +all: ndppd ndppd.1.gz ndppd.conf.5.gz nd-proxy install: all mkdir -p ${SBINDIR} ${MANDIR} ${MANDIR}/man1 ${MANDIR}/man5 @@ -23,6 +25,7 @@ install: all chmod +x ${SBINDIR}/ndppd cp ndppd.1.gz ${MANDIR}/man1 cp ndppd.conf.5.gz ${MANDIR}/man5 + cp nd-proxy ${SBINDIR} ndppd.1.gz: ${GZIP} < ndppd.1 > ndppd.1.gz @@ -33,8 +36,11 @@ ndppd.conf.5.gz: ndppd: ${OBJS} ${CXX} -o ndppd ${LDFLAGS} ${LIBS} ${OBJS} +nd-proxy: nd-proxy.c + ${CXX} -o nd-proxy -Wall -Werror ${LDFLAGS} `${PKG_CONFIG} --cflags glib-2.0` nd-proxy.c `${PKG_CONFIG} --libs glib-2.0` + .cc.o: ${CXX} -c ${CPPFLAGS} $(CXXFLAGS) -o $@ $< clean: - rm -f ndppd ndppd.conf.5.gz ndppd.1.gz ${OBJS} + rm -f ndppd ndppd.conf.5.gz ndppd.1.gz ${OBJS} nd-proxy diff --git a/nd-proxy.c b/nd-proxy.c new file mode 100644 index 0000000..1c620c0 --- /dev/null +++ b/nd-proxy.c @@ -0,0 +1,550 @@ +/** + * @file nd-proxy.c + * + * Copyright 2016, Allied Telesis Labs New Zealand, Ltd + * + * +---------+ + * If A | | If B + * A-----------------| PROXY |-----------------B + * | | + * +---------+ + * IPv6: A IPv6: PA IPv6: PB IPv6: B + * L2: a L2: pa L2: pb L2: b + * + * RS/RA proxy + * RS + * --------------------> + * L3src=A, L3dst=AllR L3src=A, L3dst=AllR + * L2src=a, L2dst=allr, SLL=a L2src=pb, L2dst=allr, SLL=pb + * + * RA + * <-------------------- + * L3src=B, L3dst=AllN L3src=B, L3dst=AllN + * L2src=pa, L2dst=alln, SLL=pa L2src=b, L2dst=alln, SLL=b + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +/* Mode */ +#define PROXY_RS (1 << 0) +#define PROXY_RA (1 << 1) +#define PROXY_NS (1 << 2) +#define PROXY_NA (1 << 3) +#define PROXY_RD (1 << 4) + +/* Debug macros */ +#define DEBUG(fmt, args...) if (debug) printf (fmt, ## args) +#define ERROR(fmt, args...) \ +{ \ + syslog(LOG_ERR, fmt, ## args); \ + fprintf(stderr, fmt, ## args); \ +} + +/* Proxy interface */ +typedef struct _iface_t { + char *name; + uint32_t flags; + int ifindex; + uint8_t hwaddr[ETH_ALEN]; + int fd; + guint src; +} iface_t; + +/* Globals */ +static bool debug = false; +static GList *ifaces = NULL; + +/* Find the specified option in the ICMPv6 message */ +static struct nd_opt_hdr * +find_option(struct icmp6_hdr *icmp6_hdr, int len, uint8_t type) +{ + struct nd_opt_hdr *nd_opt; + int icmp_hlen; + + /* Each ND type has a different offest to the options */ + switch (icmp6_hdr->icmp6_type) { + case ND_ROUTER_SOLICIT: + icmp_hlen = sizeof(struct nd_router_solicit); + break; + case ND_ROUTER_ADVERT: + icmp_hlen = sizeof(struct nd_router_advert); + break; + case ND_NEIGHBOR_SOLICIT: + icmp_hlen = sizeof(struct nd_neighbor_solicit); + break; + case ND_NEIGHBOR_ADVERT: + icmp_hlen = sizeof(struct nd_neighbor_advert); + break; + case ND_REDIRECT: + icmp_hlen = sizeof(struct nd_redirect); + break; + default: + return NULL; + } + + /* Find the option */ + nd_opt = (struct nd_opt_hdr *)((uint8_t *)icmp6_hdr + icmp_hlen); + len -= icmp_hlen; + while (len > 0) { + int opt_len = nd_opt->nd_opt_len * 8; + if (nd_opt->nd_opt_type == type) + return nd_opt; + nd_opt = (struct nd_opt_hdr *)((uint8_t *)nd_opt + + sizeof(struct nd_opt_hdr) + opt_len); + len -= (sizeof(struct nd_opt_hdr) + opt_len); + } + return NULL; +} + +/* Update the SLLA option in the packet (and checksum) */ +static void +update_slla_option(struct icmp6_hdr *icmp6_hdr, int len, uint8_t *mac) +{ + struct nd_opt_hdr *nd_opt; + + /* Find the "source link-layer address" option */ + nd_opt = find_option(icmp6_hdr, len, ND_OPT_SOURCE_LINKADDR); + + /* Update the slla if we found it */ + if (nd_opt) { + /* Option data is the mac address - it is always 16-bit aligned */ + uint8_t *slla = (uint8_t *)nd_opt + sizeof(struct nd_opt_hdr); + + /* Update ICMPv6 header checksum based on the old and new mac adddress */ + uint16_t *omac = (uint16_t *)slla; + uint16_t *nmac = (uint16_t *)mac; + int i; + for (i = 0; i < ETH_ALEN / 2; i++) { + uint16_t hc_complement = ~ntohs(icmp6_hdr->icmp6_cksum); + uint16_t m_complement = ~ntohs(omac[i]); + uint16_t m_prime = ntohs(nmac[i]); + uint32_t sum = hc_complement + m_complement + m_prime; + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + icmp6_hdr->icmp6_cksum = htons(~((uint16_t)sum)); + } + + /* Copy the outgoing interface's hw addr into the + * "source link-layer address" option in the pkt. */ + memcpy(slla, mac, ETH_ALEN); + } +} + +/* Proxying of both RS and RA */ +static void +proxy_rsra(iface_t *iface, uint8_t *msg, int len) +{ + struct ether_header *eth_hdr; + struct ip6_hdr *ip6; + struct icmp6_hdr *icmp6_hdr; + struct sockaddr_ll socket_address; + + /* Parse the packet */ + eth_hdr = (struct ether_header *)msg; + ip6 = (struct ip6_hdr *)(msg + sizeof(struct ether_header)); + icmp6_hdr = (struct icmp6_hdr *)(msg + sizeof(struct ether_header) + + sizeof(struct ip6_hdr)); + + DEBUG("Tx(%s): %s\n", iface->name, + icmp6_hdr->icmp6_type == ND_ROUTER_SOLICIT ? + "ND_ROUTER_SOLICIT" : "ND_ROUTER_ADVERT"); + + /* Avoid proxying spoofed packets */ + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_src)) { + DEBUG("Tx(%s): Ignoring RS/RA from spoofed address\n", iface->name); + return; + } + + /* RS should be sent to "All Routers Address" FF02::2 */ + /* RA should be sent to "All Nodes Address" FF02::1 */ + /* Can only proxy to multicast L2 destinations 33:33:.. */ + if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) || + (eth_hdr->ether_dhost[0] != 0x33 && eth_hdr->ether_dhost[1] != 0x33)) { + DEBUG("Tx(%s): Ignoring RS/RA to non-multicast address\n", iface->name); + return; + } + + /* Copy the outgoing interface's hw addr into the + * "source link-layer address" option in the pkt */ + update_slla_option(icmp6_hdr, len - ((uint8_t *)icmp6_hdr - msg), iface->hwaddr); + + /* Copy the outgoing interface's hw addr into the + * MAC source address in the pkt. */ + memcpy((uint8_t *)(eth_hdr->ether_shost), iface->hwaddr, ETH_ALEN); + + /* Send the packet */ + socket_address.sll_ifindex = iface->ifindex; + socket_address.sll_halen = ETH_ALEN; + memcpy((uint8_t *)socket_address.sll_addr, iface->hwaddr, ETH_ALEN); + if (sendto (iface->fd, msg, len, 0, (struct sockaddr *)&socket_address, + sizeof(struct sockaddr_ll)) < 0) { + ERROR("Tx(%s): Failed to send packet\n", iface->name); + return; + } +} + +static gboolean +handle_fd(gint fd, GIOCondition condition, gpointer data) +{ + iface_t *iface = (iface_t *)data; + struct msghdr mhdr; + struct iovec iov; + struct sockaddr_ll t_saddr; + uint8_t msg[4096]; + int size = 4096; + int len; + + /* Receive a packet */ + iov.iov_len = size; + iov.iov_base = (caddr_t)msg; + memset(&mhdr, 0, sizeof(mhdr)); + mhdr.msg_name = (caddr_t)&t_saddr; + mhdr.msg_namelen = sizeof(struct sockaddr); + mhdr.msg_iov = &iov; + mhdr.msg_iovlen = 1; + if ((len = recvmsg(fd, &mhdr, 0)) < 0) { + DEBUG("Rx(%s):Interface has gone away\n", iface->name); + return true; + } + + /* Check we have at least the icmp header */ + if ((size_t) len < (ETH_HLEN + sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr))) { + ERROR("Rx(%s): Ignoring short packet (%d bytes)\n", iface->name, len); + return true; + } + + struct icmp6_hdr *icmp6_hdr = (struct icmp6_hdr *)(msg + ETH_HLEN + sizeof(struct ip6_hdr)); + uint8_t icmp6_type = icmp6_hdr->icmp6_type; + switch (icmp6_type) { + case ND_ROUTER_SOLICIT: + DEBUG("Rx(%s): ND_ROUTER_SOLICIT\n", iface->name); + if (iface->flags & PROXY_RS) { + GList *iter; + for (iter = ifaces; iter; iter = g_list_next(iter)) { + iface_t *oiface = (iface_t *)iter->data; + if (oiface != iface) + proxy_rsra(oiface, msg, len); + } + } + break; + case ND_ROUTER_ADVERT: + DEBUG("Rx(%s): ND_ROUTER_ADVERT\n", iface->name); + if (iface->flags & PROXY_RA) { + GList *iter; + for (iter = ifaces; iter; iter = g_list_next(iter)) { + iface_t *oiface = (iface_t *)iter->data; + if (oiface != iface) + proxy_rsra(oiface, msg, len); + } + } + break; + case ND_NEIGHBOR_SOLICIT: + case ND_NEIGHBOR_ADVERT: + case ND_REDIRECT: + default: + DEBUG("Rx(%s): ignoring ICMPv6 packets of type %d\n", iface->name, icmp6_type); + break; + } + + return true; +} + +static char *flags_to_string(uint32_t flags) +{ + static char sbuffer[256]; + sbuffer[0] = '\0'; + if (flags == 0) + sprintf(sbuffer, "no packets"); + if (flags & PROXY_NS) + sprintf(sbuffer + strlen(sbuffer), "%sNS", + strlen(sbuffer) ? "," : ""); + if (flags & PROXY_NA) + sprintf(sbuffer + strlen(sbuffer), "%sNA", + strlen(sbuffer) ? "," : ""); + if (flags & PROXY_RS) + sprintf(sbuffer + strlen(sbuffer), "%sRS", + strlen(sbuffer) ? "," : ""); + if (flags & PROXY_RA) + sprintf(sbuffer + strlen(sbuffer), "%sRA", + strlen(sbuffer) ? "," : ""); + if (flags & PROXY_RD) + sprintf(sbuffer + strlen(sbuffer), "%sRD", + strlen(sbuffer) ? "," : ""); + return sbuffer; +} + +static struct sock_filter bpf_filter[] = { + /* Load the ether_type. */ + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, + offsetof(struct ether_header, ether_type)), + /* Bail if it's* not* ETHERTYPE_IPV6. */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 9), + /* Load the next header type. */ + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, + sizeof(struct ether_header) + offsetof(struct ip6_hdr, + ip6_nxt)), + /* Bail if it's* not* IPPROTO_ICMPV6. */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 7), + /* Load the ICMPv6 type. */ + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, + sizeof(struct ether_header) + sizeof(struct ip6_hdr) + + offsetof(struct icmp6_hdr, icmp6_type)), + /* Bail if it's* not* ND */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_SOLICIT, 4, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 3, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 2, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_REDIRECT, 0, 1), + /* Keep packet. */ + BPF_STMT(BPF_RET | BPF_K, (u_int32_t)-1), + /* Drop packet. */ + BPF_STMT(BPF_RET | BPF_K, 0) +}; + +static void iface_open(gpointer data, gpointer user) +{ + iface_t *iface = (iface_t *)data; + struct sockaddr_ll lladdr; + struct sock_fprog fprog; + struct ifreq ifr; + int on = 1; + int fd; + + DEBUG("Open(%s): %s\n", iface->name, flags_to_string(iface->flags)); + + /* Check the interface exists by getting its ifindex */ + iface->ifindex = if_nametoindex(iface->name); + if (!iface->ifindex) { + ERROR("Open(%s): Could not find interface\n", iface->name); + exit(-1); + } + + /* Create raw socket for tx/rx of IPv6 packets */ + if ((fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IPV6))) < 0) { + ERROR("Open(%s): Unable to create socket\n", iface->name); + exit(-1); + } + + /* Bind the socket to the specified interface */ + memset(&lladdr, 0, sizeof(struct sockaddr_ll)); + lladdr.sll_family = AF_PACKET; + lladdr.sll_protocol = htons(ETH_P_IPV6); + lladdr.sll_ifindex = iface->ifindex; + if (bind(fd, (struct sockaddr *)&lladdr, sizeof(struct sockaddr_ll)) < 0) { + close(fd); + ERROR("Open(%s): Failed to bind to interface\n", iface->name); + exit(-1); + } + + /* Set the socket non-blocking */ + if (ioctl(fd, FIONBIO, (char *)&on) < 0) { + close(fd); + ERROR("Open(%s): Failed to make interface non-blocking\n", iface->name); + exit(-1); + } + + /* Setup a filter to only receive ND packets */ + fprog.len = sizeof(bpf_filter) / sizeof(bpf_filter[0]); + fprog.filter = bpf_filter; + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) { + close(fd); + ERROR("Open(%s): Failed to set filter for ND packets\n", iface->name); + exit(-1); + } + + /* Enable all multicast for this interface */ + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, iface->name, IFNAMSIZ - 1); + if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) { + close(fd); + ERROR("Open(%s): Failed to get flags for interface\n", iface->name); + exit(-1); + } + ifr.ifr_flags |= IFF_ALLMULTI; + if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) { + close(fd); + ERROR("Open(%s): Failed to set flags for interface\n", iface->name); + exit(-1); + } + + /* Get the hwaddr of the interface */ + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, iface->name, IFNAMSIZ - 1); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { + ERROR("Open(%s): Failed to get interface hwaddr\n", iface->name); + exit(-1); + } + memcpy(iface->hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + /* Watch for packets */ + iface->fd = fd; + iface->src = g_unix_fd_add(fd, G_IO_IN, handle_fd, iface); +} + +static void iface_close(gpointer data) +{ + iface_t *iface = (iface_t *)data; + DEBUG("Close(%s)\n", iface->name); + g_source_remove(iface->src); + close(iface->fd); + free(iface->name); + free(iface); +} + +static iface_t *parse_interface(char *desc) +{ + char *name = NULL; + uint32_t flags = PROXY_RS | PROXY_RA; + char *pflags = strchr(desc, ':'); + + if (pflags) { + char *token = strtok(pflags + 1, ","); + flags = 0; + while (token != NULL) { + if (strcmp("NS", token) == 0) + flags |= PROXY_NS; + else if (strcmp("NA", token) == 0) + flags |= PROXY_NA; + else if (strcmp("RA", token) == 0) + flags |= PROXY_RA; + else if (strcmp("RS", token) == 0) + flags |= PROXY_RS; + else if (strcmp("RD", token) == 0) + flags |= PROXY_RD; + else + return NULL; + token = strtok(NULL, ","); + } + name = strndup(desc, pflags - desc); + } else { + name = strdup(desc); + } + iface_t *iface = (iface_t *)g_malloc0(sizeof(iface_t)); + iface->name = name; + iface->flags = flags; + iface->fd = -1; + iface->src = 0; + return iface; +} + +static gboolean termination_handler(gpointer arg1) +{ + GMainLoop *loop = (GMainLoop *) arg1; + g_main_loop_quit(loop); + return false; +} + +void help(char *app_name) +{ + printf("Usage: %s [-h] [-b] [-d] -i [:[,]..]\n" + " -h show this help\n" + " -b background mode\n" + " -d enable verbose debug\n" + " -i proxy [NS,NA,RS,RA,RD] messages received on \n" + "\n" "e.g %s -i eth1:RS -i eth2:RA\n", app_name, app_name); +} + +int main(int argc, char *argv[]) +{ + int i = 0; + bool background = false; + GMainLoop *loop = NULL; + + /* Parse options */ + while ((i = getopt(argc, argv, "hdbi:")) != -1) { + switch (i) { + case 'd': + debug = true; + background = false; + break; + case 'b': + background = true; + break; + case 'i': + { + iface_t *iface = parse_interface(optarg); + if (!iface) { + help(argv[0]); + ERROR("ERROR: Invalid interface specification (%s)\n", optarg); + return 0; + } + ifaces = g_list_prepend(ifaces, iface); + break; + } + case '?': + case 'h': + default: + help(argv[0]); + return 0; + } + } + + /* Check required */ + if (g_list_length(ifaces) < 2) { + help(argv[0]); + ERROR("ERROR: Require at least 2 interfaces.\n"); + return 0; + } + + /* Daemonize */ + if (background && fork() != 0) { + /* Parent */ + return 0; + } + + /* Main loop instance */ + loop = g_main_loop_new(NULL, true); + + /* Handle SIGTERM/SIGINT/SIGPIPE gracefully */ + g_unix_signal_add(SIGINT, termination_handler, loop); + g_unix_signal_add(SIGTERM, termination_handler, loop); + + /* Startup */ + g_list_foreach(ifaces, iface_open, NULL); + + /* Loop while not terminated */ + g_main_loop_run(loop); + + /* Shutdown */ + g_list_free_full(ifaces, iface_close); + + /* Free the glib main loop */ + if (loop) + g_main_loop_unref(loop); + + return 0; +} diff --git a/src/iface.cc b/src/iface.cc index b2df7b7..74d2fd6 100644 --- a/src/iface.cc +++ b/src/iface.cc @@ -34,6 +34,7 @@ #include +#include #include #include #include @@ -341,7 +342,11 @@ ssize_t iface::write(int fd, const address& daddr, const uint8_t* msg, size_t si int len; if ((len = sendmsg(fd,& mhdr, 0)) < 0) + { + int e = errno; + logger::error() << "iface::write() failed! errno=" << e; return -1; + } return len; } @@ -427,7 +432,7 @@ ssize_t iface::write_advert(const address& daddr, const address& taddr, bool rou opt->nd_opt_len = 1; na->nd_na_type = ND_NEIGHBOR_ADVERT; - na->nd_na_flags_reserved = ND_NA_FLAG_SOLICITED | (router ? ND_NA_FLAG_ROUTER : 0); + na->nd_na_flags_reserved = (daddr.is_multicast() ? 0 : ND_NA_FLAG_SOLICITED) | (router ? ND_NA_FLAG_ROUTER : 0); memcpy(&na->nd_na_target,& taddr.const_addr(), sizeof(struct in6_addr)); diff --git a/src/nd-netlink.cc b/src/nd-netlink.cc new file mode 100644 index 0000000..f0137c4 --- /dev/null +++ b/src/nd-netlink.cc @@ -0,0 +1,275 @@ +// +//@file nd-netlink.cc +// +// Copyright 2016, Allied Telesis Labs New Zealand, Ltd +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include "ndppd.h" +#include + +NDPPD_NS_BEGIN + +pthread_mutex_t cs_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct in6_addr* +address_create_ipv6(struct in6_addr *local) +{ + struct in6_addr *addr = (struct in6_addr *)calloc(1, sizeof(struct in6_addr)); + memcpy(addr, local, sizeof(struct in6_addr)); + return addr; +} + +void if_add_to_list(int ifindex, const ptr& ifa) +{ + bool found = false; + + pthread_mutex_lock (&cs_mutex); + for (std::vector::iterator it = interfaces.begin(); + it != interfaces.end(); it++) { + if ((*it).ifindex == ifindex) { + found = true; + break; + } + } + if (!found) { + logger::debug() << "rule::add_iface() if=" << ifa->name(); + interface anInterface; + anInterface._name = ifa->name(); + anInterface.ifindex = ifindex; + interfaces.push_back(anInterface); + } + pthread_mutex_unlock (&cs_mutex); +} + +void +if_addr_add(int ifindex, struct in6_addr *iaddr) +{ + pthread_mutex_lock (&cs_mutex); + for (std::vector::iterator it = interfaces.begin(); + it != interfaces.end(); it++) { + if ((*it).ifindex == ifindex) { + address addr = address(*iaddr); + logger::debug() << "Adding addr " << addr.to_string(); + std::list
::iterator it_addr; + it_addr = std::find((*it).addresses.begin(), (*it).addresses.end(), addr); + if (it_addr == (*it).addresses.end()) { + (*it).addresses.push_back(addr); + } + break; + } + } + free(iaddr); + pthread_mutex_unlock (&cs_mutex); +} + +void +if_addr_del(int ifindex, struct in6_addr *iaddr) +{ + pthread_mutex_lock (&cs_mutex); + for (std::vector::iterator it = interfaces.begin(); + it != interfaces.end(); it++) { + if ((*it).ifindex == ifindex) { + address addr = address(*iaddr); + logger::debug() << "Deleting addr " << addr.to_string(); + (*it).addresses.remove(addr); + break; + } + } + free(iaddr); + pthread_mutex_unlock (&cs_mutex); +} + +bool +if_addr_find(std::string iface, const struct in6_addr *iaddr) +{ + bool found = false; + + pthread_mutex_lock (&cs_mutex); + for (std::vector::iterator it = interfaces.begin(); + it != interfaces.end(); it++) { + if (iface.compare((*it)._name) == 0) { + address addr = address(*iaddr); + std::list
::iterator it_addr; + it_addr = std::find((*it).addresses.begin(), (*it).addresses.end(), addr); + if (it_addr != (*it).addresses.end()) { + found = true; + break; + } + } + } + pthread_mutex_unlock (&cs_mutex); + return found; +} + +static void +nl_msg_newaddr(struct nlmsghdr *hdr) +{ + struct ifaddrmsg *ifaddr = + (struct ifaddrmsg *)(((char *) hdr) + (sizeof(struct nlmsghdr))); + // parse the attributes + struct nlattr *attrs[IFA_MAX + 1]; + struct nlattr *s = (struct nlattr *)(((char *) ifaddr) + (sizeof(struct ifaddrmsg))); + int len = nlmsg_datalen(hdr) - sizeof(struct ifinfomsg); + memset(&attrs, '\0', sizeof(attrs)); + nla_parse(attrs, IFA_MAX, s, len, NULL); + + struct in6_addr* addr = NULL; + + if (ifaddr->ifa_family == AF_INET6) { + addr = address_create_ipv6((struct in6_addr *)nla_data(attrs[IFA_ADDRESS])); + if_addr_add(ifaddr->ifa_index, addr); + } +} + +static void +nl_msg_deladdr(struct nlmsghdr *hdr) +{ + struct ifaddrmsg *ifaddr = + (struct ifaddrmsg *)(((char *) hdr) + (sizeof(struct nlmsghdr))); + // parse the attributes + struct nlattr *attrs[IFA_MAX + 1]; + struct nlattr *s = (struct nlattr *)(((char *) ifaddr) + (sizeof(struct ifaddrmsg))); + int len = nlmsg_datalen(hdr) - sizeof(struct ifinfomsg); + memset(&attrs, '\0', sizeof(attrs)); + nla_parse(attrs, IFA_MAX, s, len, NULL); + + struct in6_addr* addr = NULL; + + if (ifaddr->ifa_family == AF_INET6) { + addr = address_create_ipv6((struct in6_addr *)nla_data(attrs[IFA_ADDRESS])); + if_addr_del(ifaddr->ifa_index, addr); + } +} + +static void +new_addr(struct nl_object *obj, void *p) +{ + struct rtnl_addr *addr = (struct rtnl_addr *) obj; + struct nl_addr *local = rtnl_addr_get_local(addr); + int family = rtnl_addr_get_family(addr); + int ifindex = rtnl_addr_get_ifindex(addr); + struct in6_addr* in_addr = NULL; + + char ipstr[INET6_ADDRSTRLEN]; + inet_ntop(family, nl_addr_get_binary_addr(local), ipstr, INET6_ADDRSTRLEN); + + switch (family) { + case AF_INET: + break; + case AF_INET6: + in_addr = address_create_ipv6((struct in6_addr *)nl_addr_get_binary_addr(local)); + if_addr_add(ifindex, in_addr); + break; + default: + logger::error() << "Unknown message family: " << family; + } +} + +static int +nl_msg_handler(struct nl_msg *msg, void *arg) +{ + logger::debug() << "nl_msg_handler"; + struct nlmsghdr *hdr = nlmsg_hdr(msg); + + switch (hdr->nlmsg_type) { + case RTM_NEWADDR: + nl_msg_newaddr(hdr); + break; + case RTM_DELADDR: + nl_msg_deladdr(hdr); + break; + default: + logger::error() << "Unknown message type: " << hdr->nlmsg_type; + } + + return NL_OK; +} + +static void * +netlink_monitor(void *p) +{ + struct nl_sock *sock = (struct nl_sock *) p; + struct nl_cache *addr_cache; + + // get all the current addresses + if (rtnl_addr_alloc_cache(sock, &addr_cache) < 0) { + perror("rtnl_addr_alloc_cache"); + return NULL; + } + + // add existing addresses + nl_cache_foreach(addr_cache, new_addr, NULL); + // destroy the cache + nl_cache_free(addr_cache); + + // switch to notification mode + // disable sequence checking + nl_socket_disable_seq_check(sock); + // set the callback we want + nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, nl_msg_handler, NULL); + + // subscribe to the IPv6 address change callbacks + nl_socket_add_memberships(sock, RTNLGRP_IPV6_IFADDR, 0); + + while (1) + { + nl_recvmsgs_default(sock); + } + return NULL; +} + +static pthread_t monitor_thread; +static struct nl_sock *monitor_sock; +struct nl_sock *control_sock; + +bool +netlink_setup() +{ + // create a netlink socket + control_sock = nl_socket_alloc(); + nl_connect(control_sock, NETLINK_ROUTE); + + // create a thread to run the netlink monitor in + // create a netlink socket + monitor_sock = nl_socket_alloc(); + nl_connect(monitor_sock, NETLINK_ROUTE); + // increase the recv buffer size to capture all notifications + nl_socket_set_buffer_size(monitor_sock, 2048000, 0); + + pthread_create(&monitor_thread, NULL, netlink_monitor, monitor_sock); + pthread_setname_np(monitor_thread, "netlink"); + if (pthread_setschedprio(monitor_thread, -10) < 0) + { + logger::warning() << "setschedprio: " << strerror(errno); + } + return true; +} + +bool +netlink_teardown() +{ + void *res = 0; + pthread_cancel(monitor_thread); + pthread_join(monitor_thread, &res); + nl_socket_free(monitor_sock); + nl_socket_free(control_sock); + return true; +} + +NDPPD_NS_END diff --git a/src/nd-netlink.h b/src/nd-netlink.h new file mode 100644 index 0000000..c612569 --- /dev/null +++ b/src/nd-netlink.h @@ -0,0 +1,28 @@ +// +// @file nd-netlink.h +// +// Copyright 2016, Allied Telesis Labs New Zealand, Ltd +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +NDPPD_NS_BEGIN + +bool netlink_teardown(); +bool netlink_setup(); +bool if_addr_find(std::string iface, const struct in6_addr *iaddr); +void if_add_to_list(int ifindex, const ptr& ifa); + +NDPPD_NS_END diff --git a/src/ndppd.cc b/src/ndppd.cc index bec9656..fe6f05b 100644 --- a/src/ndppd.cc +++ b/src/ndppd.cc @@ -152,7 +152,7 @@ static bool configure(ptr& cf) ptr pr = proxy::open(*pr_cf); if (!pr) { - return false; + return true; } if (!(x_cf = pr_cf->find("router"))) @@ -286,6 +286,8 @@ int main(int argc, char* argv[], char* env[]) gettimeofday(&t1, 0); + netlink_setup(); + while (running) { if (iface::poll_all() < 0) { if (running) { @@ -308,6 +310,7 @@ int main(int argc, char* argv[], char* env[]) session::update_all(elapsed_time); } + netlink_teardown(); logger::notice() << "Bye"; return 0; diff --git a/src/ndppd.h b/src/ndppd.h index 008726c..57ba829 100644 --- a/src/ndppd.h +++ b/src/ndppd.h @@ -35,3 +35,4 @@ #include "proxy.h" #include "session.h" #include "rule.h" +#include "nd-netlink.h" diff --git a/src/proxy.cc b/src/proxy.cc index 8bbc2e6..d3b6e66 100644 --- a/src/proxy.cc +++ b/src/proxy.cc @@ -126,6 +126,11 @@ void proxy::handle_solicit(const address& saddr, const address& daddr, return; } else { se->add_iface((*it)->ifa()); + if (if_addr_find((*it)->ifa()->name(), &taddr.const_addr())) { + logger::debug() << "Sending NA out " << (*it)->ifa()->name(); + se->add_iface(_ifa); + se->handle_advert(); + } } } } diff --git a/src/rule.cc b/src/rule.cc index 9e72480..ad4a7ec 100644 --- a/src/rule.cc +++ b/src/rule.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include "ndppd.h" #include "rule.h" @@ -24,6 +25,8 @@ NDPPD_NS_BEGIN +std::vector interfaces; + rule::rule() { } @@ -36,6 +39,12 @@ ptr rule::create(const ptr& pr, const address& addr, const ptr_ifa = ifa; ru->_addr = addr; ru->_aut = false; + unsigned int ifindex; + + ifindex = if_nametoindex(pr->ifa()->name().c_str()); + if_add_to_list(ifindex, pr->ifa()); + ifindex = if_nametoindex(ifa->name().c_str()); + if_add_to_list(ifindex, ifa); logger::debug() << "rule::create() if=" << pr->ifa()->name() << ", addr=" << addr; diff --git a/src/rule.h b/src/rule.h index 6663066..72c30ea 100644 --- a/src/rule.h +++ b/src/rule.h @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -56,4 +57,19 @@ private: rule(); }; +class interface { +public: + // List of IPv6 addresses on this interface + std::list
addresses; + + // Index of this interface + int ifindex; + + // Name of this interface. + std::string _name; + +}; + +extern std::vector interfaces; + NDPPD_NS_END diff --git a/src/session.cc b/src/session.cc index 2c3e65c..05091f4 100644 --- a/src/session.cc +++ b/src/session.cc @@ -24,6 +24,8 @@ NDPPD_NS_BEGIN std::list > session::_sessions; +static address all_nodes = address("ff02::1"); + void session::update_all(int elapsed_time) { for (std::list >::iterator it = _sessions.begin(); @@ -69,7 +71,7 @@ ptr session::create(const ptr& pr, const address& saddr, se->_ptr = se; se->_pr = pr; - se->_saddr = saddr; + se->_saddr = address("::") == saddr ? all_nodes : saddr; se->_taddr = taddr; se->_daddr = daddr; se->_ttl = pr->timeout();