1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * IS-IS Rout(e)ing protocol - OpenFabric extensions
5 * Copyright (C) 2018 Christian Franke
7 * This file is part of FRRouting (FRR)
10 #include "isisd/fabricd.h"
11 #include "isisd/isisd.h"
12 #include "isisd/isis_circuit.h"
13 #include "isisd/isis_misc.h"
14 #include "isisd/isis_adjacency.h"
15 #include "isisd/isis_spf.h"
16 #include "isisd/isis_tlvs.h"
17 #include "isisd/isis_lsp.h"
18 #include "isisd/isis_spf_private.h"
19 #include "isisd/isis_tx_queue.h"
20 #include "isisd/isis_csm.h"
22 DEFINE_MTYPE_STATIC(ISISD
, FABRICD_STATE
, "ISIS OpenFabric");
23 DEFINE_MTYPE_STATIC(ISISD
, FABRICD_NEIGHBOR
, "ISIS OpenFabric Neighbor Entry");
24 DEFINE_MTYPE_STATIC(ISISD
, FABRICD_FLOODING_INFO
, "ISIS OpenFabric Flooding Log");
26 /* Tracks initial synchronization as per section 2.4
28 * We declare the sync complete once we have seen at least one
29 * CSNP and there are no more LSPs with SSN or SRM set.
31 enum fabricd_sync_state
{
38 struct isis_area
*area
;
40 enum fabricd_sync_state initial_sync_state
;
41 time_t initial_sync_start
;
42 struct isis_circuit
*initial_sync_circuit
;
43 struct event
*initial_sync_timeout
;
45 struct isis_spftree
*spftree
;
46 struct skiplist
*neighbors
;
47 struct hash
*neighbors_neighbors
;
52 struct event
*tier_calculation_timer
;
53 struct event
*tier_set_timer
;
56 bool always_send_csnp
;
59 /* Code related to maintaining the neighbor lists */
61 struct neighbor_entry
{
62 uint8_t id
[ISIS_SYS_ID_LEN
];
63 struct isis_adjacency
*adj
;
67 static struct neighbor_entry
*neighbor_entry_new(const uint8_t *id
,
68 struct isis_adjacency
*adj
)
70 struct neighbor_entry
*rv
= XMALLOC(MTYPE_FABRICD_NEIGHBOR
,
73 memcpy(rv
->id
, id
, sizeof(rv
->id
));
79 static void neighbor_entry_del(struct neighbor_entry
*neighbor
)
81 XFREE(MTYPE_FABRICD_NEIGHBOR
, neighbor
);
84 static void neighbor_entry_del_void(void *arg
)
86 neighbor_entry_del((struct neighbor_entry
*)arg
);
89 static void neighbor_lists_clear(struct fabricd
*f
)
91 while (!skiplist_empty(f
->neighbors
))
92 skiplist_delete_first(f
->neighbors
);
94 hash_clean(f
->neighbors_neighbors
, neighbor_entry_del_void
);
97 static unsigned neighbor_entry_hash_key(const void *np
)
99 const struct neighbor_entry
*n
= np
;
101 return jhash(n
->id
, sizeof(n
->id
), 0x55aa5a5a);
104 static bool neighbor_entry_hash_cmp(const void *a
, const void *b
)
106 const struct neighbor_entry
*na
= a
, *nb
= b
;
108 return memcmp(na
->id
, nb
->id
, sizeof(na
->id
)) == 0;
111 static int neighbor_entry_list_cmp(const void *a
, const void *b
)
113 const struct neighbor_entry
*na
= a
, *nb
= b
;
115 return -memcmp(na
->id
, nb
->id
, sizeof(na
->id
));
118 static struct neighbor_entry
*neighbor_entry_lookup_list(struct skiplist
*list
,
121 struct neighbor_entry n
= { {0} };
123 memcpy(n
.id
, id
, sizeof(n
.id
));
125 struct neighbor_entry
*rv
;
127 if (skiplist_search(list
, &n
, (void**)&rv
))
136 static struct neighbor_entry
*neighbor_entry_lookup_hash(struct hash
*hash
,
139 struct neighbor_entry n
= {{0}};
141 memcpy(n
.id
, id
, sizeof(n
.id
));
143 struct neighbor_entry
*rv
= hash_lookup(hash
, &n
);
145 if (!rv
|| !rv
->present
)
151 static int fabricd_handle_adj_state_change(struct isis_adjacency
*arg
)
153 struct fabricd
*f
= arg
->circuit
->area
->fabricd
;
158 while (!skiplist_empty(f
->neighbors
))
159 skiplist_delete_first(f
->neighbors
);
161 struct listnode
*node
;
162 struct isis_circuit
*circuit
;
164 for (ALL_LIST_ELEMENTS_RO(f
->area
->circuit_list
, node
, circuit
)) {
165 if (circuit
->state
!= C_STATE_UP
)
168 struct isis_adjacency
*adj
= circuit
->u
.p2p
.neighbor
;
170 if (!adj
|| adj
->adj_state
!= ISIS_ADJ_UP
)
173 struct neighbor_entry
*n
= neighbor_entry_new(adj
->sysid
, adj
);
175 skiplist_insert(f
->neighbors
, n
, n
);
181 static void neighbors_neighbors_update(struct fabricd
*f
)
183 hash_clean(f
->neighbors_neighbors
, neighbor_entry_del_void
);
185 struct listnode
*node
;
186 struct isis_vertex
*v
;
188 for (ALL_QUEUE_ELEMENTS_RO(&f
->spftree
->paths
, node
, v
)) {
189 if (v
->d_N
< 2 || !VTYPE_IS(v
->type
))
195 struct neighbor_entry
*n
= neighbor_entry_new(v
->N
.id
, NULL
);
196 struct neighbor_entry
*inserted
;
197 inserted
= hash_get(f
->neighbors_neighbors
, n
,
199 assert(inserted
== n
);
203 struct fabricd
*fabricd_new(struct isis_area
*area
)
205 struct fabricd
*rv
= XCALLOC(MTYPE_FABRICD_STATE
, sizeof(*rv
));
208 rv
->initial_sync_state
= FABRICD_SYNC_PENDING
;
211 isis_spftree_new(area
, &area
->lspdb
[IS_LEVEL_2
- 1],
212 area
->isis
->sysid
, ISIS_LEVEL2
, SPFTREE_IPV4
,
213 SPF_TYPE_FORWARD
, F_SPFTREE_HOPCOUNT_METRIC
);
214 rv
->neighbors
= skiplist_new(0, neighbor_entry_list_cmp
,
215 neighbor_entry_del_void
);
216 rv
->neighbors_neighbors
= hash_create(neighbor_entry_hash_key
,
217 neighbor_entry_hash_cmp
,
218 "Fabricd Neighbors");
220 rv
->tier
= rv
->tier_config
= ISIS_TIER_UNDEFINED
;
222 rv
->csnp_delay
= FABRICD_DEFAULT_CSNP_DELAY
;
226 void fabricd_finish(struct fabricd
*f
)
228 EVENT_OFF(f
->initial_sync_timeout
);
230 EVENT_OFF(f
->tier_calculation_timer
);
232 EVENT_OFF(f
->tier_set_timer
);
234 isis_spftree_del(f
->spftree
);
235 neighbor_lists_clear(f
);
236 skiplist_free(f
->neighbors
);
237 hash_free(f
->neighbors_neighbors
);
240 static void fabricd_initial_sync_timeout(struct event
*thread
)
242 struct fabricd
*f
= EVENT_ARG(thread
);
244 if (IS_DEBUG_ADJ_PACKETS
)
246 "OpenFabric: Initial synchronization on %s timed out!",
247 f
->initial_sync_circuit
->interface
->name
);
248 f
->initial_sync_state
= FABRICD_SYNC_PENDING
;
249 f
->initial_sync_circuit
= NULL
;
252 void fabricd_initial_sync_hello(struct isis_circuit
*circuit
)
254 struct fabricd
*f
= circuit
->area
->fabricd
;
259 if (f
->initial_sync_state
> FABRICD_SYNC_PENDING
)
262 f
->initial_sync_state
= FABRICD_SYNC_STARTED
;
264 long timeout
= 2 * circuit
->hello_interval
[1] * circuit
->hello_multiplier
[1];
266 f
->initial_sync_circuit
= circuit
;
267 if (f
->initial_sync_timeout
)
270 event_add_timer(master
, fabricd_initial_sync_timeout
, f
, timeout
,
271 &f
->initial_sync_timeout
);
272 f
->initial_sync_start
= monotime(NULL
);
274 if (IS_DEBUG_ADJ_PACKETS
)
276 "OpenFabric: Started initial synchronization with %pSY on %s",
277 circuit
->u
.p2p
.neighbor
->sysid
,
278 circuit
->interface
->name
);
281 bool fabricd_initial_sync_is_in_progress(struct isis_area
*area
)
283 struct fabricd
*f
= area
->fabricd
;
288 if (f
->initial_sync_state
> FABRICD_SYNC_PENDING
289 && f
->initial_sync_state
< FABRICD_SYNC_COMPLETE
)
295 bool fabricd_initial_sync_is_complete(struct isis_area
*area
)
297 struct fabricd
*f
= area
->fabricd
;
302 return f
->initial_sync_state
== FABRICD_SYNC_COMPLETE
;
305 struct isis_circuit
*fabricd_initial_sync_circuit(struct isis_area
*area
)
307 struct fabricd
*f
= area
->fabricd
;
311 return f
->initial_sync_circuit
;
314 void fabricd_initial_sync_finish(struct isis_area
*area
)
316 struct fabricd
*f
= area
->fabricd
;
321 if (monotime(NULL
) - f
->initial_sync_start
< 5)
324 zlog_info("OpenFabric: Initial synchronization on %s complete.",
325 f
->initial_sync_circuit
->interface
->name
);
326 f
->initial_sync_state
= FABRICD_SYNC_COMPLETE
;
327 f
->initial_sync_circuit
= NULL
;
328 EVENT_OFF(f
->initial_sync_timeout
);
331 static void fabricd_bump_tier_calculation_timer(struct fabricd
*f
);
332 static void fabricd_set_tier(struct fabricd
*f
, uint8_t tier
);
334 static uint8_t fabricd_calculate_fabric_tier(struct isis_area
*area
)
336 struct isis_spftree
*local_tree
= fabricd_spftree(area
);
337 struct listnode
*node
;
339 struct isis_vertex
*furthest_t0
= NULL
,
340 *second_furthest_t0
= NULL
;
342 struct isis_vertex
*v
;
344 for (ALL_QUEUE_ELEMENTS_RO(&local_tree
->paths
, node
, v
)) {
345 struct isis_lsp
*lsp
= lsp_for_vertex(local_tree
, v
);
347 if (!lsp
|| !lsp
->tlvs
348 || !lsp
->tlvs
->spine_leaf
349 || !lsp
->tlvs
->spine_leaf
->has_tier
350 || lsp
->tlvs
->spine_leaf
->tier
!= 0)
353 second_furthest_t0
= furthest_t0
;
357 if (!second_furthest_t0
) {
358 zlog_info("OpenFabric: Could not find two T0 routers");
359 return ISIS_TIER_UNDEFINED
;
363 "OpenFabric: Found %pLS as furthest t0 from local system, dist == %u",
364 furthest_t0
->N
.id
, furthest_t0
->d_N
);
366 struct isis_spftree
*remote_tree
=
367 isis_run_hopcount_spf(area
, furthest_t0
->N
.id
, NULL
);
369 struct isis_vertex
*furthest_from_remote
=
370 isis_vertex_queue_last(&remote_tree
->paths
);
372 if (!furthest_from_remote
) {
373 zlog_info("OpenFabric: Found no furthest node in remote spf");
374 isis_spftree_del(remote_tree
);
375 return ISIS_TIER_UNDEFINED
;
378 "OpenFabric: Found %pLS as furthest from remote dist == %u",
379 furthest_from_remote
->N
.id
, furthest_from_remote
->d_N
);
382 int64_t tier
= furthest_from_remote
->d_N
- furthest_t0
->d_N
;
383 isis_spftree_del(remote_tree
);
385 if (tier
< 0 || tier
>= ISIS_TIER_UNDEFINED
) {
386 zlog_info("OpenFabric: Calculated tier %" PRId64
" seems implausible",
388 return ISIS_TIER_UNDEFINED
;
391 zlog_info("OpenFabric: Calculated %" PRId64
" as tier", tier
);
395 static void fabricd_tier_set_timer(struct event
*thread
)
397 struct fabricd
*f
= EVENT_ARG(thread
);
399 fabricd_set_tier(f
, f
->tier_pending
);
402 static void fabricd_tier_calculation_cb(struct event
*thread
)
404 struct fabricd
*f
= EVENT_ARG(thread
);
405 uint8_t tier
= ISIS_TIER_UNDEFINED
;
407 tier
= fabricd_calculate_fabric_tier(f
->area
);
408 if (tier
== ISIS_TIER_UNDEFINED
)
411 zlog_info("OpenFabric: Got tier %hhu from algorithm. Arming timer.",
413 f
->tier_pending
= tier
;
414 event_add_timer(master
, fabricd_tier_set_timer
, f
,
415 f
->area
->lsp_gen_interval
[ISIS_LEVEL2
- 1],
419 static void fabricd_bump_tier_calculation_timer(struct fabricd
*f
)
421 /* Cancel timer if we already know our tier */
422 if (f
->tier
!= ISIS_TIER_UNDEFINED
|| f
->tier_set_timer
) {
423 EVENT_OFF(f
->tier_calculation_timer
);
427 /* If we need to calculate the tier, wait some
428 * time for the topology to settle before running
430 EVENT_OFF(f
->tier_calculation_timer
);
432 event_add_timer(master
, fabricd_tier_calculation_cb
, f
,
433 2 * f
->area
->lsp_gen_interval
[ISIS_LEVEL2
- 1],
434 &f
->tier_calculation_timer
);
437 static void fabricd_set_tier(struct fabricd
*f
, uint8_t tier
)
442 zlog_info("OpenFabric: Set own tier to %hhu", tier
);
445 fabricd_bump_tier_calculation_timer(f
);
446 lsp_regenerate_schedule(f
->area
, ISIS_LEVEL2
, 0);
449 void fabricd_run_spf(struct isis_area
*area
)
451 struct fabricd
*f
= area
->fabricd
;
456 isis_run_hopcount_spf(area
, area
->isis
->sysid
, f
->spftree
);
457 neighbors_neighbors_update(f
);
458 fabricd_bump_tier_calculation_timer(f
);
461 struct isis_spftree
*fabricd_spftree(struct isis_area
*area
)
463 struct fabricd
*f
= area
->fabricd
;
471 void fabricd_configure_tier(struct isis_area
*area
, uint8_t tier
)
473 struct fabricd
*f
= area
->fabricd
;
475 if (!f
|| f
->tier_config
== tier
)
478 f
->tier_config
= tier
;
479 fabricd_set_tier(f
, tier
);
482 uint8_t fabricd_tier(struct isis_area
*area
)
484 struct fabricd
*f
= area
->fabricd
;
487 return ISIS_TIER_UNDEFINED
;
492 int fabricd_write_settings(struct isis_area
*area
, struct vty
*vty
)
494 struct fabricd
*f
= area
->fabricd
;
500 if (f
->tier_config
!= ISIS_TIER_UNDEFINED
) {
501 vty_out(vty
, " fabric-tier %hhu\n", f
->tier_config
);
505 if (f
->csnp_delay
!= FABRICD_DEFAULT_CSNP_DELAY
506 || f
->always_send_csnp
) {
507 vty_out(vty
, " triggered-csnp-delay %d%s\n", f
->csnp_delay
,
508 f
->always_send_csnp
? " always" : "");
514 static void move_to_queue(struct isis_lsp
*lsp
, struct neighbor_entry
*n
,
515 enum isis_tx_type type
, struct isis_circuit
*circuit
)
519 if (n
->adj
&& n
->adj
->circuit
== circuit
)
522 if (IS_DEBUG_FLOODING
) {
523 zlog_debug("OpenFabric: Adding %s to %s",
524 print_sys_hostname(n
->id
),
525 (type
== TX_LSP_NORMAL
) ? "RF" : "DNR");
529 isis_tx_queue_add(n
->adj
->circuit
->tx_queue
, lsp
, type
);
531 uint8_t *neighbor_id
= XMALLOC(MTYPE_FABRICD_FLOODING_INFO
,
534 memcpy(neighbor_id
, n
->id
, sizeof(n
->id
));
535 listnode_add(lsp
->flooding_neighbors
[type
], neighbor_id
);
538 static void mark_neighbor_as_present(struct hash_bucket
*bucket
, void *arg
)
540 struct neighbor_entry
*n
= bucket
->data
;
545 static void handle_firsthops(struct hash_bucket
*bucket
, void *arg
)
547 struct isis_lsp
*lsp
= arg
;
548 struct fabricd
*f
= lsp
->area
->fabricd
;
549 struct isis_vertex
*vertex
= bucket
->data
;
551 struct neighbor_entry
*n
;
553 n
= neighbor_entry_lookup_list(f
->neighbors
, vertex
->N
.id
);
555 if (IS_DEBUG_FLOODING
) {
556 zlog_debug("Removing %s from NL as its in the reverse path",
557 print_sys_hostname(n
->id
));
562 n
= neighbor_entry_lookup_hash(f
->neighbors_neighbors
, vertex
->N
.id
);
564 if (IS_DEBUG_FLOODING
) {
565 zlog_debug("Removing %s from NN as its in the reverse path",
566 print_sys_hostname(n
->id
));
572 static struct isis_lsp
*lsp_for_neighbor(struct fabricd
*f
,
573 struct neighbor_entry
*n
)
575 uint8_t id
[ISIS_SYS_ID_LEN
+ 1] = {0};
577 memcpy(id
, n
->id
, sizeof(n
->id
));
579 struct isis_vertex vertex
= {0};
581 isis_vertex_id_init(&vertex
, id
, VTYPE_NONPSEUDO_TE_IS
);
583 return lsp_for_vertex(f
->spftree
, &vertex
);
586 static void fabricd_free_lsp_flooding_info(void *val
)
588 XFREE(MTYPE_FABRICD_FLOODING_INFO
, val
);
591 static void fabricd_lsp_reset_flooding_info(struct isis_lsp
*lsp
,
592 struct isis_circuit
*circuit
)
594 lsp
->flooding_time
= time(NULL
);
596 XFREE(MTYPE_FABRICD_FLOODING_INFO
, lsp
->flooding_interface
);
597 for (enum isis_tx_type type
= TX_LSP_NORMAL
;
598 type
<= TX_LSP_CIRCUIT_SCOPED
; type
++) {
599 if (lsp
->flooding_neighbors
[type
]) {
600 list_delete_all_node(lsp
->flooding_neighbors
[type
]);
604 lsp
->flooding_neighbors
[type
] = list_new();
605 lsp
->flooding_neighbors
[type
]->del
=
606 fabricd_free_lsp_flooding_info
;
610 lsp
->flooding_interface
= XSTRDUP(MTYPE_FABRICD_FLOODING_INFO
,
611 circuit
->interface
->name
);
614 lsp
->flooding_circuit_scoped
= false;
617 void fabricd_lsp_flood(struct isis_lsp
*lsp
, struct isis_circuit
*circuit
)
619 struct fabricd
*f
= lsp
->area
->fabricd
;
622 fabricd_lsp_reset_flooding_info(lsp
, circuit
);
625 struct neighbor_entry
*n
;
627 /* Mark all elements in NL as present */
628 while (!skiplist_next(f
->neighbors
, NULL
, (void **)&n
, &cursor
))
631 /* Mark all elements in NN as present */
632 hash_iterate(f
->neighbors_neighbors
, mark_neighbor_as_present
, NULL
);
634 struct isis_vertex
*originator
=
635 isis_find_vertex(&f
->spftree
->paths
,
637 VTYPE_NONPSEUDO_TE_IS
);
639 /* Remove all IS from NL and NN in the shortest path
640 * to the IS that originated the LSP */
642 hash_iterate(originator
->firsthops
, handle_firsthops
, lsp
);
644 /* Iterate over all remaining IS in NL */
646 while (!skiplist_next(f
->neighbors
, NULL
, (void **)&n
, &cursor
)) {
650 struct isis_lsp
*nlsp
= lsp_for_neighbor(f
, n
);
651 if (!nlsp
|| !nlsp
->tlvs
) {
652 if (IS_DEBUG_FLOODING
) {
653 zlog_debug("Moving %s to DNR as it has no LSP",
654 print_sys_hostname(n
->id
));
657 move_to_queue(lsp
, n
, TX_LSP_CIRCUIT_SCOPED
, circuit
);
661 if (IS_DEBUG_FLOODING
) {
662 zlog_debug("Considering %s from NL...",
663 print_sys_hostname(n
->id
));
666 /* For all neighbors of the NL IS check whether they are present
667 * in NN. If yes, remove from NN and set need_reflood. */
668 bool need_reflood
= false;
669 struct isis_extended_reach
*er
;
670 for (er
= (struct isis_extended_reach
*)nlsp
->tlvs
->extended_reach
.head
;
672 struct neighbor_entry
*nn
;
674 nn
= neighbor_entry_lookup_hash(f
->neighbors_neighbors
,
678 if (IS_DEBUG_FLOODING
) {
679 zlog_debug("Found neighbor %s in NN, removing it from NN and setting reflood.",
680 print_sys_hostname(nn
->id
));
688 move_to_queue(lsp
, n
, need_reflood
?
689 TX_LSP_NORMAL
: TX_LSP_CIRCUIT_SCOPED
,
693 if (IS_DEBUG_FLOODING
) {
694 zlog_debug("OpenFabric: Flooding algorithm complete.");
698 void fabricd_trigger_csnp(struct isis_area
*area
, bool circuit_scoped
)
700 struct fabricd
*f
= area
->fabricd
;
705 if (!circuit_scoped
&& !f
->always_send_csnp
)
708 struct listnode
*node
;
709 struct isis_circuit
*circuit
;
711 for (ALL_LIST_ELEMENTS_RO(area
->circuit_list
, node
, circuit
)) {
712 if (!circuit
->t_send_csnp
[1])
715 EVENT_OFF(circuit
->t_send_csnp
[ISIS_LEVEL2
- 1]);
716 event_add_timer_msec(master
, send_l2_csnp
, circuit
,
717 isis_jitter(f
->csnp_delay
, CSNP_JITTER
),
718 &circuit
->t_send_csnp
[ISIS_LEVEL2
- 1]);
722 struct list
*fabricd_ip_addrs(struct isis_circuit
*circuit
)
724 if (listcount(circuit
->ip_addrs
))
725 return circuit
->ip_addrs
;
727 if (!fabricd
|| !circuit
->area
|| !circuit
->area
->circuit_list
)
730 struct listnode
*node
;
731 struct isis_circuit
*c
;
733 for (ALL_LIST_ELEMENTS_RO(circuit
->area
->circuit_list
, node
, c
)) {
734 if (c
->circ_type
!= CIRCUIT_T_LOOPBACK
)
737 if (!listcount(c
->ip_addrs
))
746 void fabricd_lsp_free(struct isis_lsp
*lsp
)
748 XFREE(MTYPE_FABRICD_FLOODING_INFO
, lsp
->flooding_interface
);
749 for (enum isis_tx_type type
= TX_LSP_NORMAL
;
750 type
<= TX_LSP_CIRCUIT_SCOPED
; type
++) {
751 if (!lsp
->flooding_neighbors
[type
])
754 list_delete(&lsp
->flooding_neighbors
[type
]);
758 void fabricd_update_lsp_no_flood(struct isis_lsp
*lsp
,
759 struct isis_circuit
*circuit
)
764 fabricd_lsp_reset_flooding_info(lsp
, circuit
);
765 lsp
->flooding_circuit_scoped
= true;
768 void fabricd_configure_triggered_csnp(struct isis_area
*area
, int delay
,
769 bool always_send_csnp
)
771 struct fabricd
*f
= area
->fabricd
;
776 f
->csnp_delay
= delay
;
777 f
->always_send_csnp
= always_send_csnp
;
780 void fabricd_init(void)
782 hook_register(isis_adj_state_change_hook
,
783 fabricd_handle_adj_state_change
);