2003-12-23 09:09:43 +01:00
|
|
|
/*
|
2017-07-17 14:03:14 +02:00
|
|
|
* IS-IS Rout(e)ing protocol - isis_adjacency.c
|
2003-12-23 09:09:43 +01:00
|
|
|
* handling of IS-IS adjacencies
|
|
|
|
*
|
|
|
|
* Copyright (C) 2001,2002 Sampo Saaristo
|
2017-07-17 14:03:14 +02:00
|
|
|
* Tampere University of Technology
|
2003-12-23 09:09:43 +01:00
|
|
|
* Institute of Communications Engineering
|
|
|
|
*
|
2017-07-17 14:03:14 +02:00
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms of the GNU General Public Licenseas published by the Free
|
|
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
2003-12-23 09:09:43 +01:00
|
|
|
* any later version.
|
|
|
|
*
|
2017-07-17 14:03:14 +02:00
|
|
|
* This program 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
|
2003-12-23 09:09:43 +01:00
|
|
|
* more details.
|
2017-05-13 10:25:29 +02:00
|
|
|
*
|
|
|
|
* 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
|
2003-12-23 09:09:43 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <zebra.h>
|
|
|
|
|
|
|
|
#include "log.h"
|
|
|
|
#include "memory.h"
|
|
|
|
#include "hash.h"
|
|
|
|
#include "vty.h"
|
|
|
|
#include "linklist.h"
|
|
|
|
#include "thread.h"
|
|
|
|
#include "if.h"
|
|
|
|
#include "stream.h"
|
|
|
|
|
|
|
|
#include "isisd/isis_constants.h"
|
|
|
|
#include "isisd/isis_common.h"
|
2012-03-24 16:35:20 +01:00
|
|
|
#include "isisd/isis_flags.h"
|
2003-12-23 09:09:43 +01:00
|
|
|
#include "isisd/isisd.h"
|
|
|
|
#include "isisd/isis_circuit.h"
|
|
|
|
#include "isisd/isis_adjacency.h"
|
|
|
|
#include "isisd/isis_misc.h"
|
|
|
|
#include "isisd/isis_dr.h"
|
|
|
|
#include "isisd/isis_dynhn.h"
|
|
|
|
#include "isisd/isis_pdu.h"
|
2012-03-24 16:35:20 +01:00
|
|
|
#include "isisd/isis_lsp.h"
|
|
|
|
#include "isisd/isis_spf.h"
|
|
|
|
#include "isisd/isis_events.h"
|
2017-04-27 13:56:43 +02:00
|
|
|
#include "isisd/isis_mt.h"
|
2018-03-05 21:00:40 +01:00
|
|
|
#include "isisd/isis_tlvs.h"
|
fabricd: adjacency formation optimization as per section 2.4
OpenFabric changes IS-IS's initial database synchronization. While
regular IS-IS will simultaneuously exchange LSPs with all neighboring
routers during startup, this is considered too much churn for a densely
connected fabric.
To mitigate this, OpenFabric prescribes that a router should only
bring up an adjacency with a single neighbor and perform a full
synchronization with that neighbor, before bringing up further
adjacencies.
This is implemented by having a field `initial_sync_state` in the
fabricd datastructure which tracks whether an initial sync is still
pending, currently in progress, or complete.
When an initial sync is pending, the state will transition to the
in-progress state when the first IIH is received.
During this state, all IIHs from other routers are ignored. Any
IIHs transmitted on any link other than the one to the router with
which we are performing the initial sync will always report the far
end as DOWN in their threeway handshake state, avoiding the formation of
additional adjacencies.
The state will be left if all the SRM and SSN flags on the
initial-sync circuit are cleared (meaning that initial sync has
completed). This is checked in `lsp_tick`. When this condition occurrs,
we progress to the initial-sync-complete state, allowing other
adjacencies to form.
The state can also be left if the initial synchronization is taking too
long to succeed, for whatever reason. In that case, we fall back to the
initial-sync-pending state and will reattempt initial synchronization
with a different neighbor.
Signed-off-by: Christian Franke <chris@opensourcerouting.org>
2018-04-02 17:55:26 +02:00
|
|
|
#include "isisd/fabricd.h"
|
2019-10-17 20:33:53 +02:00
|
|
|
#include "isisd/isis_nb.h"
|
2003-12-23 09:09:43 +01:00
|
|
|
|
|
|
|
extern struct isis *isis;
|
|
|
|
|
2018-03-27 21:13:34 +02:00
|
|
|
static struct isis_adjacency *adj_alloc(const uint8_t *id)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
struct isis_adjacency *adj;
|
2003-12-23 09:09:43 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
adj = XCALLOC(MTYPE_ISIS_ADJACENCY, sizeof(struct isis_adjacency));
|
|
|
|
memcpy(adj->sysid, id, ISIS_SYS_ID_LEN);
|
2004-09-10 22:48:21 +02:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
return adj;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:34 +02:00
|
|
|
struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa,
|
2017-07-17 14:03:14 +02:00
|
|
|
int level, struct isis_circuit *circuit)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
struct isis_adjacency *adj;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
adj = adj_alloc(id); /* P2P kludge */
|
|
|
|
|
|
|
|
if (snpa) {
|
|
|
|
memcpy(adj->snpa, snpa, ETH_ALEN);
|
|
|
|
} else {
|
|
|
|
memset(adj->snpa, ' ', ETH_ALEN);
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
adj->circuit = circuit;
|
|
|
|
adj->level = level;
|
|
|
|
adj->flaps = 0;
|
|
|
|
adj->last_flap = time(NULL);
|
2018-03-05 21:00:40 +01:00
|
|
|
adj->threeway_state = ISIS_THREEWAY_DOWN;
|
2017-07-17 14:03:14 +02:00
|
|
|
if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
|
|
|
|
listnode_add(circuit->u.bc.adjdb[level - 1], adj);
|
|
|
|
adj->dischanges[level - 1] = 0;
|
|
|
|
for (i = 0; i < DIS_RECORDS;
|
|
|
|
i++) /* clear N DIS state change records */
|
|
|
|
{
|
|
|
|
adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis =
|
|
|
|
ISIS_UNKNOWN_DIS;
|
|
|
|
adj->dis_record[(i * ISIS_LEVELS) + level - 1]
|
|
|
|
.last_dis_change = time(NULL);
|
|
|
|
}
|
|
|
|
}
|
2019-08-04 03:02:37 +02:00
|
|
|
adj->adj_sids = list_new();
|
2017-07-17 14:03:14 +02:00
|
|
|
|
|
|
|
return adj;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:34 +02:00
|
|
|
struct isis_adjacency *isis_adj_lookup(const uint8_t *sysid, struct list *adjdb)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
struct isis_adjacency *adj;
|
|
|
|
struct listnode *node;
|
2003-12-23 09:09:43 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj))
|
|
|
|
if (memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN) == 0)
|
|
|
|
return adj;
|
2004-09-10 22:48:21 +02:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
return NULL;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
2018-03-27 21:13:34 +02:00
|
|
|
struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa,
|
2017-07-17 14:03:14 +02:00
|
|
|
struct list *adjdb)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
struct listnode *node;
|
|
|
|
struct isis_adjacency *adj;
|
2003-12-23 09:09:43 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj))
|
|
|
|
if (memcmp(adj->snpa, ssnpa, ETH_ALEN) == 0)
|
|
|
|
return adj;
|
2004-09-10 22:48:21 +02:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
return NULL;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
2019-08-04 03:02:37 +02:00
|
|
|
bool isis_adj_exists(const struct isis_area *area, int level,
|
|
|
|
const uint8_t *sysid)
|
|
|
|
{
|
|
|
|
struct isis_circuit *circuit;
|
|
|
|
struct listnode *node;
|
|
|
|
|
|
|
|
for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
|
|
|
|
struct isis_adjacency *adj;
|
|
|
|
struct listnode *anode;
|
|
|
|
struct list *adjdb;
|
|
|
|
|
|
|
|
switch (circuit->circ_type) {
|
|
|
|
case CIRCUIT_T_BROADCAST:
|
|
|
|
adjdb = circuit->u.bc.adjdb[level - 1];
|
|
|
|
if (!adjdb)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (ALL_LIST_ELEMENTS_RO(adjdb, anode, adj)) {
|
|
|
|
if (!memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CIRCUIT_T_P2P:
|
|
|
|
adj = circuit->u.p2p.neighbor;
|
|
|
|
if (!adj)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN))
|
|
|
|
return true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-27 14:23:06 +02:00
|
|
|
DEFINE_HOOK(isis_adj_state_change_hook, (struct isis_adjacency *adj), (adj))
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
void isis_delete_adj(void *arg)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
struct isis_adjacency *adj = arg;
|
2012-03-24 16:35:20 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
if (!adj)
|
|
|
|
return;
|
2004-09-10 22:48:21 +02:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
THREAD_TIMER_OFF(adj->t_expire);
|
2018-09-27 14:23:06 +02:00
|
|
|
if (adj->adj_state != ISIS_ADJ_DOWN) {
|
|
|
|
adj->adj_state = ISIS_ADJ_DOWN;
|
|
|
|
hook_call(isis_adj_state_change_hook, adj);
|
|
|
|
}
|
2012-03-24 16:35:20 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
/* remove from SPF trees */
|
|
|
|
spftree_area_adj_del(adj->circuit->area, adj);
|
2005-10-01 08:03:04 +02:00
|
|
|
|
2019-02-25 21:18:13 +01:00
|
|
|
XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->area_addresses);
|
|
|
|
XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ipv4_addresses);
|
|
|
|
XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ipv6_addresses);
|
2012-03-24 16:35:20 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
adj_mt_finish(adj);
|
2019-08-04 03:02:37 +02:00
|
|
|
list_delete(&adj->adj_sids);
|
2017-04-27 13:56:41 +02:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
XFREE(MTYPE_ISIS_ADJACENCY, adj);
|
|
|
|
return;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
static const char *adj_state2string(int state)
|
2012-03-24 16:35:20 +01:00
|
|
|
{
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
switch (state) {
|
|
|
|
case ISIS_ADJ_INITIALIZING:
|
|
|
|
return "Initializing";
|
|
|
|
case ISIS_ADJ_UP:
|
|
|
|
return "Up";
|
|
|
|
case ISIS_ADJ_DOWN:
|
|
|
|
return "Down";
|
|
|
|
default:
|
|
|
|
return "Unknown";
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL; /* not reached */
|
2012-03-24 16:35:20 +01:00
|
|
|
}
|
|
|
|
|
2020-07-01 19:37:16 +02:00
|
|
|
static const char *adj_level2string(int level)
|
|
|
|
{
|
|
|
|
switch (level) {
|
|
|
|
case IS_LEVEL_1:
|
|
|
|
return "level-1";
|
|
|
|
case IS_LEVEL_2:
|
|
|
|
return "level-2";
|
|
|
|
case IS_LEVEL_1_AND_2:
|
|
|
|
return "level-1-2";
|
|
|
|
default:
|
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL; /* not reached */
|
|
|
|
}
|
|
|
|
|
2018-03-05 21:00:40 +01:00
|
|
|
void isis_adj_process_threeway(struct isis_adjacency *adj,
|
|
|
|
struct isis_threeway_adj *tw_adj,
|
|
|
|
enum isis_adj_usage adj_usage)
|
|
|
|
{
|
|
|
|
enum isis_threeway_state next_tw_state = ISIS_THREEWAY_DOWN;
|
|
|
|
|
2018-03-05 21:29:15 +01:00
|
|
|
if (tw_adj && !adj->circuit->disable_threeway_adj) {
|
2018-03-05 21:00:40 +01:00
|
|
|
if (tw_adj->state == ISIS_THREEWAY_DOWN) {
|
|
|
|
next_tw_state = ISIS_THREEWAY_INITIALIZING;
|
|
|
|
} else if (tw_adj->state == ISIS_THREEWAY_INITIALIZING) {
|
|
|
|
next_tw_state = ISIS_THREEWAY_UP;
|
|
|
|
} else if (tw_adj->state == ISIS_THREEWAY_UP) {
|
|
|
|
if (adj->threeway_state == ISIS_THREEWAY_DOWN)
|
|
|
|
next_tw_state = ISIS_THREEWAY_DOWN;
|
|
|
|
else
|
|
|
|
next_tw_state = ISIS_THREEWAY_UP;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
next_tw_state = ISIS_THREEWAY_UP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (next_tw_state != adj->threeway_state) {
|
2020-06-19 21:04:33 +02:00
|
|
|
if (IS_DEBUG_ADJ_PACKETS) {
|
2018-03-05 21:00:40 +01:00
|
|
|
zlog_info("ISIS-Adj (%s): Threeway state change %s to %s",
|
|
|
|
adj->circuit->area->area_tag,
|
|
|
|
isis_threeway_state_name(adj->threeway_state),
|
|
|
|
isis_threeway_state_name(next_tw_state));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
fabricd: adjacency formation optimization as per section 2.4
OpenFabric changes IS-IS's initial database synchronization. While
regular IS-IS will simultaneuously exchange LSPs with all neighboring
routers during startup, this is considered too much churn for a densely
connected fabric.
To mitigate this, OpenFabric prescribes that a router should only
bring up an adjacency with a single neighbor and perform a full
synchronization with that neighbor, before bringing up further
adjacencies.
This is implemented by having a field `initial_sync_state` in the
fabricd datastructure which tracks whether an initial sync is still
pending, currently in progress, or complete.
When an initial sync is pending, the state will transition to the
in-progress state when the first IIH is received.
During this state, all IIHs from other routers are ignored. Any
IIHs transmitted on any link other than the one to the router with
which we are performing the initial sync will always report the far
end as DOWN in their threeway handshake state, avoiding the formation of
additional adjacencies.
The state will be left if all the SRM and SSN flags on the
initial-sync circuit are cleared (meaning that initial sync has
completed). This is checked in `lsp_tick`. When this condition occurrs,
we progress to the initial-sync-complete state, allowing other
adjacencies to form.
The state can also be left if the initial synchronization is taking too
long to succeed, for whatever reason. In that case, we fall back to the
initial-sync-pending state and will reattempt initial synchronization
with a different neighbor.
Signed-off-by: Christian Franke <chris@opensourcerouting.org>
2018-04-02 17:55:26 +02:00
|
|
|
if (next_tw_state != ISIS_THREEWAY_DOWN)
|
|
|
|
fabricd_initial_sync_hello(adj->circuit);
|
|
|
|
|
2018-03-05 21:00:40 +01:00
|
|
|
if (next_tw_state == ISIS_THREEWAY_DOWN) {
|
2020-04-18 02:06:07 +02:00
|
|
|
isis_adj_state_change(&adj, ISIS_ADJ_DOWN,
|
|
|
|
"Neighbor restarted");
|
2018-03-05 21:00:40 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (next_tw_state == ISIS_THREEWAY_UP) {
|
|
|
|
if (adj->adj_state != ISIS_ADJ_UP) {
|
2020-04-18 02:06:07 +02:00
|
|
|
isis_adj_state_change(&adj, ISIS_ADJ_UP, NULL);
|
2018-03-05 21:00:40 +01:00
|
|
|
adj->adj_usage = adj_usage;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-10 16:00:16 +02:00
|
|
|
if (adj->threeway_state != next_tw_state) {
|
|
|
|
send_hello_sched(adj->circuit, 0, TRIGGERED_IIH_DELAY);
|
|
|
|
}
|
|
|
|
|
2018-03-05 21:00:40 +01:00
|
|
|
adj->threeway_state = next_tw_state;
|
|
|
|
}
|
2020-07-01 19:37:16 +02:00
|
|
|
void isis_log_adj_change(struct isis_adjacency *adj,
|
|
|
|
enum isis_adj_state old_state,
|
|
|
|
enum isis_adj_state new_state, const char *reason)
|
|
|
|
{
|
|
|
|
const char *adj_name;
|
|
|
|
struct isis_dynhn *dyn;
|
|
|
|
|
|
|
|
dyn = dynhn_find_by_id(adj->sysid);
|
|
|
|
if (dyn)
|
|
|
|
adj_name = dyn->hostname;
|
|
|
|
else
|
|
|
|
adj_name = sysid_print(adj->sysid);
|
|
|
|
|
|
|
|
zlog_info(
|
|
|
|
"%%ADJCHANGE: Adjacency to %s (%s) for %s changed from %s to %s, %s",
|
|
|
|
adj_name, adj->circuit->interface->name,
|
|
|
|
adj_level2string(adj->level), adj_state2string(old_state),
|
|
|
|
adj_state2string(new_state), reason ? reason : "unspecified");
|
|
|
|
}
|
2020-04-18 02:06:07 +02:00
|
|
|
void isis_adj_state_change(struct isis_adjacency **padj,
|
2017-07-17 14:03:14 +02:00
|
|
|
enum isis_adj_state new_state, const char *reason)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2020-04-18 02:06:07 +02:00
|
|
|
struct isis_adjacency *adj = *padj;
|
2018-10-10 16:00:16 +02:00
|
|
|
enum isis_adj_state old_state = adj->adj_state;
|
|
|
|
struct isis_circuit *circuit = adj->circuit;
|
2020-04-18 02:06:07 +02:00
|
|
|
bool del = false;
|
2017-07-17 14:03:14 +02:00
|
|
|
|
2020-06-08 18:00:42 +02:00
|
|
|
if (new_state == old_state)
|
|
|
|
return;
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
adj->adj_state = new_state;
|
2020-06-08 18:00:42 +02:00
|
|
|
send_hello_sched(circuit, adj->level, TRIGGERED_IIH_DELAY);
|
2017-07-17 14:03:14 +02:00
|
|
|
|
2020-06-19 21:04:33 +02:00
|
|
|
if (IS_DEBUG_ADJ_PACKETS) {
|
2017-07-17 14:03:14 +02:00
|
|
|
zlog_debug("ISIS-Adj (%s): Adjacency state change %d->%d: %s",
|
|
|
|
circuit->area->area_tag, old_state, new_state,
|
|
|
|
reason ? reason : "unspecified");
|
|
|
|
}
|
|
|
|
|
2020-07-01 19:37:16 +02:00
|
|
|
if (circuit->area->log_adj_changes)
|
|
|
|
isis_log_adj_change(adj, old_state, new_state, reason);
|
2017-07-17 14:03:14 +02:00
|
|
|
|
2019-09-23 14:38:02 +02:00
|
|
|
circuit->adj_state_changes++;
|
2018-11-14 15:24:00 +01:00
|
|
|
#ifndef FABRICD
|
|
|
|
/* send northbound notification */
|
|
|
|
isis_notif_adj_state_change(adj, new_state, reason);
|
|
|
|
#endif /* ifndef FABRICD */
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
|
2018-10-10 16:00:16 +02:00
|
|
|
for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
|
2017-07-17 14:03:14 +02:00
|
|
|
if ((adj->level & level) == 0)
|
|
|
|
continue;
|
|
|
|
if (new_state == ISIS_ADJ_UP) {
|
|
|
|
circuit->upadjcount[level - 1]++;
|
2018-09-27 14:23:06 +02:00
|
|
|
hook_call(isis_adj_state_change_hook, adj);
|
2017-07-17 14:03:14 +02:00
|
|
|
/* update counter & timers for debugging
|
|
|
|
* purposes */
|
|
|
|
adj->last_flap = time(NULL);
|
|
|
|
adj->flaps++;
|
2020-06-08 18:00:42 +02:00
|
|
|
} else if (old_state == ISIS_ADJ_UP) {
|
2017-07-17 14:03:14 +02:00
|
|
|
listnode_delete(circuit->u.bc.adjdb[level - 1],
|
|
|
|
adj);
|
2017-10-03 01:42:22 +02:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
circuit->upadjcount[level - 1]--;
|
2017-10-03 01:42:22 +02:00
|
|
|
if (circuit->upadjcount[level - 1] == 0)
|
fabricd: reimplement LSP transmission logic
Before this commit, isisd/fabricd maintained a bitfield for each LSP
to track the SRM bit for each circuit, which specifies whether an LSP
needs to be sent on that circuit. Every second, it would scan over all
LSPs in `lsp_tick` and queue them up for transmission accordingly.
This design has two drawbacks: a) it scales poorly b) it adds
unacceptable latency to the update process: each router takes a random
amount of time between 0 and 1 seconds to forward an update. In a
network with a diamter of 10, it might already take 10 seconds for an
update to traverse the network.
To mitigate this, a new design was chosen. Instead of tracking SRM in a
bitfield, have one tx_queue per circuit and declare that an LSP is in
that queue if and only if it would have SRM set for that circuit.
This way, we can track SRM similarly as we did before, however, on
insertion into the LSP queue, we can add a timer for (re)transmission,
alleviating the need for a periodic scan with LSP tick and reducing the
latency for forwarding of updates.
Signed-off-by: Christian Franke <chris@opensourcerouting.org>
2018-05-10 17:37:05 +02:00
|
|
|
isis_tx_queue_clean(circuit->tx_queue);
|
2017-10-03 01:42:22 +02:00
|
|
|
|
2018-09-27 14:23:06 +02:00
|
|
|
hook_call(isis_adj_state_change_hook, adj);
|
2020-06-08 18:00:42 +02:00
|
|
|
if (new_state == ISIS_ADJ_DOWN)
|
|
|
|
del = true;
|
2017-07-17 14:03:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (circuit->u.bc.lan_neighs[level - 1]) {
|
|
|
|
list_delete_all_node(
|
|
|
|
circuit->u.bc.lan_neighs[level - 1]);
|
|
|
|
isis_adj_build_neigh_list(
|
|
|
|
circuit->u.bc.adjdb[level - 1],
|
|
|
|
circuit->u.bc.lan_neighs[level - 1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* On adjacency state change send new pseudo LSP if we
|
|
|
|
* are the DR */
|
|
|
|
if (circuit->u.bc.is_dr[level - 1])
|
|
|
|
lsp_regenerate_schedule_pseudo(circuit, level);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (circuit->circ_type == CIRCUIT_T_P2P) {
|
2018-10-10 16:00:16 +02:00
|
|
|
for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
|
2017-07-17 14:03:14 +02:00
|
|
|
if ((adj->level & level) == 0)
|
|
|
|
continue;
|
|
|
|
if (new_state == ISIS_ADJ_UP) {
|
|
|
|
circuit->upadjcount[level - 1]++;
|
2018-09-27 14:23:06 +02:00
|
|
|
hook_call(isis_adj_state_change_hook, adj);
|
2017-07-17 14:03:14 +02:00
|
|
|
|
|
|
|
/* update counter & timers for debugging
|
|
|
|
* purposes */
|
|
|
|
adj->last_flap = time(NULL);
|
|
|
|
adj->flaps++;
|
|
|
|
|
fabricd: adjacency formation optimization as per section 2.4
OpenFabric changes IS-IS's initial database synchronization. While
regular IS-IS will simultaneuously exchange LSPs with all neighboring
routers during startup, this is considered too much churn for a densely
connected fabric.
To mitigate this, OpenFabric prescribes that a router should only
bring up an adjacency with a single neighbor and perform a full
synchronization with that neighbor, before bringing up further
adjacencies.
This is implemented by having a field `initial_sync_state` in the
fabricd datastructure which tracks whether an initial sync is still
pending, currently in progress, or complete.
When an initial sync is pending, the state will transition to the
in-progress state when the first IIH is received.
During this state, all IIHs from other routers are ignored. Any
IIHs transmitted on any link other than the one to the router with
which we are performing the initial sync will always report the far
end as DOWN in their threeway handshake state, avoiding the formation of
additional adjacencies.
The state will be left if all the SRM and SSN flags on the
initial-sync circuit are cleared (meaning that initial sync has
completed). This is checked in `lsp_tick`. When this condition occurrs,
we progress to the initial-sync-complete state, allowing other
adjacencies to form.
The state can also be left if the initial synchronization is taking too
long to succeed, for whatever reason. In that case, we fall back to the
initial-sync-pending state and will reattempt initial synchronization
with a different neighbor.
Signed-off-by: Christian Franke <chris@opensourcerouting.org>
2018-04-02 17:55:26 +02:00
|
|
|
if (level == IS_LEVEL_1) {
|
|
|
|
thread_add_timer(master, send_l1_csnp,
|
|
|
|
circuit, 0,
|
|
|
|
&circuit->t_send_csnp[0]);
|
|
|
|
} else {
|
|
|
|
thread_add_timer(master, send_l2_csnp,
|
|
|
|
circuit, 0,
|
|
|
|
&circuit->t_send_csnp[1]);
|
|
|
|
}
|
2020-06-08 18:00:42 +02:00
|
|
|
} else if (old_state == ISIS_ADJ_UP) {
|
2017-07-17 14:03:14 +02:00
|
|
|
if (adj->circuit->u.p2p.neighbor == adj)
|
|
|
|
adj->circuit->u.p2p.neighbor = NULL;
|
|
|
|
circuit->upadjcount[level - 1]--;
|
2017-10-03 01:42:22 +02:00
|
|
|
if (circuit->upadjcount[level - 1] == 0)
|
fabricd: reimplement LSP transmission logic
Before this commit, isisd/fabricd maintained a bitfield for each LSP
to track the SRM bit for each circuit, which specifies whether an LSP
needs to be sent on that circuit. Every second, it would scan over all
LSPs in `lsp_tick` and queue them up for transmission accordingly.
This design has two drawbacks: a) it scales poorly b) it adds
unacceptable latency to the update process: each router takes a random
amount of time between 0 and 1 seconds to forward an update. In a
network with a diamter of 10, it might already take 10 seconds for an
update to traverse the network.
To mitigate this, a new design was chosen. Instead of tracking SRM in a
bitfield, have one tx_queue per circuit and declare that an LSP is in
that queue if and only if it would have SRM set for that circuit.
This way, we can track SRM similarly as we did before, however, on
insertion into the LSP queue, we can add a timer for (re)transmission,
alleviating the need for a periodic scan with LSP tick and reducing the
latency for forwarding of updates.
Signed-off-by: Christian Franke <chris@opensourcerouting.org>
2018-05-10 17:37:05 +02:00
|
|
|
isis_tx_queue_clean(circuit->tx_queue);
|
2017-10-03 01:42:22 +02:00
|
|
|
|
2018-09-27 14:23:06 +02:00
|
|
|
hook_call(isis_adj_state_change_hook, adj);
|
2020-06-08 18:00:42 +02:00
|
|
|
if (new_state == ISIS_ADJ_DOWN)
|
|
|
|
del = true;
|
2017-07-17 14:03:14 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-18 02:06:07 +02:00
|
|
|
}
|
2017-07-17 14:03:14 +02:00
|
|
|
|
2020-04-18 02:06:07 +02:00
|
|
|
if (del) {
|
|
|
|
isis_delete_adj(adj);
|
|
|
|
*padj = NULL;
|
2017-07-17 14:03:14 +02:00
|
|
|
}
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
void isis_adj_print(struct isis_adjacency *adj)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
struct isis_dynhn *dyn;
|
|
|
|
|
|
|
|
if (!adj)
|
|
|
|
return;
|
|
|
|
dyn = dynhn_find_by_id(adj->sysid);
|
|
|
|
if (dyn)
|
2017-07-05 18:37:36 +02:00
|
|
|
zlog_debug("%s", dyn->hostname);
|
2017-07-17 14:03:14 +02:00
|
|
|
|
2020-03-24 19:15:04 +01:00
|
|
|
zlog_debug("SystemId %20s SNPA %s, level %d; Holding Time %d",
|
2017-07-17 14:03:14 +02:00
|
|
|
sysid_print(adj->sysid), snpa_print(adj->snpa), adj->level,
|
|
|
|
adj->hold_time);
|
2017-06-21 15:21:00 +02:00
|
|
|
if (adj->ipv4_address_count) {
|
2017-07-17 14:03:14 +02:00
|
|
|
zlog_debug("IPv4 Address(es):");
|
2017-06-21 15:21:00 +02:00
|
|
|
for (unsigned int i = 0; i < adj->ipv4_address_count; i++)
|
|
|
|
zlog_debug("%s", inet_ntoa(adj->ipv4_addresses[i]));
|
2017-07-17 14:03:14 +02:00
|
|
|
}
|
|
|
|
|
2017-06-21 15:21:00 +02:00
|
|
|
if (adj->ipv6_address_count) {
|
2017-07-17 14:03:14 +02:00
|
|
|
zlog_debug("IPv6 Address(es):");
|
2017-06-21 15:21:00 +02:00
|
|
|
for (unsigned int i = 0; i < adj->ipv6_address_count; i++) {
|
|
|
|
char buf[INET6_ADDRSTRLEN];
|
|
|
|
inet_ntop(AF_INET6, &adj->ipv6_addresses[i], buf,
|
|
|
|
sizeof(buf));
|
|
|
|
zlog_debug("%s", buf);
|
2017-07-17 14:03:14 +02:00
|
|
|
}
|
2004-09-10 22:48:21 +02:00
|
|
|
}
|
2017-07-17 14:03:14 +02:00
|
|
|
zlog_debug("Speaks: %s", nlpid2string(&adj->nlpids));
|
2003-12-23 09:09:43 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
return;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
2019-10-17 20:33:53 +02:00
|
|
|
const char *isis_adj_yang_state(enum isis_adj_state state)
|
|
|
|
{
|
|
|
|
switch (state) {
|
|
|
|
case ISIS_ADJ_DOWN:
|
|
|
|
return "down";
|
|
|
|
case ISIS_ADJ_UP:
|
|
|
|
return "up";
|
|
|
|
case ISIS_ADJ_INITIALIZING:
|
|
|
|
return "init";
|
|
|
|
default:
|
|
|
|
return "failed";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
int isis_adj_expire(struct thread *thread)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
struct isis_adjacency *adj;
|
2003-12-23 09:09:43 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
/*
|
|
|
|
* Get the adjacency
|
|
|
|
*/
|
|
|
|
adj = THREAD_ARG(thread);
|
|
|
|
assert(adj);
|
|
|
|
adj->t_expire = NULL;
|
2003-12-23 09:09:43 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
/* trigger the adj expire event */
|
2020-04-18 02:06:07 +02:00
|
|
|
isis_adj_state_change(&adj, ISIS_ADJ_DOWN, "holding time expired");
|
2003-12-23 09:09:43 +01:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
return 0;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-03-24 16:35:20 +01:00
|
|
|
* show isis neighbor [detail]
|
2003-12-23 09:09:43 +01:00
|
|
|
*/
|
2017-07-17 14:03:14 +02:00
|
|
|
void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty,
|
|
|
|
char detail)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
time_t now;
|
|
|
|
struct isis_dynhn *dyn;
|
|
|
|
int level;
|
|
|
|
|
|
|
|
dyn = dynhn_find_by_id(adj->sysid);
|
|
|
|
if (dyn)
|
2017-07-05 18:37:36 +02:00
|
|
|
vty_out(vty, " %-20s", dyn->hostname);
|
2017-07-17 14:03:14 +02:00
|
|
|
else
|
|
|
|
vty_out(vty, " %-20s", sysid_print(adj->sysid));
|
|
|
|
|
|
|
|
if (detail == ISIS_UI_LEVEL_BRIEF) {
|
|
|
|
if (adj->circuit)
|
|
|
|
vty_out(vty, "%-12s", adj->circuit->interface->name);
|
|
|
|
else
|
|
|
|
vty_out(vty, "NULL circuit!");
|
|
|
|
vty_out(vty, "%-3u", adj->level); /* level */
|
|
|
|
vty_out(vty, "%-13s", adj_state2string(adj->adj_state));
|
|
|
|
now = time(NULL);
|
|
|
|
if (adj->last_upd)
|
|
|
|
vty_out(vty, "%-9llu",
|
|
|
|
(unsigned long long)adj->last_upd
|
|
|
|
+ adj->hold_time - now);
|
|
|
|
else
|
|
|
|
vty_out(vty, "- ");
|
|
|
|
vty_out(vty, "%-10s", snpa_print(adj->snpa));
|
|
|
|
vty_out(vty, "\n");
|
2004-09-10 22:48:21 +02:00
|
|
|
}
|
2017-07-17 14:03:14 +02:00
|
|
|
|
|
|
|
if (detail == ISIS_UI_LEVEL_DETAIL) {
|
2019-08-04 03:02:37 +02:00
|
|
|
struct sr_adjacency *sra;
|
|
|
|
struct listnode *anode;
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
level = adj->level;
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
if (adj->circuit)
|
|
|
|
vty_out(vty, " Interface: %s",
|
|
|
|
adj->circuit->interface->name);
|
|
|
|
else
|
|
|
|
vty_out(vty, " Interface: NULL circuit");
|
|
|
|
vty_out(vty, ", Level: %u", adj->level); /* level */
|
|
|
|
vty_out(vty, ", State: %s", adj_state2string(adj->adj_state));
|
|
|
|
now = time(NULL);
|
|
|
|
if (adj->last_upd)
|
|
|
|
vty_out(vty, ", Expires in %s",
|
|
|
|
time2string(adj->last_upd + adj->hold_time
|
|
|
|
- now));
|
|
|
|
else
|
|
|
|
vty_out(vty, ", Expires in %s",
|
|
|
|
time2string(adj->hold_time));
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
vty_out(vty, " Adjacency flaps: %u", adj->flaps);
|
|
|
|
vty_out(vty, ", Last: %s ago",
|
|
|
|
time2string(now - adj->last_flap));
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
vty_out(vty, " Circuit type: %s",
|
|
|
|
circuit_t2string(adj->circuit_t));
|
|
|
|
vty_out(vty, ", Speaks: %s", nlpid2string(&adj->nlpids));
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
if (adj->mt_count != 1
|
|
|
|
|| adj->mt_set[0] != ISIS_MT_IPV4_UNICAST) {
|
|
|
|
vty_out(vty, " Topologies:\n");
|
|
|
|
for (unsigned int i = 0; i < adj->mt_count; i++)
|
|
|
|
vty_out(vty, " %s\n",
|
|
|
|
isis_mtid2str(adj->mt_set[i]));
|
|
|
|
}
|
|
|
|
vty_out(vty, " SNPA: %s", snpa_print(adj->snpa));
|
|
|
|
if (adj->circuit
|
|
|
|
&& (adj->circuit->circ_type == CIRCUIT_T_BROADCAST)) {
|
|
|
|
dyn = dynhn_find_by_id(adj->lanid);
|
|
|
|
if (dyn)
|
2017-07-05 18:37:36 +02:00
|
|
|
vty_out(vty, ", LAN id: %s.%02x", dyn->hostname,
|
2017-07-17 14:03:14 +02:00
|
|
|
adj->lanid[ISIS_SYS_ID_LEN]);
|
|
|
|
else
|
|
|
|
vty_out(vty, ", LAN id: %s.%02x",
|
|
|
|
sysid_print(adj->lanid),
|
|
|
|
adj->lanid[ISIS_SYS_ID_LEN]);
|
|
|
|
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
vty_out(vty, " LAN Priority: %u",
|
|
|
|
adj->prio[adj->level - 1]);
|
|
|
|
|
|
|
|
vty_out(vty, ", %s, DIS flaps: %u, Last: %s ago",
|
|
|
|
isis_disflag2string(
|
|
|
|
adj->dis_record[ISIS_LEVELS + level - 1]
|
|
|
|
.dis),
|
|
|
|
adj->dischanges[level - 1],
|
2017-07-22 14:52:33 +02:00
|
|
|
time2string(now - (adj->dis_record[ISIS_LEVELS
|
|
|
|
+ level - 1]
|
|
|
|
.last_dis_change)));
|
2017-07-17 14:03:14 +02:00
|
|
|
}
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
|
2017-06-21 15:21:00 +02:00
|
|
|
if (adj->area_address_count) {
|
2017-07-17 14:03:14 +02:00
|
|
|
vty_out(vty, " Area Address(es):\n");
|
2017-06-21 15:21:00 +02:00
|
|
|
for (unsigned int i = 0; i < adj->area_address_count;
|
|
|
|
i++) {
|
2017-07-17 14:03:14 +02:00
|
|
|
vty_out(vty, " %s\n",
|
2018-03-06 20:02:52 +01:00
|
|
|
isonet_print(adj->area_addresses[i]
|
|
|
|
.area_addr,
|
|
|
|
adj->area_addresses[i]
|
|
|
|
.addr_len));
|
2017-06-21 15:21:00 +02:00
|
|
|
}
|
2017-07-17 14:03:14 +02:00
|
|
|
}
|
2017-06-21 15:21:00 +02:00
|
|
|
if (adj->ipv4_address_count) {
|
2017-07-17 14:03:14 +02:00
|
|
|
vty_out(vty, " IPv4 Address(es):\n");
|
2017-06-21 15:21:00 +02:00
|
|
|
for (unsigned int i = 0; i < adj->ipv4_address_count;
|
|
|
|
i++)
|
|
|
|
vty_out(vty, " %s\n",
|
2018-03-06 20:02:52 +01:00
|
|
|
inet_ntoa(adj->ipv4_addresses[i]));
|
2017-07-17 14:03:14 +02:00
|
|
|
}
|
2017-06-21 15:21:00 +02:00
|
|
|
if (adj->ipv6_address_count) {
|
2017-07-17 14:03:14 +02:00
|
|
|
vty_out(vty, " IPv6 Address(es):\n");
|
2017-06-21 15:21:00 +02:00
|
|
|
for (unsigned int i = 0; i < adj->ipv6_address_count;
|
|
|
|
i++) {
|
|
|
|
char buf[INET6_ADDRSTRLEN];
|
|
|
|
inet_ntop(AF_INET6, &adj->ipv6_addresses[i],
|
|
|
|
buf, sizeof(buf));
|
|
|
|
vty_out(vty, " %s\n", buf);
|
2017-07-17 14:03:14 +02:00
|
|
|
}
|
|
|
|
}
|
2019-08-04 03:02:37 +02:00
|
|
|
for (ALL_LIST_ELEMENTS_RO(adj->adj_sids, anode, sra)) {
|
|
|
|
const char *adj_type;
|
|
|
|
const char *backup;
|
|
|
|
uint32_t sid;
|
|
|
|
|
|
|
|
switch (sra->adj->circuit->circ_type) {
|
|
|
|
case CIRCUIT_T_BROADCAST:
|
|
|
|
adj_type = "LAN Adjacency-SID";
|
|
|
|
sid = sra->u.ladj_sid->sid;
|
|
|
|
break;
|
|
|
|
case CIRCUIT_T_P2P:
|
|
|
|
adj_type = "Adjacency-SID";
|
|
|
|
sid = sra->u.adj_sid->sid;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
backup = (sra->type == ISIS_SR_LAN_BACKUP) ? " (backup)"
|
|
|
|
: "";
|
|
|
|
|
|
|
|
vty_out(vty, " %s %s%s: %u\n",
|
|
|
|
(sra->nexthop.family == AF_INET) ? "IPv4"
|
|
|
|
: "IPv6",
|
|
|
|
adj_type, backup, sid);
|
|
|
|
}
|
2017-07-17 14:03:14 +02:00
|
|
|
vty_out(vty, "\n");
|
2004-09-10 22:48:21 +02:00
|
|
|
}
|
2017-07-17 14:03:14 +02:00
|
|
|
return;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
void isis_adj_build_neigh_list(struct list *adjdb, struct list *list)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
struct isis_adjacency *adj;
|
|
|
|
struct listnode *node;
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
zlog_warn("isis_adj_build_neigh_list(): NULL list");
|
|
|
|
return;
|
2004-09-10 22:48:21 +02:00
|
|
|
}
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) {
|
|
|
|
if (!adj) {
|
|
|
|
zlog_warn("isis_adj_build_neigh_list(): NULL adj");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((adj->adj_state == ISIS_ADJ_UP
|
|
|
|
|| adj->adj_state == ISIS_ADJ_INITIALIZING))
|
|
|
|
listnode_add(list, adj->snpa);
|
|
|
|
}
|
|
|
|
return;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
void isis_adj_build_up_list(struct list *adjdb, struct list *list)
|
2003-12-23 09:09:43 +01:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
struct isis_adjacency *adj;
|
|
|
|
struct listnode *node;
|
|
|
|
|
|
|
|
if (adjdb == NULL) {
|
|
|
|
zlog_warn("isis_adj_build_up_list(): adjacency DB is empty");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!list) {
|
|
|
|
zlog_warn("isis_adj_build_up_list(): NULL list");
|
|
|
|
return;
|
2004-09-10 22:48:21 +02:00
|
|
|
}
|
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) {
|
|
|
|
if (!adj) {
|
|
|
|
zlog_warn("isis_adj_build_up_list(): NULL adj");
|
|
|
|
return;
|
|
|
|
}
|
2004-09-10 22:48:21 +02:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
if (adj->adj_state == ISIS_ADJ_UP)
|
|
|
|
listnode_add(list, adj);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
2003-12-23 09:09:43 +01:00
|
|
|
}
|
2017-04-27 13:56:41 +02:00
|
|
|
|
2017-07-17 14:03:14 +02:00
|
|
|
int isis_adj_usage2levels(enum isis_adj_usage usage)
|
2017-04-27 13:56:41 +02:00
|
|
|
{
|
2017-07-17 14:03:14 +02:00
|
|
|
switch (usage) {
|
|
|
|
case ISIS_ADJ_LEVEL1:
|
|
|
|
return IS_LEVEL_1;
|
|
|
|
case ISIS_ADJ_LEVEL2:
|
|
|
|
return IS_LEVEL_2;
|
|
|
|
case ISIS_ADJ_LEVEL1AND2:
|
|
|
|
return IS_LEVEL_1 | IS_LEVEL_2;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
2017-04-27 13:56:41 +02:00
|
|
|
}
|