From 00a09f3c367e79297f827b52ec5f16842db1ac4e Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Sun, 15 Apr 2012 15:07:58 +0200 Subject: [PATCH] Implement RA_ACCEPTED mode of route propagation. --- nest/route.h | 7 +- nest/rt-table.c | 416 ++++++++++++++++++++++++++++----------------- proto/bgp/bgp.c | 7 +- proto/bgp/bgp.h | 1 + proto/bgp/config.Y | 10 +- 5 files changed, 281 insertions(+), 160 deletions(-) diff --git a/nest/route.h b/nest/route.h index 34536609..59daf803 100644 --- a/nest/route.h +++ b/nest/route.h @@ -219,11 +219,12 @@ typedef struct rte { } u; } rte; -#define REF_COW 1 /* Copy this rte on write */ +#define REF_COW 1 /* Copy this rte on write */ /* Types of route announcement, also used as flags */ -#define RA_OPTIMAL 1 /* Announcement of optimal route change */ -#define RA_ANY 2 /* Announcement of any route change */ +#define RA_OPTIMAL 1 /* Announcement of optimal route change */ +#define RA_ACCEPTED 2 /* Announcement of first accepted route */ +#define RA_ANY 3 /* Announcement of any route change */ struct config; diff --git a/nest/rt-table.c b/nest/rt-table.c index 310c1afd..578a6ba3 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -182,86 +182,73 @@ rte_trace_out(unsigned int flag, struct proto *p, rte *e, char *msg) rte_trace(p, e, '<', msg); } -static inline void -do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) +static rte * +export_filter(struct announce_hook *ah, rte *rt0, rte **rt_free, ea_list **tmpa, int silent) { struct proto *p = ah->proto; struct filter *filter = ah->out_filter; struct proto_stats *stats = ah->stats; + ea_list *tmpb = NULL; + rte *rt; + int v; - rte *new0 = new; - rte *old0 = old; - int ok; + rt = rt0; + *rt_free = NULL; - if (new) + /* If called does not care for eattrs, we prepare one internally */ + if (!tmpa) { - stats->exp_updates_received++; - - char *drop_reason = NULL; - if ((ok = p->import_control ? p->import_control(p, &new, &tmpa, rte_update_pool) : 0) < 0) - { - stats->exp_updates_rejected++; - drop_reason = "rejected by protocol"; - } - else if (ok) - rte_trace_out(D_FILTERS, p, new, "forced accept by protocol"); - else if ((filter == FILTER_REJECT) || - (filter && f_run(filter, &new, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)) - { - stats->exp_updates_filtered++; - drop_reason = "filtered out"; - } - if (drop_reason) - { - rte_trace_out(D_FILTERS, p, new, drop_reason); - if (new != new0) - rte_free(new); - new = NULL; - } - } - else - stats->exp_withdraws_received++; - - /* - * This is a tricky part - we don't know whether route 'old' was - * exported to protocol 'p' or was filtered by the export filter. - * We try tu run the export filter to know this to have a correct - * value in 'old' argument of rte_update (and proper filter value) - * - * FIXME - this is broken because 'configure soft' may change - * filters but keep routes. Refeed is expected to be called after - * change of the filters and with old == new, therefore we do not - * even try to run the filter on an old route, This may lead to - * 'spurious withdraws' but ensure that there are no 'missing - * withdraws'. - * - * This is not completely safe as there is a window between - * reconfiguration and the end of refeed - if a newly filtered - * route disappears during this period, proper withdraw is not - * sent (because old would be also filtered) and the route is - * not refeeded (because it disappeared before that). - */ - - if (old && !refeed) - { - if (filter == FILTER_REJECT) - old = NULL; - else - { - ea_list *tmpb = p->make_tmp_attrs ? p->make_tmp_attrs(old, rte_update_pool) : NULL; - ok = p->import_control ? p->import_control(p, &old, &tmpb, rte_update_pool) : 0; - if (ok < 0 || (!ok && filter && f_run(filter, &old, &tmpb, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)) - { - if (old != old0) - rte_free(old); - old = NULL; - } - } + struct proto *src = rt->attrs->proto; + tmpb = src->make_tmp_attrs ? src->make_tmp_attrs(rt, rte_update_pool) : NULL; + tmpa = &tmpb; } - /* FIXME - This is broken because of incorrect 'old' value (see above) */ - if (!new && !old) - return; + v = p->import_control ? p->import_control(p, &rt, tmpa, rte_update_pool) : 0; + if (v < 0) + { + if (silent) + goto reject; + + stats->exp_updates_rejected++; + rte_trace_out(D_FILTERS, p, rt, "rejected by protocol"); + goto reject; + } + if (v > 0) + { + if (!silent) + rte_trace_out(D_FILTERS, p, rt, "forced accept by protocol"); + goto accept; + } + + v = filter && ((filter == FILTER_REJECT) || + (f_run(filter, &rt, tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)); + if (v) + { + if (silent) + goto reject; + + stats->exp_updates_filtered++; + rte_trace_out(D_FILTERS, p, rt, "filtered out"); + goto reject; + } + + accept: + if (rt != rt0) + *rt_free = rt; + return rt; + + reject: + /* Discard temporary rte */ + if (rt != rt0) + rte_free(rt); + return NULL; +} + +static void +do_rt_notify(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) +{ + struct proto *p = ah->proto; + struct proto_stats *stats = ah->stats; if (new) stats->exp_updates_accepted++; @@ -297,10 +284,174 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r } else p->rt_notify(p, ah->table, net, new, old, new->attrs->eattrs); - if (new && new != new0) /* Discard temporary rte's */ - rte_free(new); - if (old && old != old0) - rte_free(old); + +} + + +static void +rt_notify_basic(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) +{ + // struct proto *p = ah->proto; + struct proto_stats *stats = ah->stats; + + rte *new_free = NULL; + rte *old_free = NULL; + + if (new) + stats->exp_updates_received++; + else + stats->exp_withdraws_received++; + + /* + * This is a tricky part - we don't know whether route 'old' was + * exported to protocol 'p' or was filtered by the export filter. + * We try to run the export filter to know this to have a correct + * value in 'old' argument of rte_update (and proper filter value) + * + * FIXME - this is broken because 'configure soft' may change + * filters but keep routes. Refeed is expected to be called after + * change of the filters and with old == new, therefore we do not + * even try to run the filter on an old route, This may lead to + * 'spurious withdraws' but ensure that there are no 'missing + * withdraws'. + * + * This is not completely safe as there is a window between + * reconfiguration and the end of refeed - if a newly filtered + * route disappears during this period, proper withdraw is not + * sent (because old would be also filtered) and the route is + * not refeeded (because it disappeared before that). + */ + + if (new) + new = export_filter(ah, new, &new_free, &tmpa, 0); + + if (old && !refeed) + old = export_filter(ah, old, &old_free, NULL, 1); + + /* FIXME - This is broken because of incorrect 'old' value (see above) */ + if (!new && !old) + return; + + do_rt_notify(ah, net, new, old, tmpa, refeed); + + /* Discard temporary rte's */ + if (new_free) + rte_free(new_free); + if (old_free) + rte_free(old_free); +} + +static void +rt_notify_accepted(struct announce_hook *ah, net *net, rte *new_changed, rte *old_changed, rte *before_old, + ea_list *tmpa, int feed) +{ + // struct proto *p = ah->proto; + struct proto_stats *stats = ah->stats; + + rte *new_best = NULL; + rte *old_best = NULL; + rte *new_free = NULL; + rte *old_free = NULL; + rte *r; + + /* Used to track whethe we met old_changed position. If it is NULL + it was the first and met it implicitly before current best route. */ + int old_meet = (old_changed && !before_old) ? 1 : 0; + + if (new_changed) + stats->exp_updates_received++; + else + stats->exp_withdraws_received++; + + /* First, find the new_best route - first accepted by filters */ + for (r=net->routes; r; r=r->next) + { + if (new_best = export_filter(ah, r, &new_free, &tmpa, 0)) + break; + + /* Note if we walked around the position of old_changed route */ + if (r == before_old) + old_meet = 1; + } + + /* + * Second, handle the feed case. That means we do not care for + * old_best. It is NULL for feed, and the new_best for refeed. + * For refeed, there is a hack similar to one in rt_notify_basic() + * to ensure withdraws in case of changed filters + */ + if (feed) + { + if (feed == 2) /* refeed */ + old_best = new_best ? new_best : net->routes; + else + old_best = NULL; + + if (!new_best && !old_best) + return; + + goto found; + } + + /* + * Now, we find the old_best route. Generally, it is the same as the + * new_best, unless new_best is the same as new_changed or + * old_changed is accepted before new_best. + * + * There are four cases: + * + * - We would find and accept old_changed before new_best, therefore + * old_changed is old_best. In remaining cases we suppose this + * is not true. + * + * - We found no new_best, therefore there is also no old_best and + * we ignore this withdraw. + * + * - We found new_best different than new_changed, therefore + * old_best is the same as new_best and we ignore this update. + * + * - We found new_best the same as new_changed, therefore it cannot + * be old_best and we have to continue search for old_best. + */ + + /* First case */ + if (old_meet) + if (old_best = export_filter(ah, old_changed, &old_free, NULL, 1)) + goto found; + + /* Second case */ + if (!new_best) + return; + + /* Third case, we use r insead of new_best, because export_filter() could change it */ + if (r != new_changed) + { + if (new_free) + rte_free(new_free); + return; + } + + /* Fourth case */ + for (; r; r=r->next) + { + if (old_best = export_filter(ah, r, &old_free, NULL, 1)) + goto found; + + if (r == before_old) + if (old_best = export_filter(ah, old_changed, &old_free, NULL, 1)) + goto found; + } + + /* Implicitly, old_best is NULL and new_best is non-NULL */ + + found: + do_rt_notify(ah, net, new_best, old_best, tmpa, (feed == 2)); + + /* Discard temporary rte's */ + if (new_free) + rte_free(new_free); + if (old_free) + rte_free(old_free); } /** @@ -333,7 +484,7 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r * the protocol gets called. */ static void -rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, ea_list *tmpa) +rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, rte *before_old, ea_list *tmpa) { struct announce_hook *a; @@ -352,11 +503,13 @@ rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, ea_list * { ASSERT(a->proto->core_state == FS_HAPPY || a->proto->core_state == FS_FEEDING); if (a->proto->accept_ra_types == type) - do_rte_announce(a, type, net, new, old, tmpa, 0); + if (type == RA_ACCEPTED) + rt_notify_accepted(a, net, new, old, before_old, tmpa, 0); + else + rt_notify_basic(a, net, new, old, tmpa, 0); } } - static inline int rte_validate(rte *e) { @@ -419,9 +572,10 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str struct proto *p = ah->proto; struct rtable *table = ah->table; struct proto_stats *stats = ah->stats; + rte *before_old = NULL; rte *old_best = net->routes; rte *old = NULL; - rte **k, *r, *s; + rte **k; k = &net->routes; /* Find and remove original route from the same protocol */ while (old = *k) @@ -466,8 +620,12 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str break; } k = &old->next; + before_old = old; } + if (!old) + before_old = NULL; + if (!old && !new) { stats->imp_withdraws_ignored++; @@ -484,82 +642,21 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str if (old) stats->imp_routes--; - rte_announce(table, RA_ANY, net, new, old, tmpa); - - if (src->rte_recalculate && src->rte_recalculate(table, net, new, old, old_best)) - goto do_recalculate; - - if (new && rte_better(new, old_best)) + if (new) { - /* The first case - the new route is cleary optimal, we link it - at the first position and announce it */ + for (k=&net->routes; *k; k=&(*k)->next) + if (rte_better(new, *k)) + break; - rte_trace_in(D_ROUTES, p, new, "added [best]"); - rte_announce(table, RA_OPTIMAL, net, new, old_best, tmpa); - new->next = net->routes; - net->routes = new; - } - else if (old == old_best) - { - /* The second case - the old best route disappeared, we add the - new route (if we have any) to the list (we don't care about - position) and then we elect the new optimal route and relink - that route at the first position and announce it. New optimal - route might be NULL if there is no more routes */ + new->lastmod = now; + new->next = *k; + *k = new; - do_recalculate: - /* Add the new route to the list */ - if (new) - { - rte_trace_in(D_ROUTES, p, new, "added"); - new->next = net->routes; - net->routes = new; - } - - /* Find new optimal route */ - r = NULL; - for (s=net->routes; s; s=s->next) - if (rte_better(s, r)) - r = s; - - /* Announce optimal route */ - rte_announce(table, RA_OPTIMAL, net, r, old_best, tmpa); - - /* And relink it (if there is any) */ - if (r) - { - k = &net->routes; - while (s = *k) - { - if (s == r) - { - *k = r->next; - break; - } - k = &s->next; - } - r->next = net->routes; - net->routes = r; - } - else if (table->gc_counter++ >= table->config->gc_max_ops && - table->gc_time + table->config->gc_min_time <= now) - rt_schedule_gc(table); - } - else if (new) - { - /* The third case - the new route is not better than the old - best route (therefore old_best != NULL) and the old best - route was not removed (therefore old_best == net->routes). - We just link the new route after the old best route. */ - - ASSERT(net->routes != NULL); - new->next = net->routes->next; - net->routes->next = new; - rte_trace_in(D_ROUTES, p, new, "added"); + rte_trace_in(D_ROUTES, p, new, net->routes == new ? "added [best]" : "added"); } /* Log the route removal */ - if (!new && old && (p->debug & D_ROUTES)) + if (!new && (p->debug & D_ROUTES)) { if (old != old_best) rte_trace_in(D_ROUTES, p, old, "removed"); @@ -569,6 +666,15 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str rte_trace_in(D_ROUTES, p, old, "removed [sole]"); } + rte_announce(table, RA_ANY, net, new, old, NULL, tmpa); + rte_announce(table, RA_OPTIMAL, net, net->routes, old_best, NULL, tmpa); + rte_announce(table, RA_ACCEPTED, net, new, old, before_old, tmpa); + + if (!net->routes && + (table->gc_counter++ >= table->config->gc_max_ops) && + (table->gc_time + table->config->gc_min_time <= now)) + rt_schedule_gc(table); + if (old) { if (p->rte_remove) @@ -577,7 +683,6 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str } if (new) { - new->lastmod = now; if (p->rte_insert) p->rte_insert(net, new); } @@ -709,7 +814,7 @@ rte_announce_i(rtable *tab, unsigned type, net *n, rte *new, rte *old) rte_update_lock(); src = new->attrs->proto; tmpa = src->make_tmp_attrs ? src->make_tmp_attrs(new, rte_update_pool) : NULL; - rte_announce(tab, type, n, new, old, tmpa); + rte_announce(tab, type, n, new, old, NULL, tmpa); rte_update_unlock(); } @@ -1259,12 +1364,15 @@ rt_commit(struct config *new, struct config *old) static inline void do_feed_baby(struct proto *p, int type, struct announce_hook *h, net *n, rte *e) { - struct proto *q = e->attrs->proto; + struct proto *src = e->attrs->proto; ea_list *tmpa; rte_update_lock(); - tmpa = q->make_tmp_attrs ? q->make_tmp_attrs(e, rte_update_pool) : NULL; - do_rte_announce(h, type, n, e, p->refeeding ? e : NULL, tmpa, p->refeeding); + tmpa = src->make_tmp_attrs ? src->make_tmp_attrs(e, rte_update_pool) : NULL; + if (type == RA_ACCEPTED) + rt_notify_accepted(h, n, e, NULL, NULL, tmpa, p->refeeding ? 2 : 1); + else + rt_notify_basic(h, n, e, p->refeeding ? e : NULL, tmpa, p->refeeding); rte_update_unlock(); } diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index 4dd4b7be..099a39a9 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -909,7 +909,7 @@ bgp_init(struct proto_config *C) struct proto *P = proto_new(C, sizeof(struct bgp_proto)); struct bgp_proto *p = (struct bgp_proto *) P; - P->accept_ra_types = RA_OPTIMAL; + P->accept_ra_types = c->secondary ? RA_ACCEPTED : RA_OPTIMAL; P->rt_notify = bgp_rt_notify; P->rte_better = bgp_rte_better; P->import_control = bgp_import_control; @@ -955,12 +955,14 @@ bgp_check_config(struct bgp_config *c) if (internal && c->rs_client) cf_error("Only external neighbor can be RS client"); + /* if (c->multihop && (c->gw_mode == GW_DIRECT)) cf_error("Multihop BGP cannot use direct gateway mode"); if (c->multihop && (ipa_has_link_scope(c->remote_ip) || ipa_has_link_scope(c->source_addr))) cf_error("Multihop BGP cannot be used with link-local addresses"); + */ /* Different default based on rs_client */ if (!c->missing_lladdr) @@ -968,7 +970,8 @@ bgp_check_config(struct bgp_config *c) /* Different default for gw_mode */ if (!c->gw_mode) - c->gw_mode = (c->multihop || internal) ? GW_RECURSIVE : GW_DIRECT; + // c->gw_mode = (c->multihop || internal) ? GW_RECURSIVE : GW_DIRECT; + c->gw_mode = GW_DIRECT; } static int diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index a8c5818e..87734425 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -43,6 +43,7 @@ struct bgp_config { u32 route_limit; /* Number of routes that may be imported, 0 means disable limit */ int passive; /* Do not initiate outgoing connection */ int interpret_communities; /* Hardwired handling of well-known communities */ + int secondary; /* Accept also non-best routes (i.e. RA_ACCEPTED) */ unsigned connect_retry_time; unsigned hold_time, initial_hold_time; unsigned keepalive_time; diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index 78ca52de..356415a2 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -25,7 +25,8 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, CLUSTER, ID, AS4, ADVERTISE, IPV4, CAPABILITIES, LIMIT, PASSIVE, PREFER, OLDER, MISSING, LLADDR, DROP, IGNORE, ROUTE, REFRESH, INTERPRET, COMMUNITIES, BGP_ORIGINATOR_ID, BGP_CLUSTER_LIST, IGP, - TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, SECURITY, DETERMINISTIC) + TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, SECURITY, DETERMINISTIC, + SECONDARY) CF_GRAMMAR @@ -73,19 +74,25 @@ bgp_proto: | bgp_proto STARTUP HOLD TIME expr ';' { BGP_CFG->initial_hold_time = $5; } | bgp_proto CONNECT RETRY TIME expr ';' { BGP_CFG->connect_retry_time = $5; } | bgp_proto KEEPALIVE TIME expr ';' { BGP_CFG->keepalive_time = $4; } +/* | bgp_proto MULTIHOP ';' { BGP_CFG->multihop = 64; } | bgp_proto MULTIHOP expr ';' { BGP_CFG->multihop = $3; if (($3<1) || ($3>255)) cf_error("Multihop must be in range 1-255"); } +*/ | bgp_proto NEXT HOP SELF ';' { BGP_CFG->next_hop_self = 1; } | bgp_proto MISSING LLADDR SELF ';' { BGP_CFG->missing_lladdr = MLL_SELF; } | bgp_proto MISSING LLADDR DROP ';' { BGP_CFG->missing_lladdr = MLL_DROP; } | bgp_proto MISSING LLADDR IGNORE ';' { BGP_CFG->missing_lladdr = MLL_IGNORE; } +/* | bgp_proto GATEWAY DIRECT ';' { BGP_CFG->gw_mode = GW_DIRECT; } | bgp_proto GATEWAY RECURSIVE ';' { BGP_CFG->gw_mode = GW_RECURSIVE; } +*/ | bgp_proto PATH METRIC bool ';' { BGP_CFG->compare_path_lengths = $4; } | bgp_proto MED METRIC bool ';' { BGP_CFG->med_metric = $4; } | bgp_proto IGP METRIC bool ';' { BGP_CFG->igp_metric = $4; } | bgp_proto PREFER OLDER bool ';' { BGP_CFG->prefer_older = $4; } +/* | bgp_proto DETERMINISTIC MED bool ';' { BGP_CFG->deterministic_med = $4; } +*/ | bgp_proto DEFAULT BGP_MED expr ';' { BGP_CFG->default_med = $4; } | bgp_proto DEFAULT BGP_LOCAL_PREF expr ';' { BGP_CFG->default_local_pref = $4; } | bgp_proto SOURCE ADDRESS ipa ';' { BGP_CFG->source_addr = $4; } @@ -101,6 +108,7 @@ bgp_proto: | bgp_proto ROUTE LIMIT expr ';' { BGP_CFG->route_limit = $4; } | bgp_proto PASSIVE bool ';' { BGP_CFG->passive = $3; } | bgp_proto INTERPRET COMMUNITIES bool ';' { BGP_CFG->interpret_communities = $4; } + | bgp_proto SECONDARY bool ';' { BGP_CFG->secondary = $3; } | bgp_proto IGP TABLE rtable ';' { BGP_CFG->igp_table = $4; } | bgp_proto TTL SECURITY bool ';' { BGP_CFG->ttl_security = $4; } ;