// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * PIM for Quagga
 * Copyright (C) 2008  Everton da Silva Marques
 */

#include <zebra.h>

#include "log.h"
#include "prefix.h"
#include "memory.h"
#include "if.h"
#include "vty.h"
#include "plist.h"
#include "lib_errors.h"

#include "pimd.h"
#include "pim_instance.h"
#include "pim_neighbor.h"
#include "pim_time.h"
#include "pim_str.h"
#include "pim_iface.h"
#include "pim_pim.h"
#include "pim_upstream.h"
#include "pim_ifchannel.h"
#include "pim_rp.h"
#include "pim_zebra.h"
#include "pim_join.h"
#include "pim_jp_agg.h"
#include "pim_bfd.h"
#include "pim_register.h"
#include "pim_oil.h"
#include "pim_dm.h"

static void dr_election_by_addr(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	struct listnode *node;
	struct pim_neighbor *neigh;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	pim_ifp->pim_dr_addr = pim_ifp->primary_address;

	if (PIM_DEBUG_PIM_TRACE) {
		zlog_debug("%s: on interface %s", __func__, ifp->name);
	}

	for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
		if (pim_addr_cmp(neigh->source_addr, pim_ifp->pim_dr_addr) > 0)
			pim_ifp->pim_dr_addr = neigh->source_addr;
	}
}

static void dr_election_by_pri(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	struct listnode *node;
	struct pim_neighbor *neigh;
	uint32_t dr_pri;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	pim_ifp->pim_dr_addr = pim_ifp->primary_address;
	dr_pri = pim_ifp->pim_dr_priority;

	if (PIM_DEBUG_PIM_TRACE) {
		zlog_debug("%s: dr pri %u on interface %s", __func__, dr_pri,
			   ifp->name);
	}

	for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
		if (PIM_DEBUG_PIM_TRACE) {
			zlog_info("%s: neigh pri %u addr %pPA if dr addr %pPA",
				  __func__, neigh->dr_priority,
				  &neigh->source_addr, &pim_ifp->pim_dr_addr);
		}
		if ((neigh->dr_priority > dr_pri) ||
		    ((neigh->dr_priority == dr_pri) &&
		     (pim_addr_cmp(neigh->source_addr, pim_ifp->pim_dr_addr) >
		      0))) {
			pim_ifp->pim_dr_addr = neigh->source_addr;
			dr_pri = neigh->dr_priority;
		}
	}
}

/*
  RFC 4601: 4.3.2.  DR Election

  A router's idea of the current DR on an interface can change when a
  PIM Hello message is received, when a neighbor times out, or when a
  router's own DR Priority changes.
 */
int pim_if_dr_election(struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;
	pim_addr old_dr_addr;

	++pim_ifp->pim_dr_election_count;

	old_dr_addr = pim_ifp->pim_dr_addr;

	if (pim_ifp->pim_dr_num_nondrpri_neighbors) {
		dr_election_by_addr(ifp);
	} else {
		dr_election_by_pri(ifp);
	}

	/* DR changed ? */
	if (pim_addr_cmp(old_dr_addr, pim_ifp->pim_dr_addr)) {

		if (PIM_DEBUG_PIM_EVENTS)
			zlog_debug(
				"%s: DR was %pPA now is %pPA on interface %s",
				__func__, &old_dr_addr, &pim_ifp->pim_dr_addr,
				ifp->name);

		pim_ifp->pim_dr_election_last =
			pim_time_monotonic_sec(); /* timestamp */
		++pim_ifp->pim_dr_election_changes;
		pim_if_update_join_desired(pim_ifp);
		pim_if_update_could_assert(ifp);
		pim_if_update_assert_tracking_desired(ifp);

		if (PIM_I_am_DR(pim_ifp)) {
			pim_ifp->am_i_dr = true;
			pim_clear_nocache_state(pim_ifp);
		} else {
			if (pim_ifp->am_i_dr == true) {
				pim_reg_del_on_couldreg_fail(ifp);
				pim_ifp->am_i_dr = false;
			}
		}

		return 1;
	}

	return 0;
}

