ndppd/src/iface.c

523 lines
14 KiB
C
Raw Normal View History

2019-12-11 11:34:34 +01:00
/*
* This file is part of ndppd.
*
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
*
* ndppd 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.
*
* ndppd 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 ndppd. If not, see <https://www.gnu.org/licenses/>.
*/
#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>
#include <netinet/ip6.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "addr.h"
#include "iface.h"
#include "ndppd.h"
#include "neigh.h"
#include "proxy.h"
#include "sio.h"
extern int nd_conf_invalid_ttl;
extern int nd_conf_valid_ttl;
extern int nd_conf_renew;
extern int nd_conf_retrans_limit;
extern int nd_conf_retrans_time;
extern bool nd_conf_keepalive;
static nd_iface_t *ndL_first_iface, *ndL_first_free_iface;
bool nd_iface_no_restore_flags;
typedef struct
{
struct ip6_hdr ip6_hdr;
struct icmp6_hdr icmp6_hdr;
} ndL_icmp6_msg_t;
static void ndL_handle_ns(nd_iface_t *ifa, ndL_icmp6_msg_t *msg)
{
struct nd_neighbor_solicit *ns = (struct nd_neighbor_solicit *)&msg->icmp6_hdr;
if (msg->ip6_hdr.ip6_plen < sizeof(struct nd_neighbor_solicit))
return;
/* TODO: We need to properly parse options here. */
size_t optlen = ntohs(msg->ip6_hdr.ip6_plen) - sizeof(struct nd_neighbor_solicit);
if (optlen < 8)
return;
struct nd_opt_hdr *opt = (struct nd_opt_hdr *)((void *)ns + sizeof(struct nd_neighbor_solicit));
if (opt->nd_opt_len != 1 || opt->nd_opt_type != ND_OPT_SOURCE_LINKADDR)
return;
uint8_t *lladdr = (uint8_t *)((void *)opt + 2);
if (ifa->proxy)
nd_proxy_handle_ns(ifa->proxy, &msg->ip6_hdr.ip6_src, &msg->ip6_hdr.ip6_dst, &ns->nd_ns_target, lladdr);
}
static void ndL_handle_na(nd_iface_t *iface, ndL_icmp6_msg_t *msg)
{
if (msg->ip6_hdr.ip6_plen < sizeof(struct nd_neighbor_advert))
return;
struct nd_neighbor_advert *na = (struct nd_neighbor_advert *)&msg->icmp6_hdr;
nd_neigh_t *neigh;
ND_LL_SEARCH(iface->neighs, neigh, next_in_iface, nd_addr_eq(&neigh->tgt, &na->nd_na_target));
if (!neigh)
return;
neigh->state = ND_STATE_VALID;
neigh->ttl = nd_conf_valid_ttl;
neigh->touched_at = nd_current_time;
}
static uint16_t ndL_calculate_checksum(uint32_t sum, const void *data, size_t length)
{
uint8_t *p = (uint8_t *)data;
for (size_t i = 0; i < length; i += 2)
{
if (i + 1 < length)
sum += ntohs(*(uint16_t *)p), p += 2;
else
sum += *p++;
if (sum > 0xffff)
sum -= 0xffff;
}
return sum;
}
static uint16_t ndL_calculate_icmp6_checksum(ndL_icmp6_msg_t *msg, size_t size)
{
/* IPv6 pseudo-header. */
struct __attribute__((packed))
{
struct in6_addr src;
struct in6_addr dst;
uint32_t len;
uint8_t unused[3];
uint8_t type;
struct icmp6_hdr icmp6_hdr;
} hdr;
hdr.src = msg->ip6_hdr.ip6_src;
hdr.dst = msg->ip6_hdr.ip6_dst;
hdr.len = htonl(size - sizeof(struct ip6_hdr));
hdr.unused[0] = 0;
hdr.unused[1] = 0;
hdr.unused[2] = 0;
hdr.type = IPPROTO_ICMPV6;
hdr.icmp6_hdr = msg->icmp6_hdr;
hdr.icmp6_hdr.icmp6_cksum = 0;
uint16_t sum;
sum = ndL_calculate_checksum(0xffff, &hdr, sizeof(hdr));
sum = ndL_calculate_checksum(sum, (void *)msg + sizeof(ndL_icmp6_msg_t), size - sizeof(ndL_icmp6_msg_t));
return htons(~sum);
}
static void ndL_handle_packet(nd_iface_t *iface, uint8_t *buf, size_t buflen)
{
ndL_icmp6_msg_t *msg = (ndL_icmp6_msg_t *)buf;
if ((size_t)buflen < sizeof(ndL_icmp6_msg_t))
/* TODO: log. Invalid length. */
return;
if ((size_t)buflen != sizeof(struct ip6_hdr) + ntohs(msg->ip6_hdr.ip6_plen))
/* TODO: log. Invalid length. */
return;
if (msg->ip6_hdr.ip6_nxt != IPPROTO_ICMPV6)
/* TODO: log. Invalid next header. */
return;
if (ndL_calculate_icmp6_checksum(msg, buflen) != msg->icmp6_hdr.icmp6_cksum)
/* TODO: log. Invalid checksum. */
return;
/* TODO: Validate checksum, lengths, etc. */
if (msg->icmp6_hdr.icmp6_type == ND_NEIGHBOR_SOLICIT)
ndL_handle_ns(iface, msg);
else if (msg->icmp6_hdr.icmp6_type == ND_NEIGHBOR_ADVERT)
ndL_handle_na(iface, msg);
}
static void ndL_sio_handler(nd_sio_t *sio, __attribute__((unused)) int events)
{
nd_iface_t *ifa = (nd_iface_t *)sio->data;
struct sockaddr_ll lladdr;
memset(&lladdr, 0, sizeof(struct sockaddr_ll));
lladdr.sll_family = AF_PACKET;
lladdr.sll_protocol = htons(ETH_P_IPV6);
lladdr.sll_ifindex = (int)ifa->index;
uint8_t buf[1024];
for (;;)
{
ssize_t len = nd_sio_recv(sio, (struct sockaddr *)&lladdr, sizeof(lladdr), buf, sizeof(buf));
if (len == 0)
return;
if (len < 0)
{
if (errno == EAGAIN)
return;
/* TODO */
return;
}
ndL_handle_packet(ifa, buf, len);
}
}
nd_iface_t *nd_iface_open(const char *name, unsigned int index)
{
char tmp_name[IF_NAMESIZE];
if (!name && !index)
return NULL;
if (name && index && if_nametoindex(name) != index)
{
nd_log_error("Expected interface %s to have index %d", name, index);
return NULL;
}
else if (name && !(index = if_nametoindex(name)))
{
nd_log_error("Failed to get index of interface %s: %s", name, strerror(errno));
return NULL;
}
else if (!(name = if_indextoname(index, tmp_name)))
{
nd_log_error("Failed to get name of interface index %d: %s", index, strerror(errno));
return NULL;
}
/* If the specified interface is already opened, just increase the reference counter. */
nd_iface_t *iface;
ND_LL_SEARCH(ndL_first_iface, iface, next, iface->index == index);
if (iface)
{
iface->refs++;
return iface;
}
/* No such interface. */
nd_sio_t *sio = nd_sio_open(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6));
if (!sio)
{
nd_log_error("Failed to create socket: %s", strerror(errno));
return NULL;
}
/* Determine link-layer address. */
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, name);
if (ioctl(sio->fd, SIOCGIFHWADDR, &ifr) < 0)
{
nd_sio_close(sio);
nd_log_error("Failed to determine link-layer address: %s", strerror(errno));
return NULL;
}
/* 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(sio->fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0)
{
nd_sio_close(sio);
nd_log_error("Failed to configure netfilter: %s", strerror(errno));
return NULL;
}
/* Allocate the nd_ifa_t object. */
iface = ndL_first_free_iface;
if (iface != NULL)
ND_LL_DELETE(ndL_first_free_iface, iface, next);
else
iface = ND_ALLOC(nd_iface_t);
memset(iface, 0, sizeof(nd_iface_t));
ND_LL_PREPEND(ndL_first_iface, iface, next);
iface->sio = sio;
iface->index = index;
iface->refs = 1;
iface->old_allmulti = -1;
iface->old_promisc = -1;
strcpy(iface->name, name);
memcpy(iface->lladdr, ifr.ifr_hwaddr.sa_data, 6);
sio->data = (intptr_t)iface;
sio->handler = ndL_sio_handler;
nd_log_info("New interface %s (%d)", iface->name, iface->index);
return iface;
}
void nd_iface_close(nd_iface_t *iface)
{
if (--iface->refs > 0)
return;
if (!nd_iface_no_restore_flags)
{
if (iface->old_promisc >= 0)
nd_iface_set_promisc(iface, iface->old_promisc);
if (iface->old_allmulti >= 0)
nd_iface_set_allmulti(iface, iface->old_allmulti);
}
nd_sio_close(iface->sio);
ND_LL_DELETE(ndL_first_iface, iface, next);
ND_LL_PREPEND(ndL_first_free_iface, iface, next);
}
void ndL_get_local_addr(nd_iface_t *iface, nd_addr_t *addr)
{
addr->s6_addr[0] = 0xfe;
addr->s6_addr[1] = 0x80;
addr->s6_addr[8] = iface->lladdr[0] ^ 0x02U;
addr->s6_addr[9] = iface->lladdr[1];
addr->s6_addr[10] = iface->lladdr[2];
addr->s6_addr[11] = 0xff;
addr->s6_addr[12] = 0xfe;
addr->s6_addr[13] = iface->lladdr[3];
addr->s6_addr[14] = iface->lladdr[4];
addr->s6_addr[15] = iface->lladdr[5];
}
static ssize_t ndL_send_icmp6(nd_iface_t *ifa, ndL_icmp6_msg_t *msg, size_t size, const uint8_t *hwaddr)
{
assert(size >= sizeof(ndL_icmp6_msg_t));
msg->ip6_hdr.ip6_flow = htonl((6U << 28U) | (0U << 20U) | 0U);
msg->ip6_hdr.ip6_plen = htons(size - sizeof(struct ip6_hdr));
msg->ip6_hdr.ip6_hops = 255;
msg->ip6_hdr.ip6_nxt = IPPROTO_ICMPV6;
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);
return nd_sio_send(ifa->sio, (struct sockaddr *)&addr, sizeof(addr), msg, size);
}
ssize_t nd_iface_write_na(nd_iface_t *iface, nd_addr_t *dst, uint8_t *dst_ll, nd_addr_t *tgt, bool router)
{
struct
{
struct ip6_hdr ip;
struct nd_neighbor_advert na;
struct nd_opt_hdr opt;
uint8_t lladdr[6];
} msg;
memset(&msg, 0, sizeof(msg));
msg.ip.ip6_src = *tgt;
msg.ip.ip6_dst = *dst;
msg.na.nd_na_type = ND_NEIGHBOR_ADVERT;
msg.na.nd_na_target = *tgt;
if (nd_addr_is_multicast(dst))
msg.na.nd_na_flags_reserved |= ND_NA_FLAG_SOLICITED;
if (router)
msg.na.nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
msg.opt.nd_opt_type = ND_OPT_TARGET_LINKADDR;
msg.opt.nd_opt_len = 1;
memcpy(msg.lladdr, iface->lladdr, sizeof(msg.lladdr));
nd_log_info("Write NA tgt=%s, dst=%s [%x:%x:%x:%x:%x:%x dev %s]", nd_addr_to_string(tgt), nd_addr_to_string(dst),
dst_ll[0], dst_ll[1], dst_ll[2], dst_ll[3], dst_ll[4], dst_ll[5], iface->name);
return ndL_send_icmp6(iface, (ndL_icmp6_msg_t *)&msg, sizeof(msg), dst_ll);
}
ssize_t nd_iface_write_ns(nd_iface_t *ifa, nd_addr_t *tgt)
{
struct
{
struct ip6_hdr ip;
struct nd_neighbor_solicit ns;
struct nd_opt_hdr opt;
uint8_t lladdr[6];
} msg;
memset(&msg, 0, sizeof(msg));
msg.ns.nd_ns_type = ND_NEIGHBOR_SOLICIT;
msg.ns.nd_ns_target = *tgt;
msg.opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR;
msg.opt.nd_opt_len = 1;
ndL_get_local_addr(ifa, &msg.ip.ip6_src);
const uint8_t multicast[] = { 255, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 255, 0, 0, 0 };
memcpy(&msg.ip.ip6_dst, multicast, sizeof(struct in6_addr));
msg.ip.ip6_dst.s6_addr[13] = tgt->s6_addr[13];
msg.ip.ip6_dst.s6_addr[14] = tgt->s6_addr[14];
msg.ip.ip6_dst.s6_addr[15] = tgt->s6_addr[15];
memcpy(msg.lladdr, ifa->lladdr, sizeof(msg.lladdr));
uint8_t ll_mcast[6] = { 0x33, 0x33 };
*(uint32_t *)&ll_mcast[2] = tgt->s6_addr32[3];
nd_log_trace("Write NS iface=%s, tgt=%s", ifa->name, nd_addr_to_string(tgt));
return ndL_send_icmp6(ifa, (ndL_icmp6_msg_t *)&msg, sizeof(msg), ll_mcast);
}
bool nd_iface_set_allmulti(nd_iface_t *iface, bool on)
{
nd_log_debug("%s all multicast mode for interface %s", on ? "Enabling" : "Disabling", iface->name);
struct ifreq ifr;
memcpy(ifr.ifr_name, iface->name, IFNAMSIZ);
if (ioctl(iface->sio->fd, SIOCGIFFLAGS, &ifr) < 0)
{
nd_log_error("Failed to get interface flags: %s", strerror(errno));
return false;
}
if (iface->old_allmulti < 0)
iface->old_allmulti = (ifr.ifr_flags & IFF_ALLMULTI) != 0;
if (on == ((ifr.ifr_flags & IFF_ALLMULTI) != 0))
return true;
if (on)
ifr.ifr_flags |= IFF_ALLMULTI;
else
ifr.ifr_flags &= ~IFF_ALLMULTI;
if (ioctl(iface->sio->fd, SIOCSIFFLAGS, &ifr) < 0)
{
nd_log_error("Failed to set interface flags: %s", strerror(errno));
return false;
}
return true;
}
bool nd_iface_set_promisc(nd_iface_t *iface, bool on)
{
nd_log_debug("%s promiscuous mode for interface %s", on ? "Enabling" : "Disabling", iface->name);
struct ifreq ifr;
memcpy(ifr.ifr_name, iface->name, IFNAMSIZ);
if (ioctl(iface->sio->fd, SIOCGIFFLAGS, &ifr) < 0)
{
nd_log_error("Failed to get interface flags: %s", strerror(errno));
return false;
}
if (iface->old_promisc < 0)
iface->old_promisc = (ifr.ifr_flags & IFF_PROMISC) != 0;
if (on == ((ifr.ifr_flags & IFF_PROMISC) != 0))
return true;
if (on)
ifr.ifr_flags |= IFF_PROMISC;
else
ifr.ifr_flags &= ~IFF_PROMISC;
if (ioctl(iface->sio->fd, SIOCSIFFLAGS, &ifr) < 0)
{
nd_log_error("Failed to set interface flags: %s", strerror(errno));
return false;
}
return true;
}
void nd_iface_cleanup()
{
ND_LL_FOREACH_S(ndL_first_iface, iface, tmp, next)
{
/* We're gonna be bad and just ignore refs here as all memory will soon be invalid anyway. */
iface->refs = 1;
nd_iface_close(iface);
}
}