isisd: rework BFD integration

Rewrite the BFD integration code to use the new library.

Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
This commit is contained in:
Igor Ryzhov 2021-05-06 14:44:05 +03:00
parent 03d39784e7
commit 13bf38300e
10 changed files with 96 additions and 412 deletions

View File

@ -105,7 +105,7 @@ struct isis_adjacency {
struct isis_circuit *circuit; /* back pointer */
uint16_t *mt_set; /* Topologies this adjacency is valid for */
unsigned int mt_count; /* Number of entries in mt_set */
struct bfd_session *bfd_session;
struct bfd_session_params *bfd_session;
struct list *adj_sids; /* Segment Routing Adj-SIDs. */
uint32_t snmp_idx;
struct listnode *snmp_list_node;

View File

@ -34,319 +34,32 @@
DEFINE_MTYPE_STATIC(ISISD, BFD_SESSION, "ISIS BFD Session");
struct bfd_session {
int family;
union g_addr dst_ip;
union g_addr src_ip;
int status;
};
static struct bfd_session *bfd_session_new(int family, union g_addr *dst_ip,
union g_addr *src_ip)
static void adj_bfd_cb(struct bfd_session_params *bsp,
const struct bfd_session_status *bss, void *arg)
{
struct bfd_session *rv;
rv = XCALLOC(MTYPE_BFD_SESSION, sizeof(*rv));
rv->family = family;
rv->dst_ip = *dst_ip;
rv->src_ip = *src_ip;
return rv;
}
static void bfd_session_free(struct bfd_session **session)
{
if (!*session)
return;
XFREE(MTYPE_BFD_SESSION, *session);
}
static bool bfd_session_same(const struct bfd_session *session, int family,
const union g_addr *src, const union g_addr *dst)
{
if (session->family != family)
return false;
switch (session->family) {
case AF_INET:
if (!IPV4_ADDR_SAME(&session->dst_ip.ipv4, &dst->ipv4))
return false;
if (!IPV4_ADDR_SAME(&session->src_ip.ipv4, &src->ipv4))
return false;
break;
case AF_INET6:
if (!IPV6_ADDR_SAME(&session->dst_ip.ipv6, &dst->ipv6))
return false;
if (!IPV6_ADDR_SAME(&session->src_ip.ipv6, &src->ipv6))
return false;
break;
default:
flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address-family: %u",
__func__, session->family);
exit(1);
}
return true;
}
static void bfd_adj_event(struct isis_adjacency *adj, struct prefix *dst,
int new_status)
{
if (!adj->bfd_session) {
if (IS_DEBUG_BFD)
zlog_debug(
"ISIS-BFD: Ignoring update for adjacency with %s, could not find bfd session on the adjacency",
isis_adj_name(adj));
return;
}
if (adj->bfd_session->family != dst->family) {
if (IS_DEBUG_BFD)
zlog_debug(
"ISIS-BFD: Ignoring update for adjacency with %s, address family does not match the family on the adjacency",
isis_adj_name(adj));
return;
}
switch (adj->bfd_session->family) {
case AF_INET:
if (!IPV4_ADDR_SAME(&adj->bfd_session->dst_ip.ipv4,
&dst->u.prefix4)) {
if (IS_DEBUG_BFD)
zlog_debug(
"ISIS-BFD: Ignoring update for adjacency with %s, IPv4 address does not match",
isis_adj_name(adj));
return;
}
break;
case AF_INET6:
if (!IPV6_ADDR_SAME(&adj->bfd_session->dst_ip.ipv6,
&dst->u.prefix6)) {
if (IS_DEBUG_BFD)
zlog_debug(
"ISIS-BFD: Ignoring update for adjacency with %s, IPv6 address does not match",
isis_adj_name(adj));
return;
}
break;
default:
flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address-family: %u",
__func__, adj->bfd_session->family);
exit(1);
}
int old_status = adj->bfd_session->status;
BFD_SET_CLIENT_STATUS(adj->bfd_session->status, new_status);
if (old_status == new_status) {
if (IS_DEBUG_BFD)
zlog_debug(
"ISIS-BFD: Ignoring update for adjacency with %s, new status matches current known status",
isis_adj_name(adj));
return;
}
if (IS_DEBUG_BFD) {
char dst_str[INET6_ADDRSTRLEN];
inet_ntop(adj->bfd_session->family, &adj->bfd_session->dst_ip,
dst_str, sizeof(dst_str));
zlog_debug("ISIS-BFD: Peer %s on %s changed from %s to %s",
dst_str, adj->circuit->interface->name,
bfd_get_status_str(old_status),
bfd_get_status_str(new_status));
}
if (old_status != BFD_STATUS_UP
|| new_status != BFD_STATUS_DOWN) {
return;
}
adj->circuit->area->bfd_signalled_down = true;
isis_adj_state_change(&adj, ISIS_ADJ_DOWN, "bfd session went down");
}
static int isis_bfd_interface_dest_update(ZAPI_CALLBACK_ARGS)
{
struct interface *ifp;
struct prefix dst_ip, src_ip;
int status;
ifp = bfd_get_peer_info(zclient->ibuf, &dst_ip, &src_ip, &status, NULL,
vrf_id);
if (!ifp || (dst_ip.family != AF_INET && dst_ip.family != AF_INET6))
return 0;
if (IS_DEBUG_BFD) {
char dst_buf[INET6_ADDRSTRLEN];
inet_ntop(dst_ip.family, &dst_ip.u.prefix, dst_buf,
sizeof(dst_buf));
zlog_debug("ISIS-BFD: Received update for %s on %s: Changed state to %s",
dst_buf, ifp->name, bfd_get_status_str(status));
}
struct isis_circuit *circuit = circuit_scan_by_ifp(ifp);
if (!circuit) {
if (IS_DEBUG_BFD)
zlog_debug(
"ISIS-BFD: Ignoring update, could not find circuit");
return 0;
}
if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
struct list *adjdb = circuit->u.bc.adjdb[level - 1];
struct listnode *node, *nnode;
struct isis_adjacency *adj;
for (ALL_LIST_ELEMENTS(adjdb, node, nnode, adj))
bfd_adj_event(adj, &dst_ip, status);
}
} else if (circuit->circ_type == CIRCUIT_T_P2P) {
if (circuit->u.p2p.neighbor) {
bfd_adj_event(circuit->u.p2p.neighbor,
&dst_ip, status);
}
}
return 0;
}
static int isis_bfd_nbr_replay(ZAPI_CALLBACK_ARGS)
{
bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id);
struct listnode *anode;
struct isis_area *area;
struct isis *isis = NULL;
isis = isis_lookup_by_vrfid(vrf_id);
if (isis == NULL) {
zlog_warn(" %s : ISIS routing instance not found", __func__);
return -1;
}
struct isis_adjacency *adj = arg;
if (IS_DEBUG_BFD)
zlog_debug("ISIS-BFD: Got neighbor replay request, resending neighbors.");
zlog_debug(
"ISIS-BFD: BFD changed status for adjacency %s old %s new %s",
isis_adj_name(adj),
bfd_get_status_str(bss->previous_state),
bfd_get_status_str(bss->state));
for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) {
struct listnode *cnode;
struct isis_circuit *circuit;
for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit))
isis_bfd_circuit_cmd(circuit, ZEBRA_BFD_DEST_UPDATE);
if (bss->state == BFD_STATUS_DOWN
&& bss->previous_state == BFD_STATUS_UP) {
adj->circuit->area->bfd_signalled_down = true;
isis_adj_state_change(&adj, ISIS_ADJ_DOWN,
"bfd session went down");
}
if (IS_DEBUG_BFD)
zlog_debug("ISIS-BFD: Done with replay.");
return 0;
}
static void (*orig_zebra_connected)(struct zclient *);
static void isis_bfd_zebra_connected(struct zclient *zclient)
{
if (orig_zebra_connected)
orig_zebra_connected(zclient);
bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT);
}
static void bfd_debug(int family, union g_addr *dst, union g_addr *src,
const char *interface, int command)
{
if (!(IS_DEBUG_BFD))
return;
char dst_str[INET6_ADDRSTRLEN];
char src_str[INET6_ADDRSTRLEN];
inet_ntop(family, dst, dst_str, sizeof(dst_str));
inet_ntop(family, src, src_str, sizeof(src_str));
const char *command_str;
switch (command) {
case ZEBRA_BFD_DEST_REGISTER:
command_str = "Register";
break;
case ZEBRA_BFD_DEST_DEREGISTER:
command_str = "Deregister";
break;
case ZEBRA_BFD_DEST_UPDATE:
command_str = "Update";
break;
default:
command_str = "Unknown-Cmd";
break;
}
zlog_debug("ISIS-BFD: %s peer %s on %s (src %s)",
command_str, dst_str, interface, src_str);
}
static void bfd_command(int command, struct bfd_info *bfd_info, int family,
const void *dst_ip, const void *src_ip,
const char *if_name)
{
struct bfd_session_arg args = {};
size_t addrlen;
args.cbit = 1;
args.family = family;
args.vrf_id = VRF_DEFAULT;
args.command = command;
args.bfd_info = bfd_info;
if (args.bfd_info) {
args.min_rx = bfd_info->required_min_rx;
args.min_tx = bfd_info->desired_min_tx;
args.detection_multiplier = bfd_info->detect_mult;
if (bfd_info->profile[0]) {
args.profilelen = strlen(bfd_info->profile);
strlcpy(args.profile, bfd_info->profile,
sizeof(args.profile));
}
}
addrlen = family == AF_INET ? sizeof(struct in_addr)
: sizeof(struct in6_addr);
memcpy(&args.dst, dst_ip, addrlen);
if (src_ip)
memcpy(&args.src, src_ip, addrlen);
if (if_name) {
strlcpy(args.ifname, if_name, sizeof(args.ifname));
args.ifnamelen = strlen(args.ifname);
}
zclient_bfd_command(zclient, &args);
}
static void bfd_handle_adj_down(struct isis_adjacency *adj)
{
if (!adj->bfd_session)
return;
bfd_debug(adj->bfd_session->family, &adj->bfd_session->dst_ip,
&adj->bfd_session->src_ip, adj->circuit->interface->name,
ZEBRA_BFD_DEST_DEREGISTER);
bfd_command(ZEBRA_BFD_DEST_DEREGISTER, NULL, adj->bfd_session->family,
&adj->bfd_session->dst_ip, &adj->bfd_session->src_ip,
(adj->circuit->interface) ? adj->circuit->interface->name
: NULL);
bfd_session_free(&adj->bfd_session);
bfd_sess_free(&adj->bfd_session);
}
static void bfd_handle_adj_up(struct isis_adjacency *adj, int command)
static void bfd_handle_adj_up(struct isis_adjacency *adj)
{
struct isis_circuit *circuit = adj->circuit;
int family;
@ -355,10 +68,10 @@ static void bfd_handle_adj_up(struct isis_adjacency *adj, int command)
struct list *local_ips;
struct prefix *local_ip;
if (!circuit->bfd_info) {
if (!circuit->bfd_config.enabled) {
if (IS_DEBUG_BFD)
zlog_debug(
"ISIS-BFD: skipping BFD initialization on adjacency with %s because there is no bfd_info in the circuit",
"ISIS-BFD: skipping BFD initialization on adjacency with %s because BFD is not enabled for the circuit",
isis_adj_name(adj));
goto out;
}
@ -407,28 +120,20 @@ static void bfd_handle_adj_up(struct isis_adjacency *adj, int command)
} else
goto out;
if (adj->bfd_session) {
if (bfd_session_same(adj->bfd_session, family, &src_ip,
&dst_ip))
bfd_handle_adj_down(adj);
}
if (!adj->bfd_session) {
if (IS_DEBUG_BFD)
zlog_debug(
"ISIS-BFD: creating BFD session for adjacency with %s",
isis_adj_name(adj));
adj->bfd_session = bfd_session_new(family, &dst_ip, &src_ip);
}
bfd_debug(adj->bfd_session->family, &adj->bfd_session->dst_ip,
&adj->bfd_session->src_ip, circuit->interface->name, command);
bfd_command(command, circuit->bfd_info, family,
&adj->bfd_session->dst_ip, &adj->bfd_session->src_ip,
(adj->circuit->interface) ? adj->circuit->interface->name
: NULL);
if (adj->bfd_session == NULL)
adj->bfd_session = bfd_sess_new(adj_bfd_cb, adj);
bfd_sess_set_timers(adj->bfd_session, BFD_DEF_DETECT_MULT,
BFD_DEF_MIN_RX, BFD_DEF_MIN_TX);
if (family == AF_INET)
bfd_sess_set_ipv4_addrs(adj->bfd_session, &src_ip.ipv4,
&dst_ip.ipv4);
else
bfd_sess_set_ipv6_addrs(adj->bfd_session, &src_ip.ipv6,
&dst_ip.ipv6);
bfd_sess_set_interface(adj->bfd_session, adj->circuit->interface->name);
bfd_sess_set_profile(adj->bfd_session, circuit->bfd_config.profile);
bfd_sess_install(adj->bfd_session);
return;
out:
bfd_handle_adj_down(adj);
@ -437,23 +142,21 @@ out:
static int bfd_handle_adj_state_change(struct isis_adjacency *adj)
{
if (adj->adj_state == ISIS_ADJ_UP)
bfd_handle_adj_up(adj, ZEBRA_BFD_DEST_REGISTER);
bfd_handle_adj_up(adj);
else
bfd_handle_adj_down(adj);
return 0;
}
static void bfd_adj_cmd(struct isis_adjacency *adj, int command)
static void bfd_adj_cmd(struct isis_adjacency *adj)
{
if (adj->adj_state == ISIS_ADJ_UP
&& command != ZEBRA_BFD_DEST_DEREGISTER) {
bfd_handle_adj_up(adj, command);
} else {
if (adj->adj_state == ISIS_ADJ_UP && adj->circuit->bfd_config.enabled)
bfd_handle_adj_up(adj);
else
bfd_handle_adj_down(adj);
}
}
void isis_bfd_circuit_cmd(struct isis_circuit *circuit, int command)
void isis_bfd_circuit_cmd(struct isis_circuit *circuit)
{
switch (circuit->circ_type) {
case CIRCUIT_T_BROADCAST:
@ -464,45 +167,18 @@ void isis_bfd_circuit_cmd(struct isis_circuit *circuit, int command)
struct isis_adjacency *adj;
for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj))
bfd_adj_cmd(adj, command);
bfd_adj_cmd(adj);
}
break;
case CIRCUIT_T_P2P:
if (circuit->u.p2p.neighbor)
bfd_adj_cmd(circuit->u.p2p.neighbor, command);
bfd_adj_cmd(circuit->u.p2p.neighbor);
break;
default:
break;
}
}
void isis_bfd_circuit_param_set(struct isis_circuit *circuit, uint32_t min_rx,
uint32_t min_tx, uint32_t detect_mult,
const char *profile, int defaults)
{
int command = 0;
bfd_set_param(&circuit->bfd_info, min_rx, min_tx, detect_mult, profile,
defaults, &command);
if (command)
isis_bfd_circuit_cmd(circuit, command);
}
#ifdef FABRICD
static int bfd_circuit_write_settings(struct isis_circuit *circuit,
struct vty *vty)
{
struct bfd_info *bfd_info = circuit->bfd_info;
if (!bfd_info)
return 0;
vty_out(vty, " %s bfd\n", PROTO_NAME);
return 1;
}
#endif
static int bfd_handle_adj_ip_enabled(struct isis_adjacency *adj, int family)
{
@ -515,7 +191,7 @@ static int bfd_handle_adj_ip_enabled(struct isis_adjacency *adj, int family)
if (adj->adj_state != ISIS_ADJ_UP)
return 0;
bfd_handle_adj_up(adj, ZEBRA_BFD_DEST_REGISTER);
bfd_handle_adj_up(adj);
return 0;
}
@ -535,26 +211,17 @@ static int bfd_handle_circuit_add_addr(struct isis_circuit *circuit)
if (adj->adj_state != ISIS_ADJ_UP)
continue;
bfd_handle_adj_up(adj, ZEBRA_BFD_DEST_REGISTER);
bfd_handle_adj_up(adj);
}
return 0;
}
void isis_bfd_init(void)
void isis_bfd_init(struct thread_master *tm)
{
bfd_gbl_init();
bfd_protocol_integration_init(zclient, tm);
orig_zebra_connected = zclient->zebra_connected;
zclient->zebra_connected = isis_bfd_zebra_connected;
zclient->interface_bfd_dest_update = isis_bfd_interface_dest_update;
zclient->bfd_dest_replay = isis_bfd_nbr_replay;
hook_register(isis_adj_state_change_hook,
bfd_handle_adj_state_change);
#ifdef FABRICD
hook_register(isis_circuit_config_write,
bfd_circuit_write_settings);
#endif
hook_register(isis_adj_state_change_hook, bfd_handle_adj_state_change);
hook_register(isis_adj_ip_enabled_hook, bfd_handle_adj_ip_enabled);
hook_register(isis_circuit_add_addr_hook, bfd_handle_circuit_add_addr);
}

