Commit 3a8197a9 authored by Maria Matejka's avatar Maria Matejka
Browse files

Limit containment

parent 46739f00
Loading
Loading
Loading
Loading

nest/limit.h

0 → 100644
+49 −0
Original line number Diff line number Diff line
/*
 *	BIRD Internet Routing Daemon -- Limits
 *
 *	(c) 1998--2000 Martin Mares <mj@ucw.cz>
 *	(c) 2021 Maria Matejka <mq@jmq.cz>
 *
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

#ifndef _BIRD_LIMIT_H_
#define _BIRD_LIMIT_H_

struct limit {
  u32 max;
  u32 count;
  int (*action)(struct limit *, void *data);
};

static inline int limit_do_action(struct limit *l, void *data)
{
  return l->action ? l->action(l, data) : 1;
}

static inline int limit_push(struct limit *l, void *data)
{
  if ((l->count >= l->max) && limit_do_action(l, data))
    return 1;

  l->count++;
  return 0;
}

static inline void limit_pop(struct limit *l)
{
  --l->count;
}

static inline void limit_reset(struct limit *l)
{
  l->count = 0;
}

static inline void limit_update(struct limit *l, void *data, u32 max)
{
  if (l->count > (l->max = max))
    limit_do_action(l, data);
}

#endif
+115 −96
Original line number Diff line number Diff line
@@ -52,9 +52,9 @@ static void channel_request_reload(struct channel *c);
static void proto_shutdown_loop(timer *);
static void proto_rethink_goal(struct proto *p);
static char *proto_state_name(struct proto *p);
static void channel_verify_limits(struct channel *c);
static inline void channel_reset_limit(struct channel_limit *l);

static void channel_init_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf);
static void channel_update_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf);
static void channel_reset_limit(struct channel *c, struct limit *l, int dir);

static inline int proto_is_done(struct proto *p)
{ return (p->proto_state == PS_DOWN) && (p->active_channels == 0); }
@@ -168,9 +168,10 @@ proto_add_channel(struct proto *p, struct channel_config *cf)

  c->in_filter = cf->in_filter;
  c->out_filter = cf->out_filter;
  c->rx_limit = cf->rx_limit;
  c->in_limit = cf->in_limit;
  c->out_limit = cf->out_limit;

  channel_init_limit(c, &c->rx_limit, PLD_RX, &cf->rx_limit);
  channel_init_limit(c, &c->in_limit, PLD_IN, &cf->in_limit);
  channel_init_limit(c, &c->out_limit, PLD_OUT, &cf->out_limit);

  c->net_type = cf->net_type;
  c->ra_mode = cf->ra_mode;
@@ -280,14 +281,12 @@ channel_feed_loop(void *ptr)
  }

  /* Reset export limit if the feed ended with acceptable number of exported routes */
  struct channel_limit *l = &c->out_limit;
  if (c->refeeding &&
      (l->state == PLS_BLOCKED) &&
      (c->refeed_count <= l->limit) &&
      (c->export_stats.routes <= l->limit))
      (c->limit_active & (1 << PLD_OUT)) &&
      (c->refeed_count <= c->out_limit.max))
  {
    log(L_INFO "Protocol %s resets route export limit (%u)", c->proto->name, l->limit);
    channel_reset_limit(&c->out_limit);
    log(L_INFO "Protocol %s resets route export limit (%u)", c->proto->name, c->out_limit.max);
    channel_reset_limit(c, &c->out_limit, PLD_OUT);

    /* Continue in feed - it will process routing table again from beginning */
    c->refeed_count = 0;
@@ -461,7 +460,8 @@ channel_stop_export(struct channel *c)
    rt_feed_channel_abort(c);

  c->export_state = ES_DOWN;
  c->export_stats.routes = 0;

  channel_reset_limit(c, &c->out_limit, PLD_OUT);
  bmap_reset(&c->export_map, 1024);
  bmap_reset(&c->export_reject_map, 1024);
}
@@ -556,9 +556,9 @@ channel_do_start(struct channel *c)
  memset(&c->export_stats, 0, sizeof(struct export_stats));
  memset(&c->import_stats, 0, sizeof(struct import_stats));

  channel_reset_limit(&c->rx_limit);
  channel_reset_limit(&c->in_limit);
  channel_reset_limit(&c->out_limit);
  channel_reset_limit(c, &c->rx_limit, PLD_RX);
  channel_reset_limit(c, &c->in_limit, PLD_IN);
  channel_reset_limit(c, &c->out_limit, PLD_OUT);

  CALL(c->channel->start, c);
}
@@ -604,8 +604,8 @@ channel_do_down(struct channel *c)
  rt_unlock_table(c->table);
  c->proto->active_channels--;

  if ((c->import_stats.routes + c->import_stats.filtered) != 0)
    log(L_ERR "%s: Channel %s is down but still has some routes", c->proto->name, c->name);
  if (c->in_limit.count || c->rx_limit.count)
    bug("%s: Channel %s is down but still has some routes", c->proto->name, c->name);

  // bmap_free(&c->export_map);
  memset(&c->import_stats, 0, sizeof(struct import_stats));
