diff options
Diffstat (limited to 'usr.sbin/ldpd/neighbor.c')
-rw-r--r-- | usr.sbin/ldpd/neighbor.c | 767 |
1 files changed, 767 insertions, 0 deletions
diff --git a/usr.sbin/ldpd/neighbor.c b/usr.sbin/ldpd/neighbor.c new file mode 100644 index 00000000000..123f460bb21 --- /dev/null +++ b/usr.sbin/ldpd/neighbor.c @@ -0,0 +1,767 @@ +/* $OpenBSD: neighbor.c,v 1.1 2009/06/01 20:59:45 michele Exp $ */ + +/* + * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004, 2005, 2008 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 <unistd.h> + +#include "ldpd.h" +#include "ldp.h" +#include "ldpe.h" +#include "log.h" +#include "lde.h" + +int nbr_establish_connection(struct nbr *); +void nbr_send_labelmappings(struct nbr *); + +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; + +extern struct ldpd_conf *leconf; + +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 */ +/* Discovery States */ + {NBR_STA_DOWN, NBR_EVT_HELLO_RCVD, NBR_ACT_STRT_ITIMER, NBR_STA_PRESENT}, + {NBR_STA_UP, NBR_EVT_HELLO_RCVD, NBR_ACT_RST_ITIMER, 0}, +/* Passive Role */ + {NBR_STA_PRESENT, NBR_EVT_SESSION_UP, NBR_ACT_SESSION_EST, NBR_STA_INITIAL}, + {NBR_STA_INITIAL, NBR_EVT_INIT_RCVD, NBR_ACT_INIT_SEND, NBR_STA_OPENREC}, + {NBR_STA_OPENREC, NBR_EVT_KEEPALIVE_RCVD, NBR_ACT_STRT_KTIMER, NBR_STA_OPER}, +/* Active Role */ + {NBR_STA_PRESENT, NBR_EVT_INIT_SENT, NBR_ACT_NOTHING, NBR_STA_OPENSENT}, + {NBR_STA_OPENSENT, NBR_EVT_INIT_RCVD, NBR_ACT_KEEPALIVE_SEND, NBR_STA_OPER}, +/* Session Maintenance */ + {NBR_STA_OPER, NBR_EVT_PDU_RCVD, NBR_ACT_RST_KTIMEOUT, 0}, +/* Session Close */ + {NBR_STA_SESSION, NBR_EVT_CLOSE_SESSION, NBR_ACT_CLOSE_SESSION, NBR_STA_PRESENT}, + {NBR_STA_UP, NBR_EVT_DOWN, NBR_ACT_CLOSE_SESSION, }, + {-1, NBR_EVT_NOTHING, NBR_ACT_NOTHING, 0}, +}; + +const char * const nbr_event_names[] = { + "NOTHING", + "HELLO RECEIVED", + "SESSION UP", + "SESSION CLOSE", + "INIT RECEIVED", + "KEEPALIVE RECEIVED", + "PDU RECEIVED", + "INIT SENT", + "DOWN" +}; + +const char * const nbr_action_names[] = { + "NOTHING", + "START INACTIVITY TIMER", + "RESET INACTIVITY TIMER", + "RESET KEEPALIVE TIMEOUT", + "START KEEPALIVE TIMER", + "RESET KEEPALIVE TIMER", + "START NEIGHBOR SESSION", + "SEND INIT", + "SEND KEEPALIVE", + "CLOSE SESSION" +}; + +int +nbr_fsm(struct nbr *nbr, enum nbr_event event) +{ + int old_state; + int new_state = 0; + int i, ret = 0; + + if (nbr == nbr->iface->self) + return (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) { + /* 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_names[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_RST_KTIMEOUT: + ret = nbr_act_reset_ktimeout(nbr); + break; + case NBR_ACT_RST_KTIMER: + ret = nbr_act_reset_ktimer(nbr); + break; + case NBR_ACT_STRT_KTIMER: + /* XXX */ + ret = nbr_act_start_ktimer(nbr); + nbr_act_start_ktimeout(nbr); + send_address(nbr, NULL); + nbr_send_labelmappings(nbr); + break; + case NBR_ACT_SESSION_EST: + ret = nbr_act_session_establish(nbr, 0); + break; + case NBR_ACT_INIT_SEND: + /* XXX */ + send_keepalive(nbr); + ret = send_init(nbr); + break; + case NBR_ACT_KEEPALIVE_SEND: + /* XXX */ + ret = nbr_act_start_ktimer(nbr); + nbr_act_start_ktimeout(nbr); + send_keepalive(nbr); + send_address(nbr, NULL); + nbr_send_labelmappings(nbr); + break; + case NBR_ACT_CLOSE_SESSION: + session_close(nbr); + ret = nbr_act_start_idtimer(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_names[event], nbr_state_name(old_state)); + return (-1); + } + + if (new_state != 0) + nbr->state = new_state; + + if (old_state != nbr->state) { + log_debug("nbr_fsm: event %s resulted in action %s and " + "changing state for neighbor ID %s from %s to %s", + nbr_event_names[event], + nbr_action_names[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) +{ + struct nbr_head *head; + struct nbr *nbr; + 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; + + /* allocate a dummy neighbor used for self originated AS ext routes */ + if ((nbr = calloc(1, sizeof(*nbr))) == NULL) + fatal("nbr_init"); + + nbr->id.s_addr = ldpe_router_id(); + nbr->state = NBR_STA_DOWN; + nbr->peerid = NBR_IDSELF; + head = NBR_HASH(nbr->peerid); + LIST_INSERT_HEAD(head, nbr, hash); + + TAILQ_INIT(&nbr->mapping_list); + TAILQ_INIT(&nbr->withdraw_list); + TAILQ_INIT(&nbr->request_list); + TAILQ_INIT(&nbr->release_list); + TAILQ_INIT(&nbr->abortreq_list); +} + +struct nbr * +nbr_new(u_int32_t nbr_id, u_int16_t lspace, struct iface *iface, int self) +{ + struct nbr_head *head; + struct nbr *nbr; + struct lde_nbr rn; + + if ((nbr = calloc(1, sizeof(*nbr))) == NULL) + fatal("nbr_new"); + + nbr->state = NBR_STA_DOWN; + nbr->id.s_addr = nbr_id; + nbr->lspace = lspace; + + /* 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); + + if (self) { + nbr->addr.s_addr = iface->addr.s_addr; + nbr->priority = iface->priority; + } + + /* set event structures */ + evtimer_set(&nbr->inactivity_timer, nbr_itimer, nbr); + evtimer_set(&nbr->keepalive_timeout, nbr_ktimeout, nbr); + evtimer_set(&nbr->keepalive_timer, nbr_ktimer, nbr); + evtimer_set(&nbr->initdelay_timer, nbr_idtimer, nbr); + + bzero(&rn, sizeof(rn)); + rn.id.s_addr = nbr->id.s_addr; + rn.lspace = nbr->lspace; + rn.ifindex = nbr->iface->ifindex; + rn.self = self; + ldpe_imsg_compose_lde(IMSG_NEIGHBOR_UP, nbr->peerid, 0, &rn, + sizeof(rn)); + + return (nbr); +} + +void +nbr_del(struct nbr *nbr) +{ + ldpe_imsg_compose_lde(IMSG_NEIGHBOR_DOWN, nbr->peerid, 0, NULL, 0); + + if (evtimer_pending(&nbr->inactivity_timer, NULL)) + evtimer_del(&nbr->inactivity_timer); + if (evtimer_pending(&nbr->keepalive_timer, NULL)) + evtimer_del(&nbr->keepalive_timer); + if (evtimer_pending(&nbr->keepalive_timeout, NULL)) + evtimer_del(&nbr->keepalive_timeout); + if (evtimer_pending(&nbr->initdelay_timer, NULL)) + evtimer_del(&nbr->initdelay_timer); + + nbr_mapping_list_clr(nbr, &nbr->mapping_list); + + LIST_REMOVE(nbr, entry); + LIST_REMOVE(nbr, hash); + + free(nbr); +} + +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 rtr_id) +{ + struct nbr *nbr = NULL; + + LIST_FOREACH(nbr, &iface->nbr_list, entry) { + if (nbr->addr.s_addr == rtr_id) + return (nbr); + } + + return (NULL); +} + +struct nbr * +nbr_find_ldpid(struct iface *iface, u_int32_t rtr_id, u_int16_t lspace) +{ + struct nbr *nbr = NULL; + + LIST_FOREACH(nbr, &iface->nbr_list, entry) { + if (nbr->id.s_addr == rtr_id && nbr->lspace == lspace) + return (nbr); + } + + return (NULL); +} + +/* timers */ + +/* Inactivity timer: timeout based on hellos */ +/* ARGSUSED */ +void +nbr_itimer(int fd, short event, void *arg) +{ + struct nbr *nbr = arg; + + log_debug("nbr_itimer: neighbor ID %s peerid %lu", inet_ntoa(nbr->id), + nbr->peerid); + + nbr_del(nbr); +} + +void +nbr_start_itimer(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = nbr->holdtime; + + if (evtimer_add(&nbr->inactivity_timer, &tv) == -1) + fatal("nbr_start_itimer"); +} + +void +nbr_stop_itimer(struct nbr *nbr) +{ + if (evtimer_del(&nbr->inactivity_timer) == -1) + fatal("nbr_stop_itimer"); +} + +void +nbr_reset_itimer(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = nbr->holdtime; + + if (evtimer_add(&nbr->inactivity_timer, &tv) == -1) + fatal("nbr_reset_itimer"); +} + +/* Keepalive timer: timer to send keepalive message to neighbors */ + +void +nbr_ktimer(int fd, short event, void *arg) +{ + struct nbr *nbr = arg; + struct timeval tv; + + log_debug("nbr_ktimer: neighbor ID %s peerid %lu", inet_ntoa(nbr->id), + nbr->peerid); + + send_keepalive(nbr); + + timerclear(&tv); + tv.tv_sec = (time_t)(nbr->keepalive / KEEPALIVE_PER_PERIOD); + if (evtimer_add(&nbr->keepalive_timer, &tv) == -1) + fatal("nbr_ktimer"); +} + +void +nbr_start_ktimer(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + + /* XXX: just to be sure it will send three keepalives per period */ + tv.tv_sec = (time_t)(nbr->keepalive / KEEPALIVE_PER_PERIOD); + + if (evtimer_add(&nbr->keepalive_timer, &tv) == -1) + fatal("nbr_start_ktimer"); +} + +void +nbr_stop_ktimer(struct nbr *nbr) +{ + if (evtimer_del(&nbr->keepalive_timer) == -1) + fatal("nbr_stop_ktimer"); +} + +void +nbr_reset_ktimer(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + + /* XXX: just to be sure it will send three keepalives per period */ + tv.tv_sec = (time_t)(nbr->keepalive / KEEPALIVE_PER_PERIOD); + + if (evtimer_add(&nbr->keepalive_timer, &tv) == -1) + fatal("nbr_reset_ktimer"); +} + +/* Keepalive timeout: if the nbr hasn't sent keepalive */ + +void +nbr_ktimeout(int fd, short event, void *arg) +{ + struct nbr *nbr = arg; + + log_debug("nbr_ktimeout: neighbor ID %s peerid %lu", inet_ntoa(nbr->id), + nbr->peerid); + + send_notification_nbr(nbr, S_KEEPALIVE_TMR); + close(nbr->fd); +} + +void +nbr_start_ktimeout(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = nbr->keepalive; + + if (evtimer_add(&nbr->keepalive_timeout, &tv) == -1) + fatal("nbr_start_ktimeout"); +} + +void +nbr_stop_ktimeout(struct nbr *nbr) +{ + if (evtimer_del(&nbr->keepalive_timeout) == -1) + fatal("nbr_stop_ktimeout"); +} + +void +nbr_reset_ktimeout(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = nbr->keepalive; + + if (evtimer_add(&nbr->keepalive_timeout, &tv) == -1) + fatal("nbr_reset_ktimeout"); +} + +/* Init delay timer: timer to retry to iniziatize session */ + +void +nbr_idtimer(int fd, short event, void *arg) +{ + struct nbr *nbr = arg; + + log_debug("nbr_idtimer: neighbor ID %s peerid %lu", inet_ntoa(nbr->id), + nbr->peerid); + + nbr_act_session_establish(nbr, 1); +} + +void +nbr_start_idtimer(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = INIT_DELAY_TMR; + + if (evtimer_add(&nbr->initdelay_timer, &tv) == -1) + fatal("nbr_start_idtimer"); +} + +void +nbr_stop_idtimer(struct nbr *nbr) +{ + if (evtimer_del(&nbr->initdelay_timer) == -1) + fatal("nbr_stop_idtimer"); +} + +int +nbr_pending_idtimer(struct nbr *nbr) +{ + if (evtimer_pending(&nbr->initdelay_timer, NULL)) + return (1); + + return (0); +} + + +void +nbr_reset_idtimer(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = INIT_DELAY_TMR; + + if (evtimer_add(&nbr->initdelay_timer, &tv) == -1) + fatal("nbr_reset_idtimer"); +} +/* actions */ +int +nbr_act_reset_itimer(struct nbr *nbr) +{ + nbr_reset_itimer(nbr); + + return (0); +} + +int +nbr_act_start_itimer(struct nbr *nbr) +{ + nbr_start_itimer(nbr); + + return (0); +} + +int +nbr_act_reset_ktimer(struct nbr *nbr) +{ + nbr_reset_ktimer(nbr); + + return (0); +} + +int +nbr_act_start_ktimer(struct nbr *nbr) +{ + nbr_start_ktimer(nbr); + + return (0); +} + +int +nbr_act_reset_ktimeout(struct nbr *nbr) +{ + nbr_reset_ktimeout(nbr); + + return (0); +} + +int +nbr_act_start_ktimeout(struct nbr *nbr) +{ + nbr_start_ktimeout(nbr); + + return (0); +} + +int +nbr_act_reset_idtimer(struct nbr *nbr) +{ + nbr_reset_idtimer(nbr); + + return (0); +} + +int +nbr_act_start_idtimer(struct nbr *nbr) +{ + if (nbr->addr.s_addr < nbr->iface->addr.s_addr) + nbr_start_idtimer(nbr); + + return (0); +} + +int +nbr_establish_connection(struct nbr *nbr) +{ + struct sockaddr_in in; + int st; + + in.sin_family = AF_INET; + in.sin_port = htons(LDP_PORT); + in.sin_addr.s_addr = nbr->addr.s_addr; + + nbr->fd = socket(AF_INET, SOCK_STREAM, 0); + if (nbr->fd < 0) { + log_debug("nbr_establish_connection: error while " + "creating socket"); + return (-1); + } + + st = connect(nbr->fd, (struct sockaddr *)&in, sizeof(in)); + if (st < 0 ) { + log_debug("nbr_establish_connection: error while " + "connecting to %s", inet_ntoa(nbr->addr)); + nbr_act_start_idtimer(nbr); + return (-1); + } + + return (0); +} + +int +nbr_close_connection(struct nbr *nbr) +{ + bufferevent_disable(nbr->bev, EV_READ|EV_WRITE); + bufferevent_free(nbr->bev); + close(nbr->fd); + + return (0); +} + +int +nbr_act_session_establish(struct nbr *nbr, int active) +{ + evbuffercb readfn = session_read; + everrorcb errorfn = session_error; + + if (active) { + if (nbr_establish_connection(nbr) < 0) + return (-1); + } + + nbr->bev = bufferevent_new(nbr->fd, readfn, NULL, errorfn, nbr); + bufferevent_settimeout(nbr->bev, 0, 0); + bufferevent_enable(nbr->bev, EV_READ|EV_WRITE); + + if (active) { + send_init(nbr); + nbr_fsm(nbr, NBR_EVT_INIT_SENT); + } + + + return (0); +} + +void +nbr_send_labelmappings(struct nbr *nbr) +{ + if (leconf->mode & MODE_ADV_UNSOLICITED) { + ldpe_imsg_compose_lde(IMSG_LABEL_MAPPING_FULL, nbr->peerid, 0, + 0, 0); + } +} + +void +nbr_mapping_add(struct nbr *nbr, struct mapping_head *mh, struct map *map) +{ + struct mapping_entry *me; + + me = calloc(1, sizeof(*me)); + if (me == NULL) + fatal("nbr_mapping_add"); + + me->prefix = map->prefix; + me->prefixlen = map->prefixlen; + me->label = map->label; + + TAILQ_INSERT_HEAD(mh, me, entry); +} + +struct mapping_entry * +nbr_mapping_find(struct nbr *nbr, struct mapping_head *mh, struct map *map) +{ + struct mapping_entry *me = NULL; + + TAILQ_FOREACH(me, mh, entry) { + if (me->prefix == map->prefix && + me->prefixlen == map->prefixlen) + return (me); + } + + return (NULL); +} + +void +nbr_mapping_del(struct nbr *nbr, struct mapping_head *mh, struct map *map) +{ + struct mapping_entry *me; + + me = nbr_mapping_find(nbr, mh, map); + if (me == NULL) + return; + + TAILQ_REMOVE(mh, me, entry); + free(me); +} + +void +nbr_mapping_list_clr(struct nbr *nbr, struct mapping_head *mh) +{ + struct mapping_entry *me; + + while ((me = TAILQ_FIRST(mh)) != NULL) { + TAILQ_REMOVE(mh, me, entry); + free(me); + } +} + +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.nbr_state = nbr->state; + + gettimeofday(&now, NULL); + if (evtimer_pending(&nbr->inactivity_timer, &tv)) { + timersub(&tv, &now, &res); + if (nbr->state & NBR_STA_DOWN) + nctl.dead_timer = DEFAULT_NBR_TMOUT - res.tv_sec; + else + nctl.dead_timer = res.tv_sec; + } else + nctl.dead_timer = 0; +/* + if (nbr->state == NBR_STA_FULL) { + nctl.uptime = now.tv_sec - nbr->uptime; + } else + nctl.uptime = 0; +*/ + return (&nctl); +} |