]> git.proxmox.com Git - mirror_frr.git/blobdiff - isisd/fabricd.c
*: Replace hash_cmp function return value to a bool
[mirror_frr.git] / isisd / fabricd.c
index d37e6a71d87031428f3becfca9f75af25fc9c2b2..76c8087f2d8331b14bfffee89a2552cd88adadaa 100644 (file)
 #include "isisd/isis_circuit.h"
 #include "isisd/isis_misc.h"
 #include "isisd/isis_adjacency.h"
+#include "isisd/isis_spf.h"
+#include "isisd/isis_tlvs.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_spf_private.h"
+#include "isisd/isis_tx_queue.h"
 
 DEFINE_MTYPE_STATIC(ISISD, FABRICD_STATE, "ISIS OpenFabric")
+DEFINE_MTYPE_STATIC(ISISD, FABRICD_NEIGHBOR, "ISIS OpenFabric Neighbor Entry")
 
 /* Tracks initial synchronization as per section 2.4
  *
@@ -41,20 +47,177 @@ enum fabricd_sync_state {
 };
 
 struct fabricd {
+       struct isis_area *area;
+
        enum fabricd_sync_state initial_sync_state;
        time_t initial_sync_start;
        struct isis_circuit *initial_sync_circuit;
        struct thread *initial_sync_timeout;
+
+       struct isis_spftree *spftree;
+       struct skiplist *neighbors;
+       struct hash *neighbors_neighbors;
+
+       uint8_t tier;
+       uint8_t tier_config;
+       uint8_t tier_pending;
+       struct thread *tier_calculation_timer;
+       struct thread *tier_set_timer;
 };
 
-struct fabricd *fabricd_new(void)
+/* Code related to maintaining the neighbor lists */
+
+struct neighbor_entry {
+       struct isis_vertex *vertex;
+       bool present;
+};
+
+static struct neighbor_entry *neighbor_entry_new(struct isis_vertex *vertex)
+{
+       struct neighbor_entry *rv = XMALLOC(MTYPE_FABRICD_NEIGHBOR, sizeof(*rv));
+
+       rv->vertex = vertex;
+       return rv;
+}
+
+static void neighbor_entry_del(struct neighbor_entry *neighbor)
+{
+       XFREE(MTYPE_FABRICD_NEIGHBOR, neighbor);
+}
+
+static void neighbor_entry_del_void(void *arg)
+{
+       neighbor_entry_del((struct neighbor_entry *)arg);
+}
+
+static void neighbor_lists_clear(struct fabricd *f)
+{
+       while (!skiplist_empty(f->neighbors))
+               skiplist_delete_first(f->neighbors);
+
+       hash_clean(f->neighbors_neighbors, neighbor_entry_del_void);
+}
+
+static unsigned neighbor_entry_hash_key(void *np)
+{
+       struct neighbor_entry *n = np;
+
+       return jhash(n->vertex->N.id, ISIS_SYS_ID_LEN, 0x55aa5a5a);
+}
+
+static bool neighbor_entry_hash_cmp(const void *a, const void *b)
+{
+       const struct neighbor_entry *na = a, *nb = b;
+
+       return memcmp(na->vertex->N.id, nb->vertex->N.id, ISIS_SYS_ID_LEN) == 0;
+}
+
+static int neighbor_entry_list_cmp(void *a, void *b)
+{
+       struct neighbor_entry *na = a, *nb = b;
+
+       return -memcmp(na->vertex->N.id, nb->vertex->N.id, ISIS_SYS_ID_LEN);
+}
+
+static struct neighbor_entry *neighbor_entry_lookup_list(struct skiplist *list,
+                                                        const uint8_t *id)
+{
+       struct isis_vertex querier;
+       isis_vertex_id_init(&querier, id, VTYPE_NONPSEUDO_TE_IS);
+
+       struct neighbor_entry n = {
+               .vertex = &querier
+       };
+
+       struct neighbor_entry *rv;
+
+       if (skiplist_search(list, &n, (void**)&rv))
+               return NULL;
+
+       if (!rv->present)
+               return NULL;
+
+       return rv;
+}
+
+static struct neighbor_entry *neighbor_entry_lookup_hash(struct hash *hash,
+                                                        const uint8_t *id)
+{
+       struct isis_vertex querier;
+       isis_vertex_id_init(&querier, id, VTYPE_NONPSEUDO_TE_IS);
+
+       struct neighbor_entry n = {
+               .vertex = &querier
+       };
+
+       struct neighbor_entry *rv = hash_lookup(hash, &n);
+
+       if (!rv || !rv->present)
+               return NULL;
+
+       return rv;
+}
+
+static void neighbor_lists_update(struct fabricd *f)
+{
+       neighbor_lists_clear(f);
+
+       struct listnode *node;
+       struct isis_vertex *v;
+
+       for (ALL_QUEUE_ELEMENTS_RO(&f->spftree->paths, node, v)) {
+               if (!v->d_N || !VTYPE_IS(v->type))
+                       continue;
+
+               if (v->d_N > 2)
+                       break;
+
+               struct neighbor_entry *n = neighbor_entry_new(v);
+               if (v->d_N == 1) {
+                       skiplist_insert(f->neighbors, n, n);
+               } else {
+                       struct neighbor_entry *inserted;
+                       inserted = hash_get(f->neighbors_neighbors, n, hash_alloc_intern);
+                       assert(inserted == n);
+               }
+       }
+}
+
+struct fabricd *fabricd_new(struct isis_area *area)
 {
        struct fabricd *rv = XCALLOC(MTYPE_FABRICD_STATE, sizeof(*rv));
 
+       rv->area = area;
        rv->initial_sync_state = FABRICD_SYNC_PENDING;
+
+       rv->spftree = isis_spftree_new(area);
+       rv->neighbors = skiplist_new(0, neighbor_entry_list_cmp,
+                                    neighbor_entry_del_void);
+       rv->neighbors_neighbors = hash_create(neighbor_entry_hash_key,
+                                             neighbor_entry_hash_cmp,
+                                             "Fabricd Neighbors");
+
+       rv->tier = rv->tier_config = ISIS_TIER_UNDEFINED;
        return rv;
 };
 
