[PATCH 3/3] * Implement general protocol limits, v3
--- doc/bird.sgml | 17 +++++- nest/config.Y | 28 ++++++++- nest/proto-hooks.c | 12 ++++ nest/proto.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++-- nest/protocol.h | 45 ++++++++++++- nest/rt-table.c | 132 +++++++++++++++++++++++++++++--------- proto/bgp/bgp.c | 37 ++++++++--- proto/bgp/bgp.h | 1 - proto/bgp/config.Y | 9 ++- proto/bgp/packets.c | 3 - proto/pipe/pipe.c | 11 +++ proto/pipe/pipe.h | 3 + 12 files changed, 419 insertions(+), 57 deletions(-) diff --git a/doc/bird.sgml b/doc/bird.sgml index 7f53f02..ef9901d 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -415,6 +415,20 @@ to zero to disable it. An empty <cf><m/switch/</cf> is equivalent to <cf/on/ <tag>export <m/filter/</tag> This is similar to the <cf>import</cf> keyword, except that it works in the direction from the routing table to the protocol. Default: <cf/none/. + <tag>import limit <m/number/ exceed warn | block | restart | disable</tag> + Specify limit action to be taken when import routes limit is hit. Warn action + does nothing more than printing error log message. Block action ignores + new routes (and converts route updates to withdraws) coming from protocol. + Restart action shuts protocol down (and core immediately restarts it). + Disable action takes protocol down and restrict automatic protocol restart + until protocol is explicitly enabled from CLI. + Default: <cf/none/. + + <tag>export limit <m/number/ exceed warn | block | disable</tag> + Specify limit action to be taken when export routes limit is hit. + Actions are the same as in <cf>import</cf> keyword except <cf/restart/. + Default: <cf/none/. + <tag>description "<m/text/"</tag> This is an optional description of the protocol. It is displayed as a part of the output of 'show route all' command. @@ -1271,7 +1285,8 @@ for each neighbor using the following configuration parameters: <tag>route limit <m/number/</tag> The maximal number of routes that may be imported from the protocol. If the route limit is - exceeded, the connection is closed with error. Default: no limit. + exceeded, the connection is closed with error. Limit is currently implemented as + <cf/import limit number exceed restart/. Default: no limit. <tag>disable after error <m/switch/</tag> When an error is encountered (either locally or by the other side), disable the instance automatically diff --git a/nest/config.Y b/nest/config.Y index a6baf4e..0f2a8a8 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -43,6 +43,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(EXCEED, LIMIT, WARN, BLOCK, RESTART, DISABLE) CF_KEYWORDS(PASSWORD, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, INTERFACES) CF_KEYWORDS(PRIMARY, STATS, COUNT, FOR, COMMANDS, PREEXPORT, GENERATE) CF_KEYWORDS(LISTEN, BGP, V6ONLY, DUAL, ADDRESS, PORT, PASSWORDS, DESCRIPTION) @@ -59,7 +60,7 @@ CF_ENUM(T_ENUM_RTD, RTD_, ROUTER, DEVICE, BLACKHOLE, UNREACHABLE, PROHIBIT, MULT %type <r> rtable %type <s> optsym %type <ra> r_args -%type <i> proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_or_preexport +%type <i> proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_or_preexport limit_action %type <ps> proto_patt proto_patt2 CF_GRAMMAR @@ -153,6 +154,24 @@ proto_item: | MRTDUMP mrtdump_mask { this_proto->mrtdump = $2; } | IMPORT imexport { this_proto->in_filter = $2; } | EXPORT imexport { this_proto->out_filter = $2; } + | IMPORT LIMIT expr EXCEED limit_action { + if (!this_proto->in_limit) + this_proto->in_limit = cfg_allocz(sizeof(struct proto_limit)); + this_proto->in_limit->direction = PL_IMPORT; + this_proto->in_limit->limit = $3; + this_proto->in_limit->action = $5; + if (($5 == PL_ACTION_RESTART) && (config_is_pipe(this_proto))) + cf_error("RESTART action can't be used in pipes. Use DISABLE action instead"); + } + | EXPORT LIMIT expr EXCEED limit_action { + if (!this_proto->out_limit) + this_proto->out_limit = cfg_allocz(sizeof(struct proto_limit)); + this_proto->out_limit->direction = PL_EXPORT; + this_proto->out_limit->limit = $3; + this_proto->out_limit->action = $5; + if ($5 == PL_ACTION_RESTART) + cf_error("RESTART action can't be used in export limiter. Use DISABLE action instead"); + } | TABLE rtable { this_proto->table = $2; } | ROUTER ID idval { this_proto->router_id = $3; } | DESCRIPTION TEXT { this_proto->dsc = $2; } @@ -165,6 +184,13 @@ imexport: | NONE { $$ = FILTER_REJECT; } ; +limit_action: + WARN { $$ = PL_ACTION_WARN; } + | BLOCK { $$ = PL_ACTION_BLOCK; } + | RESTART { $$ = PL_ACTION_RESTART; } + | DISABLE { $$ = PL_ACTION_DISABLE; } + ; + rtable: SYM { if ($1->class != SYM_TABLE) cf_error("Table name expected"); diff --git a/nest/proto-hooks.c b/nest/proto-hooks.c index f026192..92bdf01 100644 --- a/nest/proto-hooks.c +++ b/nest/proto-hooks.c @@ -205,6 +205,18 @@ void rt_notify(struct proto *p, net *net, rte *new, rte *old, ea_list *attrs) { DUMMY; } /** + * limit_notify - notify instance about limit hit + * @p: protocol instance + * @a: announce hook + * @l: linit structure + * @table: table where route limit occurs + * + * Function should return 0 unless it shuts protocol down + */ +int limit_notify(struct proto *, struct announce_hook *, struct proto_limit *l, struct rtable *table) +{ DUMMY; } + +/** * neigh_notify - notify instance about neighbor status change * @neigh: a neighbor cache entry * diff --git a/nest/proto.c b/nest/proto.c index 451db2c..3b261a2 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -36,6 +36,10 @@ static list initial_proto_list; static list flush_proto_list; static struct proto *initial_device_proto; +/* protocol limiting variables */ +static struct rate_limit rl_rt_limit; +static event *proto_shut_event; + static event *proto_flush_event; static char *p_states[] = { "DOWN", "START", "UP", "STOP" }; @@ -44,6 +48,8 @@ static char *c_states[] = { "HUNGRY", "FEEDING", "HAPPY", "FLUSHING" }; static void proto_flush_all(void *); static void proto_rethink_goal(struct proto *p); static char *proto_state_name(struct proto *p); +static char *proto_limit_name(struct proto_limit *l); +static void proto_shutdown_abusers(void *unused UNUSED); static void proto_enqueue(list *l, struct proto *p) @@ -388,6 +394,8 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config { a->in_filter = nc->in_filter; a->out_filter = nc->out_filter; + a->in_limit = nc->in_limit; + a->out_limit = nc->out_limit; } /* Execute protocol specific reconfigure hook */ @@ -492,7 +500,7 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty PD(p, "Unconfigured"); p->cf_new = NULL; } - p->reconfiguring = 1; + p->flags |= PFLAG_RECONFIGURING; config_add_obstacle(old); proto_rethink_goal(p); } @@ -525,7 +533,7 @@ proto_rethink_goal(struct proto *p) { struct protocol *q; - if (p->reconfiguring && p->core_state == FS_HUNGRY && p->proto_state == PS_DOWN) + if (PROTO_IS_RECONFIGURING(p) && p->core_state == FS_HUNGRY && p->proto_state == PS_DOWN) { struct proto_config *nc = p->cf_new; DBG("%s has shut down for reconfiguration\n", p->name); @@ -539,7 +547,7 @@ proto_rethink_goal(struct proto *p) } /* Determine what state we want to reach */ - if (p->disabled || p->reconfiguring) + if (p->disabled || PROTO_IS_RECONFIGURING(p)) { p->core_goal = FS_HUNGRY; if (p->core_state == FS_HUNGRY && p->proto_state == PS_DOWN) @@ -560,6 +568,8 @@ proto_rethink_goal(struct proto *p) DBG("Kicking %s up\n", p->name); PD(p, "Starting"); proto_init_instance(p); + /* Zeroing limit-related flags */ + p->flags &= ~PLIMIT_FLAGS; proto_notify_state(p, (q->start ? q->start(p) : PS_UP)); } } @@ -675,6 +685,8 @@ protos_build(void) proto_pool = rp_new(&root_pool, "Protocols"); proto_flush_event = ev_new(proto_pool); proto_flush_event->hook = proto_flush_all; + proto_shut_event = ev_new(proto_pool); + proto_shut_event->hook = proto_shutdown_abusers; } static void @@ -733,6 +745,9 @@ proto_feed_initial(void *P) /* Set filters */ a->in_filter = p->cf->in_filter; a->out_filter = p->cf->out_filter; + /* Set limits */ + a->in_limit = p->cf->in_limit; + a->out_limit = p->cf->out_limit; /* Set pointer to stats */ a->stats = &p->stats; } @@ -773,6 +788,13 @@ proto_schedule_feed(struct proto *p, int initial) if (!initial) p->stats.exp_routes = 0; + /* + * Remove export limit if set. + * We assume something is changed (protocol limit or filter or + * other host announces) so refeeding protocol will not cause + * export limit to hit again. + */ + proto_clear_limits(p, PFLAG_ELIMIT|PFLAG_ELIMIT_BLOCK); proto_relink(p); p->attn->hook = initial ? proto_feed_initial : proto_feed_more; ev_schedule(p->attn); @@ -808,6 +830,100 @@ proto_request_feeding(struct proto *p) } /** + * proto_clear_limits - clear limiting flags in protocol instance + * @p: given protocol + * @flags: flags to clear + * + */ +void proto_clear_limits(struct proto *p, u32 flags) +{ + struct announce_hook *a; + + p->flags &= ~flags; + + for (a = p->ahooks; a; a = a->next) + a->flags &= ~flags; +} + +static void +proto_shutdown_abusers(void *unused UNUSED) +{ + struct proto *p, *p_next; + int disable; + + WALK_LIST_DELSAFE(p, p_next, active_proto_list) + { + if (!(p->flags & (PFLAG_RESTART|PFLAG_DISABLE))) + continue; + + disable = (p->flags & PFLAG_DISABLE) ? 1 : 0; + + p->disabled = 1; + proto_rethink_goal(p); + if (!disable) + { + p->disabled = 0; + proto_rethink_goal(p); + } + } +} + +/** + * proto_notify_limit: notify protocol instance about limit hit and take appropriate action + * @p: given protocol + * @a: announce hook + * @l: limit being hit + * @table: table where limititng occurs + * + * If limit hook exists it is called and returned value is examined. + * If non-zero value returned, processing stops. Overwise action is taken + * depending on l->action configured in limit instance. Non-zero value should be + * used for proper protocol shutdown only. Setting one of PFLAG_ILIMIT|PFLAG_ELIMIT is required + * if protocol is not going down. + * + * Returns 1 if processing must be stopped, 0 overwise. + */ +int +proto_notify_limit(struct proto *p, struct announce_hook *a, struct proto_limit *l, struct rtable *table) +{ + int ret = 1, flag; + + log_rl(&rl_rt_limit, L_ERR "Protocol %s hits route %s limit (%d), action: %s", p->name, + (l->direction == PL_IMPORT) ? "import" : "export", l->limit, proto_limit_name(l)); + + flag = (l->direction == PL_IMPORT) ? PFLAG_ILIMIT : PFLAG_ELIMIT; + + /* Skip multiple filter hit invocations */ + if ((l->action != PL_ACTION_WARN) && (p->flags & flag)) + return 1; + + p->flags |= flag; + if (a) + a->flags |= flag; + + if (p->limit_notify && (p->limit_notify(p, a, l, table))) + return 1; + + switch (l->action) + { + case PL_ACTION_WARN: + ret = 0; + break; + case PL_ACTION_BLOCK: + p->flags |= (l->direction == PL_IMPORT) ? PFLAG_ILIMIT_BLOCK : PFLAG_ELIMIT_BLOCK; + break; + case PL_ACTION_RESTART: + case PL_ACTION_DISABLE: + /* Schedule instance shutdown */ + p->flags |= (l->action == PL_ACTION_DISABLE) ? PFLAG_DISABLE : PFLAG_RESTART; + ev_schedule(proto_shut_event); + break; + } + + return ret; +} + +/** * proto_notify_state - notify core about protocol state change * @p: protocol the state of which has changed * @ps: the new status @@ -911,6 +1027,19 @@ proto_state_name(struct proto *p) #undef P } +static char * +proto_limit_name(struct proto_limit *l) +{ + switch (l->action) + { + case PL_ACTION_WARN: return "WARN"; + case PL_ACTION_BLOCK: return "BLOCK"; + case PL_ACTION_RESTART: return "RESTART"; + case PL_ACTION_DISABLE: return "DISABLE"; + } + return "unknown"; +} + static void proto_do_show_stats(struct proto_stats *s) { @@ -934,7 +1063,8 @@ proto_do_show_stats(struct proto_stats *s) void proto_cmd_show(struct proto *p, unsigned int verbose, int cnt) { - byte buf[256], tbuf[TM_DATETIME_BUFFER_SIZE]; + byte buf[256], tbuf[TM_DATETIME_BUFFER_SIZE], lbuf[25]; + struct proto_limit *l; struct announce_hook *a; /* First protocol - show header */ @@ -944,12 +1074,16 @@ proto_cmd_show(struct proto *p, unsigned int verbose, int cnt) buf[0] = 0; if (p->proto->get_status) p->proto->get_status(p, buf); + if (p->flags & (PFLAG_ILIMIT|PFLAG_ELIMIT)) + bsnprintf(lbuf, sizeof(lbuf), "%s/%s", proto_state_name(p), "LI"); + else + strcpy(lbuf, proto_state_name(p)); tm_format_datetime(tbuf, &config->tf_proto, p->last_state_change); cli_msg(-1002, "%-8s %-8s %-8s %-5s %-10s %s", p->name, p->proto->name, p->table->name, - proto_state_name(p), + lbuf, tbuf, buf); if (verbose) @@ -965,6 +1099,18 @@ proto_cmd_show(struct proto *p, unsigned int verbose, int cnt) cli_msg(-1006, " Table: %s", a->table->name); cli_msg(-1006, " Input filter: %s", filter_name(a->in_filter)); cli_msg(-1006, " Output filter: %s", filter_name(a->out_filter)); + if (l = a->in_limit) + { + cli_msg(-1006, " Import limit: %4d, action: %7s%s", l->limit, + proto_limit_name(l), (a->flags & PFLAG_ILIMIT) ? " [ LIMIT HIT ]" : ""); + } else if (a->flags & PFLAG_ILIMIT) + cli_msg(-1006, " Old Import limit: [HIT][reload/restart required]"); + if (l = a->out_limit) + { + cli_msg(-1006, " Export limit: %4d, action: %7s%s", l->limit, + proto_limit_name(l), (a->flags & PFLAG_ELIMIT) ? " [ LIMIT HIT ]" : ""); + } else if (a->flags & PFLAG_ELIMIT) + cli_msg(-1006, " Old export limit: [HIT][reload/restart required]"); if (p->proto_state != PS_DOWN) { @@ -1038,6 +1184,8 @@ proto_cmd_restart(struct proto *p, unsigned int arg UNUSED, int cnt UNUSED) void proto_cmd_reload(struct proto *p, unsigned int dir, int cnt UNUSED) { + u32 flags; + if (p->disabled) { cli_msg(-8, "%s: already disabled", p->name); @@ -1052,13 +1200,31 @@ proto_cmd_reload(struct proto *p, unsigned int dir, int cnt UNUSED) /* re-importing routes */ if (dir != CMD_RELOAD_OUT) + { + /* + * Removing limit hit flags should be safe because: + * current (and planned) limiting actions block + * new route import only. Route withdrawal is not blocked. + * At this moment core has a small (possibly zero) subset of + * routes which are announced by protocol. Same route announce + * from the same protocol are ignored by core so we can safely + * re-import all routes. + */ + flags = p->flags & (PFLAG_ILIMIT|PFLAG_ILIMIT_BLOCK); + p->flags &= ~flags; if (! (p->reload_routes && p->reload_routes(p))) { cli_msg(-8006, "%s: reload failed", p->name); + /* reload failed, adding removed flags back */ + p->flags |= flags; return; } + } - /* re-exporting routes */ + /* + * re-exporting routes. + * Export limit flags are dispatched in proto_request_feeding() + */ if (dir != CMD_RELOAD_IN) proto_request_feeding(p); diff --git a/nest/protocol.h b/nest/protocol.h index 09ccd9c..91bb07b 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -28,6 +28,7 @@ struct event; struct ea_list; struct eattr; struct symbol; +struct announce_hook; /* * Routing Protocol @@ -68,6 +69,26 @@ void protos_dump_all(void); #define GA_FULL 2 /* Result = both name and value */ /* + * Protocol limits + */ +#define PL_IMPORT 1 /* Import limit*/ +#define PL_EXPORT 2 /* Export limit */ + +#define PL_ACTION_WARN 1 /* Issue log warning */ +#define PL_ACTION_BLOCK 2 /* Block new routes */ +#define PL_ACTION_RESTART 4 /* Force protocol restart */ +#define PL_ACTION_DISABLE 5 /* Shutdown and disable protocol */ + +struct proto_limit { + int direction; /* PL_IMPORT|PL_EXPORT */ + int limit; /* maximum prefix number */ + int action; /* action to take */ +}; + +int proto_notify_limit(struct proto *p, struct announce_hook *a, struct proto_limit *l, struct rtable *table); +void proto_clear_limits(struct proto *p, u32 flags); + +/* * Known protocols */ @@ -92,12 +113,15 @@ 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 *in_limit; /* Limit for importing routes from protocol */ + struct proto_limit *out_limit; /* Limit for exporting routes to protocol */ /* Check proto_reconfigure() and proto_copy_config() after changing struct proto_config */ /* Protocol-specific data follow... */ }; + /* Protocol statistics */ struct proto_stats { /* Import - from protocol to core */ @@ -141,7 +165,7 @@ struct proto { unsigned proto_state; /* Protocol state machine (see below) */ unsigned core_state; /* Core state machine (see below) */ unsigned core_goal; /* State we want to reach (see below) */ - unsigned reconfiguring; /* We're shutting down due to reconfiguration */ + unsigned flags; /* Various protocol flags */ unsigned refeeding; /* We are refeeding (valid only if core_state == FS_FEEDING) */ u32 hash_key; /* Random key used for hashing of neighbors */ bird_clock_t last_state_change; /* Time of last state transition */ @@ -154,6 +178,7 @@ struct proto { * if_notify Notify protocol about interface state changes. * ifa_notify Notify protocol about interface address changes. * rt_notify Notify protocol about routing table updates. + * limit_notify Notify protocol about import/export limit hit. * neigh_notify Notify protocol about neighbor cache events. * make_tmp_attrs Construct ea_list from private attrs stored in rte. * store_tmp_attrs Store private attrs back to the rte. @@ -169,6 +194,7 @@ struct proto { void (*if_notify)(struct proto *, unsigned flags, struct iface *i); void (*ifa_notify)(struct proto *, unsigned flags, struct ifa *a); void (*rt_notify)(struct proto *, struct rtable *table, struct network *net, struct rte *new, struct rte *old, struct ea_list *attrs); + int (*limit_notify)(struct proto *, struct announce_hook *a, struct proto_limit *l, struct rtable *table); void (*neigh_notify)(struct neighbor *neigh); struct ea_list *(*make_tmp_attrs)(struct rte *rt, struct linpool *pool); void (*store_tmp_attrs)(struct rte *rt, struct ea_list *attrs); @@ -199,6 +225,18 @@ struct proto { /* Hic sunt protocol-specific data */ }; +#define PFLAG_RECONFIGURING 0x01 /* We're shutting down due to reconfiguration */ +#define PFLAG_ILIMIT 0x02 /* Import route limit reached */ +#define PFLAG_ELIMIT 0x04 /* Export route limit reached */ +#define PFLAG_ILIMIT_BLOCK 0x08 /* Block route imports */ +#define PFLAG_ELIMIT_BLOCK 0x10 /* Block route exports */ +#define PFLAG_RESTART 0x20 /* Protocol needs restart */ +#define PFLAG_DISABLE 0x40 /* Protocol needs disabling */ + +#define PLIMIT_FLAGS (PFLAG_ILIMIT|PFLAG_ELIMIT|PFLAG_ILIMIT_BLOCK|PFLAG_ELIMIT_BLOCK|PFLAG_DISABLE) + +#define PROTO_IS_RECONFIGURING(x) ((x)->flags & PFLAG_RECONFIGURING) + struct proto_spec { void *ptr; int patt; @@ -346,12 +384,15 @@ extern struct proto_config *cf_dev_proto; struct announce_hook { node n; + struct announce_hook *next; /* Next hook for the same protocol */ struct rtable *table; struct proto *proto; + u32 flags; /* General flags */ struct filter *in_filter; /* Input filter */ struct filter *out_filter; /* Output filter */ + struct proto_limit *in_limit; /* Input limit */ + struct proto_limit *out_limit; /* Output limit */ struct proto_stats *stats; /* Per-table protocol statistics */ - struct announce_hook *next; /* Next hook for the same protocol */ }; struct announce_hook *proto_add_announce_hook(struct proto *, struct rtable *); diff --git a/nest/rt-table.c b/nest/rt-table.c index 0c0a042..c5752bc 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -161,34 +161,49 @@ rte_better(rte *new, rte *old) } static void -rte_trace(struct proto *p, rte *e, int dir, char *msg) +rte_trace(struct proto *p, struct rtable *t, rte *e, int dir, char *msg) { byte via[STD_ADDRESS_P_LENGTH+32]; rt_format_via(e, via); - log(L_TRACE "%s %c %s %I/%d %s", p->name, dir, msg, e->net->n.prefix, e->net->n.pxlen, via); + log(L_TRACE "%s %c [%s] %s %I/%d %s", p->name, dir, t->name, msg, e->net->n.prefix, e->net->n.pxlen, via); } static inline void -rte_trace_in(unsigned int flag, struct proto *p, rte *e, char *msg) +rte_trace_in(unsigned int flag, struct proto *p, struct rtable *t, rte *e, char *msg) { if (p->debug & flag) - rte_trace(p, e, '>', msg); + rte_trace(p, t, e, '>', msg); } static inline void -rte_trace_out(unsigned int flag, struct proto *p, rte *e, char *msg) +rte_trace_out(unsigned int flag, struct proto *p, struct rtable *t, rte *e, char *msg) { if (p->debug & flag) - rte_trace(p, e, '<', msg); + rte_trace(p, t, e, '<', msg); } -static inline void +/** + * do_rte_announce - announce new rte to protocol + * @a: pointer to announce hook + * @type: announce type (RA_ANY or RA_OPTIMAL) + * @net: pointer to announced network + * @new: new rte or NULL + * @old: previous rte or NULL + * @tmpa: new rte attributes (possibly modified by filter) + * @refeed: are we refeeding protocol + * + * Returns 1 if feeding can continue, 0 overwise + * + */ +static inline int do_rte_announce(struct announce_hook *a, int type UNUSED, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) { struct proto *p = a->proto; struct filter *filter = a->out_filter; struct proto_stats *stats = a->stats; + struct proto_limit *l = a->out_limit; + struct rtable *table = a->table; rte *new0 = new; rte *old0 = old; int ok; @@ -204,7 +219,7 @@ do_rte_announce(struct announce_hook *a, int type UNUSED, net *net, rte *new, rt drop_reason = "rejected by protocol"; } else if (ok) - rte_trace_out(D_FILTERS, p, new, "forced accept by protocol"); + rte_trace_out(D_FILTERS, p, table, new, "forced accept by protocol"); else if ((filter == FILTER_REJECT) || (filter && f_run(filter, &new, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)) { @@ -213,7 +228,7 @@ do_rte_announce(struct announce_hook *a, int type UNUSED, net *net, rte *new, rt } if (drop_reason) { - rte_trace_out(D_FILTERS, p, new, drop_reason); + rte_trace_out(D_FILTERS, p, table, new, drop_reason); if (new != new0) rte_free(new); new = NULL; @@ -261,7 +276,38 @@ do_rte_announce(struct announce_hook *a, int type UNUSED, net *net, rte *new, rt /* FIXME - This is broken because of incorrect 'old' value (see above) */ if (!new && !old) - return; + return 1; + + /* + * XXX: there is (currently) no way to determine if rte was announced to the protocol, + * especially in case of route limiting active. As a result if block flag is set we're + * 1) ignoring new routes + * 2) convert updates to withdrawals + * + */ + if (new && (p->flags & PFLAG_ELIMIT_BLOCK)) + { + rte_trace_out(D_FILTERS, p, table, new, "ignored [outbound limit hit]"); + if (new != new0) + rte_free(new); + /* New route announce, simply ignore */ + if (!old) + return 0; + rte_trace_out(D_FILTERS, p, table, new, "converted to withdraw [outbound limit hit]"); + /* Route update, change to withdraw */ + new = NULL; + } + + /* Check route limits for new routes */ + if (new && l && !old && (stats->exp_routes + 1 > l->limit) && (proto_notify_limit(p, a, l, table) == 1)) + { + rte_trace_out(D_FILTERS, p, table, new, "ignored [outbound limit hit]"); + /* free allocated data and return */ + if (new != new0) + rte_free(new); + + return 0; + } if (new) stats->exp_updates_accepted++; @@ -278,29 +324,31 @@ do_rte_announce(struct announce_hook *a, int type UNUSED, net *net, rte *new, rt if (p->debug & D_ROUTES) { if (new && old) - rte_trace_out(D_ROUTES, p, new, "replaced"); + rte_trace_out(D_ROUTES, p, table, new, "replaced"); else if (new) - rte_trace_out(D_ROUTES, p, new, "added"); + rte_trace_out(D_ROUTES, p, table, new, "added"); else if (old) - rte_trace_out(D_ROUTES, p, old, "removed"); + rte_trace_out(D_ROUTES, p, table, old, "removed"); } if (!new) - p->rt_notify(p, a->table, net, NULL, old, NULL); + p->rt_notify(p, table, net, NULL, old, NULL); else if (tmpa) { ea_list *t = tmpa; while (t->next) t = t->next; t->next = new->attrs->eattrs; - p->rt_notify(p, a->table, net, new, old, tmpa); + p->rt_notify(p, table, net, new, old, tmpa); t->next = NULL; } else - p->rt_notify(p, a->table, net, new, old, new->attrs->eattrs); + p->rt_notify(p, 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); + + return 1; } /** @@ -417,11 +465,13 @@ static void rte_recalculate(rtable *table, struct announce_hook *a, net *net, struct proto *p, struct proto *src, rte *new, ea_list *tmpa) { struct proto_stats *stats; + struct proto_limit *l; rte *old_best = net->routes; rte *old = NULL; rte **k, *r, *s; stats = a ? a->stats : &p->stats; + l = a ? a->in_limit : p->cf->in_limit; k = &net->routes; /* Find and remove original route from the same protocol */ while (old = *k) { @@ -451,7 +501,7 @@ rte_recalculate(rtable *table, struct announce_hook *a, net *net, struct proto * { /* No changes, ignore the new route */ stats->imp_updates_ignored++; - rte_trace_in(D_ROUTES, p, new, "ignored"); + rte_trace_in(D_ROUTES, p, table, new, "ignored"); rte_free_quick(new); #ifdef CONFIG_RIP /* lastmod is used internally by RIP as the last time @@ -473,6 +523,22 @@ rte_recalculate(rtable *table, struct announce_hook *a, net *net, struct proto * return; } + /* Check limit for imported routes */ + if (new && !old) + { + if (p->flags & PFLAG_ILIMIT_BLOCK) + { + rte_trace_in(D_FILTERS, p, table, new, "ignored [inbound limit in action]"); + return; + } + + if (l && (stats->imp_routes + 1 > l->limit) && (proto_notify_limit(p, a, l, table) == 1)) + { + rte_trace_in(D_FILTERS, p, table, new, "ignored [inbound limit hit]"); + return; + } + } + if (new) stats->imp_updates_accepted++; else @@ -490,7 +556,7 @@ rte_recalculate(rtable *table, struct announce_hook *a, net *net, struct proto * /* The first case - the new route is cleary optimal, we link it at the first position and announce it */ - rte_trace_in(D_ROUTES, p, new, "added [best]"); + rte_trace_in(D_ROUTES, p, table, new, "added [best]"); rte_announce(table, RA_OPTIMAL, net, new, old_best, tmpa); new->next = net->routes; net->routes = new; @@ -506,7 +572,7 @@ rte_recalculate(rtable *table, struct announce_hook *a, net *net, struct proto * /* Add the new route to the list */ if (new) { - rte_trace_in(D_ROUTES, p, new, "added"); + rte_trace_in(D_ROUTES, p, table, new, "added"); new->next = net->routes; net->routes = new; } @@ -550,18 +616,18 @@ rte_recalculate(rtable *table, struct announce_hook *a, net *net, struct proto * 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, table, new, "added"); } /* Log the route removal */ if (!new && old && (p->debug & D_ROUTES)) { if (old != old_best) - rte_trace_in(D_ROUTES, p, old, "removed"); + rte_trace_in(D_ROUTES, p, table, old, "removed"); else if (net->routes) - rte_trace_in(D_ROUTES, p, old, "removed [replaced]"); + rte_trace_in(D_ROUTES, p, table, old, "removed [replaced]"); else - rte_trace_in(D_ROUTES, p, old, "removed [sole]"); + rte_trace_in(D_ROUTES, p, table, old, "removed [sole]"); } if (old) @@ -661,14 +727,14 @@ do_rte_update(rtable *table, struct announce_hook *a, net *net, struct proto *p, stats->imp_updates_received++; if (!rte_validate(new)) { - rte_trace_in(D_FILTERS, p, new, "invalid"); + rte_trace_in(D_FILTERS, p, table, new, "invalid"); stats->imp_updates_invalid++; goto drop; } if (filter == FILTER_REJECT) { stats->imp_updates_filtered++; - rte_trace_in(D_FILTERS, p, new, "filtered out"); + rte_trace_in(D_FILTERS, p, table, new, "filtered out"); goto drop; } if (src->make_tmp_attrs) @@ -680,7 +746,7 @@ do_rte_update(rtable *table, struct announce_hook *a, net *net, struct proto *p, if (fr > F_ACCEPT) { stats->imp_updates_filtered++; - rte_trace_in(D_FILTERS, p, new, "filtered out"); + rte_trace_in(D_FILTERS, p, table, new, "filtered out"); goto drop; } if (tmpa != old_tmpa && src->store_tmp_attrs) @@ -1012,7 +1078,7 @@ rt_next_hop_update_net(rtable *tab, net *n) *k = new; rte_announce_i(tab, RA_ANY, n, new, e); - rte_trace_in(D_ROUTES, new->sender, new, "updated"); + rte_trace_in(D_ROUTES, new->sender, tab, new, "updated"); if (e != old_best) rte_free_quick(e); @@ -1040,7 +1106,7 @@ rt_next_hop_update_net(rtable *tab, net *n) if (new != old_best) { rte_announce_i(tab, RA_OPTIMAL, n, new, old_best); - rte_trace_in(D_ROUTES, new->sender, new, "updated [best]"); + rte_trace_in(D_ROUTES, new->sender, tab, new, "updated [best]"); } if (free_old_best) @@ -1194,16 +1260,19 @@ rt_commit(struct config *new, struct config *old) DBG("\tdone\n"); } -static inline void +static inline int do_feed_baby(struct proto *p, int type, struct announce_hook *h, net *n, rte *e) { struct proto *q = e->attrs->proto; + int res; 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); + res = do_rte_announce(h, type, n, e, p->refeeding ? e : NULL, tmpa, p->refeeding); rte_update_unlock(); + + return res; } /** @@ -1250,8 +1319,9 @@ again: { if (p->core_state != FS_FEEDING) return 1; /* In the meantime, the protocol fell down. */ - do_feed_baby(p, RA_OPTIMAL, h, n, e); max_feed--; + if (do_feed_baby(p, RA_OPTIMAL, h, n, e) == 0) + break; } if (p->accept_ra_types == RA_ANY) diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index 675342d..88e9667 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -542,19 +542,33 @@ bgp_active(struct bgp_proto *p) bgp_start_timer(conn->connect_retry_timer, delay); } -int -bgp_apply_limits(struct bgp_proto *p) +static int +bgp_limit_notify(struct proto *P, struct announce_hook *a, struct proto_limit *l, struct rtable *table) { - if (p->cf->route_limit && (p->p.stats.imp_routes > p->cf->route_limit)) + struct bgp_proto *p = (struct bgp_proto *) P; + int subcode; + if ((l->direction != PL_IMPORT) && (l->action != PL_ACTION_DISABLE)) + return 0; + + switch (l->action) { - log(L_WARN "%s: Route limit exceeded, shutting down", p->p.name); - bgp_store_error(p, NULL, BE_AUTO_DOWN, BEA_ROUTE_LIMIT_EXCEEDED); - bgp_update_startup_delay(p); - bgp_stop(p, 1); // Errcode 6, 1 - max number of prefixes reached - return -1; + case PL_ACTION_RESTART: + case PL_ACTION_DISABLE: + if (l->action == PL_ACTION_DISABLE) + P->disabled = 1; + log(L_WARN "%s: Route limit exceeded, shutting down", P->name); + bgp_store_error(p, NULL, BE_AUTO_DOWN, BEA_ROUTE_LIMIT_EXCEEDED); + bgp_update_startup_delay(p); + /* + * Send 6,2 (Administrative Shutdown) for export limit + * Send 6,1 (Maximum Number of Prefixes Reached) overwise + */ + subcode = (P->disabled && (l->direction == PL_EXPORT)) ? 2 : 1; + bgp_stop(p, subcode); + return 1; + default: + return 0; } - - return 0; } @@ -866,7 +880,7 @@ bgp_shutdown(struct proto *P) BGP_TRACE(D_EVENTS, "Shutdown requested"); bgp_store_error(p, NULL, BE_MAN_DOWN, 0); - if (P->reconfiguring) + if (PROTO_IS_RECONFIGURING(P)) { if (P->cf_new) subcode = 6; // Errcode 6, 6 - other configuration change @@ -907,6 +921,7 @@ bgp_init(struct proto_config *C) P->rte_better = bgp_rte_better; P->import_control = bgp_import_control; P->neigh_notify = bgp_neigh_notify; + P->limit_notify = bgp_limit_notify; P->reload_routes = bgp_reload_routes; p->cf = c; p->local_as = c->local_as; diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index 437ba33..f271ef3 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -150,7 +150,6 @@ void bgp_conn_enter_established_state(struct bgp_conn *conn); void bgp_conn_enter_close_state(struct bgp_conn *conn); void bgp_conn_enter_idle_state(struct bgp_conn *conn); void bgp_store_error(struct bgp_proto *p, struct bgp_conn *c, u8 class, u32 code); -int bgp_apply_limits(struct bgp_proto *p); void bgp_stop(struct bgp_proto *p, unsigned subcode); diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index 03c233d..de3b254 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -94,7 +94,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 ';' { + if (!this_proto->in_limit) + this_proto->in_limit = cfg_allocz(sizeof(struct proto_limit)); + struct proto_limit *l = this_proto->in_limit; + l->direction = PL_IMPORT; + l->limit = $4; + l->action = PL_ACTION_RESTART; + } | bgp_proto PASSIVE bool ';' { BGP_CFG->passive = $3; } | bgp_proto INTERPRET COMMUNITIES bool ';' { BGP_CFG->interpret_communities = $4; } | bgp_proto IGP TABLE rtable ';' { BGP_CFG->igp_table = $4; } diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c index c3a8673..8eb6483 100644 --- a/proto/bgp/packets.c +++ b/proto/bgp/packets.c @@ -883,9 +883,6 @@ bgp_do_rx_update(struct bgp_conn *conn, if (n = net_find(p->p.table, prefix, pxlen)) rte_update(p->p.table, n, &p->p, &p->p, NULL); } - - if (bgp_apply_limits(p) < 0) - goto done; } done: diff --git a/proto/pipe/pipe.c b/proto/pipe/pipe.c index 2f1b519..6b13a53 100644 --- a/proto/pipe/pipe.c +++ b/proto/pipe/pipe.c @@ -130,7 +130,9 @@ pipe_reload_routes(struct proto *P) * together, both directions are reloaded during refeed and 'reload * out' command works like 'reload' command. For symmetry, we also * request refeed when 'reload in' command is used. + * We have to remove import limit here, too */ + proto_clear_limits(P, PFLAG_ILIMIT|PFLAG_ILIMIT_BLOCK|PFLAG_ELIMIT|PFLAG_ELIMIT_BLOCK); proto_request_feeding(P); return 1; } @@ -246,6 +248,15 @@ pipe_reconfigure(struct proto *P, struct proto_config *new) a->in_filter = FILTER_ACCEPT; pa->in_filter = FILTER_ACCEPT; + /* + * Update limits + * Note that import/export limit can have different functionality + * (for example, RESTART action can't be used in export filter) + * so this trick may impose some usage limitation. + */ + pa->out_limit = a->in_limit; + a->in_limit = NULL; + return 1; } diff --git a/proto/pipe/pipe.h b/proto/pipe/pipe.h index 6eb5b03..6f2bad5 100644 --- a/proto/pipe/pipe.h +++ b/proto/pipe/pipe.h @@ -32,6 +32,9 @@ extern struct protocol proto_pipe; static inline int proto_is_pipe(struct proto *p) { return p->proto == &proto_pipe; } +static inline int config_is_pipe(struct proto_config *c) +{ return c->protocol == &proto_pipe; } + void pipe_show_stats(struct proto *P); #endif -- 1.7.3.2 --------------070507000102070502090107--
participants (1)
-
Alexander V. Chernikov