static void update_dr_priority(struct pim_neighbor *neigh,
			       pim_hello_options hello_options,
			       uint32_t dr_priority)
{
	pim_hello_options will_set_pri; /* boolean */
	pim_hello_options bit_flip;     /* boolean */
	pim_hello_options pri_change;   /* boolean */

	will_set_pri =
		PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_DR_PRIORITY);

	bit_flip = (will_set_pri
		    != PIM_OPTION_IS_SET(neigh->hello_options,
					 PIM_OPTION_MASK_DR_PRIORITY));

	if (bit_flip) {
		struct pim_interface *pim_ifp = neigh->interface->info;

		/* update num. of neighbors without dr_pri */

		if (will_set_pri) {
			--pim_ifp->pim_dr_num_nondrpri_neighbors;
		} else {
			++pim_ifp->pim_dr_num_nondrpri_neighbors;
		}
	}

	pri_change = (bit_flip || (neigh->dr_priority != dr_priority));

	if (will_set_pri) {
		neigh->dr_priority = dr_priority;
	} else {
		neigh->dr_priority = 0; /* cosmetic unset */
	}

	if (pri_change) {
		/*
		  RFC 4601: 4.3.2.  DR Election

		  A router's idea of the current DR on an interface can change
		  when a
		  PIM Hello message is received, when a neighbor times out, or
		  when a
		  router's own DR Priority changes.
		*/
		pim_if_dr_election(
			neigh->interface); // router's own DR Priority changes
	}
}

static void on_neighbor_timer(struct event *t)
{
	struct pim_neighbor *neigh;
	struct interface *ifp;
	char msg[100];

	neigh = EVENT_ARG(t);

	ifp = neigh->interface;

	if (PIM_DEBUG_PIM_TRACE)
		zlog_debug(
			"Expired %d sec holdtime for neighbor %pPA on interface %s",
			neigh->holdtime, &neigh->source_addr, ifp->name);

	snprintf(msg, sizeof(msg), "%d-sec holdtime expired", neigh->holdtime);
	pim_neighbor_delete(ifp, neigh, msg);

	/*
	  RFC 4601: 4.3.2.  DR Election

	  A router's idea of the current DR on an interface can change when a
	  PIM Hello message is received, when a neighbor times out, or when a
	  router's own DR Priority changes.
	*/
	pim_if_dr_election(ifp); // neighbor times out
}

void pim_neighbor_timer_reset(struct pim_neighbor *neigh, uint16_t holdtime)
{
	neigh->holdtime = holdtime;

	event_cancel(&neigh->t_expire_timer);

	/*
	  0xFFFF is request for no holdtime
	 */
	if (neigh->holdtime == 0xFFFF) {
		return;
	}

	if (PIM_DEBUG_PIM_TRACE_DETAIL)
		zlog_debug("%s: starting %u sec timer for neighbor %pPA on %s",
			   __func__, neigh->holdtime, &neigh->source_addr,
			   neigh->interface->name);

	event_add_timer(router->master, on_neighbor_timer, neigh,
			neigh->holdtime, &neigh->t_expire_timer);
}

static void on_neighbor_jp_timer(struct event *t)
{
	struct pim_neighbor *neigh = EVENT_ARG(t);
	struct pim_rpf rpf;

	if (PIM_DEBUG_PIM_TRACE)
		zlog_debug("%s:Sending JP Agg to %pPA on %s with %d groups",
			   __func__, &neigh->source_addr,
			   neigh->interface->name,
			   neigh->upstream_jp_agg->count);

	rpf.source_nexthop.interface = neigh->interface;
	rpf.rpf_addr = neigh->source_addr;
	pim_joinprune_send(&rpf, neigh->upstream_jp_agg);

	event_add_timer(router->master, on_neighbor_jp_timer, neigh, pim_neigh_jp_period(neigh),
			&neigh->jp_timer);
}

static void pim_neighbor_start_jp_timer(struct pim_neighbor *neigh)
{
	event_cancel(&neigh->jp_timer);
	event_add_timer(router->master, on_neighbor_jp_timer, neigh, pim_neigh_jp_period(neigh),
			&neigh->jp_timer);
}

static struct pim_neighbor *
pim_neighbor_new(struct interface *ifp, pim_addr source_addr,
		 pim_hello_options hello_options, uint16_t holdtime,
		 uint16_t propagation_delay, uint16_t override_interval,
		 uint32_t dr_priority, uint32_t generation_id,
		 struct list *addr_list)
{
	struct pim_interface *pim_ifp;
	struct pim_neighbor *neigh;
	struct channel_oil *c_oil;