@@ -748,8 +748,8 @@ channel_request_reload(struct channel *c)
   * Should this be done before reload_routes() hook?
   * Perhaps, but routes are updated asynchronously.
   */
  channel_reset_limit(&c->rx_limit);
  channel_reset_limit(&c->in_limit);
  channel_reset_limit(c, &c->rx_limit, PLD_RX);
  channel_reset_limit(c, &c->in_limit, PLD_IN);
}

const struct channel_class channel_basic = {
@@ -852,9 +852,10 @@ channel_reconfigure(struct channel *c, struct channel_config *cf)
  /* Reconfigure channel fields */
  c->in_filter = cf->in_filter;
  c->out_filter = cf->out_filter;
  c->rx_limit = cf->rx_limit;
  c->in_limit = cf->in_limit;
  c->out_limit = cf->out_limit;

  channel_update_limit(c, &c->rx_limit, PLD_RX, &cf->rx_limit);
  channel_update_limit(c, &c->in_limit, PLD_IN, &cf->in_limit);
  channel_update_limit(c, &c->out_limit, PLD_OUT, &cf->out_limit);

  // c->ra_mode = cf->ra_mode;
  c->merge_limit = cf->merge_limit;
@@ -863,8 +864,6 @@ channel_reconfigure(struct channel *c, struct channel_config *cf)
  c->in_keep_filtered = cf->in_keep_filtered;
  c->rpki_reload = cf->rpki_reload;

  channel_verify_limits(c);

  /* Execute channel-specific reconfigure hook */
  if (c->channel->reconfigure && !c->channel->reconfigure(c, cf, &import_changed, &export_changed))
    return 0;
@@ -1785,88 +1784,104 @@ proto_set_message(struct proto *p, char *msg, int len)
}