View File

@ -20,12 +20,10 @@
#define ISIS_BFD_H
struct isis_circuit;
struct thread_master;
void isis_bfd_circuit_cmd(struct isis_circuit *circuit, int command);
void isis_bfd_circuit_param_set(struct isis_circuit *circuit, uint32_t min_rx,
uint32_t min_tx, uint32_t detect_mult,
const char *profile, int defaults);
void isis_bfd_init(void);
void isis_bfd_circuit_cmd(struct isis_circuit *circuit);
void isis_bfd_init(struct thread_master *tm);
#endif

View File

@ -205,6 +205,7 @@ void isis_circuit_del(struct isis_circuit *circuit)
isis_lfa_excluded_ifaces_clear(circuit, ISIS_LEVEL1);
isis_lfa_excluded_ifaces_clear(circuit, ISIS_LEVEL2);
XFREE(MTYPE_TMP, circuit->bfd_config.profile);
XFREE(MTYPE_ISIS_CIRCUIT, circuit->tag);
/* and lastly the circuit itself */
@ -1282,6 +1283,10 @@ static int isis_interface_config_write(struct vty *vty)
circuit->passwd.passwd);
write++;
}
if (circuit->bfd_config.enabled) {
vty_out(vty, " " PROTO_NAME " bfd\n");
write++;
}
write += hook_call(isis_circuit_config_write,
circuit, vty);
}