+void fabricd_finish(struct fabricd *f)
+{
+       if (f->initial_sync_timeout)
+               thread_cancel(f->initial_sync_timeout);
+
+       if (f->tier_calculation_timer)
+               thread_cancel(f->tier_calculation_timer);
+
+       if (f->tier_set_timer)
+               thread_cancel(f->tier_set_timer);
+
+       isis_spftree_del(f->spftree);
+       neighbor_lists_clear(f);
+       skiplist_free(f->neighbors);
+       hash_free(f->neighbors_neighbors);
+}
+
 static int fabricd_initial_sync_timeout(struct thread *thread)
 {
        struct fabricd *f = THREAD_ARG(thread);
@@ -108,6 +271,16 @@ bool fabricd_initial_sync_is_in_progress(struct isis_area *area)
        return false;
 }
 
+bool fabricd_initial_sync_is_complete(struct isis_area *area)
+{
+       struct fabricd *f = area->fabricd;
+
+       if (!f)
+               return false;
+
+       return f->initial_sync_state == FABRICD_SYNC_COMPLETE;
+}
+
 struct isis_circuit *fabricd_initial_sync_circuit(struct isis_area *area)
 {
        struct fabricd *f = area->fabricd;
@@ -134,3 +307,411 @@ void fabricd_initial_sync_finish(struct isis_area *area)
        thread_cancel(f->initial_sync_timeout);
        f->initial_sync_timeout = NULL;
 }
