OpenBSD-4.6/usr.sbin/dvmrpd/neighbor.c

Compare this file to the similar file:
Show the results in this format:

/*	$OpenBSD: neighbor.c,v 1.7 2009/04/16 20:11:12 michele Exp $ */

/*
 * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
 * Copyright (c) 2004, 2005, 2006 Esben Norby <norby@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>

#include <ctype.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <event.h>

#include "igmp.h"
#include "dvmrpd.h"
#include "dvmrp.h"
#include "dvmrpe.h"
#include "log.h"
#include "rde.h"

LIST_HEAD(nbr_head, nbr);

struct nbr_table {
	struct nbr_head		*hashtbl;
	u_int32_t		 hashmask;
} nbrtable;

#define NBR_HASH(x)		\
	&nbrtable.hashtbl[(x) & nbrtable.hashmask]

u_int32_t	peercnt = NBR_CNTSTART;

struct {
	int		state;
	enum nbr_event	event;
	enum nbr_action	action;
	int		new_state;
} nbr_fsm_tbl[] = {
    /* current state	event that happened	action to take		resulting state */
    {NBR_STA_DOWN,	NBR_EVT_PROBE_RCVD,	NBR_ACT_STRT_ITIMER,	NBR_STA_1_WAY},
    {NBR_STA_ACTIVE,	NBR_EVT_PROBE_RCVD,	NBR_ACT_RST_ITIMER,	0},
    {NBR_STA_1_WAY,	NBR_EVT_2_WAY_RCVD,	NBR_ACT_STRT_ITIMER,	NBR_STA_2_WAY},
    {NBR_STA_ACTIVE,	NBR_EVT_1_WAY_RCVD,	NBR_ACT_RESET,		NBR_STA_1_WAY},
    {NBR_STA_ANY,	NBR_EVT_KILL_NBR,	NBR_ACT_DEL,		NBR_STA_DOWN},
    {NBR_STA_ANY,	NBR_EVT_ITIMER,		NBR_ACT_DEL,		NBR_STA_DOWN},
    {-1,		NBR_EVT_NOTHING,	NBR_ACT_NOTHING,	0},
};

const char * const nbr_event_names[] = {
	"NOTHING",
	"PROBE RCVD",
	"1-WAY RCVD",
	"2-WAY RCVD",
	"KILL NBR",
	"ITIMER",
	"LL DOWN"
};

const char * const nbr_action_names[] = {
	"NOTHING",
	"RESET ITIMER",
	"START ITIMER",
	"RESET",
	"DELETE",
	"CLEAR LISTS"
};

int
nbr_fsm(struct nbr *nbr, enum nbr_event event)
{
	struct timeval	now;
	int		old_state;
	int		new_state = 0;
	int		i, ret = 0;

	old_state = nbr->state;
	for (i = 0; nbr_fsm_tbl[i].state != -1; i++)
		if ((nbr_fsm_tbl[i].state & old_state) &&
		    (nbr_fsm_tbl[i].event == event)) {
			new_state = nbr_fsm_tbl[i].new_state;
			break;
		}

	if (nbr_fsm_tbl[i].state == -1) {
		/* XXX event outside of the defined fsm, ignore it. */
		log_warnx("nbr_fsm: neighbor ID %s, "
		    "event '%s' not expected in state '%s'",
		    inet_ntoa(nbr->id), nbr_event_name(event),
		    nbr_state_name(old_state));
		return (0);
	}

	switch (nbr_fsm_tbl[i].action) {
	case NBR_ACT_RST_ITIMER:
		ret = nbr_act_reset_itimer(nbr);
		break;
	case NBR_ACT_STRT_ITIMER:
		ret = nbr_act_start_itimer(nbr);
		break;
	case NBR_ACT_RESET:
		/* XXX nbr action reset */
		break;
	case NBR_ACT_DEL:
		ret = nbr_act_delete(nbr);
		break;
	case NBR_ACT_CLR_LST:
		ret = nbr_act_clear_lists(nbr);
		break;
	case NBR_ACT_NOTHING:
		/* do nothing */
		break;
	}

	if (ret) {
		log_warnx("nbr_fsm: error changing state for neighbor ID %s, "
		    "event '%s', state '%s'", inet_ntoa(nbr->id),
		    nbr_event_name(event), nbr_state_name(old_state));
		return (-1);
	}

	if (new_state != 0)
		nbr->state = new_state;

	if (old_state != nbr->state) {
		if (old_state & NBR_STA_2_WAY || nbr->state & NBR_STA_2_WAY) {
			/* neighbor changed from/to 2_WAY */

			gettimeofday(&now, NULL);
			nbr->uptime = now.tv_sec;

			if (nbr->state & NBR_STA_2_WAY)
				nbr->iface->adj_cnt++;
			else
				nbr->iface->adj_cnt--;
		}

		log_debug("nbr_fsm: event '%s' resulted in action '%s' and "
		    "changing state for neighbor ID %s from '%s' to '%s'",
		    nbr_event_name(event),
		    nbr_action_name(nbr_fsm_tbl[i].action),
		    inet_ntoa(nbr->id), nbr_state_name(old_state),
		    nbr_state_name(nbr->state));
	}

	return (ret);
}

