Hello list!

At the moment bird has route limits implemented for BGP only (route
limit XXX).

This seems to be not enough in various BGP+IGP configurations.

Vendors implement the following approaches:

Cisco:
* warning-only (OSPF, redistribute maximum-prefix) - print warning message
* withdraw (OSPF, redistribute maximum-prefix) - Prevents additional
redistribution when the number of routes defined by the maximum argument
have been redistributed.
* clear (IS-IS) - clears all redistributed prefixes

See
http://www.cisco.com/en/US/docs/ios/12_0s/feature/guide/fsoredis.htmlfor
more information

Juniper:
* log-only (general, maximum-prefixes) - print warning message only
* withdraw (general, maximum-prefixes) - any additional routes are rejected
* clear (OSPF, ISIS, prefix-export-limit) - clear database from all
extrnal routes


This patch introduces general limiting functionality for any protocol.

Import/export limits can be configured with the following actions:
* warn (prints warning message)
* block (blocks new import/exports from/to the protocol)
* shutdown (restart the protocol, import only)
* disable (shutdown and disable protocol)

If any protocol limit is hit and block action is taken, protocol can be
returned to 'normal' state by using reload [in|out] protocol (or
restaring it).

From f9c8c639593aa98723397a640f8d899b85c39fb7 Mon Sep 17 00:00:00 2001
From: Alexander V. Chernikov <[email protected]>
Date: Mon, 14 Nov 2011 15:33:27 +0000
Subject: [PATCH 1/1] * Implement  general protocol limiting

---
 doc/bird.sgml       |   16 +++++-
 nest/config.Y       |   32 +++++++++
 nest/proto.c        |  175 +++++++++++++++++++++++++++++++++++++++++++++++++--
 nest/protocol.h     |   42 ++++++++++++-
 nest/rt-table.c     |   49 ++++++++++++++-
 proto/bgp/bgp.c     |   28 +++++---
 proto/bgp/bgp.h     |    1 -
 proto/bgp/config.Y  |    9 +++-
 proto/bgp/packets.c |    3 -
 9 files changed, 329 insertions(+), 26 deletions(-)

diff --git a/doc/bird.sgml b/doc/bird.sgml
index 7f53f02..4060f05 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -415,6 +415,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 | shutdown | 
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 (not route updates) coming from protocol. Shutdown 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/shutdown/.
+       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 +1284,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 shutdown/. 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..d02b1f7 100644
--- a/nest/config.Y
+++ b/nest/config.Y
@@ -21,6 +21,7 @@ static struct iface_patt *this_ipatt;
 static struct iface_patt_node *this_ipn;
 static list *this_p_list;
 static struct password_item *this_p_item;
+static struct proto_limit *this_limit;
 static int password_id;
 
 static inline void
@@ -43,6 +44,7 @@ CF_DECLS
 
 CF_KEYWORDS(ROUTER, ID, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, 
OFF, DIRECT)
 CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, TABLE, STATES, ROUTES, 
FILTERS)
+CF_KEYWORDS(EXCEED, LIMIT, WARN, BLOCK, SHUTDOWN, 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)
@@ -153,6 +155,25 @@ 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 {
+    if ((!strcmp(this_proto->protocol->name, "Device")) ||
+      (!strcmp(this_proto->protocol->name, "Static")))
+       cf_error("%s protocol does not support import limits", 
this_proto->protocol->name);
+    if (!this_proto->in_limit)
+      this_proto->in_limit = cfg_allocz(sizeof(struct proto_limit));
+    this_limit = this_proto->in_limit;
+    this_limit->direction = PL_IMPORT;
+    this_limit->limit = $3;
+  }
+   EXCEED limit_action
+ | EXPORT LIMIT expr {
+    if (!this_proto->out_limit)
+      this_proto->out_limit = cfg_allocz(sizeof(struct proto_limit));
+    this_limit = this_proto->out_limit;
+    this_limit->direction = PL_EXPORT;
+    this_limit->limit = $3;
+  }
+   EXCEED limit_action
  | TABLE rtable { this_proto->table = $2; }
  | ROUTER ID idval { this_proto->router_id = $3; }
  | DESCRIPTION TEXT { this_proto->dsc = $2; }
