src/sbin/slaacd/frontend.c

1138 lines
28 KiB
C

/* $OpenBSD: frontend.c,v 1.65 2023/12/14 09:58:59 claudio Exp $ */
/*
* Copyright (c) 2017 Florian Obser <florian@openbsd.org>
* Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2004 Esben Norby <norby@openbsd.org>
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/uio.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet6/nd6.h>
#include <netinet6/in6_var.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet/icmp6.h>
#include <errno.h>
#include <event.h>
#include <ifaddrs.h>
#include <imsg.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "log.h"
#include "slaacd.h"
#include "frontend.h"
#include "control.h"
#define ROUTE_SOCKET_BUF_SIZE 16384
#define ALLROUTER "ff02::2"
struct icmp6_ev {
struct event ev;
uint8_t answer[1500];
struct msghdr rcvmhdr;
struct iovec rcviov[1];
struct sockaddr_in6 from;
int refcnt;
};
struct iface {
LIST_ENTRY(iface) entries;
struct icmp6_ev *icmp6ev;
struct ether_addr hw_address;
uint32_t if_index;
int rdomain;
int send_solicitation;
int ll_tentative;
};
__dead void frontend_shutdown(void);
void frontend_sig_handler(int, short, void *);
void update_iface(uint32_t, char*);
void frontend_startup(void);
void route_receive(int, short, void *);
void handle_route_message(struct rt_msghdr *, struct sockaddr **);
void get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
void icmp6_receive(int, short, void *);
int get_flags(char *);
int get_xflags(char *);
int get_ifrdomain(char *);
struct iface *get_iface_by_id(uint32_t);
void remove_iface(uint32_t);
struct icmp6_ev *get_icmp6ev_by_rdomain(int);
void unref_icmp6ev(struct iface *);
void set_icmp6sock(int, int);
void send_solicitation(uint32_t);
#ifndef SMALL
const char *flags_to_str(int);
#endif /* SMALL */
LIST_HEAD(, iface) interfaces;
static struct imsgev *iev_main;
static struct imsgev *iev_engine;
struct event ev_route;
struct msghdr sndmhdr;
struct iovec sndiov[4];
struct nd_router_solicit rs;
struct nd_opt_hdr nd_opt_hdr;
struct ether_addr nd_opt_source_link_addr;
struct sockaddr_in6 dst;
int ioctlsock;
void
frontend_sig_handler(int sig, short event, void *bula)
{
/*
* Normal signal handler rules don't apply because libevent
* decouples for us.
*/
switch (sig) {
case SIGINT:
case SIGTERM:
frontend_shutdown();
default:
fatalx("unexpected signal");
}
}
void
frontend(int debug, int verbose)
{
struct event ev_sigint, ev_sigterm;
struct passwd *pw;
struct in6_pktinfo *pi;
struct cmsghdr *cm;
size_t sndcmsglen;
int hoplimit = 255;
uint8_t *sndcmsgbuf;
log_init(debug, LOG_DAEMON);
log_setverbose(verbose);
if ((pw = getpwnam(SLAACD_USER)) == NULL)
fatal("getpwnam");
if (chdir("/") == -1)
fatal("chdir(\"/\")");
if (unveil("/", "") == -1)
fatal("unveil /");
if (unveil(NULL, NULL) == -1)
fatal("unveil");
setproctitle("%s", "frontend");
log_procinit("frontend");
if ((ioctlsock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)) == -1)
fatal("socket");
if (setgroups(1, &pw->pw_gid) ||
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
fatal("can't drop privileges");
if (pledge("stdio unix recvfd route", NULL) == -1)
fatal("pledge");
event_init();
/* Setup signal handler. */
signal_set(&ev_sigint, SIGINT, frontend_sig_handler, NULL);
signal_set(&ev_sigterm, SIGTERM, frontend_sig_handler, NULL);
signal_add(&ev_sigint, NULL);
signal_add(&ev_sigterm, NULL);
signal(SIGPIPE, SIG_IGN);
signal(SIGHUP, SIG_IGN);
/* Setup pipe and event handler to the parent process. */
if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
fatal(NULL);
imsg_init(&iev_main->ibuf, 3);
iev_main->handler = frontend_dispatch_main;
iev_main->events = EV_READ;
event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
iev_main->handler, iev_main);
event_add(&iev_main->ev, NULL);
sndcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
CMSG_SPACE(sizeof(int));
if ((sndcmsgbuf = malloc(sndcmsglen)) == NULL)
fatal("malloc");
rs.nd_rs_type = ND_ROUTER_SOLICIT;
rs.nd_rs_code = 0;
rs.nd_rs_cksum = 0;
rs.nd_rs_reserved = 0;
nd_opt_hdr.nd_opt_type = ND_OPT_SOURCE_LINKADDR;
nd_opt_hdr.nd_opt_len = 1;
memset(&dst, 0, sizeof(dst));
dst.sin6_family = AF_INET6;
if (inet_pton(AF_INET6, ALLROUTER, &dst.sin6_addr.s6_addr) != 1)
fatal("inet_pton");
sndmhdr.msg_namelen = sizeof(struct sockaddr_in6);
sndmhdr.msg_iov = sndiov;
sndmhdr.msg_iovlen = 3;
sndmhdr.msg_control = (caddr_t)sndcmsgbuf;
sndmhdr.msg_controllen = sndcmsglen;
sndmhdr.msg_name = (caddr_t)&dst;
sndmhdr.msg_iov[0].iov_base = (caddr_t)&rs;
sndmhdr.msg_iov[0].iov_len = sizeof(rs);
sndmhdr.msg_iov[1].iov_base = (caddr_t)&nd_opt_hdr;
sndmhdr.msg_iov[1].iov_len = sizeof(nd_opt_hdr);
sndmhdr.msg_iov[2].iov_base = (caddr_t)&nd_opt_source_link_addr;
sndmhdr.msg_iov[2].iov_len = sizeof(nd_opt_source_link_addr);
cm = CMSG_FIRSTHDR(&sndmhdr);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_PKTINFO;
cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
pi = (struct in6_pktinfo *)CMSG_DATA(cm);
memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr));
pi->ipi6_ifindex = 0;
cm = CMSG_NXTHDR(&sndmhdr, cm);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_HOPLIMIT;
cm->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int));
LIST_INIT(&interfaces);
event_dispatch();
frontend_shutdown();
}
__dead void
frontend_shutdown(void)
{
/* Close pipes. */
msgbuf_write(&iev_engine->ibuf.w);
msgbuf_clear(&iev_engine->ibuf.w);
close(iev_engine->ibuf.fd);
msgbuf_write(&iev_main->ibuf.w);
msgbuf_clear(&iev_main->ibuf.w);
close(iev_main->ibuf.fd);
free(iev_engine);
free(iev_main);
log_info("frontend exiting");
exit(0);
}
int
frontend_imsg_compose_main(int type, pid_t pid, void *data,
uint16_t datalen)
{
return (imsg_compose_event(iev_main, type, 0, pid, -1, data,
datalen));
}
int
frontend_imsg_compose_engine(int type, uint32_t peerid, pid_t pid,
void *data, uint16_t datalen)
{
return (imsg_compose_event(iev_engine, type, peerid, pid, -1,
data, datalen));
}
void
frontend_dispatch_main(int fd, short event, void *bula)
{
struct imsg imsg;
struct imsgev *iev = bula;
struct imsgbuf *ibuf = &iev->ibuf;
ssize_t n;
int shut = 0, icmp6sock, rdomain;
if (event & EV_READ) {
if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
fatal("imsg_read error");
if (n == 0) /* Connection closed. */
shut = 1;
}
if (event & EV_WRITE) {
if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
fatal("msgbuf_write");
if (n == 0) /* Connection closed. */
shut = 1;
}
for (;;) {
if ((n = imsg_get(ibuf, &imsg)) == -1)
fatal("%s: imsg_get error", __func__);
if (n == 0) /* No more messages. */
break;
switch (imsg.hdr.type) {
case IMSG_SOCKET_IPC:
/*
* Setup pipe and event handler to the engine
* process.
*/
if (iev_engine)
fatalx("%s: received unexpected imsg fd "
"to frontend", __func__);
if ((fd = imsg_get_fd(&imsg)) == -1)
fatalx("%s: expected to receive imsg fd to "
"frontend but didn't receive any",
__func__);
iev_engine = malloc(sizeof(struct imsgev));
if (iev_engine == NULL)
fatal(NULL);
imsg_init(&iev_engine->ibuf, fd);
iev_engine->handler = frontend_dispatch_engine;
iev_engine->events = EV_READ;
event_set(&iev_engine->ev, iev_engine->ibuf.fd,
iev_engine->events, iev_engine->handler, iev_engine);
event_add(&iev_engine->ev, NULL);
break;
case IMSG_ICMP6SOCK:
if ((icmp6sock = imsg_get_fd(&imsg)) == -1)
fatalx("%s: expected to receive imsg "
"ICMPv6 fd but didn't receive any",
__func__);
if (IMSG_DATA_SIZE(imsg) != sizeof(rdomain))
fatalx("%s: IMSG_ICMP6SOCK wrong length: "
"%lu", __func__, IMSG_DATA_SIZE(imsg));
memcpy(&rdomain, imsg.data, sizeof(rdomain));
set_icmp6sock(icmp6sock, rdomain);
break;
case IMSG_ROUTESOCK:
if ((fd = imsg_get_fd(&imsg)) == -1)
fatalx("%s: expected to receive imsg "
"routesocket fd but didn't receive any",
__func__);
event_set(&ev_route, fd, EV_READ | EV_PERSIST,
route_receive, NULL);
break;
case IMSG_STARTUP:
frontend_startup();
break;
#ifndef SMALL
case IMSG_CONTROLFD:
if ((fd = imsg_get_fd(&imsg)) == -1)
fatalx("%s: expected to receive imsg "
"control fd but didn't receive any",
__func__);
/* Listen on control socket. */
control_listen(fd);
break;
case IMSG_CTL_END:
control_imsg_relay(&imsg);
break;
#endif /* SMALL */
default:
log_debug("%s: error handling imsg %d", __func__,
imsg.hdr.type);
break;
}
imsg_free(&imsg);
}
if (!shut)
imsg_event_add(iev);
else {
/* This pipe is dead. Remove its event handler. */
event_del(&iev->ev);
event_loopexit(NULL);
}
}
void
frontend_dispatch_engine(int fd, short event, void *bula)
{
struct imsgev *iev = bula;
struct imsgbuf *ibuf = &iev->ibuf;
struct imsg imsg;
ssize_t n;
int shut = 0;
uint32_t if_index;
if (event & EV_READ) {
if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
fatal("imsg_read error");
if (n == 0) /* Connection closed. */
shut = 1;
}
if (event & EV_WRITE) {
if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
fatal("msgbuf_write");
if (n == 0) /* Connection closed. */
shut = 1;
}
for (;;) {
if ((n = imsg_get(ibuf, &imsg)) == -1)
fatal("%s: imsg_get error", __func__);
if (n == 0) /* No more messages. */
break;
switch (imsg.hdr.type) {
#ifndef SMALL
case IMSG_CTL_END:
case IMSG_CTL_SHOW_INTERFACE_INFO:
case IMSG_CTL_SHOW_INTERFACE_INFO_RA:
case IMSG_CTL_SHOW_INTERFACE_INFO_RA_PREFIX:
case IMSG_CTL_SHOW_INTERFACE_INFO_RA_RDNS:
case IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSALS:
case IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL:
case IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS:
case IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL:
case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSALS:
case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSAL:
control_imsg_relay(&imsg);
break;
#endif /* SMALL */
case IMSG_CTL_SEND_SOLICITATION:
if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
fatalx("%s: IMSG_CTL_SEND_SOLICITATION wrong "
"length: %lu", __func__,
IMSG_DATA_SIZE(imsg));
if_index = *((uint32_t *)imsg.data);
send_solicitation(if_index);
break;
default:
log_debug("%s: error handling imsg %d", __func__,
imsg.hdr.type);
break;
}
imsg_free(&imsg);
}
if (!shut)
imsg_event_add(iev);
else {
/* This pipe is dead. Remove its event handler. */
event_del(&iev->ev);
event_loopexit(NULL);
}
}
int
get_flags(char *if_name)
{
struct ifreq ifr;
strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
if (ioctl(ioctlsock, SIOCGIFFLAGS, (caddr_t)&ifr) == -1) {
log_warn("SIOCGIFFLAGS");
return -1;
}
return ifr.ifr_flags;
}
int
get_xflags(char *if_name)
{
struct ifreq ifr;
strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
if (ioctl(ioctlsock, SIOCGIFXFLAGS, (caddr_t)&ifr) == -1) {
log_warn("SIOCGIFXFLAGS");
return -1;
}
return ifr.ifr_flags;
}
int
get_ifrdomain(char *if_name)
{
struct ifreq ifr;
strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
if (ioctl(ioctlsock, SIOCGIFRDOMAIN, (caddr_t)&ifr) == -1) {
log_warn("SIOCGIFRDOMAIN");
return -1;
}
return ifr.ifr_rdomainid;
}
void
update_iface(uint32_t if_index, char* if_name)
{
struct iface *iface;
struct ifaddrs *ifap, *ifa;
struct imsg_ifinfo imsg_ifinfo;
struct sockaddr_dl *sdl;
struct sockaddr_in6 *sin6;
struct in6_ifreq ifr6;
int flags, xflags, ifrdomain;
if ((flags = get_flags(if_name)) == -1 || (xflags =
get_xflags(if_name)) == -1)
return;
if (!(xflags & (IFXF_AUTOCONF6 | IFXF_AUTOCONF6TEMP)))
return;
if((ifrdomain = get_ifrdomain(if_name)) == -1)
return;
iface = get_iface_by_id(if_index);
if (iface != NULL) {
if (iface->rdomain != ifrdomain) {
unref_icmp6ev(iface);
iface->rdomain = ifrdomain;
iface->icmp6ev = get_icmp6ev_by_rdomain(ifrdomain);
}
} else {
if ((iface = calloc(1, sizeof(*iface))) == NULL)
fatal("calloc");
iface->if_index = if_index;
iface->rdomain = ifrdomain;
iface->icmp6ev = get_icmp6ev_by_rdomain(ifrdomain);
iface->ll_tentative = 1;
LIST_INSERT_HEAD(&interfaces, iface, entries);
}
memset(&imsg_ifinfo, 0, sizeof(imsg_ifinfo));
imsg_ifinfo.if_index = if_index;
imsg_ifinfo.rdomain = ifrdomain;
imsg_ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP |
IFF_RUNNING);
imsg_ifinfo.autoconf = (xflags & IFXF_AUTOCONF6);
imsg_ifinfo.temporary = (xflags & IFXF_AUTOCONF6TEMP);
imsg_ifinfo.soii = !(xflags & IFXF_INET6_NOSOII);
if (getifaddrs(&ifap) != 0)
fatal("getifaddrs");
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
if (strcmp(if_name, ifa->ifa_name) != 0)
continue;
if (ifa->ifa_addr == NULL)
continue;
switch(ifa->ifa_addr->sa_family) {
case AF_LINK:
imsg_ifinfo.link_state =
((struct if_data *)ifa->ifa_data)->ifi_link_state;
sdl = (struct sockaddr_dl *)ifa->ifa_addr;
if (sdl->sdl_type != IFT_ETHER ||
sdl->sdl_alen != ETHER_ADDR_LEN)
continue;
memcpy(iface->hw_address.ether_addr_octet,
LLADDR(sdl), ETHER_ADDR_LEN);
memcpy(imsg_ifinfo.hw_address.ether_addr_octet,
LLADDR(sdl), ETHER_ADDR_LEN);
case AF_INET6:
sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
#ifdef __KAME__
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) &&
sin6->sin6_scope_id == 0) {
sin6->sin6_scope_id = ntohs(*(u_int16_t *)
&sin6->sin6_addr.s6_addr[2]);
sin6->sin6_addr.s6_addr[2] =
sin6->sin6_addr.s6_addr[3] = 0;
}
#endif
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
memcpy(&imsg_ifinfo.ll_address, sin6,
sizeof(imsg_ifinfo.ll_address));
if (!iface->ll_tentative)
break;
memset(&ifr6, 0, sizeof(ifr6));
strlcpy(ifr6.ifr_name, if_name,
sizeof(ifr6.ifr_name));
memcpy(&ifr6.ifr_addr, sin6,
sizeof(ifr6.ifr_addr));
if (ioctl(ioctlsock, SIOCGIFAFLAG_IN6,
(caddr_t)&ifr6) == -1) {
log_warn("SIOCGIFAFLAG_IN6");
break;
}
if (!(ifr6.ifr_ifru.ifru_flags6 &
IN6_IFF_TENTATIVE)) {
iface->ll_tentative = 0;
if (iface->send_solicitation)
send_solicitation(
iface->if_index);
}
}
break;
default:
break;
}
}
freeifaddrs(ifap);
frontend_imsg_compose_main(IMSG_UPDATE_IF, 0, &imsg_ifinfo,
sizeof(imsg_ifinfo));
}
#ifndef SMALL
const char*
flags_to_str(int flags)
{
static char buf[sizeof(" anycast tentative duplicated detached "
"deprecated autoconf temporary")];
buf[0] = '\0';
if (flags & IN6_IFF_ANYCAST)
strlcat(buf, " anycast", sizeof(buf));
if (flags & IN6_IFF_TENTATIVE)
strlcat(buf, " tentative", sizeof(buf));
if (flags & IN6_IFF_DUPLICATED)
strlcat(buf, " duplicated", sizeof(buf));
if (flags & IN6_IFF_DETACHED)
strlcat(buf, " detached", sizeof(buf));
if (flags & IN6_IFF_DEPRECATED)
strlcat(buf, " deprecated", sizeof(buf));
if (flags & IN6_IFF_AUTOCONF)
strlcat(buf, " autoconf", sizeof(buf));
if (flags & IN6_IFF_TEMPORARY)
strlcat(buf, " temporary", sizeof(buf));
return (buf);
}
#endif /* SMALL */
void
frontend_startup(void)
{
struct if_nameindex *ifnidxp, *ifnidx;
if (!event_initialized(&ev_route))
fatalx("%s: did not receive a route socket from the main "
"process", __func__);
event_add(&ev_route, NULL);
if ((ifnidxp = if_nameindex()) == NULL)
fatalx("if_nameindex");
for(ifnidx = ifnidxp; ifnidx->if_index !=0 && ifnidx->if_name != NULL;
ifnidx++)
update_iface(ifnidx->if_index, ifnidx->if_name);
if_freenameindex(ifnidxp);
}
void
route_receive(int fd, short events, void *arg)
{
static uint8_t *buf;
struct rt_msghdr *rtm;
struct sockaddr *sa, *rti_info[RTAX_MAX];
ssize_t n;
if (buf == NULL) {
buf = malloc(ROUTE_SOCKET_BUF_SIZE);
if (buf == NULL)
fatal("malloc");
}
rtm = (struct rt_msghdr *)buf;
if ((n = read(fd, buf, ROUTE_SOCKET_BUF_SIZE)) == -1) {
if (errno == EAGAIN || errno == EINTR)
return;
log_warn("dispatch_rtmsg: read error");
return;
}
if (n == 0)
fatal("routing socket closed");
if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) {
log_warnx("partial rtm of %zd in buffer", n);
return;
}
if (rtm->rtm_version != RTM_VERSION)
return;
sa = (struct sockaddr *)(buf + rtm->rtm_hdrlen);
get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
handle_route_message(rtm, rti_info);
}
void
handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info)
{
struct if_msghdr *ifm;
struct if_announcemsghdr *ifan;
struct imsg_del_addr del_addr;
struct imsg_del_route del_route;
struct imsg_dup_addr dup_addr;
struct sockaddr_rtlabel *rl;
struct sockaddr_in6 *sin6;
struct in6_ifreq ifr6;
struct in6_addr *in6;
int xflags, if_index;
char ifnamebuf[IFNAMSIZ];
char *if_name;
switch (rtm->rtm_type) {
case RTM_IFINFO:
ifm = (struct if_msghdr *)rtm;
if_index = ifm->ifm_index;
if_name = if_indextoname(if_index, ifnamebuf);
if (if_name == NULL) {
log_debug("RTM_IFINFO: lost if %d", if_index);
frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
&if_index, sizeof(if_index));
remove_iface(if_index);
break;
}
xflags = get_xflags(if_name);
if (xflags == -1 || !(xflags & (IFXF_AUTOCONF6 |
IFXF_AUTOCONF6TEMP))) {
log_debug("RTM_IFINFO: %s(%d) no(longer) autoconf6",
if_name, if_index);
frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0,
0, &if_index, sizeof(if_index));
remove_iface(if_index);
} else {
update_iface(if_index, if_name);
}
break;
case RTM_IFANNOUNCE:
ifan = (struct if_announcemsghdr *)rtm;
if_index = ifan->ifan_index;
if (ifan->ifan_what == IFAN_DEPARTURE) {
frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
&if_index, sizeof(if_index));
remove_iface(if_index);
}
break;
case RTM_NEWADDR:
ifm = (struct if_msghdr *)rtm;
if_index = ifm->ifm_index;
if_name = if_indextoname(if_index, ifnamebuf);
if (if_name == NULL) {
log_debug("RTM_NEWADDR: lost if %d", if_index);
frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
&if_index, sizeof(if_index));
remove_iface(if_index);
break;
}
log_debug("RTM_NEWADDR: %s[%u]", if_name, if_index);
update_iface(if_index, if_name);
break;
case RTM_DELADDR:
ifm = (struct if_msghdr *)rtm;
if_index = ifm->ifm_index;
if_name = if_indextoname(if_index, ifnamebuf);
if (if_name == NULL) {
log_debug("RTM_DELADDR: lost if %d", if_index);
frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
&if_index, sizeof(if_index));
remove_iface(if_index);
break;
}
if (rtm->rtm_addrs & RTA_IFA && rti_info[RTAX_IFA]->sa_family
== AF_INET6) {
del_addr.if_index = if_index;
memcpy(&del_addr.addr, rti_info[RTAX_IFA], sizeof(
del_addr.addr));
frontend_imsg_compose_engine(IMSG_DEL_ADDRESS,
0, 0, &del_addr, sizeof(del_addr));
log_debug("RTM_DELADDR: %s[%u]", if_name, if_index);
}
break;
case RTM_CHGADDRATTR:
ifm = (struct if_msghdr *)rtm;
if_index = ifm->ifm_index;
if_name = if_indextoname(if_index, ifnamebuf);
if (if_name == NULL) {
log_debug("RTM_CHGADDRATTR: lost if %d", if_index);
frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
&if_index, sizeof(if_index));
remove_iface(if_index);
break;
}
if (rtm->rtm_addrs & RTA_IFA && rti_info[RTAX_IFA]->sa_family
== AF_INET6) {
sin6 = (struct sockaddr_in6 *) rti_info[RTAX_IFA];
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
update_iface(if_index, if_name);
break;
}
memset(&ifr6, 0, sizeof(ifr6));
strlcpy(ifr6.ifr_name, if_name, sizeof(ifr6.ifr_name));
memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr));
if (ioctl(ioctlsock, SIOCGIFAFLAG_IN6, (caddr_t)&ifr6)
== -1) {
log_warn("SIOCGIFAFLAG_IN6");
break;
}
#ifndef SMALL
log_debug("RTM_CHGADDRATTR: %s - %s",
sin6_to_str(sin6),
flags_to_str(ifr6.ifr_ifru.ifru_flags6));
#endif /* SMALL */
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DUPLICATED) {
dup_addr.if_index = if_index;
dup_addr.addr = *sin6;
frontend_imsg_compose_engine(IMSG_DUP_ADDRESS,
0, 0, &dup_addr, sizeof(dup_addr));
}
}
break;
case RTM_DELETE:
ifm = (struct if_msghdr *)rtm;
if ((rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY | RTA_LABEL)) !=
(RTA_DST | RTA_GATEWAY | RTA_LABEL))
break;
if (rti_info[RTAX_DST]->sa_family != AF_INET6)
break;
if (!IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)
rti_info[RTAX_DST])->sin6_addr))
break;
if (rti_info[RTAX_GATEWAY]->sa_family != AF_INET6)
break;
if (rti_info[RTAX_LABEL]->sa_len !=
sizeof(struct sockaddr_rtlabel))
break;
rl = (struct sockaddr_rtlabel *)rti_info[RTAX_LABEL];
if (strcmp(rl->sr_label, SLAACD_RTA_LABEL) != 0)
break;
if_index = ifm->ifm_index;
if_name = if_indextoname(if_index, ifnamebuf);
if (if_name == NULL) {
log_debug("RTM_DELETE: lost if %d", if_index);
frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0,
&if_index, sizeof(if_index));
remove_iface(if_index);
break;
}
del_route.if_index = if_index;
memcpy(&del_route.gw, rti_info[RTAX_GATEWAY],
sizeof(del_route.gw));
in6 = &del_route.gw.sin6_addr;
#ifdef __KAME__
/* XXX from route(8) p_sockaddr() */
if ((IN6_IS_ADDR_LINKLOCAL(in6) ||
IN6_IS_ADDR_MC_LINKLOCAL(in6) ||
IN6_IS_ADDR_MC_INTFACELOCAL(in6)) &&
del_route.gw.sin6_scope_id == 0) {
del_route.gw.sin6_scope_id =
(u_int32_t)ntohs(*(u_short *) &in6->s6_addr[2]);
*(u_short *)&in6->s6_addr[2] = 0;
}
#endif
frontend_imsg_compose_engine(IMSG_DEL_ROUTE,
0, 0, &del_route, sizeof(del_route));
log_debug("RTM_DELETE: %s[%u]", if_name,
ifm->ifm_index);
break;
#ifndef SMALL
case RTM_PROPOSAL:
if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
log_debug("RTP_PROPOSAL_SOLICIT");
frontend_imsg_compose_engine(IMSG_REPROPOSE_RDNS,
0, 0, NULL, 0);
}
break;
#endif /* SMALL */
default:
log_debug("unexpected RTM: %d", rtm->rtm_type);
break;
}
}
#define ROUNDUP(a) \
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
void
get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
{
int i;
for (i = 0; i < RTAX_MAX; i++) {
if (addrs & (1 << i)) {
rti_info[i] = sa;
sa = (struct sockaddr *)((char *)(sa) +
ROUNDUP(sa->sa_len));
} else
rti_info[i] = NULL;
}
}
void
icmp6_receive(int fd, short events, void *arg)
{
struct imsg_ra ra;
struct icmp6_hdr *icmp6_hdr;
struct icmp6_ev *icmp6ev;
struct in6_pktinfo *pi = NULL;
struct cmsghdr *cm;
ssize_t len;
int if_index = 0, *hlimp = NULL;
char ntopbuf[INET6_ADDRSTRLEN];
#ifndef SMALL
char ifnamebuf[IFNAMSIZ];
#endif /* SMALL */
icmp6ev = arg;
if ((len = recvmsg(fd, &icmp6ev->rcvmhdr, 0)) == -1) {
log_warn("recvmsg");
return;
}
if ((size_t)len < sizeof(struct icmp6_hdr))
return;
icmp6_hdr = (struct icmp6_hdr *)icmp6ev->answer;
if (icmp6_hdr->icmp6_type != ND_ROUTER_ADVERT)
return;
/* extract optional information via Advanced API */
for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&icmp6ev->rcvmhdr); cm;
cm = (struct cmsghdr *)CMSG_NXTHDR(&icmp6ev->rcvmhdr, cm)) {
if (cm->cmsg_level == IPPROTO_IPV6 &&
cm->cmsg_type == IPV6_PKTINFO &&
cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
pi = (struct in6_pktinfo *)(CMSG_DATA(cm));
if_index = pi->ipi6_ifindex;
}
if (cm->cmsg_level == IPPROTO_IPV6 &&
cm->cmsg_type == IPV6_HOPLIMIT &&
cm->cmsg_len == CMSG_LEN(sizeof(int)))
hlimp = (int *)CMSG_DATA(cm);
}
if (if_index == 0) {
log_warnx("failed to get receiving interface");
return;
}
if (hlimp == NULL) {
log_warnx("failed to get receiving hop limit");
return;
}
if (*hlimp != 255) {
log_warnx("invalid RA with hop limit of %d from %s on %s",
*hlimp, inet_ntop(AF_INET6, &icmp6ev->from.sin6_addr,
ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index,
ifnamebuf));
return;
}
if ((size_t)len > sizeof(ra.packet)) {
log_warnx("invalid RA with size %ld from %s on %s",
len, inet_ntop(AF_INET6, &icmp6ev->from.sin6_addr,
ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index,
ifnamebuf));
return;
}
ra.if_index = if_index;
memcpy(&ra.from, &icmp6ev->from, sizeof(ra.from));
ra.len = len;
memcpy(ra.packet, icmp6ev->answer, len);
frontend_imsg_compose_engine(IMSG_RA, 0, 0, &ra, sizeof(ra));
}
void
send_solicitation(uint32_t if_index)
{
struct in6_pktinfo *pi;
struct cmsghdr *cm;
struct iface *iface;
log_debug("%s(%u)", __func__, if_index);
if ((iface = get_iface_by_id(if_index)) == NULL)
return;
if (!event_initialized(&iface->icmp6ev->ev)) {
iface->send_solicitation = 1;
return;
} else if (iface->ll_tentative) {
iface->send_solicitation = 1;
return;
}
iface->send_solicitation = 0;
dst.sin6_scope_id = if_index;
cm = CMSG_FIRSTHDR(&sndmhdr);
pi = (struct in6_pktinfo *)CMSG_DATA(cm);
pi->ipi6_ifindex = if_index;
memcpy(&nd_opt_source_link_addr, &iface->hw_address,
sizeof(nd_opt_source_link_addr));
if (sendmsg(EVENT_FD(&iface->icmp6ev->ev), &sndmhdr, 0) != sizeof(rs) +
sizeof(nd_opt_hdr) + sizeof(nd_opt_source_link_addr))
log_warn("sendmsg");
}
struct iface*
get_iface_by_id(uint32_t if_index)
{
struct iface *iface;
LIST_FOREACH (iface, &interfaces, entries) {
if (iface->if_index == if_index)
return (iface);
}
return (NULL);
}
void
remove_iface(uint32_t if_index)
{
struct iface *iface;
iface = get_iface_by_id(if_index);
if (iface == NULL)
return;
LIST_REMOVE(iface, entries);
unref_icmp6ev(iface);
free(iface);
}
struct icmp6_ev*
get_icmp6ev_by_rdomain(int rdomain)
{
struct iface *iface;
struct icmp6_ev *icmp6ev = NULL;
LIST_FOREACH (iface, &interfaces, entries) {
if (iface->rdomain == rdomain) {
icmp6ev = iface->icmp6ev;
break;
}
}
if (icmp6ev == NULL) {
if ((icmp6ev = calloc(1, sizeof(*icmp6ev))) == NULL)
fatal("calloc");
icmp6ev->rcviov[0].iov_base = (caddr_t)icmp6ev->answer;
icmp6ev->rcviov[0].iov_len = sizeof(icmp6ev->answer);
icmp6ev->rcvmhdr.msg_name = (caddr_t)&icmp6ev->from;
icmp6ev->rcvmhdr.msg_namelen = sizeof(icmp6ev->from);
icmp6ev->rcvmhdr.msg_iov = icmp6ev->rcviov;
icmp6ev->rcvmhdr.msg_iovlen = 1;
icmp6ev->rcvmhdr.msg_controllen =
CMSG_SPACE(sizeof(struct in6_pktinfo)) +
CMSG_SPACE(sizeof(int));
if ((icmp6ev->rcvmhdr.msg_control = malloc(icmp6ev->
rcvmhdr.msg_controllen)) == NULL)
fatal("malloc");
frontend_imsg_compose_main(IMSG_OPEN_ICMP6SOCK, 0,
&rdomain, sizeof(rdomain));
}
icmp6ev->refcnt++;
return (icmp6ev);
}
void
unref_icmp6ev(struct iface *iface)
{
struct icmp6_ev *icmp6ev = iface->icmp6ev;
iface->icmp6ev = NULL;
if (icmp6ev != NULL) {
icmp6ev->refcnt--;
if (icmp6ev->refcnt == 0) {
event_del(&icmp6ev->ev);
close(EVENT_FD(&icmp6ev->ev));
free(icmp6ev);
}
}
}
void
set_icmp6sock(int icmp6sock, int rdomain)
{
struct iface *iface;
LIST_FOREACH (iface, &interfaces, entries) {
if (!event_initialized(&iface->icmp6ev->ev) && iface->rdomain
== rdomain) {
event_set(&iface->icmp6ev->ev, icmp6sock, EV_READ |
EV_PERSIST, icmp6_receive, iface->icmp6ev);
event_add(&iface->icmp6ev->ev, NULL);
icmp6sock = -1;
break;
}
}
if (icmp6sock != -1) {
/*
* The interface disappeared or changed rdomain while we were
* waiting for the parent process to open the raw socket.
*/
close(icmp6sock);
return;
}
LIST_FOREACH (iface, &interfaces, entries) {
if (event_initialized(&iface->icmp6ev->ev) &&
iface->send_solicitation)
send_solicitation(iface->if_index);
}
}