File: [local] / src / usr.sbin / ospf6d / rde_spf.c (download)
Revision 1.29, Wed Mar 8 04:43:14 2023 UTC (14 months, 3 weeks ago) by guenther
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD Changes since 1.28: +1 -2 lines
Delete obsolete /* ARGSUSED */ lint comments.
ok miod@ millert@
|
/* $OpenBSD: rde_spf.c,v 1.29 2023/03/08 04:43:14 guenther Exp $ */
/*
* Copyright (c) 2005 Esben Norby <norby@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 <netinet/in.h>
#include <arpa/inet.h>
#include <err.h>
#include <stdlib.h>
#include <string.h>
#include "ospf6d.h"
#include "ospf6.h"
#include "log.h"
#include "rde.h"
extern struct ospfd_conf *rdeconf;
TAILQ_HEAD(, vertex) cand_list;
RB_HEAD(rt_tree, rt_node) rt;
RB_PROTOTYPE(rt_tree, rt_node, entry, rt_compare)
RB_GENERATE(rt_tree, rt_node, entry, rt_compare)
struct vertex *spf_root = NULL;
struct in6_addr *calc_nexthop_lladdr(struct vertex *, struct lsa_rtr_link *,
unsigned int);
void calc_nexthop_transit_nbr(struct vertex *, struct vertex *,
unsigned int);
void calc_nexthop(struct vertex *, struct vertex *,
struct area *, struct lsa_rtr_link *);
void rt_nexthop_clear(struct rt_node *);
void rt_nexthop_add(struct rt_node *, struct v_nexthead *,
u_int16_t, struct in_addr);
void rt_update(struct in6_addr *, u_int8_t, struct v_nexthead *,
u_int16_t, u_int32_t, u_int32_t, struct in_addr,
struct in_addr, enum path_type, enum dst_type, u_int8_t,
u_int32_t);
struct rt_node *rt_lookup(enum dst_type, struct in6_addr *);
void rt_invalidate(struct area *);
int linked(struct vertex *, struct vertex *);
void
spf_calc(struct area *area)
{
struct vertex *v, *w;
struct lsa_rtr_link *rtr_link = NULL;
struct lsa_net_link *net_link;
u_int32_t d;
unsigned int i;
/* clear SPF tree */
spf_tree_clr(area);
cand_list_clr();
/* initialize SPF tree */
if ((v = spf_root = lsa_find_rtr(area, rde_router_id())) == NULL)
/* empty area because no interface is active */
return;
area->transit = 0;
spf_root->cost = 0;
w = NULL;
/* calculate SPF tree */
do {
/* loop links */
for (i = 0; i < lsa_num_links(v); i++) {
switch (v->type) {
case LSA_TYPE_ROUTER:
rtr_link = get_rtr_link(v, i);
switch (rtr_link->type) {
case LINK_TYPE_POINTTOPOINT:
case LINK_TYPE_VIRTUAL:
/* find router LSA */
w = lsa_find_rtr(area,
rtr_link->nbr_rtr_id);
break;
case LINK_TYPE_TRANSIT_NET:
/* find network LSA */
w = lsa_find_tree(&area->lsa_tree,
htons(LSA_TYPE_NETWORK),
rtr_link->nbr_iface_id,
rtr_link->nbr_rtr_id);
break;
default:
fatalx("spf_calc: invalid link type");
}
break;
case LSA_TYPE_NETWORK:
net_link = get_net_link(v, i);
/* find router LSA */
w = lsa_find_rtr(area, net_link->att_rtr);
break;
default:
fatalx("spf_calc: invalid LSA type");
}
if (w == NULL)
continue;
if (ntohs(w->lsa->hdr.age) == MAX_AGE)
continue;
if (lsa_num_links(w) == 0)
continue;
if (!linked(w, v)) {
log_debug("spf_calc: w adv_rtr %s ls_id %s "
"type 0x%x numlinks %hu has no link to "
"v adv_rtr %s ls_id %s type 0x%x",
log_rtr_id(htonl(w->adv_rtr)),
log_rtr_id(htonl(w->ls_id)), w->type,
lsa_num_links(w),
log_rtr_id(htonl(v->adv_rtr)),
log_rtr_id(htonl(v->ls_id)), v->type);
continue;
}
if (v->type == LSA_TYPE_ROUTER)
d = v->cost + ntohs(rtr_link->metric);
else
d = v->cost;
if (cand_list_present(w)) {
if (d > w->cost)
continue;
if (d < w->cost) {
w->cost = d;
vertex_nexthop_clear(w);
calc_nexthop(w, v, area, rtr_link);
/*
* need to readd to candidate list
* because the list is sorted
*/
TAILQ_REMOVE(&cand_list, w, cand);
cand_list_add(w);
} else
/* equal cost path */
calc_nexthop(w, v, area, rtr_link);
} else if (w->cost == LS_INFINITY && d < LS_INFINITY) {
w->cost = d;
vertex_nexthop_clear(w);
calc_nexthop(w, v, area, rtr_link);
cand_list_add(w);
}
}
/* get next vertex */
v = cand_list_pop();
w = NULL;
} while (v != NULL);
/* spf_dump(area); */
log_debug("spf_calc: area %s calculated", inet_ntoa(area->id));
/* Dump SPF tree to log */
RB_FOREACH(v, lsa_tree, &area->lsa_tree) {
struct v_nexthop *vn;
char hops[4096];
struct iface *iface;
bzero(hops, sizeof(hops));
if (v->type != LSA_TYPE_ROUTER && v->type != LSA_TYPE_NETWORK)
continue;
TAILQ_FOREACH(vn, &v->nexthop, entry) {
strlcat(hops, log_in6addr(&vn->nexthop), sizeof(hops));
strlcat(hops, "%", sizeof(hops));
if ((iface = if_find(vn->ifindex)) == NULL)
fatalx("spf_calc: lost iface");
strlcat(hops, iface->name, sizeof(hops));
if (vn != TAILQ_LAST(&v->nexthop, v_nexthead))
strlcat(hops, ", ", sizeof(hops));
}
log_debug("%s(%s, 0x%x, %s) cost %u has nexthops [%s]",
v == spf_root ? "*" : " ", log_rtr_id(htonl(v->adv_rtr)),
v->type, log_rtr_id(htonl(v->ls_id)), v->cost, hops);
}
area->num_spf_calc++;
start_spf_timer();
}
void
rt_calc(struct vertex *v, struct area *area, struct ospfd_conf *conf)
{
struct vertex *w;
struct lsa_intra_prefix *iap;
struct lsa_prefix *prefix;
struct in_addr adv_rtr;
struct in6_addr ia6;
u_int16_t i, off;
u_int8_t flags;
lsa_age(v);
if (ntohs(v->lsa->hdr.age) == MAX_AGE)
return;
switch (v->type) {
case LSA_TYPE_ROUTER:
if (v->cost >= LS_INFINITY || TAILQ_EMPTY(&v->nexthop))
return;
/* router, only add border and as-external routers */
flags = LSA_24_GETHI(ntohl(v->lsa->data.rtr.opts));
if ((flags & (OSPF_RTR_B | OSPF_RTR_E)) == 0)
return;
bzero(&ia6, sizeof(ia6));
adv_rtr.s_addr = htonl(v->adv_rtr);
bcopy(&adv_rtr, &ia6.s6_addr[12], sizeof(adv_rtr));
rt_update(&ia6, 128, &v->nexthop, v->type, v->cost, 0, area->id,
adv_rtr, PT_INTER_AREA, DT_RTR, flags, 0);
break;
case LSA_TYPE_INTRA_A_PREFIX:
/* Find referenced LSA */
iap = &v->lsa->data.pref_intra;
switch (ntohs(iap->ref_type)) {
case LSA_TYPE_ROUTER:
w = lsa_find_rtr(area, iap->ref_adv_rtr);
if (w == NULL) {
warnx("rt_calc: Intra-Area-Prefix LSA (%s, %u) "
"references non-existent router %s",
log_rtr_id(htonl(v->adv_rtr)),
v->ls_id, log_rtr_id(iap->ref_adv_rtr));
return;
}
flags = LSA_24_GETHI(ntohl(w->lsa->data.rtr.opts));
break;
case LSA_TYPE_NETWORK:
w = lsa_find_tree(&area->lsa_tree, iap->ref_type,
iap->ref_ls_id, iap->ref_adv_rtr);
if (w == NULL) {
warnx("rt_calc: Intra-Area-Prefix LSA (%s, %u) "
"references non-existent Network LSA (%s, "
"%u)", log_rtr_id(htonl(v->adv_rtr)),
v->ls_id, log_rtr_id(iap->ref_adv_rtr),
ntohl(iap->ref_ls_id));
return;
}
flags = 0;
break;
default:
warnx("rt_calc: Intra-Area-Prefix LSA (%s, %u) has "
"invalid ref_type 0x%hx", log_rtr_id(v->adv_rtr),
v->ls_id, ntohs(iap->ref_type));
return;
}
if (w->cost >= LS_INFINITY || TAILQ_EMPTY(&w->nexthop))
return;
/* Add prefixes listed in Intra-Area-Prefix LSA to routing
* table, using w as destination. */
off = sizeof(v->lsa->hdr) + sizeof(struct lsa_intra_prefix);
for (i = 0; i < ntohs(v->lsa->data.pref_intra.numprefix); i++) {
prefix = (struct lsa_prefix *)((char *)(v->lsa) + off);
if (!(prefix->options & OSPF_PREFIX_NU)) {
bzero(&ia6, sizeof(ia6));
bcopy(prefix + 1, &ia6,
LSA_PREFIXSIZE(prefix->prefixlen));
adv_rtr.s_addr = htonl(w->adv_rtr);
rt_update(&ia6, prefix->prefixlen, &w->nexthop,
v->type, w->cost + ntohs(prefix->metric), 0,
area->id, adv_rtr, PT_INTRA_AREA, DT_NET,
flags, 0);
}
off += sizeof(struct lsa_prefix)
+ LSA_PREFIXSIZE(prefix->prefixlen);
}
break;
case LSA_TYPE_INTER_A_PREFIX:
/* XXX if ABR only look at area 0.0.0.0 LSA */
/* ignore self-originated stuff */
if (v->self)
return;
adv_rtr.s_addr = htonl(v->adv_rtr);
w = lsa_find_rtr(area, adv_rtr.s_addr);
if (w == NULL) {
warnx("rt_calc: Inter-Area-Router LSA (%s, %u) "
"originated from non-existent router",
log_rtr_id(htonl(v->adv_rtr)),
v->ls_id);
return;
}
if (w->cost >= LS_INFINITY || TAILQ_EMPTY(&w->nexthop))
return;
/* Add prefix listed in Inter-Area-Prefix LSA to routing
* table, using w as destination. */
off = sizeof(v->lsa->hdr) + sizeof(struct lsa_prefix_sum);
prefix = (struct lsa_prefix *)((char *)(v->lsa) + off);
if (prefix->options & OSPF_PREFIX_NU)
return;
bzero(&ia6, sizeof(ia6));
bcopy(prefix + 1, &ia6, LSA_PREFIXSIZE(prefix->prefixlen));
rt_update(&ia6, prefix->prefixlen, &w->nexthop, v->type,
w->cost + (ntohs(v->lsa->data.rtr_sum.metric) &
LSA_METRIC_MASK), 0, area->id, adv_rtr, PT_INTER_AREA,
DT_NET, 0, 0);
break;
case LSA_TYPE_INTER_A_ROUTER:
/* XXX if ABR only look at area 0.0.0.0 LSA */
/* ignore self-originated stuff */
if (v->self)
return;
adv_rtr.s_addr = htonl(v->adv_rtr);
w = lsa_find_rtr(area, adv_rtr.s_addr);
if (w == NULL) {
warnx("rt_calc: Inter-Area-Router LSA (%s, %u) "
"originated from non-existent router",
log_rtr_id(htonl(v->adv_rtr)),
v->ls_id);
return;
}
if (w->cost >= LS_INFINITY || TAILQ_EMPTY(&w->nexthop))
return;
/* Add router listed in Inter-Area-Router LSA to routing
* table, using w as destination. */
bzero(&ia6, sizeof(ia6));
bcopy(&v->lsa->data.rtr_sum.dest_rtr_id, &ia6.s6_addr[12],
4);
rt_update(&ia6, 128, &w->nexthop, v->type, w->cost +
(ntohs(v->lsa->data.rtr_sum.metric) & LSA_METRIC_MASK), 0,
area->id, adv_rtr, PT_INTER_AREA, DT_RTR, 0, 0);
break;
default:
break;
}
}
void
asext_calc(struct vertex *v)
{
struct in6_addr addr, fw_addr;
struct rt_node *r;
struct rt_nexthop *rn;
struct lsa_prefix *prefix;
struct in_addr adv_rtr, area;
char *p;
u_int32_t metric, cost2, ext_tag = 0;
enum path_type type;
lsa_age(v);
if (ntohs(v->lsa->hdr.age) == MAX_AGE ||
(ntohl(v->lsa->data.asext.metric) & LSA_METRIC_MASK) >=
LS_INFINITY)
return;
switch (v->type) {
case LSA_TYPE_EXTERNAL:
/* ignore self-originated stuff */
if (v->self)
return;
adv_rtr.s_addr = htonl(v->adv_rtr);
bzero(&addr, sizeof(addr));
bcopy(&adv_rtr, &addr.s6_addr[12], sizeof(adv_rtr));
if ((r = rt_lookup(DT_RTR, &addr)) == NULL)
return;
prefix = &v->lsa->data.asext.prefix;
if (prefix->options & OSPF_PREFIX_NU)
break;
bzero(&addr, sizeof(addr));
bcopy(prefix + 1, &addr,
LSA_PREFIXSIZE(prefix->prefixlen));
p = (char *)(prefix + 1) + LSA_PREFIXSIZE(prefix->prefixlen);
metric = ntohl(v->lsa->data.asext.metric);
if (metric & LSA_ASEXT_F_FLAG) {
bcopy(p, &fw_addr, sizeof(fw_addr));
p += sizeof(fw_addr);
/* lookup forwarding address */
if ((r = rt_lookup(DT_NET, &fw_addr)) == NULL ||
(r->p_type != PT_INTRA_AREA &&
r->p_type != PT_INTER_AREA))
return;
}
if (metric & LSA_ASEXT_T_FLAG) {
bcopy(p, &ext_tag, sizeof(ext_tag));
p += sizeof(ext_tag);
}
if (metric & LSA_ASEXT_E_FLAG) {
v->cost = r->cost;
cost2 = metric & LSA_METRIC_MASK;
type = PT_TYPE2_EXT;
} else {
v->cost = r->cost + (metric & LSA_METRIC_MASK);
cost2 = 0;
type = PT_TYPE1_EXT;
}
area.s_addr = 0;
vertex_nexthop_clear(v);
TAILQ_FOREACH(rn, &r->nexthop, entry) {
if (rn->invalid)
continue;
if (rn->connected && r->d_type == DT_NET) {
if (metric & LSA_ASEXT_F_FLAG)
vertex_nexthop_add(v, NULL, &fw_addr,
rn->ifindex);
else
fatalx("asext_calc: I'm sorry Dave, "
"I'm afraid I can't do that.");
} else
vertex_nexthop_add(v, NULL, &rn->nexthop,
rn->ifindex);
}
rt_update(&addr, prefix->prefixlen, &v->nexthop, v->type,
v->cost, cost2, area, adv_rtr, type, DT_NET, 0, ext_tag);
break;
default:
fatalx("asext_calc: invalid LSA type");
}
}
void
spf_tree_clr(struct area *area)
{
struct lsa_tree *tree = &area->lsa_tree;
struct vertex *v;
RB_FOREACH(v, lsa_tree, tree) {
v->cost = LS_INFINITY;
vertex_nexthop_clear(v);
}
}
struct in6_addr *
calc_nexthop_lladdr(struct vertex *dst, struct lsa_rtr_link *rtr_link,
unsigned int ifindex)
{
struct iface *iface;
struct vertex *link;
struct rde_nbr *nbr;
/* Find outgoing interface, we need its LSA tree */
LIST_FOREACH(iface, &dst->area->iface_list, entry) {
if (ifindex == iface->ifindex)
break;
}
if (!iface) {
log_warnx("calc_nexthop_lladdr: no interface found for "
"ifindex %d", ntohl(rtr_link->iface_id));
return (NULL);
}
/* Determine neighbor's link-local address.
* Try to get it from link LSA first. */
link = lsa_find_tree(&iface->lsa_tree,
htons(LSA_TYPE_LINK), rtr_link->iface_id,
htonl(dst->adv_rtr));
if (link)
return &link->lsa->data.link.lladdr;
/* Not found, so fall back to source address
* advertised in hello packet. */
if ((nbr = rde_nbr_find(dst->peerid)) == NULL)
fatalx("next hop is not a neighbor");
return &nbr->addr;
}
void
calc_nexthop_transit_nbr(struct vertex *dst, struct vertex *parent,
unsigned int ifindex)
{
struct lsa_rtr_link *rtr_link;
unsigned int i;
struct in6_addr *lladdr;
if (dst->type != LSA_TYPE_ROUTER)
fatalx("calc_nexthop_transit_nbr: dst is not a router");
if (parent->type != LSA_TYPE_NETWORK)
fatalx("calc_nexthop_transit_nbr: parent is not a network");
/* dst is a neighbor on a directly attached transit network.
* Figure out dst's link local address and add it as nexthop. */
for (i = 0; i < lsa_num_links(dst); i++) {
rtr_link = get_rtr_link(dst, i);
if (rtr_link->type == LINK_TYPE_TRANSIT_NET &&
rtr_link->nbr_rtr_id == parent->lsa->hdr.adv_rtr &&
rtr_link->nbr_iface_id == parent->lsa->hdr.ls_id) {
lladdr = calc_nexthop_lladdr(dst, rtr_link, ifindex);
vertex_nexthop_add(dst, parent, lladdr, ifindex);
}
}
}
void
calc_nexthop(struct vertex *dst, struct vertex *parent,
struct area *area, struct lsa_rtr_link *rtr_link)
{
struct v_nexthop *vn;
struct in6_addr *nexthop;
/* case 1 */
if (parent == spf_root) {
switch (dst->type) {
case LSA_TYPE_ROUTER:
if (rtr_link->type != LINK_TYPE_POINTTOPOINT)
fatalx("inconsistent SPF tree");
nexthop = calc_nexthop_lladdr(dst, rtr_link,
ntohl(rtr_link->iface_id));
break;
case LSA_TYPE_NETWORK:
if (rtr_link->type != LINK_TYPE_TRANSIT_NET)
fatalx("inconsistent SPF tree");
/* Next hop address cannot be determined yet,
* we only know the outgoing interface. */
nexthop = NULL;
break;
default:
fatalx("calc_nexthop: invalid dst type");
}
vertex_nexthop_add(dst, parent, nexthop,
ntohl(rtr_link->iface_id));
return;
}
/* case 2 */
if (parent->type == LSA_TYPE_NETWORK && dst->type == LSA_TYPE_ROUTER) {
TAILQ_FOREACH(vn, &parent->nexthop, entry) {
if (vn->prev == spf_root)
calc_nexthop_transit_nbr(dst, parent,
vn->ifindex);
else
/* dst is more than one transit net away */
vertex_nexthop_add(dst, parent, &vn->nexthop,
vn->ifindex);
}
return;
}
/* case 3 */
TAILQ_FOREACH(vn, &parent->nexthop, entry)
vertex_nexthop_add(dst, parent, &vn->nexthop, vn->ifindex);
}
/* candidate list */
void
cand_list_init(void)
{
TAILQ_INIT(&cand_list);
}
void
cand_list_add(struct vertex *v)
{
struct vertex *c = NULL;
TAILQ_FOREACH(c, &cand_list, cand) {
if (c->cost > v->cost) {
TAILQ_INSERT_BEFORE(c, v, cand);
return;
} else if (c->cost == v->cost && c->type == LSA_TYPE_ROUTER &&
v->type == LSA_TYPE_NETWORK) {
TAILQ_INSERT_BEFORE(c, v, cand);
return;
}
}
TAILQ_INSERT_TAIL(&cand_list, v, cand);
}
struct vertex *
cand_list_pop(void)
{
struct vertex *c;
if ((c = TAILQ_FIRST(&cand_list)) != NULL) {
TAILQ_REMOVE(&cand_list, c, cand);
}
return (c);
}
int
cand_list_present(struct vertex *v)
{
struct vertex *c;
TAILQ_FOREACH(c, &cand_list, cand) {
if (c == v)
return (1);
}
return (0);
}
void
cand_list_clr(void)
{
struct vertex *c;
while ((c = TAILQ_FIRST(&cand_list)) != NULL) {
TAILQ_REMOVE(&cand_list, c, cand);
}
}
/* timers */
void
spf_timer(int fd, short event, void *arg)
{
struct vertex *v;
struct ospfd_conf *conf = arg;
struct area *area;
struct rt_node *r;
switch (conf->spf_state) {
case SPF_IDLE:
fatalx("spf_timer: invalid state IDLE");
case SPF_HOLDQUEUE:
conf->spf_state = SPF_DELAY;
/* FALLTHROUGH */
case SPF_DELAY:
LIST_FOREACH(area, &conf->area_list, entry) {
if (area->dirty) {
/* invalidate RIB entries of this area */
rt_invalidate(area);
/* calculate SPF tree */
spf_calc(area);
/* calculate route table */
RB_FOREACH(v, lsa_tree, &area->lsa_tree) {
rt_calc(v, area, conf);
}
area->dirty = 0;
}
}
/* calculate as-external routes, first invalidate them */
rt_invalidate(NULL);
RB_FOREACH(v, lsa_tree, &asext_tree) {
asext_calc(v);
}
RB_FOREACH(r, rt_tree, &rt) {
LIST_FOREACH(area, &conf->area_list, entry)
rde_summary_update(r, area);
if (r->d_type != DT_NET)
continue;
if (r->invalid)
rde_send_delete_kroute(r);
else
rde_send_change_kroute(r);
}
LIST_FOREACH(area, &conf->area_list, entry)
lsa_remove_invalid_sums(area);
start_spf_holdtimer(conf);
break;
case SPF_HOLD:
conf->spf_state = SPF_IDLE;
break;
default:
fatalx("spf_timer: unknown state");
}
}
void
start_spf_timer(void)
{
struct timeval tv;
switch (rdeconf->spf_state) {
case SPF_IDLE:
timerclear(&tv);
tv.tv_sec = rdeconf->spf_delay;
rdeconf->spf_state = SPF_DELAY;
if (evtimer_add(&rdeconf->ev, &tv) == -1)
fatal("start_spf_timer");
break;
case SPF_DELAY:
/* ignore */
break;
case SPF_HOLD:
rdeconf->spf_state = SPF_HOLDQUEUE;
break;
case SPF_HOLDQUEUE:
/* ignore */
break;
default:
fatalx("start_spf_timer: invalid spf_state");
}
}
void
stop_spf_timer(struct ospfd_conf *conf)
{
if (evtimer_del(&conf->ev) == -1)
fatal("stop_spf_timer");
}
void
start_spf_holdtimer(struct ospfd_conf *conf)
{
struct timeval tv;
switch (conf->spf_state) {
case SPF_DELAY:
timerclear(&tv);
tv.tv_sec = conf->spf_hold_time;
conf->spf_state = SPF_HOLD;
if (evtimer_add(&conf->ev, &tv) == -1)
fatal("start_spf_holdtimer");
break;
case SPF_IDLE:
case SPF_HOLD:
case SPF_HOLDQUEUE:
fatalx("start_spf_holdtimer: invalid state");
default:
fatalx("start_spf_holdtimer: unknown state");
}
}
/* route table */
void
rt_init(void)
{
RB_INIT(&rt);
}
int
rt_compare(struct rt_node *a, struct rt_node *b)
{
int i;
/* XXX maybe a & b need to be switched */
i = memcmp(&a->prefix, &b->prefix, sizeof(a->prefix));
if (i)
return (i);
if (a->prefixlen < b->prefixlen)
return (-1);
if (a->prefixlen > b->prefixlen)
return (1);
if (a->d_type > b->d_type)
return (-1);
if (a->d_type < b->d_type)
return (1);
return (0);
}
struct rt_node *
rt_find(struct in6_addr *prefix, u_int8_t prefixlen, enum dst_type d_type)
{
struct rt_node s;
s.prefix = *prefix;
s.prefixlen = prefixlen;
s.d_type = d_type;
return (RB_FIND(rt_tree, &rt, &s));
}
int
rt_insert(struct rt_node *r)
{
if (RB_INSERT(rt_tree, &rt, r) != NULL) {
log_warnx("rt_insert failed for %s/%u",
log_in6addr(&r->prefix), r->prefixlen);
free(r);
return (-1);
}
return (0);
}
int
rt_remove(struct rt_node *r)
{
if (RB_REMOVE(rt_tree, &rt, r) == NULL) {
log_warnx("rt_remove failed for %s/%u",
log_in6addr(&r->prefix), r->prefixlen);
return (-1);
}
rt_nexthop_clear(r);
free(r);
return (0);
}
void
rt_invalidate(struct area *area)
{
struct rt_node *r, *nr;
struct rt_nexthop *rn, *nrn;
for (r = RB_MIN(rt_tree, &rt); r != NULL; r = nr) {
nr = RB_NEXT(rt_tree, &rt, r);
if (area == NULL) {
/* look only at as_ext routes */
if (r->p_type != PT_TYPE1_EXT &&
r->p_type != PT_TYPE2_EXT)
continue;
} else {
/* ignore all as_ext routes */
if (r->p_type == PT_TYPE1_EXT ||
r->p_type == PT_TYPE2_EXT)
continue;
/* look only at routes matching the area */
if (r->area.s_addr != area->id.s_addr)
continue;
}
r->invalid = 1;
for (rn = TAILQ_FIRST(&r->nexthop); rn != NULL; rn = nrn) {
nrn = TAILQ_NEXT(rn, entry);
if (rn->invalid) {
TAILQ_REMOVE(&r->nexthop, rn, entry);
free(rn);
} else
rn->invalid = 1;
}
if (TAILQ_EMPTY(&r->nexthop))
rt_remove(r);
}
}
void
rt_nexthop_clear(struct rt_node *r)
{
struct rt_nexthop *rn;
while ((rn = TAILQ_FIRST(&r->nexthop)) != NULL) {
TAILQ_REMOVE(&r->nexthop, rn, entry);
free(rn);
}
}
void
rt_nexthop_add(struct rt_node *r, struct v_nexthead *vnh, u_int16_t type,
struct in_addr adv_rtr)
{
struct v_nexthop *vn;
struct rt_nexthop *rn;
struct timespec now;
TAILQ_FOREACH(vn, vnh, entry) {
TAILQ_FOREACH(rn, &r->nexthop, entry) {
if (!IN6_ARE_ADDR_EQUAL(&rn->nexthop, &vn->nexthop))
continue;
rn->adv_rtr.s_addr = adv_rtr.s_addr;
rn->connected = (type == LSA_TYPE_NETWORK &&
vn->prev == spf_root) ||
(IN6_IS_ADDR_UNSPECIFIED(&vn->nexthop));
rn->invalid = 0;
r->invalid = 0;
break;
}
if (rn)
continue;
if ((rn = calloc(1, sizeof(struct rt_nexthop))) == NULL)
fatal("rt_nexthop_add");
clock_gettime(CLOCK_MONOTONIC, &now);
rn->nexthop = vn->nexthop;
rn->ifindex = vn->ifindex;
rn->adv_rtr.s_addr = adv_rtr.s_addr;
rn->uptime = now.tv_sec;
rn->connected = (type == LSA_TYPE_NETWORK &&
vn->prev == spf_root) ||
(IN6_IS_ADDR_UNSPECIFIED(&vn->nexthop));
rn->invalid = 0;
r->invalid = 0;
TAILQ_INSERT_TAIL(&r->nexthop, rn, entry);
}
}
void
rt_clear(void)
{
struct rt_node *r;
while ((r = RB_MIN(rt_tree, &rt)) != NULL)
rt_remove(r);
}
void
rt_dump(struct in_addr area, pid_t pid, u_int8_t r_type)
{
static struct ctl_rt rtctl;
struct timespec now;
struct rt_node *r;
struct rt_nexthop *rn;
clock_gettime(CLOCK_MONOTONIC, &now);
RB_FOREACH(r, rt_tree, &rt) {
if (r->invalid)
continue;
if (r->area.s_addr != area.s_addr)
continue;
switch (r_type) {
case RIB_RTR:
if (r->d_type != DT_RTR)
continue;
break;
case RIB_NET:
if (r->d_type != DT_NET)
continue;
if (r->p_type == PT_TYPE1_EXT ||
r->p_type == PT_TYPE2_EXT)
continue;
break;
case RIB_EXT:
if (r->p_type != PT_TYPE1_EXT &&
r->p_type != PT_TYPE2_EXT)
continue;
break;
default:
fatalx("rt_dump: invalid RIB type");
}
memset(&rtctl, 0, sizeof(rtctl));
rtctl.prefix = r->prefix;
rtctl.area.s_addr = r->area.s_addr;
rtctl.cost = r->cost;
rtctl.cost2 = r->cost2;
rtctl.p_type = r->p_type;
rtctl.d_type = r->d_type;
rtctl.flags = r->flags;
rtctl.prefixlen = r->prefixlen;
TAILQ_FOREACH(rn, &r->nexthop, entry) {
if (rn->invalid)
continue;
rtctl.connected = rn->connected;
rtctl.nexthop = rn->nexthop;
rtctl.ifindex = rn->ifindex;
rtctl.adv_rtr.s_addr = rn->adv_rtr.s_addr;
rtctl.uptime = now.tv_sec - rn->uptime;
rde_imsg_compose_ospfe(IMSG_CTL_SHOW_RIB, 0, pid,
&rtctl, sizeof(rtctl));
}
}
}
void
rt_update(struct in6_addr *prefix, u_int8_t prefixlen, struct v_nexthead *vnh,
u_int16_t v_type, u_int32_t cost, u_int32_t cost2, struct in_addr area,
struct in_addr adv_rtr, enum path_type p_type, enum dst_type d_type,
u_int8_t flags, u_int32_t tag)
{
struct rt_node *rte;
struct rt_nexthop *rn;
int better = 0, equal = 0;
if (vnh == NULL || TAILQ_EMPTY(vnh)) /* XXX remove */
fatalx("rt_update: invalid nexthop");
if ((rte = rt_find(prefix, prefixlen, d_type)) == NULL) {
if ((rte = calloc(1, sizeof(struct rt_node))) == NULL)
fatal("rt_update");
TAILQ_INIT(&rte->nexthop);
rte->prefix = *prefix;
rte->prefixlen = prefixlen;
rte->cost = cost;
rte->cost2 = cost2;
rte->area = area;
rte->p_type = p_type;
rte->d_type = d_type;
rte->flags = flags;
rte->ext_tag = tag;
rt_nexthop_add(rte, vnh, v_type, adv_rtr);
rt_insert(rte);
} else {
/* order:
* 1. intra-area
* 2. inter-area
* 3. type 1 as ext
* 4. type 2 as ext
*/
if (rte->invalid) /* everything is better than invalid */
better = 1;
else if (p_type < rte->p_type)
better = 1;
else if (p_type == rte->p_type)
switch (p_type) {
case PT_INTRA_AREA:
case PT_INTER_AREA:
if (cost < rte->cost)
better = 1;
else if (cost == rte->cost &&
rte->area.s_addr == area.s_addr)
equal = 1;
break;
case PT_TYPE1_EXT:
if (cost < rte->cost)
better = 1;
else if (cost == rte->cost)
equal = 1;
break;
case PT_TYPE2_EXT:
if (cost2 < rte->cost2)
better = 1;
else if (cost2 == rte->cost2 &&
cost < rte->cost)
better = 1;
else if (cost2 == rte->cost2 &&
cost == rte->cost)
equal = 1;
break;
}
if (better) {
TAILQ_FOREACH(rn, &rte->nexthop, entry)
rn->invalid = 1;
rte->area = area;
rte->cost = cost;
rte->cost2 = cost2;
rte->p_type = p_type;
rte->flags = flags;
rte->ext_tag = tag;
}
if (equal || better)
rt_nexthop_add(rte, vnh, v_type, adv_rtr);
}
}
struct rt_node *
rt_lookup(enum dst_type type, struct in6_addr *addr)
{
struct rt_node *rn;
struct in6_addr ina;
u_int8_t i = 128;
if (type == DT_RTR) {
rn = rt_find(addr, 128, type);
if (rn && rn->invalid == 0)
return (rn);
return (NULL);
}
/* type == DT_NET */
do {
inet6applymask(&ina, addr, i);
if ((rn = rt_find(&ina, i, type)) && rn->invalid == 0)
return (rn);
} while (i-- != 0);
return (NULL);
}
/* router LSA links */
struct lsa_rtr_link *
get_rtr_link(struct vertex *v, unsigned int idx)
{
struct lsa_rtr_link *rtr_link = NULL;
unsigned int frag = 1;
unsigned int frag_nlinks;
unsigned int nlinks = 0;
unsigned int i;
if (v->type != LSA_TYPE_ROUTER)
fatalx("get_rtr_link: invalid LSA type");
/* Treat multiple Router-LSAs originated by the same router
* as an aggregate. */
do {
/* number of links validated earlier by lsa_check() */
rtr_link = (struct lsa_rtr_link *)((char *)v->lsa +
sizeof(v->lsa->hdr) + sizeof(struct lsa_rtr));
frag_nlinks = ((ntohs(v->lsa->hdr.len) -
sizeof(struct lsa_hdr) - sizeof(struct lsa_rtr)) /
sizeof(struct lsa_rtr_link));
if (nlinks + frag_nlinks > idx) {
for (i = 0; i < frag_nlinks; i++) {
if (i + nlinks == idx)
return (rtr_link);
rtr_link++;
}
}
nlinks += frag_nlinks;
v = lsa_find_rtr_frag(v->area, htonl(v->adv_rtr), frag++);
} while (v);
fatalx("get_rtr_link: index not found");
}
/* network LSA links */
struct lsa_net_link *
get_net_link(struct vertex *v, unsigned int idx)
{
struct lsa_net_link *net_link = NULL;
char *buf = (char *)v->lsa;
unsigned int i, nlinks;
if (v->type != LSA_TYPE_NETWORK)
fatalx("get_net_link: invalid LSA type");
net_link = (struct lsa_net_link *)(buf + sizeof(v->lsa->hdr) +
sizeof(struct lsa_net));
/* number of links validated earlier by lsa_check() */
nlinks = lsa_num_links(v);
for (i = 0; i < nlinks; i++) {
if (i == idx)
return (net_link);
net_link++;
}
fatalx("get_net_link: index not found");
}
/* misc */
int
linked(struct vertex *w, struct vertex *v)
{
struct lsa_rtr_link *rtr_link = NULL;
struct lsa_net_link *net_link = NULL;
unsigned int i;
switch (w->type) {
case LSA_TYPE_ROUTER:
for (i = 0; i < lsa_num_links(w); i++) {
rtr_link = get_rtr_link(w, i);
switch (v->type) {
case LSA_TYPE_ROUTER:
if (rtr_link->type == LINK_TYPE_POINTTOPOINT &&
rtr_link->nbr_rtr_id == htonl(v->adv_rtr))
return (1);
break;
case LSA_TYPE_NETWORK:
if (rtr_link->type == LINK_TYPE_TRANSIT_NET &&
rtr_link->nbr_rtr_id == htonl(v->adv_rtr) &&
rtr_link->nbr_iface_id == htonl(v->ls_id))
return (1);
break;
default:
fatalx("linked: invalid type");
}
}
return (0);
case LSA_TYPE_NETWORK:
for (i = 0; i < lsa_num_links(w); i++) {
net_link = get_net_link(w, i);
switch (v->type) {
case LSA_TYPE_ROUTER:
if (net_link->att_rtr == htonl(v->adv_rtr))
return (1);
break;
default:
fatalx("linked: invalid type");
}
}
return (0);
default:
fatalx("linked: invalid LSA type");
}
return (0);
}