	assert(ifp);
	pim_ifp = ifp->info;
	assert(pim_ifp);

	neigh = XCALLOC(MTYPE_PIM_NEIGHBOR, sizeof(*neigh));

	neigh->creation = pim_time_monotonic_sec();
	neigh->source_addr = source_addr;
	neigh->hello_options = hello_options;
	neigh->propagation_delay_msec = propagation_delay;
	neigh->override_interval_msec = override_interval;
	neigh->dr_priority = dr_priority;
	neigh->generation_id = generation_id;
	neigh->prefix_list = addr_list;
	neigh->t_expire_timer = NULL;
	neigh->interface = ifp;

	neigh->upstream_jp_agg = list_new();
	neigh->upstream_jp_agg->cmp = pim_jp_agg_group_list_cmp;
	neigh->upstream_jp_agg->del =
		(void (*)(void *))pim_jp_agg_group_list_free;
	pim_neighbor_start_jp_timer(neigh);

	pim_neighbor_timer_reset(neigh, holdtime);
	/*
	 * The pim_ifstat_hello_sent variable is used to decide if
	 * we should expedite a hello out the interface.  If we
	 * establish a new neighbor, we unfortunately need to
	 * reset the value so that we can know to hurry up and
	 * hello
	 */
	PIM_IF_FLAG_UNSET_HELLO_SENT(pim_ifp->flags);

	if (PIM_DEBUG_PIM_EVENTS)
		zlog_debug("%s: creating PIM neighbor %pPA on interface %s",
			   __func__, &source_addr, ifp->name);

	zlog_notice("PIM NEIGHBOR UP: neighbor %pPA on interface %s",
		    &source_addr, ifp->name);

	if (neigh->propagation_delay_msec
	    > pim_ifp->pim_neighbors_highest_propagation_delay_msec) {
		pim_ifp->pim_neighbors_highest_propagation_delay_msec =
			neigh->propagation_delay_msec;
	}
	if (neigh->override_interval_msec
	    > pim_ifp->pim_neighbors_highest_override_interval_msec) {
		pim_ifp->pim_neighbors_highest_override_interval_msec =
			neigh->override_interval_msec;
	}

	if (!PIM_OPTION_IS_SET(neigh->hello_options,
			       PIM_OPTION_MASK_LAN_PRUNE_DELAY)) {
		/* update num. of neighbors without hello option lan_delay */
		++pim_ifp->pim_number_of_nonlandelay_neighbors;
	}

	if (!PIM_OPTION_IS_SET(neigh->hello_options,
			       PIM_OPTION_MASK_DR_PRIORITY)) {
		/* update num. of neighbors without hello option dr_pri */
		++pim_ifp->pim_dr_num_nondrpri_neighbors;
	}

	// Register PIM Neighbor with BFD
	pim_bfd_info_nbr_create(pim_ifp, neigh);

	/* flood to the new neighor if needed */
	if (HAVE_DENSE_MODE(pim_ifp->pim_mode)) {
		frr_each (rb_pim_oil, &pim_ifp->pim->channel_oil_head, c_oil) {
			if (pim_is_grp_dm(pim_ifp->pim, *oil_mcastgrp(c_oil)) && c_oil->installed &&
			    !oil_if_has(c_oil, pim_ifp->mroute_vif_index)) {
				oil_if_set(c_oil, pim_ifp->mroute_vif_index, 1);
				pim_upstream_mroute_update(c_oil, __func__);
			}
		}
	}
	return neigh;
}

static void delete_prefix_list(struct pim_neighbor *neigh)
{
	if (neigh->prefix_list) {

#ifdef DUMP_PREFIX_LIST
		struct listnode *p_node;
		struct prefix *p;
		int list_size = neigh->prefix_list
					? (int)listcount(neigh->prefix_list)
					: -1;
		int i = 0;
		for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, p_node, p)) {
			zlog_debug(
				"%s: DUMP_PREFIX_LIST neigh=%x prefix_list=%x prefix=%x addr=%pFXh [%d/%d]",
				__func__, (unsigned)neigh,
				(unsigned)neigh->prefix_list, (unsigned)p, p, i,
				list_size);
			++i;
		}
