Separate import and receive limits.

They have different behavior w.r.t. filtered routes that are kept.
This commit is contained in:
Ondrej Zajicek 2013-01-10 13:07:33 +01:00
parent 79b4e12e60
commit b662290f40
8 changed files with 94 additions and 22 deletions

2
README
View file

@ -3,7 +3,7 @@
(c) 1998--2008 Martin Mares <mj@ucw.cz>
(c) 1998--2000 Pavel Machek <pavel@ucw.cz>
(c) 1998--2008 Ondrej Filip <feela@network.cz>
(c) 2009--2011 CZ.NIC z.s.p.o.
(c) 2009--2013 CZ.NIC z.s.p.o.
================================================================================

View file

@ -482,15 +482,23 @@ to zero to disable it. An empty <cf><m/switch/</cf> is equivalent to <cf/on/
Specify an import route limit (a maximum number of routes
imported from the protocol) and optionally the action to be
taken when the limit is hit. Warn action just prints warning
log message. Block action ignores new routes coming from the
log message. Block action discards new routes coming from the
protocol. Restart and disable actions shut the protocol down
like appropriate commands. Disable is the default action if an
action is not explicitly specified. Note that limits are reset
during protocol reconfigure, reload or restart. Also note that
if <cf/import keep filtered/ is active, filtered routes are
counted towards the limit and blocked routes are forgotten, as
the main purpose of the import limit is to protect routing
tables from overflow. Default: <cf/none/.
during protocol reconfigure, reload or restart. Default: <cf/none/.
<tag>receive limit <m/number/ [action warn | block | restart | disable]</tag>
Specify an receive route limit (a maximum number of routes
received from the protocol and remembered). It works almost
identically to <cf>import limit</cf> option, the only
difference is that if <cf/import keep filtered/ option is
active, filtered routes are counted towards the limit and
blocked routes are forgotten, as the main purpose of the
receive limit is to protect routing tables from
overflow. Import limit, on the contrary, counts accepted
routes only and routes blocked by the limit are handled like
filtered routes. Default: <cf/none/.
<tag>export limit <m/number/ [action warn | block | restart | disable]</tag>
Specify an export route limit, works similarly to

View file

@ -44,7 +44,7 @@ CF_DECLS
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, KEEP, FILTERED)
CF_KEYWORDS(RECEIVE, LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE, KEEP, FILTERED)
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, SORTED)
@ -185,6 +185,7 @@ proto_item:
| MRTDUMP mrtdump_mask { this_proto->mrtdump = $2; }
| IMPORT imexport { this_proto->in_filter = $2; }
| EXPORT imexport { this_proto->out_filter = $2; }
| RECEIVE LIMIT limit_spec { this_proto->rx_limit = $3; }
| IMPORT LIMIT limit_spec { this_proto->in_limit = $3; }
| EXPORT LIMIT limit_spec { this_proto->out_limit = $3; }
| IMPORT KEEP FILTERED bool { this_proto->in_keep_filtered = $4; }

View file

