Show configuration for display all configured summary routes with
matching external LSA information.
+======
+TI-LFA
+======
+
+Experimental support for Topology Independent LFA (Loop-Free Alternate), see
+for example 'draft-bashandy-rtgwg-segment-routing-ti-lfa-05'. Note that
+TI-LFA requires a proper Segment Routing configuration.
+
+.. index:: fast-reroute ti-lfa
+.. clicmd:: fast-reroute ti-lfa
+
+ Configured on the router level. Activates TI-LFA for all interfaces.
+ Currently just link protection for P2P interfaces is supported.
+
Debugging OSPF
==============
/* router-LSA related functions. */
/* Get router-LSA flags. */
-static uint8_t router_lsa_flags(struct ospf_area *area)
+uint8_t router_lsa_flags(struct ospf_area *area)
{
uint8_t flags;
}
/* Set a link information. */
-static char link_info_set(struct stream **s, struct in_addr id,
- struct in_addr data, uint8_t type, uint8_t tos,
- uint16_t cost)
+char link_info_set(struct stream **s, struct in_addr id, struct in_addr data,
+ uint8_t type, uint8_t tos, uint16_t cost)
{
/* LSA stream is initially allocated to OSPF_MAX_LSA_SIZE, suits
* vast majority of cases. Some rare routers with lots of links need
}
/* Set router-LSA body. */
-static void ospf_router_lsa_body_set(struct stream **s, struct ospf_area *area)
+void ospf_router_lsa_body_set(struct stream **s, struct ospf_area *area)
{
unsigned long putp;
uint16_t cnt;
extern void ospf_lsa_data_free(struct lsa_header *);
/* Prototype for various LSAs */
+extern void ospf_router_lsa_body_set(struct stream **s, struct ospf_area *area);
+extern uint8_t router_lsa_flags(struct ospf_area *area);
extern int ospf_router_lsa_update(struct ospf *);
extern int ospf_router_lsa_update_area(struct ospf_area *);
extern int metric_type(struct ospf *, uint8_t, unsigned short);
extern int metric_value(struct ospf *, uint8_t, unsigned short);
+extern char link_info_set(struct stream **s, struct in_addr id,
+ struct in_addr data, uint8_t type, uint8_t tos,
+ uint16_t cost);
+
extern struct in_addr ospf_get_nssa_ip(struct ospf_area *);
extern int ospf_translated_nssa_compare(struct ospf_lsa *, struct ospf_lsa *);
extern struct ospf_lsa *ospf_translated_nssa_refresh(struct ospf *,
DEFINE_MTYPE(OSPFD, OSPF_SR_PARAMS, "OSPF Segment Routing parameters")
DEFINE_MTYPE(OSPFD, OSPF_GR_HELPER, "OSPF Graceful Restart Helper")
DEFINE_MTYPE(OSPFD, OSPF_EXTERNAL_RT_AGGR, "OSPF External Route Summarisation")
+DEFINE_MTYPE(OSPFD, OSPF_P_SPACE, "OSPF TI-LFA P-Space")
+DEFINE_MTYPE(OSPFD, OSPF_Q_SPACE, "OSPF TI-LFA Q-Space")
DECLARE_MTYPE(OSPF_EXT_PARAMS)
DECLARE_MTYPE(OSPF_GR_HELPER)
DECLARE_MTYPE(OSPF_EXTERNAL_RT_AGGR)
+DECLARE_MTYPE(OSPF_P_SPACE)
+DECLARE_MTYPE(OSPF_Q_SPACE)
#endif /* _QUAGGA_OSPF_MEMORY_H */
return 1;
}
+static int ospf_route_backup_path_same(struct sr_nexthop_info *srni1,
+ struct sr_nexthop_info *srni2)
+{
+ struct mpls_label_stack *ls1, *ls2;
+ uint8_t label_count;
+
+ ls1 = srni1->backup_label_stack;
+ ls2 = srni2->backup_label_stack;
+
+ if (!ls1 && !ls2)
+ return 1;
+
+ if ((ls1 && !ls2) || (!ls1 && ls2))
+ return 0;
+
+ if (ls1->num_labels != ls2->num_labels)
+ return 0;
+
+ for (label_count = 0; label_count < ls1->num_labels; label_count++) {
+ if (ls1->label[label_count] != ls2->label[label_count])
+ return 0;
+ }
+
+ if (!IPV4_ADDR_SAME(&srni1->backup_nexthop, &srni2->backup_nexthop))
+ return 0;
+
+ return 1;
+}
+
/* If a prefix and a nexthop match any route in the routing table,
then return 1, otherwise return 0. */
int ospf_route_match_same(struct route_table *rt, struct prefix_ipv4 *prefix,
return 0;
if (op->ifindex != newop->ifindex)
return 0;
+
+ /* check TI-LFA backup paths */
+ if (!ospf_route_backup_path_same(&op->srni,
+ &newop->srni))
+ return 0;
}
return 1;
} else if (prefix_same(&rn->p, (struct prefix *)prefix))
zlog_debug("========================================");
}
-void ospf_route_table_print(struct vty *vty, struct route_table *rt)
-{
- struct route_node *rn;
- struct ospf_route * or ;
- struct listnode *pnode;
- struct ospf_path *path;
-
- vty_out(vty, "========== OSPF routing table ==========\n");
- for (rn = route_top(rt); rn; rn = route_next(rn))
- if ((or = rn->info) != NULL) {
- if (or->type == OSPF_DESTINATION_NETWORK) {
- vty_out(vty, "N %-18pFX %-15pI4 %s %d\n",
- &rn->p, & or->u.std.area_id,
- ospf_path_type_str[or->path_type],
- or->cost);
- for (ALL_LIST_ELEMENTS_RO(or->paths, pnode,
- path))
- if (path->nexthop.s_addr != INADDR_ANY)
- vty_out(vty, " -> %pI4\n",
- &path->nexthop);
- else
- vty_out(vty, " -> %s\n",
- "directly connected");
- } else
- vty_out(vty, "R %-18pI4 %-15pI4 %s %d\n",
- &rn->p.u.prefix4, & or->u.std.area_id,
- ospf_path_type_str[or->path_type],
- or->cost);
- }
- vty_out(vty, "========================================\n");
-}
-
/* This is 16.4.1 implementation.
o Intra-area paths using non-backbone areas are always the most preferred.
o The other paths, intra-area backbone paths and inter-area paths,
|| area->spf_dry_run) {
path = ospf_path_new();
path->nexthop = nexthop->router;
+ path->adv_router = v->id;
if (oi) {
path->ifindex = oi->ifp->ifindex;
* or NULL if next hop is the destination of the prefix
*/
struct sr_node *nexthop;
+
+ /* TI-LFA */
+ struct mpls_label_stack *backup_label_stack;
+ struct in_addr backup_nexthop;
};
/* OSPF Path. */
#include "ospfd/ospf_abr.h"
#include "ospfd/ospf_dump.h"
#include "ospfd/ospf_sr.h"
+#include "ospfd/ospf_ti_lfa.h"
#include "ospfd/ospf_errors.h"
/* Variables to ensure a SPF scheduled log message is printed only once */
if (vp->parent == root && vp->nexthop) {
vertex_nexthop_free(vp->nexthop);
vp->nexthop = NULL;
+ if (vp->local_nexthop) {
+ vertex_nexthop_free(vp->local_nexthop);
+ vp->local_nexthop = NULL;
+ }
}
}
}
* vertex_nexthop, with refcounts.
*/
static struct vertex_parent *vertex_parent_new(struct vertex *v, int backlink,
- struct vertex_nexthop *hop)
+ struct vertex_nexthop *hop,
+ struct vertex_nexthop *lhop)
{
struct vertex_parent *new;
new->parent = v;
new->backlink = backlink;
new->nexthop = hop;
+ new->local_nexthop = lhop;
return new;
}
XFREE(MTYPE_OSPF_VERTEX_PARENT, p);
}
-static int vertex_parent_cmp(void *aa, void *bb)
+int vertex_parent_cmp(void *aa, void *bb)
{
struct vertex_parent *a = aa, *b = bb;
return IPV4_ADDR_CMP(&a->nexthop->router, &b->nexthop->router);
}
}
+/* Find a vertex according to its router id */
+struct vertex *ospf_spf_vertex_find(struct in_addr id, struct list *vertex_list)
+{
+ struct listnode *node;
+ struct vertex *found;
+
+ for (ALL_LIST_ELEMENTS_RO(vertex_list, node, found)) {
+ if (found->id.s_addr == id.s_addr)
+ return found;
+ }
+
+ return NULL;
+}
+
+/* Create a deep copy of a SPF vertex without children and parents */
+static struct vertex *ospf_spf_vertex_copy(struct vertex *vertex)
+{
+ struct vertex *copy;
+
+ copy = XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex));
+
+ memcpy(copy, vertex, sizeof(struct vertex));
+ copy->parents = list_new();
+ copy->parents->del = vertex_parent_free;
+ copy->parents->cmp = vertex_parent_cmp;
+ copy->children = list_new();
+
+ return copy;
+}
+
+/* Create a deep copy of a SPF vertex_parent */
+static struct vertex_parent *
+ospf_spf_vertex_parent_copy(struct vertex_parent *vertex_parent)
+{
+ struct vertex_parent *vertex_parent_copy;
+ struct vertex_nexthop *nexthop_copy, *local_nexthop_copy;
+
+ vertex_parent_copy =
+ XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex_parent));
+ nexthop_copy =
+ XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex_nexthop));
+ local_nexthop_copy =
+ XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex_nexthop));
+
+ memcpy(vertex_parent_copy, vertex_parent, sizeof(struct vertex_parent));
+ memcpy(nexthop_copy, vertex_parent->nexthop,
+ sizeof(struct vertex_nexthop));
+ memcpy(local_nexthop_copy, vertex_parent->local_nexthop,
+ sizeof(struct vertex_nexthop));
+
+ vertex_parent_copy->nexthop = nexthop_copy;
+ vertex_parent_copy->local_nexthop = local_nexthop_copy;
+
+ return vertex_parent_copy;
+}
+
+/* Create a deep copy of a SPF tree */
+void ospf_spf_copy(struct vertex *vertex, struct list *vertex_list)
+{
+ struct listnode *node;
+ struct vertex *vertex_copy, *child, *child_copy, *parent_copy;
+ struct vertex_parent *vertex_parent, *vertex_parent_copy;
+
+ /* First check if the node is already in the vertex list */
+ vertex_copy = ospf_spf_vertex_find(vertex->id, vertex_list);
+ if (!vertex_copy) {
+ vertex_copy = ospf_spf_vertex_copy(vertex);
+ listnode_add(vertex_list, vertex_copy);
+ }
+
+ /* Copy all parents, create parent nodes if necessary */
+ for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, vertex_parent)) {
+ parent_copy = ospf_spf_vertex_find(vertex_parent->parent->id,
+ vertex_list);
+ if (!parent_copy) {
+ parent_copy =
+ ospf_spf_vertex_copy(vertex_parent->parent);
+ listnode_add(vertex_list, parent_copy);
+ }
+ vertex_parent_copy = ospf_spf_vertex_parent_copy(vertex_parent);
+ vertex_parent_copy->parent = parent_copy;
+ listnode_add(vertex_copy->parents, vertex_parent_copy);
+ }
+
+ /* Copy all children, create child nodes if necessary */
+ for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) {
+ child_copy = ospf_spf_vertex_find(child->id, vertex_list);
+ if (!child_copy) {
+ child_copy = ospf_spf_vertex_copy(child);
+ listnode_add(vertex_list, child_copy);
+ }
+ listnode_add(vertex_copy->children, child_copy);
+ }
+
+ /* Finally continue copying with child nodes */
+ for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child))
+ ospf_spf_copy(child, vertex_list);
+}
+
+static void ospf_spf_remove_branch(struct vertex_parent *vertex_parent,
+ struct vertex *child,
+ struct list *vertex_list)
+{
+ struct listnode *node, *nnode, *inner_node, *inner_nnode;
+ struct vertex *grandchild;
+ struct vertex_parent *vertex_parent_found;
+ bool has_more_links = false;
+
+ /*
+ * First check if there are more nexthops for that parent to that child
+ */
+ for (ALL_LIST_ELEMENTS_RO(child->parents, node, vertex_parent_found)) {
+ if (vertex_parent_found->parent->id.s_addr
+ == vertex_parent->parent->id.s_addr
+ && vertex_parent_found->nexthop->router.s_addr
+ != vertex_parent->nexthop->router.s_addr)
+ has_more_links = true;
+ }
+
+ /*
+ * No more links from that parent? Then delete the child from its
+ * children list.
+ */
+ if (!has_more_links)
+ listnode_delete(vertex_parent->parent->children, child);
+
+ /*
+ * Delete the vertex_parent from the child parents list, this needs to
+ * be done anyway.
+ */
+ listnode_delete(child->parents, vertex_parent);
+
+ /*
+ * Are there actually more parents left? If not, then delete the child!
+ * This is done by recursively removing the links to the grandchildren,
+ * such that finally the child can be removed without leaving unused
+ * partial branches.
+ */
+ if (child->parents->count == 0) {
+ for (ALL_LIST_ELEMENTS(child->children, node, nnode,
+ grandchild)) {
+ for (ALL_LIST_ELEMENTS(grandchild->parents, inner_node,
+ inner_nnode,
+ vertex_parent_found)) {
+ ospf_spf_remove_branch(vertex_parent_found,
+ grandchild, vertex_list);
+ }
+ }
+ listnode_delete(vertex_list, child);
+ ospf_vertex_free(child);
+ }
+}
+
+int ospf_spf_remove_link(struct vertex *vertex, struct list *vertex_list,
+ struct router_lsa_link *link)
+{
+ struct listnode *node, *inner_node;
+ struct vertex *child;
+ struct vertex_parent *vertex_parent;
+
+ /*
+ * Identify the node who shares a subnet (given by the link) with a
+ * child and remove the branch of this particular child.
+ */
+ for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) {
+ for (ALL_LIST_ELEMENTS_RO(child->parents, inner_node,
+ vertex_parent)) {
+ if ((vertex_parent->local_nexthop->router.s_addr
+ & link->link_data.s_addr)
+ == (link->link_id.s_addr
+ & link->link_data.s_addr)) {
+ ospf_spf_remove_branch(vertex_parent, child,
+ vertex_list);
+ return 0;
+ }
+ }
+ }
+
+ /* No link found yet, move on recursively */
+ for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) {
+ if (ospf_spf_remove_link(child, vertex_list, link) == 0)
+ return 0;
+ }
+
+ /* link was not removed yet */
+ return 1;
+}
+
static void ospf_spf_init(struct ospf_area *area, struct ospf_lsa *root_lsa,
bool is_dry_run, bool is_root_node)
{
*/
static void ospf_spf_add_parent(struct vertex *v, struct vertex *w,
struct vertex_nexthop *newhop,
+ struct vertex_nexthop *newlhop,
unsigned int distance)
{
struct vertex_parent *vp, *wp;
}
}
- vp = vertex_parent_new(v, ospf_lsa_has_link(w->lsa, v->lsa), newhop);
+ vp = vertex_parent_new(v, ospf_lsa_has_link(w->lsa, v->lsa), newhop,
+ newlhop);
listnode_add_sort(w->parents, vp);
return;
unsigned int distance, int lsa_pos)
{
struct listnode *node, *nnode;
- struct vertex_nexthop *nh;
+ struct vertex_nexthop *nh, *lnh;
struct vertex_parent *vp;
unsigned int added = 0;
char buf1[BUFSIZ];
nh = vertex_nexthop_new();
nh->router = nexthop;
nh->lsa_pos = lsa_pos;
- ospf_spf_add_parent(v, w, nh, distance);
+
+ /*
+ * Since v is the root the nexthop and
+ * local nexthop are the same.
+ */
+ lnh = vertex_nexthop_new();
+ memcpy(lnh, nh,
+ sizeof(struct vertex_nexthop));
+
+ ospf_spf_add_parent(v, w, nh, lnh,
+ distance);
return 1;
} else
zlog_info(
nh = vertex_nexthop_new();
nh->router = vl_data->nexthop.router;
nh->lsa_pos = vl_data->nexthop.lsa_pos;
- ospf_spf_add_parent(v, w, nh, distance);
+
+ /*
+ * Since v is the root the nexthop and
+ * local nexthop are the same.
+ */
+ lnh = vertex_nexthop_new();
+ memcpy(lnh, nh,
+ sizeof(struct vertex_nexthop));
+
+ ospf_spf_add_parent(v, w, nh, lnh,
+ distance);
return 1;
} else
zlog_info(
nh = vertex_nexthop_new();
nh->router.s_addr = 0; /* Nexthop not required */
nh->lsa_pos = lsa_pos;
- ospf_spf_add_parent(v, w, nh, distance);
+
+ /*
+ * Since v is the root the nexthop and
+ * local nexthop are the same.
+ */
+ lnh = vertex_nexthop_new();
+ memcpy(lnh, nh, sizeof(struct vertex_nexthop));
+
+ ospf_spf_add_parent(v, w, nh, lnh, distance);
return 1;
}
} /* end V is the root */
nh = vertex_nexthop_new();
nh->router = l->link_data;
nh->lsa_pos = vp->nexthop->lsa_pos;
+
+ /*
+ * Since v is the root the nexthop and
+ * local nexthop are the same.
+ */
+ lnh = vertex_nexthop_new();
+ memcpy(lnh, nh,
+ sizeof(struct vertex_nexthop));
+
added = 1;
- ospf_spf_add_parent(v, w, nh, distance);
+ ospf_spf_add_parent(v, w, nh, lnh,
+ distance);
}
/*
* Note lack of return is deliberate. See next
for (ALL_LIST_ELEMENTS(v->parents, node, nnode, vp)) {
added = 1;
- ospf_spf_add_parent(v, w, vp->nexthop, distance);
+
+ /*
+ * The nexthop is inherited, but the local nexthop still needs
+ * to be created.
+ */
+ if (l) {
+ lnh = vertex_nexthop_new();
+ lnh->router = l->link_data;
+ lnh->lsa_pos = lsa_pos;
+ } else {
+ lnh = NULL;
+ }
+
+ ospf_spf_add_parent(v, w, vp->nexthop, lnh, distance);
}
return added;
}
+static int ospf_spf_is_protected_link(struct ospf_area *area,
+ struct router_lsa_link *link)
+{
+ struct router_lsa_link *p_link;
+
+ p_link = area->spf_protected_link;
+ if (!p_link)
+ return 0;
+
+ if ((p_link->link_id.s_addr & p_link->link_data.s_addr)
+ == (link->link_data.s_addr & p_link->link_data.s_addr))
+ return 1;
+
+ return 0;
+}
+
/*
* RFC2328 16.1 (2).
* v is on the SPF tree. Examine the links in v's LSA. Update the list of
if ((type = l->m[0].type) == LSA_LINK_TYPE_STUB)
continue;
+ /*
+ * Don't process TI-LFA protected links.
+ *
+ * TODO: Replace this by a proper solution, e.g. remove
+ * corresponding links from the LSDB and run the SPF
+ * algo with the stripped-down LSDB.
+ */
+ if (ospf_spf_is_protected_link(area, l))
+ continue;
+
/*
* (b) Otherwise, W is a transit vertex (router or
* transit network). Look up the vertex W's LSA
struct vertex_parent *parent;
if (v->type == OSPF_VERTEX_ROUTER) {
- vty_out(vty, "SPF Result: depth %d [R] %pI4\n", i,
- &v->lsa->id);
+ vty_out(vty, "SPF Result: depth %d [R] %pI4\n", i, &v->lsa->id);
} else {
struct network_lsa *lsa = (struct network_lsa *)v->lsa;
vty_out(vty, "SPF Result: depth %d [N] %pI4/%d\n", i,
}
for (ALL_LIST_ELEMENTS_RO(v->parents, nnode, parent)) {
- vty_out(vty, " nexthop %pI4 lsa pos %d\n",
- &parent->nexthop->router,
- parent->nexthop->lsa_pos);
+ vty_out(vty,
+ " nexthop %pI4 lsa pos %d -- local nexthop %pI4 lsa pos %d\n",
+ &parent->nexthop->router, parent->nexthop->lsa_pos,
+ &parent->local_nexthop->router,
+ parent->local_nexthop->lsa_pos);
}
i++;
p += (OSPF_ROUTER_LSA_LINK_SIZE
+ (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE));
- if (l->m[0].type == LSA_LINK_TYPE_STUB)
+ /* Don't process TI-LFA protected links */
+ if (l->m[0].type == LSA_LINK_TYPE_STUB
+ && !ospf_spf_is_protected_link(area, l))
ospf_intra_add_stub(rt, l, v, area,
parent_is_root, lsa_pos);
lsa_pos++;
void ospf_spf_cleanup(struct vertex *spf, struct list *vertex_list)
{
+
/*
* Free nexthop information, canonical versions of which are
* attached the first level of router vertices attached to the
* root vertex, see ospf_nexthop_calculation.
*/
- ospf_canonical_nexthops_free(spf);
+ if (spf)
+ ospf_canonical_nexthops_free(spf);
/* Free SPF vertices list with deconstructor ospf_vertex_free. */
- list_delete(&vertex_list);
+ if (vertex_list)
+ list_delete(&vertex_list);
}
#if 0
if (IS_DEBUG_OSPF_EVENT)
zlog_debug("ospf_spf_calculate: Stop. %zd vertices",
mtype_stats_alloc(MTYPE_OSPF_VERTEX));
+}
+
+void ospf_spf_calculate_area(struct ospf *ospf, struct ospf_area *area,
+ struct route_table *new_table,
+ struct route_table *new_rtrs)
+{
+ ospf_spf_calculate(area, area->router_lsa_self, new_table, new_rtrs,
+ false, true);
- /* If this is a dry run then keep the SPF data in place */
- if (!area->spf_dry_run)
- ospf_spf_cleanup(area->spf, area->spf_vertex_list);
+ if (ospf->ti_lfa_enabled)
+ ospf_ti_lfa_compute(area, new_table);
+
+ ospf_spf_cleanup(area->spf, area->spf_vertex_list);
}
-int ospf_spf_calculate_areas(struct ospf *ospf, struct route_table *new_table,
- struct route_table *new_rtrs, bool is_dry_run,
- bool is_root_node)
+void ospf_spf_calculate_areas(struct ospf *ospf, struct route_table *new_table,
+ struct route_table *new_rtrs)
{
struct ospf_area *area;
struct listnode *node, *nnode;
- int areas_processed = 0;
/* Calculate SPF for each area. */
for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) {
if (ospf->backbone && ospf->backbone == area)
continue;
- ospf_spf_calculate(area, area->router_lsa_self, new_table,
- new_rtrs, is_dry_run, is_root_node);
- areas_processed++;
+ ospf_spf_calculate_area(ospf, area, new_table, new_rtrs);
}
/* SPF for backbone, if required */
- if (ospf->backbone) {
- area = ospf->backbone;
- ospf_spf_calculate(area, area->router_lsa_self, new_table,
- new_rtrs, is_dry_run, is_root_node);
- areas_processed++;
- }
-
- return areas_processed;
+ if (ospf->backbone)
+ ospf_spf_calculate_area(ospf, ospf->backbone, new_table,
+ new_rtrs);
}
/* Worker for SPF calculation scheduler. */
struct ospf *ospf = THREAD_ARG(thread);
struct route_table *new_table, *new_rtrs;
struct timeval start_time, spf_start_time;
- int areas_processed;
unsigned long ia_time, prune_time, rt_time;
unsigned long abr_time, total_spf_time, spf_time;
char rbuf[32]; /* reason_buf */
monotime(&spf_start_time);
new_table = route_table_init(); /* routing table */
new_rtrs = route_table_init(); /* ABR/ASBR routing table */
- areas_processed = ospf_spf_calculate_areas(ospf, new_table, new_rtrs,
- false, true);
+ ospf_spf_calculate_areas(ospf, new_table, new_rtrs);
spf_time = monotime_since(&spf_start_time, NULL);
ospf_vl_shut_unapproved(ospf);
zlog_info(" RouteInstall: %ld", rt_time);
if (IS_OSPF_ABR(ospf))
zlog_info(" ABR: %ld (%d areas)",
- abr_time, areas_processed);
+ abr_time, ospf->areas->count);
zlog_info("Reason(s) for SPF: %s", rbuf);
}
struct list *children; /* list of children in SPF tree*/
};
-/* A nexthop taken on the root node to get to this (parent) vertex */
struct vertex_nexthop {
struct in_addr router; /* router address to send to */
int lsa_pos; /* LSA position for resolving the interface */
};
struct vertex_parent {
- struct vertex_nexthop *nexthop; /* nexthop address for this parent */
- struct vertex *parent; /* parent vertex */
+ struct vertex_nexthop *nexthop; /* nexthop taken on the root node */
+ struct vertex_nexthop *local_nexthop; /* local nexthop of the parent */
+ struct vertex *parent; /* parent vertex */
int backlink; /* index back to parent for router-lsa's */
};
struct route_table *new_table,
struct route_table *new_rtrs, bool is_dry_run,
bool is_root_node);
-extern int ospf_spf_calculate_areas(struct ospf *ospf,
+extern void ospf_spf_calculate_area(struct ospf *ospf, struct ospf_area *area,
struct route_table *new_table,
- struct route_table *new_rtrs,
- bool is_dry_run, bool is_root_node);
+ struct route_table *new_rtrs);
+extern void ospf_spf_calculate_areas(struct ospf *ospf,
+ struct route_table *new_table,
+ struct route_table *new_rtrs);
extern void ospf_rtrs_free(struct route_table *);
extern void ospf_spf_cleanup(struct vertex *spf, struct list *vertex_list);
+extern void ospf_spf_copy(struct vertex *vertex, struct list *vertex_list);
+extern int ospf_spf_remove_link(struct vertex *vertex, struct list *vertex_list,
+ struct router_lsa_link *link);
+extern struct vertex *ospf_spf_vertex_find(struct in_addr id,
+ struct list *vertex_list);
+extern int vertex_parent_cmp(void *aa, void *bb);
extern void ospf_spf_print(struct vty *vty, struct vertex *v, int i);
return new;
}
+/* Supposed to be used for testing */
+struct sr_node *ospf_sr_node_create(struct in_addr *rid)
+{
+ struct sr_node *srn;
+
+ srn = hash_get(OspfSR.neighbors, (void *)rid, (void *)sr_node_new);
+
+ return srn;
+}
+
/* Delete Segment Routing node */
static void sr_node_del(struct sr_node *srn)
{
return label;
}
+/* Get the prefix sid for a specific router id */
+mpls_label_t ospf_sr_get_prefix_sid_by_id(struct in_addr *id)
+{
+ struct sr_node *srn;
+ struct sr_prefix *srp;
+ mpls_label_t label;
+
+ srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, id);
+
+ if (srn) {
+ /*
+ * TODO: Here we assume that the SRGBs are the same,
+ * and that the node's prefix SID is at the head of
+ * the list, probably needs tweaking.
+ */
+ srp = listnode_head(srn->ext_prefix);
+ label = index2label(srp->sid, srn->srgb);
+ } else {
+ label = MPLS_INVALID_LABEL;
+ }
+
+ return label;
+}
+
/* Get neighbor full structure from address */
static struct ospf_neighbor *get_neighbor_by_addr(struct ospf *top,
struct in_addr addr)
struct prefix *p);
/* Segment Routing re-routing function */
extern void ospf_sr_update_task(struct ospf *ospf);
+
+/* Support for TI-LFA */
+extern mpls_label_t ospf_sr_get_prefix_sid_by_id(struct in_addr *id);
+extern struct sr_node *ospf_sr_node_create(struct in_addr *rid);
+
#endif /* _FRR_OSPF_SR_H */
--- /dev/null
+/*
+ * OSPF TI-LFA
+ * Copyright (C) 2020 NetDEF, Inc.
+ * Sascha Kattelmann
+ *
+ * This file is part of FRR.
+ *
+ * FRR is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * FRR is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "prefix.h"
+#include "table.h"
+
+#include "ospfd/ospfd.h"
+#include "ospfd/ospf_asbr.h"
+#include "ospfd/ospf_lsa.h"
+#include "ospfd/ospf_spf.h"
+#include "ospfd/ospf_sr.h"
+#include "ospfd/ospf_route.h"
+#include "ospfd/ospf_ti_lfa.h"
+
+
+DECLARE_RBTREE_UNIQ(p_spaces, struct p_space, p_spaces_item,
+ p_spaces_compare_func)
+DECLARE_RBTREE_UNIQ(q_spaces, struct q_space, q_spaces_item,
+ q_spaces_compare_func)
+
+
+static void ospf_ti_lfa_find_p_node(struct vertex *pc_node,
+ struct p_space *p_space,
+ struct q_space *q_space,
+ struct ospf_ti_lfa_node_info *node_info)
+{
+ struct listnode *node;
+ struct vertex *p_node = NULL, *p_node_pc_parent;
+ struct vertex_parent *pc_vertex_parent;
+
+ node_info->type = OSPF_TI_LFA_UNDEFINED_NODE;
+
+ for (ALL_LIST_ELEMENTS_RO(pc_node->parents, node, pc_vertex_parent)) {
+ p_node = ospf_spf_vertex_find(pc_vertex_parent->parent->id,
+ p_space->vertex_list);
+
+ /* Just take the first discovered P node */
+ if (p_node)
+ break;
+ }
+
+ if (!p_node)
+ return;
+
+ node_info->node = p_node;
+ node_info->type = OSPF_TI_LFA_P_NODE;
+
+ /* For the nexthop we just use the first vertex parent */
+ p_node_pc_parent =
+ ospf_spf_vertex_find(p_node->id, p_space->pc_vertex_list);
+ pc_vertex_parent = listnode_head(p_node_pc_parent->parents);
+
+ /*
+ * It can happen that the P node is the root node itself (hence there
+ * can be no parents). In this case we don't need to set a nexthop.
+ */
+ node_info->nexthop.s_addr = INADDR_ANY;
+ if (pc_vertex_parent)
+ node_info->nexthop = pc_vertex_parent->nexthop->router;
+}
+
+static void ospf_ti_lfa_find_q_node(struct vertex *pc_node,
+ struct p_space *p_space,
+ struct q_space *q_space,
+ struct ospf_ti_lfa_node_info *node_info)
+{
+ struct listnode *node;
+ struct vertex *p_node, *q_node, *q_space_parent = NULL;
+ struct vertex_parent *pc_vertex_parent;
+
+ p_node = ospf_spf_vertex_find(pc_node->id, p_space->vertex_list);
+ q_node = ospf_spf_vertex_find(pc_node->id, q_space->vertex_list);
+
+ /*
+ * If we don't find the node in the Q space then there's really
+ * something wrong (since we check the parent, see below).
+ */
+ assert(q_node);
+
+ node_info->type = OSPF_TI_LFA_UNDEFINED_NODE;
+
+ if (p_node && q_node) {
+ node_info->node = pc_node;
+ node_info->type = OSPF_TI_LFA_PQ_NODE;
+
+ /* For the nexthop we just use the first vertex parent */
+ pc_vertex_parent = listnode_head(pc_node->parents);
+ node_info->nexthop = pc_vertex_parent->nexthop->router;
+ return;
+ }
+
+ if (pc_node->parents->count == 0)
+ return;
+
+ /* First check if the same link also exists in the Q space */
+ for (ALL_LIST_ELEMENTS_RO(pc_node->parents, node, pc_vertex_parent)) {
+ /*
+ * Note that the Q space has the 'reverse' direction of the PC
+ * SPF. Hence compare PC SPF parents to Q space children.
+ */
+ q_space_parent = ospf_spf_vertex_find(
+ pc_vertex_parent->parent->id, q_node->children);
+ if (q_space_parent)
+ break;
+ }
+
+ /*
+ * If the Q space parent doesn't exist we 'hit' the border to the P
+ * space and hence got our Q node.
+ */
+ if (!q_space_parent) {
+ node_info->node = pc_node;
+ node_info->type = OSPF_TI_LFA_Q_NODE;
+
+ /* For the nexthop we just use the first vertex parent */
+ pc_vertex_parent = listnode_head(pc_node->parents);
+ node_info->nexthop = pc_vertex_parent->nexthop->router;
+ return;
+ }
+
+ return ospf_ti_lfa_find_q_node(pc_vertex_parent->parent, p_space,
+ q_space, node_info);
+}
+
+static struct mpls_label_stack *
+ospf_ti_lfa_create_label_stack(mpls_label_t labels[], uint32_t num_labels)
+{
+ struct mpls_label_stack *label_stack;
+ uint32_t i;
+
+ /* Sanity check */
+ for (i = 0; i < num_labels; i++) {
+ if (labels[i] == MPLS_INVALID_LABEL)
+ return NULL;
+ }
+
+ label_stack = XCALLOC(MTYPE_OSPF_Q_SPACE,
+ sizeof(struct mpls_label_stack)
+ + num_labels * sizeof(mpls_label_t));
+ label_stack->num_labels = num_labels;
+
+ for (i = 0; i < num_labels; i++)
+ label_stack->label[i] = labels[i];
+
+ return label_stack;
+}
+
+static void ospf_ti_lfa_generate_label_stack(struct p_space *p_space,
+ struct q_space *q_space)
+{
+ struct ospf_ti_lfa_node_info p_node_info, q_node_info;
+ mpls_label_t labels[2];
+ struct vertex *pc_node;
+
+ zlog_debug("%s: Generating Label stack for src %pI4 and dest %pI4.",
+ __func__, &p_space->root->id, &q_space->root->id);
+
+ pc_node = ospf_spf_vertex_find(q_space->root->id,
+ p_space->pc_vertex_list);
+ if (!pc_node) {
+ zlog_debug(
+ "%s: There seems to be no post convergence path (yet).",
+ __func__);
+ return;
+ }
+
+ ospf_ti_lfa_find_q_node(pc_node, p_space, q_space, &q_node_info);
+ if (q_node_info.type == OSPF_TI_LFA_UNDEFINED_NODE) {
+ zlog_debug("%s: Q node not found!", __func__);
+ return;
+ }
+
+ /* Found a PQ node? Then we are done here. */
+ if (q_node_info.type == OSPF_TI_LFA_PQ_NODE) {
+ labels[0] = ospf_sr_get_prefix_sid_by_id(&q_node_info.node->id);
+ q_space->label_stack =
+ ospf_ti_lfa_create_label_stack(labels, 1);
+ q_space->nexthop = q_node_info.nexthop;
+ return;
+ }
+
+ /* Otherwise find the adjacent P node. */
+ pc_node = ospf_spf_vertex_find(q_node_info.node->id,
+ p_space->pc_vertex_list);
+ ospf_ti_lfa_find_p_node(pc_node, p_space, q_space, &p_node_info);
+ if (p_node_info.type == OSPF_TI_LFA_UNDEFINED_NODE) {
+ zlog_debug("%s: P node not found!", __func__);
+ return;
+ }
+
+ /*
+ * It can happen that the P node is the root itself, therefore we don't
+ * need a label for it.
+ */
+ if (p_node_info.node->id.s_addr == p_space->root->id.s_addr) {
+ labels[0] = ospf_sr_get_prefix_sid_by_id(&q_node_info.node->id);
+ q_space->label_stack =
+ ospf_ti_lfa_create_label_stack(labels, 1);
+ q_space->nexthop = q_node_info.nexthop;
+ return;
+ }
+
+ /* Otherwise we have a P and also a Q node which we need labels for. */
+ labels[0] = ospf_sr_get_prefix_sid_by_id(&p_node_info.node->id);
+ labels[1] = ospf_sr_get_prefix_sid_by_id(&q_node_info.node->id);
+ q_space->label_stack = ospf_ti_lfa_create_label_stack(labels, 2);
+ q_space->nexthop = p_node_info.nexthop;
+}
+
+static void ospf_ti_lfa_generate_q_spaces(struct ospf_area *area,
+ struct p_space *p_space,
+ struct vertex *dest)
+{
+ struct listnode *node;
+ struct vertex *child;
+ struct route_table *new_table, *new_rtrs;
+ struct q_space *q_space, q_space_search;
+ char buf[MPLS_LABEL_STRLEN];
+
+ /* Check if we already have a Q space for this destination */
+ q_space_search.root = dest;
+ if (q_spaces_find(p_space->q_spaces, &q_space_search))
+ return;
+
+ q_space = XCALLOC(MTYPE_OSPF_Q_SPACE, sizeof(struct q_space));
+
+ new_table = route_table_init();
+ new_rtrs = route_table_init();
+
+ /*
+ * Generate a new SPF tree for this vertex,
+ * dry run true, root node false
+ */
+ ospf_spf_calculate(area, dest->lsa_p, new_table, new_rtrs, true, false);
+
+ q_space->root = area->spf;
+ q_space->vertex_list = area->spf_vertex_list;
+ q_space->label_stack = NULL;
+
+ /* 'Cut' the branch of the protected link out of the new SPF tree */
+ ospf_spf_remove_link(q_space->root, q_space->vertex_list,
+ p_space->protected_link);
+
+ /*
+ * Generate the smallest possible label stack from the root of the P
+ * space to the root of the Q space.
+ */
+ ospf_ti_lfa_generate_label_stack(p_space, q_space);
+
+ if (q_space->label_stack) {
+ mpls_label2str(q_space->label_stack->num_labels,
+ q_space->label_stack->label, buf,
+ MPLS_LABEL_STRLEN, true);
+ zlog_info(
+ "%s: Generated label stack %s for root %pI4 and destination %pI4 for protected link %pI4",
+ __func__, buf, &p_space->root->id, &q_space->root->id,
+ &p_space->protected_link->link_id);
+ } else {
+ zlog_info(
+ "%s: NO label stack generated for root %pI4 and destination %pI4 for protected link %pI4",
+ __func__, &p_space->root->id, &q_space->root->id,
+ &p_space->protected_link->link_id);
+ }
+
+ /* We are finished, store the new Q space in the P space struct */
+ q_spaces_add(p_space->q_spaces, q_space);
+
+ /* Recursively generate Q spaces for all children */
+ for (ALL_LIST_ELEMENTS_RO(dest->children, node, child))
+ ospf_ti_lfa_generate_q_spaces(area, p_space, child);
+}
+
+static void ospf_ti_lfa_generate_post_convergence_spf(struct ospf_area *area,
+ struct p_space *p_space)
+{
+ struct route_table *new_table, *new_rtrs;
+
+ new_table = route_table_init();
+ new_rtrs = route_table_init();
+
+ area->spf_protected_link = p_space->protected_link;
+
+ /*
+ * The 'post convergence' SPF tree is generated here
+ * dry run true, root node false
+ *
+ * So how does this work? During the SPF calculation the algorithm
+ * checks if a link belongs to a protected stub and then just ignores
+ * it. This is actually _NOT_ a good way to calculate the post
+ * convergence SPF tree. The preferred way would be to delete the
+ * relevant links from a copy of the LSDB and then just run the SPF
+ * algorithm on that as usual. However, removing links from router
+ * LSAs appears to be its own endeavour (because LSAs are stored as a
+ * 'raw' stream), so we go with this rather hacky way for now.
+ */
+ ospf_spf_calculate(area, area->router_lsa_self, new_table, new_rtrs,
+ true, false);
+
+ p_space->pc_spf = area->spf;
+ p_space->pc_vertex_list = area->spf_vertex_list;
+
+ area->spf_protected_link = NULL;
+}
+
+static void ospf_ti_lfa_generate_p_space(struct ospf_area *area,
+ struct vertex *child,
+ struct router_lsa_link *link)
+{
+ struct vertex *spf_orig;
+ struct list *vertex_list, *vertex_list_orig;
+ struct p_space *p_space;
+
+ p_space = XCALLOC(MTYPE_OSPF_P_SPACE, sizeof(struct p_space));
+ vertex_list = list_new();
+
+ /* The P-space will get its own SPF tree, so copy the old one */
+ ospf_spf_copy(area->spf, vertex_list);
+ p_space->root = listnode_head(vertex_list);
+ p_space->vertex_list = vertex_list;
+ p_space->protected_link = link;
+
+ /* Initialize the Q spaces for this P space and protected link */
+ p_space->q_spaces =
+ XCALLOC(MTYPE_OSPF_Q_SPACE, sizeof(struct q_spaces_head));
+ q_spaces_init(p_space->q_spaces);
+
+ /* 'Cut' the child branch out of the new SPF tree */
+ ospf_spf_remove_link(p_space->root, p_space->vertex_list,
+ p_space->protected_link);
+
+ /*
+ * Since we are going to calculate more SPF trees for Q spaces, keep the
+ * 'original' one here temporarily
+ */
+ spf_orig = area->spf;
+ vertex_list_orig = area->spf_vertex_list;
+
+ /* Generate the post convergence SPF as a blueprint for backup paths */
+ ospf_ti_lfa_generate_post_convergence_spf(area, p_space);
+
+ /* Generate the relevant Q spaces for this particular P space */
+ ospf_ti_lfa_generate_q_spaces(area, p_space, child);
+
+ /* Put the 'original' SPF tree back in place */
+ area->spf = spf_orig;
+ area->spf_vertex_list = vertex_list_orig;
+
+ /* We are finished, store the new P space */
+ p_spaces_add(area->p_spaces, p_space);
+}
+
+void ospf_ti_lfa_generate_p_spaces(struct ospf_area *area)
+{
+ struct listnode *node, *inner_node;
+ struct vertex *root, *child;
+ struct vertex_parent *vertex_parent;
+ uint8_t *p, *lim;
+ struct router_lsa_link *l = NULL;
+ struct prefix stub_prefix, child_prefix;
+
+ area->p_spaces =
+ XCALLOC(MTYPE_OSPF_P_SPACE, sizeof(struct p_spaces_head));
+ p_spaces_init(area->p_spaces);
+
+ root = area->spf;
+
+ /* Root or its router LSA was not created yet? */
+ if (!root || !root->lsa)
+ return;
+
+ stub_prefix.family = AF_INET;
+ child_prefix.family = AF_INET;
+ child_prefix.prefixlen = IPV4_MAX_PREFIXLEN;
+
+ p = ((uint8_t *)root->lsa) + OSPF_LSA_HEADER_SIZE + 4;
+ lim = ((uint8_t *)root->lsa) + ntohs(root->lsa->length);
+
+ zlog_info("%s: Generating P spaces for area %pI4", __func__,
+ &area->area_id);
+
+ /*
+ * Iterate over all stub networks which target other OSPF neighbors.
+ * Check the nexthop of the child vertex if a stub network is relevant.
+ */
+ while (p < lim) {
+ l = (struct router_lsa_link *)p;
+ p += (OSPF_ROUTER_LSA_LINK_SIZE
+ + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE));
+
+ if (l->m[0].type != LSA_LINK_TYPE_STUB)
+ continue;
+
+ stub_prefix.prefixlen = ip_masklen(l->link_data);
+ stub_prefix.u.prefix4 = l->link_id;
+
+ for (ALL_LIST_ELEMENTS_RO(root->children, node, child)) {
+
+ if (child->type != OSPF_VERTEX_ROUTER)
+ continue;
+
+ for (ALL_LIST_ELEMENTS_RO(child->parents, inner_node,
+ vertex_parent)) {
+
+ child_prefix.u.prefix4 =
+ vertex_parent->nexthop->router;
+
+ /*
+ * If there's a link for that stub network then
+ * we will protect it. Hence generate a P space
+ * for that particular link including the
+ * Q spaces so we can later on generate a
+ * backup path for the link.
+ */
+ if (prefix_match(&stub_prefix, &child_prefix)) {
+ zlog_info(
+ "%s: Generating P space for %pI4",
+ __func__, &l->link_id);
+ ospf_ti_lfa_generate_p_space(area,
+ child, l);
+ }
+ }
+ }
+ }
+}
+
+static struct p_space *
+ospf_ti_lfa_get_p_space_by_nexthop(struct ospf_area *area,
+ struct in_addr *nexthop)
+{
+ struct p_space *p_space;
+ struct router_lsa_link *link;
+
+ frr_each(p_spaces, area->p_spaces, p_space) {
+ link = p_space->protected_link;
+ if ((nexthop->s_addr & link->link_data.s_addr)
+ == (link->link_id.s_addr & link->link_data.s_addr))
+ return p_space;
+ }
+
+ return NULL;
+}
+
+void ospf_ti_lfa_insert_backup_paths(struct ospf_area *area,
+ struct route_table *new_table)
+{
+ struct route_node *rn;
+ struct ospf_route *or;
+ struct ospf_path *path;
+ struct listnode *node;
+ struct p_space *p_space;
+ struct q_space *q_space, q_space_search;
+ struct vertex root_search;
+
+ for (rn = route_top(new_table); rn; rn = route_next(rn)) {
+ or = rn->info;
+ if (or == NULL)
+ continue;
+
+ /* Insert a backup path for all OSPF paths */
+ for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) {
+ p_space = ospf_ti_lfa_get_p_space_by_nexthop(
+ area, &path->nexthop);
+ if (!p_space) {
+ zlog_debug(
+ "%s: P space not found for nexthop %pI4.",
+ __func__, &path->nexthop);
+ continue;
+ }
+
+ root_search.id = path->adv_router;
+ q_space_search.root = &root_search;
+ q_space = q_spaces_find(p_space->q_spaces,
+ &q_space_search);
+ if (!q_space) {
+ zlog_debug(
+ "%s: Q space not found for advertising router %pI4.",
+ __func__, &path->adv_router);
+ continue;
+ }
+
+ path->srni.backup_label_stack = q_space->label_stack;
+ path->srni.backup_nexthop = q_space->nexthop;
+ }
+ }
+}
+
+void ospf_ti_lfa_free_p_spaces(struct ospf_area *area)
+{
+ struct p_space *p_space;
+ struct q_space *q_space;
+
+ while ((p_space = p_spaces_pop(area->p_spaces))) {
+ while ((q_space = q_spaces_pop(p_space->q_spaces))) {
+ ospf_spf_cleanup(q_space->root, q_space->vertex_list);
+
+ /*
+ * TODO: label stack is used for route installation
+ * XFREE(MTYPE_OSPF_Q_SPACE, q_space->label_stack);
+ */
+
+ XFREE(MTYPE_OSPF_Q_SPACE, q_space);
+ }
+ ospf_spf_cleanup(p_space->root, p_space->vertex_list);
+ ospf_spf_cleanup(p_space->pc_spf, p_space->pc_vertex_list);
+
+ q_spaces_fini(p_space->q_spaces);
+ XFREE(MTYPE_OSPF_Q_SPACE, p_space->q_spaces);
+ }
+
+ p_spaces_fini(area->p_spaces);
+ XFREE(MTYPE_OSPF_P_SPACE, area->p_spaces);
+}
+
+void ospf_ti_lfa_compute(struct ospf_area *area, struct route_table *new_table)
+{
+ /*
+ * Generate P spaces per protected link and their respective Q spaces,
+ * generate backup paths (MPLS label stacks) by finding P/Q nodes.
+ */
+ ospf_ti_lfa_generate_p_spaces(area);
+
+ /* Insert the generated backup paths into the routing table. */
+ ospf_ti_lfa_insert_backup_paths(area, new_table);
+
+ /* Cleanup P spaces and related datastructures including Q spaces. */
+ ospf_ti_lfa_free_p_spaces(area);
+}
--- /dev/null
+/*
+ * OSPF calculation.
+ * Copyright (C) 2020 NetDEF, Inc.
+ * Sascha Kattelmann
+ *
+ * This file is part of FRR.
+ *
+ * FRR is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * FRR is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _OSPF_TI_LFA_H
+#define _OSPF_TI_LFA_H
+
+extern void ospf_ti_lfa_compute(struct ospf_area *area,
+ struct route_table *new_table);
+
+/* unit testing */
+extern void ospf_ti_lfa_generate_p_spaces(struct ospf_area *area);
+extern void ospf_ti_lfa_insert_backup_paths(struct ospf_area *area,
+ struct route_table *new_table);
+extern void ospf_ti_lfa_free_p_spaces(struct ospf_area *area);
+
+#endif /* _OSPF_TI_LFA_H */
"Write multiplier\n"
"Maximum number of interface serviced per write\n")
+DEFUN(ospf_ti_lfa, ospf_ti_lfa_cmd, "fast-reroute ti-lfa",
+ "Fast Reroute for MPLS and IP resilience\n"
+ "Topology Independent LFA (Loop-Free Alternate)\n")
+{
+ VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf);
+
+ ospf->ti_lfa_enabled = true;
+
+ ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_ospf_ti_lfa, no_ospf_ti_lfa_cmd, "no fast-reroute ti-lfa",
+ NO_STR
+ "Fast Reroute for MPLS and IP resilience\n"
+ "Topology Independent LFA (Loop-Free Alternate)\n")
+{
+ VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf);
+
+ ospf->ti_lfa_enabled = false;
+
+ ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE);
+
+ return CMD_SUCCESS;
+}
+
static const char *const ospf_abr_type_descr_str[] = {
"Unknown", "Standard (RFC2328)", "Alternative IBM",
"Alternative Cisco", "Alternative Shortcut"
json_lstype);
}
-static void show_ip_ospf_database_summary(struct vty *vty, struct ospf *ospf,
- int self, json_object *json)
+void show_ip_ospf_database_summary(struct vty *vty, struct ospf *ospf, int self,
+ json_object *json)
{
struct ospf_lsa *lsa;
struct route_node *rn;
oi->ifp->name, &oi->address->u.prefix4);
}
+ /* TI-LFA print. */
+ if (ospf->ti_lfa_enabled)
+ vty_out(vty, " fast-reroute ti-lfa\n");
+
/* Network area print. */
config_write_network_area(vty, ospf);
install_element(OSPF_NODE, &ospf_proactive_arp_cmd);
install_element(OSPF_NODE, &no_ospf_proactive_arp_cmd);
+ /* TI-LFA commands */
+ install_element(OSPF_NODE, &ospf_ti_lfa_cmd);
+ install_element(OSPF_NODE, &no_ospf_ti_lfa_cmd);
+
/* Init interface related vty commands. */
ospf_vty_if_init();
extern void ospf_vty_clear_init(void);
extern int str2area_id(const char *, struct in_addr *, int *);
+/* unit tests */
+void show_ip_ospf_database_summary(struct vty *vty, struct ospf *ospf, int self,
+ json_object *json);
+
#endif /* _QUAGGA_OSPF_VTY_H */
return 0;
}
+/* Nexthop, ifindex, distance and metric information. */
+static void ospf_zebra_add_nexthop(struct ospf *ospf, struct ospf_path *path,
+ struct zapi_route *api)
+{
+ struct zapi_nexthop *api_nh;
+ struct zapi_nexthop *api_nh_backup;
+
+ /* TI-LFA backup path label stack comes first, if present */
+ if (path->srni.backup_label_stack) {
+ api_nh_backup = &api->backup_nexthops[api->backup_nexthop_num];
+ api_nh_backup->vrf_id = ospf->vrf_id;
+
+ api_nh_backup->type = NEXTHOP_TYPE_IPV4_IFINDEX;
+ api_nh_backup->gate.ipv4 = path->srni.backup_nexthop;
+
+ api_nh_backup->label_num =
+ path->srni.backup_label_stack->num_labels;
+ memcpy(api_nh_backup->labels,
+ path->srni.backup_label_stack->label,
+ sizeof(mpls_label_t) * api_nh_backup->label_num);
+
+ api->backup_nexthop_num++;
+ }
+
+ /* And here comes the primary nexthop */
+ api_nh = &api->nexthops[api->nexthop_num];
+#ifdef HAVE_NETLINK
+ if (path->unnumbered
+ || (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0)) {
+#else /* HAVE_NETLINK */
+ if (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0) {
+#endif /* HAVE_NETLINK */
+ api_nh->gate.ipv4 = path->nexthop;
+ api_nh->ifindex = path->ifindex;
+ api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX;
+ } else if (path->nexthop.s_addr != INADDR_ANY) {
+ api_nh->gate.ipv4 = path->nexthop;
+ api_nh->type = NEXTHOP_TYPE_IPV4;
+ } else {
+ api_nh->ifindex = path->ifindex;
+ api_nh->type = NEXTHOP_TYPE_IFINDEX;
+ }
+ api_nh->vrf_id = ospf->vrf_id;
+
+ /* Set TI-LFA backup nexthop info if present */
+ if (path->srni.backup_label_stack) {
+ SET_FLAG(api->message, ZAPI_MESSAGE_BACKUP_NEXTHOPS);
+ SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP);
+
+ /* Just care about a single TI-LFA backup path for now */
+ api_nh->backup_num = 1;
+ api_nh->backup_idx[0] = api->backup_nexthop_num - 1;
+ }
+
+ api->nexthop_num++;
+}
+
void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *p,
struct ospf_route * or)
{
struct zapi_route api;
- struct zapi_nexthop *api_nh;
uint8_t distance;
struct ospf_path *path;
struct listnode *node;
- int count = 0;
memset(&api, 0, sizeof(api));
api.vrf_id = ospf->vrf_id;
api.distance = distance;
}
- /* Nexthop, ifindex, distance and metric information. */
for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) {
- if (count >= MULTIPATH_NUM)
+ if (api.nexthop_num >= MULTIPATH_NUM)
break;
- api_nh = &api.nexthops[count];
-#ifdef HAVE_NETLINK
- if (path->unnumbered || (path->nexthop.s_addr != INADDR_ANY
- && path->ifindex != 0)) {
-#else /* HAVE_NETLINK */
- if (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0) {
-#endif /* HAVE_NETLINK */
- api_nh->gate.ipv4 = path->nexthop;
- api_nh->ifindex = path->ifindex;
- api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX;
- } else if (path->nexthop.s_addr != INADDR_ANY) {
- api_nh->gate.ipv4 = path->nexthop;
- api_nh->type = NEXTHOP_TYPE_IPV4;
- } else {
- api_nh->ifindex = path->ifindex;
- api_nh->type = NEXTHOP_TYPE_IFINDEX;
- }
- api_nh->vrf_id = ospf->vrf_id;
- count++;
+
+ ospf_zebra_add_nexthop(ospf, path, &api);
if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) {
struct interface *ifp;
ifp ? ifp->name : " ");
}
}
- api.nexthop_num = count;
zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api);
}
#define OSPF_EXTERNAL_LSA_ORIGINATE_DELAY 1
+int p_spaces_compare_func(const struct p_space *a, const struct p_space *b)
+{
+ return (a->protected_link->link_id.s_addr
+ - b->protected_link->link_id.s_addr);
+}
+
+int q_spaces_compare_func(const struct q_space *a, const struct q_space *b)
+{
+ return (a->root->id.s_addr - b->root->id.s_addr);
+}
+
+DECLARE_RBTREE_UNIQ(p_spaces, struct p_space, p_spaces_item,
+ p_spaces_compare_func)
+
void ospf_process_refresh_data(struct ospf *ospf, bool reset)
{
struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id);
return 0;
}
-/* Allocate new ospf structure. */
-static struct ospf *ospf_new(unsigned short instance, const char *name)
+struct ospf *ospf_new_alloc(unsigned short instance, const char *name)
{
int i;
struct vrf *vrf = NULL;
new->maxage_delay = OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT;
new->maxage_lsa = route_table_init();
new->t_maxage_walker = NULL;
- thread_add_timer(master, ospf_lsa_maxage_walker, new,
- OSPF_LSA_MAXAGE_CHECK_INTERVAL, &new->t_maxage_walker);
/* Distance table init. */
new->distance_table = route_table_init();
new->lsa_refresh_queue.index = 0;
new->lsa_refresh_interval = OSPF_LSA_REFRESH_INTERVAL_DEFAULT;
new->t_lsa_refresher = NULL;
- thread_add_timer(master, ospf_lsa_refresh_walker, new,
- new->lsa_refresh_interval, &new->t_lsa_refresher);
new->lsa_refresher_started = monotime(NULL);
new->ibuf = stream_new(OSPF_MAX_PACKET_SIZE + 1);
QOBJ_REG(new, ospf);
new->fd = -1;
+
+ return new;
+}
+
+/* Allocate new ospf structure. */
+static struct ospf *ospf_new(unsigned short instance, const char *name)
+{
+ struct ospf *new;
+
+ new = ospf_new_alloc(instance, name);
+
if ((ospf_sock_init(new)) < 0) {
if (new->vrf_id != VRF_UNKNOWN)
flog_err(
__func__);
return new;
}
+
+ thread_add_timer(master, ospf_lsa_maxage_walker, new,
+ OSPF_LSA_MAXAGE_CHECK_INTERVAL, &new->t_maxage_walker);
+ thread_add_timer(master, ospf_lsa_refresh_walker, new,
+ new->lsa_refresh_interval, &new->t_lsa_refresher);
+
thread_add_read(master, ospf_read, new, new->fd, &new->t_read);
return new;
/* allocate new OSPF Area object */
-static struct ospf_area *ospf_area_new(struct ospf *ospf,
- struct in_addr area_id)
+struct ospf_area *ospf_area_new(struct ospf *ospf, struct in_addr area_id)
{
struct ospf_area *new;
}
-static void add_ospf_interface(struct connected *co, struct ospf_area *area)
+struct ospf_interface *add_ospf_interface(struct connected *co,
+ struct ospf_area *area)
{
struct ospf_interface *oi;
if ((area->ospf->router_id.s_addr != INADDR_ANY)
&& if_is_operative(co->ifp))
ospf_if_up(oi);
+
+ return oi;
}
static void update_redistributed(struct ospf *ospf, int add_to_ospf)
#define _ZEBRA_OSPFD_H
#include <zebra.h>
+#include "typesafe.h"
#include "qobj.h"
#include "libospf.h"
#include "ldp_sync.h"
/* MPLS LDP-IGP Sync */
struct ldp_sync_info_cmd ldp_sync_cmd;
+ /* TI-LFA support for all interfaces. */
+ bool ti_lfa_enabled;
+
QOBJ_FIELDS
};
DECLARE_QOBJ_TYPE(ospf)
+enum ospf_ti_lfa_node_type {
+ OSPF_TI_LFA_UNDEFINED_NODE,
+ OSPF_TI_LFA_PQ_NODE,
+ OSPF_TI_LFA_P_NODE,
+ OSPF_TI_LFA_Q_NODE,
+};
+
+struct ospf_ti_lfa_node_info {
+ struct vertex *node;
+ enum ospf_ti_lfa_node_type type;
+ struct in_addr nexthop;
+};
+
+PREDECL_RBTREE_UNIQ(q_spaces)
+struct q_space {
+ struct vertex *root;
+ struct list *vertex_list;
+ struct mpls_label_stack *label_stack;
+ struct in_addr nexthop;
+ struct q_spaces_item q_spaces_item;
+};
+
+PREDECL_RBTREE_UNIQ(p_spaces)
+struct p_space {
+ struct vertex *root;
+ struct router_lsa_link *protected_link;
+ struct q_spaces_head *q_spaces;
+ struct list *vertex_list;
+ struct vertex *pc_spf;
+ struct list *pc_vertex_list;
+ struct p_spaces_item p_spaces_item;
+};
+
/* OSPF area structure. */
struct ospf_area {
/* OSPF instance. */
bool spf_root_node; /* flag for checking if the calculating node is the
root node of the SPF tree */
+ /* TI-LFA protected link for SPF calculations */
+ struct router_lsa_link *spf_protected_link;
+
+ /* P/Q spaces for TI-LFA */
+ struct p_spaces_head *p_spaces;
+
/* Threads. */
struct thread *t_stub_router; /* Stub-router timer */
struct thread *t_opaque_lsa_self; /* Type-10 Opaque-LSAs origin. */
extern struct ospf *ospf_lookup_instance(unsigned short);
extern struct ospf *ospf_get(unsigned short instance, const char *name,
bool *created);
+extern struct ospf *ospf_new_alloc(unsigned short instance, const char *name);
extern struct ospf *ospf_get_instance(unsigned short, bool *created);
extern struct ospf *ospf_lookup_by_inst_name(unsigned short instance,
const char *name);
struct in_addr *, int);
extern int ospf_oi_count(struct interface *);
+extern struct ospf_area *ospf_area_new(struct ospf *ospf,
+ struct in_addr area_id);
extern struct ospf_area *ospf_area_get(struct ospf *, struct in_addr);
extern void ospf_area_check_free(struct ospf *, struct in_addr);
extern struct ospf_area *ospf_area_lookup_by_area_id(struct ospf *,
int ospf_area_nssa_no_summary_set(struct ospf *, struct in_addr);
const char *ospf_get_name(const struct ospf *ospf);
+extern struct ospf_interface *add_ospf_interface(struct connected *co,
+ struct ospf_area *area);
+
+extern int p_spaces_compare_func(const struct p_space *a,
+ const struct p_space *b);
+extern int q_spaces_compare_func(const struct q_space *a,
+ const struct q_space *b);
#endif /* _ZEBRA_OSPFD_H */
ospfd/ospf_route.c \
ospfd/ospf_routemap.c \
ospfd/ospf_spf.c \
+ ospfd/ospf_ti_lfa.c \
ospfd/ospf_sr.c \
ospfd/ospf_te.c \
ospfd/ospf_vty.c \
ospfd/ospf_ri.h \
ospfd/ospf_route.h \
ospfd/ospf_spf.h \
+ ospfd/ospf_ti_lfa.h \
ospfd/ospf_sr.h \
ospfd/ospf_te.h \
ospfd/ospf_vty.h \
--- /dev/null
+/*_afl/*
+test_ospf_spf
+core
--- /dev/null
+#include <zebra.h>
+
+#include "lib/stream.h"
+#include "lib/vty.h"
+#include "lib/mpls.h"
+#include "lib/if.h"
+
+#include "ospfd/ospfd.h"
+#include "ospfd/ospf_spf.h"
+#include "ospfd/ospf_flood.h"
+#include "ospfd/ospf_lsa.h"
+#include "ospfd/ospf_lsdb.h"
+#include "ospfd/ospf_interface.h"
+#include "ospfd/ospf_sr.h"
+
+#include "common.h"
+
+struct thread_master *master;
+struct zebra_privs_t ospfd_privs;
+
+
+struct ospf_topology *test_find_topology(const char *name)
+{
+ if (strmatch(name, "topo1"))
+ return &topo1;
+ else if (strmatch(name, "topo2"))
+ return &topo2;
+ else if (strmatch(name, "topo3"))
+ return &topo3;
+
+ return NULL;
+}
+
+struct ospf_test_node *test_find_node(struct ospf_topology *topology,
+ const char *hostname)
+{
+ for (int i = 0; topology->nodes[i].hostname[0]; i++)
+ if (strmatch(hostname, topology->nodes[i].hostname))
+ return &topology->nodes[i];
+
+ return NULL;
+}
+
+static void inject_router_lsa(struct vty *vty, struct ospf *ospf,
+ struct ospf_topology *topology,
+ struct ospf_test_node *root,
+ struct ospf_test_node *tnode)
+{
+ struct ospf_area *area;
+ struct in_addr router_id;
+ struct in_addr adj_router_id;
+ struct prefix_ipv4 prefix;
+ struct in_addr data;
+ struct stream *s;
+ struct lsa_header *lsah;
+ struct ospf_lsa *new;
+ int length;
+ unsigned long putp;
+ uint16_t link_count;
+ struct ospf_test_node *tfound_adj_node;
+ struct ospf_test_adj *tadj;
+ bool is_self_lsa = false;
+
+ area = ospf->backbone;
+ inet_aton(tnode->router_id, &router_id);
+
+ if (strncmp(root->router_id, tnode->router_id, 256) == 0)
+ is_self_lsa = true;
+
+ s = stream_new(OSPF_MAX_LSA_SIZE);
+ lsa_header_set(s, LSA_OPTIONS_GET(area) | LSA_OPTIONS_NSSA_GET(area),
+ OSPF_ROUTER_LSA, router_id, router_id);
+
+ stream_putc(s, router_lsa_flags(area));
+ stream_putc(s, 0);
+
+ putp = stream_get_endp(s);
+ stream_putw(s, 0);
+
+ for (link_count = 0; tnode->adjacencies[link_count].hostname[0];
+ link_count++) {
+ tadj = &tnode->adjacencies[link_count];
+ tfound_adj_node = test_find_node(topology, tadj->hostname);
+ str2prefix_ipv4(tnode->adjacencies[link_count].network,
+ &prefix);
+
+ inet_aton(tfound_adj_node->router_id, &adj_router_id);
+ data.s_addr = prefix.prefix.s_addr;
+ link_info_set(&s, adj_router_id, data,
+ LSA_LINK_TYPE_POINTOPOINT, 0, tadj->metric);
+
+ masklen2ip(prefix.prefixlen, &data);
+ link_info_set(&s, prefix.prefix, data, LSA_LINK_TYPE_STUB, 0,
+ tadj->metric);
+ }
+
+ /* Don't forget the node itself (just a stub) */
+ str2prefix_ipv4(tnode->router_id, &prefix);
+ data.s_addr = 0xffffffff;
+ link_info_set(&s, prefix.prefix, data, LSA_LINK_TYPE_STUB, 0, 0);
+
+ /* Take twice the link count (for P2P and stub) plus the local stub */
+ stream_putw_at(s, putp, (2 * link_count) + 1);
+
+ length = stream_get_endp(s);
+ lsah = (struct lsa_header *)STREAM_DATA(s);
+ lsah->length = htons(length);
+
+ new = ospf_lsa_new_and_data(length);
+ new->area = area;
+ new->vrf_id = area->ospf->vrf_id;
+
+ if (is_self_lsa)
+ SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED);
+
+ memcpy(new->data, lsah, length);
+ stream_free(s);
+
+ ospf_lsdb_add(area->lsdb, new);
+
+ if (is_self_lsa) {
+ ospf_lsa_unlock(&area->router_lsa_self);
+ area->router_lsa_self = ospf_lsa_lock(new);
+ }
+}
+
+static void inject_sr_db_entry(struct vty *vty, struct ospf_test_node *tnode)
+{
+ struct in_addr router_id;
+ struct sr_node *srn;
+ struct sr_prefix *srp;
+
+ inet_aton(tnode->router_id, &router_id);
+
+ srn = ospf_sr_node_create(&router_id);
+
+ srn->srgb.range_size = 8000;
+ srn->srgb.lower_bound = 16000;
+ srn->msd = 16;
+
+ srp = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_prefix));
+
+ srp->adv_router = router_id;
+ srp->sid = tnode->label;
+ srp->srn = srn;
+
+ listnode_add(srn->ext_prefix, srp);
+}
+
+int topology_load(struct vty *vty, struct ospf_topology *topology,
+ struct ospf_test_node *root, struct ospf *ospf)
+{
+ struct ospf_test_node *tnode;
+
+ for (int i = 0; topology->nodes[i].hostname[0]; i++) {
+ tnode = &topology->nodes[i];
+
+ /* Inject a router LSA for each node, used for SPF */
+ inject_router_lsa(vty, ospf, topology, root, tnode);
+
+ /*
+ * SR information could also be inected via LSAs, but directly
+ * filling the SR DB with labels is just easier.
+ */
+ inject_sr_db_entry(vty, tnode);
+ }
+
+ return 0;
+}
--- /dev/null
+#ifndef _COMMON_OSPF_H
+#define _COMMON_OSPF_H
+
+#define MAX_ADJACENCIES 8
+#define MAX_NODES 12
+
+struct ospf_test_adj {
+ char hostname[256];
+ char network[256];
+ uint32_t metric;
+};
+
+struct ospf_test_node {
+ char hostname[256];
+ const char *router_id;
+ mpls_label_t label;
+ struct ospf_test_adj adjacencies[MAX_ADJACENCIES + 1];
+};
+
+struct ospf_topology {
+ struct ospf_test_node nodes[MAX_NODES + 1];
+};
+
+/* Prototypes. */
+extern struct ospf_topology *test_find_topology(const char *name);
+extern struct ospf_test_node *test_find_node(struct ospf_topology *topology,
+ const char *hostname);
+extern int topology_load(struct vty *vty, struct ospf_topology *topology,
+ struct ospf_test_node *root, struct ospf *ospf);
+
+/* Global variables. */
+extern struct thread_master *master;
+extern struct ospf_topology topo1;
+extern struct ospf_topology topo2;
+extern struct ospf_topology topo3;
+extern struct zebra_privs_t ospfd_privs;
+
+#endif /* _COMMON_OSPF_H */
--- /dev/null
+#include <zebra.h>
+
+#include "getopt.h"
+#include "thread.h"
+#include <lib/version.h>
+#include "vty.h"
+#include "command.h"
+#include "log.h"
+#include "vrf.h"
+#include "table.h"
+#include "mpls.h"
+
+#include "ospfd/ospfd.h"
+#include "ospfd/ospf_asbr.h"
+#include "ospfd/ospf_lsa.h"
+#include "ospfd/ospf_route.h"
+#include "ospfd/ospf_spf.h"
+#include "ospfd/ospf_ti_lfa.h"
+#include "ospfd/ospf_vty.h"
+#include "ospfd/ospf_dump.h"
+#include "ospfd/ospf_sr.h"
+
+#include "common.h"
+
+DECLARE_RBTREE_UNIQ(p_spaces, struct p_space, p_spaces_item,
+ p_spaces_compare_func)
+DECLARE_RBTREE_UNIQ(q_spaces, struct q_space, q_spaces_item,
+ q_spaces_compare_func)
+
+static struct ospf *test_init(struct ospf_test_node *root)
+{
+ struct ospf *ospf;
+ struct ospf_area *area;
+ struct in_addr area_id;
+ struct in_addr router_id;
+
+ ospf = ospf_new_alloc(0, NULL);
+
+ area_id.s_addr = OSPF_AREA_BACKBONE;
+ area = ospf_area_new(ospf, area_id);
+ listnode_add_sort(ospf->areas, area);
+
+ inet_aton(root->router_id, &router_id);
+ ospf->router_id = router_id;
+ ospf->router_id_static = router_id;
+
+ return ospf;
+}
+
+static void test_run_spf(struct vty *vty, struct ospf *ospf)
+{
+ struct route_table *new_table, *new_rtrs;
+ struct ospf_area *area;
+ struct p_space *p_space;
+ struct q_space *q_space;
+ char buf[MPLS_LABEL_STRLEN];
+
+ /* Just use the backbone for testing */
+ area = ospf->backbone;
+
+ new_table = route_table_init();
+ new_rtrs = route_table_init();
+
+ /* dryrun true, root_node false */
+ ospf_spf_calculate(area, area->router_lsa_self, new_table, new_rtrs,
+ true, false);
+
+ ospf_spf_print(vty, area->spf, 0);
+ ospf_route_table_print(vty, new_table);
+
+ /* TI-LFA testrun */
+ ospf_ti_lfa_generate_p_spaces(area);
+ ospf_ti_lfa_insert_backup_paths(area, new_table);
+
+ /* Print P/Q space information */
+ frr_each(p_spaces, area->p_spaces, p_space) {
+ vty_out(vty, "\n\nP Space for root %pI4 and link %pI4:\n",
+ &p_space->root->id, &p_space->protected_link->link_id);
+ ospf_spf_print(vty, p_space->root, 0);
+
+ frr_each(q_spaces, p_space->q_spaces, q_space) {
+ vty_out(vty, "\nQ Space for destination %pI4:\n",
+ &q_space->root->id);
+ ospf_spf_print(vty, q_space->root, 0);
+ if (q_space->label_stack) {
+ mpls_label2str(q_space->label_stack->num_labels,
+ q_space->label_stack->label, buf,
+ MPLS_LABEL_STRLEN, true);
+ vty_out(vty, "\nLabel stack: %s\n", buf);
+ } else {
+ vty_out(vty, "\nLabel stack not generated!\n");
+ }
+ }
+
+ vty_out(vty,
+ "\nPost-convergence path for root %pI4 and link %pI4:\n",
+ &p_space->root->id, &p_space->protected_link->link_id);
+ ospf_spf_print(vty, p_space->pc_spf, 0);
+ }
+
+ /* Cleanup */
+ ospf_ti_lfa_free_p_spaces(area);
+ ospf_spf_cleanup(area->spf, area->spf_vertex_list);
+
+ /*
+ * Print the new routing table which is augmented with TI-LFA backup
+ * paths (label stacks).
+ */
+ vty_out(vty, "\nNew route table:\n");
+ ospf_route_table_print(vty, new_table);
+}
+
+static int test_run(struct vty *vty, struct ospf_topology *topology,
+ struct ospf_test_node *root)
+{
+ struct ospf *ospf;
+
+ ospf = test_init(root);
+
+ /* Inject LSAs into the OSPF backbone according to the topology */
+ if (topology_load(vty, topology, root, ospf)) {
+ vty_out(vty, "%% Failed to load topology\n");
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "\n");
+ show_ip_ospf_database_summary(vty, ospf, 0, NULL);
+
+ test_run_spf(vty, ospf);
+
+ return 0;
+}
+
+DEFUN(test_ospf, test_ospf_cmd, "test ospf topology WORD root HOSTNAME",
+ "Test mode\n"
+ "Choose OSPF for SPF testing\n"
+ "Network topology to choose\n"
+ "Name of the network topology to choose\n"
+ "Root node to choose\n"
+ "Hostname of the root node to choose\n")
+{
+ struct ospf_topology *topology;
+ struct ospf_test_node *root;
+ int idx = 0;
+
+ /* Parse topology. */
+ argv_find(argv, argc, "topology", &idx);
+ topology = test_find_topology(argv[idx + 1]->arg);
+ if (!topology) {
+ vty_out(vty, "%% Topology not found\n");
+ return CMD_WARNING;
+ }
+
+ argv_find(argv, argc, "root", &idx);
+ root = test_find_node(topology, argv[idx + 1]->arg);
+ if (!root) {
+ vty_out(vty, "%% Root not found\n");
+ return CMD_WARNING;
+ }
+
+ return test_run(vty, topology, root);
+}
+
+static void vty_do_exit(int isexit)
+{
+ printf("\nend.\n");
+
+ cmd_terminate();
+ vty_terminate();
+ thread_master_free(master);
+
+ if (!isexit)
+ exit(0);
+}
+
+struct option longopts[] = {{"help", no_argument, NULL, 'h'},
+ {"debug", no_argument, NULL, 'd'},
+ {0} };
+
+/* Help information display. */
+static void usage(char *progname, int status)
+{
+ if (status != 0)
+ fprintf(stderr, "Try `%s --help' for more information.\n",
+ progname);
+ else {
+ printf("Usage : %s [OPTION...]\n\
+ospfd SPF test program.\n\n\
+-u, --debug Enable debugging\n\
+-h, --help Display this help and exit\n\
+\n\
+Report bugs to %s\n",
+ progname, FRR_BUG_ADDRESS);
+ }
+ exit(status);
+}
+
+int main(int argc, char **argv)
+{
+ char *p;
+ char *progname;
+ struct thread thread;
+ bool debug = false;
+
+ /* Set umask before anything for security */
+ umask(0027);
+
+ /* get program name */
+ progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]);
+
+ while (1) {
+ int opt;
+
+ opt = getopt_long(argc, argv, "hd", longopts, 0);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 0:
+ break;
+ case 'd':
+ debug = true;
+ break;
+ case 'h':
+ usage(progname, 0);
+ break;
+ default:
+ usage(progname, 1);
+ break;
+ }
+ }
+
+ /* master init. */
+ master = thread_master_create(NULL);
+
+ /* Library inits. */
+ cmd_init(1);
+ vty_init(master, false);
+ if (debug)
+ zlog_aux_init("NONE: ", LOG_DEBUG);
+ else
+ zlog_aux_init("NONE: ", ZLOG_DISABLED);
+
+ /* Install test command. */
+ install_element(VIEW_NODE, &test_ospf_cmd);
+
+ /* needed for SR DB init */
+ ospf_vty_init();
+ ospf_sr_init();
+
+ term_debug_ospf_event = 1;
+
+ /* Read input from .in file. */
+ vty_stdio(vty_do_exit);
+
+ /* Fetch next active thread. */
+ while (thread_fetch(master, &thread))
+ thread_call(&thread);
+
+ /* Not reached. */
+ exit(0);
+}
--- /dev/null
+import frrtest
+
+class TestOspfSPF(frrtest.TestMultiOut):
+ program = './test_ospf_spf'
+
+TestOspfSPF.exit_cleanly()
--- /dev/null
+#include <zebra.h>
+
+#include "mpls.h"
+#include "if.h"
+
+#include "ospfd/ospfd.h"
+
+#include "common.h"
+
+/*
+ * +---------+ +---------+
+ * | | | |
+ * | RT1 |eth-rt2 eth-rt1| RT2 |
+ * | 1.1.1.1 +---------------------+ 2.2.2.2 |
+ * | | 10.0.1.0/24 | |
+ * +---------+ +---------+
+ * |eth-rt3 eth-rt3|
+ * | |
+ * |10.0.3.0/24 |
+ * | |
+ * |eth-rt1 |
+ * +---------+ |
+ * | |eth-rt2 10.0.2.0/24|
+ * | RT3 +--------------------------+
+ * | 3.3.3.3 |
+ * | |
+ * +---------+
+ *
+ * P and Q spaces overlap here, hence just one P/Q node regardless of which
+ * link is protected. Hence the backup label stack just has one label.
+ */
+struct ospf_topology topo1 = {
+ .nodes =
+ {
+ {
+ .hostname = "rt1",
+ .router_id = "1.1.1.1",
+ .label = 10,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt2",
+ .network =
+ "10.0.1.1/24",
+ .metric = 10,
+ },
+ {
+ .hostname = "rt3",
+ .network =
+ "10.0.3.1/24",
+ .metric = 10,
+ },
+ },
+ },
+ {
+ .hostname = "rt2",
+ .router_id = "2.2.2.2",
+ .label = 20,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt1",
+ .network =
+ "10.0.1.2/24",
+ .metric = 10,
+ },
+ {
+ .hostname = "rt3",
+ .network =
+ "10.0.2.1/24",
+ .metric = 10,
+ },
+ },
+ },
+ {
+ .hostname = "rt3",
+ .router_id = "3.3.3.3",
+ .label = 30,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt1",
+ .network =
+ "10.0.3.2/24",
+ .metric = 10,
+ },
+ {
+ .hostname = "rt2",
+ .network =
+ "10.0.2.2/24",
+ .metric = 10,
+ },
+ },
+ },
+ },
+};
+
+
+/*
+ * +---------+ +---------+
+ * | | | |
+ * | RT1 |eth-rt2 eth-rt1| RT2 |
+ * | 1.1.1.1 +---------------------+ 2.2.2.2 |
+ * | | 10.0.1.0/24 (10) | |
+ * +---------+ +---------+
+ * |eth-rt3 eth-rt3|
+ * | |
+ * |10.0.3.0/24 (30) |
+ * | |
+ * |eth-rt1 |
+ * +---------+ |
+ * | |eth-rt2 10.0.2.0/24|(10)
+ * | RT3 +--------------------------+
+ * | 3.3.3.3 |
+ * | |
+ * +---------+
+ *
+ * Regarding the subnet 10.0.1.0/24, the P space of RT1 is just RT1 itself
+ * while the Q space of RT3 consists of RT3 and RT2. Hence the P and Q
+ * nodes are disjunct (tricky: the root node is the P node here). For the
+ * backup label stack just one label is necessary.
+ */
+struct ospf_topology topo2 = {
+ .nodes =
+ {
+ {
+ .hostname = "rt1",
+ .router_id = "1.1.1.1",
+ .label = 10,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt2",
+ .network =
+ "10.0.1.1/24",
+ .metric = 10,
+ },
+ {
+ .hostname = "rt3",
+ .network =
+ "10.0.3.1/24",
+ .metric = 30,
+ },
+ },
+ },
+ {
+ .hostname = "rt2",
+ .router_id = "2.2.2.2",
+ .label = 20,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt1",
+ .network =
+ "10.0.1.2/24",
+ .metric = 10,
+ },
+ {
+ .hostname = "rt3",
+ .network =
+ "10.0.2.1/24",
+ .metric = 10,
+ },
+ },
+ },
+ {
+ .hostname = "rt3",
+ .router_id = "3.3.3.3",
+ .label = 30,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt1",
+ .network =
+ "10.0.3.2/24",
+ .metric = 30,
+ },
+ {
+ .hostname = "rt2",
+ .network =
+ "10.0.2.2/24",
+ .metric = 10,
+ },
+ },
+ },
+ },
+};
+
+/*
+ * +---------+ +---------+
+ * | | | |
+ * | RT1 |eth-rt4 eth-rt1| RT4 |
+ * | 1.1.1.1 +---------------------+ 4.4.4.4 |
+ * | | 10.0.4.0/24 (10) | |
+ * +---------+ +---------+
+ * |eth-rt2 eth-rt3|
+ * | |
+ * |10.0.1.0/24 (10) |
+ * | 10.0.3.0/24 (10) |
+ * |eth-rt1 eth-rt4|
+ * +---------+ +---------+
+ * | |eth-rt3 eth-rt2| |
+ * | RT2 +---------------------+ RT3 |
+ * | 2.2.2.2 | 10.0.2.0/24 (20) | 3.3.3.3 |
+ * | | | |
+ * +---------+ +---------+
+ *
+ * Regarding the protected subnet 10.0.4.0/24, the P and Q spaces for root RT1
+ * and destination RT4 are disjunct and the P node is RT2 while RT3 is the Q
+ * node. Hence the backup label stack here is 16020/16030. Note that here the
+ * P and Q nodes are neither the root nor the destination nodes, so this is a
+ * case where you really need a label stack consisting of two labels.
+ */
+struct ospf_topology topo3 = {
+ .nodes =
+ {
+ {
+ .hostname = "rt1",
+ .router_id = "1.1.1.1",
+ .label = 10,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt2",
+ .network =
+ "10.0.1.1/24",
+ .metric = 10,
+ },
+ {
+ .hostname = "rt4",
+ .network =
+ "10.0.4.1/24",
+ .metric = 10,
+ },
+ },
+ },
+ {
+ .hostname = "rt2",
+ .router_id = "2.2.2.2",
+ .label = 20,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt1",
+ .network =
+ "10.0.1.2/24",
+ .metric = 10,
+ },
+ {
+ .hostname = "rt3",
+ .network =
+ "10.0.2.1/24",
+ .metric = 20,
+ },
+ },
+ },
+ {
+ .hostname = "rt3",
+ .router_id = "3.3.3.3",
+ .label = 30,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt2",
+ .network =
+ "10.0.2.2/24",
+ .metric = 20,
+ },
+ {
+ .hostname = "rt4",
+ .network =
+ "10.0.3.1/24",
+ .metric = 10,
+ },
+ },
+ },
+ {
+ .hostname = "rt4",
+ .router_id = "4.4.4.4",
+ .label = 40,
+ .adjacencies =
+ {
+ {
+ .hostname = "rt1",
+ .network =
+ "10.0.4.2/24",
+ .metric = 10,
+ },
+ {
+ .hostname = "rt3",
+ .network =
+ "10.0.3.2/24",
+ .metric = 10,
+ },
+ },
+ },
+ },
+};
IGNORE_ISISD = --ignore=isisd/
endif
+if OSPFD
+TESTS_OSPFD = \
+ tests/ospfd/test_ospf_spf \
+ # end
+else
+TESTS_OSPFD =
+endif
+
if OSPF6D
TESTS_OSPF6D = \
tests/ospf6d/test_lsdb \
tests/lib/northbound/test_oper_data \
$(TESTS_BGPD) \
$(TESTS_ISISD) \
+ $(TESTS_OSPFD) \
$(TESTS_OSPF6D) \
$(TESTS_ZEBRA) \
# end
tests/lib/cli/common_cli.h \
tests/lib/test_typelist.h \
tests/isisd/test_common.h \
+ tests/ospfd/common.h \
# end
#
ALL_TESTS_LDADD = lib/libfrr.la $(LIBCAP)
BGP_TEST_LDADD = bgpd/libbgp.a $(RFPLDADD) $(ALL_TESTS_LDADD) -lm
ISISD_TEST_LDADD = isisd/libisis.a $(ALL_TESTS_LDADD)
+OSPFD_TEST_LDADD = ospfd/libfrrospf.a $(ALL_TESTS_LDADD)
OSPF6_TEST_LDADD = ospf6d/libospf6.a $(ALL_TESTS_LDADD)
ZEBRA_TEST_LDADD = zebra/label_manager.o $(ALL_TESTS_LDADD)
tests_isisd_test_isis_vertex_queue_LDADD = $(ISISD_TEST_LDADD)
tests_isisd_test_isis_vertex_queue_SOURCES = tests/isisd/test_isis_vertex_queue.c tests/isisd/test_common.c
+tests_ospfd_test_ospf_spf_CFLAGS = $(TESTS_CFLAGS)
+tests_ospfd_test_ospf_spf_CPPFLAGS = $(TESTS_CPPFLAGS)
+tests_ospfd_test_ospf_spf_LDADD = $(OSPFD_TEST_LDADD)
+tests_ospfd_test_ospf_spf_SOURCES = tests/ospfd/test_ospf_spf.c tests/ospfd/common.c tests/ospfd/topologies.c
+
tests_lib_cxxcompat_CFLAGS = $(TESTS_CFLAGS) $(CXX_COMPAT_CFLAGS) $(WERROR)
tests_lib_cxxcompat_CPPFLAGS = $(TESTS_CPPFLAGS)
tests_lib_cxxcompat_SOURCES = tests/lib/cxxcompat.c
tests/isisd/test_isis_spf.in \
tests/isisd/test_isis_spf.refout \
tests/isisd/test_isis_vertex_queue.py \
+ tests/ospfd/test_ospf_spf.py \
tests/lib/cli/test_commands.in \
tests/lib/cli/test_commands.py \
tests/lib/cli/test_commands.refout \