#endif

		list_delete(&neigh->prefix_list);
	}
}

void pim_neighbor_free(struct pim_neighbor *neigh)
{
	assert(!neigh->t_expire_timer);

	delete_prefix_list(neigh);

	list_delete(&neigh->upstream_jp_agg);
	event_cancel(&neigh->jp_timer);

	bfd_sess_free(&neigh->bfd_session);

	XFREE(MTYPE_PIM_NEIGHBOR, neigh);
}

struct pim_neighbor *pim_neighbor_find_by_secondary(struct interface *ifp,
						    struct prefix *src)
{
	struct pim_interface *pim_ifp;
	struct listnode *node, *pnode;
	struct pim_neighbor *neigh;
	struct prefix *p;

	if (!ifp || !ifp->info)
		return NULL;

	pim_ifp = ifp->info;

	for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
		for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, pnode, p)) {
			if (prefix_same(p, src))
				return neigh;
		}
	}

	return NULL;
}

struct pim_neighbor *pim_neighbor_find(struct interface *ifp,
				       pim_addr source_addr, bool secondary)
{
	struct pim_interface *pim_ifp;
	struct listnode *node;
	struct pim_neighbor *neigh;

	if (!ifp)
		return NULL;

	pim_ifp = ifp->info;
	if (!pim_ifp)
		return NULL;

	for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
		if (!pim_addr_cmp(source_addr, neigh->source_addr)) {
			return neigh;
		}
	}

	if (secondary) {
		struct prefix p;

		pim_addr_to_prefix(&p, source_addr);
		return pim_neighbor_find_by_secondary(ifp, &p);
	}

	return NULL;
}

/*
 * Find the *one* interface out
 * this interface.  If more than
 * one return NULL
 */
struct pim_neighbor *pim_neighbor_find_if(struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;

	if (!pim_ifp || pim_ifp->pim_neighbor_list->count != 1)
		return NULL;

	return listnode_head(pim_ifp->pim_neighbor_list);
}

struct pim_neighbor *
pim_neighbor_add(struct interface *ifp, pim_addr source_addr,
		 pim_hello_options hello_options, uint16_t holdtime,
		 uint16_t propagation_delay, uint16_t override_interval,
		 uint32_t dr_priority, uint32_t generation_id,
		 struct list *addr_list, int send_hello_now)
{
	struct pim_interface *pim_ifp;
	struct pim_neighbor *neigh;

	neigh = pim_neighbor_new(ifp, source_addr, hello_options, holdtime,
				 propagation_delay, override_interval,
				 dr_priority, generation_id, addr_list);
	if (!neigh) {
		return 0;
	}

	pim_ifp = ifp->info;
	assert(pim_ifp);

	listnode_add(pim_ifp->pim_neighbor_list, neigh);

	if (PIM_DEBUG_PIM_TRACE_DETAIL)
		zlog_debug("%s: neighbor %pPA added ", __func__, &source_addr);
	/*
	  RFC 4601: 4.3.2.  DR Election

	  A router's idea of the current DR on an interface can change when a
	  PIM Hello message is received, when a neighbor times out, or when a
	  router's own DR Priority changes.
	*/
	pim_if_dr_election(neigh->interface); // new neighbor -- should not
					      // trigger dr election...

	/*
	  RFC 4601: 4.3.1.  Sending Hello Messages

	  To allow new or rebooting routers to learn of PIM neighbors quickly,
	  when a Hello message is received from a new neighbor, or a Hello
	  message with a new GenID is received from an existing neighbor, a
	  new Hello message should be sent on this interface after a
	  randomized delay between 0 and Triggered_Hello_Delay.

	  This is a bit silly to do it that way.  If I get a new
	  genid we need to send the hello *now* because we've
	  lined up a bunch of join/prune messages to go out the
	  interface.
	*/
	if (send_hello_now)
		pim_hello_restart_now(ifp);
	else
		pim_hello_restart_triggered(neigh->interface);

	pim_upstream_find_new_rpf(pim_ifp->pim);

	/* RNH can send nexthop update prior to PIM neibhor UP
	   in that case nexthop cache would not consider this neighbor
	   as RPF.
	   Upon PIM neighbor UP, iterate all RPs and update
	   nexthop cache with this neighbor.
	 */
	pim_resolve_rp_nh(pim_ifp->pim, neigh);

