// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * EIGRP VTY Interface.
 * Copyright (C) 2013-2016
 * Authors:
 *   Donnie Savage
 *   Jan Janovic
 *   Matej Perina
 *   Peter Orsag
 *   Peter Paluch
 *   Frantisek Gazo
 *   Tomas Hvorkovy
 *   Martin Kontsek
 *   Lukas Koribsky
 */

#include <zebra.h>

#include "memory.h"
#include "frrevent.h"
#include "prefix.h"
#include "table.h"
#include "vty.h"
#include "command.h"
#include "plist.h"
#include "log.h"
#include "zclient.h"
#include "keychain.h"
#include "linklist.h"
#include "distribute.h"

#include "eigrpd/eigrp_structs.h"
#include "eigrpd/eigrpd.h"
#include "eigrpd/eigrp_interface.h"
#include "eigrpd/eigrp_neighbor.h"
#include "eigrpd/eigrp_packet.h"
#include "eigrpd/eigrp_zebra.h"
#include "eigrpd/eigrp_vty.h"
#include "eigrpd/eigrp_network.h"
#include "eigrpd/eigrp_dump.h"
#include "eigrpd/eigrp_const.h"

#include "eigrpd/eigrp_vty_clippy.c"

static void eigrp_vty_display_prefix_entry(struct vty *vty, struct eigrp *eigrp,
					   struct eigrp_prefix_descriptor *pe,
					   bool all)
{
	bool first = true;
	struct eigrp_route_descriptor *te;
	struct listnode *node;

	for (ALL_LIST_ELEMENTS_RO(pe->entries, node, te)) {
		if (all
		    || (((te->flags & EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG)
			 == EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG)
			|| ((te->flags & EIGRP_ROUTE_DESCRIPTOR_FSUCCESSOR_FLAG)
			    == EIGRP_ROUTE_DESCRIPTOR_FSUCCESSOR_FLAG))) {
			show_ip_eigrp_route_descriptor(vty, eigrp, te, &first);
			first = false;
		}
	}
}

static struct eigrp *eigrp_vty_get_eigrp(struct vty *vty, const char *vrf_name)
{
	struct vrf *vrf;

	if (vrf_name)
		vrf = vrf_lookup_by_name(vrf_name);
	else
		vrf = vrf_lookup_by_id(VRF_DEFAULT);

	if (!vrf) {
		vty_out(vty, "VRF %s specified does not exist",
			vrf_name ? vrf_name : VRF_DEFAULT_NAME);
		return NULL;
	}

	return eigrp_lookup(vrf->vrf_id);
}

static void eigrp_topology_helper(struct vty *vty, struct eigrp *eigrp,
				  const char *all)
{
	struct eigrp_prefix_descriptor *tn;
	struct route_node *rn;

	show_ip_eigrp_topology_header(vty, eigrp);

	for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) {
		if (!rn->info)
			continue;

		tn = rn->info;
		eigrp_vty_display_prefix_entry(vty, eigrp, tn,
					       all ? true : false);
	}
}

DEFPY (show_ip_eigrp_topology_all,
       show_ip_eigrp_topology_all_cmd,
       "show ip eigrp [vrf NAME] topology [all-links$all]",
       SHOW_STR
       IP_STR
       "IP-EIGRP show commands\n"
       VRF_CMD_HELP_STR
       "IP-EIGRP topology\n"
       "Show all links in topology table\n")
{
	struct eigrp *eigrp;

	if (vrf && strncmp(vrf, "all", sizeof("all")) == 0) {
		struct vrf *v;

		RB_FOREACH (v, vrf_name_head, &vrfs_by_name) {
			eigrp = eigrp_lookup(v->vrf_id);
			if (!eigrp)
				continue;

			vty_out(vty, "VRF %s:\n", v->name);

			eigrp_topology_helper(vty, eigrp, all);
		}
	} else {
		eigrp = eigrp_vty_get_eigrp(vty, vrf);
		if (eigrp == NULL) {
			vty_out(vty, " EIGRP Routing Process not enabled\n");
			return CMD_SUCCESS;
		}

		eigrp_topology_helper(vty, eigrp, all);
	}

	return CMD_SUCCESS;
}