void
nbr_init(u_int32_t hashsize)
{
	u_int32_t        hs, i;

	for (hs = 1; hs < hashsize; hs <<= 1)
		;
	nbrtable.hashtbl = calloc(hs, sizeof(struct nbr_head));
	if (nbrtable.hashtbl == NULL)
		fatal("nbr_init");

	for (i = 0; i < hs; i++)
		LIST_INIT(&nbrtable.hashtbl[i]);

	nbrtable.hashmask = hs - 1;
}

struct nbr *
nbr_new(u_int32_t nbr_id, struct iface *iface, int self)
{
	struct nbr_head	*head;
	struct nbr	*nbr = NULL;

	if ((nbr = calloc(1, sizeof(*nbr))) == NULL)
		fatal("nbr_new");

	nbr->state = NBR_STA_DOWN;
	nbr->id.s_addr = nbr_id;

	/* get next unused peerid */
	while (nbr_find_peerid(++peercnt))
		;
	nbr->peerid = peercnt;
	head = NBR_HASH(nbr->peerid);
	LIST_INSERT_HEAD(head, nbr, hash);

	/* add to peer list */
	nbr->iface = iface;
	LIST_INSERT_HEAD(&iface->nbr_list, nbr, entry);

	TAILQ_INIT(&nbr->rr_list);

	/* set event structures */
	evtimer_set(&nbr->inactivity_timer, nbr_itimer, nbr);

	log_debug("nbr_new: neighbor ID %s, peerid %lu",
	    inet_ntoa(nbr->id), nbr->peerid);

	return (nbr);
}

int
nbr_del(struct nbr *nbr)
{
	/* clear lists */
	rr_list_clr(&nbr->rr_list);

	LIST_REMOVE(nbr, entry);
	LIST_REMOVE(nbr, hash);

	free(nbr);

	return (0);
}

struct nbr *
nbr_find_peerid(u_int32_t peerid)
{
	struct nbr_head	*head;
	struct nbr	*nbr;

	head = NBR_HASH(peerid);

	LIST_FOREACH(nbr, head, hash) {
		if (nbr->peerid == peerid)
			return (nbr);
	}

	return (NULL);
}

struct nbr *
nbr_find_ip(struct iface *iface, u_int32_t src_ip)
{
	struct nbr	*nbr = NULL;

	LIST_FOREACH(nbr, &iface->nbr_list, entry) {
		if (nbr->id.s_addr == src_ip) {
			return (nbr);
		}
	}

	return (NULL);
}

/* timers */
void
nbr_itimer(int fd, short event, void *arg)
{
	struct nbr *nbr = arg;

	log_debug("nbr_itimer: %s", inet_ntoa(nbr->id));

	nbr_fsm(nbr, NBR_EVT_ITIMER);
}

