2 * IS-IS Rout(e)ing protocol - OpenFabric extensions
4 * Copyright (C) 2018 Christian Franke
6 * This file is part of FRRouting (FRR)
8 * FRR is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2, or (at your option) any
13 * FRR is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; see the file COPYING; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 #include "isisd/fabricd.h"
24 #include "isisd/isisd.h"
25 #include "isisd/isis_circuit.h"
26 #include "isisd/isis_misc.h"
27 #include "isisd/isis_adjacency.h"
28 #include "isisd/isis_spf.h"
29 #include "isisd/isis_tlvs.h"
30 #include "isisd/isis_lsp.h"
31 #include "isisd/isis_spf_private.h"
32 #include "isisd/isis_tx_queue.h"
33 #include "isisd/isis_csm.h"
35 DEFINE_MTYPE_STATIC(ISISD
, FABRICD_STATE
, "ISIS OpenFabric");
36 DEFINE_MTYPE_STATIC(ISISD
, FABRICD_NEIGHBOR
, "ISIS OpenFabric Neighbor Entry");
37 DEFINE_MTYPE_STATIC(ISISD
, FABRICD_FLOODING_INFO
, "ISIS OpenFabric Flooding Log");
39 /* Tracks initial synchronization as per section 2.4
41 * We declare the sync complete once we have seen at least one
42 * CSNP and there are no more LSPs with SSN or SRM set.
44 enum fabricd_sync_state
{
51 struct isis_area
*area
;
53 enum fabricd_sync_state initial_sync_state
;
54 time_t initial_sync_start
;
55 struct isis_circuit
*initial_sync_circuit
;
56 struct thread
*initial_sync_timeout
;
58 struct isis_spftree
*spftree
;
59 struct skiplist
*neighbors
;
60 struct hash
*neighbors_neighbors
;
65 struct thread
*tier_calculation_timer
;
66 struct thread
*tier_set_timer
;
69 bool always_send_csnp
;
72 /* Code related to maintaining the neighbor lists */
74 struct neighbor_entry
{
75 uint8_t id
[ISIS_SYS_ID_LEN
];
76 struct isis_adjacency
*adj
;
80 static struct neighbor_entry
*neighbor_entry_new(const uint8_t *id
,
81 struct isis_adjacency
*adj
)
83 struct neighbor_entry
*rv
= XMALLOC(MTYPE_FABRICD_NEIGHBOR
,
86 memcpy(rv
->id
, id
, sizeof(rv
->id
));
92 static void neighbor_entry_del(struct neighbor_entry
*neighbor
)
94 XFREE(MTYPE_FABRICD_NEIGHBOR
, neighbor
);
97 static void neighbor_entry_del_void(void *arg
)
99 neighbor_entry_del((struct neighbor_entry
*)arg
);
102 static void neighbor_lists_clear(struct fabricd
*f
)
104 while (!skiplist_empty(f
->neighbors
))
105 skiplist_delete_first(f
->neighbors
);
107 hash_clean(f
->neighbors_neighbors
, neighbor_entry_del_void
);
110 static unsigned neighbor_entry_hash_key(const void *np
)
112 const struct neighbor_entry
*n
= np
;
114 return jhash(n
->id
, sizeof(n
->id
), 0x55aa5a5a);
117 static bool neighbor_entry_hash_cmp(const void *a
, const void *b
)
119 const struct neighbor_entry
*na
= a
, *nb
= b
;
121 return memcmp(na
->id
, nb
->id
, sizeof(na
->id
)) == 0;
124 static int neighbor_entry_list_cmp(const void *a
, const void *b
)
126 const struct neighbor_entry
*na
= a
, *nb
= b
;
128 return -memcmp(na
->id
, nb
->id
, sizeof(na
->id
));
131 static struct neighbor_entry
*neighbor_entry_lookup_list(struct skiplist
*list
,
134 struct neighbor_entry n
= { {0} };
136 memcpy(n
.id
, id
, sizeof(n
.id
));
138 struct neighbor_entry
*rv
;
140 if (skiplist_search(list
, &n
, (void**)&rv
))
149 static struct neighbor_entry
*neighbor_entry_lookup_hash(struct hash
*hash
,
152 struct neighbor_entry n
= {{0}};
154 memcpy(n
.id
, id
, sizeof(n
.id
));
156 struct neighbor_entry
*rv
= hash_lookup(hash
, &n
);
158 if (!rv
|| !rv
->present
)
164 static int fabricd_handle_adj_state_change(struct isis_adjacency
*arg
)
166 struct fabricd
*f
= arg
->circuit
->area
->fabricd
;
171 while (!skiplist_empty(f
->neighbors
))
172 skiplist_delete_first(f
->neighbors
);
174 struct listnode
*node
;
175 struct isis_circuit
*circuit
;
177 for (ALL_LIST_ELEMENTS_RO(f
->area
->circuit_list
, node
, circuit
)) {
178 if (circuit
->state
!= C_STATE_UP
)
181 struct isis_adjacency
*adj
= circuit
->u
.p2p
.neighbor
;
183 if (!adj
|| adj
->adj_state
!= ISIS_ADJ_UP
)
186 struct neighbor_entry
*n
= neighbor_entry_new(adj
->sysid
, adj
);
188 skiplist_insert(f
->neighbors
, n
, n
);
194 static void neighbors_neighbors_update(struct fabricd
*f
)
196 hash_clean(f
->neighbors_neighbors
, neighbor_entry_del_void
);
198 struct listnode
*node
;
199 struct isis_vertex
*v
;
201 for (ALL_QUEUE_ELEMENTS_RO(&f
->spftree
->paths
, node
, v
)) {
202 if (v
->d_N
< 2 || !VTYPE_IS(v
->type
))
208 struct neighbor_entry
*n
= neighbor_entry_new(v
->N
.id
, NULL
);
209 struct neighbor_entry
*inserted
;
210 inserted
= hash_get(f
->neighbors_neighbors
, n
,
212 assert(inserted
== n
);
216 struct fabricd
*fabricd_new(struct isis_area
*area
)
218 struct fabricd
*rv
= XCALLOC(MTYPE_FABRICD_STATE
, sizeof(*rv
));
221 rv
->initial_sync_state
= FABRICD_SYNC_PENDING
;
224 isis_spftree_new(area
, &area
->lspdb
[IS_LEVEL_2
- 1],
225 area
->isis
->sysid
, ISIS_LEVEL2
, SPFTREE_IPV4
,
226 SPF_TYPE_FORWARD
, F_SPFTREE_HOPCOUNT_METRIC
);
227 rv
->neighbors
= skiplist_new(0, neighbor_entry_list_cmp
,
228 neighbor_entry_del_void
);
229 rv
->neighbors_neighbors
= hash_create(neighbor_entry_hash_key
,
230 neighbor_entry_hash_cmp
,
231 "Fabricd Neighbors");
233 rv
->tier
= rv
->tier_config
= ISIS_TIER_UNDEFINED
;
235 rv
->csnp_delay
= FABRICD_DEFAULT_CSNP_DELAY
;
239 void fabricd_finish(struct fabricd
*f
)
241 thread_cancel(&(f
->initial_sync_timeout
));
243 thread_cancel(&(f
->tier_calculation_timer
));
245 thread_cancel(&(f
->tier_set_timer
));
247 isis_spftree_del(f
->spftree
);
248 neighbor_lists_clear(f
);
249 skiplist_free(f
->neighbors
);
250 hash_free(f
->neighbors_neighbors
);
253 static void fabricd_initial_sync_timeout(struct thread
*thread
)
255 struct fabricd
*f
= THREAD_ARG(thread
);
257 zlog_info("OpenFabric: Initial synchronization on %s timed out!",
258 f
->initial_sync_circuit
->interface
->name
);
259 f
->initial_sync_state
= FABRICD_SYNC_PENDING
;
260 f
->initial_sync_circuit
= NULL
;
263 void fabricd_initial_sync_hello(struct isis_circuit
*circuit
)
265 struct fabricd
*f
= circuit
->area
->fabricd
;
270 if (f
->initial_sync_state
> FABRICD_SYNC_PENDING
)
273 f
->initial_sync_state
= FABRICD_SYNC_STARTED
;
275 long timeout
= 2 * circuit
->hello_interval
[1] * circuit
->hello_multiplier
[1];
277 f
->initial_sync_circuit
= circuit
;
278 if (f
->initial_sync_timeout
)
281 thread_add_timer(master
, fabricd_initial_sync_timeout
, f
,
282 timeout
, &f
->initial_sync_timeout
);
283 f
->initial_sync_start
= monotime(NULL
);
285 zlog_info("OpenFabric: Started initial synchronization with %s on %s",
286 sysid_print(circuit
->u
.p2p
.neighbor
->sysid
),
287 circuit
->interface
->name
);
290 bool fabricd_initial_sync_is_in_progress(struct isis_area
*area
)
292 struct fabricd
*f
= area
->fabricd
;
297 if (f
->initial_sync_state
> FABRICD_SYNC_PENDING
298 && f
->initial_sync_state
< FABRICD_SYNC_COMPLETE
)
304 bool fabricd_initial_sync_is_complete(struct isis_area
*area
)
306 struct fabricd
*f
= area
->fabricd
;
311 return f
->initial_sync_state
== FABRICD_SYNC_COMPLETE
;
314 struct isis_circuit
*fabricd_initial_sync_circuit(struct isis_area
*area
)
316 struct fabricd
*f
= area
->fabricd
;
320 return f
->initial_sync_circuit
;
323 void fabricd_initial_sync_finish(struct isis_area
*area
)
325 struct fabricd
*f
= area
->fabricd
;
330 if (monotime(NULL
) - f
->initial_sync_start
< 5)
333 zlog_info("OpenFabric: Initial synchronization on %s complete.",
334 f
->initial_sync_circuit
->interface
->name
);
335 f
->initial_sync_state
= FABRICD_SYNC_COMPLETE
;
336 f
->initial_sync_circuit
= NULL
;
337 thread_cancel(&(f
->initial_sync_timeout
));
340 static void fabricd_bump_tier_calculation_timer(struct fabricd
*f
);
341 static void fabricd_set_tier(struct fabricd
*f
, uint8_t tier
);
343 static uint8_t fabricd_calculate_fabric_tier(struct isis_area
*area
)
345 struct isis_spftree
*local_tree
= fabricd_spftree(area
);
346 struct listnode
*node
;
348 struct isis_vertex
*furthest_t0
= NULL
,
349 *second_furthest_t0
= NULL
;
351 struct isis_vertex
*v
;
353 for (ALL_QUEUE_ELEMENTS_RO(&local_tree
->paths
, node
, v
)) {
354 struct isis_lsp
*lsp
= lsp_for_vertex(local_tree
, v
);
356 if (!lsp
|| !lsp
->tlvs
357 || !lsp
->tlvs
->spine_leaf
358 || !lsp
->tlvs
->spine_leaf
->has_tier
359 || lsp
->tlvs
->spine_leaf
->tier
!= 0)
362 second_furthest_t0
= furthest_t0
;
366 if (!second_furthest_t0
) {
367 zlog_info("OpenFabric: Could not find two T0 routers");
368 return ISIS_TIER_UNDEFINED
;
371 zlog_info("OpenFabric: Found %s as furthest t0 from local system, dist == %u", rawlspid_print(furthest_t0
->N
.id
), furthest_t0
->d_N
);
373 struct isis_spftree
*remote_tree
=
374 isis_run_hopcount_spf(area
, furthest_t0
->N
.id
, NULL
);
376 struct isis_vertex
*furthest_from_remote
=
377 isis_vertex_queue_last(&remote_tree
->paths
);
379 if (!furthest_from_remote
) {
380 zlog_info("OpenFabric: Found no furthest node in remote spf");
381 isis_spftree_del(remote_tree
);
382 return ISIS_TIER_UNDEFINED
;
384 zlog_info("OpenFabric: Found %s as furthest from remote dist == %u", rawlspid_print(furthest_from_remote
->N
.id
),
385 furthest_from_remote
->d_N
);
388 int64_t tier
= furthest_from_remote
->d_N
- furthest_t0
->d_N
;
389 isis_spftree_del(remote_tree
);
391 if (tier
< 0 || tier
>= ISIS_TIER_UNDEFINED
) {
392 zlog_info("OpenFabric: Calculated tier %" PRId64
" seems implausible",
394 return ISIS_TIER_UNDEFINED
;
397 zlog_info("OpenFabric: Calculated %" PRId64
" as tier", tier
);
401 static void fabricd_tier_set_timer(struct thread
*thread
)
403 struct fabricd
*f
= THREAD_ARG(thread
);
405 fabricd_set_tier(f
, f
->tier_pending
);
408 static void fabricd_tier_calculation_cb(struct thread
*thread
)
410 struct fabricd
*f
= THREAD_ARG(thread
);
411 uint8_t tier
= ISIS_TIER_UNDEFINED
;
413 tier
= fabricd_calculate_fabric_tier(f
->area
);
414 if (tier
== ISIS_TIER_UNDEFINED
)
417 zlog_info("OpenFabric: Got tier %hhu from algorithm. Arming timer.",
419 f
->tier_pending
= tier
;
420 thread_add_timer(master
, fabricd_tier_set_timer
, f
,
421 f
->area
->lsp_gen_interval
[ISIS_LEVEL2
- 1],
426 static void fabricd_bump_tier_calculation_timer(struct fabricd
*f
)
428 /* Cancel timer if we already know our tier */
429 if (f
->tier
!= ISIS_TIER_UNDEFINED
|| f
->tier_set_timer
) {
430 thread_cancel(&(f
->tier_calculation_timer
));
434 /* If we need to calculate the tier, wait some
435 * time for the topology to settle before running
437 thread_cancel(&(f
->tier_calculation_timer
));
439 thread_add_timer(master
, fabricd_tier_calculation_cb
, f
,
440 2 * f
->area
->lsp_gen_interval
[ISIS_LEVEL2
- 1],
441 &f
->tier_calculation_timer
);
444 static void fabricd_set_tier(struct fabricd
*f
, uint8_t tier
)
449 zlog_info("OpenFabric: Set own tier to %hhu", tier
);
452 fabricd_bump_tier_calculation_timer(f
);
453 lsp_regenerate_schedule(f
->area
, ISIS_LEVEL2
, 0);
456 void fabricd_run_spf(struct isis_area
*area
)
458 struct fabricd
*f
= area
->fabricd
;
463 isis_run_hopcount_spf(area
, area
->isis
->sysid
, f
->spftree
);
464 neighbors_neighbors_update(f
);
465 fabricd_bump_tier_calculation_timer(f
);
468 struct isis_spftree
*fabricd_spftree(struct isis_area
*area
)
470 struct fabricd
*f
= area
->fabricd
;
478 void fabricd_configure_tier(struct isis_area
*area
, uint8_t tier
)
480 struct fabricd
*f
= area
->fabricd
;
482 if (!f
|| f
->tier_config
== tier
)
485 f
->tier_config
= tier
;
486 fabricd_set_tier(f
, tier
);
489 uint8_t fabricd_tier(struct isis_area
*area
)
491 struct fabricd
*f
= area
->fabricd
;
494 return ISIS_TIER_UNDEFINED
;
499 int fabricd_write_settings(struct isis_area
*area
, struct vty
*vty
)
501 struct fabricd
*f
= area
->fabricd
;
507 if (f
->tier_config
!= ISIS_TIER_UNDEFINED
) {
508 vty_out(vty
, " fabric-tier %hhu\n", f
->tier_config
);
512 if (f
->csnp_delay
!= FABRICD_DEFAULT_CSNP_DELAY
513 || f
->always_send_csnp
) {
514 vty_out(vty
, " triggered-csnp-delay %d%s\n", f
->csnp_delay
,
515 f
->always_send_csnp
? " always" : "");
521 static void move_to_queue(struct isis_lsp
*lsp
, struct neighbor_entry
*n
,
522 enum isis_tx_type type
, struct isis_circuit
*circuit
)
526 if (n
->adj
&& n
->adj
->circuit
== circuit
)
529 if (IS_DEBUG_FLOODING
) {
530 zlog_debug("OpenFabric: Adding %s to %s",
531 print_sys_hostname(n
->id
),
532 (type
== TX_LSP_NORMAL
) ? "RF" : "DNR");
536 isis_tx_queue_add(n
->adj
->circuit
->tx_queue
, lsp
, type
);
538 uint8_t *neighbor_id
= XMALLOC(MTYPE_FABRICD_FLOODING_INFO
,
541 memcpy(neighbor_id
, n
->id
, sizeof(n
->id
));
542 listnode_add(lsp
->flooding_neighbors
[type
], neighbor_id
);
545 static void mark_neighbor_as_present(struct hash_bucket
*bucket
, void *arg
)
547 struct neighbor_entry
*n
= bucket
->data
;
552 static void handle_firsthops(struct hash_bucket
*bucket
, void *arg
)
554 struct isis_lsp
*lsp
= arg
;
555 struct fabricd
*f
= lsp
->area
->fabricd
;
556 struct isis_vertex
*vertex
= bucket
->data
;
558 struct neighbor_entry
*n
;
560 n
= neighbor_entry_lookup_list(f
->neighbors
, vertex
->N
.id
);
562 if (IS_DEBUG_FLOODING
) {
563 zlog_debug("Removing %s from NL as its in the reverse path",
564 print_sys_hostname(n
->id
));
569 n
= neighbor_entry_lookup_hash(f
->neighbors_neighbors
, vertex
->N
.id
);
571 if (IS_DEBUG_FLOODING
) {
572 zlog_debug("Removing %s from NN as its in the reverse path",
573 print_sys_hostname(n
->id
));
579 static struct isis_lsp
*lsp_for_neighbor(struct fabricd
*f
,
580 struct neighbor_entry
*n
)
582 uint8_t id
[ISIS_SYS_ID_LEN
+ 1] = {0};
584 memcpy(id
, n
->id
, sizeof(n
->id
));
586 struct isis_vertex vertex
= {0};
588 isis_vertex_id_init(&vertex
, id
, VTYPE_NONPSEUDO_TE_IS
);
590 return lsp_for_vertex(f
->spftree
, &vertex
);
593 static void fabricd_free_lsp_flooding_info(void *val
)
595 XFREE(MTYPE_FABRICD_FLOODING_INFO
, val
);
598 static void fabricd_lsp_reset_flooding_info(struct isis_lsp
*lsp
,
599 struct isis_circuit
*circuit
)
601 lsp
->flooding_time
= time(NULL
);
603 XFREE(MTYPE_FABRICD_FLOODING_INFO
, lsp
->flooding_interface
);
604 for (enum isis_tx_type type
= TX_LSP_NORMAL
;
605 type
<= TX_LSP_CIRCUIT_SCOPED
; type
++) {
606 if (lsp
->flooding_neighbors
[type
]) {
607 list_delete_all_node(lsp
->flooding_neighbors
[type
]);
611 lsp
->flooding_neighbors
[type
] = list_new();
612 lsp
->flooding_neighbors
[type
]->del
=
613 fabricd_free_lsp_flooding_info
;
617 lsp
->flooding_interface
= XSTRDUP(MTYPE_FABRICD_FLOODING_INFO
,
618 circuit
->interface
->name
);
621 lsp
->flooding_circuit_scoped
= false;
624 void fabricd_lsp_flood(struct isis_lsp
*lsp
, struct isis_circuit
*circuit
)
626 struct fabricd
*f
= lsp
->area
->fabricd
;
629 fabricd_lsp_reset_flooding_info(lsp
, circuit
);
632 struct neighbor_entry
*n
;
634 /* Mark all elements in NL as present */
635 while (!skiplist_next(f
->neighbors
, NULL
, (void **)&n
, &cursor
))
638 /* Mark all elements in NN as present */
639 hash_iterate(f
->neighbors_neighbors
, mark_neighbor_as_present
, NULL
);
641 struct isis_vertex
*originator
=
642 isis_find_vertex(&f
->spftree
->paths
,
644 VTYPE_NONPSEUDO_TE_IS
);
646 /* Remove all IS from NL and NN in the shortest path
647 * to the IS that originated the LSP */
649 hash_iterate(originator
->firsthops
, handle_firsthops
, lsp
);
651 /* Iterate over all remaining IS in NL */
653 while (!skiplist_next(f
->neighbors
, NULL
, (void **)&n
, &cursor
)) {
657 struct isis_lsp
*nlsp
= lsp_for_neighbor(f
, n
);
658 if (!nlsp
|| !nlsp
->tlvs
) {
659 if (IS_DEBUG_FLOODING
) {
660 zlog_debug("Moving %s to DNR as it has no LSP",
661 print_sys_hostname(n
->id
));
664 move_to_queue(lsp
, n
, TX_LSP_CIRCUIT_SCOPED
, circuit
);
668 if (IS_DEBUG_FLOODING
) {
669 zlog_debug("Considering %s from NL...",
670 print_sys_hostname(n
->id
));
673 /* For all neighbors of the NL IS check whether they are present
674 * in NN. If yes, remove from NN and set need_reflood. */
675 bool need_reflood
= false;
676 struct isis_extended_reach
*er
;
677 for (er
= (struct isis_extended_reach
*)nlsp
->tlvs
->extended_reach
.head
;
679 struct neighbor_entry
*nn
;
681 nn
= neighbor_entry_lookup_hash(f
->neighbors_neighbors
,
685 if (IS_DEBUG_FLOODING
) {
686 zlog_debug("Found neighbor %s in NN, removing it from NN and setting reflood.",
687 print_sys_hostname(nn
->id
));
695 move_to_queue(lsp
, n
, need_reflood
?
696 TX_LSP_NORMAL
: TX_LSP_CIRCUIT_SCOPED
,
700 if (IS_DEBUG_FLOODING
) {
701 zlog_debug("OpenFabric: Flooding algorithm complete.");
705 void fabricd_trigger_csnp(struct isis_area
*area
, bool circuit_scoped
)
707 struct fabricd
*f
= area
->fabricd
;
712 if (!circuit_scoped
&& !f
->always_send_csnp
)
715 struct listnode
*node
;
716 struct isis_circuit
*circuit
;
718 for (ALL_LIST_ELEMENTS_RO(area
->circuit_list
, node
, circuit
)) {
719 if (!circuit
->t_send_csnp
[1])
722 thread_cancel(&(circuit
->t_send_csnp
[ISIS_LEVEL2
- 1]));
723 thread_add_timer_msec(master
, send_l2_csnp
, circuit
,
724 isis_jitter(f
->csnp_delay
, CSNP_JITTER
),
725 &circuit
->t_send_csnp
[ISIS_LEVEL2
- 1]);
729 struct list
*fabricd_ip_addrs(struct isis_circuit
*circuit
)
731 if (circuit
->ip_addrs
&& listcount(circuit
->ip_addrs
))
732 return circuit
->ip_addrs
;
734 if (!fabricd
|| !circuit
->area
|| !circuit
->area
->circuit_list
)
737 struct listnode
*node
;
738 struct isis_circuit
*c
;
740 for (ALL_LIST_ELEMENTS_RO(circuit
->area
->circuit_list
, node
, c
)) {
741 if (c
->circ_type
!= CIRCUIT_T_LOOPBACK
)
744 if (!c
->ip_addrs
|| !listcount(c
->ip_addrs
))
753 void fabricd_lsp_free(struct isis_lsp
*lsp
)
755 XFREE(MTYPE_FABRICD_FLOODING_INFO
, lsp
->flooding_interface
);
756 for (enum isis_tx_type type
= TX_LSP_NORMAL
;
757 type
<= TX_LSP_CIRCUIT_SCOPED
; type
++) {
758 if (!lsp
->flooding_neighbors
[type
])
761 list_delete(&lsp
->flooding_neighbors
[type
]);
765 void fabricd_update_lsp_no_flood(struct isis_lsp
*lsp
,
766 struct isis_circuit
*circuit
)
771 fabricd_lsp_reset_flooding_info(lsp
, circuit
);
772 lsp
->flooding_circuit_scoped
= true;
775 void fabricd_configure_triggered_csnp(struct isis_area
*area
, int delay
,
776 bool always_send_csnp
)
778 struct fabricd
*f
= area
->fabricd
;
783 f
->csnp_delay
= delay
;
784 f
->always_send_csnp
= always_send_csnp
;
787 void fabricd_init(void)
789 hook_register(isis_adj_state_change_hook
,
790 fabricd_handle_adj_state_change
);