]> git.proxmox.com Git - mirror_frr.git/blame - isisd/isis_lfa.c
isisd: store LSPs associated to all SPF adjacencies
[mirror_frr.git] / isisd / isis_lfa.c
CommitLineData
c951ee6e
RW
1/*
2 * Copyright (C) 2020 NetDEF, Inc.
3 * Renato Westphal
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <zebra.h>
21
22#include "linklist.h"
23#include "log.h"
24#include "memory.h"
25#include "vrf.h"
26#include "table.h"
27#include "srcdest_table.h"
28
29#include "isis_common.h"
30#include "isisd.h"
31#include "isis_misc.h"
32#include "isis_adjacency.h"
33#include "isis_circuit.h"
34#include "isis_lsp.h"
35#include "isis_spf.h"
36#include "isis_route.h"
37#include "isis_mt.h"
38#include "isis_tlvs.h"
39#include "isis_spf_private.h"
40#include "isisd/isis_errors.h"
41
42DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_NODE, "ISIS SPF Node");
43
44static inline int isis_spf_node_compare(const struct isis_spf_node *a,
45 const struct isis_spf_node *b)
46{
47 return memcmp(a->sysid, b->sysid, sizeof(a->sysid));
48}
49RB_GENERATE(isis_spf_nodes, isis_spf_node, entry, isis_spf_node_compare)
50
51/**
52 * Initialize list of SPF nodes.
53 *
54 * @param nodes List of SPF nodes
55 */
56void isis_spf_node_list_init(struct isis_spf_nodes *nodes)
57{
58 RB_INIT(isis_spf_nodes, nodes);
59}
60
61/**
62 * Clear list of SPF nodes, releasing all allocated memory.
63 *
64 * @param nodes List of SPF nodes
65 */
66void isis_spf_node_list_clear(struct isis_spf_nodes *nodes)
67{
68 while (!RB_EMPTY(isis_spf_nodes, nodes)) {
69 struct isis_spf_node *node = RB_ROOT(isis_spf_nodes, nodes);
70
71 if (node->adjacencies)
72 list_delete(&node->adjacencies);
73 if (node->lfa.spftree)
74 isis_spftree_del(node->lfa.spftree);
75 if (node->lfa.spftree_reverse)
76 isis_spftree_del(node->lfa.spftree_reverse);
77 isis_spf_node_list_clear(&node->lfa.p_space);
78 RB_REMOVE(isis_spf_nodes, nodes, node);
79 XFREE(MTYPE_ISIS_SPF_NODE, node);
80 }
81}
82
83/**
84 * Add new node to list of SPF nodes.
85 *
86 * @param nodes List of SPF nodes
87 * @param sysid Node System ID
88 *
89 * @return Pointer to new IS-IS SPF node structure.
90 */
91struct isis_spf_node *isis_spf_node_new(struct isis_spf_nodes *nodes,
92 const uint8_t *sysid)
93{
94 struct isis_spf_node *node;
95
96 node = XCALLOC(MTYPE_ISIS_SPF_NODE, sizeof(*node));
97 memcpy(node->sysid, sysid, sizeof(node->sysid));
98 node->adjacencies = list_new();
99 isis_spf_node_list_init(&node->lfa.p_space);
100 RB_INSERT(isis_spf_nodes, nodes, node);
101
102 return node;
103}
104
105/**
106 * Lookup SPF node by its System ID on the given list.
107 *
108 * @param nodes List of SPF nodes
109 * @param sysid Node System ID
110 *
111 * @return Pointer to SPF node if found, NULL otherwise
112 */
113struct isis_spf_node *isis_spf_node_find(const struct isis_spf_nodes *nodes,
114 const uint8_t *sysid)
115{
116 struct isis_spf_node node = {};
117
118 memcpy(node.sysid, sysid, sizeof(node.sysid));
119 return RB_FIND(isis_spf_nodes, nodes, &node);
120}
121
122/**
123 * Check if a given IS-IS adjacency needs to be excised when computing the SPF
124 * post-convergence tree.
125 *
126 * @param spftree IS-IS SPF tree
127 * @param id Adjacency System ID (or LAN ID of the designated router
128 * for broadcast interfaces)
129 *
130 * @return true if the adjacency needs to be excised, false
131 * otherwise
132 */
133bool isis_lfa_excise_adj_check(const struct isis_spftree *spftree,
134 const uint8_t *id)
135{
136 const struct lfa_protected_resource *resource;
137
138 if (spftree->type != SPF_TYPE_TI_LFA)
139 return false;
140
141 /*
142 * Adjacencies formed over the failed interface should be excised both
143 * when using link and node protection.
144 */
145 resource = &spftree->lfa.protected_resource;
146 if (!memcmp(resource->adjacency, id, ISIS_SYS_ID_LEN + 1))
147 return true;
148
149 return false;
150}
151
152/**
153 * Check if a given IS-IS node needs to be excised when computing the SPF
154 * post-convergence tree.
155 *
156 * @param spftree IS-IS SPF tree
157 * @param id Node System ID
158 *
159 * @return true if the node needs to be excised, false otherwise
160 */
161bool isis_lfa_excise_node_check(const struct isis_spftree *spftree,
162 const uint8_t *id)
163{
164 const struct lfa_protected_resource *resource;
165
166 if (spftree->type != SPF_TYPE_TI_LFA)
167 return false;
168
169 /*
170 * When using node protection, nodes reachable over the failed interface
171 * must be excised.
172 */
173 resource = &spftree->lfa.protected_resource;
174 if (resource->type == LFA_LINK_PROTECTION)
175 return false;
176
177 if (isis_spf_node_find(&resource->nodes, id))
178 return true;
179
180 return false;
181}
182
c951ee6e
RW
183struct tilfa_find_pnode_prefix_sid_args {
184 uint32_t sid_index;
185};
186
187static int tilfa_find_pnode_prefix_sid_cb(const struct prefix *prefix,
188 uint32_t metric, bool external,
189 struct isis_subtlvs *subtlvs,
190 void *arg)
191{
192 struct tilfa_find_pnode_prefix_sid_args *args = arg;
193 struct isis_prefix_sid *psid;
194
195 if (!subtlvs || subtlvs->prefix_sids.count == 0)
196 return LSP_ITER_CONTINUE;
197
198 psid = (struct isis_prefix_sid *)subtlvs->prefix_sids.head;
199
200 /* Require the node flag to be set. */
201 if (!CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_NODE))
202 return LSP_ITER_CONTINUE;
203
204 args->sid_index = psid->value;
205
206 return LSP_ITER_STOP;
207}
208
209/* Find Prefix-SID associated to a System ID. */
210static uint32_t tilfa_find_pnode_prefix_sid(struct isis_spftree *spftree,
211 const uint8_t *sysid)
212{
213 struct isis_lsp *lsp;
214 struct tilfa_find_pnode_prefix_sid_args args;
215
216 lsp = isis_root_system_lsp(spftree->lspdb, sysid);
217 if (!lsp)
218 return UINT32_MAX;
219
220 args.sid_index = UINT32_MAX;
221 isis_lsp_iterate_ip_reach(lsp, spftree->family, spftree->mtid,
222 tilfa_find_pnode_prefix_sid_cb, &args);
223
224 return args.sid_index;
225}
226
227struct tilfa_find_qnode_adj_sid_args {
228 const uint8_t *qnode_sysid;
229 mpls_label_t label;
230};
231
232static int tilfa_find_qnode_adj_sid_cb(const uint8_t *id, uint32_t metric,
233 bool oldmetric,
234 struct isis_ext_subtlvs *subtlvs,
235 void *arg)
236{
237 struct tilfa_find_qnode_adj_sid_args *args = arg;
238 struct isis_adj_sid *adj_sid;
239
240 if (memcmp(id, args->qnode_sysid, ISIS_SYS_ID_LEN))
241 return LSP_ITER_CONTINUE;
242 if (!subtlvs || subtlvs->adj_sid.count == 0)
243 return LSP_ITER_CONTINUE;
244
245 adj_sid = (struct isis_adj_sid *)subtlvs->adj_sid.head;
246 args->label = adj_sid->sid;
247
248 return LSP_ITER_STOP;
249}
250
251/* Find Adj-SID associated to a pair of System IDs. */
252static mpls_label_t tilfa_find_qnode_adj_sid(struct isis_spftree *spftree,
253 const uint8_t *source_sysid,
254 const uint8_t *qnode_sysid)
255{
256 struct isis_lsp *lsp;
257 struct tilfa_find_qnode_adj_sid_args args;
258
259 lsp = isis_root_system_lsp(spftree->lspdb, source_sysid);
260 if (!lsp)
261 return MPLS_INVALID_LABEL;
262
263 args.qnode_sysid = qnode_sysid;
264 args.label = MPLS_INVALID_LABEL;
265 isis_lsp_iterate_is_reach(lsp, spftree->mtid,
266 tilfa_find_qnode_adj_sid_cb, &args);
267
268 return args.label;
269}
270
271/*
272 * Compute the MPLS label stack associated to a TI-LFA repair list. This
273 * needs to be computed separately for each adjacency since different
274 * neighbors can have different SRGBs.
275 */
276static struct mpls_label_stack *
277tilfa_compute_label_stack(struct lspdb_head *lspdb,
278 const struct isis_spf_adj *sadj,
279 const struct list *repair_list)
280{
281 struct mpls_label_stack *label_stack;
282 struct isis_tilfa_sid *sid;
283 struct listnode *node;
284 size_t i = 0;
285
286 /* Allocate label stack. */
287 label_stack = XCALLOC(MTYPE_ISIS_NEXTHOP_LABELS,
288 sizeof(struct mpls_label_stack)
289 + listcount(repair_list)
290 * sizeof(mpls_label_t));
291 label_stack->num_labels = listcount(repair_list);
292
293 for (ALL_LIST_ELEMENTS_RO(repair_list, node, sid)) {
4c75f7c7 294 const uint8_t *target_node;
c951ee6e
RW
295 struct isis_sr_block *srgb;
296 mpls_label_t label;
297
298 switch (sid->type) {
299 case TILFA_SID_PREFIX:
4c75f7c7
RW
300 if (sid->value.index.remote)
301 target_node = sid->value.index.remote_sysid;
302 else
303 target_node = sadj->id;
304 srgb = isis_sr_find_srgb(lspdb, target_node);
c951ee6e
RW
305 if (!srgb) {
306 zlog_warn("%s: SRGB not found for node %s",
307 __func__,
4c75f7c7 308 print_sys_hostname(target_node));
c951ee6e
RW
309 goto error;
310 }
311
312 /* Check if the SID index falls inside the SRGB. */
4c75f7c7 313 if (sid->value.index.value >= srgb->range_size) {
c951ee6e
RW
314 flog_warn(
315 EC_ISIS_SID_OVERFLOW,
316 "%s: SID index %u falls outside remote SRGB range",
4c75f7c7 317 __func__, sid->value.index.value);
c951ee6e
RW
318 goto error;
319 }
320
321 /*
322 * Prefix-SID: map SID index to label value within the
323 * SRGB.
324 */
4c75f7c7 325 label = srgb->lower_bound + sid->value.index.value;
c951ee6e
RW
326 break;
327 case TILFA_SID_ADJ:
328 /* Adj-SID: absolute label value can be used directly */
329 label = sid->value.label;
330 break;
331 default:
332 flog_err(EC_LIB_DEVELOPMENT,
333 "%s: unknown TI-LFA SID type [%u]", __func__,
334 sid->type);
335 exit(1);
336 }
337 label_stack->label[i++] = label;
338 }
339
340 return label_stack;
341
342error:
343 XFREE(MTYPE_ISIS_NEXTHOP_LABELS, label_stack);
344 return NULL;
345}
346
347static int tilfa_repair_list_apply(struct isis_spftree *spftree,
348 struct isis_vertex *vertex_dest,
349 const struct isis_vertex *vertex_pnode,
350 const struct list *repair_list)
351{
352 struct isis_vertex_adj *vadj;
353 struct listnode *node;
354
355 for (ALL_LIST_ELEMENTS_RO(vertex_dest->Adj_N, node, vadj)) {
356 struct isis_spf_adj *sadj = vadj->sadj;
357 struct mpls_label_stack *label_stack;
358
359 if (!isis_vertex_adj_exists(spftree, vertex_pnode, sadj))
360 continue;
361
362 assert(!vadj->label_stack);
363 label_stack = tilfa_compute_label_stack(spftree->lspdb, sadj,
364 repair_list);
365 if (!label_stack) {
366 char buf[VID2STR_BUFFER];
367
368 vid2string(vertex_dest, buf, sizeof(buf));
369 zlog_warn(
370 "%s: %s %s adjacency %s: failed to compute label stack",
371 __func__, vtype2string(vertex_dest->type), buf,
372 print_sys_hostname(sadj->id));
373 return -1;
374 }
375
376 vadj->label_stack = label_stack;
377 }
378
379 return 0;
380}
381
382/*
383 * Check if a node belongs to the extended P-space corresponding to a given
384 * destination.
385 */
386static bool lfa_ext_p_space_check(const struct isis_spftree *spftree_pc,
387 const struct isis_vertex *vertex_dest,
388 const struct isis_vertex *vertex)
389{
390 struct isis_spftree *spftree_old = spftree_pc->lfa.old.spftree;
391 struct isis_vertex_adj *vadj;
392 struct listnode *node;
393
394 /* Check the local P-space first. */
395 if (isis_spf_node_find(&spftree_pc->lfa.p_space, vertex->N.id))
396 return true;
397
398 /*
399 * Check the P-space of the adjacent routers used to reach the
400 * destination.
401 */
402 for (ALL_LIST_ELEMENTS_RO(vertex_dest->Adj_N, node, vadj)) {
403 struct isis_spf_adj *sadj = vadj->sadj;
404 struct isis_spf_node *adj_node;
405
406 adj_node =
407 isis_spf_node_find(&spftree_old->adj_nodes, sadj->id);
408 if (!adj_node)
409 continue;
410
411 if (isis_spf_node_find(&adj_node->lfa.p_space, vertex->N.id))
412 return true;
413 }
414
415 return false;
416}
417
418/* Check if a node belongs to the Q-space. */
419static bool lfa_q_space_check(const struct isis_spftree *spftree_pc,
420 const struct isis_vertex *vertex)
421{
422 return isis_spf_node_find(&spftree_pc->lfa.q_space, vertex->N.id);
423}
424
425/* This is a recursive function. */
426static int tilfa_build_repair_list(struct isis_spftree *spftree_pc,
427 struct isis_vertex *vertex_dest,
428 const struct isis_vertex *vertex,
429 const struct isis_vertex *vertex_child,
430 struct isis_spf_nodes *used_pnodes,
431 struct list *repair_list)
432{
433 struct isis_vertex *pvertex;
434 struct listnode *node;
435 bool is_pnode, is_qnode;
436 char buf[VID2STR_BUFFER];
4c75f7c7
RW
437 struct isis_tilfa_sid sid_dest = {}, sid_qnode = {}, sid_pnode = {};
438 uint32_t sid_index;
c951ee6e
RW
439 mpls_label_t label_qnode;
440
2866b119 441 if (IS_DEBUG_LFA) {
c951ee6e 442 vid2string(vertex, buf, sizeof(buf));
2866b119
RW
443 zlog_debug("ISIS-LFA: vertex %s %s", vtype2string(vertex->type),
444 buf);
c951ee6e
RW
445 }
446
4c75f7c7
RW
447 /* Push original Prefix-SID label when necessary. */
448 if (VTYPE_IP(vertex->type) && vertex->N.ip.sr.present) {
449 pvertex = listnode_head(vertex->parents);
450 assert(pvertex);
451
452 sid_index = vertex->N.ip.sr.sid.value;
2866b119 453 if (IS_DEBUG_LFA)
4c75f7c7 454 zlog_debug(
2866b119 455 "ISIS-LFA: pushing Prefix-SID to %pFX (index %u)",
4c75f7c7
RW
456 &vertex->N.ip.p.dest, sid_index);
457 sid_dest.type = TILFA_SID_PREFIX;
458 sid_dest.value.index.value = sid_index;
459 sid_dest.value.index.remote = true;
460 memcpy(sid_dest.value.index.remote_sysid, pvertex->N.id,
461 sizeof(sid_dest.value.index.remote_sysid));
462 listnode_add_head(repair_list, &sid_dest);
463 }
464
c951ee6e
RW
465 if (!vertex_child)
466 goto parents;
467 if (vertex->type != VTYPE_NONPSEUDO_IS
468 && vertex->type != VTYPE_NONPSEUDO_TE_IS)
469 goto parents;
470 if (!VTYPE_IS(vertex_child->type))
471 vertex_child = NULL;
472
473 /* Check if node is part of the extended P-space and/or Q-space. */
474 is_pnode = lfa_ext_p_space_check(spftree_pc, vertex_dest, vertex);
475 is_qnode = lfa_q_space_check(spftree_pc, vertex);
476
477 /* Push Adj-SID label when necessary. */
478 if ((!is_qnode
479 || spftree_pc->lfa.protected_resource.type == LFA_NODE_PROTECTION)
480 && vertex_child) {
481 label_qnode = tilfa_find_qnode_adj_sid(spftree_pc, vertex->N.id,
482 vertex_child->N.id);
483 if (label_qnode == MPLS_INVALID_LABEL) {
2866b119 484 zlog_warn("ISIS-LFA: failed to find %s->%s Adj-SID",
c951ee6e
RW
485 print_sys_hostname(vertex->N.id),
486 print_sys_hostname(vertex_child->N.id));
487 return -1;
488 }
2866b119 489 if (IS_DEBUG_LFA)
c951ee6e 490 zlog_debug(
2866b119 491 "ISIS-LFA: pushing %s->%s Adj-SID (label %u)",
c951ee6e
RW
492 print_sys_hostname(vertex->N.id),
493 print_sys_hostname(vertex_child->N.id),
494 label_qnode);
495 sid_qnode.type = TILFA_SID_ADJ;
496 sid_qnode.value.label = label_qnode;
497 listnode_add_head(repair_list, &sid_qnode);
498 }
499
500 /* Push Prefix-SID label when necessary. */
501 if (is_pnode) {
c951ee6e
RW
502 /* The same P-node can't be used more than once. */
503 if (isis_spf_node_find(used_pnodes, vertex->N.id)) {
2866b119 504 if (IS_DEBUG_LFA)
c951ee6e 505 zlog_debug(
2866b119 506 "ISIS-LFA: skipping already used P-node");
c951ee6e
RW
507 return 0;
508 }
509 isis_spf_node_new(used_pnodes, vertex->N.id);
510
511 if (!vertex_child) {
2866b119 512 if (IS_DEBUG_LFA)
c951ee6e 513 zlog_debug(
2866b119 514 "ISIS-LFA: destination is within Ext-P-Space");
c951ee6e
RW
515 return 0;
516 }
517
518 sid_index =
519 tilfa_find_pnode_prefix_sid(spftree_pc, vertex->N.id);
520 if (sid_index == UINT32_MAX) {
521 zlog_warn(
2866b119 522 "ISIS-LFA: failed to find Prefix-SID corresponding to PQ-node %s",
c951ee6e
RW
523 print_sys_hostname(vertex->N.id));
524 return -1;
525 }
526
2866b119 527 if (IS_DEBUG_LFA)
c951ee6e 528 zlog_debug(
2866b119 529 "ISIS-LFA: pushing Node-SID to %s (index %u)",
c951ee6e
RW
530 print_sys_hostname(vertex->N.id), sid_index);
531 sid_pnode.type = TILFA_SID_PREFIX;
4c75f7c7 532 sid_pnode.value.index.value = sid_index;
c951ee6e
RW
533 listnode_add_head(repair_list, &sid_pnode);
534
535 /* Apply repair list. */
6dfb7f59
RW
536 if (listcount(repair_list)
537 > spftree_pc->area->srdb.config.msd) {
538 zlog_warn(
2866b119 539 "ISIS-LFA: list of repair segments exceeds locally configured MSD (%u > %u)",
6dfb7f59
RW
540 listcount(repair_list),
541 spftree_pc->area->srdb.config.msd);
542 return -1;
543 }
c951ee6e
RW
544 if (tilfa_repair_list_apply(spftree_pc, vertex_dest, vertex,
545 repair_list)
546 != 0)
547 return -1;
548 return 0;
549 }
550
551parents:
552 for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, pvertex)) {
553 struct list *repair_list_parent;
554 bool ecmp;
555 int ret;
556
557 ecmp = (listcount(vertex->parents) > 1) ? true : false;
558 repair_list_parent = ecmp ? list_dup(repair_list) : repair_list;
559 ret = tilfa_build_repair_list(spftree_pc, vertex_dest, pvertex,
560 vertex, used_pnodes,
561 repair_list_parent);
562 if (ecmp)
563 list_delete(&repair_list_parent);
564 if (ret != 0)
565 return ret;
566 }
567
568 return 0;
569}
570
571static const char *lfa_protection_type2str(enum lfa_protection_type type)
572{
573 switch (type) {
574 case LFA_LINK_PROTECTION:
575 return "link protection";
576 case LFA_NODE_PROTECTION:
577 return "node protection";
578 default:
579 return "unknown protection type";
580 }
581}
582
583static const char *
584lfa_protected_resource2str(const struct lfa_protected_resource *resource)
585{
586 const uint8_t *fail_id;
587 static char buffer[128];
588
589 fail_id = resource->adjacency;
590 snprintf(buffer, sizeof(buffer), "%s.%u's failure (%s)",
591 print_sys_hostname(fail_id), LSP_PSEUDO_ID(fail_id),
592 lfa_protection_type2str(resource->type));
593
594 return buffer;
595}
596
597static bool
598spf_adj_check_is_affected(const struct isis_spf_adj *sadj,
599 const struct lfa_protected_resource *resource,
600 const uint8_t *root_sysid, bool reverse)
601{
602 if (!!CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST)
603 != !!LSP_PSEUDO_ID(resource->adjacency))
604 return false;
605
606 if (CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST)) {
607 if (!memcmp(sadj->lan.desig_is_id, resource->adjacency,
608 ISIS_SYS_ID_LEN + 1))
609 return true;
610 } else {
611 if (!reverse
612 && !memcmp(sadj->id, resource->adjacency, ISIS_SYS_ID_LEN))
613 return true;
614 if (reverse && !memcmp(sadj->id, root_sysid, ISIS_SYS_ID_LEN))
615 return true;
616 }
617
618 return false;
619}
620
621/* Check if the given SPF vertex needs LFA protection. */
622static bool lfa_check_needs_protection(const struct isis_spftree *spftree_pc,
623 const struct isis_vertex *vertex)
624{
625 struct isis_vertex *vertex_old;
626 struct listnode *node;
627 size_t affected_nhs = 0;
628 struct isis_vertex_adj *vadj;
629
d4fcd8bd
RW
630 /* Local routes don't need protection. */
631 if (VTYPE_IP(vertex->type) && vertex->depth == 1)
632 return false;
633
c951ee6e
RW
634 /* Only local adjacencies need Adj-SID protection. */
635 if (VTYPE_IS(vertex->type)
636 && !isis_adj_find(spftree_pc->area, spftree_pc->level,
637 vertex->N.id))
638 return false;
639
640 vertex_old = isis_find_vertex(&spftree_pc->lfa.old.spftree->paths,
641 &vertex->N, vertex->type);
642 if (!vertex_old)
643 return false;
644
645 for (ALL_LIST_ELEMENTS_RO(vertex_old->Adj_N, node, vadj)) {
646 struct isis_spf_adj *sadj = vadj->sadj;
647
648 if (spf_adj_check_is_affected(
649 sadj, &spftree_pc->lfa.protected_resource,
650 spftree_pc->sysid, false))
651 affected_nhs++;
652 }
653
654 /*
655 * No need to compute backup paths for ECMP routes, except if all
656 * primary nexthops share the same broadcast interface.
657 */
658 if (listcount(vertex_old->Adj_N) == affected_nhs)
659 return true;
660
661 return false;
662}
663
664/**
665 * Check if the given SPF vertex needs protection and, if so, compute and
666 * install the corresponding repair paths.
667 *
668 * @param spftree_pc The post-convergence SPF tree
669 * @param vertex IS-IS SPF vertex to check
670 *
671 * @return 0 if the vertex needs to be protected, -1 otherwise
672 */
673int isis_lfa_check(struct isis_spftree *spftree_pc, struct isis_vertex *vertex)
674{
675 struct isis_spf_nodes used_pnodes;
676 char buf[VID2STR_BUFFER];
677 struct list *repair_list;
678 int ret;
679
680 if (!spftree_pc->area->srdb.enabled)
681 return -1;
682
2866b119 683 if (IS_DEBUG_LFA)
c951ee6e
RW
684 vid2string(vertex, buf, sizeof(buf));
685
686 if (!lfa_check_needs_protection(spftree_pc, vertex)) {
2866b119 687 if (IS_DEBUG_LFA)
c951ee6e 688 zlog_debug(
2866b119 689 "ISIS-LFA: %s %s unaffected by %s",
c951ee6e
RW
690 vtype2string(vertex->type), buf,
691 lfa_protected_resource2str(
692 &spftree_pc->lfa.protected_resource));
693
694 return -1;
695 }
696
697 /*
054fda12 698 * Check if the route/adjacency was already covered by node protection.
c951ee6e 699 */
054fda12
RW
700 if (VTYPE_IS(vertex->type)) {
701 struct isis_adjacency *adj;
702
703 adj = isis_adj_find(spftree_pc->area, spftree_pc->level,
704 vertex->N.id);
705 if (adj
706 && isis_sr_adj_sid_find(adj, spftree_pc->family,
707 ISIS_SR_LAN_BACKUP)) {
2866b119 708 if (IS_DEBUG_LFA)
054fda12 709 zlog_debug(
2866b119 710 "ISIS-LFA: %s %s already covered by node protection",
054fda12
RW
711 vtype2string(vertex->type), buf);
712
713 return -1;
714 }
715 }
c951ee6e
RW
716 if (VTYPE_IP(vertex->type)) {
717 struct route_table *route_table;
718
719 route_table = spftree_pc->lfa.old.spftree->route_table_backup;
d47d6089 720 if (route_node_lookup(route_table, &vertex->N.ip.p.dest)) {
2866b119 721 if (IS_DEBUG_LFA)
c951ee6e 722 zlog_debug(
2866b119 723 "ISIS-LFA: %s %s already covered by node protection",
c951ee6e
RW
724 vtype2string(vertex->type), buf);
725
726 return -1;
727 }
728 }
729
2866b119 730 if (IS_DEBUG_LFA)
c951ee6e 731 zlog_debug(
2866b119 732 "ISIS-LFA: computing repair path(s) of %s %s w.r.t %s",
c951ee6e
RW
733 vtype2string(vertex->type), buf,
734 lfa_protected_resource2str(
735 &spftree_pc->lfa.protected_resource));
736
737 /* Create base repair list. */
738 repair_list = list_new();
739
740 isis_spf_node_list_init(&used_pnodes);
741 ret = tilfa_build_repair_list(spftree_pc, vertex, vertex, NULL,
742 &used_pnodes, repair_list);
743 isis_spf_node_list_clear(&used_pnodes);
744 list_delete(&repair_list);
745 if (ret != 0)
6dfb7f59 746 zlog_warn(
2866b119 747 "ISIS-LFA: failed to compute repair path(s) of %s %s w.r.t %s",
6dfb7f59
RW
748 vtype2string(vertex->type), buf,
749 lfa_protected_resource2str(
750 &spftree_pc->lfa.protected_resource));
c951ee6e
RW
751
752 return ret;
753}
754
755static bool
756spf_adj_node_is_affected(struct isis_spf_node *adj_node,
757 const struct lfa_protected_resource *resource,
758 const uint8_t *root_sysid)
759{
760 struct isis_spf_adj *sadj;
761 struct listnode *node;
762
763 for (ALL_LIST_ELEMENTS_RO(adj_node->adjacencies, node, sadj)) {
764 if (sadj->metric != adj_node->best_metric)
765 continue;
766 if (spf_adj_check_is_affected(sadj, resource, root_sysid,
767 false))
768 return true;
769 }
770
771 return false;
772}
773
774static bool vertex_is_affected(struct isis_spftree *spftree_root,
775 const struct isis_spf_nodes *adj_nodes,
776 bool p_space, const struct isis_vertex *vertex,
777 const struct lfa_protected_resource *resource)
778{
779 struct isis_vertex *pvertex;
780 struct listnode *node, *vnode;
781
782 for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, pvertex)) {
783 struct isis_spftree *spftree_parent;
784 struct isis_vertex *vertex_child;
785 struct isis_vertex_adj *vadj;
786 bool reverse = false;
787 char buf1[VID2STR_BUFFER];
788 char buf2[VID2STR_BUFFER];
789
2866b119
RW
790 if (IS_DEBUG_LFA)
791 zlog_debug("ISIS-LFA: vertex %s parent %s",
c951ee6e
RW
792 vid2string(vertex, buf1, sizeof(buf1)),
793 vid2string(pvertex, buf2, sizeof(buf2)));
794
795 if (p_space && resource->type == LFA_NODE_PROTECTION) {
796 if (isis_spf_node_find(&resource->nodes, vertex->N.id))
797 return true;
798 goto parents;
799 }
800
801 /* Check if either the vertex or its parent is the root node. */
802 if (memcmp(vertex->N.id, spftree_root->sysid, ISIS_SYS_ID_LEN)
803 && memcmp(pvertex->N.id, spftree_root->sysid,
804 ISIS_SYS_ID_LEN))
805 goto parents;
806
807 /* Get SPT of the parent vertex. */
808 if (!memcmp(pvertex->N.id, spftree_root->sysid,
809 ISIS_SYS_ID_LEN))
810 spftree_parent = spftree_root;
811 else {
812 struct isis_spf_node *adj_node;
813
814 adj_node = isis_spf_node_find(adj_nodes, pvertex->N.id);
815 assert(adj_node);
816 spftree_parent = adj_node->lfa.spftree;
817 assert(spftree_parent);
818 reverse = true;
819 }
820
821 /* Get paths pvertex uses to reach vertex. */
822 vertex_child = isis_find_vertex(&spftree_parent->paths,
823 &vertex->N, vertex->type);
824 if (!vertex_child)
825 goto parents;
826
827 /* Check if any of these paths use the protected resource. */
828 for (ALL_LIST_ELEMENTS_RO(vertex_child->Adj_N, vnode, vadj))
829 if (spf_adj_check_is_affected(vadj->sadj, resource,
830 spftree_root->sysid,
831 reverse))
832 return true;
833
834 parents:
835 if (vertex_is_affected(spftree_root, adj_nodes, p_space,
836 pvertex, resource))
837 return true;
838 }
839
840 return false;
841}
842
843/* Calculate set of nodes reachable without using the protected interface. */
844static void lfa_calc_reach_nodes(struct isis_spftree *spftree,
845 struct isis_spftree *spftree_root,
846 const struct isis_spf_nodes *adj_nodes,
847 bool p_space,
848 const struct lfa_protected_resource *resource,
849 struct isis_spf_nodes *nodes)
850{
851 struct isis_vertex *vertex;
852 struct listnode *node;
853
854 for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, node, vertex)) {
855 char buf[VID2STR_BUFFER];
856
857 if (!VTYPE_IS(vertex->type))
858 continue;
859
860 /* Skip root node. */
861 if (!memcmp(vertex->N.id, spftree_root->sysid, ISIS_SYS_ID_LEN))
862 continue;
863
864 /* Don't add the same node twice. */
865 if (isis_spf_node_find(nodes, vertex->N.id))
866 continue;
867
2866b119
RW
868 if (IS_DEBUG_LFA)
869 zlog_debug("ISIS-LFA: checking %s",
c951ee6e
RW
870 vid2string(vertex, buf, sizeof(buf)));
871
872 if (!vertex_is_affected(spftree_root, adj_nodes, p_space,
873 vertex, resource)) {
2866b119 874 if (IS_DEBUG_LFA)
c951ee6e 875 zlog_debug(
2866b119 876 "ISIS-LFA: adding %s",
c951ee6e
RW
877 vid2string(vertex, buf, sizeof(buf)));
878
879 isis_spf_node_new(nodes, vertex->N.id);
880 }
881 }
882}
883
884/**
885 * Helper function used to create an SPF tree structure and run reverse SPF on
886 * it.
887 *
888 * @param spftree IS-IS SPF tree
889 *
2866b119 890 * @return Pointer to new SPF tree structure.
c951ee6e
RW
891 */
892struct isis_spftree *isis_spf_reverse_run(const struct isis_spftree *spftree)
893{
894 struct isis_spftree *spftree_reverse;
895
896 spftree_reverse = isis_spftree_new(
897 spftree->area, spftree->lspdb, spftree->sysid, spftree->level,
898 spftree->tree_id, SPF_TYPE_REVERSE,
899 F_SPFTREE_NO_ADJACENCIES | F_SPFTREE_NO_ROUTES);
900 isis_run_spf(spftree_reverse);
901
902 return spftree_reverse;
903}
904
905/*
906 * Calculate the Extended P-space and Q-space associated to a given link
907 * failure.
908 */
909static void lfa_calc_pq_spaces(struct isis_spftree *spftree_pc,
910 const struct lfa_protected_resource *resource)
911{
912 struct isis_spftree *spftree;
913 struct isis_spftree *spftree_reverse;
914 struct isis_spf_nodes *adj_nodes;
915 struct isis_spf_node *adj_node;
916
917 /* Obtain pre-failure SPTs and list of adjacent nodes. */
918 spftree = spftree_pc->lfa.old.spftree;
919 spftree_reverse = spftree_pc->lfa.old.spftree_reverse;
920 adj_nodes = &spftree->adj_nodes;
921
2866b119
RW
922 if (IS_DEBUG_LFA)
923 zlog_debug("ISIS-LFA: computing P-space (self)");
c951ee6e
RW
924 lfa_calc_reach_nodes(spftree, spftree, adj_nodes, true, resource,
925 &spftree_pc->lfa.p_space);
926
927 RB_FOREACH (adj_node, isis_spf_nodes, adj_nodes) {
928 if (spf_adj_node_is_affected(adj_node, resource,
929 spftree->sysid)) {
2866b119
RW
930 if (IS_DEBUG_LFA)
931 zlog_debug("ISIS-LFA: computing Q-space (%s)",
932 print_sys_hostname(adj_node->sysid));
c951ee6e
RW
933
934 /*
935 * Compute the reverse SPF in the behalf of the node
936 * adjacent to the failure.
937 */
938 adj_node->lfa.spftree_reverse =
939 isis_spf_reverse_run(adj_node->lfa.spftree);
940
941 lfa_calc_reach_nodes(adj_node->lfa.spftree_reverse,
942 spftree_reverse, adj_nodes, false,
943 resource,
944 &spftree_pc->lfa.q_space);
945 } else {
2866b119
RW
946 if (IS_DEBUG_LFA)
947 zlog_debug("ISIS-LFA: computing P-space (%s)",
948 print_sys_hostname(adj_node->sysid));
c951ee6e
RW
949 lfa_calc_reach_nodes(adj_node->lfa.spftree, spftree,
950 adj_nodes, true, resource,
951 &adj_node->lfa.p_space);
952 }
953 }
954}
955
956/**
957 * Compute the TI-LFA backup paths for a given protected interface.
958 *
959 * @param area IS-IS area
960 * @param spftree IS-IS SPF tree
961 * @param spftree_reverse IS-IS Reverse SPF tree
962 * @param resource Protected resource
963 *
964 * @return Pointer to the post-convergence SPF tree
965 */
966struct isis_spftree *isis_tilfa_compute(struct isis_area *area,
967 struct isis_spftree *spftree,
968 struct isis_spftree *spftree_reverse,
969 struct lfa_protected_resource *resource)
970{
971 struct isis_spftree *spftree_pc;
972 struct isis_spf_node *adj_node;
973
2866b119
RW
974 if (IS_DEBUG_LFA)
975 zlog_debug("ISIS-LFA: computing the P/Q spaces w.r.t. %s",
c951ee6e
RW
976 lfa_protected_resource2str(resource));
977
978 /* Populate list of nodes affected by link failure. */
979 if (resource->type == LFA_NODE_PROTECTION) {
980 isis_spf_node_list_init(&resource->nodes);
981 RB_FOREACH (adj_node, isis_spf_nodes, &spftree->adj_nodes) {
982 if (spf_adj_node_is_affected(adj_node, resource,
983 spftree->sysid))
984 isis_spf_node_new(&resource->nodes,
985 adj_node->sysid);
986 }
987 }
988
989 /* Create post-convergence SPF tree. */
990 spftree_pc = isis_spftree_new(area, spftree->lspdb, spftree->sysid,
991 spftree->level, spftree->tree_id,
992 SPF_TYPE_TI_LFA, spftree->flags);
993 spftree_pc->lfa.old.spftree = spftree;
994 spftree_pc->lfa.old.spftree_reverse = spftree_reverse;
995 spftree_pc->lfa.protected_resource = *resource;
996
997 /* Compute the extended P-space and Q-space. */
998 lfa_calc_pq_spaces(spftree_pc, resource);
999
2866b119 1000 if (IS_DEBUG_LFA)
c951ee6e 1001 zlog_debug(
2866b119 1002 "ISIS-LFA: computing the post convergence SPT w.r.t. %s",
c951ee6e
RW
1003 lfa_protected_resource2str(resource));
1004
1005 /* Re-run SPF in the local node to find the post-convergence paths. */
1006 isis_run_spf(spftree_pc);
1007
1008 /* Clear list of nodes affeted by link failure. */
1009 if (resource->type == LFA_NODE_PROTECTION)
1010 isis_spf_node_list_clear(&resource->nodes);
1011
1012 return spftree_pc;
1013}
1014
1015/**
1016 * Run forward SPF on all adjacent routers.
1017 *
1018 * @param spftree IS-IS SPF tree
1019 *
1020 * @return 0 on success, -1 otherwise
1021 */
1022int isis_spf_run_neighbors(struct isis_spftree *spftree)
1023{
1024 struct isis_lsp *lsp;
1025 struct isis_spf_node *adj_node;
1026
1027 lsp = isis_root_system_lsp(spftree->lspdb, spftree->sysid);
1028 if (!lsp)
1029 return -1;
1030
1031 RB_FOREACH (adj_node, isis_spf_nodes, &spftree->adj_nodes) {
2866b119
RW
1032 if (IS_DEBUG_LFA)
1033 zlog_debug("ISIS-LFA: running SPF on neighbor %s",
c951ee6e
RW
1034 print_sys_hostname(adj_node->sysid));
1035
1036 /* Compute the SPT on behalf of the neighbor. */
1037 adj_node->lfa.spftree = isis_spftree_new(
1038 spftree->area, spftree->lspdb, adj_node->sysid,
1039 spftree->level, spftree->tree_id, SPF_TYPE_FORWARD,
1040 F_SPFTREE_NO_ADJACENCIES | F_SPFTREE_NO_ROUTES);
1041 isis_run_spf(adj_node->lfa.spftree);
1042 }
1043
1044 return 0;
1045}
1046
1047/**
1048 * Run the TI-LFA algorithm for all proctected interfaces.
1049 *
1050 * @param area IS-IS area
1051 * @param spftree IS-IS SPF tree
1052 */
1053void isis_spf_run_lfa(struct isis_area *area, struct isis_spftree *spftree)
1054{
1055 struct isis_spftree *spftree_reverse;
1056 struct isis_circuit *circuit;
1057 struct listnode *node;
1058
1059 /* Run reverse SPF locally. */
1060 spftree_reverse = isis_spf_reverse_run(spftree);
1061
1062 /* Run forward SPF on all adjacent routers. */
1063 isis_spf_run_neighbors(spftree);
1064
1065 /* Check which interfaces are protected. */
1066 for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
1067 struct lfa_protected_resource resource = {};
1068 struct isis_adjacency *adj;
1069 struct isis_spftree *spftree_pc_link;
1070 struct isis_spftree *spftree_pc_node;
1071 static uint8_t null_sysid[ISIS_SYS_ID_LEN + 1];
1072
1073 if (!(circuit->is_type & spftree->level))
1074 continue;
1075
1076 if (!circuit->tilfa_protection[spftree->level - 1])
1077 continue;
1078
1079 /* Fill in the protected resource. */
1080 switch (circuit->circ_type) {
1081 case CIRCUIT_T_BROADCAST:
1082 if (spftree->level == 1)
1083 memcpy(resource.adjacency,
1084 circuit->u.bc.l1_desig_is,
1085 ISIS_SYS_ID_LEN + 1);
1086 else
1087 memcpy(resource.adjacency,
1088 circuit->u.bc.l2_desig_is,
1089 ISIS_SYS_ID_LEN + 1);
1090 /* Do nothing if no DR was elected yet. */
1091 if (!memcmp(resource.adjacency, null_sysid,
1092 ISIS_SYS_ID_LEN + 1))
1093 continue;
1094 break;
1095 case CIRCUIT_T_P2P:
1096 adj = circuit->u.p2p.neighbor;
1097 if (!adj)
1098 continue;
1099 memcpy(resource.adjacency, adj->sysid, ISIS_SYS_ID_LEN);
1100 LSP_PSEUDO_ID(resource.adjacency) = 0;
1101 break;
1102 default:
1103 continue;
1104 }
1105
1106 /* Compute node protecting repair paths first (if necessary). */
1107 if (circuit->tilfa_node_protection[spftree->level - 1]) {
1108 resource.type = LFA_NODE_PROTECTION;
1109 spftree_pc_node = isis_tilfa_compute(
1110 area, spftree, spftree_reverse, &resource);
1111 isis_spftree_del(spftree_pc_node);
1112 }
1113
1114 /* Compute link protecting repair paths. */
1115 resource.type = LFA_LINK_PROTECTION;
1116 spftree_pc_link = isis_tilfa_compute(
1117 area, spftree, spftree_reverse, &resource);
1118 isis_spftree_del(spftree_pc_link);
1119 }
1120
1121 isis_spftree_del(spftree_reverse);
1122}