DEFPY (show_ip_eigrp_topology,
       show_ip_eigrp_topology_cmd,
       "show ip eigrp [vrf NAME] topology <A.B.C.D$address|A.B.C.D/M$prefix>",
       SHOW_STR
       IP_STR
       "IP-EIGRP show commands\n"
       VRF_CMD_HELP_STR
       "IP-EIGRP topology\n"
       "For a specific address\n"
       "For a specific prefix\n")
{
	struct eigrp *eigrp;
	struct eigrp_prefix_descriptor *tn;
	struct route_node *rn;
	struct prefix cmp;

	if (vrf && strncmp(vrf, "all", sizeof("all")) == 0) {
		vty_out(vty, "Specifying vrf `all` for a particular address/prefix makes no sense\n");
		return CMD_SUCCESS;
	}

	eigrp = eigrp_vty_get_eigrp(vty, vrf);
	if (eigrp == NULL) {
		vty_out(vty, " EIGRP Routing Process not enabled\n");
		return CMD_SUCCESS;
	}

	show_ip_eigrp_topology_header(vty, eigrp);

	if (address_str)
		prefix_str = address_str;

	if (str2prefix(prefix_str, &cmp) < 0) {
		vty_out(vty, "%% Malformed address\n");
		return CMD_WARNING;
	}

	rn = route_node_match(eigrp->topology_table, &cmp);
	if (!rn) {
		vty_out(vty, "%% Network not in table\n");
		return CMD_WARNING;
	}

	if (!rn->info) {
		vty_out(vty, "%% Network not in table\n");
		route_unlock_node(rn);
		return CMD_WARNING;
	}

	tn = rn->info;
	eigrp_vty_display_prefix_entry(vty, eigrp, tn, argc == 5);

	route_unlock_node(rn);
	return CMD_SUCCESS;
}

static void eigrp_interface_helper(struct vty *vty, struct eigrp *eigrp,
				   const char *ifname, const char *detail)
{
	struct eigrp_interface *ei;

	if (!ifname)
		show_ip_eigrp_interface_header(vty, eigrp);

	frr_each (eigrp_interface_hash, &eigrp->eifs, ei) {
		if (!ifname || strcmp(ei->ifp->name, ifname) == 0) {
			show_ip_eigrp_interface_sub(vty, eigrp, ei);
			if (detail)
				show_ip_eigrp_interface_detail(vty, eigrp, ei);
		}
	}
}

DEFPY (show_ip_eigrp_interfaces,
       show_ip_eigrp_interfaces_cmd,
       "show ip eigrp [vrf NAME] interfaces [IFNAME] [detail]$detail",
       SHOW_STR
       IP_STR
       "IP-EIGRP show commands\n"
       VRF_CMD_HELP_STR
       "IP-EIGRP interfaces\n"
       "Interface name to look at\n"
       "Detailed information\n")
{
	struct eigrp *eigrp;

	if (vrf && strncmp(vrf, "all", sizeof("all")) == 0) {
		struct vrf *v;

		RB_FOREACH (v, vrf_name_head, &vrfs_by_name) {
			eigrp = eigrp_lookup(v->vrf_id);
			if (!eigrp)
				continue;

			vty_out(vty, "VRF %s:\n", v->name);

			eigrp_interface_helper(vty, eigrp, ifname, detail);
		}
	} else {
		eigrp = eigrp_vty_get_eigrp(vty, vrf);
		if (eigrp == NULL) {
			vty_out(vty, "EIGRP Routing Process not enabled\n");
			return CMD_SUCCESS;
		}

		eigrp_interface_helper(vty, eigrp, ifname, detail);
	}


	return CMD_SUCCESS;
}