+
+static void fabricd_bump_tier_calculation_timer(struct fabricd *f);
+static void fabricd_set_tier(struct fabricd *f, uint8_t tier);
+
+static uint8_t fabricd_calculate_fabric_tier(struct isis_area *area)
+{
+       struct isis_spftree *local_tree = fabricd_spftree(area);
+       struct listnode *node;
+
+       struct isis_vertex *furthest_t0 = NULL,
+                          *second_furthest_t0 = NULL;
+
+       struct isis_vertex *v;
+
+       for (ALL_QUEUE_ELEMENTS_RO(&local_tree->paths, node, v)) {
+               struct isis_lsp *lsp = lsp_for_vertex(local_tree, v);
+
+               if (!lsp || !lsp->tlvs
+                   || !lsp->tlvs->spine_leaf
+                   || !lsp->tlvs->spine_leaf->has_tier
+                   || lsp->tlvs->spine_leaf->tier != 0)
+                       continue;
+
+               second_furthest_t0 = furthest_t0;
+               furthest_t0 = v;
+       }
+
+       if (!second_furthest_t0) {
+               zlog_info("OpenFabric: Could not find two T0 routers");
+               return ISIS_TIER_UNDEFINED;
+       }
+
+       zlog_info("OpenFabric: Found %s as furthest t0 from local system, dist == %"
+                 PRIu32, rawlspid_print(furthest_t0->N.id), furthest_t0->d_N);
+
+       struct isis_spftree *remote_tree =
+               isis_run_hopcount_spf(area, furthest_t0->N.id, NULL);
+
+       struct isis_vertex *furthest_from_remote =
+               isis_vertex_queue_last(&remote_tree->paths);
+
+       if (!furthest_from_remote) {
+               zlog_info("OpenFabric: Found no furthest node in remote spf");
+               isis_spftree_del(remote_tree);
+               return ISIS_TIER_UNDEFINED;
+       } else {
+               zlog_info("OpenFabric: Found %s as furthest from remote dist == %"
+                         PRIu32, rawlspid_print(furthest_from_remote->N.id),
+                         furthest_from_remote->d_N);
+       }
+
+       int64_t tier = furthest_from_remote->d_N - furthest_t0->d_N;
+       isis_spftree_del(remote_tree);
+
+       if (tier < 0 || tier >= ISIS_TIER_UNDEFINED) {
+               zlog_info("OpenFabric: Calculated tier %" PRId64 " seems implausible",
+                         tier);
+               return ISIS_TIER_UNDEFINED;
+       }
+
+       zlog_info("OpenFabric: Calculated %" PRId64 " as tier", tier);
+       return tier;
+}
+
+static int fabricd_tier_set_timer(struct thread *thread)
+{
+       struct fabricd *f = THREAD_ARG(thread);
+       f->tier_set_timer = NULL;
+
+       fabricd_set_tier(f, f->tier_pending);
+       return 0;
+}
+
+static int fabricd_tier_calculation_cb(struct thread *thread)
+{
+       struct fabricd *f = THREAD_ARG(thread);
+       uint8_t tier = ISIS_TIER_UNDEFINED;
+       f->tier_calculation_timer = NULL;
+
+       tier = fabricd_calculate_fabric_tier(f->area);
+       if (tier == ISIS_TIER_UNDEFINED)
+               return 0;
+
+       zlog_info("OpenFabric: Got tier %" PRIu8 " from algorithm. Arming timer.",
+                 tier);
+       f->tier_pending = tier;
+       thread_add_timer(master, fabricd_tier_set_timer, f,
+                        f->area->lsp_gen_interval[ISIS_LEVEL2 - 1],
+                        &f->tier_set_timer);
+
+       return 0;
+}
+
+static void fabricd_bump_tier_calculation_timer(struct fabricd *f)
+{
+       /* Cancel timer if we already know our tier */
+       if (f->tier != ISIS_TIER_UNDEFINED
+           || f->tier_set_timer) {
+               if (f->tier_calculation_timer) {
+                       thread_cancel(f->tier_calculation_timer);
+                       f->tier_calculation_timer = NULL;
+               }
+               return;
+       }
+
+       /* If we need to calculate the tier, wait some
+        * time for the topology to settle before running
+        * the calculation */
+       if (f->tier_calculation_timer) {
+               thread_cancel(f->tier_calculation_timer);
+               f->tier_calculation_timer = NULL;
+       }
+
+       thread_add_timer(master, fabricd_tier_calculation_cb, f,
+                        2 * f->area->lsp_gen_interval[ISIS_LEVEL2 - 1],
+                        &f->tier_calculation_timer);
+}
+
+static void fabricd_set_tier(struct fabricd *f, uint8_t tier)
+{
+       if (f->tier == tier)
+               return;
+
+       zlog_info("OpenFabric: Set own tier to %" PRIu8, tier);
+       f->tier = tier;
+
+       fabricd_bump_tier_calculation_timer(f);
+       lsp_regenerate_schedule(f->area, ISIS_LEVEL2, 0);
+}
+
+void fabricd_run_spf(struct isis_area *area)
+{
+       struct fabricd *f = area->fabricd;
+
+       if (!f)
+               return;
+
+       isis_run_hopcount_spf(area, isis->sysid, f->spftree);
+       neighbor_lists_update(f);
+       fabricd_bump_tier_calculation_timer(f);
+}
+
+struct isis_spftree *fabricd_spftree(struct isis_area *area)
+{
+       struct fabricd *f = area->fabricd;
+
+       if (!f)
+               return NULL;
+
+       return f->spftree;
+}
+
+void fabricd_configure_tier(struct isis_area *area, uint8_t tier)
+{
+       struct fabricd *f = area->fabricd;
+
+       if (!f || f->tier_config == tier)
+               return;
+
+       f->tier_config = tier;
+       fabricd_set_tier(f, tier);
+}
+
+uint8_t fabricd_tier(struct isis_area *area)
+{
+       struct fabricd *f = area->fabricd;
+
+       if (!f)
+               return ISIS_TIER_UNDEFINED;
+
+       return f->tier;
+}
+
+int fabricd_write_settings(struct isis_area *area, struct vty *vty)
+{
+       struct fabricd *f = area->fabricd;
+       int written = 0;
+
+       if (!f)
+               return written;
+
+       if (f->tier_config != ISIS_TIER_UNDEFINED) {
+               vty_out(vty, " fabric-tier %" PRIu8 "\n", f->tier_config);
+               written++;
+       }
+
+       return written;
+}
+
+static void move_to_dnr(struct isis_lsp *lsp, struct neighbor_entry *n)
+{
+       struct isis_adjacency *adj = listnode_head(n->vertex->Adj_N);
+
+       n->present = false;
+
+       if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+               char buff[PREFIX2STR_BUFFER];
+               zlog_debug("OpenFabric: Adding %s to DNR",
+                          vid2string(n->vertex, buff, sizeof(buff)));
+       }
+
+       if (adj) {
+               isis_tx_queue_add(adj->circuit->tx_queue, lsp,
+                                 TX_LSP_CIRCUIT_SCOPED);
+       }
+}
+
+static void move_to_rf(struct isis_lsp *lsp, struct neighbor_entry *n)
+{
+       struct isis_adjacency *adj = listnode_head(n->vertex->Adj_N);
+
+       n->present = false;
+
+       if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+               char buff[PREFIX2STR_BUFFER];
+               zlog_debug("OpenFabric: Adding %s to RF",
+                          vid2string(n->vertex, buff, sizeof(buff)));
+       }
+
+       if (adj) {
+               isis_tx_queue_add(adj->circuit->tx_queue, lsp,
+                                 TX_LSP_NORMAL);
+       }
+}
+
+static void mark_neighbor_as_present(struct hash_backet *backet, void *arg)
+{
+       struct neighbor_entry *n = backet->data;
+
+       n->present = true;
+}
+
+static void handle_firsthops(struct hash_backet *backet, void *arg)
+{
+       struct isis_lsp *lsp = arg;
+       struct fabricd *f = lsp->area->fabricd;
+       struct isis_vertex *vertex = backet->data;
+
+       struct neighbor_entry *n;
+
+       n = neighbor_entry_lookup_list(f->neighbors, vertex->N.id);
+       if (n) {
+               if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+                       char buff[PREFIX2STR_BUFFER];
+                       zlog_debug("Removing %s from NL as its in the reverse path",
+                                  vid2string(vertex, buff, sizeof(buff)));
+               }
+               n->present = false;
+       }
+
+       n = neighbor_entry_lookup_hash(f->neighbors_neighbors, vertex->N.id);
+       if (n) {
+               if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+                       char buff[PREFIX2STR_BUFFER];
+                       zlog_debug("Removing %s from NN as its in the reverse path",
+                                  vid2string(vertex, buff, sizeof(buff)));
+               }
+               n->present = false;
+       }
+}
+
+void fabricd_lsp_flood(struct isis_lsp *lsp)
+{
+       struct fabricd *f = lsp->area->fabricd;
+       assert(f);
+
+       void *cursor = NULL;
+       struct neighbor_entry *n;
+
+       if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+               zlog_debug("OpenFabric: Flooding LSP %s",
+                          rawlspid_print(lsp->hdr.lsp_id));
+       }
+
+       /* Mark all elements in NL as present and move T0s into DNR */
+       while (!skiplist_next(f->neighbors, NULL, (void **)&n, &cursor)) {
+               n->present = true;
+
+               struct isis_lsp *lsp = lsp_for_vertex(f->spftree, n->vertex);
+               if (!lsp || !lsp->tlvs || !lsp->tlvs->spine_leaf)
+                       continue;
+
+               if (!lsp->tlvs->spine_leaf->has_tier
+                   || lsp->tlvs->spine_leaf->tier != 0)
+                       continue;
+
+               if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+                       zlog_debug("Moving %s to DNR because it's T0",
+                                  rawlspid_print(lsp->hdr.lsp_id));
+               }
+
+               move_to_dnr(lsp, n);
+       }
+
+       /* Mark all elements in NN as present */
+       hash_iterate(f->neighbors_neighbors, mark_neighbor_as_present, NULL);
+
+       struct isis_vertex *originator = isis_find_vertex(&f->spftree->paths,
+                                                         lsp->hdr.lsp_id,
+                                                         VTYPE_NONPSEUDO_TE_IS);
+
+       /* Remove all IS from NL and NN in the shortest path
+        * to the IS that originated the LSP */
+       if (originator)
+               hash_iterate(originator->firsthops, handle_firsthops, lsp);
+
+       /* Iterate over all remaining IS in NL */
+       cursor = NULL;
+       while (!skiplist_next(f->neighbors, NULL, (void **)&n, &cursor)) {
+               if (!n->present)
+                       continue;
+
+               struct isis_lsp *nlsp = lsp_for_vertex(f->spftree, n->vertex);
+               if (!nlsp || !nlsp->tlvs) {
+                       if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+                               char buff[PREFIX2STR_BUFFER];
+                               zlog_debug("Moving %s to DNR as it has no LSP",
+                                          vid2string(n->vertex, buff, sizeof(buff)));
+                       }
+
+                       move_to_dnr(lsp, n);
+                       continue;
+               }
+
+               if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+                       char buff[PREFIX2STR_BUFFER];
+                       zlog_debug("Considering %s from NL...",
+                                  vid2string(n->vertex, buff, sizeof(buff)));
+               }
+
+               /* For all neighbors of the NL IS check whether they are present
+                * in NN. If yes, remove from NN and set need_reflood. */
+               bool need_reflood = false;
+               struct isis_extended_reach *er;
+               for (er = (struct isis_extended_reach *)nlsp->tlvs->extended_reach.head;
+                    er; er = er->next) {
+                       struct neighbor_entry *nn;
+
+                       nn = neighbor_entry_lookup_hash(f->neighbors_neighbors,
+                                                       er->id);
+
+                       if (nn) {
+                               if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+                                       char buff[PREFIX2STR_BUFFER];
+                                       zlog_debug("Found neighbor %s in NN, removing it from NN and setting reflood.",
+                                                  vid2string(nn->vertex, buff, sizeof(buff)));
+                               }
+
+                               nn->present = false;
+                               need_reflood = true;
+                       }
+               }
+
+               if (need_reflood)
+                       move_to_rf(lsp, n);
+               else
+                       move_to_dnr(lsp, n);
+       }
+
+       if (isis->debugs & DEBUG_FABRICD_FLOODING) {
+               zlog_debug("OpenFabric: Flooding algorithm complete.");
+       }
+}
+
+void fabricd_trigger_csnp(struct isis_area *area)
+{
+       struct fabricd *f = area->fabricd;
+
+       if (!f)
+               return;
+
+       struct listnode *node;
+       struct isis_circuit *circuit;
+
+       for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
+               if (!circuit->t_send_csnp[1])
+                       continue;
+
+               thread_cancel(circuit->t_send_csnp[ISIS_LEVEL2 - 1]);
+               thread_add_timer_msec(master, send_l2_csnp, circuit,
+                                     isis_jitter(500, CSNP_JITTER),
+                                     &circuit->t_send_csnp[ISIS_LEVEL2 - 1]);
+       }
+}
+
+struct list *fabricd_ip_addrs(struct isis_circuit *circuit)
+{
+       if (circuit->ip_addrs && listcount(circuit->ip_addrs))
+               return circuit->ip_addrs;
+
+       if (!fabricd || !circuit->area || !circuit->area->circuit_list)
+               return NULL;
+
+       struct listnode *node;
+       struct isis_circuit *c;
+
+       for (ALL_LIST_ELEMENTS_RO(circuit->area->circuit_list, node, c)) {
+               if (c->circ_type != CIRCUIT_T_LOOPBACK)
+                       continue;
+
+               if (!c->ip_addrs || !listcount(c->ip_addrs))
+                       return NULL;
+
+               return c->ip_addrs;
+       }
+
+       return NULL;
+}