From: Ondrej Zajicek Date: Mon, 16 Jul 2012 12:44:45 +0000 (+0200) Subject: Merge branch 'rt-accepted' X-Git-Tag: v1.3.8^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=abced4a91495e27fe86b142bc1967cec53bab3dc;p=thirdparty%2Fbird.git Merge branch 'rt-accepted' Conflicts: nest/config.Y nest/rt-table.c proto/bgp/bgp.c --- abced4a91495e27fe86b142bc1967cec53bab3dc diff --cc nest/config.Y index 14cff10a4,2bc5a4ab7..a75dd0c34 --- a/nest/config.Y +++ b/nest/config.Y @@@ -44,10 -44,9 +44,10 @@@ CF_DECL CF_KEYWORDS(ROUTER, ID, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT) CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, TABLE, STATES, ROUTES, FILTERS) +CF_KEYWORDS(LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE) CF_KEYWORDS(PASSWORD, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, INTERFACES) CF_KEYWORDS(PRIMARY, STATS, COUNT, FOR, COMMANDS, PREEXPORT, GENERATE, ROA, MAX, FLUSH) - CF_KEYWORDS(LISTEN, BGP, V6ONLY, DUAL, ADDRESS, PORT, PASSWORDS, DESCRIPTION) + CF_KEYWORDS(LISTEN, BGP, V6ONLY, DUAL, ADDRESS, PORT, PASSWORDS, DESCRIPTION, SORTED) CF_KEYWORDS(RELOAD, IN, OUT, MRTDUMP, MESSAGES, RESTRICT, MEMORY, IGP_METRIC) CF_ENUM(T_ENUM_RTS, RTS_, DUMMY, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT, @@@ -65,9 -64,8 +65,9 @@@ CF_ENUM(T_ENUM_ROA, ROA_, UNKNOWN, VALI %type roa_args %type roa_table_arg %type sym_args - %type proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_or_preexport roa_mode limit_action -%type proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_or_preexport roa_mode tab_sorted ++%type proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_or_preexport roa_mode limit_action tab_sorted %type proto_patt proto_patt2 +%type limit_spec CF_GRAMMAR diff --cc nest/rt-table.c index fdc767e7d,eb8304f79..165f42bb8 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@@ -198,123 -188,68 +188,115 @@@ export_filter(struct announce_hook *ah 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; - } + struct proto *src = rt->attrs->proto; + tmpb = src->make_tmp_attrs ? src->make_tmp_attrs(rt, rte_update_pool) : NULL; + tmpa = &tmpb; } - 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). - */ + v = p->import_control ? p->import_control(p, &rt, tmpa, rte_update_pool) : 0; + if (v < 0) + { + if (silent) + goto reject; - if (old && !refeed) + stats->exp_updates_rejected++; + rte_trace_out(D_FILTERS, p, rt, "rejected by protocol"); + goto reject; + } + if (v > 0) { - 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; - } - } + 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; + ++ + /* ++ * First, apply export limit. ++ * + * Export route limits has several problems. Because exp_routes + * counter is reset before refeed, we don't really know whether - * limit is breached and whether the update is new or not Therefore ++ * limit is breached and whether the update is new or not. Therefore + * the number of really exported routes may exceed the limit + * temporarily (routes exported before and new routes in refeed). + * + * Minor advantage is that if the limit is decreased and refeed is + * requested, the number of exported routes really decrease. + * + * Second problem is that with export limits, we don't know whether + * old was really exported (it might be blocked by limit). When a + * withdraw is exported, we announce it even when the previous + * update was blocked. This is not a big issue, but the same problem + * is in updating exp_routes counter. Therefore, to be consistent in + * increases and decreases of exp_routes, we count exported routes + * regardless of blocking by limits. + * + * Similar problem is in handling updates - when a new route is + * received and blocking is active, the route would be blocked, but + * when an update for the route will be received later, the update + * would be propagated (as old != NULL). Therefore, we have to block + * also non-new updates (contrary to import blocking). + */ + + struct proto_limit *l = ah->out_limit; + if (l && new) + { + if ((!old || refeed) && (stats->exp_routes >= l->limit)) + proto_notify_limit(ah, l, stats->exp_routes); + + if (l->state == PLS_BLOCKED) + { + stats->exp_routes++; /* see note above */ + stats->exp_updates_rejected++; + rte_trace_out(D_FILTERS, p, new, "rejected [limit]"); - if (new != new0) - rte_free(new); + new = NULL; ++ ++ if (!old) ++ return; + } + } + - /* FIXME - This is broken because of incorrect 'old' value (see above) */ - if (!new && !old) - return; + if (new) stats->exp_updates_accepted++; else @@@ -349,11 -284,174 +331,172 @@@ } else p->rt_notify(p, ah->table, net, new, old, new->attrs->eattrs); - + } + - + 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 whether 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; - if (new && new != new0) /* Discard temporary rte's */ - rte_free(new); - if (old && old != old0) - rte_free(old); + /* Third case, we use r instead 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->next; 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); } /** diff --cc proto/bgp/bgp.c index 3b9f7cc5e,d59b43080..0b52dedc3 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@@ -984,9 -972,15 +986,19 @@@ bgp_check_config(struct bgp_config *c if (!c->gw_mode) c->gw_mode = (c->multihop || internal) ? GW_RECURSIVE : GW_DIRECT; + /* Disable after error incompatible with restart limit action */ + if (c->c.in_limit && (c->c.in_limit->action == PLA_RESTART) && c->disable_after_error) + c->c.in_limit->action = PLA_DISABLE; ++ + + if ((c->gw_mode == GW_RECURSIVE) && c->c.table->sorted) + cf_error("BGP in recursive mode prohibits sorted table"); + + if (c->deterministic_med && c->c.table->sorted) + cf_error("BGP with deterministic MED prohibits sorted table"); + + if (c->secondary && !c->c.table->sorted) + cf_error("BGP with secondary option requires sorted table"); } static int diff --cc proto/bgp/bgp.h index 1c16f4853,87734425c..c3adf254a --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@@ -40,8 -40,10 +40,9 @@@ struct bgp_config int rr_client; /* Whether neighbor is RR client of me */ int rs_client; /* Whether neighbor is RS client of me */ int advertise_ipv4; /* Whether we should add IPv4 capability advertisement to OPEN message */ - 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 --cc proto/bgp/config.Y index 5feaea0de,f9a5be65e..8b80d7fdc --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@@ -98,13 -99,10 +99,14 @@@ bgp_proto | bgp_proto CAPABILITIES bool ';' { BGP_CFG->capabilities = $3; } | bgp_proto ADVERTISE IPV4 bool ';' { BGP_CFG->advertise_ipv4 = $4; } | bgp_proto PASSWORD TEXT ';' { BGP_CFG->password = $3; } - | bgp_proto ROUTE LIMIT expr ';' { BGP_CFG->route_limit = $4; } + | bgp_proto ROUTE LIMIT expr ';' { + this_proto->in_limit = cfg_allocz(sizeof(struct proto_limit)); + this_proto->in_limit->limit = $4; + this_proto->in_limit->action = PLA_RESTART; + } | 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; } ;