diff options
author | Michele Marchetto <michele@cvs.openbsd.org> | 2009-06-01 20:59:46 +0000 |
---|---|---|
committer | Michele Marchetto <michele@cvs.openbsd.org> | 2009-06-01 20:59:46 +0000 |
commit | 5de1d23aeb3294b99aea577259871a4a7686e9b4 (patch) | |
tree | cc434b947408b0844b7b33a08358359751c9a7e4 /usr.sbin/ldpd/neighbor.c | |
parent | 19268a0235511684a7ee9e71b0c3dd8170ff3762 (diff) |
Welcome ldpd, the Label Distribution Protocol daemon.
Built using the imsg/three process framework, its main aim
is to redistribute MPLS labels between peers.
Right now it has some really basic functionalities,
the basic protocol works and peers are able to exchange
labels and insert them in the kernel.
It still does not react to changes of topology.
Not yet connected to the builds.
ok claudio@ deraadt@
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); +} |