diff --git a/src/addr.c b/src/addr.c index 2ffbe3a..a7074a5 100644 --- a/src/addr.c +++ b/src/addr.c @@ -19,6 +19,11 @@ #include #include +#ifdef __FreeBSD__ +# include +# define s6_addr32 __u6_addr.__u6_addr32 +#endif + #include "ndppd.h" /*! Returns the string representation of addr. diff --git a/src/iface.c b/src/iface.c index 653a3b6..884553d 100644 --- a/src/iface.c +++ b/src/iface.c @@ -18,12 +18,10 @@ */ #include #include -#include -#include -#include #include -#include #include +/* Need to include netinet/in.h first on FreeBSD. */ +#include #include #include #include @@ -31,6 +29,20 @@ #include #include +#ifdef __linux__ +# include +# include +# include +# include +#else +# include +# include +# include +# include +# include +# define s6_addr32 __u6_addr.__u6_addr32 +#endif + #include "addr.h" #include "iface.h" #include "io.h" @@ -38,6 +50,10 @@ #include "proxy.h" #include "session.h" +#ifdef __clang__ +# pragma clang diagnostic ignored "-Waddress-of-packed-member" +#endif + extern int nd_conf_invalid_ttl; extern int nd_conf_valid_ttl; extern int nd_conf_renew; @@ -173,6 +189,8 @@ static __attribute__((unused)) void ndL_handle_packet(nd_iface_t *iface, uint8_t ndL_handle_na(iface, msg); } +#ifdef __linux__ +/* Called from nd_io_poll() when there are pending events on the nd_io_t. */ static void ndL_io_handler(nd_io_t *io, __attribute__((unused)) int events) { struct sockaddr_ll lladdr; @@ -192,14 +210,106 @@ static void ndL_io_handler(nd_io_t *io, __attribute__((unused)) int events) if (len < 0) return; + if ((size_t)len < sizeof(struct ether_header) + sizeof(struct ip6_hdr)) + continue; + + struct ether_header *eh = (struct ether_header *)(buf); + + if (eh->ether_type != ntohs(ETHERTYPE_IPV6)) + continue; + + struct ip6_hdr *ip6_hdr = (struct ip6_hdr *)(eh + 1); + nd_iface_t *iface; ND_LL_SEARCH(ndL_first_iface, iface, next, iface->index == (unsigned int)lladdr.sll_ifindex); if (iface) - ndL_handle_packet(iface, buf, len); + ndL_handle_packet(iface, (uint8_t *)ip6_hdr, len - sizeof(struct ether_header)); } } +#else +/* Called from nd_io_poll() when there are pending events on the nd_io_t. */ +static void ndL_io_handler(nd_io_t *io, __attribute__((unused)) int events) +{ + __attribute__((aligned(BPF_ALIGNMENT))) uint8_t buf[4096]; /* Depends on BIOCGBLEN */ + + for (;;) + { + ssize_t len = nd_io_read(io, buf, sizeof(buf)); + + if (len < 0) + { + if (errno == EAGAIN) + return; + + nd_log_error("%s", strerror(errno)); + } + + for (size_t i = 0; i < (size_t)len;) + { + struct bpf_hdr *bpf_hdr = (struct bpf_hdr *)(buf + i); + i += BPF_WORDALIGN(bpf_hdr->bh_hdrlen + bpf_hdr->bh_caplen); + + if (bpf_hdr->bh_caplen < sizeof(struct ether_header) + sizeof(struct ip6_hdr)) + continue; + + struct ether_header *eh = (struct ether_header *)((void *)bpf_hdr + bpf_hdr->bh_hdrlen); + + if (eh->ether_type != ntohs(ETHERTYPE_IPV6)) + continue; + + struct ip6_hdr *ip6_hdr = (struct ip6_hdr *)(eh + 1); + + ndL_handle_packet((nd_iface_t *)io->data, (uint8_t *)ip6_hdr, + bpf_hdr->bh_caplen - sizeof(struct ether_header)); + } + } +} +#endif + +__attribute__((unused)) static bool ndL_configure_filter(nd_io_t *io) +{ +#ifndef __linux__ +# define sock_filter bpf_insn +# define sock_fprog bpf_program +#endif + + /* Set up filter, so we only get NS and NA messages. */ + static struct sock_filter filter[] = { + /* Load ether_type. */ + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, offsetof(struct ether_header, ether_type)), + /* Drop packet if not ETHERTYPE_IPV6. */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 5), + /* Load ip6_nxt. */ + 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, 3), + /* Load icmp6_type. */ + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, + sizeof(struct ether_header) + sizeof(struct ip6_hdr) + offsetof(struct icmp6_hdr, icmp6_type)), + /* Keep if ND_NEIGHBOR_SOLICIT. */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 2, 0), + /* Keep if ND_NEIGHBOR_SOLICIT. */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0), + /* Drop packet. */ + BPF_STMT(BPF_RET | BPF_K, 0), + /* Keep packet. */ + BPF_STMT(BPF_RET | BPF_K, (u_int32_t)-1) + }; + + static struct sock_fprog fprog = { 9, filter }; + +#ifdef __linux__ + if (setsockopt(io->fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) == -1) + return false; +#else + if (ioctl(io->fd, BIOCSETF, &fprog) == -1) + return false; +#endif + + return true; +} nd_iface_t *nd_iface_open(const char *name, unsigned int index) { @@ -235,6 +345,7 @@ nd_iface_t *nd_iface_open(const char *name, unsigned int index) return iface; } +#ifdef __linux__ /* Determine link-layer address. */ struct ifreq ifr; @@ -247,7 +358,74 @@ nd_iface_t *nd_iface_open(const char *name, unsigned int index) return NULL; } - /* Allocate the nd_ifa_t object. */ + uint8_t *lladdr = (uint8_t *)ifr.ifr_hwaddr.sa_data; +#else + nd_io_t *io = NULL; + + /* This requires a cloning bpf device, but I hope most sane systems got them. */ + if (!(io = nd_io_open("/dev/bpf", O_RDWR))) + { + nd_log_error("Failed to open a /dev/bpf device"); + return NULL; + } + + io->handler = ndL_io_handler; + + /* Set buffer length. */ + + unsigned int len = 4096; /* TODO: Configure */ + if (ioctl(io->fd, BIOCSBLEN, &len) < 0) + { + nd_log_error("BIOCSBLEN: %s", strerror(errno)); + nd_io_close(io); + return NULL; + } + + /* Bind to interface. */ + + struct ifreq ifr; + strcpy(ifr.ifr_name, name); + if (ioctl(io->fd, BIOCSETIF, &ifr) < 0) + { + nd_log_error("Failed to bind to interface: %s", strerror(errno)); + nd_io_close(io); + return NULL; + } + + /* Immediate */ + + uint32_t enable = 1; + if (ioctl(io->fd, BIOCIMMEDIATE, &enable) < 0) + { + nd_log_error("BIOCIMMEDIATE: %s", strerror(errno)); + nd_io_close(io); + return NULL; + } + + /* Determine link-layer address. */ + + int mib[] = { CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, (int)index }; + uint8_t sysctl_buf[512]; + size_t sysctl_buflen = sizeof(sysctl_buf); + + if (sysctl(mib, 6, sysctl_buf, &sysctl_buflen, NULL, 0) == -1) + { + nd_log_error("Failed to determine link-layer address: %s", strerror(errno)); + nd_io_close(io); + return NULL; + } + + if (!ndL_configure_filter(io)) + { + nd_log_error("Could not configure filter: %s", strerror(errno)); + nd_io_close(io); + return NULL; + } + + uint8_t *lladdr = (uint8_t *)LLADDR((struct sockaddr_dl *)(sysctl_buf + sizeof(struct if_msghdr))); +#endif + + /* Allocate the nd_iface_t object. */ iface = ndL_first_free_iface; @@ -265,9 +443,15 @@ nd_iface_t *nd_iface_open(const char *name, unsigned int index) iface->old_allmulti = -1; iface->old_promisc = -1; strcpy(iface->name, name); - memcpy(iface->lladdr, ifr.ifr_hwaddr.sa_data, 6); + memcpy(iface->lladdr, lladdr, 6); - nd_log_info("New interface %s (%d)", iface->name, iface->index); +#ifndef __linux__ + io->data = (uintptr_t)iface; + iface->bpf_io = io; +#endif + + nd_log_info("New interface %s [%02x:%02x:%02x:%02x:%02x:%02x]", iface->name, lladdr[0], lladdr[1], lladdr[2], + lladdr[3], lladdr[4], lladdr[5]); return iface; } @@ -285,6 +469,10 @@ void nd_iface_close(nd_iface_t *iface) nd_iface_set_allmulti(iface, iface->old_allmulti); } +#ifndef __linux__ + nd_io_close(iface->bpf_io); +#endif + ND_LL_DELETE(ndL_first_iface, iface, next); ND_LL_PREPEND(ndL_first_free_iface, iface, next); } @@ -314,14 +502,27 @@ static ssize_t ndL_send_icmp6(nd_iface_t *ifa, ndL_icmp6_msg_t *msg, size_t size msg->icmp6_hdr.icmp6_cksum = ndL_calculate_icmp6_checksum(msg, size); - struct sockaddr_ll addr; - memset(&addr, 0, sizeof(struct sockaddr_ll)); - addr.sll_family = AF_PACKET; - addr.sll_protocol = htons(ETH_P_IPV6); - addr.sll_ifindex = (int)ifa->index; - memcpy(addr.sll_addr, hwaddr, 6); + uint8_t buf[512]; - return nd_io_send(ndL_io, (struct sockaddr *)&addr, sizeof(addr), msg, size); + struct ether_header *eh = (struct ether_header *)buf; + + eh->ether_type = htons(ETHERTYPE_IPV6); + /* TODO: Maybe use BIOCSHDRCMPLT instead. */ + memcpy(eh->ether_shost, ifa->lladdr, ETHER_ADDR_LEN); + memcpy(eh->ether_dhost, hwaddr, ETHER_ADDR_LEN); + + memcpy(eh + 1, msg, size); + +#ifdef __linux__ + struct sockaddr_ll ll; + memset(&ll, 0, sizeof(ll)); + ll.sll_family = AF_PACKET; + ll.sll_ifindex = (int)ifa->index; + + return nd_io_send(ndL_io, (struct sockaddr *)&ll, sizeof(ll), buf, sizeof(struct ether_header) + size); +#else + return nd_io_write(ifa->bpf_io, buf, sizeof(struct ether_header) + size); +#endif } ssize_t nd_iface_write_na(nd_iface_t *iface, nd_addr_t *dst, uint8_t *dst_ll, nd_addr_t *tgt, bool router) @@ -397,39 +598,22 @@ ssize_t nd_iface_write_ns(nd_iface_t *ifa, nd_addr_t *tgt) bool nd_iface_startup() { - if (!(ndL_io = nd_io_socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6)))) +#ifdef __linux__ + if (!(ndL_io = nd_io_socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6)))) return false; /* Set up filter, so we only get NS and NA messages. */ - static struct sock_filter filter[] = { - /* Load next header field. */ - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)), - /* Bail if it's not IPPROTO_ICMPV6. */ - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3), - /* Load the ICMPv6 type. */ - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct icmp6_hdr, icmp6_type)), - /* Keep if ND_NEIGHBOR_SOLICIT. */ - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 2, 0), - /* Keep if ND_NEIGHBOR_SOLICIT. */ - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0), - /* Drop packet. */ - BPF_STMT(BPF_RET | BPF_K, 0), - /* Keep packet. */ - BPF_STMT(BPF_RET | BPF_K, (u_int32_t)-1) - }; - - static struct sock_fprog fprog = { 7, filter }; - - if (setsockopt(ndL_io->fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) + if (!ndL_configure_filter(ndL_io)) { nd_io_close(ndL_io); ndL_io = NULL; - nd_log_error("Failed to configure netfilter: %s", strerror(errno)); + nd_log_error("Failed to configure BPF: %s", strerror(errno)); return NULL; } ndL_io->handler = ndL_io_handler; +#endif return true; } @@ -508,5 +692,6 @@ void nd_iface_cleanup() nd_iface_close(iface); } - nd_io_close(ndL_io); + if (ndL_io) + nd_io_close(ndL_io); } diff --git a/src/iface.h b/src/iface.h index 1d6e18d..073d850 100644 --- a/src/iface.h +++ b/src/iface.h @@ -38,6 +38,10 @@ struct nd_iface nd_proxy_t *proxy; nd_session_t *sessions; /* All sessions expecting NA messages to arrive here. */ + +#ifndef __linux__ + nd_io_t *bpf_io; +#endif }; extern bool nd_iface_no_restore_flags; diff --git a/src/io.c b/src/io.c index f592b2c..7d4cf87 100644 --- a/src/io.c +++ b/src/io.c @@ -21,9 +21,13 @@ #include #include #include +#include + +#if !defined(__linux__) && !defined(NDPPD_NO_USE_EPOLL) +# define NDPPD_NO_USE_EPOLL +#endif #ifndef NDPPD_NO_USE_EPOLL -# include # include #else # include @@ -56,7 +60,7 @@ static void ndL_refresh_pollfds() { int count; - ND_LL_COUNT(ndL_first_sio, count, next); + ND_LL_COUNT(ndL_first_io, count, next); if (count > pollfds_size) { @@ -65,14 +69,12 @@ static void ndL_refresh_pollfds() pollfds = (struct pollfd *)realloc(pollfds == static_pollfds ? NULL : pollfds, new_pollfds_size * sizeof(struct pollfd)); - /* TODO: Validate return value */ - pollfds_size = new_pollfds_size; } int index = 0; - ND_LL_FOREACH(ndL_first_sio, sio, next) + ND_LL_FOREACH(ndL_first_io, io, next) { pollfds[index].fd = io->fd; pollfds[index].revents = 0; @@ -84,24 +86,6 @@ static void ndL_refresh_pollfds() } #endif -nd_io_t *nd_sio_create(int fd) -{ - nd_io_t *io = ndL_first_free_io; - - if (io) - ND_LL_DELETE(ndL_first_free_io, io, next); - else - io = ND_ALLOC(nd_io_t); - - ND_LL_PREPEND(ndL_first_io, io, next); - - io->fd = fd; - io->data = 0; - io->handler = NULL; - - return io; -} - static nd_io_t *ndL_create(int fd) { int flags = fcntl(fd, F_GETFL, 0); @@ -239,6 +223,19 @@ ssize_t nd_io_recv(nd_io_t *io, struct sockaddr *addr, size_t addrlen, void *msg return len; } +ssize_t nd_io_read(nd_io_t *io, void *buf, size_t count) +{ + return read(io->fd, buf, count); +} + +ssize_t nd_io_write(nd_io_t *io, void *buf, size_t count) +{ + ssize_t len = write(io->fd, buf, count); + if (len < 0) + nd_log_error("err: %s", strerror(errno)); + return len; +} + bool nd_io_bind(nd_io_t *io, const struct sockaddr *addr, size_t addrlen) { return bind(io->fd, addr, addrlen) == 0; @@ -264,7 +261,6 @@ bool nd_io_poll() if (io->handler) io->handler(io, events[i].events); } - #else if (ndL_dirty) { @@ -285,12 +281,12 @@ bool nd_io_poll() if (pollfds[i].revents == 0) continue; - for (nd_sio_t *sio = ndL_first_sio; sio; sio = io->next) + ND_LL_FOREACH(ndL_first_io, io, next) { if (io->fd == pollfds[i].fd) { if (io->handler != NULL) - io->handler(sio, pollfds[i].revents); + io->handler(io, pollfds[i].revents); break; } diff --git a/src/io.h b/src/io.h index 81d13e4..6d3cb50 100644 --- a/src/io.h +++ b/src/io.h @@ -38,6 +38,9 @@ bool nd_io_bind(nd_io_t *io, const struct sockaddr *addr, size_t addrlen); ssize_t nd_io_send(nd_io_t *io, const struct sockaddr *addr, size_t addrlen, const void *msg, size_t msglen); ssize_t nd_io_recv(nd_io_t *io, struct sockaddr *addr, size_t addrlen, void *msg, size_t msglen); bool nd_io_poll(); +ssize_t nd_io_read(nd_io_t *io, void *buf, size_t count); +ssize_t nd_io_write(nd_io_t *io, void *buf, size_t count); + void nd_io_cleanup(); #endif /*NDPPD_IO_H*/ diff --git a/src/proxy.c b/src/proxy.c index df87034..7537d83 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -237,10 +237,12 @@ bool nd_proxy_startup() proxy->iface->proxy = proxy; +#ifdef __linux__ if (proxy->promisc) nd_iface_set_promisc(proxy->iface, true); else nd_iface_set_allmulti(proxy->iface, true); +#endif ND_LL_FOREACH(proxy->rules, rule, next) { diff --git a/src/rtnl.c b/src/rtnl.c index 5227735..aa39ab2 100644 --- a/src/rtnl.c +++ b/src/rtnl.c @@ -17,25 +17,32 @@ * along with ndppd. If not, see . */ #include -#include #include #include +#ifdef __linux__ +# include +#else +# include +# include +# include +#endif + #include "addr.h" #include "io.h" #include "ndppd.h" #include "rtnl.h" static nd_io_t *ndL_io; -static nd_rtnl_route_t *ndL_routes, *ndL_free_routes; -static nd_rtnl_addr_t *ndL_addrs, *ndL_free_addrs; +__attribute__((unused)) static nd_rtnl_route_t *ndL_routes, *ndL_free_routes; +__attribute__((unused)) static nd_rtnl_addr_t *ndL_addrs, *ndL_free_addrs; long nd_rtnl_dump_timeout; /* * This will ensure the linked list is kept sorted, so it will be easier to find a match. */ -static void ndL_insert_route(nd_rtnl_route_t *route) +__attribute__((unused)) static void ndL_insert_route(nd_rtnl_route_t *route) { nd_rtnl_route_t *prev = NULL; @@ -58,6 +65,7 @@ static void ndL_insert_route(nd_rtnl_route_t *route) } } +#ifdef __linux__ static void ndL_handle_newaddr(struct ifaddrmsg *msg, int length) { nd_addr_t *addr = NULL; @@ -227,12 +235,39 @@ static void ndL_io_handler(__attribute__((unused)) nd_io_t *unused1, __attribute } } } +#else +__attribute__((unused)) static void ndL_handle_newaddr(struct ifa_msghdr *msg, int length) +{ + (void)msg; + (void)length; + + nd_log_debug("rtnl: NEWADDR"); +} + +static void ndL_io_handler(__attribute__((unused)) nd_io_t *unused1, __attribute__((unused)) int unused2) +{ + uint8_t buf[4096]; + + for (;;) + { + ssize_t len = nd_io_read(ndL_io, buf, sizeof(buf)); + + if (len < 0) + /* Failed. */ + return; + } +} + + +#endif + bool nd_rtnl_open() { if (ndL_io != NULL) return true; +#ifdef __linux__ if (!(ndL_io = nd_io_socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE))) { nd_log_error("Failed to open netlink socket: %s", strerror(errno)); @@ -251,6 +286,13 @@ bool nd_rtnl_open() ndL_io = NULL; return false; } +#else + if (!(ndL_io = nd_io_socket(AF_ROUTE, SOCK_RAW, AF_INET6))) + { + nd_log_error("Failed to open routing socket: %s", strerror(errno)); + return false; + } +#endif ndL_io->handler = ndL_io_handler; @@ -268,6 +310,7 @@ bool nd_rtnl_query_routes() if (nd_rtnl_dump_timeout) return false; +#ifdef __linux__ struct { struct nlmsghdr hdr; @@ -291,6 +334,7 @@ bool nd_rtnl_query_routes() nd_rtnl_dump_timeout = nd_current_time + 5000; nd_io_send(ndL_io, (struct sockaddr *)&addr, sizeof(addr), &req, sizeof(req)); +#endif return false; } @@ -299,6 +343,7 @@ bool nd_rtnl_query_addresses() if (nd_rtnl_dump_timeout) return false; +#ifdef __linux__ struct { struct nlmsghdr hdr; @@ -321,6 +366,7 @@ bool nd_rtnl_query_addresses() nd_rtnl_dump_timeout = nd_current_time + 5000; nd_io_send(ndL_io, (struct sockaddr *)&addr, sizeof(addr), &req, sizeof(req)); +#endif return false; }