@@ -165,6 +186,17 @@ imexport:
  | NONE { $$ = FILTER_REJECT; }
  ;
 
+limit_action:
+   WARN { this_limit->action = PL_ACTION_WARN; }
+ | BLOCK { this_limit->action = PL_ACTION_BLOCK; }
+ | SHUTDOWN {
+    if (this_limit->direction == PL_EXPORT)
+      cf_error("SHUTDOWN action can't be used in export limiter. Use DISABLE 
action instead");
+    this_limit->action = PL_ACTION_SHUTDOWN;
+  }
+ | DISABLE { this_limit->action = PL_ACTION_DISABLE; }
+ ;
+
 rtable:
    SYM {
      if ($1->class != SYM_TABLE) cf_error("Table name expected");
diff --git a/nest/proto.c b/nest/proto.c
index d55c348..f022dc2 100644
--- a/nest/proto.c
+++ b/nest/proto.c
@@ -33,6 +33,11 @@ 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 list abuse_proto_list;
+
 static event *proto_flush_event;
 
 static char *p_states[] = { "DOWN", "START", "UP", "STOP" };
@@ -41,6 +46,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)
@@ -114,6 +121,8 @@ proto_new(struct proto_config *c, unsigned size)
   p->table = c->table->table;
   p->in_filter = c->in_filter;
   p->out_filter = c->out_filter;
+  p->in_limit = c->in_limit;
+  p->out_limit = c->out_limit;
   p->hash_key = random_u32();
   c->proto = p;
   return p;
@@ -363,6 +372,8 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, 
struct proto_config
   p->name = nc->name;
   p->in_filter = nc->in_filter;
   p->out_filter = nc->out_filter;
+  p->in_limit = nc->in_limit;
+  p->out_limit = nc->out_limit;
   p->preference = nc->preference;
 
   if (import_changed || export_changed)
@@ -457,7 +468,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);
        }
@@ -489,8 +500,9 @@ static void
 proto_rethink_goal(struct proto *p)
 {
   struct protocol *q;
+  struct proto *ap, *ap_next;
 
-  if (p->reconfiguring && p->core_state == FS_HUNGRY && p->proto_state == 
PS_DOWN)
+  if ((p->flags & PFLAG_RECONFIGURING) && 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);
@@ -504,7 +516,7 @@ proto_rethink_goal(struct proto *p)
     }
 
   /* Determine what state we want to reach */
-  if (p->disabled || p->reconfiguring)
+  if (p->disabled || (p->flags & PFLAG_RECONFIGURING))
     {
       p->core_goal = FS_HUNGRY;
       if (p->core_state == FS_HUNGRY && p->proto_state == PS_DOWN)
@@ -525,6 +537,8 @@ proto_rethink_goal(struct proto *p)
          DBG("Kicking %s up\n", p->name);
          PD(p, "Starting");
          proto_init_instance(p);
+         /* Zeroing limit flags */
+         p->flags &= ~PLIMIT_FLAGS;
          proto_notify_state(p, (q->start ? q->start(p) : PS_UP));
        }
     }
@@ -534,6 +548,14 @@ proto_rethink_goal(struct proto *p)
        {
          DBG("Kicking %s down\n", p->name);
          PD(p, "Shutting down");
+         /* Remove from pre-shutdown list if exists */
+         WALK_LIST_DELSAFE(ap, ap_next, abuse_proto_list)
+         {
+           if ((ap = SKIP_BACK(struct proto, shutdown_node, ap)) != p)
+             continue;
+           rem_node(&ap->shutdown_node);
+           break;
+         }
          proto_notify_state(p, (q->shutdown ? q->shutdown(p) : PS_DOWN));
        }
     }