View File

@ -70,8 +70,6 @@ struct isis_p2p_info {
struct thread *t_send_p2p_hello; /* send P2P IIHs in this thread */
};
struct bfd_info;
struct isis_circuit_arg {
int level;
struct isis_circuit *circuit;
@ -144,7 +142,10 @@ struct isis_circuit {
#define ISIS_CIRCUIT_FLAPPED_AFTER_SPF 0x01
uint8_t flags;
bool disable_threeway_adj;
struct bfd_info *bfd_info;
struct {
bool enabled;
char *profile;
} bfd_config;
struct ldp_sync_info *ldp_sync_info;
bool lfa_protection[ISIS_LEVELS];
bool rlfa_protection[ISIS_LEVELS];

View File

@ -269,7 +269,7 @@ int main(int argc, char **argv, char **envp)
isis_global_instance_create(VRF_DEFAULT_NAME);
isis_zebra_init(master, instance);
isis_bfd_init();
isis_bfd_init(master);
isis_ldp_sync_init();
fabricd_init();

View File

@ -2626,23 +2626,9 @@ void lib_interface_isis_bfd_monitoring_apply_finish(
struct nb_cb_apply_finish_args *args)
{
struct isis_circuit *circuit;
bool enabled;
const char *profile = NULL;
circuit = nb_running_get_entry(args->dnode, NULL, true);
enabled = yang_dnode_get_bool(args->dnode, "./enabled");
if (yang_dnode_exists(args->dnode, "./profile"))
profile = yang_dnode_get_string(args->dnode, "./profile");
if (enabled) {
isis_bfd_circuit_param_set(circuit, BFD_DEF_MIN_RX,
BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT,
profile, true);
} else {
isis_bfd_circuit_cmd(circuit, ZEBRA_BFD_DEST_DEREGISTER);
bfd_info_free(&circuit->bfd_info);
}
isis_bfd_circuit_cmd(circuit);
}
/*
@ -2651,7 +2637,14 @@ void lib_interface_isis_bfd_monitoring_apply_finish(
int lib_interface_isis_bfd_monitoring_enabled_modify(
struct nb_cb_modify_args *args)
{
/* Everything done in apply_finish */
struct isis_circuit *circuit;
if (args->event != NB_EV_APPLY)
return NB_OK;
circuit = nb_running_get_entry(args->dnode, NULL, true);
circuit->bfd_config.enabled = yang_dnode_get_bool(args->dnode, NULL);
return NB_OK;
}
@ -2661,14 +2654,30 @@ int lib_interface_isis_bfd_monitoring_enabled_modify(
int lib_interface_isis_bfd_monitoring_profile_modify(
struct nb_cb_modify_args *args)
{
/* Everything done in apply_finish */
struct isis_circuit *circuit;
if (args->event != NB_EV_APPLY)
return NB_OK;
circuit = nb_running_get_entry(args->dnode, NULL, true);
XFREE(MTYPE_TMP, circuit->bfd_config.profile);
circuit->bfd_config.profile =
XSTRDUP(MTYPE_TMP, yang_dnode_get_string(args->dnode, NULL));
return NB_OK;
}
int lib_interface_isis_bfd_monitoring_profile_destroy(
struct nb_cb_destroy_args *args)
{
/* Everything done in apply_finish */
struct isis_circuit *circuit;
if (args->event != NB_EV_APPLY)
return NB_OK;
circuit = nb_running_get_entry(args->dnode, NULL, true);
XFREE(MTYPE_TMP, circuit->bfd_config.profile);
return NB_OK;
}

View File

@ -319,13 +319,11 @@ DEFUN (isis_bfd,
if (!circuit)
return CMD_ERR_NO_MATCH;
if (circuit->bfd_info
&& CHECK_FLAG(circuit->bfd_info->flags, BFD_FLAG_PARAM_CFG)) {
if (circuit->bfd_config.enabled)
return CMD_SUCCESS;
}
isis_bfd_circuit_param_set(circuit, BFD_DEF_MIN_RX, BFD_DEF_MIN_TX,
BFD_DEF_DETECT_MULT, NULL, true);
circuit->bfd_config.enabled = true;
isis_bfd_circuit_cmd(circuit);
return CMD_SUCCESS;
}
@ -343,11 +341,12 @@ DEFUN (no_isis_bfd,
if (!circuit)
return CMD_ERR_NO_MATCH;
if (!circuit->bfd_info)
if (!circuit->bfd_config.enabled)
return CMD_SUCCESS;
isis_bfd_circuit_cmd(circuit, ZEBRA_BFD_DEST_DEREGISTER);
bfd_info_free(&circuit->bfd_info);
circuit->bfd_config.enabled = false;
isis_bfd_circuit_cmd(circuit);
return CMD_SUCCESS;
}

View File

@ -37,6 +37,7 @@
#include "nexthop.h"
#include "vrf.h"
#include "libfrr.h"
#include "bfd.h"
#include "isisd/isis_constants.h"
#include "isisd/isis_common.h"
@ -730,6 +731,7 @@ static void isis_zebra_connected(struct zclient *zclient)
zclient_register_opaque(zclient, LDP_RLFA_LABELS);
zclient_register_opaque(zclient, LDP_IGP_SYNC_IF_STATE_UPDATE);
zclient_register_opaque(zclient, LDP_IGP_SYNC_ANNOUNCE_UPDATE);
bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT);
}
/*

View File

@ -41,6 +41,7 @@
#include "vrf.h"
#include "spf_backoff.h"
#include "lib/northbound_cli.h"
#include "bfd.h"
#include "isisd/isis_constants.h"
#include "isisd/isis_common.h"
@ -664,6 +665,8 @@ void isis_terminate()
struct isis *isis;
struct listnode *node, *nnode;
bfd_protocol_integration_set_shutdown(true);
if (listcount(im->isis) == 0)
return;