summaryrefslogtreecommitdiff
path: root/proto/ospf/rt.c
diff options
context:
space:
mode:
Diffstat (limited to 'proto/ospf/rt.c')
-rw-r--r--proto/ospf/rt.c543
1 files changed, 351 insertions, 192 deletions
diff --git a/proto/ospf/rt.c b/proto/ospf/rt.c
index 1b39bda0..2a879c05 100644
--- a/proto/ospf/rt.c
+++ b/proto/ospf/rt.c
@@ -37,9 +37,15 @@ ospf_rt_initort(struct fib_node *fn)
}
static inline int
-unresolved_vlink(struct mpnh *nhs)
+nh_is_vlink(struct mpnh *nhs)
{
- return nhs && !nhs->iface;
+ return !nhs->iface;
+}
+
+static inline int
+unresolved_vlink(ort *ort)
+{
+ return ort->n.nhs && nh_is_vlink(ort->n.nhs);
}
static inline struct mpnh *
@@ -54,7 +60,7 @@ new_nexthop(struct proto_ospf *po, ip_addr gw, struct iface *iface, unsigned cha
}
static inline struct mpnh *
-copy_nexthop(struct proto_ospf *po, struct mpnh *src)
+copy_nexthop(struct proto_ospf *po, const struct mpnh *src)
{
struct mpnh *nh = lp_alloc(po->nhpool, sizeof(struct mpnh));
nh->gw = src->gw;
@@ -64,72 +70,138 @@ copy_nexthop(struct proto_ospf *po, struct mpnh *src)
return nh;
}
-
-/* If new is better return 1 */
+/* Compare nexthops during merge.
+ We need to maintain nhs sorted to eliminate duplicities */
static int
-ri_better(struct proto_ospf *po, orta *new, orta *old)
+cmp_nhs(struct mpnh *s1, struct mpnh *s2)
{
- if (old->type == RTS_DUMMY)
- return 1;
+ int r;
- if (new->type < old->type)
+ if (!s1)
return 1;
- if (new->type > old->type)
- return 0;
+ if (!s2)
+ return -1;
- if (new->metric1 < old->metric1)
- return 1;
+ r = ((int) s2->weight) - ((int) s1->weight);
+ if (r)
+ return r;
- if (new->metric1 > old->metric1)
- return 0;
+ r = ipa_compare(s1->gw, s2->gw);
+ if (r)
+ return r;
- return 0;
+ return ((int) s1->iface->index) - ((int) s2->iface->index);
}
+static struct mpnh *
+merge_nexthops(struct proto_ospf *po, struct mpnh *s1, struct mpnh *s2, int r1, int r2)
+{
+ struct mpnh *root = NULL;
+ struct mpnh **n = &root;
+ int count = po->ecmp;
-/* Whether the ASBR or the forward address destination is preferred
- in AS external route selection according to 16.4.1. */
+ /*
+ * r1, r2 signalize whether we can reuse nexthops from s1, s2.
+ * New nexthops (s2, new) can be reused if they are not inherited
+ * from the parent (i.e. it is allocated in calc_next_hop()).
+ * Current nexthops (s1, en->nhs) can be reused if they weren't
+ * inherited in previous steps (that is stored in nhs_reuse,
+ * i.e. created by merging or allocalted in calc_next_hop()).
+ *
+ * Generally, a node first inherits shared nexthops from its
+ * parent and later possibly gets reusable copy during merging.
+ */
+
+ while ((s1 || s2) && count--)
+ {
+ int cmp = cmp_nhs(s1, s2);
+ if (cmp < 0)
+ {
+ *n = r1 ? s1 : copy_nexthop(po, s1);
+ s1 = s1->next;
+ }
+ else if (cmp > 0)
+ {
+ *n = r2 ? s2 : copy_nexthop(po, s2);
+ s2 = s2->next;
+ }
+ else
+ {
+ *n = r1 ? s1 : (r2 ? s2 : copy_nexthop(po, s1));
+ s1 = s1->next;
+ s2 = s2->next;
+ }
+ n = &((*n)->next);
+ }
+ *n = NULL;
+
+ return root;
+}
+
+/* Returns true if there are device nexthops in n */
static inline int
-epath_preferred(orta *ep)
+has_device_nexthops(const struct mpnh *n)
{
- return (ep->type == RTS_OSPF) && (ep->oa->areaid != 0);
+ for (; n; n = n->next)
+ if (ipa_zero(n->gw))
+ return 1;
+
+ return 0;
}
-/* 16.4. (3), return 1 if new is better */
-static int
-ri_better_asbr(struct proto_ospf *po, orta *new, orta *old)
+/* Replace device nexthops with nexthops to gw */
+static struct mpnh *
+fix_device_nexthops(struct proto_ospf *po, const struct mpnh *n, ip_addr gw)
{
- if (old->type == RTS_DUMMY)
- return 1;
+ struct mpnh *root1 = NULL;
+ struct mpnh *root2 = NULL;
+ struct mpnh **nn1 = &root1;
+ struct mpnh **nn2 = &root2;
- if (!po->rfc1583)
- {
- int new_pref = epath_preferred(new);
- int old_pref = epath_preferred(old);
+ /* This is a bit tricky. We cannot just copy the list and update n->gw,
+ because the list should stay sorted, so we create two lists, one with new
+ gateways and one with old ones, and then merge them. */
- if (new_pref > old_pref)
- return 1;
+ for (; n; n = n->next)
+ {
+ struct mpnh *nn = new_nexthop(po, ipa_zero(n->gw) ? gw : n->gw, n->iface, n->weight);
- if (new_pref < old_pref)
- return 0;
+ if (ipa_zero(n->gw))
+ {
+ *nn1 = nn;
+ nn1 = &(nn->next);
+ }
+ else
+ {
+ *nn2 = nn;
+ nn2 = &(nn->next);
+ }
}
- if (new->metric1 < old->metric1)
- return 1;
+ return merge_nexthops(po, root1, root2, 1, 1);
+}
- if (new->metric1 > old->metric1)
- return 0;
- /* Larger area ID is preferred */
- if (new->oa->areaid > old->oa->areaid)
- return 1;
+/* Whether the ASBR or the forward address destination is preferred
+ in AS external route selection according to 16.4.1. */
+static inline int
+epath_preferred(const orta *ep)
+{
+ return (ep->type == RTS_OSPF) && (ep->oa->areaid != 0);
+}
- return 0;
+/* Whether the ext route has ASBR/next_hop marked as preferred. */
+static inline int
+orta_pref(const orta *nf)
+{
+ return !!(nf->options & ORTA_PREF);
}
+/* Classify orta entries according to RFC 3101 2.5 (6e) priorities:
+ Type-7 LSA with P-bit, Type-5 LSA, Type-7 LSA without P-bit */
static int
-orta_prio(orta *nf)
+orta_prio(const orta *nf)
{
/* RFC 3103 2.5 (6e) priorities */
u32 opts = nf->options & (ORTA_NSSA | ORTA_PROP);
@@ -145,98 +217,246 @@ orta_prio(orta *nf)
return 0;
}
-/* 16.4. (6), return 1 if new is better */
+/* Whether the route is better according to RFC 3101 2.5 (6e):
+ Prioritize Type-7 LSA with P-bit, then Type-5 LSA, then higher router ID */
+static int
+orta_prefer_lsa(const orta *new, const orta *old)
+{
+ int pn = orta_prio(new);
+ int po = orta_prio(old);
+
+ return (pn > po) || ((pn == po) && (new->en->lsa.rt > old->en->lsa.rt));
+}
+
+/*
+ * Compare an existing routing table entry with a new one. Applicable for
+ * intra-area routes, inter-area routes and router entries. Returns integer
+ * <, = or > than 0 if the new orta is less, equal or more preferred than
+ * the old orta.
+ */
static int
-ri_better_ext(struct proto_ospf *po, orta *new, orta *old)
+orta_compare(const struct proto_ospf *po, const orta *new, const orta *old)
{
+ int r;
+
if (old->type == RTS_DUMMY)
return 1;
- /* 16.4. (6a) */
- if (new->type < old->type)
+ /* Prefer intra-area to inter-area to externals */
+ r = ((int) old->type) - ((int) new->type);
+ if (r) return r;
+
+ /* Prefer lowest type 1 metric */
+ r = ((int) old->metric1) - ((int) new->metric1);
+ if (r) return r;
+
+
+ /* Rest is BIRD-specific */
+
+ /* Area-wide routes should not mix next-hops from different areas.
+ This generally should not happen unless there is some misconfiguration. */
+ if (new->oa->areaid != old->oa->areaid)
+ return (new->oa->areaid > old->oa->areaid) ? 1 : -1;
+
+ /* Prefer routes for configured stubnets (!nhs) to regular routes to dummy
+ vlink nexthops. We intentionally return -1 if both are stubnets or vlinks. */
+ if (!old->nhs)
+ return -1;
+ if (!new->nhs)
return 1;
+ if (nh_is_vlink(new->nhs))
+ return -1;
+ if (nh_is_vlink(old->nhs))
+ return 1;
+
- if (new->type > old->type)
+ if (po->ecmp)
return 0;
- /* 16.4. (6b), same type */
- if (new->type == RTS_OSPF_EXT2)
- {
- if (new->metric2 < old->metric2)
- return 1;
+ /* Prefer routes with higher Router ID, just to be more deterministic */
+ if (new->rid > old->rid)
+ return 1;
+
+ return -1;
+}
- if (new->metric2 > old->metric2)
- return 0;
- }
+/*
+ * Compare ASBR routing table entry with a new one, used for precompute ASBRs
+ * for AS external route selection (RFC 2328 16.4 (3)), Returns integer < or >
+ * than 0 if the new ASBR is less or more preferred than the old ASBR.
+ */
+static int
+orta_compare_asbr(const struct proto_ospf *po, const orta *new, const orta *old)
+{
+ int r;
+
+ if (old->type == RTS_DUMMY)
+ return 1;
- /* 16.4. (6c) */
if (!po->rfc1583)
{
- u32 new_pref = new->options & ORTA_PREF;
- u32 old_pref = old->options & ORTA_PREF;
-
- if (new_pref > old_pref)
- return 1;
-
- if (new_pref < old_pref)
- return 0;
+ r = epath_preferred(new) - epath_preferred(old);
+ if (r) return r;
}
- /* 16.4. (6d) */
- if (new->metric1 < old->metric1)
+ r = ((int) old->metric1) - ((int) new->metric1);
+ if (r) return r;
+
+ /* Larger area ID is preferred */
+ if (new->oa->areaid > old->oa->areaid)
return 1;
- if (new->metric1 > old->metric1)
- return 0;
+ /* There is just one ASBR of that RID per area, so tie is not possible */
+ return -1;
+}
- /* RFC 3103, 2.5. (6e) */
- int new_prio = orta_prio(new);
- int old_prio = orta_prio(old);
+/*
+ * Compare a routing table entry with a new one, for AS external routes
+ * (RFC 2328 16.4) and NSSA routes (RFC 3103 2.5), Returns integer <, = or >
+ * than 0 if the new orta is less, equal or more preferred than the old orta.
+ */
+static int
+orta_compare_ext(const struct proto_ospf *po, const orta *new, const orta *old)
+{
+ int r;
- if (new_prio > old_prio)
+ if (old->type == RTS_DUMMY)
return 1;
- if (old_prio > new_prio)
+ /* 16.4 (6a) - prefer routes with lower type */
+ r = ((int) old->type) - ((int) new->type);
+ if (r) return r;
+
+ /* 16.4 (6b) - prefer routes with lower type 2 metric */
+ if (new->type == RTS_OSPF_EXT2)
+ {
+ r = ((int) old->metric2) - ((int) new->metric2);
+ if (r) return r;
+ }
+
+ /* 16.4 (6c) - if not RFC1583, prefer routes with preferred ASBR/next_hop */
+ if (!po->rfc1583)
+ {
+ r = orta_pref(new) - orta_pref(old);
+ if (r) return r;
+ }
+
+ /* 16.4 (6d) - prefer routes with lower type 1 metric */
+ r = ((int) old->metric1) - ((int) new->metric1);
+ if (r) return r;
+
+
+ if (po->ecmp && po->merge_external)
return 0;
- /* make it more deterministic */
- if (new->rid > old->rid)
+ /*
+ * RFC 3101 2.5 (6e) - prioritize Type-7 LSA with P-bit, then Type-5 LSA, then
+ * LSA with higher router ID. Although this should apply just to functionally
+ * equivalent LSAs (i.e. ones with the same non-zero forwarding address), we
+ * use it also to disambiguate otherwise equally preferred nexthops.
+ */
+ if (orta_prefer_lsa(new, old))
return 1;
- return 0;
+ return -1;
+}
+
+
+static inline void
+ort_replace(ort *o, const orta *new)
+{
+ memcpy(&o->n, new, sizeof(orta));
+}
+
+static void
+ort_merge(struct proto_ospf *po, ort *o, const orta *new)
+{
+ orta *old = &o->n;
+
+ if (old->nhs != new->nhs)
+ {
+ old->nhs = merge_nexthops(po, old->nhs, new->nhs, old->nhs_reuse, new->nhs_reuse);
+ old->nhs_reuse = 1;
+ }
+
+ if (old->rid < new->rid)
+ old->rid = new->rid;
}
+static void
+ort_merge_ext(struct proto_ospf *po, ort *o, const orta *new)
+{
+ orta *old = &o->n;
+
+ if (old->nhs != new->nhs)
+ {
+ old->nhs = merge_nexthops(po, old->nhs, new->nhs, old->nhs_reuse, new->nhs_reuse);
+ old->nhs_reuse = 1;
+ }
+
+ if (old->tag != new->tag)
+ old->tag = 0;
+
+ /*
+ * Even with multipath, we store only one LSA in orta.en for the purpose of
+ * NSSA/ext translation. Therefore, we apply procedures from RFC 3101 2.5 (6e)
+ * to all chosen LSAs for given network, not just to functionally equivalent
+ * ones (i.e. ones with the same non-zero forwarding address).
+ */
+ if (orta_prefer_lsa(new, old))
+ {
+ old->options = new->options;
+ old->rid = new->rid;
+ old->oa = new->oa;
+ old->en = new->en;
+ }
+}
+
+
+
static inline void
-ri_install_net(struct proto_ospf *po, ip_addr prefix, int pxlen, orta *new)
+ri_install_net(struct proto_ospf *po, ip_addr prefix, int pxlen, const orta *new)
{
ort *old = (ort *) fib_get(&po->rtf, &prefix, pxlen);
- if (ri_better(po, new, &old->n))
- memcpy(&old->n, new, sizeof(orta));
+ int cmp = orta_compare(po, new, &old->n);
+
+ if (cmp > 0)
+ ort_replace(old, new);
+ else if (cmp == 0)
+ ort_merge(po, old, new);
}
static inline void
-ri_install_rt(struct ospf_area *oa, u32 rid, orta *new)
+ri_install_rt(struct ospf_area *oa, u32 rid, const orta *new)
{
ip_addr addr = ipa_from_rid(rid);
ort *old = (ort *) fib_get(&oa->rtr, &addr, MAX_PREFIX_LENGTH);
- if (ri_better(oa->po, new, &old->n))
- memcpy(&old->n, new, sizeof(orta));
+ int cmp = orta_compare(oa->po, new, &old->n);
+
+ if (cmp > 0)
+ ort_replace(old, new);
+ else if (cmp == 0)
+ ort_merge(oa->po, old, new);
}
static inline void
-ri_install_asbr(struct proto_ospf *po, ip_addr *addr, orta *new)
+ri_install_asbr(struct proto_ospf *po, ip_addr *addr, const orta *new)
{
ort *old = (ort *) fib_get(&po->backbone->rtr, addr, MAX_PREFIX_LENGTH);
- if (ri_better_asbr(po, new, &old->n))
- memcpy(&old->n, new, sizeof(orta));
+ if (orta_compare_asbr(po, new, &old->n) > 0)
+ ort_replace(old, new);
}
static inline void
-ri_install_ext(struct proto_ospf *po, ip_addr prefix, int pxlen, orta *new)
+ri_install_ext(struct proto_ospf *po, ip_addr prefix, int pxlen, const orta *new)
{
ort *old = (ort *) fib_get(&po->rtf, &prefix, pxlen);
- if (ri_better_ext(po, new, &old->n))
- memcpy(&old->n, new, sizeof(orta));
+ int cmp = orta_compare_ext(po, new, &old->n);
+
+ if (cmp > 0)
+ ort_replace(old, new);
+ else if (cmp == 0)
+ ort_merge_ext(po, old, new);
}
static inline struct ospf_iface *
@@ -842,7 +1062,7 @@ ospf_rt_sum_tr(struct ospf_area *oa)
/* 16.3. (5) */
if ((metric < re->n.metric1) ||
- ((metric == re->n.metric1) && unresolved_vlink(re->n.nhs)))
+ ((metric == re->n.metric1) && unresolved_vlink(re)))
{
/* We want to replace the next-hop even if the metric is equal
to replace a virtual next-hop through vlink with a real one.
@@ -1129,13 +1349,24 @@ ospf_rt_abr1(struct proto_ospf *po)
struct area_net *anet;
ort *nf, *default_nf;
+ /* RFC 2328 G.3 - incomplete resolution of virtual next hops - routers */
+ FIB_WALK(&po->backbone->rtr, nftmp)
+ {
+ nf = (ort *) nftmp;
+
+ if (nf->n.type && unresolved_vlink(nf))
+ reset_ri(nf);
+ }
+ FIB_WALK_END;
+
+
FIB_WALK(&po->rtf, nftmp)
{
nf = (ort *) nftmp;
- /* RFC 2328 G.3 - incomplete resolution of virtual next hops */
- if (nf->n.type && unresolved_vlink(nf->n.nhs))
+ /* RFC 2328 G.3 - incomplete resolution of virtual next hops - networks */
+ if (nf->n.type && unresolved_vlink(nf))
reset_ri(nf);
@@ -1361,7 +1592,7 @@ static void
ospf_ext_spf(struct proto_ospf *po)
{
ort *nf1, *nf2;
- orta nfa;
+ orta nfa = {};
struct top_hash_entry *en;
struct proto *p = &po->proto;
struct ospf_lsa_ext *le;
@@ -1369,7 +1600,6 @@ ospf_ext_spf(struct proto_ospf *po)
ip_addr ip, rtid, rt_fwaddr;
u32 br_metric, rt_metric, rt_tag;
struct ospf_area *atmp;
- struct mpnh* nhs = NULL;
OSPF_TRACE(D_EVENTS, "Starting routing table calculation for ext routes");
@@ -1465,7 +1695,7 @@ ospf_ext_spf(struct proto_ospf *po)
if (!rt_fwaddr_valid)
{
nf2 = nf1;
- nhs = nf1->n.nhs;
+ nfa.nhs = nf1->n.nhs;
br_metric = nf1->n.metric1;
}
else
@@ -1491,11 +1721,15 @@ ospf_ext_spf(struct proto_ospf *po)
if (!nf2->n.nhs)
continue;
- nhs = nf2->n.nhs;
- /* If gw is zero, it is a device route */
- if (ipa_zero(nhs->gw))
- nhs = new_nexthop(po, rt_fwaddr, nhs->iface, nhs->weight);
+ nfa.nhs = nf2->n.nhs;
br_metric = nf2->n.metric1;
+
+ /* Replace device nexthops with nexthops to forwarding address from LSA */
+ if (has_device_nexthops(nfa.nhs))
+ {
+ nfa.nhs = fix_device_nexthops(po, nfa.nhs, rt_fwaddr);
+ nfa.nhs_reuse = 1;
+ }
}
if (ebit)
@@ -1526,8 +1760,6 @@ ospf_ext_spf(struct proto_ospf *po)
nfa.tag = rt_tag;
nfa.rid = en->lsa.rt;
nfa.oa = atmp; /* undefined in RFC 2328 */
- nfa.voa = NULL;
- nfa.nhs = nhs;
nfa.en = en; /* store LSA for later (NSSA processing) */
ri_install_ext(po, ip, pxlen, &nfa);
@@ -1745,81 +1977,6 @@ calc_next_hop(struct ospf_area *oa, struct top_hash_entry *en,
return NULL;
}
-/* Compare nexthops during merge.
- We need to maintain nhs sorted to eliminate duplicities */
-static int
-cmp_nhs(struct mpnh *s1, struct mpnh *s2)
-{
- int r;
-
- if (!s1)
- return 1;
-
- if (!s2)
- return -1;
-
- r = ((int) s2->weight) - ((int) s1->weight);
- if (r)
- return r;
-
- r = ipa_compare(s1->gw, s2->gw);
- if (r)
- return r;
-
- return ((int) s1->iface->index) - ((int) s2->iface->index);
-}
-
-static void
-merge_nexthops(struct proto_ospf *po, struct top_hash_entry *en,
- struct top_hash_entry *par, struct mpnh *new)
-{
- if (en->nhs == new)
- return;
-
- int r1 = en->nhs_reuse;
- int r2 = (par->nhs != new);
- int count = po->ecmp;
- struct mpnh *s1 = en->nhs;
- struct mpnh *s2 = new;
- struct mpnh **n = &(en->nhs);
-
- /*
- * r1, r2 signalize whether we can reuse nexthops from s1, s2.
- * New nexthops (s2, new) can be reused if they are not inherited
- * from the parent (i.e. it is allocated in calc_next_hop()).
- * Current nexthops (s1, en->nhs) can be reused if they weren't
- * inherited in previous steps (that is stored in nhs_reuse,
- * i.e. created by merging or allocalted in calc_next_hop()).
- *
- * Generally, a node first inherits shared nexthops from its
- * parent and later possibly gets reusable copy during merging.
- */
-
- while ((s1 || s2) && count--)
- {
- int cmp = cmp_nhs(s1, s2);
- if (cmp < 0)
- {
- *n = r1 ? s1 : copy_nexthop(po, s1);
- s1 = s1->next;
- }
- else if (cmp > 0)
- {
- *n = r2 ? s2 : copy_nexthop(po, s2);
- s2 = s2->next;
- }
- else
- {
- *n = r1 ? s1 : (r2 ? s2 : copy_nexthop(po, s1));
- s1 = s1->next;
- s2 = s2->next;
- }
- n = &((*n)->next);
- }
- *n = NULL;
-
- en->nhs_reuse=1;
-}
/* Add LSA into list of candidates in Dijkstra's algorithm */
static void
@@ -1866,31 +2023,33 @@ add_cand(list * l, struct top_hash_entry *en, struct top_hash_entry *par,
return;
}
- if (dist == en->dist)
+ /* We know that en->color == CANDIDATE and en->nhs is defined. */
+
+ if ((dist == en->dist) && !nh_is_vlink(en->nhs))
{
/*
- * For multipath, we should merge nexthops. We do not mix dummy
- * vlink nexthops, device nexthops and gateway nexthops. We merge
- * gateway nexthops only. We prefer device nexthops over gateway
- * nexthops and gateway nexthops over vlink nexthops. We either
- * keep old nexthops, merge old and new, or replace old with new.
- *
- * We know that en->color == CANDIDATE and en->nhs is defined.
+ * For multipath, we should merge nexthops. We merge regular nexthops only.
+ * Dummy vlink nexthops are less preferred and handled as a special case.
+ *
+ * During merging, new nexthops (nhs) can be reused if they are not
+ * inherited from the parent (i.e. they are allocated in calc_next_hop()).
+ * Current nexthops (en->nhs) can be reused if they weren't inherited in
+ * previous steps (that is stored in nhs_reuse, i.e. created by merging or
+ * allocated in calc_next_hop()).
+ *
+ * Generally, a node first inherits shared nexthops from its parent and
+ * later possibly gets reusable copy during merging.
*/
- struct mpnh *onhs = en->nhs;
/* Keep old ones */
- if (!po->ecmp || !nhs->iface || (onhs->iface && ipa_zero(onhs->gw)))
+ if (!po->ecmp || nh_is_vlink(nhs) || (nhs == en->nhs))
return;
/* Merge old and new */
- if (ipa_nonzero(nhs->gw) && ipa_nonzero(onhs->gw))
- {
- merge_nexthops(po, en, par, nhs);
- return;
- }
-
- /* Fallback to replace old ones */
+ int new_reuse = (par->nhs != nhs);
+ en->nhs = merge_nexthops(po, en->nhs, nhs, en->nhs_reuse, new_reuse);
+ en->nhs_reuse = 1;
+ return;
}
DBG(" Adding candidate: rt: %R, id: %R, type: %u\n",