Add support for /dev/bpf

This commit is contained in:
Daniel Adolfsson 2019-12-12 03:10:36 +01:00
parent 7b17dcc12f
commit 25f6efa8ab
7 changed files with 309 additions and 68 deletions

View File

@ -19,6 +19,11 @@
#include <arpa/inet.h>
#include <string.h>
#ifdef __FreeBSD__
# include <sys/socket.h>
# define s6_addr32 __u6_addr.__u6_addr32
#endif
#include "ndppd.h"
/*! Returns the string representation of <tt>addr</tt>.

View File

@ -18,12 +18,10 @@
*/
#include <assert.h>
#include <errno.h>
#include <linux/filter.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/icmp6.h>
#include <netinet/in.h>
/* Need to include netinet/in.h first on FreeBSD. */
#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <stddef.h>
#include <stdint.h>
@ -31,6 +29,20 @@
#include <string.h>
#include <sys/ioctl.h>
#ifdef __linux__
# include <linux/filter.h>
# include <linux/if_ether.h>
# include <linux/if_packet.h>
# include <netinet/if_ether.h>
#else
# include <fcntl.h>
# include <net/bpf.h>
# include <net/ethernet.h>
# include <net/if_dl.h>
# include <sys/sysctl.h>
# 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);
}

View File

@ -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;

View File

@ -21,9 +21,13 @@
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#if !defined(__linux__) && !defined(NDPPD_NO_USE_EPOLL)
# define NDPPD_NO_USE_EPOLL
#endif
#ifndef NDPPD_NO_USE_EPOLL
# include <fcntl.h>
# include <sys/epoll.h>
#else
# include <poll.h>
@ -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;
}

View File

@ -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*/

View File

@ -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)
{

View File

@ -17,25 +17,32 @@
* along with ndppd. If not, see <https://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <linux/rtnetlink.h>
#include <string.h>
#include <sys/socket.h>
#ifdef __linux__
# include <linux/rtnetlink.h>
#else
# include <net/if.h>
# include <net/if_var.h>
# include <net/route.h>
#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;
}