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,
%type <ro> roa_args
%type <rot> roa_table_arg
%type <sd> sym_args
- %type <i> 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 <i> 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 <i> 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 <ps> proto_patt proto_patt2
+%type <g> limit_spec
CF_GRAMMAR
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;
}
- * limit is breached and whether the update is new or not Therefore
+ 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
- if (new != new0)
- rte_free(new);
++ * 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]");
- /* FIXME - This is broken because of incorrect 'old' value (see above) */
- if (!new && !old)
- return;
+ new = NULL;
++
++ if (!old)
++ return;
+ }
+ }
+
+
if (new)
stats->exp_updates_accepted++;
else
}
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);
}
/**