@@ -613,6 +635,7 @@ protos_build(void)
   init_list(&inactive_proto_list);
   init_list(&initial_proto_list);
   init_list(&flush_proto_list);
+  init_list(&abuse_proto_list);
   proto_build(&proto_device);
 #ifdef CONFIG_RADV
   proto_build(&proto_radv);
@@ -635,6 +658,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
@@ -715,6 +740,9 @@ proto_schedule_feed(struct proto *p, int initial)
   if (!initial)
     p->stats.exp_routes = 0;
 
+  /* Remove export limit flags from protocol */
+  p->flags &= ~(PFLAG_ELIMIT|PFLAG_ELIMIT_BLOCK);
+
   proto_relink(p);
   p->attn->hook = initial ? proto_feed_initial : proto_feed_more;
   ev_schedule(p->attn);
@@ -746,9 +774,92 @@ proto_request_feeding(struct proto *p)
       rt_feed_baby_abort(p);
     }
 
+  /*
+   * 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.
+   */
+  p->flags &= ~(PFLAG_ELIMIT|PFLAG_ELIMIT_BLOCK);
   proto_schedule_feed(p, 0);
 }
 
+static void
+proto_shutdown_abusers(void *unused UNUSED)
+{
+  struct proto *p, *p_next;
+  int disable;
+
+  WALK_LIST_DELSAFE(p, p_next, abuse_proto_list)
+    {
+      p = SKIP_BACK(struct proto, shutdown_node, p);
+
+      rem_node(&p->shutdown_node);
+      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
+ * @l: limit being hit
+ *
+ * If limit hook exists it is called and returned value is examined.
+ * If PL_HANDLED is returned, processing stops. Overwise action is taken
+ * depending on l->action configured in limit instance. PL_HANDLED 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 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 (p->limit_notify && (p->limit_notify(p, l, table) == PL_HANDLED))
+    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_SHUTDOWN:
+    case PL_ACTION_DISABLE:
+      /* Schedule instance shutdown */
+      add_tail(&abuse_proto_list, &p->shutdown_node);
+      if (l->action == PL_ACTION_DISABLE)
+       p->flags |= PFLAG_DISABLE;
+      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
@@ -853,6 +964,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_SHUTDOWN:   return "SHUTDOWN";
+    case PL_ACTION_DISABLE:    return "DISABLE";
+    }
+  return "unknown";
+}
+
 static void
 proto_do_show_stats(struct proto *p)
 {
@@ -919,7 +1043,8 @@ proto_do_show_pipe_stats(struct proto *p)
 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;
 
   /* First protocol - show header */
   if (!cnt)
@@ -928,12 +1053,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)
@@ -945,6 +1074,20 @@ proto_cmd_show(struct proto *p, unsigned int verbose, int 
cnt)
       cli_msg(-1006, "  Preference:     %d", p->preference);
       cli_msg(-1006, "  Input filter:   %s", filter_name(p->in_filter));
       cli_msg(-1006, "  Output filter:  %s", filter_name(p->out_filter));
