1699 lines
37 KiB
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);
|
|
}
|