[PATCH 2/2] Remember sequence number for LSAs.
--- proto/ospf/iface.c | 2 +- proto/ospf/lsalib.c | 5 +- proto/ospf/lsupd.c | 15 +-- proto/ospf/lsupd.h | 2 +- proto/ospf/ospf.c | 8 ++ proto/ospf/ospf.h | 4 + proto/ospf/topology.c | 306 +++++++++++++++++++++++++++++++++++++++++++------- proto/ospf/topology.h | 17 +++ 8 files changed, 301 insertions(+), 58 deletions(-) diff --git a/proto/ospf/iface.c b/proto/ospf/iface.c index f4d9be5..171cf7e 100644 --- a/proto/ospf/iface.c +++ b/proto/ospf/iface.c @@ -318,7 +318,7 @@ ospf_iface_chstate(struct ospf_iface *ifa, u8 state) { ifa->net_lsa->lsa.age = LSA_MAXAGE; if (state >= OSPF_IS_WAITING) - ospf_lsupd_flush_nlsa(po, ifa->net_lsa); + ospf_lsupd_flush_nlsa(po, ifa->oa, ifa->net_lsa); if (can_flush_lsa(po)) flush_lsa(ifa->net_lsa, po); diff --git a/proto/ospf/lsalib.c b/proto/ospf/lsalib.c index bcf7bcd..2b238e3 100644 --- a/proto/ospf/lsalib.c +++ b/proto/ospf/lsalib.c @@ -69,10 +69,7 @@ ospf_age(struct proto_ospf *po) { OSPF_TRACE(D_EVENTS, "Refreshing my LSA: Type: %u, Id: %R, Rt: %R", en->lsa.type, en->lsa.id, en->lsa.rt); - en->lsa.sn++; - en->lsa.age = 0; - en->inst_t = now; - en->ini_age = 0; + update_lsa_seq(po, en, en->lsa.sn + 1); /* XXX: LSA_MAXSEQNO */ lsasum_calculate(&en->lsa, en->lsa_body); ospf_lsupd_flood(po, NULL, NULL, &en->lsa, en->domain, 1); continue; diff --git a/proto/ospf/lsupd.c b/proto/ospf/lsupd.c index 8f65c53..65c9ae4 100644 --- a/proto/ospf/lsupd.c +++ b/proto/ospf/lsupd.c @@ -600,20 +600,14 @@ ospf_lsupd_receive(struct ospf_packet *ps_i, struct ospf_iface *ifa, if (lsadb) { OSPF_TRACE(D_EVENTS, "Reflooding new self-originated LSA with newer sequence number"); - lsadb->lsa.sn = lsatmp.sn + 1; - lsadb->lsa.age = 0; - lsadb->inst_t = now; - lsadb->ini_age = 0; - lsasum_calculate(&lsadb->lsa, lsadb->lsa_body); + update_lsa_seq(po, lsadb, lsatmp.sn + 1); /* XXX: lsatmp.sn == LSA_MAXSEQNO - 1 */ ospf_lsupd_flood(po, NULL, NULL, &lsadb->lsa, domain, 1); } else { OSPF_TRACE(D_EVENTS, "Premature aging it"); lsatmp.age = LSA_MAXAGE; - lsatmp.sn = LSA_MAXSEQNO; lsa->age = htons(LSA_MAXAGE); - lsa->sn = htonl(LSA_MAXSEQNO); lsasum_check(lsa, (lsa + 1)); /* It also calculates chsum! */ lsatmp.checksum = ntohs(lsa->checksum); ospf_lsupd_flood(po, NULL, lsa, &lsatmp, domain, 0); @@ -749,14 +743,13 @@ ospf_lsupd_receive(struct ospf_packet *ps_i, struct ospf_iface *ifa, } void -ospf_lsupd_flush_nlsa(struct proto_ospf *po, struct top_hash_entry *en) +ospf_lsupd_flush_nlsa(struct proto_ospf *po, struct ospf_area *oa, struct top_hash_entry *en) { struct ospf_lsa_header *lsa = &en->lsa; struct proto *p = &po->proto; - lsa->age = LSA_MAXAGE; - lsa->sn = LSA_MAXSEQNO; - lsasum_calculate(lsa, en->lsa_body); + flush_lsa_seq(po, oa, en); + OSPF_TRACE(D_EVENTS, "Premature aging self originated lsa!"); OSPF_TRACE(D_EVENTS, "Type: %04x, Id: %R, Rt: %R", lsa->type, lsa->id, lsa->rt); ospf_lsupd_flood(po, NULL, NULL, lsa, en->domain, 0); diff --git a/proto/ospf/lsupd.h b/proto/ospf/lsupd.h index 8bacfe6..780ba9d 100644 --- a/proto/ospf/lsupd.h +++ b/proto/ospf/lsupd.h @@ -18,7 +18,7 @@ void ospf_lsupd_receive(struct ospf_packet *ps_i, int ospf_lsupd_flood(struct proto_ospf *po, struct ospf_neighbor *n, struct ospf_lsa_header *hn, struct ospf_lsa_header *hh, u32 domain, int rtl); -void ospf_lsupd_flush_nlsa(struct proto_ospf *po, struct top_hash_entry *en); +void ospf_lsupd_flush_nlsa(struct proto_ospf *po, struct ospf_area *oa, struct top_hash_entry *en); int ospf_lsa_flooding_allowed(struct ospf_lsa_header *lsa, u32 domain, struct ospf_iface *ifa); diff --git a/proto/ospf/ospf.c b/proto/ospf/ospf.c index e751f7c..0684de9 100644 --- a/proto/ospf/ospf.c +++ b/proto/ospf/ospf.c @@ -178,6 +178,8 @@ ospf_area_add(struct proto_ospf *po, struct ospf_area_config *ac, int reconf) if (oa_is_nssa(oa) && ((struct ospf_config *) (p->cf))->abr) po->ebit = 1; + init_seq_fib(po, oa); + if (reconf) ospf_ifaces_reconfigure(oa, ac); } @@ -198,6 +200,8 @@ ospf_area_remove(struct ospf_area *oa) if (oa->translator_timer) rfree(oa->translator_timer); + free_seq_fib(oa->po, oa); + oa->po->areano--; rem_node(NODE oa); mb_free(oa); @@ -265,6 +269,8 @@ ospf_start(struct proto *p) WALK_LIST(ic, c->vlink_list) ospf_iface_new_vlink(po, ic); + init_seq_fib(po, NULL); + return PS_UP; } @@ -563,6 +569,8 @@ ospf_shutdown(struct proto *p) } FIB_WALK_END; + free_seq_fib(po, NULL); + return PS_DOWN; } diff --git a/proto/ospf/ospf.h b/proto/ospf/ospf.h index 66719e3..44b3344 100644 --- a/proto/ospf/ospf.h +++ b/proto/ospf/ospf.h @@ -756,6 +756,8 @@ struct ospf_area timer *translator_timer; /* For NSSA translator switch */ struct proto_ospf *po; struct fib rtr; /* Routing tables for routers */ + struct fib seq_fib; /* Sequence number storage for LSAs */ + timer *seq_timer; /* Timer for seq_fib pruning */ }; #define TRANS_OFF 0 @@ -786,6 +788,8 @@ struct proto_ospf sock *vlink_sk; /* IP socket used for vlink TX */ u32 router_id; u32 last_vlink_id; /* Interface IDs for vlinks (starts at 0x80000000) */ + struct fib seq_fib; /* Sequence number storage for external LSAs */ + timer *seq_timer; /* Timer for seq_fib pruning */ }; struct ospf_iface_patt diff --git a/proto/ospf/topology.c b/proto/ospf/topology.c index 6498585..cc4c97a 100644 --- a/proto/ospf/topology.c +++ b/proto/ospf/topology.c @@ -27,6 +27,9 @@ void flush_prefix_net_lsa(struct ospf_iface *ifa); static void fill_lsa_header(struct proto_ospf *po, struct ospf_area *oa, struct ospf_lsa_header *lsa, struct fib_node *fn, struct ospf_iface *ifa); +static s32 get_lsa_seq(struct proto_ospf *po, struct ospf_area *oa, struct ospf_lsa_header *lsa); +static void check_seq_fibs(struct timer *timer); + #ifdef OSPFv2 #define ipa_to_rid(x) _I(x) #else /* OSPFv3 */ @@ -141,23 +144,6 @@ lsab_end(struct proto_ospf *po) return ((byte *) po->lsab) + po->lsab_used; } -static s32 -get_seqnum(struct top_hash_entry *en) -{ - if (!en) - return LSA_INITSEQNO; - - if (en->lsa.sn == LSA_MAXSEQNO) - { - log(L_WARN "OSPF: Premature origination of LSA (Type: %04x, Id: %R, Rt: %R)", - en->lsa.type, en->lsa.id, en->lsa.rt); - return LSA_INITSEQNO; - } - - return en->lsa.sn + 1; -} - - static int configured_stubnet(struct ospf_area *oa, struct ifa *a) { @@ -490,7 +476,7 @@ originate_rt_lsa(struct ospf_area *oa) lsa.type = LSA_T_RT; fill_lsa_header(po, oa, &lsa, NULL, NULL); - lsa.sn = get_seqnum(oa->rt); + lsa.sn = get_lsa_seq(po, oa, &lsa); u32 dom = oa->areaid; body = originate_rt_lsa_body(oa, &lsa.length); @@ -594,7 +580,7 @@ originate_net_lsa(struct ospf_iface *ifa) lsa.type = LSA_T_NET; fill_lsa_header(po, ifa->oa, &lsa, NULL, ifa); - lsa.sn = get_seqnum(ifa->net_lsa); + lsa.sn = get_lsa_seq(po, ifa->oa, &lsa); body = originate_net_lsa_body(ifa, &lsa.length, po); lsasum_calculate(&lsa, body); @@ -613,9 +599,7 @@ flush_net_lsa(struct ospf_iface *ifa) return; OSPF_TRACE(D_EVENTS, "Flushing network-LSA for iface %s", ifa->ifname); - ifa->net_lsa->lsa.sn += 1; - ifa->net_lsa->lsa.age = LSA_MAXAGE; - lsasum_calculate(&ifa->net_lsa->lsa, ifa->net_lsa->lsa_body); + flush_lsa_seq(po, ifa->oa, ifa->net_lsa); ospf_lsupd_flood(po, NULL, NULL, &ifa->net_lsa->lsa, dom, 0); flush_lsa(ifa->net_lsa, po); ifa->net_lsa = NULL; @@ -684,7 +668,7 @@ check_sum_lsa_same(struct top_hash_entry *en, u32 metric) { /* Netmask already checked in check_sum_net_lsaid_collision() */ struct ospf_lsa_sum *sum = en->lsa_body; - return (en->lsa.sn != LSA_MAXSEQNO) && (sum->metric == metric); + return (en->lsa.age != LSA_MAXAGE) && (sum->metric == metric); } #define check_sum_net_lsa_same(en,metric) \ @@ -727,7 +711,7 @@ check_sum_net_lsa_same(struct top_hash_entry *en, u32 metric) { /* Prefix already checked in check_sum_net_lsaid_collision() */ struct ospf_lsa_sum_net *sum = en->lsa_body; - return (en->lsa.sn != LSA_MAXSEQNO) && (sum->metric == metric); + return (en->lsa.age != LSA_MAXAGE) && (sum->metric == metric); } static inline void * @@ -747,7 +731,7 @@ static inline int check_sum_rt_lsa_same(struct top_hash_entry *en, u32 drid, u32 metric, u32 options) { struct ospf_lsa_sum_rt *sum = en->lsa_body; - return (en->lsa.sn != LSA_MAXSEQNO) && (sum->options == options) && + return (en->lsa.age != LSA_MAXAGE) && (sum->options == options) && (sum->metric == metric) && (sum->drid == drid); } @@ -781,7 +765,7 @@ originate_sum_net_lsa(struct ospf_area *oa, struct fib_node *fn, int metric) if (check_sum_net_lsa_same(en, metric)) return; } - lsa.sn = get_seqnum(en); + lsa.sn = get_lsa_seq(po, oa, &lsa); body = originate_sum_net_lsa_body(po, &lsa.length, fn, metric); lsasum_calculate(&lsa, body); @@ -813,7 +797,7 @@ originate_sum_rt_lsa(struct ospf_area *oa, struct fib_node *fn, int metric, u32 if (check_sum_rt_lsa_same(en, lsa.id, metric, options)) return; } - lsa.sn = get_seqnum(en); + lsa.sn = get_lsa_seq(po, oa, &lsa); body = originate_sum_rt_lsa_body(po, &lsa.length, lsa.id, metric, options); lsasum_calculate(&lsa, body); @@ -844,10 +828,7 @@ flush_sum_lsa(struct ospf_area *oa, struct fib_node *fn, int type) return; } - struct ospf_lsa_sum *sum = en->lsa_body; - en->lsa.age = LSA_MAXAGE; - en->lsa.sn = LSA_MAXSEQNO; - lsasum_calculate(&en->lsa, sum); + flush_lsa_seq(po, oa, en); ospf_lsupd_flood(po, NULL, NULL, &en->lsa, oa->areaid, 1); if (can_flush_lsa(po)) flush_lsa(en, po); } @@ -893,7 +874,7 @@ check_ext_lsa(struct top_hash_entry *en, struct fib_node *fn, u32 metric, ip_add if (fn->pxlen != ipa_mklen(ext->netmask)) return -1; - return (en->lsa.sn != LSA_MAXSEQNO) && (ext->metric == metric) && + return (en->lsa.age != LSA_MAXAGE) && (ext->metric == metric) && (ext->tag == tag) && ipa_equal(ext->fwaddr,fwaddr); } @@ -946,7 +927,7 @@ check_ext_lsa(struct top_hash_entry *en, struct fib_node *fn, u32 metric, ip_add if ((fn->pxlen != pxlen) || !ipa_equal(fn->prefix, prefix)) return -1; - if (en->lsa.sn == LSA_MAXSEQNO) + if (en->lsa.age == LSA_MAXAGE) return 0; u32 rt_metric = ext->metric & METRIC_MASK; @@ -1081,7 +1062,7 @@ originate_ext_lsa(struct ospf_area *oa, struct fib_node *fn, int src, if (rv > 0) return; } - lsa.sn = get_seqnum(en); + lsa.sn = get_lsa_seq(po, NULL, &lsa); body = originate_ext_lsa_body(po, &lsa.length, fn, metric, fwaddr, tag, pbit); lsasum_calculate(&lsa, body); @@ -1126,7 +1107,7 @@ flush_ext_lsa(struct ospf_area *oa, struct fib_node *fn, int nssa) } fn->x1 = 0; - ospf_lsupd_flush_nlsa(po, en); + ospf_lsupd_flush_nlsa(po, NULL, en); } } @@ -1216,6 +1197,251 @@ fill_lsa_header(struct proto_ospf *po, struct ospf_area *oa, struct ospf_lsa_hea lsa->id = lsid; } +static struct seq_data * +find_seq(struct proto_ospf *po, struct ospf_area *oa, struct ospf_lsa_header *lsa, int create) +{ + struct seq_fib_data *sfd; + struct fib *seq_fib; + ip_addr addr; + + memset(&addr, 0, sizeof(addr)); +#ifdef IPV6 + _I3(addr) = lsa->id; +#else + addr = _I(lsa->id); +#endif + + if (oa != NULL) + seq_fib = &oa->seq_fib; + else + seq_fib = &po->seq_fib; + + if (create != 0) + sfd = (struct seq_fib_data *)fib_get(seq_fib, &addr, BITS_PER_IP_ADDRESS); + else + sfd = (struct seq_fib_data *)fib_find(seq_fib, &addr, BITS_PER_IP_ADDRESS); + + if (sfd == NULL) + return NULL; + + return &sfd->type[lsa->type % SEQ_DATA_MAX]; +} + +static s32 +get_lsa_seq(struct proto_ospf *po, struct ospf_area *oa, struct ospf_lsa_header *lsa) +{ + struct seq_data *sd; + + sd = find_seq(po, oa, lsa, 1); + + if (sd->active == 0) + { + /* New entry */ + sd->seq = LSA_INITSEQNO; + } + else + { + if (++sd->seq == LSA_MAXSEQNO) + { + /* + * According to 12.1.6 in 2338 RFC + * we need to ensure that previous version + * is flushed: + * + * 'As soon as this flood has been acknowledged by + * all adjacent neighbors, a new instance can be + * originated with sequence number of InitialSequenceNumber.' + * + * XXX: We're currently not controlling if our adjacent neighbors + * acknowledge LSA flush. However, since this is VERY rare case, + * simply set seq to InitialSequenceNumber. + */ + sd->seq = LSA_INITSEQNO; + } + } + + sd->active = 1; + sd->last = now; + + return sd->seq; +} + +/** + * update_lsa_seq - updates age/seq/checksum for given LSA. + * @po: OSPF protocol + * @oa: OSPF area + * @en: LSA base + * @sn: new checksum + */ +void +update_lsa_seq(struct proto_ospf *po, struct top_hash_entry *en, s32 sn) +{ + struct ospf_lsa_header *lsa = &en->lsa; + struct ospf_area *oa = NULL; + struct seq_data *sd; + + lsa->sn = sn; + lsa->age = 0; + en->inst_t = now; + en->ini_age = 0; + lsasum_calculate(lsa, en->lsa_body); + + /* + * Get area to search into. + * Treat LSA_SCOPE_LINK as AREA and + * LSA_SCOPE_RES (if any) as AS + */ + switch (LSA_SCOPE(lsa)) + { +#ifndef OSPFv2 + case LSA_SCOPE_LINK: +#endif + case LSA_SCOPE_AREA: + oa = ospf_find_area(po, en->domain); + break; + } + + if ((sd = find_seq(po, oa, lsa, 0)) == NULL) + { + log(L_WARN "Unable to find LSA sequence for LSA (id=%R type %d)", lsa->id, lsa->type); + return; + } + + sd->seq = lsa->sn; + sd->last = now; +} + +/** + * flush_lsa_seq - notify we're not using given LSA anymore. Updates LSA age/checksum. + * @po: OSPF protocol + * @oa: OSPF area + * @en: LSA base + */ +void +flush_lsa_seq(struct proto_ospf *po, struct ospf_area *oa, struct top_hash_entry *en) +{ + struct ospf_lsa_header *lsa = &en->lsa; + struct seq_data *sd; + + lsa->age = LSA_MAXAGE; + lsasum_calculate(lsa, en->lsa_body); + + if ((sd = find_seq(po, oa, lsa, 0)) == NULL) + { + log(L_WARN "Unable to flush LSA sequence for LSA (id=%R type %d)", lsa->id, lsa->type); + return; + } + + sd->active = -1; + sd->last = now; +} + +/** + * check_seq_fib - remove unused prefixes from fib + * @po: OSPF protocol + * @seq_fib: fib put prune + */ +static void +check_seq_fib(struct proto_ospf *po, struct fib *seq_fib) +{ + struct fib_iterator fit; + struct seq_fib_data *sfd; + struct seq_data *sd; + int i, keep; + + FIB_ITERATE_INIT(&fit, seq_fib); +again: + FIB_ITERATE_START(seq_fib, &fit, nftmp) + { + sfd = (struct seq_fib_data *) nftmp; + + /* + * Check if we need purge "old" prefixes. + * + * RFC 2328, 14 tells us: + * + * A MaxAge LSA must be removed immediately from the router's link + * state database as soon as both a) it is no longer contained on any + * neighbor Link state retransmission lists and b) none of the router's + * neighbors are in states Exchange or Loading. + * + * This basically means we have no guaranteed timing on MaxAge LSA + * removal, so we keep entries for considerably long amount of time. + */ + keep = 0; + for (i = 0; i < SEQ_DATA_MAX; i++) + { + sd = &sfd->type[i]; + if (sd->active == 1 || sd->last + 3600 >= now) + keep = 1; + } + if (keep == 0) + { + FIB_ITERATE_PUT(&fit, nftmp); + fib_delete(seq_fib, nftmp); + goto again; + } + } + FIB_ITERATE_END(nftmp); +} + +/** + * check_seq_fib - remove unused prefixes from fib + * @timer: timer + */ +static void +check_seq_fibs(timer *timer) +{ + struct proto_ospf *po = timer->data; + struct ospf_area *oa; + + /* Check protocol fib first */ + check_seq_fib(po, &po->seq_fib); + + WALK_LIST(oa, po->area_list) + check_seq_fib(po, &oa->seq_fib); +} + + +/** + * init_seq_fib - Initialize fib for sequence storage + * @po: OSPF protocol + * @oa: OSPF area, if set + */ +void +init_seq_fib(struct proto_ospf *po, struct ospf_area *oa) +{ + timer *t; + struct fib *seq_fib = (oa == NULL) ? &po->seq_fib : &oa->seq_fib; + + fib_init(seq_fib, po->proto.pool, sizeof(struct seq_fib_data), 0, NULL); + + if (oa == NULL) + { + t = tm_new(po->proto.pool); + t->data = po; + t->randomize = 0; + t->hook = check_seq_fibs; + t->recurrent = 3600 / 3; + po->seq_timer = t; + tm_start(t, t->recurrent); + } +} + +/** + * free_seq_fib - iFree sequence storage fib + * @po: OSPF protocol + */ +void +free_seq_fib(struct proto_ospf *po, struct ospf_area *oa) +{ + struct fib *seq_fib = (oa == NULL) ? &po->seq_fib : &oa->seq_fib; + + fib_free(seq_fib); + if (oa != NULL) + rfree(oa->seq_timer); +} + #ifdef OSPFv3 static void * @@ -1267,7 +1493,7 @@ originate_link_lsa(struct ospf_iface *ifa) lsa.type = LSA_T_LINK; fill_lsa_header(po, ifa->oa, &lsa, NULL, ifa); - lsa.sn = get_seqnum(ifa->link_lsa); + lsa.sn = get_lsa_seq(po, ifa->oa, &lsa); u32 dom = ifa->iface_id; body = originate_link_lsa_body(ifa, &lsa.length); @@ -1411,7 +1637,7 @@ originate_prefix_rt_lsa(struct ospf_area *oa) lsa.type = LSA_T_PREFIX; fill_lsa_header(po, oa, &lsa, NULL, NULL); - lsa.sn = get_seqnum(oa->pxr_lsa); + lsa.sn = get_lsa_seq(po, oa, &lsa); u32 dom = oa->areaid; body = originate_prefix_rt_lsa_body(oa, &lsa.length); @@ -1548,7 +1774,7 @@ originate_prefix_net_lsa(struct ospf_iface *ifa) lsa.type = LSA_T_PREFIX; fill_lsa_header(po, ifa->oa, &lsa, NULL, ifa); - lsa.sn = get_seqnum(ifa->pxn_lsa); + lsa.sn = get_lsa_seq(po, ifa->oa, &lsa); u32 dom = ifa->oa->areaid; body = originate_prefix_net_lsa_body(ifa, &lsa.length); @@ -1570,9 +1796,7 @@ flush_prefix_net_lsa(struct ospf_iface *ifa) OSPF_TRACE(D_EVENTS, "Flushing network prefix-LSA for iface %s", ifa->ifname); - en->lsa.sn += 1; - en->lsa.age = LSA_MAXAGE; - lsasum_calculate(&en->lsa, en->lsa_body); + flush_lsa_seq(po, ifa->oa, en); ospf_lsupd_flood(po, NULL, NULL, &en->lsa, dom, 0); flush_lsa(en, po); ifa->pxn_lsa = NULL; diff --git a/proto/ospf/topology.h b/proto/ospf/topology.h index cb87648..5cc64c9 100644 --- a/proto/ospf/topology.h +++ b/proto/ospf/topology.h @@ -47,6 +47,19 @@ struct top_graph unsigned int hash_entries_min, hash_entries_max; }; +struct seq_data { + int active; + s32 seq; + bird_clock_t last; +}; +#define SEQ_DATA_MAX 16 + +struct seq_fib_data +{ + struct fib_node fn; + struct seq_data type[SEQ_DATA_MAX]; /* One for each possible LSA types */ +}; + struct top_graph *ospf_top_new(pool *); void ospf_top_free(struct top_graph *); void ospf_top_dump(struct top_graph *, struct proto *); @@ -73,6 +86,10 @@ void originate_sum_rt_lsa(struct ospf_area *oa, struct fib_node *fn, int metric, void flush_sum_lsa(struct ospf_area *oa, struct fib_node *fn, int type); void originate_ext_lsa(struct ospf_area *oa, struct fib_node *fn, int src, u32 metric, ip_addr fwaddr, u32 tag, int pbit); void flush_ext_lsa(struct ospf_area *oa, struct fib_node *fn, int nssa); +void flush_lsa_seq(struct proto_ospf *po, struct ospf_area *oa, struct top_hash_entry *en); +void update_lsa_seq(struct proto_ospf *po, struct top_hash_entry *en, s32 sn); +void init_seq_fib(struct proto_ospf *po, struct ospf_area *oa); +void free_seq_fib(struct proto_ospf *po, struct ospf_area *oa); #ifdef OSPFv2 -- 1.7.11.5 --------------000206050504040902070200--
participants (1)
-
Alexander V. Chernikov