bird/proto/ospf/topology.c
2009-08-25 16:42:14 +02:00

1524 lines
35 KiB
C

/*
* BIRD -- OSPF Topological Database
*
* (c) 1999 Martin Mares <mj@ucw.cz>
* (c) 1999--2004 Ondrej Filip <feela@network.cz>
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include "nest/bird.h"
#include "lib/string.h"
#include "ospf.h"
#define HASH_DEF_ORDER 6
#define HASH_HI_MARK *4
#define HASH_HI_STEP 2
#define HASH_HI_MAX 16
#define HASH_LO_MARK /5
#define HASH_LO_STEP 2
#define HASH_LO_MIN 8
void originate_prefix_rt_lsa(struct ospf_area *oa);
void originate_prefix_net_lsa(struct ospf_iface *ifa);
#ifdef OSPFv2
#define ipa_to_rid(x) _I(x)
#else /* OSPFv3 */
#define ipa_to_rid(x) _I3(x)
#endif
/* FIXME very ugly hack */
#ifdef OSPFv2
#define ipa_to_lsaid(x) _I(x)
#else /* OSPFv3 */
#define ipa_to_lsaid(x) _I0(x) ^ _I1(x) ^ _I2(x) ^ _I3(x)
#endif
static void *
lsab_alloc(struct proto_ospf *po, unsigned size)
{
unsigned offset = po->lsab_used;
po->lsab_used += size;
if (po->lsab_used > po->lsab_size)
{
po->lsab_size = MAX(po->lsab_used, 2 * po->lsab_size);
po->lsab = mb_realloc(po->proto.pool, po->lsab, po->lsab_size);
}
return ((byte *) po->lsab) + offset;
}
static inline void *
lsab_allocz(struct proto_ospf *po, unsigned size)
{
void *r = lsab_alloc(po, size);
bzero(r, size);
return r;
}
static inline void *
lsab_flush(struct proto_ospf *po)
{
void *r = mb_alloc(po->proto.pool, po->lsab_size);
memcpy(r, po->lsab, po->lsab_used);
po->lsab_used = 0;
return r;
}
static inline void *
lsab_offset(struct proto_ospf *po, unsigned offset)
{
return ((byte *) po->lsab) + offset;
}
static inline void *
lsab_end(struct proto_ospf *po)
{
return ((byte *) po->lsab) + po->lsab_used;
}
#ifdef OSPFv3
#define IPV6_PREFIX_SPACE(x) ((((x) + 63) / 32) * 4)
#define IPV6_PREFIX_WORDS(x) (((x) + 63) / 32)
static inline u32 *
put_ipv6_prefix(u32 *buf, ip_addr addr, u8 pxlen, u8 pxopts, u16 lh)
{
*buf++ = ((pxlen << 24) | (pxopts << 16) | lh);
if (pxlen > 0)
*buf++ = _I0(addr);
if (pxlen > 32)
*buf++ = _I1(addr);
if (pxlen > 64)
*buf++ = _I2(addr);
if (pxlen > 96)
*buf++ = _I3(addr);
return buf;
}
static inline u32 *
put_ipv6_addr(u32 *buf, ip_addr addr)
{
*(ip_addr *) buf = addr;
return buf + 4;
}
#endif
static int
configured_stubnet(struct ospf_area *oa, struct ifa *a)
{
struct ospf_stubnet_config *sn;
WALK_LIST(sn, oa->ac->stubnet_list)
{
if (sn->summary)
{
if (ipa_in_net(a->prefix, sn->px.addr, sn->px.len) && (a->pxlen >= sn->px.len))
return 1;
}
else
{
if (ipa_equal(a->prefix, sn->px.addr) && (a->pxlen == sn->px.len))
return 1;
}
}
return 0;
}
int
bcast_net_active(struct ospf_iface *ifa)
{
struct ospf_neighbor *neigh;
if (ifa->state == OSPF_IS_WAITING)
return 0;
WALK_LIST(neigh, ifa->neigh_list)
{
if (neigh->state == NEIGHBOR_FULL)
{
if (neigh->rid == ifa->drid)
return 1;
if (ifa->state == OSPF_IS_DR)
return 1;
}
}
return 0;
}
#ifdef OSPFv2
static void *
originate_rt_lsa_body(struct ospf_area *oa, u16 *length)
{
struct proto_ospf *po = oa->po;
struct ospf_iface *ifa;
int i = 0, bitv = 0;
struct ospf_lsa_rt *rt;
struct ospf_lsa_rt_link *ln;
struct ospf_neighbor *neigh;
DBG("%s: Originating RT_lsa body for area %R.\n", po->proto.name,
oa->areaid);
ASSERT(po->lsab_used == 0);
rt = lsab_allocz(po, sizeof(struct ospf_lsa_rt));
rt->options = 0
if (po->areano > 1)
rt->options |= OPT_RT_B;
if ((po->ebit) && (!oa->stub))
rt->options |= OPT_RT_E;
rt = NULL; /* buffer might be reallocated later */
WALK_LIST(ifa, po->iface_list)
{
int master = 0;
if ((ifa->type == OSPF_IT_VLINK) && (ifa->voa == oa) &&
(!EMPTY_LIST(ifa->neigh_list)))
{
neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list);
if ((neigh->state == NEIGHBOR_FULL) && (ifa->cost <= 0xffff))
bitv = 1;
}
if ((ifa->oa != oa) || (ifa->state == OSPF_IS_DOWN))
continue;
/* BIRD does not support interface loops */
ASSERT(ifa->state != OSPF_IS_LOOP);
switch (ifa->type)
{
case OSPF_IT_PTP: /* RFC2328 - 12.4.1.1 */
neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list);
if ((!EMPTY_LIST(ifa->neigh_list)) && (neigh->state == NEIGHBOR_FULL))
{
ln = lsab_alloc(po, sizeof(struct ospf_lsa_rt_link));
ln->type = LSART_PTP;
ln->id = neigh->rid;
ln->data = (ifa->iface->addr->flags & IA_UNNUMBERED) ?
ifa->iface->index : ipa_to_u32(ifa->iface->addr->ip);
ln->metric = ifa->cost;
ln->notos = 0;
i++;
master = 1;
}
break;
case OSPF_IT_BCAST: /* RFC2328 - 12.4.1.2 */
case OSPF_IT_NBMA:
if (bcast_net_active(ifa))
{
ln = lsab_alloc(po, sizeof(struct ospf_lsa_rt_link));
ln->type = LSART_NET;
ln->id = ipa_to_u32(ifa->drip);
ln->data = ipa_to_u32(ifa->iface->addr->ip);
ln->metric = ifa->cost;
ln->notos = 0;
i++;
master = 1;
}
break;
case OSPF_IT_VLINK: /* RFC2328 - 12.4.1.3 */
neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list);
if ((!EMPTY_LIST(ifa->neigh_list)) && (neigh->state == NEIGHBOR_FULL) && (ifa->cost <= 0xffff))
{
ln = lsab_alloc(po, sizeof(struct ospf_lsa_rt_link));
ln->type = LSART_VLNK;
ln->id = neigh->rid;
ln->data = ipa_to_u32(ifa->iface->addr->ip);
ln->metric = ifa->cost;
ln->notos = 0;
i++;
master = 1;
}
break;
default:
log("Unknown interface type %s", ifa->iface->name);
break;
}
/* Now we will originate stub areas for interfaces addresses */
struct ifa *a;
WALK_LIST(a, ifa->iface->addrs)
{
if (((a == ifa->iface->addr) && master) ||
(a->flags & IA_SECONDARY) ||
(a->flags & IA_UNNUMBERED) ||
configured_stubnet(oa, a))
continue;
ln = lsab_alloc(po, sizeof(struct ospf_lsa_rt_link));
ln->type = LSART_STUB;
ln->id = ipa_to_u32(a->prefix);
ln->data = ipa_to_u32(ipa_mkmask(a->pxlen));
ln->metric = ifa->cost;
ln->notos = 0;
i++;
}
}
struct ospf_stubnet_config *sn;
WALK_LIST(sn, oa->ac->stubnet_list)
if (!sn->hidden)
{
ln = lsab_alloc(po, sizeof(struct ospf_lsa_rt_link));
ln->type = LSART_STUB;
ln->id = ipa_to_u32(sn->px.addr);
ln->data = ipa_to_u32(ipa_mkmask(sn->px.len));
ln->metric = sn->cost;
ln->notos = 0;
i++;
}
rt = po->lsab;
rt->links = i;
if (bitv)
rt->options |= OPT_RT_V;
*length = po->lsab_used + sizeof(struct ospf_lsa_header);
return lsab_flush(po);
}
#else /* OSPFv3 */
static void
add_lsa_rt_link(struct proto_ospf *po, struct ospf_iface *ifa, u8 type, u32 nif, u32 id)
{
struct ospf_lsa_rt_link *ln = lsab_alloc(po, sizeof(struct ospf_lsa_rt_link));
ln->type = type;
ln->padding = 0;
ln->metric = ifa->cost;
ln->lif = ifa->iface->index;
ln->nif = nif;
ln->id = id;
}
static void *
originate_rt_lsa_body(struct ospf_area *oa, u16 *length)
{
struct proto_ospf *po = oa->po;
struct ospf_iface *ifa;
int bitv = 0;
struct ospf_lsa_rt *rt;
struct ospf_neighbor *neigh;
DBG("%s: Originating RT_lsa body for area %R.\n", po->proto.name,
oa->areaid);
ASSERT(po->lsab_used == 0);
rt = lsab_allocz(po, sizeof(struct ospf_lsa_rt));
rt->options = oa->options & OPTIONS_MASK;
if (po->areano > 1)
rt->options |= OPT_RT_B;
if ((po->ebit) && (!oa->stub))
rt->options |= OPT_RT_E;
rt = NULL; /* buffer might be reallocated later */
WALK_LIST(ifa, po->iface_list)
{
if ((ifa->type == OSPF_IT_VLINK) && (ifa->voa == oa) &&
(!EMPTY_LIST(ifa->neigh_list)))
{
neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list);
if ((neigh->state == NEIGHBOR_FULL) && (ifa->cost <= 0xffff))
bitv = 1;
}
if ((ifa->oa != oa) || (ifa->state == OSPF_IS_DOWN))
continue;
/* BIRD does not support interface loops */
ASSERT(ifa->state != OSPF_IS_LOOP);
/* RFC5340 - 4.4.3.2 */
switch (ifa->type)
{
case OSPF_IT_PTP:
neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list);
if ((!EMPTY_LIST(ifa->neigh_list)) && (neigh->state == NEIGHBOR_FULL))
add_lsa_rt_link(po, ifa, LSART_PTP, neigh->iface_id, neigh->rid);
break;
case OSPF_IT_BCAST:
case OSPF_IT_NBMA:
if (bcast_net_active(ifa))
add_lsa_rt_link(po, ifa, LSART_NET, ifa->dr_iface_id, ifa->drid);
break;
case OSPF_IT_VLINK:
neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list);
if ((!EMPTY_LIST(ifa->neigh_list)) && (neigh->state == NEIGHBOR_FULL) && (ifa->cost <= 0xffff))
add_lsa_rt_link(po, ifa, LSART_VLNK, neigh->iface_id, neigh->rid);
break;
default:
log("Unknown interface type %s", ifa->iface->name);
break;
}
}
if (bitv)
{
rt = po->lsab;
rt->options |= OPT_RT_V;
}
*length = po->lsab_used + sizeof(struct ospf_lsa_header);
return lsab_flush(po);
}
#endif
/**
* originate_rt_lsa - build new instance of router LSA
* @oa: ospf_area which is LSA built to
*
* It builds router LSA walking through all OSPF interfaces in
* specified OSPF area. This function is mostly called from
* area_disp(). Builds new LSA, increases sequence number (if old
* instance exists) and sets age of LSA to zero.
*/
void
originate_rt_lsa(struct ospf_area *oa)
{
struct ospf_lsa_header lsa;
struct proto_ospf *po = oa->po;
struct proto *p = &po->proto;
u32 rid = po->proto.cf->global->router_id;
void *body;
OSPF_TRACE(D_EVENTS, "Originating RT_lsa for area %R.", oa->areaid);
lsa.age = 0;
lsa.type = LSA_T_RT;
#ifdef OSPFv2
lsa.options = oa->options;
#endif
lsa.id = rid;
lsa.rt = rid;
lsa.sn = oa->rt ? (oa->rt->lsa.sn + 1) : LSA_INITSEQNO;
u32 dom = oa->areaid;
body = originate_rt_lsa_body(oa, &lsa.length);
lsasum_calculate(&lsa, body);
oa->rt = lsa_install_new(po, &lsa, dom, body);
ospf_lsupd_flood(po, NULL, NULL, &lsa, dom, 1);
}
void
update_rt_lsa(struct ospf_area *oa)
{
struct proto_ospf *po = oa->po;
if ((oa->rt) && ((oa->rt->inst_t + MINLSINTERVAL)) > now)
return;
/*
* Tick is probably set to very low value. We cannot
* originate new LSA before MINLSINTERVAL. We will
* try to do it next tick.
*/
originate_rt_lsa(oa);
originate_prefix_rt_lsa(oa);
schedule_rtcalc(po);
oa->origrt = 0;
}
static void *
originate_net_lsa_body(struct ospf_iface *ifa, u16 *length,
struct proto_ospf *po)
{
u16 i = 1;
struct ospf_neighbor *n;
struct ospf_lsa_net *net;
int nodes = ifa->fadj + 1;
net = mb_alloc(po->proto.pool, sizeof(struct ospf_lsa_net)
+ nodes * sizeof(u32));
#ifdef OSPFv2
net->netmask = ipa_mkmask(ifa->iface->addr->pxlen);
#endif
#ifdef OSPFv3
/* In OSPFv3, we would like to merge options from Link LSAs of added neighbors */
struct top_hash_entry *en;
u32 options = 0;
#endif
net->routers[0] = po->proto.cf->global->router_id;
WALK_LIST(n, ifa->neigh_list)
{
if (n->state == NEIGHBOR_FULL)
{
#ifdef OSPFv3
en = ospf_hash_find(po->gr, ifa->iface->index, n->iface_id, n->rid, LSA_T_LINK);
if (en)
options |= ((struct ospf_lsa_link *) en->lsa_body)->options;
#endif
net->routers[i] = n->rid;
i++;
}
}
ASSERT(i == nodes);
#ifdef OSPFv3
net->options = options & OPTIONS_MASK;
#endif
*length = sizeof(struct ospf_lsa_header) + sizeof(struct ospf_lsa_net)
+ nodes * sizeof(u32);
return net;
}
/**
* originate_net_lsa - originates of deletes network LSA
* @ifa: interface which is LSA originated for
*
* Interface counts number of adjacent neighbors. If this number is
* lower than one or interface is not in state %OSPF_IS_DR it deletes
* and premature ages instance of network LSA for specified interface.
* In other case, new instance of network LSA is originated.
*/
void
originate_net_lsa(struct ospf_iface *ifa)
{
struct proto_ospf *po = ifa->oa->po;
struct ospf_lsa_header lsa;
u32 rid = po->proto.cf->global->router_id;
u32 dom = ifa->oa->areaid;
struct proto *p = &po->proto;
void *body;
OSPF_TRACE(D_EVENTS, "Originating Net lsa for iface \"%s\".",
ifa->iface->name);
lsa.age = 0;
lsa.type = LSA_T_NET;
#ifdef OSPFv2
lsa.options = ifa->oa->options;
lsa.id = ipa_to_u32(ifa->iface->addr->ip);
#else /* OSPFv3 */
lsa.id = ifa->iface->index;
#endif
lsa.rt = rid;
lsa.sn = ifa->net_lsa ? (ifa->net_lsa->lsa.sn + 1) : LSA_INITSEQNO;
body = originate_net_lsa_body(ifa, &lsa.length, po);
lsasum_calculate(&lsa, body);
ifa->net_lsa = lsa_install_new(po, &lsa, dom, body);
ospf_lsupd_flood(po, NULL, NULL, &lsa, dom, 1);
}
void
flush_net_lsa(struct ospf_iface *ifa)
{
struct proto_ospf *po = ifa->oa->po;
struct proto *p = &po->proto;
u32 dom = ifa->oa->areaid;
if (ifa->net_lsa == NULL)
return;
OSPF_TRACE(D_EVENTS, "Deleting Net lsa for iface \"%s\".",
ifa->iface->name);
ifa->net_lsa->lsa.sn += 1;
ifa->net_lsa->lsa.age = LSA_MAXAGE;
lsasum_calculate(&ifa->net_lsa->lsa, ifa->net_lsa->lsa_body);
ospf_lsupd_flood(po, NULL, NULL, &ifa->net_lsa->lsa, dom, 0);
flush_lsa(ifa->net_lsa, po);
ifa->net_lsa = NULL;
}
void
update_net_lsa(struct ospf_iface *ifa)
{
struct proto_ospf *po = ifa->oa->po;
if (ifa->net_lsa && ((ifa->net_lsa->inst_t + MINLSINTERVAL) > now))
return;
/*
* It's too early to originate new network LSA. We will
* try to do it next tick
*/
if ((ifa->state != OSPF_IS_DR) || (ifa->fadj == 0))
{
flush_net_lsa(ifa);
flush_prefix_net_lsa(ifa);
}
else
{
originate_net_lsa(ifa);
originate_prefix_net_lsa(ifa);
}
schedule_rtcalc(po);
ifa->orignet = 0;
}
#ifdef OSPFv2
static void *
originate_sum_lsa_body(struct proto_ospf *po, u16 *length, u32 mlen, u32 metric)
{
struct ospf_lsa_sum *sum = mb_alloc(po->proto.pool, sizeof(struct ospf_lsa_sum));
*length = sizeof(struct ospf_lsa_header) + sizeof(struct ospf_lsa_sum);
sum->netmask = ipa_mkmask(mlen);
sum->metric = metric;
return sum;
}
#define originate_sum_net_lsa_body(po,length,fn,metric) \
originate_sum_lsa_body(po, length, (fn)->pxlen, metric)
#define originate_sum_rt_lsa_body(po,length,drid,metric,options) \
originate_sum_lsa_body(po, length, 0, metric)
#else /* OSPFv3 */
static void *
originate_sum_net_lsa_body(struct proto_ospf *po, u16 *length, struct fib_node *fn, u32 metric)
{
int size = sizeof(struct ospf_lsa_sum_net) + IPV6_PREFIX_SPACE(fn->pxlen);
struct ospf_lsa_sum_net *sum = mb_alloc(po->proto.pool, size);
*length = sizeof(struct ospf_lsa_header) + size;
sum->metric = metric;
put_ipv6_prefix(sum->prefix, fn->prefix, fn->pxlen, 0, 0);
return sum;
}
static void *
originate_sum_rt_lsa_body(struct proto_ospf *po, u16 *length, u32 drid, u32 metric, u32 options)
{
struct ospf_lsa_sum_rt *sum = mb_alloc(po->proto.pool, sizeof(struct ospf_lsa_sum_rt));
*length = sizeof(struct ospf_lsa_header) + sizeof(struct ospf_lsa_sum_rt);
sum->options = options & OPTIONS_MASK;
sum->metric = metric;
sum->drid = drid;
return sum;
}
#endif
void
originate_sum_lsa(struct ospf_area *oa, struct fib_node *fn, int type, int metric, u32 options)
{
struct proto_ospf *po = oa->po;
struct proto *p = &po->proto;
struct top_hash_entry *en;
u32 rid = po->proto.cf->global->router_id;
struct ospf_lsa_header lsa;
void *body;
/* options argument is used in ORT_NET and OSPFv3 only */
lsa.age = 0;
lsa.rt = rid;
lsa.sn = LSA_INITSEQNO;
#ifdef OSPFv2
lsa.options = oa->options;
#endif
if (type == ORT_NET)
{
/* FIXME proper handling of LSA IDs and check for the same network */
lsa.id = ipa_to_lsaid(fn->prefix);
lsa.type = LSA_T_SUM_NET;
}
else
{
/* In OSPFv6, LSA ID is meaningless, but we still use Router ID of ASBR */
lsa.id = ipa_to_rid(fn->prefix);
lsa.type = LSA_T_SUM_RT;
}
u32 dom = oa->areaid;
/* FIXME check for the same LSA */
if ((en = ospf_hash_find_header(po->gr, oa->areaid, &lsa)) != NULL)
lsa.sn = en->lsa.sn + 1;
if (type == ORT_NET)
{
OSPF_TRACE(D_EVENTS, "Originating Net-Summary-LSA for %I/%d (metric %d).",
fn->prefix, fn->pxlen, metric);
body = originate_sum_net_lsa_body(po, &lsa.length, fn, metric);
}
else
{
OSPF_TRACE(D_EVENTS, "Originating RT-Summary-LSA for %R (metric %d).",
lsa.id, metric);
body = originate_sum_rt_lsa_body(po, &lsa.length, lsa.id, metric, options);
}
lsasum_calculate(&lsa, body);
en = lsa_install_new(po, &lsa, dom, body);
ospf_lsupd_flood(po, NULL, NULL, &lsa, dom, 1);
}
void
flush_sum_lsa(struct ospf_area *oa, struct fib_node *fn, int type)
{
struct proto_ospf *po = oa->po;
struct proto *p = &po->proto;
struct top_hash_entry *en;
u32 rid = po->proto.cf->global->router_id;
struct ospf_lsa_header lsa;
lsa.rt = rid;
if (type == ORT_NET)
{
/* FIXME proper handling of LSA IDs and check for the same network */
lsa.id = ipa_to_lsaid(fn->prefix);
lsa.type = LSA_T_SUM_NET;
}
else
{
/* In OSPFv6, LSA ID is meaningless, but we still use Router ID of ASBR */
lsa.id = ipa_to_rid(fn->prefix);
lsa.type = LSA_T_SUM_RT;
}
if ((en = ospf_hash_find_header(po->gr, oa->areaid, &lsa)) != NULL)
{
struct ospf_lsa_sum *sum = en->lsa_body;
en->lsa.age = LSA_MAXAGE;
en->lsa.sn = LSA_MAXSEQNO;
lsasum_calculate(&en->lsa, sum);
OSPF_TRACE(D_EVENTS, "Flushing summary lsa. (id=%R, type=%d)",
en->lsa.id, en->lsa.type);
ospf_lsupd_flood(po, NULL, NULL, &en->lsa, oa->areaid, 1);
if (can_flush_lsa(po)) flush_lsa(en, po);
}
}
void
check_sum_lsa(struct proto_ospf *po, ort *nf, int dest)
{
struct ospf_area *oa;
int flush, mlen;
ip_addr ip;
if (po->areano < 2) return;
if ((nf->n.type > RTS_OSPF_IA) && (nf->o.type > RTS_OSPF_IA)) return;
#ifdef LOCAL_DEBUG
DBG("Checking...dest = %d, %I/%d\n", dest, nf->fn.prefix, nf->fn.pxlen);
if (nf->n.oa) DBG("New: met=%d, oa=%d\n", nf->n.metric1, nf->n.oa->areaid);
if (nf->o.oa) DBG("Old: met=%d, oa=%d\n", nf->o.metric1, nf->o.oa->areaid);
#endif
WALK_LIST(oa, po->area_list)
{
flush = 0;
if ((nf->n.metric1 >= LSINFINITY) || (nf->n.type > RTS_OSPF_IA))
flush = 1;
if ((dest == ORT_ROUTER) && (!(nf->n.options & ORTA_ASBR)))
flush = 1;
if ((!nf->n.oa) || (nf->n.oa->areaid == oa->areaid))
flush = 1;
if (nf->n.ifa) {
if (nf->n.ifa->oa->areaid == oa->areaid)
flush = 1;
}
else flush = 1;
/* Don't send summary into stub areas
* We send just default route (and later) */
if (oa->stub)
flush = 1;
mlen = nf->fn.pxlen;
ip = ipa_and(nf->fn.prefix, ipa_mkmask(mlen));
if ((oa == po->backbone) && (nf->n.type == RTS_OSPF_IA))
flush = 1; /* Only intra-area can go to the backbone */
if ((!flush) && (dest == ORT_NET) && fib_route(&nf->n.oa->net_fib, ip, mlen))
{
/* The route fits into area networks */
flush = 1;
if ((nf->n.oa == po->backbone) && (oa->trcap)) flush = 0;
}
if (flush)
flush_sum_lsa(oa, &nf->fn, dest);
else
originate_sum_lsa(oa, &nf->fn, dest, nf->n.metric1, nf->n.options);
}
}
static void *
originate_ext_lsa_body(net *n, rte *e, u16 *length, struct proto_ospf *po,
struct ea_list *attrs)
{
struct proto *p = &po->proto;
struct ospf_lsa_ext *ext;
u32 m1 = ea_get_int(attrs, EA_OSPF_METRIC1, LSINFINITY);
u32 m2 = ea_get_int(attrs, EA_OSPF_METRIC2, 10000);
u32 tag = ea_get_int(attrs, EA_OSPF_TAG, 0);
int gw = 0;
int size = sizeof(struct ospf_lsa_ext);
u32 *buf;
if (!ipa_equal(e->attrs->gw, IPA_NONE))
{
/* FIXME: check for link-local in OSPFv3 ? */
if (ospf_iface_find((struct proto_ospf *) p, e->attrs->iface) != NULL)
gw = 1;
}
#ifdef OSPFv3
size += IPV6_PREFIX_SPACE(n->n.pxlen);
if (gw)
size += 16;
if (tag)
size += 4;
#endif
ext = mb_alloc(p->pool, size);
*length = sizeof(struct ospf_lsa_header) + size;
ext->metric = (m1 != LSINFINITY) ? m1 : (m2 & LSA_EXT_EBIT);
#ifdef OSPFv2
ext->netmask = ipa_mkmask(n->n.pxlen);
ext->fwaddr = gw ? IPA_NONE : e->attrs->gw;
ext->tag = tag;
#else /* OSPFv3 */
buf = ext->rest;
buf = put_ipv6_prefix(buf, n->n.prefix, n->n.pxlen, 0, 0);
if (gw)
{
ext->metric |= LSA_EXT_FBIT;
buf = put_ipv6_addr(buf, e->attrs->gw);
}
if (tag)
{
ext->metric |= LSA_EXT_TBIT;
*buf++ = tag;
}
#endif
return ext;
}
/**
* originate_ext_lsa - new route received from nest and filters
* @n: network prefix and mask
* @e: rte
* @po: current instance of OSPF
* @attrs: list of extended attributes
*
* If I receive a message that new route is installed, I try to originate an
* external LSA. The LSA header of such LSA does not contain information about
* prefix length, so if I have to originate multiple LSAs for route with
* different prefixes I try to increment prefix id to find a "free" one.
*
* The function also sets flag ebit. If it's the first time, the new router lsa
* origination is necessary.
*/
void
originate_ext_lsa(net * n, rte * e, struct proto_ospf *po,
struct ea_list *attrs)
{
struct ospf_lsa_header lsa;
u32 rid = po->proto.cf->global->router_id;
struct top_hash_entry *en = NULL;
void *body;
struct proto *p = &po->proto;
struct ospf_area *oa;
OSPF_TRACE(D_EVENTS, "Originating Ext lsa for %I/%d.", n->n.prefix,
n->n.pxlen);
lsa.age = 0;
lsa.type = LSA_T_EXT;
lsa.rt = rid;
lsa.sn = LSA_INITSEQNO;
#ifdef OSPFv2
lsa.options = 0; /* or oa->options ? */
#endif
/* FIXME proper handling of LSA IDs and check for the same network */
lsa.id = ipa_to_lsaid(n->n.prefix);
if ((en = ospf_hash_find_header(po->gr, 0, &lsa)) != NULL)
{
lsa.sn = en->lsa.sn + 1;
}
body = originate_ext_lsa_body(n, e, &lsa.length, po, attrs);
lsasum_calculate(&lsa, body);
en = lsa_install_new(po, &lsa, 0, body);
ospf_lsupd_flood(po, NULL, NULL, &lsa, 0, 1);
if (po->ebit == 0)
{
po->ebit = 1;
WALK_LIST(oa, po->area_list)
{
schedule_rt_lsa(oa);
}
}
}
void
flush_ext_lsa(net *n, struct proto_ospf *po)
{
u32 rid = po->proto.cf->global->router_id;
struct ospf_area *oa;
struct top_hash_entry *en;
/* FIXME proper handling of LSA IDs and check for the same network */
u32 lsaid = ipa_to_lsaid(n->n.prefix);
if (en = ospf_hash_find(po->gr, 0, lsaid, rid, LSA_T_EXT))
{
/* FIXME this is nonsense */
WALK_LIST(oa, po->area_list)
{
ospf_lsupd_flush_nlsa(po, en);
}
}
}
#ifdef OSPFv3
static void *
originate_link_lsa_body(struct ospf_iface *ifa, u16 *length)
{
struct proto_ospf *po = ifa->oa->po;
struct ospf_lsa_link *ll;
int i = 0;
u8 flags;
ASSERT(po->lsab_used == 0);
ll = lsab_allocz(po, sizeof(struct ospf_lsa_link));
ll->options = ifa->oa->options | (ifa->priority << 24);
ll->lladdr = ifa->lladdr;
ll = NULL; /* buffer might be reallocated later */
struct ifa *a;
WALK_LIST(a, ifa->iface->addrs)
{
if ((a->flags & IA_SECONDARY) ||
(a->scope < SCOPE_SITE))
continue;
flags = (a->pxlen < MAX_PREFIX_LENGTH) ? 0 : OPT_PX_LA;
put_ipv6_prefix(lsab_alloc(po, IPV6_PREFIX_SPACE(a->pxlen)),
a->ip, a->pxlen, flags, 0);
i++;
}
ll = po->lsab;
ll->pxcount = i;
*length = po->lsab_used + sizeof(struct ospf_lsa_header);
return lsab_flush(po);
}
void
originate_link_lsa(struct ospf_iface *ifa)
{
struct ospf_lsa_header lsa;
struct proto_ospf *po = ifa->oa->po;
struct proto *p = &po->proto;
u32 rid = po->proto.cf->global->router_id;
void *body;
/* FIXME check for vlink and skip that? */
OSPF_TRACE(D_EVENTS, "Originating Link_lsa for iface %s.", ifa->iface->name);
lsa.age = 0;
lsa.type = LSA_T_LINK;
lsa.id = ifa->iface->index;
lsa.rt = rid;
lsa.sn = ifa->link_lsa ? (ifa->link_lsa->lsa.sn + 1) : LSA_INITSEQNO;
u32 dom = ifa->iface->index;
body = originate_link_lsa_body(ifa, &lsa.length);
lsasum_calculate(&lsa, body);
ifa->link_lsa = lsa_install_new(po, &lsa, dom, body);
ospf_lsupd_flood(po, NULL, NULL, &lsa, dom, 1);
}
void
update_link_lsa(struct ospf_iface *ifa)
{
if (ifa->link_lsa && ((ifa->link_lsa->inst_t + MINLSINTERVAL) > now))
return;
/*
* It's too early to originate new link LSA. We will
* try to do it next tick
*/
originate_link_lsa(ifa);
ifa->origlink = 0;
}
static void *
originate_prefix_rt_lsa_body(struct ospf_area *oa, u16 *length)
{
struct proto_ospf *po = oa->po;
struct ospf_iface *ifa;
struct ospf_lsa_prefix *lp;
u32 rid = po->proto.cf->global->router_id;
int net_lsa;
int i = 0;
u8 flags;
ASSERT(po->lsab_used == 0);
lp = lsab_allocz(po, sizeof(struct ospf_lsa_prefix));
lp->ref_type = LSA_T_RT;
lp->ref_id = 0;
lp->ref_rt = rid;
lp = NULL; /* buffer might be reallocated later */
WALK_LIST(ifa, po->iface_list)
{
if ((ifa->oa != oa) || (ifa->state == OSPF_IS_DOWN))
continue;
if ((ifa->type == OSPF_IT_BCAST) ||
(ifa->type == OSPF_IT_NBMA))
net_lsa = bcast_net_active(ifa);
else
net_lsa = 0;
struct ifa *a;
WALK_LIST(a, ifa->iface->addrs)
{
if (((a->pxlen < MAX_PREFIX_LENGTH) && net_lsa) ||
(a->flags & IA_SECONDARY) ||
(a->flags & IA_UNNUMBERED) ||
configured_stubnet(oa, a))
continue;
flags = (a->pxlen < MAX_PREFIX_LENGTH) ? 0 : OPT_PX_LA;
put_ipv6_prefix(lsab_alloc(po, IPV6_PREFIX_SPACE(a->pxlen)),
a->ip, a->pxlen, flags, ifa->cost);
i++;
}
}
/* FIXME Handle vlinks? see RFC5340, page 38 */
struct ospf_stubnet_config *sn;
WALK_LIST(sn, oa->ac->stubnet_list)
if (!sn->hidden)
{
flags = (sn->px.len < MAX_PREFIX_LENGTH) ? 0 : OPT_PX_LA;
put_ipv6_prefix(lsab_alloc(po, IPV6_PREFIX_SPACE(sn->px.len)),
sn->px.addr, sn->px.len, flags, sn->cost);
i++;
}
lp = po->lsab;
lp->pxcount = i;
*length = po->lsab_used + sizeof(struct ospf_lsa_header);
return lsab_flush(po);
}
void
originate_prefix_rt_lsa(struct ospf_area *oa)
{
struct ospf_lsa_header lsa;
struct proto_ospf *po = oa->po;
u32 rid = po->proto.cf->global->router_id;
void *body;
lsa.age = 0;
lsa.type = LSA_T_PREFIX;
lsa.id = 1 << 31;
lsa.rt = rid;
lsa.sn = oa->pxr_lsa ? (oa->pxr_lsa->lsa.sn + 1) : LSA_INITSEQNO;
u32 dom = oa->areaid;
body = originate_prefix_rt_lsa_body(oa, &lsa.length);
lsasum_calculate(&lsa, body);
oa->pxr_lsa = lsa_install_new(po, &lsa, dom, body);
ospf_lsupd_flood(po, NULL, NULL, &lsa, dom, 1);
}
static inline int
prefix_space(u32 *buf)
{
int pxl = *buf >> 24;
return IPV6_PREFIX_SPACE(pxl);
}
static inline int
prefix_same(u32 *b1, u32 *b2)
{
int pxl1 = *b1 >> 24;
int pxl2 = *b2 >> 24;
int pxs, i;
if (pxl1 != pxl2)
return 0;
pxs = IPV6_PREFIX_WORDS(pxl1);
for (i = 1; i < pxs; i++)
if (b1[i] != b2[i])
return 0;
return 1;
}
static inline u32 *
prefix_advance(u32 *buf)
{
return buf + prefix_space(buf);
}
static void
add_prefix(struct proto_ospf *po, u32 *px, u32 *pxl, int *pxc)
{
int i;
for (i = 0; i < *pxc; i++)
{
if (prefix_same(px, pxl))
{
/* Options should be logically OR'ed together */
*pxl |= *px;
return;
}
pxl = prefix_advance(pxl);
}
ASSERT(pxl == lsab_end(po));
int pxspace = prefix_space(px);
pxl = lsab_alloc(po, pxspace);
memcpy(pxl, px, pxspace);
(*pxc)++;
}
static void *
originate_prefix_net_lsa_body(struct ospf_iface *ifa, u16 *length)
{
struct proto_ospf *po = ifa->oa->po;
struct ospf_lsa_prefix *lp;
struct ospf_lsa_link *ll;
struct ospf_neighbor *n;
struct top_hash_entry *en;
u32 rid = po->proto.cf->global->router_id;
u32 *pxb;
int i, j, offset;
ASSERT(po->lsab_used == 0);
lp = lsab_allocz(po, sizeof(struct ospf_lsa_prefix));
lp->ref_type = LSA_T_NET;
lp->ref_id = ifa->net_lsa->lsa.id;
lp->ref_rt = rid;
lp = NULL; /* buffer might be reallocated later */
i = 0;
offset = po->lsab_used;
/* Find all Link LSA associated with the link and merge their prefixes */
WALK_LIST(n, ifa->neigh_list)
if ((n->state == NEIGHBOR_FULL) &&
(en = ospf_hash_find(po->gr, ifa->iface->index, n->iface_id, n->rid, LSA_T_LINK)))
{
ll = en->lsa_body;
pxb = ll->rest;
for (j = 0; j < ll->pxcount; j++)
{
add_prefix(po, pxb, lsab_offset(po, offset), &i);
pxb = prefix_advance(pxb);
}
}
lp = po->lsab;
lp->pxcount = i;
*length = po->lsab_used + sizeof(struct ospf_lsa_header);
return lsab_flush(po);
}
void
originate_prefix_net_lsa(struct ospf_iface *ifa)
{
struct ospf_lsa_header lsa;
struct proto_ospf *po = ifa->oa->po;
u32 rid = po->proto.cf->global->router_id;
void *body;
lsa.age = 0;
lsa.type = LSA_T_PREFIX;
lsa.id = ifa->iface->index;
lsa.rt = rid;
lsa.sn = ifa->pxn_lsa ? (ifa->pxn_lsa->lsa.sn + 1) : LSA_INITSEQNO;
u32 dom = ifa->oa->areaid;
body = originate_prefix_net_lsa_body(ifa, &lsa.length);
lsasum_calculate(&lsa, body);
ifa->pxn_lsa = lsa_install_new(po, &lsa, dom, body);
ospf_lsupd_flood(po, NULL, NULL, &lsa, dom, 1);
}
#endif
static void
ospf_top_ht_alloc(struct top_graph *f)
{
f->hash_size = 1 << f->hash_order;
f->hash_mask = f->hash_size - 1;
if (f->hash_order > HASH_HI_MAX - HASH_HI_STEP)
f->hash_entries_max = ~0;
else
f->hash_entries_max = f->hash_size HASH_HI_MARK;
if (f->hash_order < HASH_LO_MIN + HASH_LO_STEP)
f->hash_entries_min = 0;
else
f->hash_entries_min = f->hash_size HASH_LO_MARK;
DBG("Allocating OSPF hash of order %d: %d hash_entries, %d low, %d high\n",
f->hash_order, f->hash_size, f->hash_entries_min, f->hash_entries_max);
f->hash_table =
mb_alloc(f->pool, f->hash_size * sizeof(struct top_hash_entry *));
bzero(f->hash_table, f->hash_size * sizeof(struct top_hash_entry *));
}
static inline void
ospf_top_ht_free(struct top_hash_entry **h)
{
mb_free(h);
}
static inline u32
ospf_top_hash_u32(u32 a)
{
/* Shamelessly stolen from IP address hashing in ipv4.h */
a ^= a >> 16;
a ^= a << 10;
return a;
}
static inline unsigned
ospf_top_hash(struct top_graph *f, u32 domain, u32 lsaid, u32 rtrid, u32 type)
{
/* In OSPFv2, we don't know Router ID when looking for network lsas.
dirty patch to make rt table calculation work. */
return (ospf_top_hash_u32(lsaid) + type +
#ifdef OSPFv2
((type == LSA_T_NET) ? 0 : ospf_top_hash_u32(rtrid)) +
#else /* OSPFv3 */
ospf_top_hash_u32(rtrid) +
#endif
domain) & f->hash_mask;
/*
return (ospf_top_hash_u32(lsaid) + ospf_top_hash_u32(rtrid) +
type + areaid) & f->hash_mask;
*/
}
/**
* ospf_top_new - allocated new topology database
* @p: current instance of ospf
*
* this dynamically hashed structure is often used for keeping lsas. mainly
* its used in @ospf_area structure.
*/
struct top_graph *
ospf_top_new(pool *pool)
{
struct top_graph *f;
f = mb_allocz(pool, sizeof(struct top_graph));
f->pool = pool;
f->hash_slab = sl_new(f->pool, sizeof(struct top_hash_entry));
f->hash_order = HASH_DEF_ORDER;
ospf_top_ht_alloc(f);
f->hash_entries = 0;
f->hash_entries_min = 0;
return f;
}
void
ospf_top_free(struct top_graph *f)
{
rfree(f->hash_slab);
ospf_top_ht_free(f->hash_table);
mb_free(f);
}
static void
ospf_top_rehash(struct top_graph *f, int step)
{
unsigned int oldn, oldh;
struct top_hash_entry **n, **oldt, **newt, *e, *x;
oldn = f->hash_size;
oldt = f->hash_table;
DBG("re-hashing topology hash from order %d to %d\n", f->hash_order,
f->hash_order + step);
f->hash_order += step;
ospf_top_ht_alloc(f);
newt = f->hash_table;
for (oldh = 0; oldh < oldn; oldh++)
{
e = oldt[oldh];
while (e)
{
x = e->next;
n = newt + ospf_top_hash(f, e->domain, e->lsa.id, e->lsa.rt, e->lsa.type);
e->next = *n;
*n = e;
e = x;
}
}
ospf_top_ht_free(oldt);
}
u32
ospf_lsa_domain(u32 type, struct ospf_iface *ifa)
{
switch (type & LSA_SCOPE_MASK)
{
case LSA_SCOPE_LINK:
return ifa->iface->index;
case LSA_SCOPE_AREA:
return ifa->oa->areaid;
case LSA_SCOPE_AS:
default:
return 0;
}
}
struct top_hash_entry *
ospf_hash_find_header(struct top_graph *f, u32 domain, struct ospf_lsa_header *h)
{
return ospf_hash_find(f, domain, h->id, h->rt, h->type);
}
struct top_hash_entry *
ospf_hash_get_header(struct top_graph *f, u32 domain, struct ospf_lsa_header *h)
{
return ospf_hash_get(f, domain, h->id, h->rt, h->type);
}
struct top_hash_entry *
ospf_hash_find(struct top_graph *f, u32 domain, u32 lsa, u32 rtr, u32 type)
{
struct top_hash_entry *e;
e = f->hash_table[ospf_top_hash(f, domain, lsa, rtr, type)];
#ifdef OSPFv2
/* dirty patch to make rt table calculation work. */
if (type == LSA_T_NET)
{
while (e && (e->lsa.id != lsa || e->lsa.type != LSA_T_NET || e->domain != domain))
e = e->next;
return e;
}
#endif
while (e && (e->lsa.id != lsa || e->lsa.type != type || e->lsa.rt != rtr || e->domain != domain))
e = e->next;
return e;
}
struct top_hash_entry *
ospf_hash_get(struct top_graph *f, u32 domain, u32 lsa, u32 rtr, u32 type)
{
struct top_hash_entry **ee;
struct top_hash_entry *e;
ee = f->hash_table + ospf_top_hash(f, domain, lsa, rtr, type);
e = *ee;
while (e && (e->lsa.id != lsa || e->lsa.rt != rtr || e->lsa.type != type || e->domain != domain))
e = e->next;
if (e)
return e;
e = sl_alloc(f->hash_slab);
e->color = OUTSPF;
e->dist = LSINFINITY;
e->nhi = NULL;
e->nh = IPA_NONE;
e->lb = IPA_NONE;
e->lsa.id = lsa;
e->lsa.rt = rtr;
e->lsa.type = type;
e->lsa_body = NULL;
e->nhi = NULL;
e->domain = domain;
e->next = *ee;
*ee = e;
if (f->hash_entries++ > f->hash_entries_max)
ospf_top_rehash(f, HASH_HI_STEP);
return e;
}
void
ospf_hash_delete(struct top_graph *f, struct top_hash_entry *e)
{
struct top_hash_entry **ee = f->hash_table +
ospf_top_hash(f, e->domain, e->lsa.id, e->lsa.rt, e->lsa.type);
while (*ee)
{
if (*ee == e)
{
*ee = e->next;
sl_free(f->hash_slab, e);
if (f->hash_entries-- < f->hash_entries_min)
ospf_top_rehash(f, -HASH_LO_STEP);
return;
}
ee = &((*ee)->next);
}
bug("ospf_hash_delete() called for invalid node");
}
static void
ospf_dump_lsa(struct top_hash_entry *he, struct proto *p)
{
/*
struct ospf_lsa_rt *rt = NULL;
struct ospf_lsa_rt_link *rr = NULL;
struct ospf_lsa_net *ln = NULL;
u32 *rts = NULL;
u32 i, max;
OSPF_TRACE(D_EVENTS, "- %1x %-1R %-1R %4u 0x%08x 0x%04x %-1R",
he->lsa.type, he->lsa.id, he->lsa.rt, he->lsa.age, he->lsa.sn,
he->lsa.checksum, he->domain);
switch (he->lsa.type)
{
case LSA_T_RT:
rt = he->lsa_body;
rr = (struct ospf_lsa_rt_link *) (rt + 1);
for (i = 0; i < lsa_rt_items(&he->lsa); i++)
OSPF_TRACE(D_EVENTS, " - %1x %-1R %-1R %5u",
rr[i].type, rr[i].id, rr[i].data, rr[i].metric);
break;
case LSA_T_NET:
ln = he->lsa_body;
rts = (u32 *) (ln + 1);
for (i = 0; i < lsa_net_items(&he->lsa); i++)
OSPF_TRACE(D_EVENTS, " - %-1R", rts[i]);
break;
default:
break;
}
*/
}
void
ospf_top_dump(struct top_graph *f, struct proto *p)
{
unsigned int i;
OSPF_TRACE(D_EVENTS, "Hash entries: %d", f->hash_entries);
for (i = 0; i < f->hash_size; i++)
{
struct top_hash_entry *e;
for (e = f->hash_table[i]; e != NULL; e = e->next)
ospf_dump_lsa(e, p);
}
}
/* This is very inefficient, please don't call it often */
/* I should also test for every LSA if it's in some link state
* retransmission list for every neighbor. I will not test it.
* It could happen that I'll receive some strange ls ack's.
*/
int
can_flush_lsa(struct proto_ospf *po)
{
struct ospf_iface *ifa;
struct ospf_neighbor *n;
WALK_LIST(ifa, po->iface_list)
{
WALK_LIST(n, ifa->neigh_list)
{
if ((n->state == NEIGHBOR_EXCHANGE) || (n->state == NEIGHBOR_LOADING))
return 0;
break;
}
}
return 1;
}