/* $OpenBSD: neighbor.c,v 1.7 2009/04/16 20:11:12 michele Exp $ */ /* * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2004, 2005, 2006 Esben Norby * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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); }