static void eigrp_neighbors_helper(struct vty *vty, struct eigrp *eigrp,
				   const char *ifname, const char *detail)
{
	struct eigrp_interface *ei;
	struct eigrp_neighbor *nbr;

	show_ip_eigrp_neighbor_header(vty, eigrp);

	frr_each (eigrp_interface_hash, &eigrp->eifs, ei) {
		if (!ifname || strcmp(ei->ifp->name, ifname) == 0) {
			frr_each (eigrp_nbr_hash, &ei->nbr_hash_head, nbr) {
				if (detail || (nbr->state == EIGRP_NEIGHBOR_UP))
					show_ip_eigrp_neighbor_sub(vty, nbr,
								   !!detail);
			}
		}
	}
}

DEFPY (show_ip_eigrp_neighbors,
       show_ip_eigrp_neighbors_cmd,
       "show ip eigrp [vrf NAME] neighbors [IFNAME] [detail]$detail",
       SHOW_STR
       IP_STR
       "IP-EIGRP show commands\n"
       VRF_CMD_HELP_STR
       "IP-EIGRP neighbors\n"
       "Interface to show on\n"
       "Detailed Information\n")
{
	struct eigrp *eigrp;

	if (vrf && strncmp(vrf, "all", sizeof("all")) == 0) {
		struct vrf *tvrf;

		RB_FOREACH (tvrf, vrf_name_head, &vrfs_by_name) {
			eigrp = eigrp_lookup(tvrf->vrf_id);
			if (!eigrp)
				continue;

			vty_out(vty, "VRF %s:\n", tvrf->name);

			eigrp_neighbors_helper(vty, eigrp, ifname, detail);
		}
	} else {
		eigrp = eigrp_vty_get_eigrp(vty, vrf);
		if (eigrp == NULL) {
			vty_out(vty, " EIGRP Routing Process not enabled\n");
			return CMD_SUCCESS;
		}

		eigrp_neighbors_helper(vty, eigrp, ifname, detail);
	}

	return CMD_SUCCESS;
}

/*
 * Execute hard restart for all neighbors
 */
DEFPY (clear_ip_eigrp_neighbors,
       clear_ip_eigrp_neighbors_cmd,
       "clear ip eigrp [vrf NAME] neighbors",
       CLEAR_STR
       IP_STR
       "Clear IP-EIGRP\n"
       VRF_CMD_HELP_STR
       "Clear IP-EIGRP neighbors\n")
{
	struct eigrp *eigrp;
	struct eigrp_interface *ei;
	struct eigrp_neighbor *nbr;

	/* Check if eigrp process is enabled */
	eigrp = eigrp_vty_get_eigrp(vty, vrf);
	if (eigrp == NULL) {
		vty_out(vty, " EIGRP Routing Process not enabled\n");
		return CMD_SUCCESS;
	}

	/* iterate over all eigrp interfaces */
	frr_each (eigrp_interface_hash, &eigrp->eifs, ei) {
		/* send Goodbye Hello */
		eigrp_hello_send(ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN, NULL);

		/* iterate over all neighbors on eigrp interface */
		frr_each_safe (eigrp_nbr_hash, &ei->nbr_hash_head, nbr) {
			if (nbr->state != EIGRP_NEIGHBOR_DOWN) {
				zlog_debug(
					"Neighbor %pI4 (%s) is down: manually cleared",
					&nbr->src,
					ifindex2ifname(nbr->ei->ifp->ifindex,
						       eigrp->vrf_id));
				vty_time_print(vty, 0);
				vty_out(vty,
					"Neighbor %pI4 (%s) is down: manually cleared\n",
					&nbr->src,
					ifindex2ifname(nbr->ei->ifp->ifindex,
						       eigrp->vrf_id));

				/* set neighbor to DOWN */
				nbr->state = EIGRP_NEIGHBOR_DOWN;
				/* delete neighbor */
				eigrp_nbr_delete(nbr);
			}
		}
	}

	return CMD_SUCCESS;
}

/*
 * Execute hard restart for all neighbors on interface
 */