	pim_rp_setup(pim_ifp->pim);

	sched_rpf_cache_refresh(pim_ifp->pim);
	return neigh;
}

static uint16_t find_neighbors_next_highest_propagation_delay_msec(
	struct interface *ifp, struct pim_neighbor *highest_neigh)
{
	struct pim_interface *pim_ifp;
	struct listnode *neigh_node;
	struct pim_neighbor *neigh;
	uint16_t next_highest_delay_msec;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	next_highest_delay_msec = pim_ifp->pim_propagation_delay_msec;

	for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node,
				  neigh)) {
		if (neigh == highest_neigh)
			continue;
		if (neigh->propagation_delay_msec > next_highest_delay_msec)
			next_highest_delay_msec = neigh->propagation_delay_msec;
	}

	return next_highest_delay_msec;
}

static uint16_t find_neighbors_next_highest_override_interval_msec(
	struct interface *ifp, struct pim_neighbor *highest_neigh)
{
	struct pim_interface *pim_ifp;
	struct listnode *neigh_node;
	struct pim_neighbor *neigh;
	uint16_t next_highest_interval_msec;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	next_highest_interval_msec = pim_ifp->pim_override_interval_msec;

	for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node,
				  neigh)) {
		if (neigh == highest_neigh)
			continue;
		if (neigh->override_interval_msec > next_highest_interval_msec)
			next_highest_interval_msec =
				neigh->override_interval_msec;
	}

	return next_highest_interval_msec;
}

void pim_neighbor_delete(struct interface *ifp, struct pim_neighbor *neigh,
			 const char *delete_message)
{
	struct pim_interface *pim_ifp;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	zlog_notice("PIM NEIGHBOR DOWN: neighbor %pPA on interface %s: %s",
		    &neigh->source_addr, ifp->name, delete_message);

	event_cancel(&neigh->t_expire_timer);

	pim_if_assert_on_neighbor_down(ifp, neigh->source_addr);

	if (!PIM_OPTION_IS_SET(neigh->hello_options,
			       PIM_OPTION_MASK_LAN_PRUNE_DELAY)) {
		/* update num. of neighbors without hello option lan_delay */

		--pim_ifp->pim_number_of_nonlandelay_neighbors;
	}

	if (!PIM_OPTION_IS_SET(neigh->hello_options,
			       PIM_OPTION_MASK_DR_PRIORITY)) {
		/* update num. of neighbors without dr_pri */

		--pim_ifp->pim_dr_num_nondrpri_neighbors;
	}

	assert(neigh->propagation_delay_msec
	       <= pim_ifp->pim_neighbors_highest_propagation_delay_msec);
	assert(neigh->override_interval_msec
	       <= pim_ifp->pim_neighbors_highest_override_interval_msec);

	if (pim_if_lan_delay_enabled(ifp)) {

		/* will delete a neighbor with highest propagation delay? */
		if (neigh->propagation_delay_msec
		    == pim_ifp->pim_neighbors_highest_propagation_delay_msec) {
			/* then find the next highest propagation delay */
			pim_ifp->pim_neighbors_highest_propagation_delay_msec =
				find_neighbors_next_highest_propagation_delay_msec(
					ifp, neigh);
		}

		/* will delete a neighbor with highest override interval? */
		if (neigh->override_interval_msec
		    == pim_ifp->pim_neighbors_highest_override_interval_msec) {
			/* then find the next highest propagation delay */
			pim_ifp->pim_neighbors_highest_override_interval_msec =
				find_neighbors_next_highest_override_interval_msec(
					ifp, neigh);
		}
	}

	if (PIM_DEBUG_PIM_TRACE) {
		zlog_debug("%s: deleting PIM neighbor %pPA on interface %s",
			   __func__, &neigh->source_addr, ifp->name);
	}

	listnode_delete(pim_ifp->pim_neighbor_list, neigh);

	pim_neighbor_free(neigh);

	sched_rpf_cache_refresh(pim_ifp->pim);
}

