summaryrefslogtreecommitdiff
path: root/usr.sbin/ospfd/kroute.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/ospfd/kroute.c')
-rw-r--r--usr.sbin/ospfd/kroute.c1270
1 files changed, 1270 insertions, 0 deletions
diff --git a/usr.sbin/ospfd/kroute.c b/usr.sbin/ospfd/kroute.c
new file mode 100644
index 00000000000..2aa833051b4
--- /dev/null
+++ b/usr.sbin/ospfd/kroute.c
@@ -0,0 +1,1270 @@
+/* $OpenBSD: kroute.c,v 1.1 2005/01/28 14:05:40 claudio Exp $ */
+
+/*
+ * Copyright (c) 2004 Esben Norby <esben.norby@ericsson.com>
+ * 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/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/tree.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ospfd.h"
+#include "ospfd.h"
+#include "log.h"
+
+/* XXX */
+void main_imsg_compose_ospfe(int, pid_t, void *, u_int16_t);
+void main_imsg_compose_rde(int, pid_t, void *, u_int16_t);
+extern struct ospfd_conf *conf;
+
+struct {
+ u_int32_t rtseq;
+ pid_t pid;
+ int fib_sync;
+ int fd;
+ struct event ev;
+} kr_state;
+
+struct kroute_node {
+ RB_ENTRY(kroute_node) entry;
+ struct kroute r;
+};
+
+struct knexthop_node {
+ RB_ENTRY(knexthop_node) entry;
+ struct in_addr nexthop;
+ struct kroute_node *kroute;
+};
+
+struct kif_kr {
+ LIST_ENTRY(kif_kr) entry;
+ struct kroute_node *kr;
+};
+
+LIST_HEAD(kif_kr_head, kif_kr);
+
+struct kif_node {
+ RB_ENTRY(kif_node) entry;
+ struct kif k;
+ struct kif_kr_head kroute_l;
+};
+
+int kroute_compare(struct kroute_node *, struct kroute_node *);
+int knexthop_compare(struct knexthop_node *, struct knexthop_node *);
+int kif_compare(struct kif_node *, struct kif_node *);
+
+struct kroute_node *kroute_find(in_addr_t, u_int8_t);
+int kroute_insert(struct kroute_node *);
+int kroute_remove(struct kroute_node *);
+void kroute_clear(void);
+
+struct knexthop_node *knexthop_find(struct in_addr);
+int knexthop_insert(struct knexthop_node *);
+int knexthop_remove(struct knexthop_node *);
+void knexthop_clear(void);
+
+struct kif_node *kif_find(int);
+int kif_insert(struct kif_node *);
+int kif_remove(struct kif_node *);
+void kif_clear(void);
+
+int kif_kr_insert(struct kroute_node *);
+int kif_kr_remove(struct kroute_node *);
+
+void knexthop_validate(struct knexthop_node *);
+struct kroute_node *kroute_match(in_addr_t);
+void kroute_attach_nexthop(struct knexthop_node *,
+ struct kroute_node *);
+void kroute_detach_nexthop(struct knexthop_node *);
+
+int protect_lo(void);
+u_int8_t prefixlen_classful(in_addr_t);
+u_int8_t mask2prefixlen(in_addr_t);
+in_addr_t prefixlen2mask(u_int8_t);
+void get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
+void if_change(u_short, int, struct if_data *);
+void if_announce(void *);
+
+int send_rtmsg(int, int, struct kroute *);
+int dispatch_rtmsg(void);
+int fetchtable(void);
+int fetchifs(int);
+
+RB_HEAD(kroute_tree, kroute_node) kroute_tree, krt;
+RB_PROTOTYPE(kroute_tree, kroute_node, entry, kroute_compare)
+RB_GENERATE(kroute_tree, kroute_node, entry, kroute_compare)
+
+RB_HEAD(knexthop_tree, knexthop_node) knexthop_tree, knt;
+RB_PROTOTYPE(knexthop_tree, knexthop_node, entry, knexthop_compare)
+RB_GENERATE(knexthop_tree, knexthop_node, entry, knexthop_compare)
+
+RB_HEAD(kif_tree, kif_node) kif_tree, kit;
+RB_PROTOTYPE(kif_tree, kif_node, entry, kif_compare)
+RB_GENERATE(kif_tree, kif_node, entry, kif_compare)
+
+int
+kr_init(int fs)
+{
+ int opt;
+
+ kr_state.fib_sync = fs;
+
+ if ((kr_state.fd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1) {
+ log_warn("kr_init: socket");
+ return (-1);
+ }
+
+ /* not interested in my own messages */
+ if (setsockopt(kr_state.fd, SOL_SOCKET, SO_USELOOPBACK,
+ &opt, sizeof(opt)) == -1)
+ log_warn("kr_init: setsockopt"); /* not fatal */
+
+ kr_state.pid = getpid();
+ kr_state.rtseq = 1;
+
+ RB_INIT(&krt);
+ RB_INIT(&knt);
+ RB_INIT(&kit);
+
+ if (fetchifs(0) == -1)
+ return (-1);
+
+ if (fetchtable() == -1)
+ return (-1);
+
+ if (protect_lo() == -1)
+ return (-1);
+
+ event_set(&kr_state.ev, kr_state.fd, EV_READ, kr_dispatch_msg, NULL);
+ event_add(&kr_state.ev, NULL);
+
+ return (0);
+}
+
+int
+kr_change(struct kroute *kroute)
+{
+ struct kroute_node *kr;
+ int action = RTM_ADD;
+
+ if ((kr = kroute_find(kroute->prefix.s_addr, kroute->prefixlen)) !=
+ NULL) {
+ if (kr->r.flags & F_OSPFD_INSERTED)
+ action = RTM_CHANGE;
+ else /* a non-bgp route already exists. not a problem */
+ return (0);
+ }
+
+ /* nexthop within 127/8 -> ignore silently */
+ if ((kroute->nexthop.s_addr & htonl(0xff000000)) ==
+ inet_addr("127.0.0.0"))
+ return (0);
+
+ if (send_rtmsg(kr_state.fd, action, kroute) == -1)
+ return (-1);
+
+ if (action == RTM_ADD) {
+ if ((kr = calloc(1, sizeof(struct kroute_node))) == NULL) {
+ log_warn("kr_change");
+ return (-1);
+ }
+ kr->r.prefix.s_addr = kroute->prefix.s_addr;
+ kr->r.prefixlen = kroute->prefixlen;
+ kr->r.nexthop.s_addr = kroute->nexthop.s_addr;
+ kr->r.flags = kroute->flags | F_OSPFD_INSERTED;
+
+ if (kroute_insert(kr) == -1)
+ free(kr);
+ } else {
+ kr->r.nexthop.s_addr = kroute->nexthop.s_addr;
+ if (kroute->flags & F_BLACKHOLE)
+ kr->r.flags |= F_BLACKHOLE;
+ else
+ kr->r.flags &= ~F_BLACKHOLE;
+ if (kroute->flags & F_REJECT)
+ kr->r.flags |= F_REJECT;
+ else
+ kr->r.flags &= ~F_REJECT;
+ }
+
+ return (0);
+}
+
+int
+kr_delete(struct kroute *kroute)
+{
+ struct kroute_node *kr;
+
+ if ((kr = kroute_find(kroute->prefix.s_addr, kroute->prefixlen)) ==
+ NULL)
+ return (0);
+
+ if (!(kr->r.flags & F_OSPFD_INSERTED))
+ return (0);
+
+ /* nexthop within 127/8 -> ignore silently */
+ if ((kroute->nexthop.s_addr & htonl(0xff000000)) ==
+ inet_addr("127.0.0.0"))
+ return (0);
+
+ if (send_rtmsg(kr_state.fd, RTM_DELETE, kroute) == -1)
+ return (-1);
+
+ if (kroute_remove(kr) == -1)
+ return (-1);
+
+ return (0);
+}
+
+void
+kr_shutdown(void)
+{
+ kr_fib_decouple();
+ knexthop_clear();
+ kroute_clear();
+ kif_clear();
+}
+
+void
+kr_fib_couple(void)
+{
+ struct kroute_node *kr;
+
+ if (kr_state.fib_sync == 1) /* already coupled */
+ return;
+
+ kr_state.fib_sync = 1;
+
+ RB_FOREACH(kr, kroute_tree, &krt)
+ if ((kr->r.flags & F_OSPFD_INSERTED))
+ send_rtmsg(kr_state.fd, RTM_ADD, &kr->r);
+
+ log_info("kernel routing table coupled");
+}
+
+void
+kr_fib_decouple(void)
+{
+ struct kroute_node *kr;
+
+ if (kr_state.fib_sync == 0) /* already decoupled */
+ return;
+
+ RB_FOREACH(kr, kroute_tree, &krt)
+ if ((kr->r.flags & F_OSPFD_INSERTED))
+ send_rtmsg(kr_state.fd, RTM_DELETE, &kr->r);
+
+ kr_state.fib_sync = 0;
+
+ log_info("kernel routing table decoupled");
+}
+
+void
+kr_dispatch_msg(int fd, short event, void *bula)
+{
+ dispatch_rtmsg();
+}
+
+int
+kr_nexthop_add(struct in_addr addr)
+{
+ struct knexthop_node *h;
+
+ if ((h = knexthop_find(addr)) != NULL) {
+ /* should not happen... this is really an error path */
+ struct kroute_nexthop nh;
+
+ bzero(&nh, sizeof(nh));
+ memcpy(&nh.nexthop, &addr, sizeof(nh.nexthop));
+ if (h->kroute != NULL) {
+ nh.valid = 1;
+ nh.connected = h->kroute->r.flags & F_CONNECTED;
+ if (h->kroute->r.nexthop.s_addr != 0)
+ nh.gateway.s_addr =
+ h->kroute->r.nexthop.s_addr;
+ memcpy(&nh.kr, &h->kroute->r, sizeof(nh.kr));
+ }
+ send_nexthop_update(&nh);
+ } else {
+ if ((h = calloc(1, sizeof(struct knexthop_node))) == NULL) {
+ log_warn("kr_nexthop_add");
+ return (-1);
+ }
+ memcpy(&h->nexthop, &addr, sizeof(h->nexthop));
+
+ if (knexthop_insert(h) == -1)
+ return (-1);
+ }
+
+ return (0);
+}
+
+void
+kr_nexthop_delete(struct in_addr addr)
+{
+ struct knexthop_node *kn;
+
+ if ((kn = knexthop_find(addr)) == NULL)
+ return;
+
+ knexthop_remove(kn);
+}
+
+void
+kr_show_route(struct imsg *imsg)
+{
+ struct area *area = NULL;
+ struct iface *iface = NULL;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_SHOW_INTERFACE:
+ LIST_FOREACH(area, &conf->area_list, entry) {
+ LIST_FOREACH(iface, &area->iface_list, entry) {
+ main_imsg_compose_ospfe(IMSG_CTL_SHOW_INTERFACE,
+ imsg->hdr.pid, iface, sizeof(*iface));
+ }
+ }
+ break;
+ default:
+ log_debug("kr_show_route: error handling imsg");
+ break;
+ }
+
+ main_imsg_compose_ospfe(IMSG_CTL_END, imsg->hdr.pid, NULL, 0);
+}
+
+void
+kr_ifinfo(char *ifname)
+{
+ struct kif_node *kif;
+
+ RB_FOREACH(kif, kif_tree, &kit)
+ if (!strcmp(ifname, kif->k.ifname)) {
+ main_imsg_compose_ospfe(IMSG_IFINFO, 0,
+ &kif->k, sizeof(kif->k));
+ return;
+ }
+}
+
+/* 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);
+ return (0);
+}
+
+int
+knexthop_compare(struct knexthop_node *a, struct knexthop_node *b)
+{
+ u_int32_t r;
+
+ if ((r = b->nexthop.s_addr - a->nexthop.s_addr) != 0)
+ return (r);
+
+ return (0);
+}
+
+int
+kif_compare(struct kif_node *a, struct kif_node *b)
+{
+ return (b->k.ifindex - a->k.ifindex);
+}
+
+/* tree management */
+struct kroute_node *
+kroute_find(in_addr_t prefix, u_int8_t prefixlen)
+{
+ struct kroute_node s;
+
+ s.r.prefix.s_addr = prefix;
+ s.r.prefixlen = prefixlen;
+
+ return (RB_FIND(kroute_tree, &krt, &s));
+}
+
+int
+kroute_insert(struct kroute_node *kr)
+{
+ struct knexthop_node *h;
+ in_addr_t mask, ina;
+
+ if (RB_INSERT(kroute_tree, &krt, kr) != NULL) {
+ log_warnx("kroute_tree insert failed for %s/%u",
+ inet_ntoa(kr->r.prefix), kr->r.prefixlen);
+ free(kr);
+ return (-1);
+ }
+
+ if (kr->r.flags & F_KERNEL) {
+ if (!(kr->r.flags & F_CONNECTED))
+ kr->r.flags |= F_STATIC;
+
+ mask = prefixlen2mask(kr->r.prefixlen);
+ ina = ntohl(kr->r.prefix.s_addr);
+ RB_FOREACH(h, knexthop_tree, &knt)
+ if ((ntohl(h->nexthop.s_addr) & mask) == ina)
+ knexthop_validate(h);
+
+ if (kr->r.flags & F_CONNECTED)
+ if (kif_kr_insert(kr) == -1)
+ return (-1);
+ }
+ return (0);
+}
+
+int
+kroute_remove(struct kroute_node *kr)
+{
+ struct knexthop_node *s;
+
+ if (RB_REMOVE(kroute_tree, &krt, kr) == NULL) {
+ log_warnx("kroute_remove failed for %s/%u",
+ inet_ntoa(kr->r.prefix), kr->r.prefixlen);
+ return (-1);
+ }
+
+ /* check wether a nexthop depends on this kroute */
+ if ((kr->r.flags & F_KERNEL) && (kr->r.flags & F_NEXTHOP))
+ RB_FOREACH(s, knexthop_tree, &knt)
+ if (s->kroute == kr)
+ knexthop_validate(s);
+
+ if (kr->r.flags & F_CONNECTED)
+ if (kif_kr_remove(kr) == -1) {
+ free(kr);
+ return (-1);
+ }
+
+ free(kr);
+ return (0);
+}
+
+void
+kroute_clear(void)
+{
+ struct kroute_node *kr;
+
+ while ((kr = RB_MIN(kroute_tree, &krt)) != NULL)
+ kroute_remove(kr);
+}
+
+struct knexthop_node *
+knexthop_find(struct in_addr addr)
+{
+ struct knexthop_node s;
+
+ memcpy(&s.nexthop, &addr, sizeof(s.nexthop));
+
+ return (RB_FIND(knexthop_tree, &knt, &s));
+}
+
+int
+knexthop_insert(struct knexthop_node *kn)
+{
+ if (RB_INSERT(knexthop_tree, &knt, kn) != NULL) {
+ log_warnx("knexthop_tree insert failed for %s",
+ inet_ntoa(kn->nexthop));
+ free(kn);
+ return (-1);
+ }
+
+ knexthop_validate(kn);
+
+ return (0);
+}
+
+int
+knexthop_remove(struct knexthop_node *kn)
+{
+ kroute_detach_nexthop(kn);
+
+ if (RB_REMOVE(knexthop_tree, &knt, kn) == NULL) {
+ log_warnx("knexthop_remove failed for %s",
+ inet_ntoa(kn->nexthop));
+ return (-1);
+ }
+
+ free(kn);
+ return (0);
+}
+
+void
+knexthop_clear(void)
+{
+ struct knexthop_node *kn;
+
+ while ((kn = RB_MIN(knexthop_tree, &knt)) != NULL)
+ knexthop_remove(kn);
+}
+
+struct kif_node *
+kif_find(int ifindex)
+{
+ struct kif_node s;
+
+ bzero(&s, sizeof(s));
+ s.k.ifindex = ifindex;
+
+ return (RB_FIND(kif_tree, &kit, &s));
+}
+
+int
+kif_insert(struct kif_node *kif)
+{
+ LIST_INIT(&kif->kroute_l);
+
+ if (RB_INSERT(kif_tree, &kit, kif) != NULL) {
+ log_warnx("RB_INSERT(kif_tree, &kit, kif)");
+ free(kif);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+kif_remove(struct kif_node *kif)
+{
+ struct kif_kr *kkr;
+
+ if (RB_REMOVE(kif_tree, &kit, kif) == NULL) {
+ log_warnx("RB_REMOVE(kif_tree, &kit, kif)");
+ return (-1);
+ }
+
+ while ((kkr = LIST_FIRST(&kif->kroute_l)) != NULL) {
+ LIST_REMOVE(kkr, entry);
+ kkr->kr->r.flags &= ~F_NEXTHOP;
+ kroute_remove(kkr->kr);
+ free(kkr);
+ }
+
+ free(kif);
+ return (0);
+}
+
+void
+kif_clear(void)
+{
+ struct kif_node *kif;
+
+ while ((kif = RB_MIN(kif_tree, &kit)) != NULL)
+ kif_remove(kif);
+}
+
+int
+kif_kr_insert(struct kroute_node *kr)
+{
+ struct kif_node *kif;
+ struct kif_kr *kkr;
+
+ if ((kif = kif_find(kr->r.ifindex)) == NULL) {
+ if (kr->r.ifindex)
+ log_warnx("interface with index %u not found",
+ kr->r.ifindex);
+ return (0);
+ }
+
+ if ((kkr = calloc(1, sizeof(struct kif_kr))) == NULL) {
+ log_warn("kif_kr_insert");
+ return (-1);
+ }
+
+ kkr->kr = kr;
+
+ LIST_INSERT_HEAD(&kif->kroute_l, kkr, entry);
+
+ return (0);
+}
+
+int
+kif_kr_remove(struct kroute_node *kr)
+{
+ struct kif_node *kif;
+ struct kif_kr *kkr;
+
+ if ((kif = kif_find(kr->r.ifindex)) == NULL) {
+ if (kr->r.ifindex)
+ log_warnx("interface with index %u not found",
+ kr->r.ifindex);
+ return (0);
+ }
+
+ for (kkr = LIST_FIRST(&kif->kroute_l); kkr != NULL && kkr->kr != kr;
+ kkr = LIST_NEXT(kkr, entry))
+ ; /* nothing */
+
+ if (kkr == NULL) {
+ log_warnx("can't remove connected route from interface "
+ "with index %u: not found", kr->r.ifindex);
+ return (-1);
+ }
+
+ LIST_REMOVE(kkr, entry);
+ free(kkr);
+
+ return (0);
+}
+
+/* nexthop validation */
+void
+knexthop_validate(struct knexthop_node *kn)
+{
+ struct kroute_node *kr;
+ struct kroute_nexthop n;
+ int was_valid = 0;
+
+ if (kn->kroute != NULL && (!(kn->kroute->r.flags & F_DOWN)))
+ was_valid = 1;
+
+ bzero(&n, sizeof(n));
+ memcpy(&n.nexthop, &kn->nexthop, sizeof(n.nexthop));
+ kroute_detach_nexthop(kn);
+
+ if ((kr = kroute_match(kn->nexthop.s_addr)) == NULL) {
+ if (was_valid)
+ send_nexthop_update(&n);
+ } else { /* match */
+ if (kr->r.flags & F_DOWN) { /* is down */
+ if (was_valid)
+ send_nexthop_update(&n);
+ } else { /* valid */
+ n.valid = 1;
+ n.connected = kr->r.flags & F_CONNECTED;
+ n.gateway.s_addr = kr->r.nexthop.s_addr;
+ memcpy(&n.kr, &kr->r, sizeof(n.kr));
+ send_nexthop_update(&n);
+ }
+ kroute_attach_nexthop(kn, kr);
+ }
+}
+
+struct kroute_node *
+kroute_match(in_addr_t key)
+{
+ int i;
+ struct kroute_node *kr;
+ in_addr_t ina;
+
+ ina = ntohl(key);
+
+ /* we will never match the default route */
+ for (i = 32; i > 0; i--)
+ if ((kr =
+ kroute_find(htonl(ina & prefixlen2mask(i)), i)) != NULL)
+ return (kr);
+
+ /* if we don't have a match yet, try to find a default route */
+ if ((kr = kroute_find(0, 0)) != NULL)
+ return (kr);
+
+ return (NULL);
+}
+
+void
+kroute_attach_nexthop(struct knexthop_node *kn, struct kroute_node *kr)
+{
+ kn->kroute = kr;
+ kr->r.flags |= F_NEXTHOP;
+}
+
+void
+kroute_detach_nexthop(struct knexthop_node *kn)
+{
+ struct knexthop_node *s;
+
+ /*
+ * check wether there's another nexthop depending on this kroute
+ * if not remove the flag
+ */
+
+ if (kn->kroute == NULL)
+ return;
+
+ for (s = RB_MIN(knexthop_tree, &knt); s != NULL &&
+ s->kroute != kn->kroute; s = RB_NEXT(knexthop_tree, &knt, s))
+ ; /* nothing */
+
+ if (s == NULL)
+ kn->kroute->r.flags &= ~F_NEXTHOP;
+
+ kn->kroute = NULL;
+}
+
+/* misc */
+int
+protect_lo(void)
+{
+ struct kroute_node *kr;
+
+ /* special protection for 127/8 */
+ if ((kr = calloc(1, sizeof(struct kroute_node))) == NULL) {
+ log_warn("protect_lo");
+ return (-1);
+ }
+ kr->r.prefix.s_addr = inet_addr("127.0.0.1");
+ kr->r.prefixlen = 8;
+ kr->r.flags = F_KERNEL|F_CONNECTED;
+
+ if (RB_INSERT(kroute_tree, &krt, kr) != NULL)
+ free(kr); /* kernel route already there, no problem */
+
+ return (0);
+}
+
+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 (0xffffffff << (32 - prefixlen));
+}
+
+#define ROUNDUP(a, size) \
+ (((a) & ((size) - 1)) ? (1 + ((a) | ((size) - 1))) : (a))
+
+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, sizeof(long)));
+ } else
+ rti_info[i] = NULL;
+ }
+}
+
+void
+if_change(u_short ifindex, int flags, struct if_data *ifd)
+{
+ struct kif_node *kif;
+ struct kif_kr *kkr;
+ struct kroute_nexthop nh;
+ struct knexthop_node *n;
+ u_int8_t reachable;
+
+ if ((kif = kif_find(ifindex)) == NULL) {
+ log_warnx("interface with index %u not found",
+ ifindex);
+ return;
+ }
+
+ kif->k.flags = flags;
+ kif->k.link_state = ifd->ifi_link_state;
+ kif->k.media_type = ifd->ifi_type;
+ kif->k.baudrate = ifd->ifi_baudrate;
+
+ main_imsg_compose_ospfe(IMSG_IFINFO, 0, &kif->k, sizeof(kif->k));
+
+ if ((reachable = (flags & IFF_UP) &&
+ (ifd->ifi_link_state != LINK_STATE_DOWN)) == kif->k.nh_reachable)
+ return; /* nothing changed wrt nexthop validity */
+
+ kif->k.nh_reachable = reachable;
+
+ LIST_FOREACH(kkr, &kif->kroute_l, entry) {
+ /*
+ * we treat link_state == LINK_STATE_UNKNOWN as valid
+ * not all interfaces have a concept of "link state" and/or
+ * do not report up
+ */
+ if (reachable)
+ kkr->kr->r.flags &= ~F_DOWN;
+ else
+ kkr->kr->r.flags |= F_DOWN;
+
+ RB_FOREACH(n, knexthop_tree, &knt)
+ if (n->kroute == kkr->kr) {
+ bzero(&nh, sizeof(nh));
+ memcpy(&nh.nexthop, &n->nexthop,
+ sizeof(nh.nexthop));
+ if (!(kkr->kr->r.flags & F_DOWN)) {
+ nh.valid = 1;
+ nh.connected = 1;
+ nh.gateway.s_addr =
+ kkr->kr->r.nexthop.s_addr;
+ }
+ memcpy(&nh.kr, &kkr->kr->r, sizeof(nh.kr));
+ send_nexthop_update(&nh);
+ }
+ }
+}
+
+void
+if_announce(void *msg)
+{
+ struct if_announcemsghdr *ifan;
+ struct kif_node *kif;
+
+ ifan = msg;
+
+ switch (ifan->ifan_what) {
+ case IFAN_ARRIVAL:
+ if ((kif = calloc(1, sizeof(struct kif_node))) == NULL) {
+ log_warn("if_announce");
+ return;
+ }
+
+ kif->k.ifindex = ifan->ifan_index;
+ strlcpy(kif->k.ifname, ifan->ifan_name, sizeof(kif->k.ifname));
+ kif_insert(kif);
+ break;
+ case IFAN_DEPARTURE:
+ kif = kif_find(ifan->ifan_index);
+ kif_remove(kif);
+ break;
+ }
+}
+
+/* rtsock */
+int
+send_rtmsg(int fd, int action, struct kroute *kroute)
+{
+ struct {
+ struct rt_msghdr hdr;
+ struct sockaddr_in prefix;
+ struct sockaddr_in nexthop;
+ struct sockaddr_in mask;
+ } r;
+
+ if (kr_state.fib_sync == 0)
+ return (0);
+
+ bzero(&r, sizeof(r));
+ r.hdr.rtm_msglen = sizeof(r);
+ r.hdr.rtm_version = RTM_VERSION;
+ r.hdr.rtm_type = action;
+ r.hdr.rtm_flags = RTF_PROTO1;
+ if (kroute->flags & F_BLACKHOLE)
+ r.hdr.rtm_flags |= RTF_BLACKHOLE;
+ if (kroute->flags & F_REJECT)
+ r.hdr.rtm_flags |= RTF_REJECT;
+ r.hdr.rtm_seq = kr_state.rtseq++; /* overflow doesn't matter */
+ r.hdr.rtm_addrs = RTA_DST|RTA_GATEWAY|RTA_NETMASK;
+ r.prefix.sin_len = sizeof(r.prefix);
+ r.prefix.sin_family = AF_INET;
+ r.prefix.sin_addr.s_addr = kroute->prefix.s_addr;
+
+ r.nexthop.sin_len = sizeof(r.nexthop);
+ r.nexthop.sin_family = AF_INET;
+ r.nexthop.sin_addr.s_addr = kroute->nexthop.s_addr;
+ if (kroute->nexthop.s_addr != 0)
+ r.hdr.rtm_flags |= RTF_GATEWAY;
+
+ r.mask.sin_len = sizeof(r.mask);
+ r.mask.sin_family = AF_INET;
+ r.mask.sin_addr.s_addr = htonl(prefixlen2mask(kroute->prefixlen));
+
+retry:
+ if (write(fd, &r, sizeof(r)) == -1) {
+ switch (errno) {
+ case ESRCH:
+ if (r.hdr.rtm_type == RTM_CHANGE) {
+ r.hdr.rtm_type = RTM_ADD;
+ goto retry;
+ } else if (r.hdr.rtm_type == RTM_DELETE) {
+ log_info("route %s/%u vanished before delete",
+ inet_ntoa(kroute->prefix),
+ kroute->prefixlen);
+ return (0);
+ } else {
+ log_warnx("send_rtmsg: action %u, "
+ "prefix %s/%u: %s", r.hdr.rtm_type,
+ inet_ntoa(kroute->prefix),
+ kroute->prefixlen, strerror(errno));
+ return (0);
+ }
+ break;
+ default:
+ log_warnx("send_rtmsg: action %u, prefix %s/%u: %s",
+ r.hdr.rtm_type, inet_ntoa(kroute->prefix),
+ kroute->prefixlen, strerror(errno));
+ return (0);
+ }
+ }
+
+ return (0);
+}
+
+int
+fetchtable(void)
+{
+ size_t len;
+ int mib[6];
+ char *buf, *next, *lim;
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+ struct sockaddr_in *sa_in;
+ struct kroute_node *kr;
+
+ mib[0] = CTL_NET;
+ mib[1] = AF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+
+ if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1) {
+ log_warn("sysctl");
+ return (-1);
+ }
+ if ((buf = malloc(len)) == NULL) {
+ log_warn("fetchtable");
+ return (-1);
+ }
+ if (sysctl(mib, 6, buf, &len, NULL, 0) == -1) {
+ log_warn("sysctl");
+ free(buf);
+ return (-1);
+ }
+
+ lim = buf + len;
+ for (next = buf; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)next;
+ sa = (struct sockaddr *)(rtm + 1);
+ get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+
+ if ((sa = rti_info[RTAX_DST]) == NULL)
+ continue;
+
+ if (rtm->rtm_flags & RTF_LLINFO) /* arp cache */
+ continue;
+
+#ifdef RTF_MPATH
+ if (rtm->rtm_flags & RTF_MPATH) /* multipath */
+ continue;
+#endif
+
+ if ((kr = calloc(1, sizeof(struct kroute_node))) == NULL) {
+ log_warn("fetchtable");
+ free(buf);
+ return (-1);
+ }
+
+ kr->r.flags = F_KERNEL;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ kr->r.prefix.s_addr =
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr;
+ sa_in = (struct sockaddr_in *)rti_info[RTAX_NETMASK];
+ if (sa_in != NULL) {
+ if (sa_in->sin_len == 0)
+ break;
+ kr->r.prefixlen =
+ mask2prefixlen(sa_in->sin_addr.s_addr);
+ } else if (rtm->rtm_flags & RTF_HOST)
+ kr->r.prefixlen = 32;
+ else
+ kr->r.prefixlen =
+ prefixlen_classful(kr->r.prefix.s_addr);
+ break;
+ default:
+ free(kr);
+ continue;
+ /* not reached */
+ }
+
+ if ((sa = rti_info[RTAX_GATEWAY]) != NULL)
+ switch (sa->sa_family) {
+ case AF_INET:
+ kr->r.nexthop.s_addr =
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr;
+ break;
+ case AF_LINK:
+ kr->r.flags |= F_CONNECTED;
+ kr->r.ifindex = rtm->rtm_index;
+ break;
+ }
+
+ kroute_insert(kr);
+
+ }
+ free(buf);
+ return (0);
+}
+
+int
+fetchifs(int ifindex)
+{
+ size_t len;
+ int mib[6];
+ char *buf, *next, *lim;
+ struct if_msghdr ifm;
+ struct kif_node *kif;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+ struct sockaddr_dl *sdl;
+
+ mib[0] = CTL_NET;
+ mib[1] = AF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = ifindex;
+
+ if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1) {
+ log_warn("sysctl");
+ return (-1);
+ }
+ if ((buf = malloc(len)) == NULL) {
+ log_warn("fetchif");
+ return (-1);
+ }
+ if (sysctl(mib, 6, buf, &len, NULL, 0) == -1) {
+ log_warn("sysctl");
+ free(buf);
+ return (-1);
+ }
+
+ lim = buf + len;
+ for (next = buf; next < lim; next += ifm.ifm_msglen) {
+ memcpy(&ifm, next, sizeof(ifm));
+ sa = (struct sockaddr *)(next + sizeof(ifm));
+ get_rtaddrs(ifm.ifm_addrs, sa, rti_info);
+
+ if (ifm.ifm_type != RTM_IFINFO)
+ continue;
+
+ if ((kif = calloc(1, sizeof(struct kif_node))) == NULL) {
+ log_warn("fetchifs");
+ free(buf);
+ return (-1);
+ }
+
+ kif->k.ifindex = ifm.ifm_index;
+ kif->k.flags = ifm.ifm_flags;
+ kif->k.link_state = ifm.ifm_data.ifi_link_state;
+ kif->k.media_type = ifm.ifm_data.ifi_type;
+ kif->k.baudrate = ifm.ifm_data.ifi_baudrate;
+ kif->k.nh_reachable = (kif->k.flags & IFF_UP) &&
+ (ifm.ifm_data.ifi_link_state != LINK_STATE_DOWN);
+
+ if ((sa = rti_info[RTAX_IFP]) != NULL)
+ if (sa->sa_family == AF_LINK) {
+ sdl = (struct sockaddr_dl *)sa;
+ if (sdl->sdl_nlen > 0)
+ strlcpy(kif->k.ifname, sdl->sdl_data,
+ sizeof(kif->k.ifname));
+ }
+
+ kif_insert(kif);
+ }
+ free(buf);
+ return (0);
+}
+
+int
+dispatch_rtmsg(void)
+{
+ char buf[RT_BUF_SIZE];
+ ssize_t n;
+ char *next, *lim;
+ struct rt_msghdr *rtm;
+ struct if_msghdr ifm;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+ struct sockaddr_in *sa_in;
+ struct kroute_node *kr;
+ struct in_addr prefix, nexthop;
+ u_int8_t prefixlen;
+ int flags;
+ u_short ifindex;
+
+ if ((n = read(kr_state.fd, &buf, sizeof(buf))) == -1) {
+ log_warn("dispatch_rtmsg: read error");
+ return (-1);
+ }
+
+ if (n == 0) {
+ log_warnx("routing socket closed");
+ return (-1);
+ }
+
+ lim = buf + n;
+ for (next = buf; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)next;
+ sa = (struct sockaddr *)(rtm + 1);
+ get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+
+ prefix.s_addr = 0;
+ prefixlen = 0;
+ flags = F_KERNEL;
+ nexthop.s_addr = 0;
+ ifindex = 0;
+
+ if (rtm->rtm_pid == kr_state.pid) /* cause by us */
+ continue;
+
+ if (rtm->rtm_errno) /* failed attempts... */
+ continue;
+
+ if (rtm->rtm_type == RTM_ADD || rtm->rtm_type == RTM_CHANGE ||
+ rtm->rtm_type == RTM_DELETE) {
+ if (rtm->rtm_flags & RTF_LLINFO) /* arp cache */
+ continue;
+ switch (sa->sa_family) {
+ case AF_INET:
+ prefix.s_addr =
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr;
+ 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(prefix.s_addr);
+ break;
+ default:
+ continue;
+ /* not reached */
+ }
+ }
+
+ if ((sa = rti_info[RTAX_GATEWAY]) != NULL)
+ switch (sa->sa_family) {
+ case AF_INET:
+ nexthop.s_addr =
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr;
+ break;
+ case AF_LINK:
+ flags |= F_CONNECTED;
+ ifindex = rtm->rtm_index;
+ break;
+ }
+
+ switch (rtm->rtm_type) {
+ case RTM_ADD:
+ case RTM_CHANGE:
+ if (nexthop.s_addr == 0 && !(flags & F_CONNECTED)) {
+ log_warnx("dispatch_rtmsg no nexthop for %s/%u",
+ inet_ntoa(prefix), prefixlen);
+ continue;
+ }
+
+ if ((kr = kroute_find(prefix.s_addr, prefixlen)) !=
+ NULL) {
+ if (kr->r.flags & F_KERNEL) {
+ kr->r.nexthop.s_addr = nexthop.s_addr;
+ if (kr->r.flags & F_NEXTHOP)
+ flags |= F_NEXTHOP;
+ if ((kr->r.flags & F_CONNECTED) &&
+ !(flags & F_CONNECTED))
+ kif_kr_remove(kr);
+ if ((flags & F_CONNECTED) &&
+ !(kr->r.flags & F_CONNECTED))
+ kif_kr_insert(kr);
+ kr->r.flags = flags;
+ }
+ } else if (rtm->rtm_type == RTM_CHANGE) {
+ log_warnx("change req for %s/%u: not "
+ "in table", inet_ntoa(prefix),
+ prefixlen);
+ continue;
+ } else {
+ if ((kr = calloc(1,
+ sizeof(struct kroute_node))) == NULL) {
+ log_warn("dispatch_rtmsg");
+ return (-1);
+ }
+ kr->r.prefix.s_addr = prefix.s_addr;
+ kr->r.prefixlen = prefixlen;
+ kr->r.nexthop.s_addr = nexthop.s_addr;
+ kr->r.flags = flags;
+ kr->r.ifindex = ifindex;
+
+ kroute_insert(kr);
+ }
+ break;
+ case RTM_DELETE:
+ if ((kr = kroute_find(prefix.s_addr, prefixlen)) ==
+ NULL)
+ continue;
+ if (!(kr->r.flags & F_KERNEL))
+ continue;
+ if (kroute_remove(kr) == -1)
+ return (-1);
+ break;
+ case RTM_IFINFO:
+ memcpy(&ifm, next, sizeof(ifm));
+ if_change(ifm.ifm_index, ifm.ifm_flags,
+ &ifm.ifm_data);
+ break;
+ case RTM_IFANNOUNCE:
+ if_announce(next);
+ break;
+ default:
+ /* ignore for now */
+ break;
+ }
+ }
+ return (0);
+}