DEFPY (clear_ip_eigrp_neighbors_int,
       clear_ip_eigrp_neighbors_int_cmd,
       "clear ip eigrp [vrf NAME] neighbors IFNAME",
       CLEAR_STR
       IP_STR
       "Clear IP-EIGRP\n"
       VRF_CMD_HELP_STR
       "Clear IP-EIGRP neighbors\n"
       "Interface's name\n")
{
	struct eigrp *eigrp;
	struct eigrp_interface *ei;
	struct eigrp_neighbor *nbr;

	/* Check if eigrp process is enabled */
	eigrp = eigrp_vty_get_eigrp(vty, vrf);
	if (eigrp == NULL) {
		vty_out(vty, " EIGRP Routing Process not enabled\n");
		return CMD_SUCCESS;
	}

	/* lookup interface by specified name */
	ei = eigrp_if_lookup_by_name(eigrp, ifname);
	if (ei == NULL) {
		vty_out(vty, " Interface (%s) doesn't exist\n", ifname);
		return CMD_WARNING;
	}

	/* send Goodbye Hello */
	eigrp_hello_send(ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN, NULL);

	/* iterate over all neighbors on eigrp interface */
	frr_each_safe (eigrp_nbr_hash, &ei->nbr_hash_head, nbr) {
		if (nbr->state != EIGRP_NEIGHBOR_DOWN) {
			zlog_debug(
				"Neighbor %pI4 (%s) is down: manually cleared",
				&nbr->src,
				ifindex2ifname(nbr->ei->ifp->ifindex,
					       eigrp->vrf_id));
			vty_time_print(vty, 0);
			vty_out(vty,
				"Neighbor %pI4 (%s) is down: manually cleared\n",
				&nbr->src,
				ifindex2ifname(nbr->ei->ifp->ifindex,
					       eigrp->vrf_id));

			/* set neighbor to DOWN */
			nbr->state = EIGRP_NEIGHBOR_DOWN;
			/* delete neighbor */
			eigrp_nbr_delete(nbr);
		}
	}

	return CMD_SUCCESS;
}

/*
 * Execute hard restart for neighbor specified by IP
 */
DEFPY (clear_ip_eigrp_neighbors_IP,
       clear_ip_eigrp_neighbors_IP_cmd,
       "clear ip eigrp [vrf NAME] neighbors A.B.C.D$nbr_addr",
       CLEAR_STR
       IP_STR
       "Clear IP-EIGRP\n"
       VRF_CMD_HELP_STR
       "Clear IP-EIGRP neighbors\n"
       "IP-EIGRP neighbor address\n")
{
	struct eigrp *eigrp;
	struct eigrp_neighbor *nbr;

	/* Check if eigrp process is enabled */
	eigrp = eigrp_vty_get_eigrp(vty, vrf);
	if (eigrp == NULL) {
		vty_out(vty, " EIGRP Routing Process not enabled\n");
		return CMD_SUCCESS;
	}

	/* lookup neighbor in whole process */
	nbr = eigrp_nbr_lookup_by_addr_process(eigrp, nbr_addr);

	/* if neighbor doesn't exists, notify user and exit */
	if (nbr == NULL) {
		vty_out(vty, "Neighbor with entered address doesn't exists.\n");
		return CMD_WARNING;
	}

	/* execute hard reset on neighbor */
	eigrp_nbr_hard_restart(nbr, vty);

	return CMD_SUCCESS;
}

/*
 * Execute graceful restart for all neighbors
 */
DEFPY (clear_ip_eigrp_neighbors_soft,
       clear_ip_eigrp_neighbors_soft_cmd,
       "clear ip eigrp [vrf NAME] neighbors soft",
       CLEAR_STR
       IP_STR
       "Clear IP-EIGRP\n"
       VRF_CMD_HELP_STR
       "Clear IP-EIGRP neighbors\n"
       "Resync with peers without adjacency reset\n")
{
	struct eigrp *eigrp;

	/* Check if eigrp process is enabled */
	eigrp = eigrp_vty_get_eigrp(vty, vrf);
	if (eigrp == NULL) {
		vty_out(vty, " EIGRP Routing Process not enabled\n");
		return CMD_SUCCESS;
	}

	/* execute graceful restart on all neighbors */
	eigrp_update_send_process_GR(eigrp, EIGRP_GR_MANUAL, vty);

	return CMD_SUCCESS;
}