void pim_neighbor_delete_all(struct interface *ifp, const char *delete_message)
{
	struct pim_interface *pim_ifp;
	struct listnode *neigh_node;
	struct listnode *neigh_nextnode;
	struct pim_neighbor *neigh;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	for (ALL_LIST_ELEMENTS(pim_ifp->pim_neighbor_list, neigh_node,
			       neigh_nextnode, neigh)) {
		pim_neighbor_delete(ifp, neigh, delete_message);
	}
}

struct prefix *pim_neighbor_find_secondary(struct pim_neighbor *neigh,
					   struct prefix *addr)
{
	struct listnode *node;
	struct prefix *p;

	if (!neigh->prefix_list)
		return 0;

	for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, node, p)) {
		if (prefix_same(p, addr))
			return p;
	}

	return NULL;
}

/*
  RFC 4601: 4.3.4.  Maintaining Secondary Address Lists

  All the advertised secondary addresses in received Hello messages
  must be checked against those previously advertised by all other
  PIM neighbors on that interface.  If there is a conflict and the
  same secondary address was previously advertised by another
  neighbor, then only the most recently received mapping MUST be
  maintained, and an error message SHOULD be logged to the
  administrator in a rate-limited manner.
*/
static void delete_from_neigh_addr(struct interface *ifp,
				   struct list *addr_list, pim_addr neigh_addr)
{
	struct listnode *addr_node;
	struct prefix *addr;
	struct pim_interface *pim_ifp;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	assert(addr_list);

	/*
	  Scan secondary address list
	*/
	for (ALL_LIST_ELEMENTS_RO(addr_list, addr_node, addr)) {
		struct listnode *neigh_node;
		struct pim_neighbor *neigh;

		if (addr->family != PIM_AF)
			continue;
		/*
		  Scan neighbors
		*/
		for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list,
					  neigh_node, neigh)) {
			{
				struct prefix *p = pim_neighbor_find_secondary(
					neigh, addr);
				if (p) {
					zlog_info(
						"secondary addr %pFXh recvd from neigh %pPA deleted from neigh %pPA on %s",
						addr, &neigh_addr,
						&neigh->source_addr, ifp->name);

					listnode_delete(neigh->prefix_list, p);
					prefix_free(&p);
				}
			}

		} /* scan neighbors */

	} /* scan addr list */
}

void pim_neighbor_update(struct pim_neighbor *neigh,
			 pim_hello_options hello_options, uint16_t holdtime,
			 uint32_t dr_priority, struct list *addr_list)
{
	struct pim_interface *pim_ifp = neigh->interface->info;
	uint32_t old, new;

	/* Received holdtime ? */
	if (PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME)) {
		pim_neighbor_timer_reset(neigh, holdtime);
	} else {
		pim_neighbor_timer_reset(neigh,
					 PIM_IF_DEFAULT_HOLDTIME(pim_ifp));
	}

#ifdef DUMP_PREFIX_LIST
	zlog_debug(
		"%s: DUMP_PREFIX_LIST old_prefix_list=%x old_size=%d new_prefix_list=%x new_size=%d",
		__func__, (unsigned)neigh->prefix_list,
		neigh->prefix_list ? (int)listcount(neigh->prefix_list) : -1,
		(unsigned)addr_list,
		addr_list ? (int)listcount(addr_list) : -1);
#endif

	if (neigh->prefix_list == addr_list) {
		if (addr_list) {
			flog_err(
				EC_LIB_DEVELOPMENT,
				"%s: internal error: trying to replace same prefix list=%p",
				__func__, (void *)addr_list);
		}
	} else {
		/* Delete existing secondary address list */
		delete_prefix_list(neigh);
	}

	if (addr_list) {
		delete_from_neigh_addr(neigh->interface, addr_list,
				       neigh->source_addr);
	}

	/* Replace secondary address list */
	neigh->prefix_list = addr_list;

	update_dr_priority(neigh, hello_options, dr_priority);
	new = PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY);
	old = PIM_OPTION_IS_SET(neigh->hello_options,
				PIM_OPTION_MASK_LAN_PRUNE_DELAY);

	if (old != new) {
		if (old)
			++pim_ifp->pim_number_of_nonlandelay_neighbors;
		else
			--pim_ifp->pim_number_of_nonlandelay_neighbors;
	}
	/*
	  Copy flags
	 */
	neigh->hello_options = hello_options;
}
