[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