/* * BIRD -- OSPF Topological Database * * (c) 1999 Martin Mares * (c) 1999--2004 Ondrej Filip * * Can be freely distributed and used under the terms of the GNU GPL. */ #include "nest/bird.h" #include "lib/string.h" #include "ospf.h" #define HASH_DEF_ORDER 6 #define HASH_HI_MARK *4 #define HASH_HI_STEP 2 #define HASH_HI_MAX 16 #define HASH_LO_MARK /5 #define HASH_LO_STEP 2 #define HASH_LO_MIN 8 static void * originate_rt_lsa_body(struct ospf_area *oa, u16 * length) { struct proto_ospf *po = oa->po; struct ospf_iface *ifa; int j = 0, k = 0; u16 i = 0; struct ospf_lsa_rt *rt; struct ospf_lsa_rt_link *ln; struct ospf_neighbor *neigh; DBG("%s: Originating RT_lsa body for area \"%I\".\n", po->proto.name, oa->areaid); WALK_LIST(ifa, po->iface_list) { if ((ifa->oa == oa) && (ifa->state != OSPF_IS_DOWN)) { i++; } } rt = mb_allocz(po->proto.pool, sizeof(struct ospf_lsa_rt) + i * sizeof(struct ospf_lsa_rt_link)); if (po->areano > 1) rt->veb.bit.b = 1; if ((po->ebit) && (!oa->stub)) rt->veb.bit.e = 1; ln = (struct ospf_lsa_rt_link *) (rt + 1); WALK_LIST(ifa, po->iface_list) { if ((ifa->type == OSPF_IT_VLINK) && (ifa->voa == oa) && (ifa->state > OSPF_IS_DOWN)) rt->veb.bit.v = 1; if ((ifa->oa != oa) || (ifa->state == OSPF_IS_DOWN)) continue; if (ifa->state == OSPF_IS_LOOP) { ln->type = 3; ln->id = ipa_to_u32(ifa->iface->addr->ip); ln->data = 0xffffffff; ln->metric = 0; ln->notos = 0; } else { switch (ifa->type) { case OSPF_IT_PTP: /* rfc2328 - pg126 */ neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list); if ((!EMPTY_LIST(ifa->neigh_list)) && (neigh->state == NEIGHBOR_FULL)) { ln->type = LSART_PTP; ln->id = neigh->rid; ln->metric = ifa->cost; ln->notos = 0; if (ifa->iface->flags && IA_UNNUMBERED) { ln->data = ifa->iface->index; } else { ln->data = ipa_to_u32(ifa->iface->addr->ip); } } else { if (ifa->state == OSPF_IS_PTP) { ln->type = LSART_STUB; ln->id = ln->id = ipa_to_u32(ifa->iface->addr->opposite); ln->metric = ifa->cost; ln->notos = 0; ln->data = 0xffffffff; } else { ln--; i--; /* No link added */ } } break; case OSPF_IT_BCAST: case OSPF_IT_NBMA: if (ifa->state == OSPF_IS_WAITING) { ln->type = LSART_STUB; ln->id = ipa_to_u32(ifa->iface->addr->prefix); ln->data = ipa_to_u32(ipa_mkmask(ifa->iface->addr->pxlen)); ln->metric = ifa->cost; ln->notos = 0; } else { j = 0, k = 0; WALK_LIST(neigh, ifa->neigh_list) { if ((neigh->rid == ifa->drid) && (neigh->state == NEIGHBOR_FULL)) k = 1; if (neigh->state == NEIGHBOR_FULL) j = 1; } if (((ifa->state == OSPF_IS_DR) && (j == 1)) || (k == 1)) { ln->type = LSART_NET; ln->id = ipa_to_u32(ifa->drip); ln->data = ipa_to_u32(ifa->iface->addr->ip); ln->metric = ifa->cost; ln->notos = 0; } else { ln->type = LSART_STUB; ln->id = ipa_to_u32(ifa->iface->addr->prefix); ln->data = ipa_to_u32(ipa_mkmask(ifa->iface->addr->pxlen)); ln->metric = ifa->cost; ln->notos = 0; } } break; case OSPF_IT_VLINK: neigh = (struct ospf_neighbor *) HEAD(ifa->neigh_list); if ((!EMPTY_LIST(ifa->neigh_list)) && (neigh->state == NEIGHBOR_FULL) && (ifa->cost <= 0xffff)) { ln->type = LSART_VLNK; ln->id = neigh->rid; ln->metric = ifa->cost; ln->notos = 0; } else { ln--; i--; /* No link added */ } break; default: ln--; i--; /* No link added */ log("Unknown interface type"); break; } } ln++; } rt->links = i; *length = i * sizeof(struct ospf_lsa_rt_link) + sizeof(struct ospf_lsa_rt) + sizeof(struct ospf_lsa_header); return rt; } /** * originate_rt_lsa - build new instance of router LSA * @oa: ospf_area which is LSA built to * * It builds router LSA walking through all OSPF interfaces in * specified OSPF area. This function is mostly called from * area_disp(). Builds new LSA, increases sequence number (if old * instance exists) and sets age of LSA to zero. */ void originate_rt_lsa(struct ospf_area *oa) { struct ospf_lsa_header lsa; struct proto_ospf *po = oa->po; struct proto *p = &po->proto; u32 rtid = po->proto.cf->global->router_id; struct top_hash_entry *en; void *body; if ((oa->rt) && ((oa->rt->inst_t + MINLSINTERVAL)) > now) return; /* * Tick is probably set to very low value. We cannot * originate new LSA before MINLSINTERVAL. We will * try to do it next tick. */ OSPF_TRACE(D_EVENTS, "Originating RT_lsa for area \"%I\".", oa->areaid); lsa.age = 0; lsa.id = rtid; lsa.type = LSA_T_RT; lsa.rt = rtid; lsa.options = oa->opt.byte; if (oa->rt == NULL) { lsa.sn = LSA_INITSEQNO; } else { lsa.sn = oa->rt->lsa.sn + 1; } body = originate_rt_lsa_body(oa, &lsa.length); lsasum_calculate(&lsa, body); en = lsa_install_new(&lsa, body, oa); oa->rt = en; en->dist = 0; /* Force area aging */ ospf_lsupd_flood(NULL, NULL, &oa->rt->lsa, NULL, oa, 1); schedule_rtcalc(po); oa->origrt = 0; } static void * originate_net_lsa_body(struct ospf_iface *ifa, u16 * length, struct proto_ospf *po) { u16 i = 1; struct ospf_neighbor *n; u32 *body; struct ospf_lsa_net *net; net = mb_alloc(po->proto.pool, sizeof(u32) * (ifa->fadj + 1) + sizeof(struct ospf_lsa_net)); net->netmask = ipa_mkmask(ifa->iface->addr->pxlen); body = (u32 *) (net + 1); i = 1; *body = po->proto.cf->global->router_id; WALK_LIST(n, ifa->neigh_list) { if (n->state == NEIGHBOR_FULL) { *(body + i) = n->rid; i++; } } *length = i * sizeof(u32) + sizeof(struct ospf_lsa_header) + sizeof(struct ospf_lsa_net); return net; } /** * originate_net_lsa - originates of deletes network LSA * @ifa: interface which is LSA originated for * * Interface counts number of adjacent neighbors. If this number is * lower than one or interface is not in state %OSPF_IS_DR it deletes * and premature ages instance of network LSA for specified interface. * In other case, new instance of network LSA is originated. */ void originate_net_lsa(struct ospf_iface *ifa) { struct proto_ospf *po = ifa->proto; struct ospf_lsa_header lsa; u32 rtid = po->proto.cf->global->router_id; struct proto *p = &po->proto; void *body; if (ifa->nlsa && ((ifa->nlsa->inst_t + MINLSINTERVAL) > now)) return; /* * It's too early to originate new network LSA. We will * try to do it next tick */ if ((ifa->state != OSPF_IS_DR) || (ifa->fadj == 0)) { if (ifa->nlsa == NULL) return; OSPF_TRACE(D_EVENTS, "Deleting Net lsa for iface \"%s\".", ifa->iface->name); ifa->nlsa->lsa.sn += 1; ifa->nlsa->lsa.age = LSA_MAXAGE; ospf_lsupd_flood(NULL, NULL, &ifa->nlsa->lsa, NULL, ifa->oa, 0); s_rem_node(SNODE ifa->nlsa); if (ifa->nlsa->lsa_body != NULL) mb_free(ifa->nlsa->lsa_body); ifa->nlsa->lsa_body = NULL; ospf_hash_delete(ifa->oa->gr, ifa->nlsa); schedule_rtcalc(po); ifa->nlsa = NULL; return; } OSPF_TRACE(D_EVENTS, "Originating Net lsa for iface \"%s\".", ifa->iface->name); lsa.age = 0; lsa.id = ipa_to_u32(ifa->iface->addr->ip); lsa.type = LSA_T_NET; lsa.rt = rtid; lsa.options = ifa->oa->opt.byte; if (ifa->nlsa == NULL) { lsa.sn = LSA_INITSEQNO; } else { lsa.sn = ifa->nlsa->lsa.sn + 1; } body = originate_net_lsa_body(ifa, &lsa.length, po); lsasum_calculate(&lsa, body); ifa->nlsa = lsa_install_new(&lsa, body, ifa->oa); ospf_lsupd_flood(NULL, NULL, &ifa->nlsa->lsa, NULL, ifa->oa, 1); ifa->orignet = 0; } static void * originate_ext_lsa_body(net * n, rte * e, struct proto_ospf *po, struct ea_list *attrs) { struct proto *p = &po->proto; struct ospf_lsa_ext *ext; struct ospf_lsa_ext_tos *et; u32 m1 = ea_get_int(attrs, EA_OSPF_METRIC1, LSINFINITY); u32 m2 = ea_get_int(attrs, EA_OSPF_METRIC2, 10000); u32 tag = ea_get_int(attrs, EA_OSPF_TAG, 0); int inas = 0; ext = mb_alloc(p->pool, sizeof(struct ospf_lsa_ext) + sizeof(struct ospf_lsa_ext_tos)); ext->netmask = ipa_mkmask(n->n.pxlen); et = (struct ospf_lsa_ext_tos *) (ext + 1); if (m1 != LSINFINITY) { et->etm.metric = m1; et->etm.etos.tos = 0; et->etm.etos.ebit = 0; } else { et->etm.metric = m2; et->etm.etos.tos = 0; et->etm.etos.ebit = 1; } et->tag = tag; if (!ipa_equal(e->attrs->gw, IPA_NONE)) { if (ospf_iface_find((struct proto_ospf *) p, e->attrs->iface) != NULL) inas = 1; } if (!inas) et->fwaddr = IPA_NONE; else et->fwaddr = e->attrs->gw; return ext; } /** * max_ext_lsa - calculate the maximum amount of external networks * possible for the given prefix length. * @pxlen: network prefix length * * This is a fix for the previous static use of MAXNETS which did * only work correct if MAXNETS < possible IPs for given prefix. * This solution is kind of a hack as there can now only be one * route for /32 type entries but this is better than the crashes * I did experience whith close together /32 routes originating * on different hosts. */ int max_ext_lsa(unsigned pxlen) { int i; for (i = 1; pxlen < BITS_PER_IP_ADDRESS; pxlen++, i <<= 1) if (i >= MAXNETS) return MAXNETS; return i; } void flush_sum_lsa(struct ospf_area *oa, struct fib_node *fn, int type) { struct proto_ospf *po = oa->po; struct proto *p = &po->proto; struct top_hash_entry *en; u32 rtid = po->proto.cf->global->router_id; struct ospf_lsa_header lsa; int max, i; struct ospf_lsa_sum *sum = NULL; union ospf_lsa_sum_tm *tm; lsa.rt = rtid; lsa.type = LSA_T_SUM_NET; if (type == ORT_ROUTER) lsa.type = LSA_T_SUM_RT; max = max_ext_lsa(fn->pxlen); for (i = 0; i < max; i++) { lsa.id = ipa_to_u32(fn->prefix) + i; if ((en = ospf_hash_find_header(oa->gr, &lsa)) != NULL) { sum = en->lsa_body; if (fn->pxlen == ipa_mklen(sum->netmask)) { en->lsa.age = LSA_MAXAGE; en->lsa.sn = LSA_MAXSEQNO; lsasum_calculate(&en->lsa, sum); OSPF_TRACE(D_EVENTS, "Flushing summary lsa. (id=%I, type=%d)", en->lsa.id, en->lsa.type); ospf_lsupd_flood(NULL, NULL, &en->lsa, NULL, oa, 1); if (can_flush_lsa(oa)) flush_lsa(en, oa); break; } } } } void originate_sum_lsa(struct ospf_area *oa, struct fib_node *fn, int type, int metric) { struct proto_ospf *po = oa->po; struct proto *p = &po->proto; struct top_hash_entry *en; u32 rtid = po->proto.cf->global->router_id; struct ospf_lsa_header lsa; int i, max, mlen = fn->pxlen, free = 0; struct ospf_lsa_sum *sum = NULL; union ospf_lsa_sum_tm *tm; lsa.type = LSA_T_SUM_NET; if (type == ORT_ROUTER) { lsa.type = LSA_T_SUM_RT; mlen = 0; } lsa.age = 0; lsa.rt = rtid; lsa.sn = LSA_INITSEQNO; lsa.length = sizeof(struct ospf_lsa_sum) + sizeof(union ospf_lsa_sum_tm) + sizeof(struct ospf_lsa_header); lsa.options = oa->opt.byte; max = max_ext_lsa(fn->pxlen); for (i = 0; i < max; i++) { lsa.id = ipa_to_u32(fn->prefix) + i; if ((en = ospf_hash_find_header(oa->gr, &lsa)) == NULL) { if (!free) free = lsa.id; } else { sum = en->lsa_body; if (mlen == ipa_mklen(sum->netmask)) { tm = (union ospf_lsa_sum_tm *) (sum + 1); if (tm->metric == (unsigned)metric) return; /* No reason for origination */ lsa.sn = en->lsa.sn + 1; free = en->lsa.id; break; } } } if(!free) { log("%s: got more routes for one /%d network then %d, ignoring", p->name, fn->pxlen, max); return; } lsa.id = free; OSPF_TRACE(D_EVENTS, "Originating summary (type %d) lsa for %I/%d.", lsa.type, fn->prefix, fn->pxlen); sum = mb_alloc(p->pool, sizeof(struct ospf_lsa_sum) + sizeof(union ospf_lsa_sum_tm)); sum->netmask = ipa_mkmask(mlen); tm = (union ospf_lsa_sum_tm *) (sum + 1); tm->metric = metric; tm->tos.tos = 0; lsasum_calculate(&lsa, sum); en = lsa_install_new(&lsa, sum, oa); ospf_lsupd_flood(NULL, NULL, &en->lsa, NULL, oa, 1); } void check_sum_lsa(struct proto_ospf *po, ort *nf, int dest) { struct proto *p = &po->proto; struct ospf_area *oa; struct area_net *anet; int flush, mlen; ip_addr ip; if (po->areano < 2) return; if ((nf->n.type > RTS_OSPF_IA) && (nf->o.type > RTS_OSPF_IA)) return; #ifdef LOCAL_DEBUG DBG("Checking...dest = %d, %I/%d", dest, nf->fn.prefix, nf->fn.pxlen); if (nf->n.oa) DBG("New: met=%d, oa=%d", nf->n.metric1, nf->n.oa->areaid); if (nf->o.oa) DBG("Old: met=%d, oa=%d", nf->o.metric1, nf->o.oa->areaid); #endif WALK_LIST(oa, po->area_list) { flush = 0; if ((nf->n.metric1 >= LSINFINITY) || (nf->n.type > RTS_OSPF_IA)) flush = 1; if ((dest == ORT_ROUTER) && (!(nf->n.capa & ORTA_ABR))) flush = 1; if ((!nf->n.oa) || (nf->n.oa->areaid == oa->areaid)) flush = 1; /* FIXME: Test next hop - is it in actual area? */ if ((dest == ORT_ROUTER) && oa->stub) flush = 1; /* FIXME stub for networks? */ mlen = nf->fn.pxlen; ip = ipa_and(nf->fn.prefix, ipa_mkmask(mlen)); if ((!flush) && (!nf->n.oa->trcap) && fib_route(&nf->n.oa->net_fib, ip, mlen)) /* The route fits into area networks */ flush = 1; if(flush) /* FIXME Go on... */ { flush_sum_lsa(oa, &nf->fn, dest); continue; } originate_sum_lsa(oa, &nf->fn, dest, nf->n.metric1); } } /** * originate_ext_lsa - new route received from nest and filters * @n: network prefix and mask * @e: rte * @po: current instance of OSPF * @attrs: list of extended attributes * * If I receive a message that new route is installed, I try to originate an * external LSA. The LSA header of such LSA does not contain information about * prefix length, so if I have to originate multiple LSAs for route with * different prefixes I try to increment prefix id to find a "free" one. * * The function also sets flag ebit. If it's the first time, the new router lsa * origination is necessary. */ void originate_ext_lsa(net * n, rte * e, struct proto_ospf *po, struct ea_list *attrs) { struct ospf_lsa_header lsa; u32 rtid = po->proto.cf->global->router_id; struct top_hash_entry *en = NULL; void *body = NULL; struct proto *p = &po->proto; struct ospf_area *oa; struct ospf_lsa_ext *ext1, *ext2; int i, max; OSPF_TRACE(D_EVENTS, "Originating Ext lsa for %I/%d.", n->n.prefix, n->n.pxlen); lsa.age = 0; lsa.id = ipa_to_u32(n->n.prefix); lsa.type = LSA_T_EXT; lsa.rt = rtid; lsa.sn = LSA_INITSEQNO; lsa.options = 0; body = originate_ext_lsa_body(n, e, po, attrs); lsa.length = sizeof(struct ospf_lsa_ext) + sizeof(struct ospf_lsa_ext_tos) + sizeof(struct ospf_lsa_header); ext1 = body; max = max_ext_lsa(n->n.pxlen); oa = HEAD(po->area_list); for (i = 0; i < max; i++) { if ((en = ospf_hash_find_header(oa->gr, &lsa)) != NULL) { ext2 = en->lsa_body; if (ipa_compare(ext1->netmask, ext2->netmask) != 0) lsa.id++; else break; } else break; } if (i == max) { log("%s: got more routes for one /%d network then %d, ignoring", p->name, n->n.pxlen, max); mb_free(body); return; } lsasum_calculate(&lsa, body); WALK_LIST(oa, po->area_list) { if (!oa->stub) { en = lsa_install_new(&lsa, body, oa); ospf_lsupd_flood(NULL, NULL, &en->lsa, NULL, oa, 1); body = originate_ext_lsa_body(n, e, po, attrs); } } mb_free(body); if (po->ebit == 0) { po->ebit = 1; WALK_LIST(oa, po->area_list) { schedule_rt_lsa(oa); } } } static void ospf_top_ht_alloc(struct top_graph *f) { f->hash_size = 1 << f->hash_order; f->hash_mask = f->hash_size - 1; if (f->hash_order > HASH_HI_MAX - HASH_HI_STEP) f->hash_entries_max = ~0; else f->hash_entries_max = f->hash_size HASH_HI_MARK; if (f->hash_order < HASH_LO_MIN + HASH_LO_STEP) f->hash_entries_min = 0; else f->hash_entries_min = f->hash_size HASH_LO_MARK; DBG("Allocating OSPF hash of order %d: %d hash_entries, %d low, %d high\n", f->hash_order, f->hash_size, f->hash_entries_min, f->hash_entries_max); f->hash_table = mb_alloc(f->pool, f->hash_size * sizeof(struct top_hash_entry *)); bzero(f->hash_table, f->hash_size * sizeof(struct top_hash_entry *)); } static inline void ospf_top_ht_free(struct top_hash_entry **h) { mb_free(h); } static inline u32 ospf_top_hash_u32(u32 a) { /* Shamelessly stolen from IP address hashing in ipv4.h */ a ^= a >> 16; a ^= a << 10; return a; } static inline unsigned ospf_top_hash(struct top_graph *f, u32 lsaid, u32 rtrid, u32 type) { #if 1 /* Dirty patch to make rt table calculation work. */ return (ospf_top_hash_u32(lsaid) + ospf_top_hash_u32((type == LSA_T_NET) ? lsaid : rtrid) + type) & f->hash_mask; #else return (ospf_top_hash_u32(lsaid) + ospf_top_hash_u32(rtrid) + type) & f->hash_mask; #endif } /** * ospf_top_new - allocated new topology database * @p: current instance of OSPF * * This dynamically hashed structure is often used for keeping LSAs. Mainly * its used in @ospf_area structure. */ struct top_graph * ospf_top_new(pool *pool) { struct top_graph *f; f = mb_allocz(pool, sizeof(struct top_graph)); f->pool = pool; f->hash_slab = sl_new(f->pool, sizeof(struct top_hash_entry)); f->hash_order = HASH_DEF_ORDER; ospf_top_ht_alloc(f); f->hash_entries = 0; f->hash_entries_min = 0; return f; } void ospf_top_free(struct top_graph *f) { rfree(f->hash_slab); ospf_top_ht_free(f->hash_table); mb_free(f); } static void ospf_top_rehash(struct top_graph *f, int step) { unsigned int oldn, oldh; struct top_hash_entry **n, **oldt, **newt, *e, *x; oldn = f->hash_size; oldt = f->hash_table; DBG("Re-hashing topology hash from order %d to %d\n", f->hash_order, f->hash_order + step); f->hash_order += step; ospf_top_ht_alloc(f); newt = f->hash_table; for (oldh = 0; oldh < oldn; oldh++) { e = oldt[oldh]; while (e) { x = e->next; n = newt + ospf_top_hash(f, e->lsa.id, e->lsa.rt, e->lsa.type); e->next = *n; *n = e; e = x; } } ospf_top_ht_free(oldt); } struct top_hash_entry * ospf_hash_find_header(struct top_graph *f, struct ospf_lsa_header *h) { return ospf_hash_find(f, h->id, h->rt, h->type); } struct top_hash_entry * ospf_hash_get_header(struct top_graph *f, struct ospf_lsa_header *h) { return ospf_hash_get(f, h->id, h->rt, h->type); } struct top_hash_entry * ospf_hash_find(struct top_graph *f, u32 lsa, u32 rtr, u32 type) { struct top_hash_entry *e = f->hash_table[ospf_top_hash(f, lsa, rtr, type)]; #if 1 /* Dirty patch to make rt table calculation work. */ if (type == LSA_T_NET) { while (e && (e->lsa.id != lsa || e->lsa.type != LSA_T_NET)) e = e->next; } else { while (e && (e->lsa.id != lsa || e->lsa.type != type || e->lsa.rt != rtr)) e = e->next; } #else while (e && (e->lsa.id != lsa || e->lsa.rt != rtr || e->lsa.type != type)) e = e->next; #endif return e; } struct top_hash_entry * ospf_hash_get(struct top_graph *f, u32 lsa, u32 rtr, u32 type) { struct top_hash_entry **ee = f->hash_table + ospf_top_hash(f, lsa, rtr, type); struct top_hash_entry *e = *ee; while (e && (e->lsa.id != lsa || e->lsa.rt != rtr || e->lsa.type != type)) e = e->next; if (e) return e; e = sl_alloc(f->hash_slab); e->color = OUTSPF; e->dist = LSINFINITY; e->nhi = NULL; e->nh = IPA_NONE; e->lb = IPA_NONE; e->lsa.id = lsa; e->lsa.rt = rtr; e->lsa.type = type; e->lsa_body = NULL; e->nhi = NULL; e->next = *ee; *ee = e; if (f->hash_entries++ > f->hash_entries_max) ospf_top_rehash(f, HASH_HI_STEP); return e; } void ospf_hash_delete(struct top_graph *f, struct top_hash_entry *e) { unsigned int h = ospf_top_hash(f, e->lsa.id, e->lsa.rt, e->lsa.type); struct top_hash_entry **ee = f->hash_table + h; while (*ee) { if (*ee == e) { *ee = e->next; sl_free(f->hash_slab, e); if (f->hash_entries-- < f->hash_entries_min) ospf_top_rehash(f, -HASH_LO_STEP); return; } ee = &((*ee)->next); } bug("ospf_hash_delete() called for invalid node"); } void ospf_top_dump(struct top_graph *f, struct proto *p) { unsigned int i; OSPF_TRACE(D_EVENTS, "Hash entries: %d", f->hash_entries); for (i = 0; i < f->hash_size; i++) { struct top_hash_entry *e = f->hash_table[i]; while (e) { OSPF_TRACE(D_EVENTS, "\t%1x %-1I %-1I %4u 0x%08x 0x%04x", e->lsa.type, e->lsa.id, e->lsa.rt, e->lsa.age, e->lsa.sn, e->lsa.checksum); e = e->next; } } } /* This is very inefficient, please don't call it often */ /* I should also test for every LSA if it's in some link state * retransmission list for every neighbor. I will not test it. * It could happen that I'll receive some strange ls ack's. */ int can_flush_lsa(struct ospf_area *oa) { struct ospf_iface *ifa; struct ospf_neighbor *n; WALK_LIST(ifa, iface_list) { if (ifa->oa == oa) { WALK_LIST(n, ifa->neigh_list) { if ((n->state == NEIGHBOR_EXCHANGE) || (n->state == NEIGHBOR_LOADING)) { return 0; } } break; } } return 1; }