@ -344,6 +344,7 @@ protos_postconfig(struct config *c)
WALK_LIST(x, c->protos)
{
DBG(" %s", x->name);
p = x->protocol;
if (p->postconfig)
p->postconfig(x);
@ -410,6 +411,7 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config
{
p->main_ahook->in_filter = nc->in_filter;
p->main_ahook->out_filter = nc->out_filter;
p->main_ahook->rx_limit = nc->rx_limit;
p->main_ahook->in_limit = nc->in_limit;
p->main_ahook->out_limit = nc->out_limit;
p->main_ahook->in_keep_filtered = nc->in_keep_filtered;
@ -804,9 +806,11 @@ proto_schedule_feed(struct proto *p, int initial)
p->main_ahook = proto_add_announce_hook(p, p->table, &p->stats);
p->main_ahook->in_filter = p->cf->in_filter;
p->main_ahook->out_filter = p->cf->out_filter;
p->main_ahook->rx_limit = p->cf->rx_limit;
p->main_ahook->in_limit = p->cf->in_limit;
p->main_ahook->out_limit = p->cf->out_limit;
p->main_ahook->in_keep_filtered = p->cf->in_keep_filtered;
proto_reset_limit(p->main_ahook->rx_limit);
proto_reset_limit(p->main_ahook->in_limit);
proto_reset_limit(p->main_ahook->out_limit);
}
@ -978,6 +982,7 @@ proto_limit_name(struct proto_limit *l)
* proto_notify_limit: notify about limit hit and take appropriate action
* @ah: announce hook
* @l: limit being hit
* @dir: limit direction (PLD_*)
* @rt_count: the number of routes
*
* The function is called by the route processing core when limit @l
@ -985,10 +990,11 @@ proto_limit_name(struct proto_limit *l)
* according to @l->action.
*/
void
proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, u32 rt_count)
proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, int dir, u32 rt_count)
{
const char *dir_name[PLD_MAX] = { "receive", "import" , "export" };
const byte dir_down[PLD_MAX] = { PDC_RX_LIMIT_HIT, PDC_IN_LIMIT_HIT, PDC_OUT_LIMIT_HIT };
struct proto *p = ah->proto;
int dir = (ah->in_limit == l);
if (l->state == PLS_BLOCKED)
return;
@ -996,7 +1002,7 @@ proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, u32 rt_count
/* For warning action, we want the log message every time we hit the limit */
if (!l->state || ((l->action == PLA_WARN) && (rt_count == l->limit)))
log(L_WARN "Protocol %s hits route %s limit (%d), action: %s",
p->name, dir ? "import" : "export", l->limit, proto_limit_name(l));
p->name, dir_name[dir], l->limit, proto_limit_name(l));
switch (l->action)
{
@ -1011,8 +1017,7 @@ proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, u32 rt_count
case PLA_RESTART:
case PLA_DISABLE:
l->state = PLS_BLOCKED;
proto_schedule_down(p, l->action == PLA_RESTART,
dir ? PDC_IN_LIMIT_HIT : PDC_OUT_LIMIT_HIT);
proto_schedule_down(p, l->action == PLA_RESTART, dir_down[dir]);
break;
}
}
@ -1146,6 +1151,7 @@ proto_show_basic_info(struct proto *p)
cli_msg(-1006, " Input filter: %s", filter_name(p->cf->in_filter));
cli_msg(-1006, " Output filter: %s", filter_name(p->cf->out_filter));
proto_show_limit(p->cf->rx_limit, "Receive limit:");
proto_show_limit(p->cf->in_limit, "Import limit:");
proto_show_limit(p->cf->out_limit, "Export limit:");
@ -1267,7 +1273,10 @@ proto_cmd_reload(struct proto *p, unsigned int dir, int cnt UNUSED)
* Perhaps, but these hooks work asynchronously.
*/
if (!p->proto->multitable)
proto_reset_limit(p->main_ahook->in_limit);
{
proto_reset_limit(p->main_ahook->rx_limit);
proto_reset_limit(p->main_ahook->in_limit);
}
}
/* re-exporting routes */

View file

