[PATCH 3/3] * Implement general protocol limits, v3
Alexander V. Chernikov
melifaro at ipfw.ru
Sun Nov 27 08:12:09 CET 2011
---
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--
More information about the Bird-users
mailing list