static const char *
channel_limit_name(struct channel_limit *l)
{
  const char *actions[] = {
static const char * channel_limit_name[] = {
  [PLA_WARN] = "warn",
  [PLA_BLOCK] = "block",
  [PLA_RESTART] = "restart",
  [PLA_DISABLE] = "disable",
};

  return actions[l->action];
}

/**
 * channel_notify_limit: notify about limit hit and take appropriate action
 * @c: channel
 * @l: limit being hit
 * @dir: limit direction (PLD_*)
 * @rt_count: the number of routes
 *
 * The function is called by the route processing core when limit @l
 * is breached. It activates the limit and tooks appropriate action
 * according to @l->action.
 */
void
channel_notify_limit(struct channel *c, struct channel_limit *l, int dir, u32 rt_count)
static void
channel_log_limit(struct channel *c, struct limit *l, int dir)
{
  const char *dir_name[PLD_MAX] = { "receive", "import" , "export" };
  const byte dir_down[PLD_MAX] = { PDC_RX_LIMIT_HIT, PDC_IN_LIMIT_HIT, PDC_OUT_LIMIT_HIT };
  struct proto *p = c->proto;
  log(L_WARN "Channel %s.%s hits route %s limit (%d), action: %s",
      c->proto->name, c->name, dir_name[dir], l->max, channel_limit_name[c->limit_actions[dir]]);
}

  if (l->state == PLS_BLOCKED)
static void
channel_activate_limit(struct channel *c, struct limit *l, int dir)
{
  if (c->limit_active & (1 << dir))
    return;

  /* For warning action, we want the log message every time we hit the limit */
  if (!l->state || ((l->action == PLA_WARN) && (rt_count == l->limit)))
    log(L_WARN "Protocol %s hits route %s limit (%d), action: %s",
	p->name, dir_name[dir], l->limit, channel_limit_name(l));
  c->limit_active |= (1 << dir);
  channel_log_limit(c, l, dir);
}

  switch (l->action)
static int
channel_limit_warn(struct limit *l, void *data)
{
  case PLA_WARN:
    l->state = PLS_ACTIVE;
    break;
  struct channel_limit_data *cld = data;
  struct channel *c = cld->c;
  int dir = cld->dir;

  case PLA_BLOCK:
    l->state = PLS_BLOCKED;
    break;
  channel_log_limit(c, l, dir);

  case PLA_RESTART:
  case PLA_DISABLE:
    l->state = PLS_BLOCKED;
    if (p->proto_state == PS_UP)
      proto_schedule_down(p, l->action == PLA_RESTART, dir_down[dir]);
    break;
  return 0;
}

static int
channel_limit_block(struct limit *l, void *data)
{
  struct channel_limit_data *cld = data;
  struct channel *c = cld->c;
  int dir = cld->dir;

  channel_activate_limit(c, l, dir);

  return 1;
}

static void
channel_verify_limits(struct channel *c)
static const byte chl_dir_down[PLD_MAX] = { PDC_RX_LIMIT_HIT, PDC_IN_LIMIT_HIT, PDC_OUT_LIMIT_HIT };

static int
channel_limit_down(struct limit *l, void *data)
{
  struct channel_limit *l;
  u32 all_routes = c->import_stats.routes + c->import_stats.filtered;
  struct channel_limit_data *cld = data;
  struct channel *c = cld->c;
  struct proto *p = c->proto;
  int dir = cld->dir;

  l = &c->rx_limit;
  if (l->action && (all_routes > l->limit))
    channel_notify_limit(c, l, PLD_RX, all_routes);
  channel_activate_limit(c, l, dir);

  l = &c->in_limit;
  if (l->action && (c->import_stats.routes > l->limit))
    channel_notify_limit(c, l, PLD_IN, c->import_stats.routes);
  if (p->proto_state == PS_UP)
    proto_schedule_down(p, c->limit_actions[dir] == PLA_RESTART, chl_dir_down[dir]);

  l = &c->out_limit;
  if (l->action && (c->export_stats.routes > l->limit))
    channel_notify_limit(c, l, PLD_OUT, c->export_stats.routes);
  return 1;
}

static inline void
channel_reset_limit(struct channel_limit *l)
static int (*channel_limit_action[])(struct limit *, void *) = {
  [PLA_NONE] = NULL,
  [PLA_WARN] = channel_limit_warn,
  [PLA_BLOCK] = channel_limit_block,
  [PLA_RESTART] = channel_limit_down,
  [PLA_DISABLE] = channel_limit_down,
};

static void
channel_update_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf)
{
  l->action = channel_limit_action[cf->action];
  c->limit_actions[dir] = cf->action;

  struct channel_limit_data cld = { .c = c, .dir = dir };
  limit_update(l, &cld, cf->action ? cf->limit : ~((u32) 0));
}

static void
channel_init_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf)
{
  channel_reset_limit(c, l, dir);
  channel_update_limit(c, l, dir, cf);
}

static void
channel_reset_limit(struct channel *c, struct limit *l, int dir)
{
  if (l->action)
    l->state = PLS_INITIAL;
  limit_reset(l);
  c->limit_active &= ~(1 << dir);
}

static inline void
@@ -2017,12 +2032,16 @@ channel_show_stats(struct channel *c)
  struct import_stats *is = &c->import_stats;
  struct export_stats *es = &c->export_stats;

  u32 rx_routes = c->rx_limit.count;
  u32 in_routes = c->in_limit.count;
  u32 out_routes = c->out_limit.count;

  if (c->in_keep_filtered)
    cli_msg(-1006, "    Routes:         %u imported, %u filtered, %u exported, %u preferred",
	    is->routes, is->filtered, es->routes, is->pref);
	    in_routes, (rx_routes - in_routes), out_routes, is->pref);
  else
    cli_msg(-1006, "    Routes:         %u imported, %u exported, %u preferred",
	    is->routes, es->routes, is->pref);
	    in_routes, out_routes, is->pref);

  cli_msg(-1006, "    Route change stats:     received   rejected   filtered    ignored   accepted");
  cli_msg(-1006, "      Import updates:     %10u %10u %10u %10u %10u",
@@ -2040,13 +2059,13 @@ channel_show_stats(struct channel *c)
}