+      if (p->in_limit)
+      {
+       l = p->in_limit;
+       cli_msg(-1006, "  Import limit:   %4d, action: %7s%s", l->limit,
+         proto_limit_name(l), (p->flags & PFLAG_ILIMIT) ? " [ LIMIT HIT ]" : 
"");
+      } else if (p->flags & PFLAG_ILIMIT)
+       cli_msg(-1006, "  Old Import limit:   [HIT][reload/restart required]");
+      if (p->out_limit)
+      {
+       l = p->out_limit;
+       cli_msg(-1006, "  Export limit:   %4d, action: %7s%s", l->limit,
+         proto_limit_name(l), (p->flags & PFLAG_ELIMIT) ? " [ LIMIT HIT ]" : 
"");
+      } else if (p->flags & PFLAG_ELIMIT)
+       cli_msg(-1006, "  Old export limit:   [HIT][reload/restart required]");
 
       if (p->proto_state != PS_DOWN)
        {
@@ -1013,6 +1156,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);
@@ -1027,13 +1172,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 a7518c2..f9e6d92 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -68,6 +68,29 @@ 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_HANDLED             1       /* Limit action is handled by the 
protocol hook */
+#define PL_DEFAULT             2       /* Core has to execute default action */
+
+#define PL_ACTION_WARN         1       /* Issue log warning */
+#define PL_ACTION_BLOCK                2       /* Block new routes */
+#define PL_ACTION_CLEAR                3       /* Clear exported routes */
+#define PL_ACTION_SHUTDOWN     4       /* Force protocol shutdown */
+#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 proto_limit *l, struct rtable 
*table);
+
+/*
  *     Known protocols
  */
 