int
nbr_start_itimer(struct nbr *nbr)
{
	struct timeval	tv;

	log_debug("nbr_start_itimer: %s", inet_ntoa(nbr->id));

	timerclear(&tv);
	tv.tv_sec = nbr->iface->dead_interval;

	return (evtimer_add(&nbr->inactivity_timer, &tv));
}

int
nbr_stop_itimer(struct nbr *nbr)
{
	return (evtimer_del(&nbr->inactivity_timer));
}

int
nbr_reset_itimer(struct nbr *nbr)
{
	struct timeval	tv;

	timerclear(&tv);
	tv.tv_sec = nbr->iface->dead_interval;

	return (evtimer_add(&nbr->inactivity_timer, &tv));
}

/* actions */
int
nbr_act_start(struct nbr *nbr)
{
	log_debug("nbr_act_start: neighbor ID %s", inet_ntoa(nbr->id));

	return (-1);
}

int
nbr_act_reset_itimer(struct nbr *nbr)
{
	if (nbr_reset_itimer(nbr)) {
		log_warnx("nbr_act_reset_itimer: cannot schedule inactivity "
		    "timer, neighbor ID %s", inet_ntoa(nbr->id));
		return (-1);
	}

	return (0);
}

int
nbr_act_start_itimer(struct nbr *nbr)
{
	if (nbr_start_itimer(nbr)) {
		log_warnx("nbr_act_start_itimer: cannot schedule inactivity "
		    "timer, neighbor ID %s",
		    inet_ntoa(nbr->id));
		return (-1);
	}

	if (nbr->state == NBR_STA_1_WAY) {
		/* new nbr, send entire route table, unicast */
		log_debug("nbr_act_start_itimer: nbr %s, send route table",
		    inet_ntoa(nbr->id));

		dvmrpe_imsg_compose_rde(IMSG_FULL_ROUTE_REPORT, nbr->peerid, 0,
		    NULL, 0);
	}

	return (0);
}

int
nbr_act_delete(struct nbr *nbr)
{
	struct nbr_msg	nm;

	log_debug("nbr_act_delete: neighbor ID %s", inet_ntoa(nbr->id));

	/* stop timers */
	if (nbr_stop_itimer(nbr)) {
		log_warnx("nbr_act_delete: error removing inactivity timer, "
		    "neighbor ID %s", inet_ntoa(nbr->id));
		return (-1);
	}

	nm.address.s_addr = nbr->addr.s_addr;
	nm.ifindex = nbr->iface->ifindex;

	dvmrpe_imsg_compose_rde(IMSG_NBR_DEL, 0, 0, &nm, sizeof(nm));

	return (nbr_del(nbr));
}

int
nbr_act_clear_lists(struct nbr *nbr)
{
	log_debug("nbr_act_clear_lists: neighbor ID %s", inet_ntoa(nbr->id));
	rr_list_clr(&nbr->rr_list);

	return (0);
}

/* names */
const char *
nbr_event_name(int event)
{
	return (nbr_event_names[event]);
}

const char *
nbr_action_name(int action)
{
	return (nbr_action_names[action]);
}

struct ctl_nbr *
nbr_to_ctl(struct nbr *nbr)
{
	static struct ctl_nbr	 nctl;
	struct timeval		 tv, now, res;

	memcpy(nctl.name, nbr->iface->name, sizeof(nctl.name));
	memcpy(&nctl.id, &nbr->id, sizeof(nctl.id));
	memcpy(&nctl.addr, &nbr->addr, sizeof(nctl.addr));

	nctl.state = nbr->state;

	gettimeofday(&now, NULL);
	if (evtimer_pending(&nbr->inactivity_timer, &tv)) {
		timersub(&tv, &now, &res);
		nctl.dead_timer = res.tv_sec;
	} else
		nctl.dead_timer = 0;

	if (nbr->state == NBR_STA_2_WAY) {
		nctl.uptime = now.tv_sec - nbr->uptime;
	} else
		nctl.uptime = 0;

	return (&nctl);
}