void
channel_show_limit(struct channel_limit *l, const char *dsc)
channel_show_limit(struct limit *l, const char *dsc, int active, int action)
{
  if (!l->action)
    return;

  cli_msg(-1006, "    %-16s%d%s", dsc, l->limit, l->state ? " [HIT]" : "");
  cli_msg(-1006, "      Action:       %s", channel_limit_name(l));
  cli_msg(-1006, "    %-16s%d%s", dsc, l->max, active ? " [HIT]" : "");
  cli_msg(-1006, "      Action:       %s", channel_limit_name[action]);
}

void
@@ -2064,9 +2083,9 @@ channel_show_info(struct channel *c)
	    c->gr_lock ? " pending" : "",
	    c->gr_wait ? " waiting" : "");

  channel_show_limit(&c->rx_limit, "Receive limit:");
  channel_show_limit(&c->in_limit, "Import limit:");
  channel_show_limit(&c->out_limit, "Export limit:");
  channel_show_limit(&c->rx_limit, "Receive limit:", c->limit_active & (1 << PLD_RX), c->limit_actions[PLD_RX]);
  channel_show_limit(&c->in_limit, "Import limit:", c->limit_active & (1 << PLD_IN), c->limit_actions[PLD_IN]);
  channel_show_limit(&c->out_limit, "Export limit:", c->limit_active & (1 << PLD_OUT), c->limit_actions[PLD_OUT]);

  if (c->channel_state != CS_DOWN)
    channel_show_stats(c);
+27 −13
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#include "lib/resource.h"
#include "lib/event.h"
#include "nest/route.h"
#include "nest/limit.h"
#include "conf/conf.h"

