src/libexec/snmpd/snmpd_metrics/kroute.c

1699 lines
37 KiB
C

/* $OpenBSD: kroute.c,v 1.3 2023/03/08 04:43:06 guenther Exp $ */
/*
* Copyright (c) 2007, 2008 Reyk Floeter <reyk@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/socket.h>
#include <sys/sysctl.h>
#include <sys/tree.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <event.h>
#include "snmpd.h"
struct ktable **krt;
u_int krt_size;
struct {
struct event ks_ev;
u_long ks_iflastchange;
u_long ks_nroutes; /* 4 billions enough? */
int ks_fd;
int ks_ifd;
u_short ks_nkif;
} kr_state;
struct kroute_node {
RB_ENTRY(kroute_node) entry;
struct kroute r;
struct kroute_node *next;
};
struct kroute6_node {
RB_ENTRY(kroute6_node) entry;
struct kroute6 r;
struct kroute6_node *next;
};
struct kif_node {
RB_ENTRY(kif_node) entry;
TAILQ_HEAD(, kif_addr) addrs;
TAILQ_HEAD(, kif_arp) arps;
struct kif k;
};
int kroute_compare(struct kroute_node *, struct kroute_node *);
int kroute6_compare(struct kroute6_node *, struct kroute6_node *);
int kif_compare(struct kif_node *, struct kif_node *);
void ktable_init(void);
int ktable_new(u_int, u_int);
void ktable_free(u_int);
int ktable_exists(u_int, u_int *);
struct ktable *ktable_get(u_int);
int ktable_update(u_int);
struct kroute_node *kroute_find(struct ktable *, in_addr_t, u_int8_t,
u_int8_t);
struct kroute_node *kroute_matchgw(struct kroute_node *,
struct sockaddr_in *);
int kroute_insert(struct ktable *, struct kroute_node *);
int kroute_remove(struct ktable *, struct kroute_node *);
void kroute_clear(struct ktable *);
struct kroute6_node *kroute6_find(struct ktable *, const struct in6_addr *,
u_int8_t, u_int8_t);
struct kroute6_node *kroute6_matchgw(struct kroute6_node *,
struct sockaddr_in6 *);
int kroute6_insert(struct ktable *, struct kroute6_node *);
int kroute6_remove(struct ktable *, struct kroute6_node *);
void kroute6_clear(struct ktable *);
struct kif_arp *karp_find(struct sockaddr *, u_short);
int karp_insert(struct kif_node *, struct kif_arp *);
int karp_remove(struct kif_node *, struct kif_arp *);
struct kif_node *kif_find(u_short);
struct kif_node *kif_insert(u_short);
int kif_remove(struct kif_node *);
void kif_clear(void);
struct kif *kif_update(u_short, int, struct if_data *,
struct sockaddr_dl *);
int ka_compare(struct kif_addr *, struct kif_addr *);
void ka_insert(u_short, struct kif_addr *);
struct kif_addr *ka_find(struct sockaddr *);
int ka_remove(struct kif_addr *);
u_int8_t prefixlen_classful(in_addr_t);
u_int8_t mask2prefixlen(in_addr_t);
in_addr_t prefixlen2mask(u_int8_t);
u_int8_t mask2prefixlen6(struct sockaddr_in6 *);
struct in6_addr *prefixlen2mask6(u_int8_t);
void get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
void if_change(u_short, int, struct if_data *, struct sockaddr_dl *);
void if_newaddr(u_short, struct sockaddr *, struct sockaddr *,
struct sockaddr *);
void if_deladdr(u_short, struct sockaddr *, struct sockaddr *,
struct sockaddr *);
void if_announce(void *);
int fetchtable(struct ktable *);
int fetchifs(u_short);
int fetcharp(struct ktable *);
void dispatch_rtmsg(int, short, void *);
int rtmsg_process(char *, int);
int dispatch_rtmsg_addr(struct ktable *, struct rt_msghdr *,
struct sockaddr *[RTAX_MAX]);
RB_PROTOTYPE(kroute_tree, kroute_node, entry, kroute_compare)
RB_GENERATE(kroute_tree, kroute_node, entry, kroute_compare)
RB_PROTOTYPE(kroute6_tree, kroute6_node, entry, kroute6_compare)
RB_GENERATE(kroute6_tree, kroute6_node, entry, kroute6_compare)
RB_HEAD(kif_tree, kif_node) kit;
RB_PROTOTYPE(kif_tree, kif_node, entry, kif_compare)
RB_GENERATE(kif_tree, kif_node, entry, kif_compare)
RB_HEAD(ka_tree, kif_addr) kat;
RB_PROTOTYPE(ka_tree, kif_addr, node, ka_compare)
RB_GENERATE(ka_tree, kif_addr, node, ka_compare)
void
kr_init(void)
{
int opt = 0, rcvbuf, default_rcvbuf;
unsigned int tid = RTABLE_ANY;
socklen_t optlen;
if ((kr_state.ks_ifd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
fatal("kr_init: ioctl socket");
if ((kr_state.ks_fd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1)
fatal("kr_init: route socket");
/* not interested in my own messages */
if (setsockopt(kr_state.ks_fd, SOL_SOCKET, SO_USELOOPBACK,
&opt, sizeof(opt)) == -1)
log_warn("%s: SO_USELOOPBACK", __func__); /* not fatal */
if (snmpd_env->sc_rtfilter && setsockopt(kr_state.ks_fd, AF_ROUTE,
ROUTE_MSGFILTER, &snmpd_env->sc_rtfilter,
sizeof(snmpd_env->sc_rtfilter)) == -1)
log_warn("%s: ROUTE_MSGFILTER", __func__);
/* grow receive buffer, don't wanna miss messages */
optlen = sizeof(default_rcvbuf);
if (getsockopt(kr_state.ks_fd, SOL_SOCKET, SO_RCVBUF,
&default_rcvbuf, &optlen) == -1)
log_warn("%s: SO_RCVBUF", __func__);
else
for (rcvbuf = MAX_RTSOCK_BUF;
rcvbuf > default_rcvbuf &&
setsockopt(kr_state.ks_fd, SOL_SOCKET, SO_RCVBUF,
&rcvbuf, sizeof(rcvbuf)) == -1 && errno == ENOBUFS;
rcvbuf /= 2)
; /* nothing */
if (setsockopt(kr_state.ks_fd, AF_ROUTE, ROUTE_TABLEFILTER, &tid,
sizeof(tid)) == -1)
log_warn("%s: ROUTE_TABLEFILTER", __func__);
RB_INIT(&kit);
RB_INIT(&kat);
if (fetchifs(0) == -1)
fatalx("kr_init: fetchifs");
ktable_init();
event_set(&kr_state.ks_ev, kr_state.ks_fd, EV_READ | EV_PERSIST,
dispatch_rtmsg, NULL);
event_add(&kr_state.ks_ev, NULL);
}
void
ktable_init(void)
{
u_int i;
for (i = 0; i <= RT_TABLEID_MAX; i++)
if (ktable_exists(i, NULL))
ktable_update(i);
}
int
ktable_new(u_int rtableid, u_int rdomid)
{
struct ktable **xkrt;
struct ktable *kt;
size_t newsize, oldsize;
/* resize index table if needed */
if (rtableid >= krt_size) {
if ((xkrt = reallocarray(krt, rtableid + 1,
sizeof(struct ktable *))) == NULL) {
log_warn("%s: realloc", __func__);
return (-1);
}
krt = xkrt;
oldsize = krt_size * sizeof(struct ktable *);
krt_size = rtableid + 1;
newsize = krt_size * sizeof(struct ktable *);
bzero((char *)krt + oldsize, newsize - oldsize);
}
if (krt[rtableid])
fatalx("ktable_new: table already exists");
/* allocate new element */
kt = krt[rtableid] = calloc(1, sizeof(struct ktable));
if (kt == NULL) {
log_warn("%s: calloc", __func__);
return (-1);
}
/* initialize structure ... */
RB_INIT(&kt->krt);
RB_INIT(&kt->krt6);
kt->rtableid = rtableid;
kt->rdomain = rdomid;
/* ... and load it */
if (fetchtable(kt) == -1)
return (-1);
/* load arp information */
if (fetcharp(kt) == -1)
return (-1);
log_debug("%s: new ktable for rtableid %d", __func__, rtableid);
return (0);
}
void
ktable_free(u_int rtableid)
{
struct ktable *kt;
if ((kt = ktable_get(rtableid)) == NULL)
return;
log_debug("%s: freeing ktable rtableid %u", __func__, kt->rtableid);
kroute_clear(kt);
kroute6_clear(kt);
krt[kt->rtableid] = NULL;
free(kt);
}
struct ktable *
ktable_get(u_int rtableid)
{
if (rtableid >= krt_size)
return (NULL);
return (krt[rtableid]);
}
int
ktable_update(u_int rtableid)
{
struct ktable *kt;
u_int rdomid;
if (!ktable_exists(rtableid, &rdomid))
fatalx("ktable_update: table doesn't exist");
if (rdomid != rtableid) {
if (ktable_get(rdomid) == NULL &&
ktable_new(rdomid, rdomid) != 0)
return (-1);
}
kt = ktable_get(rtableid);
if (kt == NULL) {
if (ktable_new(rtableid, rdomid))
return (-1);
}
return (0);
}
int
ktable_exists(u_int rtableid, u_int *rdomid)
{
size_t len;
struct rt_tableinfo info;
int mib[6];
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = 0;
mib[4] = NET_RT_TABLE;
mib[5] = rtableid;
len = sizeof(info);
if (sysctl(mib, 6, &info, &len, NULL, 0) == -1) {
if (errno == ENOENT)
/* table nonexistent */
return (0);
log_warn("%s: sysctl", __func__);
/* must return 0 so that the table is considered non-existent */
return (0);
}
if (rdomid)
*rdomid = info.rti_domainid;
return (1);
}
void
kr_shutdown(void)
{
u_int i;
for (i = krt_size; i > 0; i--)
ktable_free(i - 1);
kif_clear();
}
u_int
kr_ifnumber(void)
{
return (kr_state.ks_nkif);
}
u_long
kr_iflastchange(void)
{
return (kr_state.ks_iflastchange);
}
int
kr_updateif(u_int if_index)
{
return (fetchifs(if_index));
}
u_long
kr_routenumber(void)
{
return (kr_state.ks_nroutes);
}
/* rb-tree compare */
int
kroute_compare(struct kroute_node *a, struct kroute_node *b)
{
if (ntohl(a->r.prefix.s_addr) < ntohl(b->r.prefix.s_addr))
return (-1);
if (ntohl(a->r.prefix.s_addr) > ntohl(b->r.prefix.s_addr))
return (1);
if (a->r.prefixlen < b->r.prefixlen)
return (-1);
if (a->r.prefixlen > b->r.prefixlen)
return (1);
/* if the priority is RTP_ANY finish on the first address hit */
if (a->r.priority == RTP_ANY || b->r.priority == RTP_ANY)
return (0);
if (a->r.priority < b->r.priority)
return (-1);
if (a->r.priority > b->r.priority)
return (1);
return (0);
}
int
kroute6_compare(struct kroute6_node *a, struct kroute6_node *b)
{
int i;
for (i = 0; i < 16; i++) {
if (a->r.prefix.s6_addr[i] < b->r.prefix.s6_addr[i])
return (-1);
if (a->r.prefix.s6_addr[i] > b->r.prefix.s6_addr[i])
return (1);
}
if (a->r.prefixlen < b->r.prefixlen)
return (-1);
if (a->r.prefixlen > b->r.prefixlen)
return (1);
/* if the priority is RTP_ANY finish on the first address hit */
if (a->r.priority == RTP_ANY || b->r.priority == RTP_ANY)
return (0);
if (a->r.priority < b->r.priority)
return (-1);
if (a->r.priority > b->r.priority)
return (1);
return (0);
}
int
kif_compare(struct kif_node *a, struct kif_node *b)
{
return (a->k.if_index - b->k.if_index);
}
int
ka_compare(struct kif_addr *a, struct kif_addr *b)
{
if (a->addr.sa.sa_family < b->addr.sa.sa_family)
return (-1);
if (a->addr.sa.sa_family > b->addr.sa.sa_family)
return (1);
return (memcmp(&a->addr.sa, &b->addr.sa, a->addr.sa.sa_len));
}
/* tree management */
struct kroute_node *
kroute_find(struct ktable *kt, in_addr_t prefix, u_int8_t prefixlen,
u_int8_t prio)
{
struct kroute_node s;
struct kroute_node *kn, *tmp;
s.r.prefix.s_addr = prefix;
s.r.prefixlen = prefixlen;
s.r.priority = prio;
kn = RB_FIND(kroute_tree, &kt->krt, &s);
if (kn && prio == RTP_ANY) {
tmp = RB_PREV(kroute_tree, &kt->krt, kn);
while (tmp) {
if (kroute_compare(&s, tmp) == 0)
kn = tmp;
else
break;
tmp = RB_PREV(kroute_tree, &kt->krt, kn);
}
}
return (kn);
}
struct kroute_node *
kroute_matchgw(struct kroute_node *kr, struct sockaddr_in *sa_in)
{
in_addr_t nexthop;
if (sa_in == NULL) {
log_warnx("%s: no nexthop defined", __func__);
return (NULL);
}
nexthop = sa_in->sin_addr.s_addr;
while (kr) {
if (kr->r.nexthop.s_addr == nexthop)
return (kr);
kr = kr->next;
}
return (NULL);
}
int
kroute_insert(struct ktable *kt, struct kroute_node *kr)
{
struct kroute_node *krm;
if ((krm = RB_INSERT(kroute_tree, &kt->krt, kr)) != NULL) {
/* multipath route, add at end of list */
while (krm->next != NULL)
krm = krm->next;
krm->next = kr;
kr->next = NULL; /* to be sure */
}
kr_state.ks_nroutes++;
return (0);
}
int
kroute_remove(struct ktable *kt, struct kroute_node *kr)
{
struct kroute_node *krm;
if ((krm = RB_FIND(kroute_tree, &kt->krt, kr)) == NULL) {
log_warnx("%s: failed to find %s/%u", __func__,
inet_ntoa(kr->r.prefix), kr->r.prefixlen);
return (-1);
}
if (krm == kr) {
/* head element */
if (RB_REMOVE(kroute_tree, &kt->krt, kr) == NULL) {
log_warnx("%s: failed for %s/%u", __func__,
inet_ntoa(kr->r.prefix), kr->r.prefixlen);
return (-1);
}
if (kr->next != NULL) {
if (RB_INSERT(kroute_tree, &kt->krt, kr->next)
!= NULL) {
log_warnx("%s: failed to add %s/%u", __func__,
inet_ntoa(kr->r.prefix), kr->r.prefixlen);
return (-1);
}
}
} else {
/* somewhere in the list */
while (krm->next != kr && krm->next != NULL)
krm = krm->next;
if (krm->next == NULL) {
log_warnx("%s: multipath list corrupted for %s/%u",
__func__, inet_ntoa(kr->r.prefix), kr->r.prefixlen);
return (-1);
}
krm->next = kr->next;
}
kr_state.ks_nroutes--;
free(kr);
return (0);
}
void
kroute_clear(struct ktable *kt)
{
struct kroute_node *kr;
while ((kr = RB_MIN(kroute_tree, &kt->krt)) != NULL)
kroute_remove(kt, kr);
}
struct kroute6_node *
kroute6_find(struct ktable *kt, const struct in6_addr *prefix,
u_int8_t prefixlen, u_int8_t prio)
{
struct kroute6_node s;
struct kroute6_node *kn6, *tmp;
memcpy(&s.r.prefix, prefix, sizeof(struct in6_addr));
s.r.prefixlen = prefixlen;
s.r.priority = prio;
kn6 = RB_FIND(kroute6_tree, &kt->krt6, &s);
if (kn6 && prio == RTP_ANY) {
tmp = RB_PREV(kroute6_tree, &kt->krt6, kn6);
while (tmp) {
if (kroute6_compare(&s, tmp) == 0)
kn6 = tmp;
else
break;
tmp = RB_PREV(kroute6_tree, &kt->krt6, kn6);
}
}
return (kn6);
}
struct kroute6_node *
kroute6_matchgw(struct kroute6_node *kr, struct sockaddr_in6 *sa_in6)
{
struct in6_addr nexthop;
if (sa_in6 == NULL) {
log_warnx("%s: no nexthop defined", __func__);
return (NULL);
}
memcpy(&nexthop, &sa_in6->sin6_addr, sizeof(nexthop));
while (kr) {
if (memcmp(&kr->r.nexthop, &nexthop, sizeof(nexthop)) == 0)
return (kr);
kr = kr->next;
}
return (NULL);
}
int
kroute6_insert(struct ktable *kt, struct kroute6_node *kr)
{
struct kroute6_node *krm;
if ((krm = RB_INSERT(kroute6_tree, &kt->krt6, kr)) != NULL) {
/* multipath route, add at end of list */
while (krm->next != NULL)
krm = krm->next;
krm->next = kr;
kr->next = NULL; /* to be sure */
}
kr_state.ks_nroutes++;
return (0);
}
int
kroute6_remove(struct ktable *kt, struct kroute6_node *kr)
{
struct kroute6_node *krm;
if ((krm = RB_FIND(kroute6_tree, &kt->krt6, kr)) == NULL) {
log_warnx("%s: failed for %s/%u", __func__,
log_in6addr(&kr->r.prefix), kr->r.prefixlen);
return (-1);
}
if (krm == kr) {
/* head element */
if (RB_REMOVE(kroute6_tree, &kt->krt6, kr) == NULL) {
log_warnx("%s: failed for %s/%u", __func__,
log_in6addr(&kr->r.prefix), kr->r.prefixlen);
return (-1);
}
if (kr->next != NULL) {
if (RB_INSERT(kroute6_tree, &kt->krt6, kr->next) !=
NULL) {
log_warnx("%s: failed to add %s/%u", __func__,
log_in6addr(&kr->r.prefix),
kr->r.prefixlen);
return (-1);
}
}
} else {
/* somewhere in the list */
while (krm->next != kr && krm->next != NULL)
krm = krm->next;
if (krm->next == NULL) {
log_warnx("%s: multipath list corrupted for %s/%u",
__func__, log_in6addr(&kr->r.prefix),
kr->r.prefixlen);
return (-1);
}
krm->next = kr->next;
}
kr_state.ks_nroutes--;
free(kr);
return (0);
}
void
kroute6_clear(struct ktable *kt)
{
struct kroute6_node *kr;
while ((kr = RB_MIN(kroute6_tree, &kt->krt6)) != NULL)
kroute6_remove(kt, kr);
}
static inline int
karp_compare(struct kif_arp *a, struct kif_arp *b)
{
/* Interface indices are assumed equal */
if (ntohl(a->addr.sin.sin_addr.s_addr) >
ntohl(b->addr.sin.sin_addr.s_addr))
return (1);
if (ntohl(a->addr.sin.sin_addr.s_addr) <
ntohl(b->addr.sin.sin_addr.s_addr))
return (-1);
return (0);
}
static inline struct kif_arp *
karp_search(struct kif_node *kn, struct kif_arp *ka)
{
struct kif_arp *pivot;
TAILQ_FOREACH(pivot, &kn->arps, entry) {
switch (karp_compare(ka, pivot)) {
case 0: /* found */
return (pivot);
case -1: /* ka < pivot, end the search */
return (NULL);
}
}
/* looped through the whole list and didn't find */
return (NULL);
}
struct kif_arp *
karp_find(struct sockaddr *sa, u_short ifindex)
{
struct kif_node *kn;
struct kif_arp *ka = NULL, s;
memcpy(&s.addr.sa, sa, sa->sa_len);
if (ifindex == 0) {
/*
* We iterate manually to handle zero ifindex special
* case differently from kif_find, in particular we
* want to look for the address on all available
* interfaces.
*/
RB_FOREACH(kn, kif_tree, &kit) {
if ((ka = karp_search(kn, &s)) != NULL)
break;
}
} else {
if ((kn = kif_find(ifindex)) == NULL)
return (NULL);
ka = karp_search(kn, &s);
}
return (ka);
}
int
karp_insert(struct kif_node *kn, struct kif_arp *ka)
{
struct kif_arp *pivot;
if (ka->if_index == 0)
return (-1);
if (!kn && (kn = kif_find(ka->if_index)) == NULL)
return (-1);
/* Put entry on the list in the ascending lexical order */
TAILQ_FOREACH(pivot, &kn->arps, entry) {
switch (karp_compare(ka, pivot)) {
case 0: /* collision */
return (-1);
case -1: /* ka < pivot */
TAILQ_INSERT_BEFORE(pivot, ka, entry);
return (0);
}
}
/* ka is larger than any other element on the list */
TAILQ_INSERT_TAIL(&kn->arps, ka, entry);
return (0);
}
int
karp_remove(struct kif_node *kn, struct kif_arp *ka)
{
if (ka->if_index == 0)
return (-1);
if (!kn && (kn = kif_find(ka->if_index)) == NULL)
return (-1);
TAILQ_REMOVE(&kn->arps, ka, entry);
free(ka);
return (0);
}
struct kif_arp *
karp_first(u_short ifindex)
{
struct kif_node *kn;
if ((kn = kif_find(ifindex)) == NULL)
return (NULL);
return (TAILQ_FIRST(&kn->arps));
}
struct kif_arp *
karp_getaddr(struct sockaddr *sa, u_short ifindex, int next)
{
struct kif_arp *ka;
if ((ka = karp_find(sa, ifindex)) == NULL)
return (NULL);
return (next ? TAILQ_NEXT(ka, entry) : ka);
}
struct kif_node *
kif_find(u_short if_index)
{
struct kif_node s;
if (if_index == 0)
return (RB_MIN(kif_tree, &kit));
bzero(&s, sizeof(s));
s.k.if_index = if_index;
return (RB_FIND(kif_tree, &kit, &s));
}
struct kif *
kr_getif(u_short if_index)
{
struct kif_node *kn;
kn = kif_find(if_index);
if (kn == NULL)
return (NULL);
return (&kn->k);
}
struct kif *
kr_getnextif(u_short if_index)
{
struct kif_node *kn;
if ((kn = kif_find(if_index)) == NULL)
return (NULL);
if (if_index)
kn = RB_NEXT(kif_tree, &kit, kn);
if (kn == NULL)
return (NULL);
return (&kn->k);
}
struct kif_node *
kif_insert(u_short if_index)
{
struct kif_node *kif;
if ((kif = calloc(1, sizeof(struct kif_node))) == NULL)
return (NULL);
kif->k.if_index = if_index;
TAILQ_INIT(&kif->addrs);
TAILQ_INIT(&kif->arps);
if (RB_INSERT(kif_tree, &kit, kif) != NULL)
fatalx("kif_insert: RB_INSERT");
kr_state.ks_nkif++;
kr_state.ks_iflastchange = smi_getticks();
return (kif);
}
int
kif_remove(struct kif_node *kif)
{
struct kif_addr *ka;
struct kif_arp *kr;
if (RB_REMOVE(kif_tree, &kit, kif) == NULL) {
log_warnx("%s: RB_REMOVE failed", __func__);
return (-1);
}
while ((ka = TAILQ_FIRST(&kif->addrs)) != NULL) {
TAILQ_REMOVE(&kif->addrs, ka, entry);
ka_remove(ka);
}
while ((kr = TAILQ_FIRST(&kif->arps)) != NULL) {
karp_remove(kif, kr);
}
free(kif);
kr_state.ks_nkif--;
kr_state.ks_iflastchange = smi_getticks();
return (0);
}
void
kif_clear(void)
{
struct kif_node *kif;
while ((kif = RB_MIN(kif_tree, &kit)) != NULL)
kif_remove(kif);
kr_state.ks_nkif = 0;
kr_state.ks_iflastchange = smi_getticks();
}
struct kif *
kif_update(u_short if_index, int flags, struct if_data *ifd,
struct sockaddr_dl *sdl)
{
struct kif_node *kif;
struct ether_addr *ea;
struct ifreq ifr;
if ((kif = kif_find(if_index)) == NULL)
if ((kif = kif_insert(if_index)) == NULL)
return (NULL);
kif->k.if_flags = flags;
bcopy(ifd, &kif->k.if_data, sizeof(struct if_data));
kif->k.if_ticks = smi_getticks();
if (sdl && sdl->sdl_family == AF_LINK) {
if (sdl->sdl_nlen >= sizeof(kif->k.if_name))
memcpy(kif->k.if_name, sdl->sdl_data,
sizeof(kif->k.if_name) - 1);
else if (sdl->sdl_nlen > 0)
memcpy(kif->k.if_name, sdl->sdl_data,
sdl->sdl_nlen);
/* string already terminated via calloc() */
if ((ea = (struct ether_addr *)LLADDR(sdl)) != NULL)
bcopy(&ea->ether_addr_octet, kif->k.if_lladdr,
ETHER_ADDR_LEN);
}
bzero(&ifr, sizeof(ifr));
strlcpy(ifr.ifr_name, kif->k.if_name, sizeof(ifr.ifr_name));
ifr.ifr_data = (caddr_t)&kif->k.if_descr;
if (ioctl(kr_state.ks_ifd, SIOCGIFDESCR, &ifr) == -1)
bzero(&kif->k.if_descr, sizeof(kif->k.if_descr));
return (&kif->k);
}
void
ka_insert(u_short if_index, struct kif_addr *ka)
{
if (ka->addr.sa.sa_len == 0)
return;
ka->if_index = if_index;
RB_INSERT(ka_tree, &kat, ka);
}
struct kif_addr *
ka_find(struct sockaddr *sa)
{
struct kif_addr ka;
if (sa == NULL)
return (RB_MIN(ka_tree, &kat));
bzero(&ka.addr, sizeof(ka.addr));
bcopy(sa, &ka.addr.sa, sa->sa_len);
return (RB_FIND(ka_tree, &kat, &ka));
}
int
ka_remove(struct kif_addr *ka)
{
RB_REMOVE(ka_tree, &kat, ka);
free(ka);
return (0);
}
struct kif_addr *
kr_getaddr(struct sockaddr *sa)
{
return (ka_find(sa));
}
struct kif_addr *
kr_getnextaddr(struct sockaddr *sa)
{
struct kif_addr ka;
bzero(&ka.addr, sizeof(ka.addr));
bcopy(sa, &ka.addr.sa, sa->sa_len);
return RB_NFIND(ka_tree, &kat, &ka);
}
/* misc */
u_int8_t
prefixlen_classful(in_addr_t ina)
{
/* it hurt to write this. */
if (ina >= 0xf0000000U) /* class E */
return (32);
else if (ina >= 0xe0000000U) /* class D */
return (4);
else if (ina >= 0xc0000000U) /* class C */
return (24);
else if (ina >= 0x80000000U) /* class B */
return (16);
else /* class A */
return (8);
}
u_int8_t
mask2prefixlen(in_addr_t ina)
{
if (ina == 0)
return (0);
else
return (33 - ffs(ntohl(ina)));
}
in_addr_t
prefixlen2mask(u_int8_t prefixlen)
{
if (prefixlen == 0)
return (0);
return (htonl(0xffffffff << (32 - prefixlen)));
}
u_int8_t
mask2prefixlen6(struct sockaddr_in6 *sa_in6)
{
unsigned int l = 0;
u_int8_t *ap, *ep;
/*
* sin6_len is the size of the sockaddr so subtract the offset of
* the possibly truncated sin6_addr struct.
*/
ap = (u_int8_t *)&sa_in6->sin6_addr;
ep = (u_int8_t *)sa_in6 + sa_in6->sin6_len;
for (; ap < ep; ap++) {
/* this "beauty" is adopted from sbin/route/show.c ... */
switch (*ap) {
case 0xff:
l += 8;
break;
case 0xfe:
l += 7;
goto done;
case 0xfc:
l += 6;
goto done;
case 0xf8:
l += 5;
goto done;
case 0xf0:
l += 4;
goto done;
case 0xe0:
l += 3;
goto done;
case 0xc0:
l += 2;
goto done;
case 0x80:
l += 1;
goto done;
case 0x00:
goto done;
default:
fatalx("non contiguous inet6 netmask");
}
}
done:
if (l > sizeof(struct in6_addr) * 8)
fatalx("inet6 prefixlen out of bound");
return (l);
}
struct in6_addr *
prefixlen2mask6(u_int8_t prefixlen)
{
static struct in6_addr mask;
int i;
bzero(&mask, sizeof(mask));
for (i = 0; i < prefixlen / 8; i++)
mask.s6_addr[i] = 0xff;
i = prefixlen % 8;
if (i)
mask.s6_addr[prefixlen / 8] = 0xff00 >> i;
return (&mask);
}
#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
if_change(u_short if_index, int flags, struct if_data *ifd,
struct sockaddr_dl *sdl)
{
if (kif_update(if_index, flags, ifd, sdl) == NULL)
log_warn("%s: interface %u update failed", __func__, if_index);
}
void
if_newaddr(u_short if_index, struct sockaddr *ifa, struct sockaddr *mask,
struct sockaddr *brd)
{
struct kif_node *kif;
struct kif_addr *ka;
if (ifa == NULL)
return;
if ((kif = kif_find(if_index)) == NULL) {
log_warnx("%s: corresponding if %u not found", __func__,
if_index);
return;
}
if ((ka = ka_find(ifa)) == NULL) {
if ((ka = calloc(1, sizeof(struct kif_addr))) == NULL)
fatal("if_newaddr");
bcopy(ifa, &ka->addr.sa, ifa->sa_len);
TAILQ_INSERT_TAIL(&kif->addrs, ka, entry);
ka_insert(if_index, ka);
}
if (mask)
bcopy(mask, &ka->mask.sa, mask->sa_len);
else
bzero(&ka->mask, sizeof(ka->mask));
if (brd)
bcopy(brd, &ka->dstbrd.sa, brd->sa_len);
else
bzero(&ka->dstbrd, sizeof(ka->dstbrd));
}
void
if_deladdr(u_short if_index, struct sockaddr *ifa, struct sockaddr *mask,
struct sockaddr *brd)
{
struct kif_node *kif;
struct kif_addr *ka;
if (ifa == NULL)
return;
if ((kif = kif_find(if_index)) == NULL) {
log_warnx("%s: corresponding if %u not found", __func__,
if_index);
return;
}
if ((ka = ka_find(ifa)) == NULL)
return;
TAILQ_REMOVE(&kif->addrs, ka, entry);
ka_remove(ka);
}
void
if_announce(void *msg)
{
struct if_announcemsghdr *ifan;
struct kif_node *kif;
ifan = msg;
switch (ifan->ifan_what) {
case IFAN_ARRIVAL:
kif = kif_insert(ifan->ifan_index);
strlcpy(kif->k.if_name, ifan->ifan_name,
sizeof(kif->k.if_name));
break;
case IFAN_DEPARTURE:
kif = kif_find(ifan->ifan_index);
kif_remove(kif);
break;
}
}
int
fetchtable(struct ktable *kt)
{
int mib[7];
size_t len;
char *buf;
int rv;
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[4] = NET_RT_DUMP;
mib[5] = 0;
mib[6] = kt->rtableid;
if (sysctl(mib, 7, NULL, &len, NULL, 0) == -1) {
if (kt->rtableid != 0 && errno == EINVAL)
/* table nonexistent */
return (0);
log_warn("%s: failed to fetch routing table %u size", __func__,
kt->rtableid);
return (-1);
}
if (len == 0)
return (0);
if ((buf = malloc(len)) == NULL) {
log_warn("%s: malloc", __func__);
return (-1);
}
if (sysctl(mib, 7, buf, &len, NULL, 0) == -1) {
log_warn("%s: failed to fetch routing table %u", __func__,
kt->rtableid);
free(buf);
return (-1);
}
rv = rtmsg_process(buf, len);
free(buf);
return (rv);
}
int
fetchifs(u_short if_index)
{
size_t len;
int mib[6];
char *buf;
int rv;
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = 0; /* wildcard address family */
mib[4] = NET_RT_IFLIST;
mib[5] = if_index;
if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1) {
log_warn("%s: failed to fetch address table size for %u",
__func__, if_index);
return (-1);
}
if ((buf = malloc(len)) == NULL) {
log_warn("%s: malloc", __func__);
return (-1);
}
if (sysctl(mib, 6, buf, &len, NULL, 0) == -1) {
log_warn("%s: failed to fetch address table for %u",
__func__, if_index);
free(buf);
return (-1);
}
rv = rtmsg_process(buf, len);
free(buf);
return (rv);
}
int
fetcharp(struct ktable *kt)
{
size_t len;
int mib[7];
char *buf;
int rv;
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[4] = NET_RT_FLAGS;
mib[5] = RTF_LLINFO;
mib[6] = kt->rtableid;
if (sysctl(mib, 7, NULL, &len, NULL, 0) == -1) {
log_warn("%s: failed to fetch arp table %u size", __func__,
kt->rtableid);
return (-1);
}
/* Empty table? */
if (len == 0)
return (0);
if ((buf = malloc(len)) == NULL) {
log_warn("%s: malloc", __func__);
return (-1);
}
if (sysctl(mib, 7, buf, &len, NULL, 0) == -1) {
log_warn("%s: failed to fetch arp table %u", __func__,
kt->rtableid);
free(buf);
return (-1);
}
rv = rtmsg_process(buf, len);
free(buf);
return (rv);
}
void
dispatch_rtmsg(int fd, short event, void *arg)
{
char buf[RT_BUF_SIZE];
ssize_t n;
if ((n = read(fd, &buf, sizeof(buf))) == -1) {
log_warn("%s: read error", __func__);
return;
}
if (n == 0) {
log_warnx("%s: routing socket closed", __func__);
return;
}
rtmsg_process(buf, n);
}
int
rtmsg_process(char *buf, int len)
{
struct ktable *kt;
struct rt_msghdr *rtm;
struct if_msghdr ifm;
struct ifa_msghdr *ifam;
struct sockaddr *sa, *rti_info[RTAX_MAX];
int offset;
char *next;
for (offset = 0; offset < len; offset += rtm->rtm_msglen) {
next = buf + offset;
rtm = (struct rt_msghdr *)next;
if (rtm->rtm_version != RTM_VERSION)
continue;
sa = (struct sockaddr *)(next + rtm->rtm_hdrlen);
get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
switch (rtm->rtm_type) {
case RTM_ADD:
case RTM_GET:
case RTM_CHANGE:
case RTM_DELETE:
case RTM_RESOLVE:
if (rtm->rtm_errno) /* failed attempts */
continue;
if ((kt = ktable_get(rtm->rtm_tableid)) == NULL)
continue;
if (dispatch_rtmsg_addr(kt, rtm, rti_info) == -1)
return (-1);
break;
case RTM_IFINFO:
memcpy(&ifm, next, sizeof(ifm));
if_change(ifm.ifm_index, ifm.ifm_flags, &ifm.ifm_data,
(struct sockaddr_dl *)rti_info[RTAX_IFP]);
break;
case RTM_DELADDR:
ifam = (struct ifa_msghdr *)rtm;
if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA |
RTA_BRD)) == 0)
break;
if_deladdr(ifam->ifam_index, rti_info[RTAX_IFA],
rti_info[RTAX_NETMASK], rti_info[RTAX_BRD]);
break;
case RTM_NEWADDR:
ifam = (struct ifa_msghdr *)rtm;
if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA |
RTA_BRD)) == 0)
break;
if_newaddr(ifam->ifam_index, rti_info[RTAX_IFA],
rti_info[RTAX_NETMASK], rti_info[RTAX_BRD]);
break;
case RTM_IFANNOUNCE:
if_announce(next);
break;
case RTM_DESYNC:
kr_shutdown();
if (fetchifs(0) == -1)
fatalx("rtmsg_process: fetchifs");
ktable_init();
break;
default:
/* ignore for now */
break;
}
}
return (offset);
}
int
dispatch_rtmsg_addr(struct ktable *kt, struct rt_msghdr *rtm,
struct sockaddr *rti_info[RTAX_MAX])
{
struct sockaddr *sa, *psa;
struct sockaddr_in *sa_in, *psa_in = NULL;
struct sockaddr_in6 *sa_in6, *psa_in6 = NULL;
struct sockaddr_dl *sa_dl;
struct kroute_node *kr;
struct kroute6_node *kr6;
struct kif_arp *ka;
int flags, mpath = 0;
u_int16_t ifindex;
u_int8_t prefixlen;
u_int8_t prio;
flags = 0;
ifindex = 0;
prefixlen = 0;
if ((psa = rti_info[RTAX_DST]) == NULL)
return (-1);
if (rtm->rtm_flags & RTF_STATIC)
flags |= F_STATIC;
if (rtm->rtm_flags & RTF_BLACKHOLE)
flags |= F_BLACKHOLE;
if (rtm->rtm_flags & RTF_REJECT)
flags |= F_REJECT;
if (rtm->rtm_flags & RTF_DYNAMIC)
flags |= F_DYNAMIC;
#ifdef RTF_MPATH
if (rtm->rtm_flags & RTF_MPATH)
mpath = 1;
#endif
prio = rtm->rtm_priority;
switch (psa->sa_family) {
case AF_INET:
psa_in = (struct sockaddr_in *)psa;
sa_in = (struct sockaddr_in *)rti_info[RTAX_NETMASK];
if (sa_in != NULL) {
if (sa_in->sin_len != 0)
prefixlen = mask2prefixlen(
sa_in->sin_addr.s_addr);
} else if (rtm->rtm_flags & RTF_HOST)
prefixlen = 32;
else
prefixlen =
prefixlen_classful(psa_in->sin_addr.s_addr);
break;
case AF_INET6:
psa_in6 = (struct sockaddr_in6 *)psa;
sa_in6 = (struct sockaddr_in6 *)rti_info[RTAX_NETMASK];
if (sa_in6 != NULL) {
if (sa_in6->sin6_len != 0)
prefixlen = mask2prefixlen6(sa_in6);
} else if (rtm->rtm_flags & RTF_HOST)
prefixlen = 128;
else
fatalx("in6 net addr without netmask");
break;
default:
return (0);
}
if ((sa = rti_info[RTAX_GATEWAY]) != NULL)
switch (sa->sa_family) {
case AF_INET:
case AF_INET6:
if (rtm->rtm_flags & RTF_CONNECTED) {
flags |= F_CONNECTED;
ifindex = rtm->rtm_index;
}
mpath = 0; /* link local stuff can't be mpath */
break;
case AF_LINK:
/*
* Traditional BSD connected routes have
* a gateway of type AF_LINK.
*/
flags |= F_CONNECTED;
ifindex = rtm->rtm_index;
mpath = 0; /* link local stuff can't be mpath */
break;
}
if (rtm->rtm_type == RTM_DELETE) {
if (sa != NULL && sa->sa_family == AF_LINK &&
(rtm->rtm_flags & RTF_HOST) &&
psa->sa_family == AF_INET) {
if ((ka = karp_find(psa, ifindex)) == NULL)
return (0);
if (karp_remove(NULL, ka) == -1)
return (-1);
return (0);
} else if (sa == NULL && (rtm->rtm_flags & RTF_HOST) &&
psa->sa_family == AF_INET) {
if ((ka = karp_find(psa, ifindex)) != NULL)
karp_remove(NULL, ka);
/* Continue to the route section below */
}
switch (psa->sa_family) {
case AF_INET:
sa_in = (struct sockaddr_in *)sa;
if ((kr = kroute_find(kt, psa_in->sin_addr.s_addr,
prefixlen, prio)) == NULL)
return (0);
if (mpath)
/* get the correct route */
if ((kr = kroute_matchgw(kr, sa_in)) == NULL) {
log_warnx("%s[delete]: "
"mpath route not found", __func__);
return (0);
}
if (kroute_remove(kt, kr) == -1)
return (-1);
break;
case AF_INET6:
sa_in6 = (struct sockaddr_in6 *)sa;
if ((kr6 = kroute6_find(kt, &psa_in6->sin6_addr,
prefixlen, prio)) == NULL)
return (0);
if (mpath)
/* get the correct route */
if ((kr6 = kroute6_matchgw(kr6, sa_in6)) ==
NULL) {
log_warnx("%s[delete]: "
"IPv6 mpath route not found",
__func__);
return (0);
}
if (kroute6_remove(kt, kr6) == -1)
return (-1);
break;
}
return (0);
}
if (sa == NULL && !(flags & F_CONNECTED))
return (0);
/* Add or update an ARP entry */
if ((rtm->rtm_flags & RTF_LLINFO) && (rtm->rtm_flags & RTF_HOST) &&
sa != NULL && sa->sa_family == AF_LINK &&
psa->sa_family == AF_INET) {
sa_dl = (struct sockaddr_dl *)sa;
/* ignore incomplete entries */
if (!sa_dl->sdl_alen)
return (0);
/* ignore entries that do not specify an interface */
if (ifindex == 0)
return (0);
if ((ka = karp_find(psa, ifindex)) != NULL) {
memcpy(&ka->target.sdl, sa_dl, sa_dl->sdl_len);
if (rtm->rtm_flags & RTF_PERMANENT_ARP)
flags |= F_STATIC;
ka->flags = flags;
} else {
if ((ka = calloc(1, sizeof(struct kif_arp))) == NULL) {
log_warn("%s: calloc", __func__);
return (-1);
}
memcpy(&ka->addr.sa, psa, psa->sa_len);
memcpy(&ka->target.sdl, sa_dl, sa_dl->sdl_len);
if (rtm->rtm_flags & RTF_PERMANENT_ARP)
flags |= F_STATIC;
ka->flags = flags;
ka->if_index = ifindex;
if (karp_insert(NULL, ka)) {
free(ka);
log_warnx("%s: failed to insert", __func__);
return (-1);
}
}
return (0);
}
switch (psa->sa_family) {
case AF_INET:
sa_in = (struct sockaddr_in *)sa;
if ((kr = kroute_find(kt, psa_in->sin_addr.s_addr, prefixlen,
prio)) != NULL) {
/* get the correct route */
if (mpath && rtm->rtm_type == RTM_CHANGE &&
(kr = kroute_matchgw(kr, sa_in)) == NULL) {
log_warnx("%s[change]: "
"mpath route not found", __func__);
return (-1);
} else if (mpath && rtm->rtm_type == RTM_ADD)
goto add4;
if (sa_in != NULL)
kr->r.nexthop.s_addr =
sa_in->sin_addr.s_addr;
else
kr->r.nexthop.s_addr = 0;
kr->r.flags = flags;
kr->r.if_index = ifindex;
kr->r.ticks = smi_getticks();
} else {
add4:
if ((kr = calloc(1,
sizeof(struct kroute_node))) == NULL) {
log_warn("%s: calloc", __func__);
return (-1);
}
kr->r.prefix.s_addr = psa_in->sin_addr.s_addr;
kr->r.prefixlen = prefixlen;
if (sa_in != NULL)
kr->r.nexthop.s_addr = sa_in->sin_addr.s_addr;
else
kr->r.nexthop.s_addr = 0;
kr->r.flags = flags;
kr->r.if_index = ifindex;
kr->r.ticks = smi_getticks();
kr->r.priority = prio;
kroute_insert(kt, kr);
}
break;
case AF_INET6:
sa_in6 = (struct sockaddr_in6 *)sa;
if ((kr6 = kroute6_find(kt, &psa_in6->sin6_addr, prefixlen,
prio)) != NULL) {
/* get the correct route */
if (mpath && rtm->rtm_type == RTM_CHANGE &&
(kr6 = kroute6_matchgw(kr6, sa_in6)) ==
NULL) {
log_warnx("%s[change]: "
"IPv6 mpath route not found", __func__);
return (-1);
} else if (mpath && rtm->rtm_type == RTM_ADD)
goto add6;
if (sa_in6 != NULL)
memcpy(&kr6->r.nexthop,
&sa_in6->sin6_addr,
sizeof(struct in6_addr));
else
memcpy(&kr6->r.nexthop,
&in6addr_any,
sizeof(struct in6_addr));
kr6->r.flags = flags;
kr6->r.if_index = ifindex;
kr6->r.ticks = smi_getticks();
} else {
add6:
if ((kr6 = calloc(1,
sizeof(struct kroute6_node))) == NULL) {
log_warn("%s: calloc", __func__);
return (-1);
}
memcpy(&kr6->r.prefix, &psa_in6->sin6_addr,
sizeof(struct in6_addr));
kr6->r.prefixlen = prefixlen;
if (sa_in6 != NULL)
memcpy(&kr6->r.nexthop, &sa_in6->sin6_addr,
sizeof(struct in6_addr));
else
memcpy(&kr6->r.nexthop, &in6addr_any,
sizeof(struct in6_addr));
kr6->r.flags = flags;
kr6->r.if_index = ifindex;
kr6->r.ticks = smi_getticks();
kr6->r.priority = prio;
kroute6_insert(kt, kr6);
}
break;
}
return (0);
}
struct kroute *
kroute_first(void)
{
struct kroute_node *kn;
struct ktable *kt;
if ((kt = ktable_get(0)) == NULL)
return (NULL);
kn = RB_MIN(kroute_tree, &kt->krt);
return (&kn->r);
}
struct kroute *
kroute_getaddr(in_addr_t prefix, u_int8_t prefixlen, u_int8_t prio, int next)
{
struct kroute_node *kn;
struct ktable *kt;
if ((kt = ktable_get(0)) == NULL)
return (NULL);
kn = kroute_find(kt, prefix, prefixlen, prio);
if (kn != NULL && next)
kn = RB_NEXT(kroute_tree, &kt->krt, kn);
if (kn != NULL)
return (&kn->r);
else
return (NULL);
}