/*
 * Execute graceful restart for all neighbors on interface
 */
DEFPY (clear_ip_eigrp_neighbors_int_soft,
       clear_ip_eigrp_neighbors_int_soft_cmd,
       "clear ip eigrp [vrf NAME] neighbors IFNAME soft",
       CLEAR_STR
       IP_STR
       "Clear IP-EIGRP\n"
       VRF_CMD_HELP_STR
       "Clear IP-EIGRP neighbors\n"
       "Interface's name\n"
       "Resync with peer without adjacency reset\n")
{
	struct eigrp *eigrp;
	struct eigrp_interface *ei;

	/* Check if eigrp process is enabled */
	eigrp = eigrp_vty_get_eigrp(vty, vrf);
	if (eigrp == NULL) {
		vty_out(vty, " EIGRP Routing Process not enabled\n");
		return CMD_SUCCESS;
	}

	/* lookup interface by specified name */
	ei = eigrp_if_lookup_by_name(eigrp, ifname);
	if (ei == NULL) {
		vty_out(vty, " Interface (%s) doesn't exist\n", argv[4]->arg);
		return CMD_WARNING;
	}

	/* execute graceful restart for all neighbors on interface */
	eigrp_update_send_interface_GR(ei, EIGRP_GR_MANUAL, vty);
	return CMD_SUCCESS;
}

/*
 * Execute graceful restart for neighbor specified by IP
 */
DEFPY (clear_ip_eigrp_neighbors_IP_soft,
       clear_ip_eigrp_neighbors_IP_soft_cmd,
       "clear ip eigrp [vrf NAME] neighbors A.B.C.D$nbr_addr soft",
       CLEAR_STR
       IP_STR
       "Clear IP-EIGRP\n"
       VRF_CMD_HELP_STR
       "Clear IP-EIGRP neighbors\n"
       "IP-EIGRP neighbor address\n"
       "Resync with peer without adjacency reset\n")
{
	struct eigrp *eigrp;
	struct eigrp_neighbor *nbr;


	/* Check if eigrp process is enabled */
	eigrp = eigrp_vty_get_eigrp(vty, vrf);
	if (eigrp == NULL) {
		vty_out(vty, " EIGRP Routing Process not enabled\n");
		return CMD_SUCCESS;
	}

	/* lookup neighbor in whole process */
	nbr = eigrp_nbr_lookup_by_addr_process(eigrp, nbr_addr);

	/* if neighbor doesn't exists, notify user and exit */
	if (nbr == NULL) {
		vty_out(vty, "Neighbor with entered address doesn't exists.\n");
		return CMD_WARNING;
	}

	/* execute graceful restart on neighbor */
	eigrp_update_send_GR(nbr, EIGRP_GR_MANUAL, vty);

	return CMD_SUCCESS;
}

void eigrp_vty_show_init(void)
{
	install_element(VIEW_NODE, &show_ip_eigrp_interfaces_cmd);

	install_element(VIEW_NODE, &show_ip_eigrp_neighbors_cmd);

	install_element(VIEW_NODE, &show_ip_eigrp_topology_cmd);
	install_element(VIEW_NODE, &show_ip_eigrp_topology_all_cmd);
}

/* Install EIGRP related vty commands. */
void eigrp_vty_init(void)
{
	/* commands for manual hard restart */
	install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_cmd);
	install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_int_cmd);
	install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_IP_cmd);
	/* commands for manual graceful restart */
	install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_soft_cmd);
	install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_int_soft_cmd);
	install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_IP_soft_cmd);
}
