diff options
-rw-r--r-- | doc/bird.sgml | 20 | ||||
-rw-r--r-- | nest/config.Y | 14 | ||||
-rw-r--r-- | nest/proto.c | 71 | ||||
-rw-r--r-- | nest/protocol.h | 19 | ||||
-rw-r--r-- | nest/rt-table.c | 35 | ||||
-rw-r--r-- | proto/bgp/bgp.c | 14 | ||||
-rw-r--r-- | proto/bgp/bgp.h | 1 | ||||
-rw-r--r-- | proto/pipe/config.Y | 1 | ||||
-rw-r--r-- | proto/pipe/pipe.c | 14 | ||||
-rw-r--r-- | proto/pipe/pipe.h | 1 | ||||
-rw-r--r-- | sysdep/linux/netlink/netlink.c | 14 |
11 files changed, 132 insertions, 72 deletions
diff --git a/doc/bird.sgml b/doc/bird.sgml index 6f96b862..df6e2610 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -431,13 +431,19 @@ 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 an import route limit and the action to be taken when - the limit is hit. Warn action just prints warning log - message. Block action ignores new routes (and converts route - updates to withdraws) coming from the protocol. Restart and - disable actions shut the protocol down like appropriate - commands. Default: <cf/none/. + <tag>import limit <m/number/ [exceed warn | block | restart | disable]</tag> + 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 + protocol. Restart and disable actions shut the protocol down + like appropriate commands. Disable is the default action if an + action is not explicitly specified. Default: <cf/none/. + + <tag>export limit <m/number/ [exceed warn | block | restart | disable]</tag> + Specify an export route limit, works similarly to + the <cf>import limit</cf> option, but for the routes exported + to the protocol. Default: <cf/none/. <tag>description "<m/text/"</tag> This is an optional description of the protocol. It is displayed as a part of the diff --git a/nest/config.Y b/nest/config.Y index 60b03278..c59319cb 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -179,6 +179,7 @@ proto_item: | IMPORT imexport { this_proto->in_filter = $2; } | EXPORT imexport { this_proto->out_filter = $2; } | IMPORT LIMIT limit_spec { this_proto->in_limit = $3; } + | EXPORT LIMIT limit_spec { this_proto->out_limit = $3; } | TABLE rtable { this_proto->table = $2; } | ROUTER ID idval { this_proto->router_id = $3; } | DESCRIPTION TEXT { this_proto->dsc = $2; } @@ -192,17 +193,18 @@ imexport: ; limit_action: - WARN { $$ = PLA_WARN; } - | BLOCK { $$ = PLA_BLOCK; } - | RESTART { $$ = PLA_RESTART; } - | DISABLE { $$ = PLA_DISABLE; } + /* default */ { $$ = PLA_DISABLE; } + | EXCEED WARN { $$ = PLA_WARN; } + | EXCEED BLOCK { $$ = PLA_BLOCK; } + | EXCEED RESTART { $$ = PLA_RESTART; } + | EXCEED DISABLE { $$ = PLA_DISABLE; } ; limit_spec: - expr EXCEED limit_action { + expr limit_action { struct proto_limit *l = cfg_allocz(sizeof(struct proto_limit)); l->limit = $1; - l->action = $3; + l->action = $2; $$ = l; } ; diff --git a/nest/proto.c b/nest/proto.c index 418a7a61..887d3e5e 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -406,13 +406,14 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config if (p->proto->multitable) return 1; - /* Update filters in the main announce hook */ + /* Update filters and limits in the main announce hook + Note that this also resets limit state */ if (p->main_ahook) { p->main_ahook->in_filter = nc->in_filter; p->main_ahook->out_filter = nc->out_filter; p->main_ahook->in_limit = nc->in_limit; - // p->main_ahook->out_limit = nc->out_limit; + p->main_ahook->out_limit = nc->out_limit; } /* Update routes when filters changed. If the protocol in not UP, @@ -774,9 +775,16 @@ proto_schedule_feed(struct proto *p, int initial) p->core_state = FS_FEEDING; p->refeeding = !initial; - /* Hack: reset exp_routes during refeed, and do not decrease it later */ + /* FIXME: This should be changed for better support of multitable protos */ if (!initial) - p->stats.exp_routes = 0; + { + struct announce_hook *ah; + for (ah = p->ahooks; ah; ah = ah->next) + proto_reset_limit(ah->out_limit); + + /* Hack: reset exp_routes during refeed, and do not decrease it later */ + p->stats.exp_routes = 0; + } /* Connect protocol to routing table */ if (initial && !p->proto->multitable) @@ -785,7 +793,9 @@ proto_schedule_feed(struct proto *p, int initial) p->main_ahook->in_filter = p->cf->in_filter; p->main_ahook->out_filter = p->cf->out_filter; p->main_ahook->in_limit = p->cf->in_limit; - // p->main_ahook->out_limit = p->cf->out_limit; + p->main_ahook->out_limit = p->cf->out_limit; + proto_reset_limit(p->main_ahook->in_limit); + proto_reset_limit(p->main_ahook->out_limit); } proto_relink(p); @@ -870,6 +880,8 @@ proto_schedule_flush(struct proto *p) proto_schedule_flush_loop(); } +/* Temporary hack to propagate restart to BGP */ +int proto_restart; static void proto_shutdown_loop(struct timer *t UNUSED) @@ -879,11 +891,11 @@ proto_shutdown_loop(struct timer *t UNUSED) WALK_LIST_DELSAFE(p, p_next, active_proto_list) if (p->down_sched) { - int restart = (p->down_sched == PDS_RESTART); + proto_restart = (p->down_sched == PDS_RESTART); p->disabled = 1; proto_rethink_goal(p); - if (restart) + if (proto_restart) { p->disabled = 0; proto_rethink_goal(p); @@ -953,43 +965,43 @@ 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 + * @rt_count: the number of routes * * The function is called by the route processing core when limit @l * is breached. It activates the limit and tooks appropriate action - * according to @l->action. It also says what should be done with the - * route that breached the limit. - * - * Returns 1 if the route should be freed, 0 otherwise. + * according to @l->action. */ -int -proto_notify_limit(struct announce_hook *ah, struct proto_limit *l) +void +proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, u32 rt_count) { struct proto *p = ah->proto; int dir = (ah->in_limit == l); - if (l->active) - return (l->action != PLA_WARN); + if (l->state == PLS_BLOCKED) + return; - l->active = 1; - log(L_WARN "Protocol %s hits route %s limit (%d), action: %s", - p->name, dir ? "import" : "export", l->limit, proto_limit_name(l)); + /* 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)); switch (l->action) { case PLA_WARN: - return 0; + l->state = PLS_ACTIVE; + break; case PLA_BLOCK: - return 1; + l->state = PLS_BLOCKED; + break; 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); - return 1; + break; } - - return 0; } /** @@ -1101,9 +1113,11 @@ proto_show_stats(struct proto_stats *s) void proto_show_limit(struct proto_limit *l, const char *dsc) { - if (l) - cli_msg(-1006, " %16s%d, action: %s%s", dsc, l->limit, - proto_limit_name(l), l->active ? " [HIT]" : ""); + if (!l) + return; + + cli_msg(-1006, " %-16s%d%s", dsc, l->limit, l->state ? " [HIT]" : ""); + cli_msg(-1006, " Action: %s", proto_limit_name(l)); } void @@ -1115,6 +1129,7 @@ proto_show_basic_info(struct proto *p) cli_msg(-1006, " Output filter: %s", filter_name(p->cf->out_filter)); proto_show_limit(p->cf->in_limit, "Import limit:"); + proto_show_limit(p->cf->out_limit, "Export limit:"); if (p->proto_state != PS_DOWN) proto_show_stats(&p->stats); @@ -1233,8 +1248,8 @@ proto_cmd_reload(struct proto *p, unsigned int dir, int cnt UNUSED) * Should be done before reload_routes() hook? * Perhaps, but these hooks work asynchronously. */ - if (!p->proto->multitable && p->main_ahook->in_limit) - p->main_ahook->in_limit->active = 0; + if (!p->proto->multitable) + proto_reset_limit(p->main_ahook->in_limit); } /* re-exporting routes */ diff --git a/nest/protocol.h b/nest/protocol.h index 3dd7cf2f..3f9ed96e 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -95,7 +95,7 @@ struct proto_config { 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 */ + struct proto_limit *out_limit; /* Limit for exporting routes to protocol */ /* Check proto_reconfigure() and proto_copy_config() after changing struct proto_config */ @@ -375,13 +375,24 @@ extern struct proto_config *cf_dev_proto; #define PLA_RESTART 4 /* Force protocol restart */ #define PLA_DISABLE 5 /* Shutdown and disable protocol */ +#define PLS_INITIAL 0 /* Initial limit state after protocol start */ +#define PLS_ACTIVE 1 /* Limit was hit */ +#define PLS_BLOCKED 2 /* Limit is active and blocking new routes */ + struct proto_limit { u32 limit; /* Maximum number of prefixes */ byte action; /* Action to take (PLA_*) */ - byte active; /* Limit is active */ + byte state; /* State of limit (PLS_*) */ }; -int proto_notify_limit(struct announce_hook *ah, struct proto_limit *l); +void proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, u32 rt_count); + +static inline void proto_reset_limit(struct proto_limit *l) +{ + if (l) + l->state = PLS_INITIAL; +} + /* * Route Announcement Hook @@ -394,7 +405,7 @@ struct announce_hook { 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_limit *out_limit; /* Output limit */ struct proto_stats *stats; /* Per-table protocol statistics */ struct announce_hook *next; /* Next hook for the same protocol */ }; diff --git a/nest/rt-table.c b/nest/rt-table.c index 6a28fd43..06121ea3 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -273,6 +273,23 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r if (!new && !old) return; + struct proto_limit *l = ah->out_limit; + if (l && new && (!old || refeed)) + { + if (stats->exp_routes >= l->limit) + proto_notify_limit(ah, l, stats->exp_routes); + + if (l->state == PLS_BLOCKED) + { + /* Exported route counter ignores whether the route was + blocked by limit, to be consistent when limits change */ + stats->exp_routes++; + stats->exp_updates_rejected++; + rte_trace_out(D_FILTERS, p, new, "rejected [limit]"); + goto done; + } + } + if (new) stats->exp_updates_accepted++; else @@ -307,6 +324,8 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r } else p->rt_notify(p, ah->table, net, new, old, new->attrs->eattrs); + + done: if (new && new != new0) /* Discard temporary rte's */ rte_free(new); if (old && old != old0) @@ -485,12 +504,18 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str } struct proto_limit *l = ah->in_limit; - if (l && !old && new && (stats->imp_routes >= l->limit) && proto_notify_limit(ah, l)) + if (l && !old && new) { - stats->imp_updates_ignored++; - rte_trace_in(D_FILTERS, p, new, "ignored [limit]"); - rte_free_quick(new); - return; + if (stats->imp_routes >= l->limit) + proto_notify_limit(ah, l, stats->imp_routes); + + if (l->state == PLS_BLOCKED) + { + stats->imp_updates_ignored++; + rte_trace_in(D_FILTERS, p, new, "ignored [limit]"); + rte_free_quick(new); + return; + } } if (new) diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index cf743dff..3b9f7cc5 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -848,6 +848,8 @@ bgp_start(struct proto *P) return PS_START; } +extern int proto_restart; + static int bgp_shutdown(struct proto *P) { @@ -877,10 +879,16 @@ bgp_shutdown(struct proto *P) case PDC_IN_LIMIT_HIT: subcode = 1; // Errcode 6, 1 - max number of prefixes reached + /* log message for compatibility */ log(L_WARN "%s: Route limit exceeded, shutting down", p->p.name); + goto limit; + + case PDC_OUT_LIMIT_HIT: + subcode = proto_restart ? 4 : 2; // Administrative reset or shutdown + limit: bgp_store_error(p, NULL, BE_AUTO_DOWN, BEA_ROUTE_LIMIT_EXCEEDED); - if (P->cf->in_limit->action == PLA_RESTART) + if (proto_restart) bgp_update_startup_delay(p); else p->startup_delay = 0; @@ -1165,9 +1173,9 @@ bgp_show_proto_info(struct proto *P) p->rs_client ? " route-server" : "", p->as4_session ? " AS4" : ""); cli_msg(-1006, " Source address: %I", p->source_addr); - if (p->cf->route_limit) + if (P->cf->in_limit) cli_msg(-1006, " Route limit: %d/%d", - p->p.stats.imp_routes, p->cf->route_limit); + p->p.stats.imp_routes, P->cf->in_limit->limit); cli_msg(-1006, " Hold timer: %d/%d", tm_remains(c->hold_timer), c->hold_time); cli_msg(-1006, " Keepalive timer: %d/%d", diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index aa2db4b0..1c16f485 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -40,7 +40,6 @@ 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 */ unsigned connect_retry_time; diff --git a/proto/pipe/config.Y b/proto/pipe/config.Y index 4fb2b499..40637558 100644 --- a/proto/pipe/config.Y +++ b/proto/pipe/config.Y @@ -36,7 +36,6 @@ pipe_proto: cf_error("Routing table name expected"); PIPE_CFG->peer = $4->def; } - | pipe_proto EXPORT LIMIT limit_spec ';' { PIPE_CFG->out_limit = $4; } | pipe_proto MODE OPAQUE ';' { PIPE_CFG->mode = PIPE_OPAQUE; } | pipe_proto MODE TRANSPARENT ';' { PIPE_CFG->mode = PIPE_TRANSPARENT; } ; diff --git a/proto/pipe/pipe.c b/proto/pipe/pipe.c index a5fcc6f6..6099d284 100644 --- a/proto/pipe/pipe.c +++ b/proto/pipe/pipe.c @@ -127,10 +127,8 @@ pipe_reload_routes(struct proto *P) */ proto_request_feeding(P); - if (P->main_ahook->in_limit) - P->main_ahook->in_limit->active = 0; - if (p->peer_ahook->in_limit) - p->peer_ahook->in_limit->active = 0; + proto_reset_limit(P->main_ahook->in_limit); + proto_reset_limit(p->peer_ahook->in_limit); return 1; } @@ -168,10 +166,12 @@ pipe_start(struct proto *P) P->main_ahook = proto_add_announce_hook(P, P->table, &P->stats); P->main_ahook->out_filter = cf->c.out_filter; P->main_ahook->in_limit = cf->c.in_limit; + proto_reset_limit(P->main_ahook->in_limit); p->peer_ahook = proto_add_announce_hook(P, p->peer_table, &p->peer_stats); p->peer_ahook->out_filter = cf->c.in_filter; - p->peer_ahook->in_limit = cf->out_limit; + p->peer_ahook->in_limit = cf->c.out_limit; + proto_reset_limit(p->peer_ahook->in_limit); return PS_UP; } @@ -225,7 +225,7 @@ pipe_reconfigure(struct proto *P, struct proto_config *new) if (p->peer_ahook) { p->peer_ahook->out_filter = new->in_filter; - p->peer_ahook->in_limit = nc->out_limit; + p->peer_ahook->in_limit = new->out_limit; } if ((P->proto_state != PS_UP) || (proto_reconfig_type == RECONFIG_SOFT)) @@ -311,7 +311,7 @@ pipe_show_proto_info(struct proto *P) cli_msg(-1006, " Output filter: %s", filter_name(cf->c.out_filter)); proto_show_limit(cf->c.in_limit, "Import limit:"); - proto_show_limit(cf->out_limit, "Export limit:"); + proto_show_limit(cf->c.out_limit, "Export limit:"); if (P->proto_state != PS_DOWN) pipe_show_stats(p); diff --git a/proto/pipe/pipe.h b/proto/pipe/pipe.h index e777fb41..50b31698 100644 --- a/proto/pipe/pipe.h +++ b/proto/pipe/pipe.h @@ -15,7 +15,6 @@ struct pipe_config { struct proto_config c; struct rtable_config *peer; /* Table we're connected to */ - struct proto_limit *out_limit; /* Export route limit */ int mode; /* PIPE_OPAQUE or PIPE_TRANSPARENT */ }; diff --git a/sysdep/linux/netlink/netlink.c b/sysdep/linux/netlink/netlink.c index d9d22d4c..182088a1 100644 --- a/sysdep/linux/netlink/netlink.c +++ b/sysdep/linux/netlink/netlink.c @@ -588,9 +588,9 @@ krt_capable(rte *e) switch (a->dest) { case RTD_ROUTER: - if (ipa_has_link_scope(a->gw) && (a->iface == NULL)) - return 0; case RTD_DEVICE: + if (a->iface == NULL) + return 0; case RTD_BLACKHOLE: case RTD_UNREACHABLE: case RTD_PROHIBIT: @@ -653,20 +653,16 @@ nl_send_route(struct krt_proto *p, rte *e, struct ea_list *eattrs, int new) if (ea = ea_find(eattrs, EA_KRT_REALM)) nl_add_attr_u32(&r.h, sizeof(r), RTA_FLOW, ea->u.data); + /* a->iface != NULL checked in krt_capable() for router and device routes */ + switch (a->dest) { case RTD_ROUTER: r.r.rtm_type = RTN_UNICAST; + nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index); nl_add_attr_ipa(&r.h, sizeof(r), RTA_GATEWAY, a->gw); - - /* a->iface != NULL checked in krt_capable() */ - if (ipa_has_link_scope(a->gw)) - nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index); - break; case RTD_DEVICE: - if (!a->iface) - return -1; r.r.rtm_type = RTN_UNICAST; nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index); break; |