From b662290f40ea0fa0b1a1ba283e50e833724f2050 Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Thu, 10 Jan 2013 13:07:33 +0100 Subject: [PATCH] Separate import and receive limits. They have different behavior w.r.t. filtered routes that are kept. --- README | 2 +- doc/bird.sgml | 20 +++++++++++++------ nest/config.Y | 3 ++- nest/proto.c | 21 ++++++++++++++------ nest/protocol.h | 15 ++++++++++++--- nest/rt-table.c | 49 ++++++++++++++++++++++++++++++++++++++++++----- proto/bgp/bgp.c | 1 + proto/pipe/pipe.c | 5 +++++ 8 files changed, 94 insertions(+), 22 deletions(-) diff --git a/README b/README index 5c2ef076..daeb18bd 100644 --- a/README +++ b/README @@ -3,7 +3,7 @@ (c) 1998--2008 Martin Mares (c) 1998--2000 Pavel Machek (c) 1998--2008 Ondrej Filip - (c) 2009--2011 CZ.NIC z.s.p.o. + (c) 2009--2013 CZ.NIC z.s.p.o. ================================================================================ diff --git a/doc/bird.sgml b/doc/bird.sgml index 4e04a138..b0d4e6a1 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -482,15 +482,23 @@ to zero to disable it. An empty is equivalent to receive limit + Specify an receive route limit (a maximum number of routes + received from the protocol and remembered). It works almost + identically to import limit option, the only + difference is that if export limit Specify an export route limit, works similarly to diff --git a/nest/config.Y b/nest/config.Y index dbd72055..e46b5fb5 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -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; } diff --git a/nest/proto.c b/nest/proto.c index b976a6cb..7e7fb7fa 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -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 */ diff --git a/nest/protocol.h b/nest/protocol.h index cf2ca0a4..033a0ede 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -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 */ diff --git a/nest/rt-table.c b/nest/rt-table.c index 2f0840f0..99175448 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -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) diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index 249d2e07..0f351b44 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -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 */ diff --git a/proto/pipe/pipe.c b/proto/pipe/pipe.c index 6099d284..51be3c7d 100644 --- a/proto/pipe/pipe.c +++ b/proto/pipe/pipe.c @@ -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;