struct iface;
@@ -134,8 +135,6 @@ struct proto_config {
/* Protocol statistics */
struct import_stats {
  /* Import - from protocol to core */
  u32 routes;			/* Number of routes successfully imported to the (adjacent) routing table */
  u32 filtered;			/* Number of routes rejected in import filter but kept in the routing table */
  u32 pref;			/* Number of routes selected as best in the (adjacent) routing table */
  u32 updates_received;		/* Number of route updates received */
  u32 updates_invalid;		/* Number of route updates rejected as invalid */
@@ -150,7 +149,6 @@ struct import_stats {

struct export_stats {
  /* Export - from core to protocol */
  u32 routes;			/* Number of routes successfully exported to the protocol */
  u32 updates_received;		/* Number of route updates received */
  u32 updates_rejected;		/* Number of route updates rejected by protocol */
  u32 updates_filtered;		/* Number of route updates rejected by filters */
@@ -277,7 +275,7 @@ void channel_graceful_restart_unlock(struct channel *c);

#define DEFAULT_GR_WAIT	240

void channel_show_limit(struct channel_limit *l, const char *dsc);
void channel_show_limit(struct limit *l, const char *dsc, int active, int action);
void channel_show_info(struct channel *c);
void channel_cmd_debug(struct channel *c, uint mask);

@@ -432,18 +430,29 @@ extern struct proto_config *cf_dev_proto;
#define PLA_RESTART	4	/* Force protocol restart */
#define PLA_DISABLE	5	/* Shutdown and disable protocol */

#define PLS_INITIAL	0	/* Initial limit state after protocol start */
#define PLS_ACTIVE	1	/* Limit was hit */
#define PLS_BLOCKED	2	/* Limit is active and blocking new routes */

struct channel_limit {
  u32 limit;			/* Maximum number of prefixes */
  u8 action;			/* Action to take (PLA_*) */
  u8 state;			/* State of limit (PLS_*) */
};

void channel_notify_limit(struct channel *c, struct channel_limit *l, int dir, u32 rt_count);
struct channel_limit_data {
  struct channel *c;
  int dir;
};

#define CLP__RX(_c) (&(_c)->rx_limit)
#define CLP__IN(_c) (&(_c)->in_limit)
#define CLP__OUT(_c) (&(_c)->out_limit)


#if 0
#define CHANNEL_LIMIT_LOG(_c, _dir, _op)  log(L_TRACE "%s.%s: %s limit %s %u", (_c)->proto->name, (_c)->name, #_dir, _op, (CLP__##_dir(_c))->count)
#else
#define CHANNEL_LIMIT_LOG(_c, _dir, _op)
#endif

#define CHANNEL_LIMIT_PUSH(_c, _dir)  ({ CHANNEL_LIMIT_LOG(_c, _dir, "push from"); struct channel_limit_data cld = { .c = (_c), .dir = PLD_##_dir }; limit_push(CLP__##_dir(_c), &cld); })
#define CHANNEL_LIMIT_POP(_c, _dir)   ({ limit_pop(CLP__##_dir(_c)); CHANNEL_LIMIT_LOG(_c, _dir, "pop to"); })

/*
 *	Channels
@@ -486,6 +495,7 @@ struct channel_config {
  struct proto_config *parent;		/* Where channel is defined (proto or template) */
  struct rtable_config *table;		/* Table we're attached to */
  const struct filter *in_filter, *out_filter; /* Attached filters */

  struct channel_limit rx_limit;	/* Limit for receiving routes from protocol
					   (relevant when in_keep_filtered is active) */
  struct channel_limit in_limit;	/* Limit for importing routes from protocol */
@@ -513,9 +523,13 @@ struct channel {
  const struct filter *out_filter;	/* Output filter */
  struct bmap export_map;		/* Keeps track which routes were really exported */
  struct bmap export_reject_map;	/* Keeps track which routes were rejected by export filter */
  struct channel_limit rx_limit;	/* Receive limit (for in_keep_filtered) */
  struct channel_limit in_limit;	/* Input limit */
  struct channel_limit out_limit;	/* Output limit */

  struct limit rx_limit;		/* Receive limit (for in_keep_filtered) */
  struct limit in_limit;		/* Input limit */
  struct limit out_limit;		/* Output limit */

  u8 limit_actions[PLD_MAX];		/* Limit actions enum */
  u8 limit_active;			/* Flags for active limits */

  struct event *feed_event;		/* Event responsible for feeding */
  struct fib_iterator feed_fit;		/* Routing table iterator used during feeding */
+58 −80
Original line number Diff line number Diff line
@@ -473,20 +473,16 @@ do_rt_notify(struct channel *c, const net_addr *net, rte *new, rte *old, int ref
  if (refeed && new)
    c->refeed_count++;

  /* Apply export limit */
  struct channel_limit *l = &c->out_limit;
  if (l->action && !old && new)
  {
    if (stats->routes >= l->limit)
      channel_notify_limit(c, l, PLD_OUT, stats->routes);

    if (l->state == PLS_BLOCKED)
  if (!old && new)
    if (CHANNEL_LIMIT_PUSH(c, OUT))
    {
      stats->updates_rejected++;
      rte_trace_out(D_FILTERS, c, new, "rejected [limit]");
      return;
    }
  }

  if (!new && old)
    CHANNEL_LIMIT_POP(c, OUT);

  /* Apply export table */
  struct rte_storage *old_exported = NULL;
@@ -505,16 +501,10 @@ do_rt_notify(struct channel *c, const net_addr *net, rte *new, rte *old, int ref
    stats->withdraws_accepted++;

  if (old)
  {
    bmap_clear(&c->export_map, old->id);
    stats->routes--;
  }

  if (new)
  {
    bmap_set(&c->export_map, new->id);
    stats->routes++;
  }

  if (p->debug & D_ROUTES)
  {
@@ -973,15 +963,10 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
  int new_ok = rte_is_ok(new);
  int old_ok = rte_is_ok(old);

  struct channel_limit *l = &c->rx_limit;
  if (l->action && !old && new && !c->in_table)
  if (!c->in_table)
  {
      u32 all_routes = stats->routes + stats->filtered;

      if (all_routes >= l->limit)
	channel_notify_limit(c, l, PLD_RX, all_routes);

      if (l->state == PLS_BLOCKED)
    if (!old && new)
      if (CHANNEL_LIMIT_PUSH(c, RX))
      {
	/* In receive limit the situation is simple, old is NULL so
	   we just free new and exit like nothing happened */
@@ -990,15 +975,13 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
	rte_trace_in(D_FILTERS, c, new, "ignored [limit]");
	return;
      }
    }

  l = &c->in_limit;
  if (l->action && !old_ok && new_ok)
    {
      if (stats->routes >= l->limit)
	channel_notify_limit(c, l, PLD_IN, stats->routes);
    if (old && !new)
      CHANNEL_LIMIT_POP(c, RX);
  }

      if (l->state == PLS_BLOCKED)
  if (!old_ok && new_ok)
    if (CHANNEL_LIMIT_PUSH(c, IN))
    {
      /* In import limit the situation is more complicated. We
	 shouldn't just drop the route, we should handle it like
@@ -1024,7 +1007,9 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
      new_ok = 0;
      goto skip_stats1;
    }
    }

  if (old_ok && !new_ok)
    CHANNEL_LIMIT_POP(c, IN);

  if (new_ok)
    stats->updates_accepted++;
@@ -1039,11 +1024,6 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
 skip_stats1:;
  struct rte_storage *new_stored = new ? rte_store(new, net, table) : NULL;

  if (new)
    rte_is_filtered(new) ? stats->filtered++ : stats->routes++;
  if (old)
    rte_is_filtered(old) ? stats->filtered-- : stats->routes--;

  if (table->config->sorted)
    {
      /* If routes are sorted, just insert new route to appropriate position */
@@ -2332,6 +2312,9 @@ rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *sr
	goto drop_update;
      }

      if (!new)
	CHANNEL_LIMIT_POP(c, RX);

      /* Move iterator if needed */
      if (*pos == c->reload_next_rte)
	c->reload_next_rte = (*pos)->next;
@@ -2342,24 +2325,9 @@ rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *sr
      rte_free(del, tab);
      tab->rt_count--;
    }
  else if (!new)
    goto drop_withdraw;

  if (!new)
  {
    if (!net->routes)
      fib_delete(&tab->fib, net);

    return 1;
  }

  struct channel_limit *l = &c->rx_limit;
  if (l->action && !*pos)
  else if (new)
    {
    if (tab->rt_count >= l->limit)
      channel_notify_limit(c, l, PLD_RX, tab->rt_count);

    if (l->state == PLS_BLOCKED)
      if (CHANNEL_LIMIT_PUSH(c, RX))
      {
	/* Required by rte_trace_in() */
	new->net = n;
@@ -2368,6 +2336,16 @@ rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *sr
	goto drop_update;
      }
    }
  else
    goto drop_withdraw;

  if (!new)
  {
    if (!net->routes)
      fib_delete(&tab->fib, net);

    return 1;
  }

  /* Insert the new rte */
  struct rte_storage *e = rte_store(new, net, tab);
+10 −3
Original line number Diff line number Diff line
@@ -217,6 +217,9 @@ pipe_show_stats(struct pipe_proto *p)
  struct import_stats *s2i = &p->sec->import_stats;
  struct export_stats *s2e = &p->sec->export_stats;

  u32 pri_routes = p->pri->in_limit.count;
  u32 sec_routes = p->sec->in_limit.count;

  /*
   * Pipe stats (as anything related to pipes) are a bit tricky. There
   * are two sets of stats - s1 for ahook to the primary routing and
@@ -239,7 +242,7 @@ pipe_show_stats(struct pipe_proto *p)
   */

  cli_msg(-1006, "  Routes:         %u imported, %u exported",
	  s1i->routes, s2i->routes);
	  pri_routes, sec_routes);
  cli_msg(-1006, "  Route change stats:     received   rejected   filtered    ignored   accepted");
  cli_msg(-1006, "    Import updates:     %10u %10u %10u %10u %10u",
	  s2e->updates_received, s2e->updates_rejected + s1i->updates_invalid,
@@ -270,8 +273,12 @@ pipe_show_proto_info(struct proto *P)
  cli_msg(-1006, "    Import filter:  %s", filter_name(p->sec->out_filter));
  cli_msg(-1006, "    Export filter:  %s", filter_name(p->pri->out_filter));

  channel_show_limit(&p->pri->in_limit, "Import limit:");
  channel_show_limit(&p->sec->in_limit, "Export limit:");


  channel_show_limit(&p->pri->in_limit, "Import limit:",
      (p->pri->limit_active & (1 << PLD_IN)), p->pri->limit_actions[PLD_IN]);
  channel_show_limit(&p->sec->in_limit, "Export limit:",
      (p->sec->limit_active & (1 << PLD_IN)), p->sec->limit_actions[PLD_IN]);

  if (P->proto_state != PS_DOWN)
    pipe_show_stats(p);