@ -95,6 +95,8 @@ struct proto_config {
u32 router_id; /* Protocol specific router ID */
struct rtable_config *table; /* Table we're attached to */
struct filter *in_filter, *out_filter; /* Attached filters */
struct proto_limit *rx_limit; /* Limit for receiving routes from protocol
(relevant when in_keep_filtered is active) */
struct proto_limit *in_limit; /* Limit for importing routes from protocol */
struct proto_limit *out_limit; /* Limit for exporting routes to protocol */
@ -225,8 +227,9 @@ struct proto_spec {
#define PDC_CMD_DISABLE 0x11 /* Result of disable command */
#define PDC_CMD_RESTART 0x12 /* Result of restart command */
#define PDC_CMD_SHUTDOWN 0x13 /* Result of global shutdown */
#define PDC_IN_LIMIT_HIT 0x21 /* Route import limit reached */
#define PDC_OUT_LIMIT_HIT 0x22 /* Route export limit reached */
#define PDC_RX_LIMIT_HIT 0x21 /* Route receive limit reached */
#define PDC_IN_LIMIT_HIT 0x22 /* Route import limit reached */
#define PDC_OUT_LIMIT_HIT 0x23 /* Route export limit reached */
void *proto_new(struct proto_config *, unsigned size);
@ -373,6 +376,11 @@ extern struct proto_config *cf_dev_proto;
* Protocol limits
*/
#define PLD_RX 0 /* Receive limit */
#define PLD_IN 1 /* Import limit */
#define PLD_OUT 2 /* Export limit */
#define PLD_MAX 3
#define PLA_WARN 1 /* Issue log warning */
#define PLA_BLOCK 2 /* Block new routes */
#define PLA_RESTART 4 /* Force protocol restart */
@ -388,7 +396,7 @@ struct proto_limit {
byte state; /* State of limit (PLS_*) */
};
void proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, u32 rt_count);
void proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, int dir, u32 rt_count);
static inline void
proto_reset_limit(struct proto_limit *l)
@ -408,6 +416,7 @@ struct announce_hook {
struct proto *proto;
struct filter *in_filter; /* Input filter */
struct filter *out_filter; /* Output filter */
struct proto_limit *rx_limit; /* Receive limit (for in_keep_filtered) */
struct proto_limit *in_limit; /* Input limit */
struct proto_limit *out_limit; /* Output limit */
struct proto_stats *stats; /* Per-table protocol statistics */

View file

@ -285,7 +285,7 @@ do_rt_notify(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tm
if (l && new)
{
if ((!old || refeed) && (stats->exp_routes >= l->limit))
proto_notify_limit(ah, l, stats->exp_routes);
proto_notify_limit(ah, l, PLD_OUT, stats->exp_routes);
if (l->state == PLS_BLOCKED)
{
@ -700,16 +700,22 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str
return;
}
struct proto_limit *l = ah->in_limit;
int new_ok = rte_is_ok(new);
int old_ok = rte_is_ok(old);
struct proto_limit *l = ah->rx_limit;
if (l && !old && new)
{
u32 all_routes = stats->imp_routes + stats->filt_routes;
if (all_routes >= l->limit)
proto_notify_limit(ah, l, all_routes);
proto_notify_limit(ah, l, PLD_RX, all_routes);
if (l->state == PLS_BLOCKED)
{
/* In receive limit the situation is simple, old is NULL so
we just free new and exit like nothing happened */
stats->imp_updates_ignored++;
rte_trace_in(D_FILTERS, p, new, "ignored [limit]");
rte_free_quick(new);
@ -717,8 +723,39 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str
}
}
int new_ok = rte_is_ok(new);
int old_ok = rte_is_ok(old);
l = ah->in_limit;
if (l && !old_ok && new_ok)
{
if (stats->imp_routes >= l->limit)
proto_notify_limit(ah, l, PLD_IN, stats->imp_routes);
if (l->state == PLS_BLOCKED)
{
/* In import limit the situation is more complicated. We
shouldn't just drop the route, we should handle it like
it was filtered. We also have to continue the route
processing if old or new is non-NULL, but we should exit
if both are NULL as this case is probably assumed to be
already handled. */
stats->imp_updates_ignored++;
rte_trace_in(D_FILTERS, p, new, "ignored [limit]");
if (ah->in_keep_filtered)
new->flags |= REF_FILTERED;
else
{ rte_free_quick(new); new = NULL; }
/* Note that old && !new could be possible when
ah->in_keep_filtered changed in the recent past. */
if (!old && !new)
return;
new_ok = 0;
goto skip_stats1;
}
}
if (new_ok)
stats->imp_updates_accepted++;
@ -727,6 +764,8 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str
else
stats->imp_withdraws_ignored++;
skip_stats1:
if (new)
rte_is_filtered(new) ? stats->filt_routes++ : stats->imp_routes++;
if (old)

View file

@ -878,6 +878,7 @@ bgp_shutdown(struct proto *P)
subcode = 4; // Errcode 6, 4 - administrative reset
break;
case PDC_RX_LIMIT_HIT:
case PDC_IN_LIMIT_HIT:
subcode = 1; // Errcode 6, 1 - max number of prefixes reached
/* log message for compatibility */

View file

@ -200,6 +200,11 @@ pipe_postconfig(struct proto_config *C)
cf_error("Name of peer routing table not specified");
if (c->peer == C->table)
cf_error("Primary table and peer table must be different");
if (C->in_keep_filtered)
cf_error("Pipe protocol prohibits keeping filtered routes");
if (C->rx_limit)
cf_error("Pipe protocol does not support receive limits");
}
extern int proto_reconfig_type;