@@ -92,12 +115,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 */
@@ -126,6 +152,7 @@ struct proto_stats {
 struct proto {
   node n;                              /* Node in *_proto_list */
   node glob_node;                      /* Node in global proto_list */
+  node shutdown_node;                  /* Node in limiter shutdown list */
   struct protocol *proto;              /* Protocol */
   struct proto_config *cf;             /* Configuration data */
   struct proto_config *cf_new;         /* Configuration we want to switch to 
after shutdown (NULL=delete) */
@@ -141,7 +168,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 +181,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 +197,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 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);
@@ -192,6 +221,8 @@ struct proto {
   struct rtable *table;                        /* Our primary routing table */
   struct filter *in_filter;            /* Input filter */
   struct filter *out_filter;           /* Output filter */
+  struct proto_limit *in_limit;                /* Limit for importing routes 
from protocol */
+  struct proto_limit *out_limit;       /* Limit for exporting routes to 
protocol */
   struct announce_hook *ahooks;                /* Announcement hooks for this 
protocol */
 
   struct fib_iterator *feed_iterator;  /* Routing table iterator used during 
protocol feeding */
@@ -200,6 +231,15 @@ 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_DISABLE          0x20    /* Protocol needs disabling */
+
+#define PLIMIT_FLAGS           
(PFLAG_ILIMIT|PFLAG_ELIMIT|PFLAG_ILIMIT_BLOCK|PFLAG_ELIMIT_BLOCK|PFLAG_DISABLE)
+
 struct proto_spec {
   void *ptr;
   int patt;
diff --git a/nest/rt-table.c b/nest/rt-table.c
index e20d2f6..83c0345 100644
--- a/nest/rt-table.c
+++ b/nest/rt-table.c
@@ -188,20 +188,26 @@ do_rte_announce(struct announce_hook *a, int type UNUSED, 
net *net, rte *new, rt
 {
   struct proto *p = a->proto;
   struct filter *filter = p->out_filter;
+  struct proto_limit *l = p->out_limit;
   struct proto_stats *stats = &p->stats;
   rte *new0 = new;
   rte *old0 = old;
-  int ok;
+  int ok, wrong_table = 0;
 
 #ifdef CONFIG_PIPE
   /* The secondary direction of the pipe */
   if (proto_is_pipe(p) && (p->table != a->table))
     {
       filter = p->in_filter;
+      l = p->in_limit;
       stats = pipe_get_peer_stats(p);
     }
 #endif
 
+  /* Check if we're called on non-default protocol table */
+  if ((!proto_is_pipe(p)) && (p->table != a->table))
+    wrong_table = 1;
+
   if (new)
     {
       stats->exp_updates_received++;
@@ -272,6 +278,22 @@ do_rte_announce(struct announce_hook *a, int type UNUSED, 
net *net, rte *new, rt
   if (!new && !old)
     return;
 
+  /* Check if we can export new route / exceed export limit */
+  if (new && l && !old)
+  {
+    if (p->flags & PFLAG_ELIMIT_BLOCK)
+      return;
+
+    if ((l = p->out_limit) && (p->stats.exp_routes + 1 > l->limit) && 
(proto_notify_limit(p, l, a->table) == 1))
+    {
+      /* free allocated data and return */
+      if (new != new0)
+        rte_free(new);
+
+      return;
+    }
+  }
+
   if (new)
     stats->exp_updates_accepted++;
   else
@@ -426,6 +448,7 @@ static void
 rte_recalculate(rtable *table, net *net, struct proto *p, struct proto *src, 
rte *new, ea_list *tmpa)
 {
   struct proto_stats *stats = &p->stats;
+  struct proto_limit *l;
   rte *old_best = net->routes;
   rte *old = NULL;
   rte **k, *r, *s;
@@ -486,6 +509,16 @@ rte_recalculate(rtable *table, net *net, struct proto *p, 
struct proto *src, rte
       return;
     }
 
+  /* Check limit for imported routes */
+  if (new && !old)
+  {
+    if (p->flags & PFLAG_ILIMIT_BLOCK)
+      return;
+
+    if ((l = p->in_limit) && (p->stats.imp_routes + 1 > l->limit) && 
(proto_notify_limit(p, l, table) == 1))
+      return;
+  }
+
   if (new)
     stats->imp_updates_accepted++;
   else
@@ -1233,7 +1266,11 @@ rt_feed_baby(struct proto *p)
 {
   struct announce_hook *h;
   struct fib_iterator *fit;
-  int max_feed = 256;
+  struct proto_limit *l = p->out_limit;
+  int max_feed = 256, need_check, do_check;
+
+  /* Do we need to filter route updates? */
+  need_check = (!(p->flags & PFLAG_ELIMIT_BLOCK) && l) ? 1 : 0;
 
   if (!p->feed_ahook)                  /* Need to initialize first */
     {
@@ -1248,6 +1285,8 @@ rt_feed_baby(struct proto *p)
 
 again:
   h = p->feed_ahook;
+  /* Do limit check for base protocol table only */
+  do_check = ((p->table == h->table) && need_check)? 1 : 0;
   FIB_ITERATE_START(&h->table->fib, fit, fn)
     {
       net *n = (net *) fn;
@@ -1263,6 +1302,12 @@ again:
          {
            if (p->core_state != FS_FEEDING)
              return 1;  /* In the meantime, the protocol fell down. */
+           if (do_check && (p->stats.exp_routes + 1 > l->limit))
+             if (proto_notify_limit(p, l, h->table))
+             {
+               /* limit action forbids new exports, end feed */
+               break;
+             }
            do_feed_baby(p, RA_OPTIMAL, h, n, e);
            max_feed--;
          }
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index 675342d..483a2d0 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -542,19 +542,27 @@ 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 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;
+  if (l->direction != PL_IMPORT)
+    return PL_DEFAULT;
+
+  switch (l->action)
     {
-      log(L_WARN "%s: Route limit exceeded, shutting down", p->p.name);
+    case PL_ACTION_SHUTDOWN:
+    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);
       bgp_stop(p, 1); // Errcode 6, 1 - max number of prefixes reached
-      return -1;
+      return PL_HANDLED;
+    default:
+      return PL_DEFAULT;
     }
-
-  return 0;
 }
 
 
@@ -866,7 +874,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 (P->flags & PFLAG_RECONFIGURING)
     {
       if (P->cf_new)
        subcode = 6; // Errcode 6, 6 - other configuration change
@@ -907,6 +915,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;
@@ -1141,9 +1150,6 @@ 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)
-       cli_msg(-1006, "    Route limit:      %d/%d",
-               p->p.stats.imp_routes, p->cf->route_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 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..3267ffd 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_SHUTDOWN;
+   }
  | 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:
-- 
1.7.3.2

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to