/* $OpenBSD: rde_lsdb.c,v 1.35 2006/12/13 13:24:09 claudio Exp $ */ /* * Copyright (c) 2004, 2005 Claudio Jeker * * 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 "ospf.h" #include "ospfd.h" #include "rde.h" #include "log.h" struct vertex *vertex_get(struct lsa *, struct rde_nbr *); int lsa_router_check(struct lsa *, u_int16_t); void lsa_timeout(int, short, void *); void lsa_refresh(struct vertex *); int lsa_equal(struct lsa *, struct lsa *); RB_GENERATE(lsa_tree, vertex, entry, lsa_compare) void lsa_init(struct lsa_tree *t) { RB_INIT(t); } int lsa_compare(struct vertex *a, struct vertex *b) { if (a->type < b->type) return (-1); if (a->type > b->type) return (1); if (a->ls_id < b->ls_id) return (-1); if (a->ls_id > b->ls_id) return (1); if (a->adv_rtr < b->adv_rtr) return (-1); if (a->adv_rtr > b->adv_rtr) return (1); return (0); } struct vertex * vertex_get(struct lsa *lsa, struct rde_nbr *nbr) { struct vertex *v; struct timespec tp; if ((v = calloc(1, sizeof(struct vertex))) == NULL) fatal(NULL); v->nbr = nbr; v->lsa = lsa; clock_gettime(CLOCK_MONOTONIC, &tp); v->changed = v->stamp = tp.tv_sec; v->cost = LS_INFINITY; v->ls_id = ntohl(lsa->hdr.ls_id); v->adv_rtr = ntohl(lsa->hdr.adv_rtr); v->type = lsa->hdr.type; if (!nbr->self) v->flooded = 1; /* XXX fix me */ evtimer_set(&v->ev, lsa_timeout, v); return (v); } void vertex_free(struct vertex *v) { if (v->type == LSA_TYPE_EXTERNAL) RB_REMOVE(lsa_tree, &asext_tree, v); else RB_REMOVE(lsa_tree, &v->nbr->area->lsa_tree, v); (void)evtimer_del(&v->ev); free(v->lsa); free(v); } /* returns -1 if a is older, 1 if newer and 0 if equal to b */ int lsa_newer(struct lsa_hdr *a, struct lsa_hdr *b) { int32_t a32, b32; u_int16_t a16, b16; int i; if (a == NULL) return (-1); if (b == NULL) return (1); /* * The sequence number is defined as signed 32-bit integer, * no idea how IETF came up with such a stupid idea. */ a32 = (int32_t)ntohl(a->seq_num); b32 = (int32_t)ntohl(b->seq_num); if (a32 > b32) return (1); if (a32 < b32) return (-1); a16 = ntohs(a->ls_chksum); b16 = ntohs(b->ls_chksum); if (a16 > b16) return (1); if (a16 < b16) return (-1); a16 = ntohs(a->age); b16 = ntohs(b->age); if (a16 >= MAX_AGE && b16 >= MAX_AGE) return (0); if (b16 >= MAX_AGE) return (-1); if (a16 >= MAX_AGE) return (1); i = b16 - a16; if (abs(i) > MAX_AGE_DIFF) return (i > 0 ? 1 : -1); return (0); } int lsa_check(struct rde_nbr *nbr, struct lsa *lsa, u_int16_t len) { struct area *area = nbr->area; u_int32_t metric; if (len < sizeof(lsa->hdr)) { log_warnx("lsa_check: bad packet size"); return (0); } if (ntohs(lsa->hdr.len) != len) { log_warnx("lsa_check: bad packet size"); return (0); } if (iso_cksum(lsa, len, 0)) { log_warnx("lsa_check: bad packet checksum"); return (0); } /* invalid ages */ if ((ntohs(lsa->hdr.age) < 1 && !nbr->self) || ntohs(lsa->hdr.age) > MAX_AGE) { log_warnx("lsa_check: bad age"); return (0); } /* invalid sequence number */ if (ntohl(lsa->hdr.seq_num) == RESV_SEQ_NUM) { log_warnx("ls_check: bad seq num"); return (0); } switch (lsa->hdr.type) { case LSA_TYPE_ROUTER: if (!lsa_router_check(lsa, len)) return (0); break; case LSA_TYPE_NETWORK: if ((len % sizeof(u_int32_t)) || len < sizeof(lsa->hdr) + sizeof(u_int32_t)) { log_warnx("lsa_check: bad LSA network packet"); return (0); } break; case LSA_TYPE_SUM_NETWORK: case LSA_TYPE_SUM_ROUTER: if ((len % sizeof(u_int32_t)) || len < sizeof(lsa->hdr) + sizeof(lsa->data.sum)) { log_warnx("lsa_check: bad LSA summary packet"); return (0); } metric = ntohl(lsa->data.sum.metric); if (metric & ~LSA_METRIC_MASK) { log_warnx("lsa_check: bad LSA summary metric"); return (0); } break; case LSA_TYPE_EXTERNAL: if ((len % (3 * sizeof(u_int32_t))) || len < sizeof(lsa->hdr) + sizeof(lsa->data.asext)) { log_warnx("lsa_check: bad LSA as-external packet"); return (0); } metric = ntohl(lsa->data.asext.metric); if (metric & ~(LSA_METRIC_MASK | LSA_ASEXT_E_FLAG)) { log_warnx("lsa_check: bad LSA as-external metric"); return (0); } /* AS-external-LSA are silently discarded in stub areas */ if (area->stub) return (0); break; default: log_warnx("lsa_check: unknown type %u", lsa->hdr.type); return (0); } /* MaxAge handling */ if (lsa->hdr.age == htons(MAX_AGE) && !nbr->self && lsa_find(area, lsa->hdr.type, lsa->hdr.ls_id, lsa->hdr.adv_rtr) == NULL && !rde_nbr_loading(area)) { /* * if no neighbor in state Exchange or Loading * ack LSA but don't add it. Needs to be a direct ack. */ rde_imsg_compose_ospfe(IMSG_LS_ACK, nbr->peerid, 0, &lsa->hdr, sizeof(struct lsa_hdr)); return (0); } return (1); } int lsa_router_check(struct lsa *lsa, u_int16_t len) { struct lsa_rtr_link *rtr_link; char *buf = (char *)lsa; u_int16_t i, off, nlinks; off = sizeof(lsa->hdr) + sizeof(struct lsa_rtr); if (off > len) { log_warnx("lsa_check: invalid LSA router packet"); return (0); } nlinks = ntohs(lsa->data.rtr.nlinks); for (i = 0; i < nlinks; i++) { rtr_link = (struct lsa_rtr_link *)(buf + off); off += sizeof(struct lsa_rtr_link); if (off > len) { log_warnx("lsa_check: invalid LSA router packet"); return (0); } off += rtr_link->num_tos * sizeof(u_int32_t); if (off > len) { log_warnx("lsa_check: invalid LSA router packet"); return (0); } } if (i != nlinks) { log_warnx("lsa_check: invalid LSA router packet"); return (0); } return (1); } int lsa_self(struct rde_nbr *nbr, struct lsa *new, struct vertex *v) { struct iface *iface; struct lsa *dummy; if (nbr->self) return (0); if (rde_router_id() == new->hdr.adv_rtr) goto self; if (new->hdr.type == LSA_TYPE_NETWORK) LIST_FOREACH(iface, &nbr->area->iface_list, entry) if (iface->addr.s_addr == new->hdr.ls_id) goto self; return (0); self: if (v == NULL) { /* * LSA is no longer announced, remove by premature aging. * The problem is that new may not be altered so a copy * needs to be added to the LSA DB first. */ if ((dummy = malloc(ntohs(new->hdr.len))) == NULL) fatal("lsa_self"); memcpy(dummy, new, ntohs(new->hdr.len)); dummy->hdr.age = htons(MAX_AGE); /* * The clue is that by using the remote nbr as originator * the dummy LSA will be reflooded via the default timeout * handler. */ (void)lsa_add(rde_nbr_self(nbr->area), dummy); return (1); } /* * LSA is still originated, just reflood it. But we need to create * a new instance by setting the LSA sequence number equal to the * one of new and calling lsa_refresh(). Flooding will be done by the * caller. */ v->lsa->hdr.seq_num = new->hdr.seq_num; lsa_refresh(v); return (1); } int lsa_add(struct rde_nbr *nbr, struct lsa *lsa) { struct lsa_tree *tree; struct vertex *new, *old; struct timeval tv, now, res; if (lsa->hdr.type == LSA_TYPE_EXTERNAL) tree = &asext_tree; else tree = &nbr->area->lsa_tree; new = vertex_get(lsa, nbr); old = RB_INSERT(lsa_tree, tree, new); if (old != NULL) { if (old->deleted && evtimer_pending(&old->ev, &tv)) { /* new update added before hold time expired */ gettimeofday(&now, NULL); timersub(&tv, &now, &res); /* remove old LSA and insert new LSA with delay */ vertex_free(old); RB_INSERT(lsa_tree, tree, new); new->deleted = 1; log_debug("lsa_add: removing %p and putting %p on hold " "queue (timeout %d)", old, new, res.tv_sec); if (evtimer_add(&new->ev, &res) != 0) fatal("lsa_add"); return (1); } if (!lsa_equal(new->lsa, old->lsa)) { if (lsa->hdr.type != LSA_TYPE_EXTERNAL) nbr->area->dirty = 1; start_spf_timer(); } vertex_free(old); RB_INSERT(lsa_tree, tree, new); } else { if (lsa->hdr.type != LSA_TYPE_EXTERNAL) nbr->area->dirty = 1; start_spf_timer(); } /* timeout handling either MAX_AGE or LS_REFRESH_TIME */ timerclear(&tv); if (nbr->self && ntohs(new->lsa->hdr.age) == DEFAULT_AGE) tv.tv_sec = LS_REFRESH_TIME; else tv.tv_sec = MAX_AGE - ntohs(new->lsa->hdr.age); if (evtimer_add(&new->ev, &tv) != 0) fatal("lsa_add"); return (0); } void lsa_del(struct rde_nbr *nbr, struct lsa_hdr *lsa) { struct vertex *v; struct timeval tv; v = lsa_find(nbr->area, lsa->type, lsa->ls_id, lsa->adv_rtr); if (v == NULL) return; log_debug("lsa_del: putting %p on hold queue", v); v->deleted = 1; /* hold time to make sure that a new lsa is not added premature */ timerclear(&tv); tv.tv_sec = MIN_LS_INTERVAL; if (evtimer_add(&v->ev, &tv) == -1) fatal("lsa_del"); } void lsa_age(struct vertex *v) { struct timespec tp; time_t now; int d; u_int16_t age; clock_gettime(CLOCK_MONOTONIC, &tp); now = tp.tv_sec; d = now - v->stamp; /* set stamp so that at least new calls work */ v->stamp = now; if (d < 0) { log_warnx("lsa_age: time went backwards"); return; } age = ntohs(v->lsa->hdr.age); if (age + d > MAX_AGE) age = MAX_AGE; else age += d; v->lsa->hdr.age = htons(age); } struct vertex * lsa_find(struct area *area, u_int8_t type, u_int32_t ls_id, u_int32_t adv_rtr) { struct vertex key; struct vertex *v; struct lsa_tree *tree; key.ls_id = ntohl(ls_id); key.adv_rtr = ntohl(adv_rtr); key.type = type; if (type == LSA_TYPE_EXTERNAL) tree = &asext_tree; else tree = &area->lsa_tree; v = RB_FIND(lsa_tree, tree, &key); /* LSA that are deleted are not findable */ if (v && v->deleted) return (NULL); if (v) lsa_age(v); return (v); } struct vertex * lsa_find_net(struct area *area, u_int32_t ls_id) { struct lsa_tree *tree = &area->lsa_tree; struct vertex *v; /* XXX speed me up */ RB_FOREACH(v, lsa_tree, tree) { if (v->lsa->hdr.type == LSA_TYPE_NETWORK && v->lsa->hdr.ls_id == ls_id) { /* LSA that are deleted are not findable */ if (v->deleted) return (NULL); lsa_age(v); return (v); } } return (NULL); } u_int16_t lsa_num_links(struct vertex *v) { switch (v->type) { case LSA_TYPE_ROUTER: return (ntohs(v->lsa->data.rtr.nlinks)); case LSA_TYPE_NETWORK: return ((ntohs(v->lsa->hdr.len) - sizeof(struct lsa_hdr) - sizeof(u_int32_t)) / sizeof(struct lsa_net_link)); default: fatalx("lsa_num_links: invalid LSA type"); } return (0); } void lsa_snap(struct area *area, u_int32_t peerid) { struct lsa_tree *tree = &area->lsa_tree; struct vertex *v; do { RB_FOREACH(v, lsa_tree, tree) { if (v->deleted) continue; lsa_age(v); if (ntohs(v->lsa->hdr.age) >= MAX_AGE) rde_imsg_compose_ospfe(IMSG_LS_UPD, peerid, 0, &v->lsa->hdr, ntohs(v->lsa->hdr.len)); else rde_imsg_compose_ospfe(IMSG_DB_SNAPSHOT, peerid, 0, &v->lsa->hdr, sizeof(struct lsa_hdr)); } if (tree != &area->lsa_tree) break; tree = &asext_tree; } while (1); } void lsa_dump(struct lsa_tree *tree, int imsg_type, pid_t pid) { struct vertex *v; RB_FOREACH(v, lsa_tree, tree) { if (v->deleted) continue; lsa_age(v); switch (imsg_type) { case IMSG_CTL_SHOW_DATABASE: rde_imsg_compose_ospfe(IMSG_CTL_SHOW_DATABASE, 0, pid, &v->lsa->hdr, ntohs(v->lsa->hdr.len)); continue; case IMSG_CTL_SHOW_DB_SELF: if (v->lsa->hdr.adv_rtr == rde_router_id()) break; continue; case IMSG_CTL_SHOW_DB_EXT: if (v->type == LSA_TYPE_EXTERNAL) break; continue; case IMSG_CTL_SHOW_DB_NET: if (v->type == LSA_TYPE_NETWORK) break; continue; case IMSG_CTL_SHOW_DB_RTR: if (v->type == LSA_TYPE_ROUTER) break; continue; case IMSG_CTL_SHOW_DB_SUM: if (v->type == LSA_TYPE_SUM_NETWORK) break; continue; case IMSG_CTL_SHOW_DB_ASBR: if (v->type == LSA_TYPE_SUM_ROUTER) break; continue; default: log_debug("%d", imsg_type); log_warnx("lsa_dump: unknown imsg type"); return; } rde_imsg_compose_ospfe(imsg_type, 0, pid, &v->lsa->hdr, ntohs(v->lsa->hdr.len)); } } /* ARGSUSED */ void lsa_timeout(int fd, short event, void *bula) { struct vertex *v = bula; struct timeval tv; lsa_age(v); if (v->deleted) { if (ntohs(v->lsa->hdr.age) >= MAX_AGE) { log_debug("lsa_timeout: finally free %p", v); vertex_free(v); } else { v->deleted = 0; /* schedule recalculation of the RIB */ if (v->lsa->hdr.type != LSA_TYPE_EXTERNAL) v->nbr->area->dirty = 1; start_spf_timer(); rde_imsg_compose_ospfe(IMSG_LS_FLOOD, v->nbr->peerid, 0, v->lsa, ntohs(v->lsa->hdr.len)); /* timeout handling either MAX_AGE or LS_REFRESH_TIME */ timerclear(&tv); if (v->nbr->self) tv.tv_sec = LS_REFRESH_TIME; else tv.tv_sec = MAX_AGE - ntohs(v->lsa->hdr.age); log_debug("lsa_timeout: flodding %p new timeout %d", v, tv.tv_sec); if (evtimer_add(&v->ev, &tv) != 0) fatal("lsa_add"); } return; } if (v->nbr->self && ntohs(v->lsa->hdr.age) < MAX_AGE) lsa_refresh(v); rde_imsg_compose_ospfe(IMSG_LS_FLOOD, v->nbr->peerid, 0, v->lsa, ntohs(v->lsa->hdr.len)); } void lsa_refresh(struct vertex *v) { struct timeval tv; struct timespec tp; u_int32_t seqnum; u_int16_t len; /* refresh LSA by increasing sequence number by one */ v->lsa->hdr.age = htons(DEFAULT_AGE); seqnum = ntohl(v->lsa->hdr.seq_num); if (seqnum++ == MAX_SEQ_NUM) /* XXX fix me */ fatalx("sequence number wrapping"); v->lsa->hdr.seq_num = htonl(seqnum); /* recalculate checksum */ len = ntohs(v->lsa->hdr.len); v->lsa->hdr.ls_chksum = 0; v->lsa->hdr.ls_chksum = htons(iso_cksum(v->lsa, len, LS_CKSUM_OFFSET)); clock_gettime(CLOCK_MONOTONIC, &tp); v->changed = v->stamp = tp.tv_sec; timerclear(&tv); tv.tv_sec = LS_REFRESH_TIME; if (evtimer_add(&v->ev, &tv) == -1) fatal("lsa_refresh"); } void lsa_merge(struct rde_nbr *nbr, struct lsa *lsa, struct vertex *v) { struct timeval tv; struct timespec tp; time_t now; u_int16_t len; if (v == NULL) { if (lsa_add(nbr, lsa)) /* delayed update */ return; rde_imsg_compose_ospfe(IMSG_LS_FLOOD, nbr->peerid, 0, lsa, ntohs(lsa->hdr.len)); return; } /* set the seq_num to the current one. lsa_refresh() will do the ++ */ lsa->hdr.seq_num = v->lsa->hdr.seq_num; /* recalculate checksum */ len = ntohs(lsa->hdr.len); lsa->hdr.ls_chksum = 0; lsa->hdr.ls_chksum = htons(iso_cksum(lsa, len, LS_CKSUM_OFFSET)); /* compare LSA most header fields are equal so don't check them */ if (lsa_equal(lsa, v->lsa)) { free(lsa); return; } /* overwrite the lsa all other fields are unaffected */ free(v->lsa); v->lsa = lsa; start_spf_timer(); if (lsa->hdr.type != LSA_TYPE_EXTERNAL) nbr->area->dirty = 1; /* set correct timeout for reflooding the LSA */ clock_gettime(CLOCK_MONOTONIC, &tp); now = tp.tv_sec; timerclear(&tv); if (v->changed + MIN_LS_INTERVAL >= now) tv.tv_sec = MIN_LS_INTERVAL; if (evtimer_add(&v->ev, &tv) == -1) fatal("lsa_merge"); } void lsa_remove_invalid_sums(struct area *area) { struct lsa_tree *tree = &area->lsa_tree; struct vertex *v, *nv; /* XXX speed me up */ for (v = RB_MIN(lsa_tree, tree); v != NULL; v = nv) { nv = RB_NEXT(lsa_tree, tree, v); if ((v->lsa->hdr.type == LSA_TYPE_SUM_NETWORK || v->lsa->hdr.type == LSA_TYPE_SUM_ROUTER) && v->nbr->self && v->cost == LS_INFINITY && v->deleted == 0) { /* * age the lsa and call lsa_timeout() which will * actually remove it from the database. */ v->lsa->hdr.age = htons(MAX_AGE); lsa_timeout(0, 0, v); } } } int lsa_equal(struct lsa *a, struct lsa *b) { /* * compare LSA that already have same type, adv_rtr and ls_id * so not all header need to be compared */ if (a == NULL || b == NULL) return (0); if (a->hdr.len != b->hdr.len) return (0); if (a->hdr.opts != b->hdr.opts) return (0); /* LSA with age MAX_AGE are never equal */ if (a->hdr.age == htons(MAX_AGE) || b->hdr.age == htons(MAX_AGE)) return (0); if (memcmp(&a->data, &b->data, ntohs(a->hdr.len) - sizeof(struct lsa_hdr))) return (0); return (1); }